1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

バイナリ信号に合わせて任意のビットレートでLEDを光らせるプログラム

Posted at

初めに

光ファイバ通信を専門としている大学院生が、新たに始めた研究の記録のためにこの記事を書くことにしました。
PythonもArduinoもこの研究で初めて触ったようなプログラミング初心者の記事ですが、

  • 自身の知識を深めたい
  • 同じようなことで困っているプログラミング初心者の頼りになりたい

という目的で記事を書きます。ですので、有識者の方の詳しいコメントや、門外漢の方のものすごく初歩的な質問など大歓迎です。お待ちしております。

今回使用したもの

  • WS2812B 16LED 9個
DSC_1442.JPG
  • Arduino(OSOYOO The IoT kit)
    microSDカードスロットDIP化キット等が必要
  • IoTデバイス用バッテリー
  • micro SD

ハードの準備

WS2812Bのはんだ付け

今回の研究では12×12のWS2812Bを60Hzくらいでチカチカさせるのですが、WS2812Bの仕様上12×12のパネルを使わずに、4×4のパネルを9枚合体させたものを使用します。
WS2812Bは電源とは別に信号を送信して光らせるのですが、WS2812Bを使用したLEDパネルではWS2812Bが直列に接続されているため、パネルが大きくなると最初のWS2812Bと最後のWS2812Bで信号が届く時間に差が出てきてしまいます。つまり、最初のLEDは光っているのに、最後のLEDは光ってない時間が発生してしまいます。ですので、今回は複数の小さなLEDパネルに並列に信号を送ることでそのラグを小さくしています。

実際にラグの計算をしてみましょう。
WS2812Bは1パルスが1.25μs(±0.3μs)で、それをRGB各色(×3)で明るさ8bit分(×8)送っているので、1.25×3×8 ≒ 30μs。
それを各LEDに直列で送信するので、12×12のパネル(144個)の時は30×144 = 4320μs = 4.32ms、4×4のパネル(16個)の時は30×16 = 480μs = 0.48ms
つまり、今回の改善策で4.32-0.48 = 3.84msも短縮することができたのです!

60Hzってことは1パルス0.0167s = 16.7ms
うむ…まぁ意味はあったんじゃないですかね? どちらにせよ、半田付け前にちゃんと計算するべきでしたが、今回はしてませんでした…反省。

今回は小さくて、持ち運びやすいモバイルバッテリーを使うことにしました。ただ、普通のモバイルバッテリーだと、過充電防止のためスマホ以外の充電は一定時間経つと中止してしまうみたいで使えませんでした。なのでIoTデバイス用バッテリーを使いました。
(ラジコン用電源は軽くて小さくてずっと給電できていいよって後で知りました…。)

ということで、はんだ付けした結果がこんな感じです。
DSC_1430_afg.JPG
線の長さとか全く計算していなかったので余りまくってるし、LEDの固定に割りばしを使ってるので、見た目は良くないですが、はんだ付け自体はかなり上手にできました。以前エレキギターをいじってたのがこんなところで活きました。

バッテリーと合体させて…
DSC_1427.JPG

完成です!

DSC_1429.JPG
光るとこんな感じ。カッケェ~~~!
色も明るさも変えることができますが、明るさMAXだと目がおかしくなるくらい明るいです。
1599818116531.jpg

SDカードのフォーマット

プログラムが書けてこれで動くだろうと思ったのですが、SDカードから.txtファイルを全く読み込めませんでした。
調べてみた結果、SDライブラリで対応しているSDカードのフォーマットはFAT16もしくはFAT32のみでした。
FAT32でフォーマットしなおしたら無事読み取りできました。
皆さんも気を付けてください。

プログラム

実際に光らせるプログラムを紹介します。
初めに書いたプログラムは、プログラム上に送信したいbit列を打ち込んで、それ通りに光らせるものです。RGBそれぞれに信号をのせて、任意のビットレートで光らせることが可能です。

//RGBで信号をのせてLEDを光らせるプログラム

//ライブラリimport・LED数・出力ピン定義===================================
# include <FastLED.h>
# include <SD.h>
 #define NUM_LEDS 16
 #define DATA_PIN_A 10
 #define DATA_PIN_B 11
 #define DATA_PIN_C 12

// 変数(光らせるLED)定義
CRGB leds_A[NUM_LEDS];
CRGB leds_B[NUM_LEDS];
CRGB leds_C[NUM_LEDS];

//=====================================================================

//バイナリ信号定義
String moji = "012";
char tontu[][2] = {"l", "m", "h"};

//点灯時間の設定
int BitLate = 60; //[bps] //ビットレート指定
int SigTime = 1000/BitLate; //[ms]

//表示したいバイナリ信号を設定
String tar_R = "11111111110001110000"; //送信信号指定
String tar_G = "11111111111110000000"; //同じ長さを想定
String tar_B = "11111111110000001111";

//=====================================================================

void setup() {
      FastLED.addLeds<NEOPIXEL, DATA_PIN_A>(leds_A, NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, DATA_PIN_B>(leds_B, NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, DATA_PIN_C>(leds_C, NUM_LEDS);
}

void loop() {
  for (int i = 0; i < tar_R.length(); i++) { //一文字単位で符号の配列場所読み取り
    int r = moji.indexOf(tar_R[i]); //符号が書かれた文字列の場所が返ってくる(0~)例tar = 1ならs = 1(mのindex)
    int g = moji.indexOf(tar_G[i]);
    int b = moji.indexOf(tar_B[i]);
    showChr(r, g, b);
  }
}

//関数定義===============================================================================
void showChr(int r, int g, int b) { //一文字分の符号読み取り
  int j = 0;
  while (tontu[r][j] != '\0') { //信号長はRGBで等しいものとする(Rに合わせている)
    showHugou(String(tontu[r][j]), String(tontu[g][j]), String(tontu[b][j]));
    //符号単位でshowHugouに投げる(ついでにChar→String変換)
    j++;
  }
}

void showHugou(String chara_r, String chara_g, String chara_b) { //RGBでもらった符号に応じて強度(0~255)を決定し点灯
  int R = 0; //R
  if (chara_r.equals("l")) {//0
   R = 0; //0に対応する強度を決定
  } else if (chara_r.equals("m")) {//1
    R = 125;
  } else if (chara_r.equals("h")) {//2
    R = 255;
  } else {
     delay(0);
  }
  int G = 0;
  if (chara_g.equals("l")) {
   G = 0;
  } else if (chara_g.equals("m")) {//1
    G = 125;
  } else if (chara_g.equals("h")) {//2
    G = 255;
  } else {
     delay(0);
  }
  int B = 0;
  if (chara_b.equals("l")) {
   B = 0;
  } else if (chara_b.equals("m")) {//1k
    B = 125;
  } else if (chara_b.equals("h")) {//2
    B = 255;
  } else {
     delay(0);
  }
  for(int k = 0; k<NUM_LEDS; k++){
    leds_A[k] = leds_B[k] = leds_C[k] = CRGB( R, G, B);//色の指定
  }
  FastLED.show(); //上記で決めたCRGB(R,G,B)で点灯
  delay(SigTime); //点灯持続時間
}
//======================================================================================

プログラムを作る際にこちらのブログを参考にしましたので引用します。(ありがとうございました。)
arduinoでモールス信号を送信させた話(ついでにシリアルでも送信)
明るさも012に対応して3段階に設定することができますし、もっと多レベルにも設定可能です。

今回はRGBそれぞれ10000bit送信する予定ですが、このままだとArduinoのメモリをオーバーしてしまうので、プログラム上直打ちはできません。そこでSDライブラリを利用して、SDカード上の.txtファイルにある信号を読み取りLED光らせるというように改良しました。

//txtファイルから読み取ったRGB信号でLEDを光らせるプログラム

//ライブラリimport・LED数・出力ピン定義===================================
# include <FastLED.h>
# include <SPI.h>
# include <SD.h>
 #define NUM_LEDS 16
 #define DATA_PIN_A 5
 #define DATA_PIN_B 6
 #define DATA_PIN_C 7
const int chipSelect = 4;

// 変数(光らせるLED)定義
CRGB leds_A[NUM_LEDS];
CRGB leds_B[NUM_LEDS];
CRGB leds_C[NUM_LEDS];

//=====================================================================

//バイナリ信号定義
String moji = "012"; //3種類の明度
char tontu[][2] = {"l", "m", "h"};

//点灯時間の設定
int BitLate = 60; //[bps] //ビットレート指定
int SigTime = 1000/BitLate; //[ms]

//表示したいバイナリ信号を設定
txt_path = "TxBit.txt"; //3(RGB)×10000配列
//=====================================================================

void setup() {
      Serial.begin(9600);
      FastLED.addLeds<NEOPIXEL, DATA_PIN_A>(leds_A, NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, DATA_PIN_B>(leds_B, NUM_LEDS);
      FastLED.addLeds<NEOPIXEL, DATA_PIN_C>(leds_C, NUM_LEDS);
      SD.begin(chipSelect);
      pinMode(8, OUTPUT);
}

void loop() {
  File fp = SD.open(txt_path, FILE_READ);
  if(fp){
    while(fp.available()) {
      
      char Red = fp.read(); //3n番目
      char Green = fp.read(); //3n+1番目
      char Blue = fp.read(); //3n+2番目
      Serial.print(fp);

      int r = moji.indexOf(Red); //符号が書かれた文字列の場所が返ってくる(0~)例Red = 1ならr = 1(mのindex)
      int g = moji.indexOf(Green);
      int b = moji.indexOf(Blue);
      showChr(r, g, b);
    }
    fp.close();
  }
}

//関数定義===============================================================================
void showChr(int r, int g, int b) { //一文字分の符号読み取り
  int j = 0;
  while (tontu[r][j] != '\0') { //信号長はRGBで等しいものとする(Rに合わせている)
    showHugou(String(tontu[r][j]), String(tontu[g][j]), String(tontu[b][j]));
    //符号単位でshowHugouに投げる(ついでにChar→String変換)
    j++;
  }
}

void showHugou(String chara_r, String chara_g, String chara_b) { //RGBでもらった符号に応じて強度(0~255)を決定し点灯
  int R = 0; //R
  if (chara_r.equals("l")) {//0
   R = 0; //0に対応する強度を決定
  } else if (chara_r.equals("m")) {//1
    R = 125;
  } else if (chara_r.equals("h")) {//2
    R = 255;
  } else {
     delay(0);
  }
  int G = 0;
  if (chara_g.equals("l")) {
   G = 0;
  } else if (chara_g.equals("m")) {//1
    G = 125;
  } else if (chara_g.equals("h")) {//2
    G = 255;
  } else {
     delay(0);
  }
  int B = 0;
  if (chara_b.equals("l")) {
   B = 0;
  } else if (chara_b.equals("m")) {//1k
    B = 125;
  } else if (chara_b.equals("h")) {//2
    B = 255;
  } else {
     delay(0);
  }
  for(int k = 0; k<NUM_LEDS; k++){
    leds_A[k] = leds_B[k] = leds_C[k] = CRGB( R, G, B);//色の指定
  }
  FastLED.show(); //上記で決めたCRGB(R,G,B)で点灯
  delay(SigTime); //点灯持続時間
}
//======================================================================================

この送信信号TxBit.txtは1行に|RGB|RGB|RGB|RGB…と信号を並べた.txtファイルで、慣れているMATLABで作りました。

例えば、
R:111000
G:111111
B:101010
→TxBit.txt:111110111010011010
となります。

TxBit.txtはSDカードのルート直下に入れておきます。

終わりに

思ったより長くなってしまいました…
アウトプットする機会があるというのはすごく良いことですね。改めて自分の考えを整理することができて良かったです。
コードに関して、自分で追加したところだけが長くてダサいです…。もっとうまくコーディングできるようになりたいですね。

また、時間ができて落ち着いたときに続きを更新していこうと思いますのでよろしくお願いします。
最後まで読んでいただきありがとうございました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?