前回の記事で、簡易赤外線リモコンをArduino UNOで作成するための設計を行いました。
プログラムの作成が完了したので、実装編を記載します。
設計当初は難しいかなと思ってましたが、赤外線のプロトコル周りは「IRremote」というライブラリを使用したため、400行程度のプログラムで実現できました。
筆者が作成した簡易学習リモコンの試作機です。

目次
詳細設計
本プログラムでは、Arduino UNOの電源を落としても学習したデータや、リモコン送信状態を記憶しておくために変数の永続化が必要になります。
Arduinoで変数を永続化するには、フラッシュメモリを利用します。
Arduinoのフラッシュメモリは、1KBの容量があります。学習するリモコンデータは1つ4Byteなので、2つのボタンに学習する場合でも、1つあたり、100個以上のデータを記憶できるので、十分です。
学習データ管理方式
学習データはボタン1、2それぞれで配列を用意し、学習データを格納します。
Arduinoの電源投入時に、フラッシュメモリから学習データを配列に読み込みます。
リモコン送信時は、配列からデータを読み込んで送信します。
学習データ追加の際には、配列にデータを追加し、フラッシュメモリに記憶します。
配列とフラッシュメモリの学習データの内容は、常に同じ状態にします。
状態管理方式
学習データに加えて、2種類の状態管理データが必要になります。
送信状態
ボタンを押すたびに登録されているリモコンデータを順に送信するため、学習データ配列のインデックス値(次にどのデータを送信するか)を送信状態として記憶します。
送信状態は、いつArduinoの電源を落とされても良いように、フラッシュメモリに記憶します。
学習状態
学習データ配列は、固定サイズの配列とするため、別途何個のリモコンデータが登録されているか記憶する必要があり、これを学習状態とします。
学習状態も、送信状態と同様、フラッシュメモリに記憶します。
管理方式イメージ
管理方式を図に表すと以下のようになります。

フラッシュメモリの構成
フラッシュメモリには、下記順でデータを記憶します。
- ボタン1の学習データ
- ボタン2の学習データ
- ボタン1の送信状態
- ボタン2の送信状態
- ボタン1の学習状態
- ボタン2の学習状態
イメージは以下のようになります。

実装
ライブラリ導入
Arduinoで「IRremote」が利用できるように、インストールしておきます。
Arduino IDEのメニューで[スケッチ]-[ライブラリをインクルード]-[ライブラリを管理]をクリックします。

検索ボックスに「irremote」と入力して出てきた「IRremote」を[インストール]ボタンをクリックすれば完了です。

本プログラムでは、記事執筆時の最新版3.9.0を利用しています。
赤外線リモコンライブラリ「IRremote」の利用方法
初期化処理
Arduinoのsetup()関数内に下記を記述し、初期化します。
RECEIVE_PIN、SEND_PINには、それぞれ受信用、送信用のArduinoのピン番号を指定します。
IrReceiver.begin(RECEIVE_PIN);
IrSender.begin(SEND_PIN);
受信
Arduinoのloop()関数に下記を記述します。受信したらif文の条件がtrueになり、受信データが取得できます。
if(IrReceiver.decode()){
uint32_t addr = IrReceiver.decodedIRData.decodedRawData;
}
送信
リモコンデータのフォーマットは、いくつかありますが、今回のプログラムでは、NEC方式のリモコンデータを送信できるように実装しました。
Arduinoのloop()関数に下記を記述します。
IrSender.sendNECRaw(value, 0);
第1引数にリモコンデータを代入し、第2引数にリピート回数を指定します。0の場合は1回送信になります。
プログラム書き込み
以下プログラムをArduino IDEのエディタにコピペし、「IRLearning.ino」という名前で保存します。
事前にArduinoをパソコンにUSB接続し、ブートローダやCOM等の初期設定をしておき、「マイコンボードに書き込む」アイコンをクリックして、エラーが出なければ完了です。
今回のプログラムでは、1つのボタンあたり最大32個のリモコンデータを登録できるようにしました。定数MAX_BTNの値変更で120個まで増やすことができます。
// 赤外線リモコン送受信機能 | |
// | |
// 追加モジュール | |
// ・IRremote | |
// ・EEPROM | |
// | |
#include <IRremote.hpp> | |
#include <EEPROM.h> | |
// PIN定義 | |
const int MY_RECEIVE_PIN = 4; | |
const int MY_SEND_PIN = 2; | |
const int MY_BTN_1 = 7; | |
const int MY_BTN_2 = 8; | |
const int MY_LED_PIN = 3; | |
// 状態 | |
const int DEFAULT_STAT = 0; | |
const int BTN_1_LEARNING = 1; | |
const int BTN_2_LEARNING = 2; | |
// 長押し時間 | |
const int LONG_PRESS_1 = 30; | |
const int LONG_PRESS_2 = 100; | |
// 待ち時間 | |
const int N_MSEC = 100; | |
const int LED_MSEC = 300; | |
// LEDモード | |
const int LED_SEND = 0; | |
const int LED_LEARNING = 1; | |
const int LED_OK = 2; | |
const int LED_NG = 3; | |
// 1つのボタンに登録できる最大の信号数(〜120) | |
const int MAX_BTN = 32; | |
// 最大信号数を考慮したアドレス計算 | |
int addr_base = MAX_BTN * sizeof(uint32_t) * 2 + 1; | |
// モード | |
int MODE = DEFAULT_STAT; | |
// カウンタ | |
int cnt = 0; | |
// ボタン状態 | |
bool btn1_on = false; | |
bool btn2_on = false; | |
// ボタンデータ | |
uint32_t data_btn1[MAX_BTN]; | |
uint32_t data_btn2[MAX_BTN]; | |
void setup() { | |
Serial.begin(115200); | |
// IR設定 | |
IrReceiver.begin(MY_RECEIVE_PIN); | |
IrSender.begin(MY_SEND_PIN); | |
// PIN設定 | |
pinMode(MY_BTN_1, INPUT_PULLUP); | |
pinMode(MY_BTN_2, INPUT_PULLUP); | |
pinMode(MY_LED_PIN, OUTPUT); | |
// ボタンデータ読み込み | |
readBtnData(MY_BTN_1); | |
readBtnData(MY_BTN_2); | |
// セットアップ完了 | |
Serial.println("Setup OK"); | |
} | |
void loop() { | |
// 操作モード | |
if(MODE == DEFAULT_STAT){ | |
// ボタン1の状態確認 | |
int v = digitalRead(MY_BTN_1); | |
// ボタン1を押した | |
if(v == LOW && !btn1_on){ | |
btn1_on = true; | |
} | |
// ボタン1を押し続けている | |
else if(v == LOW && btn1_on){ | |
cnt++; | |
// LONG_PRESS_2秒以上押した | |
if(cnt > LONG_PRESS_2){ | |
cnt = 0; | |
btn1_on = false; | |
// ボタン1学習リセット | |
procLED(LED_OK); | |
procRESET(MY_BTN_1); | |
//showData(); | |
} | |
} | |
// ボタン1を離した | |
else if(v == HIGH && btn1_on){ | |
btn1_on = false; | |
// 信号送信 | |
if(cnt < LONG_PRESS_1){ | |
if(getBtnNum(MY_BTN_1) == 0){ | |
procLED(LED_NG); | |
} | |
else{ | |
int bs = getBtnStat(MY_BTN_1); | |
uint32_t value = data_btn1[bs]; | |
IrSender.sendNECRaw(value, 0); | |
bs = bs + 1; | |
if(bs < getBtnNum(MY_BTN_1)){ | |
setBtnStat(MY_BTN_1, bs); | |
} | |
else{ | |
setBtnStat(MY_BTN_1, 0); | |
} | |
procLED(LED_SEND); | |
//showData(); | |
} | |
} | |
// LONG_PRESS_1秒以上押した | |
else{ | |
// 最大まで登録されていたら操作モードに戻る | |
if(getBtnNum(MY_BTN_1) == MAX_BTN){ | |
procLED(LED_NG); | |
} | |
// 学習モードに移行 | |
else{ | |
MODE = BTN_1_LEARNING; | |
} | |
} | |
cnt = 0; | |
} | |
// ボタン2の状態確認 | |
v = digitalRead(MY_BTN_2); | |
// ボタン2を押した | |
if(v == LOW && !btn2_on){ | |
btn2_on = true; | |
} | |
// ボタン2を押し続けている | |
else if(v == LOW && btn2_on){ | |
cnt++; | |
// LONG_PRESS_2秒以上押した | |
if(cnt > LONG_PRESS_2){ | |
cnt = 0; | |
btn2_on = false; | |
// ボタン1学習リセット | |
procLED(LED_OK); | |
procRESET(MY_BTN_2); | |
//showData(); | |
} | |
} | |
// ボタン2を離した | |
else if(v == HIGH && btn2_on){ | |
btn2_on = false; | |
// 信号送信 | |
if(cnt < LONG_PRESS_1){ | |
if(getBtnNum(MY_BTN_2) == 0){ | |
procLED(LED_NG); | |
} | |
else{ | |
int bs = getBtnStat(MY_BTN_2); | |
uint32_t value = data_btn2[bs]; | |
IrSender.sendNECRaw(value, 0); | |
bs = bs + 1; | |
if(bs < getBtnNum(MY_BTN_2)){ | |
setBtnStat(MY_BTN_2, bs); | |
} | |
else{ | |
setBtnStat(MY_BTN_2, 0); | |
} | |
procLED(LED_SEND); | |
//showData(); | |
} | |
} | |
// LONG_PRESS_1秒以上押した | |
else{ | |
// 最大まで登録されていたら操作モードに戻る | |
if(getBtnNum(MY_BTN_2) == MAX_BTN){ | |
procLED(LED_NG); | |
} | |
// 学習モードに移行 | |
else{ | |
MODE = BTN_2_LEARNING; | |
} | |
} | |
cnt = 0; | |
} | |
} | |
// ボタン1学習モード | |
else if(MODE == BTN_1_LEARNING){ | |
procLED(LED_LEARNING); | |
if(IrReceiver.decode()){ | |
if(IrReceiver.decodedIRData.protocol == NEC){ | |
uint32_t addr = IrReceiver.decodedIRData.decodedRawData; | |
MODE = DEFAULT_STAT; | |
int bn = getBtnNum(MY_BTN_1); | |
data_btn1[bn] = addr; | |
setBtnNum(MY_BTN_1, bn + 1); | |
writeBtnData(MY_BTN_1); | |
procLED(LED_OK); | |
//showData(); | |
} | |
IrReceiver.resume(); | |
} | |
} | |
// ボタン2学習モード | |
else if(MODE == BTN_2_LEARNING){ | |
procLED(LED_LEARNING); | |
if(IrReceiver.decode()){ | |
if(IrReceiver.decodedIRData.protocol == NEC){ | |
uint32_t addr = IrReceiver.decodedIRData.decodedRawData; | |
MODE = DEFAULT_STAT; | |
int bn = getBtnNum(MY_BTN_2); | |
data_btn2[bn] = addr; | |
setBtnNum(MY_BTN_2, bn + 1); | |
writeBtnData(MY_BTN_2); | |
procLED(LED_OK); | |
//showData(); | |
} | |
IrReceiver.resume(); | |
} | |
} | |
// Nミリ秒待つ | |
delay(N_MSEC); | |
} | |
// LED処理関数 | |
void procLED(int mode){ | |
if(mode == LED_SEND){ | |
digitalWrite(MY_LED_PIN, HIGH); | |
delay(LED_MSEC); | |
digitalWrite(MY_LED_PIN, LOW); | |
delay(LED_MSEC - N_MSEC); | |
} | |
else if(mode == LED_LEARNING){ | |
digitalWrite(MY_LED_PIN, HIGH); | |
delay(LED_MSEC); | |
digitalWrite(MY_LED_PIN, LOW); | |
delay(LED_MSEC - N_MSEC); | |
} | |
if(mode == LED_OK){ | |
digitalWrite(MY_LED_PIN, HIGH); | |
delay(LED_MSEC); | |
digitalWrite(MY_LED_PIN, LOW); | |
delay(LED_MSEC); | |
digitalWrite(MY_LED_PIN, HIGH); | |
delay(LED_MSEC); | |
digitalWrite(MY_LED_PIN, LOW); | |
delay(LED_MSEC); | |
digitalWrite(MY_LED_PIN, HIGH); | |
delay(LED_MSEC); | |
digitalWrite(MY_LED_PIN, LOW); | |
delay(LED_MSEC - N_MSEC); | |
} | |
else if(mode == LED_NG){ | |
digitalWrite(MY_LED_PIN, HIGH); | |
delay(LED_MSEC); | |
digitalWrite(MY_LED_PIN, LOW); | |
delay(LED_MSEC); | |
digitalWrite(MY_LED_PIN, HIGH); | |
delay(LED_MSEC); | |
digitalWrite(MY_LED_PIN, LOW); | |
delay(LED_MSEC - N_MSEC); | |
} | |
} | |
// ボタンリセット | |
void procRESET(int btn){ | |
if(btn == MY_BTN_1){ | |
for(int i=0; i<MAX_BTN; i++){ | |
data_btn1[i] = 0; | |
} | |
} | |
else if(btn == MY_BTN_2){ | |
for(int i=0; i<MAX_BTN; i++){ | |
data_btn2[i] = 0; | |
} | |
} | |
writeBtnData(btn); | |
setBtnStat(btn, 0); | |
setBtnNum(btn, 0); | |
} | |
// ボタン状態取得 | |
int getBtnStat(int btn){ | |
if(btn == MY_BTN_1){ | |
int value = 0; | |
EEPROM.get(addr_base, value); | |
return value; | |
} | |
else if(btn == MY_BTN_2){ | |
int value = 0; | |
EEPROM.get(addr_base+sizeof(int), value); | |
return value; | |
} | |
} | |
// ボタン状態保存 | |
void setBtnStat(int btn, int value){ | |
if(btn == MY_BTN_1){ | |
EEPROM.put(addr_base, value); | |
} | |
else if(btn == MY_BTN_2){ | |
EEPROM.put(addr_base+sizeof(int), value); | |
} | |
} | |
// ボタン登録数取得 | |
int getBtnNum(int btn){ | |
if(btn == MY_BTN_1){ | |
int value = 0; | |
EEPROM.get(addr_base+sizeof(int)*2, value); | |
return value; | |
} | |
else if(btn == MY_BTN_2){ | |
int value = 0; | |
EEPROM.get(addr_base+sizeof(int)*3, value); | |
return value; | |
} | |
} | |
// ボタン登録数保存 | |
void setBtnNum(int btn, int value){ | |
if(btn == MY_BTN_1){ | |
EEPROM.put(addr_base+sizeof(int)*2, value); | |
} | |
else if(btn == MY_BTN_2){ | |
EEPROM.put(addr_base+sizeof(int)*3, value); | |
} | |
} | |
// ボタンデータ読み込み | |
void readBtnData(int btn){ | |
if(btn == MY_BTN_1){ | |
EEPROM.get(0x000, data_btn1); | |
} | |
else if(btn == MY_BTN_2){ | |
EEPROM.get(0x000+sizeof(data_btn1), data_btn2); | |
} | |
} | |
// ボタンデータ書き込み | |
void writeBtnData(int btn){ | |
if(btn == MY_BTN_1){ | |
EEPROM.put(0x000, data_btn1); | |
} | |
else if(btn == MY_BTN_2){ | |
EEPROM.put(0x000+sizeof(data_btn1), data_btn2); | |
} | |
} | |
// デバッグ用 | |
void showData(){ | |
// ボタン1状態 | |
Serial.print("BtnStat1 = "); | |
Serial.println(getBtnStat(MY_BTN_1)); | |
// ボタン2状態 | |
Serial.print("BtnStat2 = "); | |
Serial.println(getBtnStat(MY_BTN_2)); | |
// ボタン1登録数 | |
Serial.print("BtnNum1 = "); | |
Serial.println(getBtnNum(MY_BTN_1)); | |
// ボタン2登録数 | |
Serial.print("BtnNum2 = "); | |
Serial.println(getBtnNum(MY_BTN_2)); | |
// ボタン1データ | |
Serial.print("data_btn1 = "); | |
for(int i=0; i<MAX_BTN; i++){ | |
Serial.print(data_btn1[i], HEX); | |
Serial.print(","); | |
} | |
Serial.println(""); | |
// ボタン2データ | |
Serial.print("data_btn1 = "); | |
for(int i=0; i<MAX_BTN; i++){ | |
Serial.print(data_btn2[i], HEX); | |
Serial.print(","); | |
} | |
Serial.println(""); | |
} |
初期設定
プログラムを書き込んだら、フラッシュメモリの初期化操作が必要になります。
初期化の方法は、USBで電源を供給し、ボタン1、ボタン2のそれぞれを10秒以上長押しし、LEDが3回点滅すれば完了です。
使用方法
学習
学習したいボタンを3秒以上(10秒以内)長押しして離します。
LEDが点滅状態になるので、元になるリモコンのボタンを押して、リモコンデータを送信します。
学習完了後、LEDが3回点滅して消灯します。
NECフォーマット以外のリモコンデータは認識されないため、LED点滅および学習待ち状態が続きます。
学習をキャンセルしたい場合は、Arduinoの電源を落とす必要があります。
最大学習データ数を超えて学習しようとすると、LEDが2回点滅して消灯状態になり、これ以上は学習できません。
送信
送信したいボタンを短く押して離します。
LEDが1回点滅し、リモコンデータが送信されます。
ボタンを押すたびに登録されているリモコンデータが順に送信されます。
未学習状態で送信操作を行うと、LEDが2回点滅し、リモコンデータは送信されません。
学習リセット
学習リセットしたいボタンを10秒以上長押しします。
リセット完了後、LEDが3回点滅して消灯します。
作成した簡易学習リモコンの活用例です。
コメント