rikoubou.hatenablog.com
↑前にESP32で赤外線通信を行う記事を書きましたが、実用性がありませんでした。
今回かなり苦しみましたが、ESP32同士でNEC方式での赤外線通信に成功したので備忘録もかねて記事にしておきます。
まだESP32で赤外線のライブラリがないようなので、困っている人の助けになれば幸いです。
1:材料
前回の記事と同じものを使います。赤外線LEDは940nmのものであれば、どれでも大丈夫だと思います。
・赤外線LED
3φ砲弾型赤外線LED 940nm LIR034の通販ならマルツオンライン
・赤外線受光器
リモコン受光モジュール RPM7138-Rの通販ならマルツオンライン
・適当な単色LED
・ESP32-DevKitC×2
・単3電池ボックス(必要に応じて)
・200Ω程度の抵抗×2
・ブレッドボード×2
2:受信側について
・ESP32_IRrecieve.h
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/ringbuf.h"
#include "esp32-hal.h"
#include "esp_intr.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "driver/rmt.h"
#include "driver/periph_ctrl.h"
#include "soc/rmt_reg.h"
#ifdef __cplusplus
}
#endif
#define RMT_RX_CHANNEL RMT_CHANNEL_0
#define RMT_CLK_DIV 100
#define RMT_TICK_10_US (80000000/RMT_CLK_DIV/100000)
#define rmt_item32_tIMEOUT_US 9500
#define NEC_DATA_ITEM_NUM 32
#define NEC_HEADER_HIGH_US 9050
#define NEC_HEADER_LOW_US 4510
#define NEC_BIT_ONE_HIGH_US 560
#define NEC_BIT_ONE_LOW_US (2250-NEC_BIT_ONE_HIGH_US)
#define NEC_BIT_ZERO_HIGH_US 560
#define NEC_BIT_ZERO_LOW_US (1120-NEC_BIT_ZERO_HIGH_US)
#define NEC_BIT_MARGIN 100
#define NEC_ITEM_DURATION(d) ((d & 0x7fff)*10/RMT_TICK_10_US)
class ESP32_IRrecieve {
public:
uint16_t address1;
uint16_t address2;
uint16_t command1;
uint16_t command2;
ESP32_IRrecieve(int recievePin);
void irRecieve();
void printIRData();
private:
bool parseItems(rmt_item32_t* item, int item_num);
bool headerCheck(rmt_item32_t* item);
bool bit1Check(rmt_item32_t* item);
bool bit0Check(rmt_item32_t* item);
bool inRangeCheck(int duration_ticks, int target_us, int margin_us);
};
・ESP32_IRrecieve.cpp
#include <Arduino.h>
#include "ESP32_IRrecieve.h"
RingbufHandle_t rb = NULL;
ESP32_IRrecieve::ESP32_IRrecieve(int recievePin) {
rmt_config_t rmt_rx;
rmt_rx.channel = RMT_RX_CHANNEL;
rmt_rx.gpio_num = (gpio_num_t) recievePin;
rmt_rx.clk_div = RMT_CLK_DIV;
rmt_rx.mem_block_num = 1;
rmt_rx.rmt_mode = RMT_MODE_RX;
rmt_rx.rx_config.filter_en = true;
rmt_rx.rx_config.filter_ticks_thresh = 100;
rmt_rx.rx_config.idle_threshold = rmt_item32_tIMEOUT_US / 10 * (RMT_TICK_10_US);
rmt_config(&rmt_rx);
rmt_driver_install(rmt_rx.channel, 1000, 0);
rmt_get_ringbuf_handler(rmt_rx.channel, &rb);
rmt_rx_start(rmt_rx.channel, 1);
}
void ESP32_IRrecieve::irRecieve() {
while(rb) {
size_t rx_size = 0;
rmt_item32_t* item = (rmt_item32_t*) xRingbufferReceive(rb, &rx_size, 1000);
if (item) {
bool res = parseItems(item, rx_size / 4);
if(res) {
printIRData();
vRingbufferReturnItem(rb, (void*) item);
} else {
vRingbufferReturnItem(rb, (void*) item);
break;
}
}
}
}
void ESP32_IRrecieve::printIRData() {
Serial.print("address1:");
Serial.print(address1, HEX);
Serial.print(" address2:");
Serial.print(address2, HEX);
Serial.print(" command1:");
Serial.print(command1, HEX);
Serial.print(" command2:");
Serial.println(command2, HEX);
}
bool ESP32_IRrecieve::parseItems(rmt_item32_t* item, int item_num) {
if(item_num < NEC_DATA_ITEM_NUM) {
Serial.print("Header Size Error! :");
Serial.println(item_num);
return false;
}
if(!headerCheck(item++)) {
Serial.println("headerCheck Error!");
return false;
}
uint16_t add1 = 0, add2 = 0, cmd1 = 0, cmd2 = 0;
for (int j = 0; j < 8; j++) {
if(bit1Check(item)) {
add1 |= (1 << j);
} else if(bit0Check(item)) {
add1 |= (0 << j);
} else {
Serial.println("First 8bit Error!");
return false;
}
item++;
}
for (int j = 0; j < 8; j++) {
if(bit1Check(item)) {
add2 |= (1 << j);
} else if(bit0Check(item)) {
add2 |= (0 << j);
} else {
Serial.println("Second 8bit Error!");
return false;
}
item++;
}
for(int j = 0; j < 8; j++) {
if(bit1Check(item)) {
cmd1 |= (1 << j);
} else if(bit0Check(item)) {
cmd1 |= (0 << j);
} else {
Serial.println("Third 8bit Error!");
return false;
}
item++;
}
for(int j = 0; j < 8; j++) {
if(bit1Check(item)) {
cmd2 |= (1 << j);
} else if(bit0Check(item)) {
cmd2 |= (0 << j);
} else {
Serial.println("Last 8bit Error!");
return false;
}
item++;
}
address1 = add1;
address2 = add2;
command1 = cmd1;
command2 = cmd2;
return true;
}
bool ESP32_IRrecieve::headerCheck(rmt_item32_t* item) {
if(inRangeCheck(item->duration0, NEC_HEADER_HIGH_US, NEC_BIT_MARGIN)
&& inRangeCheck(item->duration1, NEC_HEADER_LOW_US, NEC_BIT_MARGIN)) {
return true;
}
return false;
}
bool ESP32_IRrecieve::bit1Check(rmt_item32_t* item) {
if(inRangeCheck(item->duration0, NEC_BIT_ONE_HIGH_US, NEC_BIT_MARGIN)
&& inRangeCheck(item->duration1, NEC_BIT_ONE_LOW_US, NEC_BIT_MARGIN)) {
return true;
}
return false;
}
bool ESP32_IRrecieve::bit0Check(rmt_item32_t* item) {
if(inRangeCheck(item->duration0, NEC_BIT_ZERO_HIGH_US, NEC_BIT_MARGIN)
&& inRangeCheck(item->duration1, NEC_BIT_ZERO_LOW_US, NEC_BIT_MARGIN)) {
return true;
}
return false;
}
bool ESP32_IRrecieve::inRangeCheck(int duration_ticks, int target_us, int margin_us) {
if((NEC_ITEM_DURATION(duration_ticks) < (target_us + margin_us))
&& ( NEC_ITEM_DURATION(duration_ticks) > (target_us - margin_us))) {
return true;
} else {
return false;
}
}
・ESP32_IRrecieveTest.ino
#include "ESP32_IRrecieve.h"
const int RECV_PIN = 25;
ESP32_IRrecieve irObj(RECV_PIN);
void setup() {
Serial.begin(115200);
Serial.println("Init complete");
xTaskCreate(irRecieveTask,"irRecieveTask", 1048, NULL, 1, NULL);
}
void loop() {
}
void irRecieveTask(void *pvParameters) {
while(1) {
irObj.irRecieve();
}
}
回路については以下を参考に、PIN1にGPIO25、PIN2にGND、PIN3に3V3を繋ぎます。
プログラムを書き込んでからシリアルモニタを立ち上げると「Init complete」の文字が現れます。
その後にNEC方式の赤外線リモコンの信号を送ると、カスタムコードとコマンドが表示されます。
たとえば以下のリモコンのAボタンを押した場合、シリアルモニタには次のように表示されます。
オプトサプライ赤外線リモコン: センサ一般 秋月電子通商 電子部品 ネット通販
3:送信側について
・ESP32_IRsend.h
#include <Arduino.h>
#define HEADER_DATA_ON_NUM 345
#define HEADER_DATA_OFF_NUM 4500
#define SEND_DATA_ON_NUM 20
class ESP32_IRsend {
public:
ESP32_IRsend(int pinNum, byte customCode1, byte customCode2);
void sendCommand(byte data);
private:
int _ledPin;
byte _custom1;
byte _custom2;
void sendIRData(byte data);
void on(int num);
void waitMicroSeconds(uint32_t waitTime);
};
・ESP32_IRsend.cpp
#include "ESP32_IRsend.h"
ESP32_IRsend::ESP32_IRsend(int pinNum, byte customCode1, byte customCode2) {
_ledPin = pinNum;
_custom1 = customCode1;
_custom2 = customCode2;
pinMode(_ledPin, OUTPUT);
}
void ESP32_IRsend::sendCommand(byte data){
on(HEADER_DATA_ON_NUM);
waitMicroSeconds(HEADER_DATA_OFF_NUM);
sendIRData(_custom1);
sendIRData(_custom2);
sendIRData(data);
sendIRData(~data);
on(SEND_DATA_ON_NUM);
}
void ESP32_IRsend::sendIRData(byte data) {
for(int i = 0; i < 8; i++) {
on(SEND_DATA_ON_NUM);
switch(data & 1) {
case 0:
waitMicroSeconds(565);
break;
case 1:
waitMicroSeconds(1690);
break;
}
data = data >> 1;
}
}
void ESP32_IRsend::on(int num) {
for(int i = 0; i < num; i++) {
GPIO.out_w1ts = (1 << _ledPin);
waitMicroSeconds(9);
GPIO.out_w1tc = (1 << _ledPin);
waitMicroSeconds(17);
}
}
void ESP32_IRsend::waitMicroSeconds(uint32_t waitTime) {
uint32_t startTime = system_get_time();
uint32_t nowTime = system_get_time();
while ((nowTime - startTime) < waitTime) {
nowTime = system_get_time();
}
}
・ESP32_IRsendTest.ino
#include "ESP32_IRsend.h"
const int IR_LED = 25;
const byte CUSTOM1 = 0x10;
const byte CUSTOM2 = 0xef;
const int LED_PIN = 26;
ESP32_IRsend irSend(IR_LED, CUSTOM1, CUSTOM2);
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
digitalWrite(LED_PIN, HIGH);
irSend.sendCommand(0xf8);
digitalWrite(LED_PIN, LOW);
delay(3000);
}
回路はGPIO25を赤外線LEDに、GPIO26を普通のLEDに繋ぐだけです。それぞれのLEDに抵抗を入れるのを忘れないようにしましょう。
プログラムを書き込むと普通のLEDが3秒間隔に短く光ります。この光っている間に赤外線LEDから赤外線信号が送信されています。
4:送信・受信確認
受信側のESP32のシリアルモニタを立ち上げた状態で、送信側の赤外線LEDを受信側の受光器に近づけましょう。
すると2:受信側についての例で上げた画像と同じカスタムコードとコマンドが表示されるはずです。
受信結果がエラーばかりになる場合は、送信側の各種定数を調整すると受信成功率が上がります。
以上がESP32での赤外線通信方法になります。ネットで探してもESP32での赤外線の情報がほとんどなく、ここまでやるのに2週間近くかかってしまいました。正直ごり押ししてる部分もあるので、もっと賢いやり方があれば教えていただけると幸いです。
・参考
・esp-idf/infrared_nec_main.c at master · espressif/esp-idf · GitHub
・赤外線リモコンの実験
・赤外線リモコンの通信フォーマット