M5Stack 赤外線利用で時間計測

2024.5.24 Coskx Lab  

1 はじめに

カートなどがスタートラインからスタートし,スタートラインと同じ位置にあるゴールラインをカートが越えるまでの時間を測定することが目的です。



図のように赤外線の発光受光を行い,カートが移動するときに赤外光路を遮断することを利用し,1回目の遮断のはじめから,2回目の遮断のおわりまでの時間を測定します。
(1回目の遮断のはじめから,2回目の遮断のはじめまでの時間を測定することも出来るでしょう)



使用するのは赤外LED(light emitting diode)と赤外線受光モジュールです。

2 使用環境

3 赤外線受光モジュール

赤外線受光モジュールのデータシート
赤外線受光モジュールは,家庭電化製品(テレビなど)の赤外線リモコンに使用されているセンサです。
赤外線発光器以外からの赤外線を含む光に反応しないように,赤外線受光モジュールは38kHzで強度変化(ON-OFFでよい)のある赤外線(38kHz変調赤外線)に対して反応するようになっています。
(テレビなどのリモコンではリモコンボタンを押すと,38kHz変調赤外線の有無のパターンの一連の信号が送られます。)

データシート(赤外線受光モジュール OSRB38C9AA)の動作図を見ると,赤外線LED側は38kHz変調赤外線を発したり休止したりしています。38kHz変調赤外線を発している期間も,休止している期間もそれぞれ600μsになっています。
それに対応して,赤外線受光モジュールは38kHz変調赤外線を受光しているときはLを,38kHz変調赤外線を受光していないときはHを,モジュール出力にしています。(負論理)
しかも,100msecが1ブロックとなっていて,ブロックの最後はある長さの休止期間が必要となっています。
この休止区間(長めの非受光期間)はリモコンボタンの一連の信号パターンの区切りを表すと同時に,受光モジュールのAGC(Automatic Gain Control)に無信号レベルをセットするのに使われているようです。


   データシート(赤外線受光モジュール OSRB38C9AA)にある動作図

赤外線受光モジュールのテスト
赤外線受光モジュール OSRB38C9AAで次のテストをしました。
(1) 赤外線LEDから600μsの38kHz変調赤外線発光と600μsの休止区間を連続して与え続けたところ,最初の0.1秒程度は赤外線受光モジュールが受光検知の信号を見せますが,その後,受光しているはずなのに非受光状態を示す信号を発するようになりました。
長めの休止区間(非受光期間)がないため,AGCのレベルセットが出来ず誤動作に至ったものと考えられます。
(2) 次の図に示すように,600μsの休止区間を挟んで,600μsの38kHz変調赤外線を2回与える動作を25msec周期で与える動作をしたところ,(約23msecの非受光区間を与えたことになります)光路に遮蔽物があるか無しかを正しくとらえることが出来るようになりました。



     テストした25msec周期の駆動パターン(1周期に2回の受光区間)


テスト結果からの指針
(2)の方式では,連続して正常な動作を行いますが,25msec毎の遮光物検査しかできません。遮光物検査の時間分解能は25msecになります。

(1)で起動直後の短かな時間では正しく動作することを利用して,遮光物検査の度に赤外線受光モジュールに通電し,検査が終わったら通電をやめる方式では,5msec毎の遮光物検査が出来ることがわかりました。
この方式では,遮光物検査の度に赤外線受光モジュールに通電し,1500μs待ってから,600μsの短い休止区間を挟んで,赤外線LEDから600μsの38kHz変調赤外線を2回与える動作になります。(2回の検査で信頼性を上げます。)また,遮光物検査の時間分解能は5msecになります。
こちらの方式を採用することにしました。



4 M5stackピン割り当てと回路

M5stackピン割り当て
M5Stackは,
(1)赤外LEDの点滅のために38kHz変調赤外線生成
(2)赤外線受光モジュールへ通電コントロール
(3)赤外線受光モジュールからの信号を受け取り
を行います。

赤外LEDの点滅のためにPWM出力できる出力ピンとして,デジタル出力ピンGPIO2を割り当てます。

赤外線受光モジュールの仕様は電源供給は3.3V,Max1.5mAになっています。
そのため赤外線受光モジュールの電源コントロールにはGPIO16の出力をそのまま使ってコントロールします。
赤外線受光モジュールからの信号は論理信号なので,デジタル入力ピンでよいのですが,動作確認も出来るようにADCピンGPIO35を割り当てます。

赤外LED
赤外LEDの電流は100mAまで可能ですが,M5Stack側のデジタル出力に無理をしないように,20mAで駆動するように制限抵抗を使います。
 M5Stackのデジタルピン出力;3.3V
 LED順電圧:1.35V
 駆動電流:20mA
 3.3V-1.35V=1.95V
 R=V/I=1.95V/0.02A=97.5Ω
ということで,電流制限抵抗は100Ω程度を使います。

赤外線受光モジュール
赤外線受光モジュールの安定動作のため,1μFのバイパスコンデンサを付けます。
またM5Stack出力ピンの電流制限のため,100Ω程度の抵抗を使います。

次のような回路となります。


5 赤外線利用で時間計測のプログラム

赤外LED38kHz変調駆動には38kHzのPWM動作を使用します。
PWM信号のデューティ比は,32/256,64/256,96/256,128/256を使用し,デフォルトは32/258です。
ボタンCでPWM信号のデューティ比を変更します。
起動直後のM5Stackの画面2行目に使用中のデューティ比が表示されます。
デューティ比が大きいほど,赤外LEDの強度は大きくなりますが,場合によっては乱反射による赤外線の回り込みが生じ,常に受光モジュールが反応してしまうことがあります。

ボタンAで計時動作をリセットします。

loop関数シーケンス内で5msecの周期で受光モジュールの状態を検査します。
検査動作では,600μsecの赤外LED38kHz変調駆動を行います。
赤外LED38kHz変調駆動開始後300μsec経過後に受光モジュールの信号をチェックし,遮光物がないとき(受光時)にLを,遮光物があるとき(非受光時)にHをデジタル値で受け取ります。



loop関数シーケンス内では,受光モジュールの状態により,stausを順次切り替えて,statusが0→1のときに計時を開始し,statusが3→4のときに計時を終了します。


//infraredsensorA.ino

#include <M5Stack.h>

const uint8_t nBits_forPWM = 8; // PWMに使用するビット数 n=1~16[bit]
const uint8_t PWM_Values[] = {32, 64, 96, 128}; //デューティ
                         //MaxDuty=2^n  DutyRatio = Duty/MaxDuty
const int nValues = sizeof(PWM_Values)/sizeof(uint8_t);
int ValueIndex = 0;

uint8_t PWM_Value = PWM_Values[ValueIndex]; //duty ratio = 32/256 に設定
const double PWM_Frequency = 38000.0;   // PWM周波数 Maxfreq=80000000.0/2^n[Hz] 38kHz

const uint8_t PWM_CH = 2;  // PWMチャンネル
const uint8_t PWM_PIN = 2; // PWM出力に使用するGPIO PIN番号
const uint8_t IRRecModuleOUT_PIN = 35; // 赤外線センサの出力に使用するGPIO PIN番号
const uint8_t IRRecModuleVCC_PIN = 16; // 赤外線センサ電源に使用するGPIO PIN番号

int finalstatus = 4;  //測定終了状態

long msecperiod = 5;  // 赤外線光路チェック周期 msec
long usecWaitingtime = 1500;  // 赤外線モジュール通電後の待機時間 μsec
long msectimetocheck = msecperiod*2;

void setup() {
  M5.begin();
  M5.Power.begin();
  M5.Lcd.setTextSize(2);
  M5.Lcd.print("Time Measurement with IR\n");
  M5.Lcd.printf("PWM FRQ:%.0lfk dutyR:%d/256 ", PWM_Frequency/1000, PWM_Value);

  //PWM信号発生ピンを出力モードにする
  pinMode(PWM_PIN, OUTPUT); 
  // チャンネルと周波数の分解能を設定
  ledcSetup(PWM_CH, PWM_Frequency, nBits_forPWM);
  // PWM出力ピンとチャンネルの設定
  ledcAttachPin(PWM_PIN, PWM_CH);

  //PWM出力ピンと周波数,分解能,チャンネルの設定 (これはESP32用の新しい関数なので,まだ使えない)
  //ledcAttachChannel(PWM_PIN, PWM_Frequency, nBits_forPWM, PWM_CH);

  //IRRecModuleOUT_PINを単純入力モードにする
  pinMode(IRRecModuleOUT_PIN, INPUT); 
  pinMode(IRRecModuleVCC_PIN, OUTPUT);
  digitalWrite(IRRecModuleVCC_PIN, LOW);
}

// 2回の受光モジュールチェックを行う
// 1: 遮光状態
// 0: 受光状態
// -1: 判定不能
int checkIRRecModule() {
  digitalWrite(IRRecModuleVCC_PIN, HIGH);
  delayMicroseconds(usecWaitingtime);

  //PWM信号発生開始
  ledcWrite(PWM_CH, PWM_Value);
  delayMicroseconds(300);
  int result1 = digitalRead(IRRecModuleOUT_PIN);
  delayMicroseconds(300);
  //PWM信号発生停止
  ledcWrite(PWM_CH, 0);

  delayMicroseconds(600);

  //PWM信号発生開始
  ledcWrite(PWM_CH, PWM_Value);
  delayMicroseconds(300);
  int result2 = digitalRead(IRRecModuleOUT_PIN);
  //delayMicroseconds(300);
  //PWM信号発生停止
  ledcWrite(PWM_CH, 0);

  digitalWrite(IRRecModuleVCC_PIN, LOW);

  //動作のみ確認用表示 これを有効にすると時間がかかって5msecより長い周期で動く
  //M5.Lcd.setTextSize(2);
  //M5.Lcd.setCursor(0,3*16);
  //M5.Lcd.printf("%4d %4d", result1, result2);

  if (result1==0 && result2==0) return 0;
  if (result1==1 && result2==1) return 1;
  return -1;
}

int IRRecModulestatus = 0;
int status = 0;
/*
status
初期状態 status = 0
status = 0 IRRecModulestatus = 1 → status = 1 start
status = 1 IRRecModulestatus = 0 → status = 2
status = 2 IRRecModulestatus = 1 → status = 3
status = 3 IRRecModulestatus = 0 → status = 4 goal
*/

long msectimeorg; //最初の遮光の時刻
double elapsedmsectime =0.;  //経過時間

void loop() {
  M5.update();
  if(M5.BtnA.wasPressed()){
    IRRecModulestatus = 0;
    status = 0;
    elapsedmsectime = 0.;
    M5.Lcd.setTextSize(5);
    M5.Lcd.setCursor(0,4*16);
    M5.Lcd.print("       ");
  }

  if(M5.BtnC.wasPressed()){
    ValueIndex++;
    if (ValueIndex == nValues) ValueIndex = 0;
    PWM_Value = PWM_Values[ValueIndex];
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(0,1*16);
    M5.Lcd.printf("PWM FRQ:%.0lfk dutyR:%d/256 ", PWM_Frequency/1000, PWM_Value);
  }

  if (msectimetocheck <= millis()) {
    msectimetocheck += msecperiod;
    IRRecModulestatus = checkIRRecModule();
    //M5.Lcd.printf(" %4d", status); //確認用表示
    if (status == 0 && IRRecModulestatus == 1) {
      status = 1;
      msectimeorg = millis(); //start
    } else if (status == 1 && IRRecModulestatus == 0) {
      status = 2;
    } else if (status == 2 && IRRecModulestatus == 1) {
      status = 3;
      elapsedmsectime = (double)(millis() - msectimeorg);
    } else if (status == 3 && IRRecModulestatus == 0 &amp;amp;amp;amp;&amp;amp;amp;amp; finalstatus==4)  {
      status = 4;
      elapsedmsectime = (double)(millis() - msectimeorg);
    }

    if (0<status && status<finalstatus) {
      elapsedmsectime = (double)(millis() - msectimeorg);
    }

    if (status == finalstatus) {
      M5.Lcd.setTextSize(2);
      M5.Lcd.setCursor(0,3*16);
      M5.Lcd.printf("%d", status); //確定表示
      M5.Lcd.setTextSize(5);
      M5.Lcd.setCursor(0,4*16);
      M5.Lcd.printf("%7.2lf", elapsedmsectime/1000.); //確定用表示
    } else {
      M5.Lcd.setTextSize(2);
      M5.Lcd.setCursor(0,3*16);
      M5.Lcd.printf("%d", status); //実行確認用表示

      //動作のみ確認用表示 これを有効にすると時間がかかって5msecより長い周期で動く
      //M5.Lcd.setTextSize(5);
      //M5.Lcd.setCursor(0,4*16);
      //M5.Lcd.printf("%7.2lf", elapsedmsectime/1000.);
    }
  }
}

/*
発行側の設定
LEDの制限抵抗は220Ω並列で110Ωとしている
LED駆動は38kHz デューティ比32/256 = 12.5%
これくらいの設定が良いようだ
*/

6 実行の様子

実際のテスト装置は次のようになります。
右側のブレッドボード上に赤外LEDがあり,左側のブレッドボード上に赤外線受光モジュールがあります。



また,テストの様子は次のようになりました。(赤外LEDは画面の右側にはみ出しています。)
カートがスタート時とゴール時に赤外光路を遮断することを模して,塗刷毛(手元にあったもの)で2回赤外光路を遮断しています。
まずボタンAを押してリセットし,赤外光路遮断の1回目の始まりの部分で計時が始まっています。
そして,赤外光路遮断の2回目の終わりの部分で計時が終了しています。
なおこの動画は動作チェック時のもので,計時誤差は10msec以上になっています。
5msec周期動作では計測中はstatus表示だけが見えて,測定終了時に測定値を表示します。



7 使用デューティ比と,センサペア間の距離

デューティ比によって利用可能な赤外発光アセンブリと赤外受光アセンブリ間の距離は次のようになっています。(実測)

デューティ比 発光と受光間の最大距離
32/256 150cm
64/256 230cm

8 まとめ

M5Stackで赤外線を使った計時のテストができました。
50cmの赤外線光路でうまく動作しています。
なお測定値には±5msecの誤差が含まれています。
製作・テストにおいて誤動作がありました。対処は次の通りでした。
(1)赤外線受光モジュールにはノイズの少ない電源を与える必要がある。セラミックコンデンサ1μFを赤外線受光モジュール直近に付加することで対処出来ました。
(2)赤外線受光モジュールのAGC(Automatic Gain Control)が働かないようにして動作しているため,外乱光にかく乱されることがありました。外乱光を防ぐためのカバーを使うことで対処しました。

補足

「測定したい時間A」の場合についてのプログラムを扱ってきましたが,「測定したい時間B」の場合については,プログラムを1行だけ変更すれば実現できます。


最初の定数設定で
  int finalstatus = 4;
  ↓
  int finalstatus = 3;

感想

2000年頃にRS232C通信の途中に赤外線通信で中継してロボコンのロボットの遠隔操作機能を作ったときには,単純に38kHz変調・復調しただけで通信ができたと記憶しています。そのころの受光モジュールは単純に38kHz変調された赤外線を復調しているだけのものが入手出来たのではないかと思います。現在入手できる赤外線受光モジュールは用途が家電製品のリモコンに特化され,耐ノイズ性能は向上したものの,特定のバーストパターンしか受け入れないようにできていて,受光状態か非受光状態かを常時監視するにはプログラムの工夫が必要となっています。
いろいろ調べていくと,OSRB38C9AA を含む最近の赤外線受光モジュールはリモコン用と書かれていて,ある長さの非受光状態をAGCが必要としているようで,Net検索してみると困っている人も多いようです。
今は廃版になってしまった赤外線受光素子 NJL21V380A や PL-IRM1261-C438 はある長さの非受光状態を必要としていないようです。(これらのモジュールは現在入手がきわめて困難です)

現状では測定値に±5msecの誤差が含まれていますが,赤外線受光素子 NJL21V380A や PL-IRM1261-C438 を使うことが出来れば,誤差を現状の1/2程度まで小さくできるはずです。