電子工作

【Arduino】赤外線リモコンでラジコンを操作する

全体像

概要

ルームライトのリモコンでラジコンを操作します。

厳密には、Arduinoと赤外線リモコンでモーターの制御を出来るようにします。

記事の内容としては、リモコンから送信されるデータの解析の後、リモコンのボタンを押すと動作するモノの製作を行うといった具合です。

リモコンを解析する

まずはリモコンの赤外線をArduinoに学習させましょう。

必要なもの

  • 赤外線受光モジュール
  • Arduino
  • エアコンのリモコン本体「TLR-002」

の3つを使います。

ここで使用するArduinoは互換品で、

赤外線受光モジュールは「1838B」です。

image.png

引用元:https://www.robodukkan.com/38-KHz-IR-Kizilotesi-Alici-Goz-VS-1838B,PR-344.html

これは私の部屋のルームライトに付属してきたリモコンです。

配線

信号のピンには、別のピンを使用してもらっても構いませんが、

後ほど使うサンプルプログラムの関係で、信号ピンには11番を使用するのが楽だと思います。

IRremoteライブラリをインストール

IRremote.hとは、リモコンから送信される情報を確認したり、逆に赤外線LEDから情報を送信する場合に使える非常に便利なライブラリです。今回はこのライブラリを使用します。

Arduinoのライブラリマネージャを開き検索ボックスに「IRremote」と入力し、該当のライブラリをインストールします。

リモコンから送信される情報を取得する

IRrecvDumpV2.inoをArduinoに書き込む

ArduinoIDEでファイル>スケッチ例>IRremote>IRrecvDumpV2を選択し、開きます。

すると新しいウィンドウで、サンプルプログラムが開かれるはずです。

サンプル用のプログラムソースは以下の通りです。


//------------------------------------------------------------------------------
// Include the IRremote library header
//
#include <IRremote.h>

//------------------------------------------------------------------------------
// Tell IRremote which Arduino pin is connected to the IR Receiver (TSOP4838)
//
int recvPin = 11;
IRrecv irrecv(recvPin);

//+=============================================================================
// Configure the Arduino
//
void  setup ( )
{
  Serial.begin(9600);   // Status message will be sent to PC at 9600 baud
  irrecv.enableIRIn();  // Start the receiver
}

//+=============================================================================
// Display IR code
//
void  ircode (decode_results *results)
{
  // Panasonic has an Address
  if (results->decode_type == PANASONIC) {
    Serial.print(results->address, HEX);
    Serial.print(":");
  }

  // Print Code
  Serial.print(results->value, HEX);
}

//+=============================================================================
// Display encoding type
//
void  encoding (decode_results *results)
{
  switch (results->decode_type) {
    default:
    case UNKNOWN:      Serial.print("UNKNOWN");       break ;
    case NEC:          Serial.print("NEC");           break ;
    case SONY:         Serial.print("SONY");          break ;
    case RC5:          Serial.print("RC5");           break ;
    case RC6:          Serial.print("RC6");           break ;
    case DISH:         Serial.print("DISH");          break ;
    case SHARP:        Serial.print("SHARP");         break ;
    case JVC:          Serial.print("JVC");           break ;
    case SANYO:        Serial.print("SANYO");         break ;
    case MITSUBISHI:   Serial.print("MITSUBISHI");    break ;
    case SAMSUNG:      Serial.print("SAMSUNG");       break ;
    case LG:           Serial.print("LG");            break ;
    case WHYNTER:      Serial.print("WHYNTER");       break ;
    case AIWA_RC_T501: Serial.print("AIWA_RC_T501");  break ;
    case PANASONIC:    Serial.print("PANASONIC");     break ;
    case DENON:        Serial.print("Denon");         break ;
  }
}

//+=============================================================================
// Dump out the decode_results structure.
//
void  dumpInfo (decode_results *results)
{
  // Check if the buffer overflowed
  if (results->overflow) {
    Serial.println("IR code too long. Edit IRremoteInt.h and increase RAWLEN");
    return;
  }

  // Show Encoding standard
  Serial.print("Encoding  : ");
  encoding(results);
  Serial.println("");

  // Show Code & length
  Serial.print("Code      : ");
  ircode(results);
  Serial.print(" (");
  Serial.print(results->bits, DEC);
  Serial.println(" bits)");
}

//+=============================================================================
// Dump out the decode_results structure.
//
void  dumpRaw (decode_results *results)
{
  // Print Raw data
  Serial.print("Timing[");
  Serial.print(results->rawlen-1, DEC);
  Serial.println("]: ");

  for (int i = 1;  i < results->rawlen;  i++) {
    unsigned long  x = results->rawbuf[i] * USECPERTICK;
    if (!(i & 1)) {  // even
      Serial.print("-");
      if (x < 1000)  Serial.print(" ") ;
      if (x < 100)   Serial.print(" ") ;
      Serial.print(x, DEC);
    } else {  // odd
      Serial.print("     ");
      Serial.print("+");
      if (x < 1000)  Serial.print(" ") ;
      if (x < 100)   Serial.print(" ") ;
      Serial.print(x, DEC);
      if (i < results->rawlen-1) Serial.print(", "); //',' not needed for last one
    }
    if (!(i % 8))  Serial.println("");
  }
  Serial.println("");                    // Newline
}

//+=============================================================================
// Dump out the decode_results structure.
//
void  dumpCode (decode_results *results)
{
  // Start declaration
  Serial.print("unsigned int  ");          // variable type
  Serial.print("rawData[");                // array name
  Serial.print(results->rawlen - 1, DEC);  // array size
  Serial.print("] = {");                   // Start declaration

  // Dump data
  for (int i = 1;  i < results->rawlen;  i++) {
    Serial.print(results->rawbuf[i] * USECPERTICK, DEC);
    if ( i < results->rawlen-1 ) Serial.print(","); // ',' not needed on last one
    if (!(i & 1))  Serial.print(" ");
  }

  // End declaration
  Serial.print("};");  // 

  // Comment
  Serial.print("  // ");
  encoding(results);
  Serial.print(" ");
  ircode(results);

  // Newline
  Serial.println("");

  // Now dump "known" codes
  if (results->decode_type != UNKNOWN) {

    // Some protocols have an address
    if (results->decode_type == PANASONIC) {
      Serial.print("unsigned int  addr = 0x");
      Serial.print(results->address, HEX);
      Serial.println(";");
    }

    // All protocols have data
    Serial.print("unsigned int  data = 0x");
    Serial.print(results->value, HEX);
    Serial.println(";");
  }
}

//+=============================================================================
// The repeating section of the code
//
void  loop ( )
{
  decode_results  results;        // Somewhere to store the results

  if (irrecv.decode(&results)) {  // Grab an IR code
    dumpInfo(&results);           // Output the results
    dumpRaw(&results);            // Output the results in RAW format
    dumpCode(&results);           // Output the results as source code
    Serial.println("");           // Blank line between entries
    irrecv.resume();              // Prepare for the next value
  }
}

シリアルモニタを確認する

このコードをArduinoに書き込み、シリアルモニタを開きましょう。

シリアルモニタは、IDEの右上のアイコンを押すことで起動します。

シリアルモニタを開いたら、赤外線モジュールに向けてリモコンのボタンを押しましょう。

するとリモコンから受信した情報が表示されるはずです。

image.png

様々な情報が出てきましたが、

今回使用するのは”0x214AD629″の部分になります。

この場合だと、「全灯」のボタンを押すことで、

リモコンから”0x214AD629″という情報が送信されているということが分かりました。

同様にして「消灯」のボタンを押した際の情報も収集します。

私の場合以下のようになりました。

ボタンバイトコード
全灯 0x214AD629
消灯 0x214ABE41

赤外線リモコンでLEDを制御してみる

前章で得たリモコンの情報を元に、

「全灯」ボタンを押してLEDをONに、「消灯」ボタンを押してLEDをOFFにするモノをプロトタイプを製作しましょう。

必要なもの

  • 前章で配線したブレッドボート
  • 適当なLED
  • 330[Ω]の抵抗

LEDと抵抗は適当なもので構いません。なんなら豆電球でもOKです。

配線

下図のように配線をします。

前章の回路にLEDを加えただけの回路です。

コードを書く

「全灯」ボタンを押してLEDをON、「消灯」ボタンを押してLEDをOFFにするプログラムを組みます。


#include<IRremote.h>

#define ON 0x214A56A9  //「全灯ボタン」
#define OFF 0x214A3EC1 //「消灯ボタン」
int RECV_PIN = 11;
int LED_PIN = 3;
IRrecv irrecv(RECV_PIN);
decode_results results;

void setup() {
 Serial.begin(9600);
 irrecv.enableIRIn();
 pinMode(LED_PIN,OUTPUT);
}

void loop() {
  if (irrecv.decode(&results)) {
    if (results.value == ON) {
       Serial.println("ON");
       digitalWrite(LED_PIN,HIGH) ; 
    }
    if (results.value == OFF) {
      Serial.println("OFF");
      digitalWrite(LED_PIN,LOW);
    }
  irrecv.resume();
  }
}

赤外線リモコンで、LEDを操作するこができました。

また、シリアルコンソールからも動作の確認ができるかと思います。

赤外線リモコンでモーターを制御する

いよいよ最後の章です。

前章の内容を元にモーターを制御しましょう!

必要なもの

  • モータードライバ「TA7291P」2個
  • 1.5Vの乾電池 2個
  • 電池ボックス(モーター用の電源)
  • モバイルバッテリー(Arduino用の電源)
  • Arduino
  • 赤外線受信モジュール
  • タミヤのダブルギアボックス https://www.tamiya.com/japan/products/70168/index.html

モータードライバについて

ものすごく簡単に(雑に)まとめると、

Arduinoのピンの電圧のHIGH、LOWの組み合わせでモーターの「前進」、「後進」、「ブレーキ」のアクションを生み出すことが出来ます。

「TA7291の使い方」などで調べれば、

他記事に詳しい情報があると思いますので、割愛させていただきます。

外観

遊びで障害物見つけると自動停止するようにしていたので赤外線測距センサがついていますが、あまり関係ないです。

モーターの駆動電源は裏側の単三乾電池になります。

image.png

ソースコード


#include <IRremote.h>

int speed = 250;
int RecvPin = 11;
IRrecv irrecv(RecvPin);
decode_results results ;

#define Right_IN1  7
#define Right_IN2  8
#define Right_VREF 9
#define Left_IN1  3
#define Left_IN2  4
#define Left_VREF 5

#define FD 0x214A56A9   //forward
#define BC 0x214A3EC1   //back
#define LF 0x214A22DD   //reft forward
#define LB 0x214A06F9   //reft back
#define RF 0x214A0AF5   //right forward
#define RB 0x214A2ED1   //right back
#define BRAKE 0x214A2CD3 //bBrake

void forward() {
    Serial.println("前進");
    digitalWrite(Right_IN1,HIGH);
    digitalWrite(Right_IN2,LOW);
    digitalWrite(Left_IN1,HIGH);
    digitalWrite(Left_IN2,LOW);
}

void back() {
    Serial.println("後進");
    digitalWrite(Right_IN1,LOW);
    digitalWrite(Right_IN2,HIGH);
    digitalWrite(Left_IN1,LOW);
    digitalWrite(Left_IN2,HIGH);
}

void left_forward() {
    Serial.println("左前進");
    digitalWrite(Right_IN1,LOW);
    digitalWrite(Right_IN2,LOW);
    digitalWrite(Left_IN1,HIGH);
    digitalWrite(Left_IN2,LOW);
}

void left_back() {
    Serial.println("左後進");
    digitalWrite(Right_IN1,LOW);
    digitalWrite(Right_IN2,LOW);
    digitalWrite(Left_IN1,LOW);
    digitalWrite(Left_IN2,HIGH);
}

void right_forward() {
    Serial.println("右前進");
    digitalWrite(Right_IN1,HIGH);
    digitalWrite(Right_IN2,LOW);
    digitalWrite(Left_IN1,LOW);
    digitalWrite(Left_IN2,LOW);
}

void right_back() {
    Serial.println("右後進");
    digitalWrite(Right_IN1,LOW);
    digitalWrite(Right_IN2,HIGH);
    digitalWrite(Left_IN1,LOW);
    digitalWrite(Left_IN2,LOW);
}

void Brake() {
    Serial.println("停止");
    digitalWrite(Right_IN1,HIGH);
    digitalWrite(Right_IN2,HIGH);
    digitalWrite(Left_IN1,HIGH);
    digitalWrite(Left_IN2,HIGH);
}

void setup() {
    Serial.begin(9600);
    irrecv.enableIRIn();

    pinMode(Right_IN1,OUTPUT);
    pinMode(Right_IN2,OUTPUT);
    pinMode(Right_VREF,OUTPUT);
    pinMode(Left_IN1,OUTPUT);
    pinMode(Left_IN2,OUTPUT);
    pinMode(Left_VREF,OUTPUT);
    analogWrite(Right_VREF,speed);
    analogWrite(Left_VREF,speed);
}

void loop() {
    if (irrecv.decode(&results)) {
        if (results.value == FD ) {
        forward();
        }
        if (results.value == BC ) {
        back();
        }
        if (results.value == LF ) {
        left_forward();
        }
        if (results.value == LB ) {
        left_back();
        }
        if (results.value == RF ) {
        right_forward();
        }
        if (results.value == RB ) {
        right_back();
        }
        if (results.value == BRAKE) {
        Brake();
        }
    irrecv.resume();
    }
    delay(1);
}

前章でLEDの点灯/消灯を制御した時と、基本的にやっていることに変わりはありません。

モータードライバの仕様に合わせて、どのピンをHIGHに、またはLOWにするかの組み合わせによって、前進、後進、右旋回、左旋回、ブレーキの動作を実現しています。

ハマったところ

今回、私が使用した赤外線受信モジュールは、Amazonに出回っている中華製のコピー品で、

赤外線が正常に読み取れないものがいくつか混ざっていました。

はずれのセンサを実装した際に、正常にラジコンが動作しなかったため、コードと2時間もにらめっこをしてしまいました。

リモコンの動作をうまく受け取れないなどの不具合があるときは、コードよりもセンサーを疑った方がいいかもしれません(笑)

その後

照明のリモコンでラジコンを操作すると、

部屋の電気が点いたり消えたりで鬱陶しいため、

ジャンク品のリモコンで操作できるように作り直したりなどしました。