●リモコンを使う

赤外線リモコンでロボットをコントロールしてみます。リモコン送信器にボタン電池(CR2025)を挿入してください。リモコン受信部はメイン基板後方の3本足の部品です。
remote

リモコン信号のフォーマットについてはこちらをご覧ください。
図の横軸は時間で、左側からON,OFF,ON,OFF...とバーコードのような赤外線信号が受信器に到達します。これが5V,0Vの電圧に変換されてマイコンのピン(PD2-ArduinoのDigital 2ピン)に入力されます。一応注意が必要なのが、ONが1信号、OFFが0信号とはならないことです。ONの後に長いOFFが来ると1、ONの後に短いOFFが来ると0と判断します。プログラムでは、Digital 2ピンの入力状態をチェックしてONの長さ、OFFの長さを時間計測しながら信号を解析(デコード)していきます。信号全体はリーダー、カスタムコード(16bit)、データコード(8bit)、データコード(8bit-反転)の順にやってきますのでその順で処理していき、今回はデータコード(8bit)の値を見てどのボタンが押されたのかを判断します。

remote_wave_2
リモコン"1"ボタンの信号(リモコン受光部の出力:HIGH/LOWが反転しています)

まずは、受信したデータコード(8bit)をシリアルモニタに出力してボタンに対するデータの値を確認します。Digital 2ピンの入力チェックには、
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);
という「外部割込み」を使います(もう一つの割り込みは「タイマー割り込み」)。これを利用すると、ピンの状態を勝手にチェックしてくれて変化があった場合には指定の関数を自動で実行してくれます。
プログラムは少し長いですが、フォーマットの図と見比べながら追ってみてください。リモコン受光部品の出力がHIGHやLOWに変化するたびにrmUpdate()が呼ばれ、HIGH時間、LOW時間を測定し信号が1なのか0なのかを調べていきます。ステートマシンという方法です。100%わからなくてもこのプログラムをコピペして実行し、リモコン送信でシリアルモニタに数字がでてくることを確認してください。
・プログラム

const uint8_t interruptPin = 2;

// remote
boolean  rmRecieved = 0;  //信号受信完了した
uint8_t  i;               //受信データの桁
uint8_t  rmState = 0;     //信号受信状況
uint8_t  dataCode;        //データコード(8bit)
uint8_t  invDataCode;     //反転データコード(8bit)
uint16_t customCode;      //カスタムコード(16bit)
uint32_t rmCode;          //コード全体(32bit)
volatile uint32_t prevMicros = 0; //時間計測用

void rmUpdate() //信号が変化した
{
  uint32_t width; //パルスの幅を計測
  if(rmState != 0){
    width = micros() - prevMicros;  //時間間隔を計算
    if(width > 10000)rmState = 0; //長すぎ
    prevMicros = micros();  //次の計算用
  }
  switch(rmState){
    case 0: //信号未達
    prevMicros = micros();  //現在時刻(microseconds)を記憶
    rmState = 1;  //最初のOFF->ON信号を検出した
    i = 0;
    return;
    case 1: //最初のON状態
      if((width > 9500) || (width < 8500)){ //リーダーコード(9ms)ではない
        rmState = 0;
      }else{
        rmState = 2;  //ON->OFFで9ms検出
      }
      break;
    case 2: //9ms検出した
      if((width > 5000) || (width < 4000)){ //リーダーコード(4.5ms)ではない
        rmState = 0;
      }else{
        rmState = 3;  //OFF->ONで4.5ms検出
      }
      break;
    case 3: //4.5ms検出した
      if((width > 700) || (width < 400)){
        rmState = 0;
      }else{
        rmState = 4;  //ON->OFFで0.56ms検出した
      }
      break;
    case 4: //0.56ms検出した
      if((width > 1800) || (width < 400)){  //OFF期間(2.25-0.56)msより長い or (1.125-0.56)msより短い
        rmState = 0;
      }else{
        if(width > 1000){ //OFF期間長い -> 1
          bitSet(rmCode, (i));
        }else{             //OFF期間短い -> 0
          bitClear(rmCode, (i));
        }
        i++;  //次のbit
        if(i > 31){ //完了
          rmRecieved = 1;
          return;
        }
        rmState = 3;  //次のON->OFFを待つ
      }
      break;
    }
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  attachInterrupt(digitalPinToInterrupt(interruptPin), rmUpdate, CHANGE);
}

void loop() {
  // put your main code here, to run repeatedly:
  if(rmRecieved){ //リモコン受信した
    detachInterrupt(digitalPinToInterrupt(interruptPin));
    rmRecieved = 0;   //初期化
    rmState = 0;      //初期化
    //図とは左右が逆であることに注意
    customCode = rmCode;    //下16bitがcustomCode
    dataCode = rmCode >> 16;  //下16bitを捨てたあとの下8bitがdataCode
    invDataCode = rmCode >> 24; //下24bitを捨てたあとの下8bitがinvDataCode
    if((dataCode + invDataCode) == 0xff){   //反転確認
      Serial.println(dataCode);
    }
    attachInterrupt(digitalPinToInterrupt(interruptPin), rmUpdate, CHANGE);
  }
}

"ms06"として保存しておきます。

<= マイクロサウルス チュートリアル(5)
チュートリアルベース
マイクロサウルス チュートリアル(7) =>