M5StickCであそぶ 〜赤外線LEDを使う〜

前回無事にボタン&LEDの操作ができましたので、今回は赤外線LEDを使用してみます。赤外線LEDの機能検証で面倒なところは、プログラムを書いてみても、パッと見ではちゃんと発光しているのかしていないのかがわからないというところです。ということで、今回は検証用の赤外線の受信機の方も一緒に作ってしまいます。

前回も書きましたが、赤外線LEDは右下にある2つの穴の上側に組み込まれています。ピン配列で言うと、GPIO09が割り当てられてます。

赤外線LEDの用途で最も多いのは、おそらく赤外線信号の送信による家電の操作ではないかと思います。そのためのライブラリもおそらく色々あるのではないかなと思うのですが、今回はそういったライブラリは使わず、シンプルに「整数値(1 Byte)を送信する」というサンプルプログラムにしようと思います。家電と関係ない玩具をつくるときなどには、むしろこちらの方が役に立つと思うので。M5StickC向けに書いていますが、普通のArduinoにも使えるプログラムになっていると思います。

プログラム自体は『ZXライドウォッチ』を作成したときにほぼ書いてしまっているので、そこから必要な部分を抜き出してくる形になります。注意点としては、以下のプログラムは送信側/受信側共に、

こちらの赤外線受信モジュールを使用することを前提としている、ということです。おそらく家電によく組み込まれているモジュールで入手性は良いと思うので、特に問題はないかと思います。おそらくですが、中心周波数が38kHzであれば、他の赤外線受信モジュールでも動作すると思います。

それではまず、M5StickC側(送信側)のプログラムです。ボタンAを押すごとに整数値を+1ずつしながらその整数値を送信、ボタンBで整数値をリセット、としてみました。

#include <M5StickC.h>

#define BTN_A_PIN  37
#define BTN_B_PIN  39
#define IR_LED_PIN  9

// この赤外線LEDは、GPIO9の電位を下げることで発光するタイプ
#define IR_LED_ON  LOW
#define IR_LED_OFF HIGH

// INPUT_PULLUPが有効かは不明だが、有効という前提で定義
#define BTN_ON  LOW
#define BTN_OFF HIGH

uint8_t prev_btn_a = BTN_OFF;
uint8_t btn_a      = BTN_OFF;
uint8_t prev_btn_b = BTN_OFF;
uint8_t btn_b      = BTN_OFF;

uint8_t counter = 0;

////////////////////////// IR送信関係 ////////////////////////// 

// 対向の赤外線受信モジュール(OSRB38C9AA)の中心周波数
// (キャリア周波数)が37.9kHzなので、それに応じて定数を定める

// IR_SHORT_DURATION_MICRO_SECは、
// 16MHzのArduinoなら10、8MHzのArduinoなら5ぐらい。
// M5StickCはデフォルトだと240MHzで動くので、12ぐらいにしてよい
#define IR_SHORT_DURATION_MICRO_SEC  12

#define IR_LONG_DURATION_MICRO_SEC  600
#define NUM_IR_ON_OFF                23 // 26μs/回 * 23回 = 598μs

#define LEN_IR_DATA  8 // 今回は8bitで表現される整数値をおくる

void send_bit_0(){
  digitalWrite(IR_LED_PIN, LOW);
  delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
}

void send_bit_1(){
  // 1回のループでおよそ26μsになるようにIR_SHORT_DURATION_MICRO_SECを設定。
  // (←うまく動かないときはまずこの値を調整する)
  // ループの全体でおよそIR_LONG_DURATION_MICRO_SEC(=600)になるようにする
  for(uint8_t i=0;i<NUM_IR_ON_OFF;i++){
    digitalWrite(IR_LED_PIN, HIGH);
    delayMicroseconds(IR_SHORT_DURATION_MICRO_SEC);
    digitalWrite(IR_LED_PIN, LOW);
    delayMicroseconds(IR_SHORT_DURATION_MICRO_SEC);
  }
}

void send_ir(uint8_t number){
  // スタートビットの送信
  send_bit_1();

  // 整数値(8bit)の送信
  for(uint8_t i=0;i<LEN_IR_DATA;i++){
    uint8_t send_bit = bitRead(number, i); // 8bitの右端から送信する
    if(send_bit == 1){
      send_bit_1();
    }else{
      send_bit_0();
    }
  }

  // ストップビットの送信
  send_bit_1();
  send_bit_0();
  send_bit_1();
  send_bit_0();

  // 送信完了表示
  M5.Lcd.setCursor(0,0);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.printf("Sent %d", number);
}

///////////////////////////////////////////////////////////////

void setup() {
  // Initialize the M5StickC object
  M5.begin();
  pinMode(BTN_A_PIN,  INPUT_PULLUP);
  pinMode(BTN_B_PIN,  INPUT_PULLUP);
  pinMode(IR_LED_PIN, OUTPUT);
  digitalWrite(IR_LED_PIN, IR_LED_OFF);
  // LCD display
  M5.Lcd.setRotation(1); // ボタンBが上になるような向き
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(2); // フォントサイズをデフォルトの2倍に
  M5.Lcd.print("IR Test");
}

void loop() {
  btn_a = digitalRead(BTN_A_PIN);
  btn_b = digitalRead(BTN_B_PIN);

  if(prev_btn_a == BTN_OFF && btn_a == BTN_ON){
    // ボタンAが押されたとき。1回ごとに、整数値を+1ずつ増やしながら送信する
    counter += 1;
    send_ir(counter);
  }

  if(prev_btn_b == BTN_OFF && btn_b == BTN_ON){
    // ボタンBが押されたとき。送信する整数値をリセットする
    counter = 0;
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0,0);
    M5.Lcd.print("Reset counter");
  }

  prev_btn_a = btn_a;
  prev_btn_b = btn_b;
}

続いて、受信器側です。こちらはArduino Unoを使用して、以下のように作成しました。とてもシンプルです。

プログラムはこちらになります。M5StickCから送信された整数値の数だけ、LEDが点滅するようにしています。

#define IR_RX_PIN 2
#define LED_PIN   3

////////////////////////// IR受信関係 //////////////////////////
#define IR_ON  LOW
#define IR_OFF HIGH

#define LEN_IR_DATA  8

// 使用する赤外線受信モジュール(OSRB38C9AA)の中心周波数
// (キャリア周波数)が37.9kHzなので、それに応じて定数を定める
#define IR_LONG_DURATION_MICRO_SEC 600 

///////////////////////////////////////////////////////////////

void setup() {
  pinMode(IR_RX_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);

  digitalWrite(LED_PIN, LOW); // 回転認識スイッチが押されている状態を想定
}

void loop() {
  while(1){ // 常にIR信号の受信待ちにする

    // スタートビット(1)チェック
    if(digitalRead(IR_RX_PIN) == IR_ON){ // とりあえず何かしらのIR信号を受信した
      delayMicroseconds(400);
    }else{
      break; // 何もIR信号を受信していないければ、ここで終了
    }

    if(digitalRead(IR_RX_PIN) == IR_ON){ // 400μs待ってまたスタートビットの信号が続いているかチェックする
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }else{
      break; // 受信した信号はスタートビットではなかったので、ここで終了
    }

    // スタートビットが確認できたので、送信された整数値を読み取る
    uint8_t number = 0;
    for(uint8_t i=0;i<LEN_IR_DATA;i++){
      number = number | (!digitalRead(IR_RX_PIN) << i); // 8bitの右端から受信する
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }

    // ストップビット(1010)チェック
    if(digitalRead(IR_RX_PIN) == IR_ON){
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }else{
      break; // ストップビットが不正なので、ここで終了
    }
    if(digitalRead(IR_RX_PIN) == IR_OFF){
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }else{
      break; // ストップビットが不正なので、ここで終了
    }
    if(digitalRead(IR_RX_PIN) == IR_ON){
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }else{
      break; // ストップビットが不正なので、ここで終了
    }
    if(digitalRead(IR_RX_PIN) == IR_OFF){
      delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
    }else{
      break; // ストップビットが不正なので、ここで終了
    }

    // ストップビットまで確認できたので、読み取った値の数だけLEDを点滅させる
    for(uint8_t i=0;i<number;i++){
      digitalWrite(LED_PIN, HIGH);
      delay(200);
      digitalWrite(LED_PIN, LOW);
      delay(200);
    }
  }

}

それでは動作確認です。

 

うん、うまく動いてくれています。ただ、赤外線の処理自体は問題なさそうですが、ボタンAを押したときにカウントが一気に連続で進んでしまうことがあったので、ちゃんとやりたいならボタンのチャリング防止的な処理は入れてあげた方がよいかもしれません。

ちなみ、どこまでいけるかは正確には測っていませんが、少なくとも受信機とM5StickCの距離を2〜3mぐらい離しても問題なく動作していました。

 

ということで、M5StickCの赤外線LEDを試してみました。今回作っていて強く思いましたが、本体にディスプレイがついていて都度デバッグ情報を表示できるというのが、開発向けとしては本当にラクで助かります。『ZXライドウォッチ』を作っているときは、赤外線通信がうまくいかないときに送信側が悪いのか受信側が悪いのかの切り分けで相当苦労したので。。。良いデバイスですね、M5StickC。

次回は、6軸センサあたりを触ってみようかと思います。