M5Stack用赤外線リモコンデコーダ
かなり出遅れた話題かもしれませんが、M5Stack Arduinoの赤外線リモコンデコーダを紹介させていただきます。あるアプリで赤外線リモコンを使いたかったのですが、M5Stackでうまく使えるものが見つけられなかったため、諸々参考させていただき自作しました。
NECフォーマット、家電協フォーマット、SONYフォーマット、ユニデンフォーマットなどがデコードできます。正確なデコードよりもコードを区別できることを主眼にしていますので、若干トリッキーなデコード方法になっています。
赤外線リモコンのコマンドはフォーマットによって長さが違うのでデコード結果はString形式で得られるようにしました。
##赤外線センサ接続方法
M5Stackの天面のコネクタに赤外線センサを接続します。+3.3V,GND,GPIO21を使用しています。
##ソースコード
M5StackIRdecode.h
#ifndef _M5StackIRdecpde_h
#define _M5StackIRdecode_h
#include <M5Stack.h>
#include "driver/rmt.h"
#include "driver/periph_ctrl.h"
#include "soc/rmt_reg.h"
#define NEC_TIC 560
#define NEC_LEADER_HIGH NEC_TIC*16
#define NEC_LEADER_LOW NEC_TIC*8
#define NEC_LEADER_LOWR NEC_TIC*4
#define NEC_MARGIN 100
#define IGNORE_REPEAT
#define KDN_TIC 400
#define KDN_LEADER_HIGH KDN_TIC*8
#define KDN_LEADER_LOW KDN_TIC*4
#define KDN_MARGIN 100
#define SNY_TIC 600
#define SNY_LEADER_HIGH SNY_TIC*4
#define SNY_MARGIN 100
#define UNI_TIC 560
#define UNI_LEADER_HIGH UNI_TIC*4
#define UNI_LEADER_LOW UNI_TIC*2
#define UNI_MARGIN 100
#define MB_TIC 300
#define MB_LEADER_HIGH MB_TIC
#define MB_LEADER_LOW MB_TIC*2
#define MB_MARGIN 100
#define TIC_MAX (NEC_LEADER_HIGH*2)
#define TIC_MIN (MB_TIC/2)
class M5StackIRdecode{
public:
M5StackIRdecode();
boolean parseData(rmt_item32_t *item, size_t rxSize);
String _ircode;
private:
String checkLeader(rmt_item32_t item);
int16_t decodeBit(int16_t d0,int16_t d1, boolean dm);
String rmttmp;
};
#endif
###M5StackIRdecode.cpp
#include "M5StackIRdecode.h"
M5StackIRdecode::M5StackIRdecode(){};
String M5StackIRdecode::checkLeader(rmt_item32_t item){
if ((item.level0 != 0) || (item.level1 != 1)) {
return "";
}
if( item.duration0 > (NEC_LEADER_HIGH-NEC_MARGIN)){
if( item.duration1> (NEC_LEADER_LOW-NEC_MARGIN)){
return "N"; //NEC
} else if( item.duration1> (NEC_LEADER_LOWR-NEC_MARGIN)){
#ifdef IGNORE_REPEAT
return "";
#else
return "R";
#endif
} else {
return "";
}
} else if((item.duration0 > (KDN_LEADER_HIGH-KDN_MARGIN))
&& (item.duration1 > (KDN_LEADER_LOW-KDN_MARGIN ))){
return "K"; // KADEN
} else if( item.duration0 > (SNY_LEADER_HIGH-SNY_MARGIN)){
return "S"; // SONY
} else if((item.duration0 > (UNI_LEADER_HIGH-UNI_MARGIN))
&& (item.duration1 > (UNI_LEADER_LOW-UNI_MARGIN ))){
return "U"; //UNIDEN
} else if(item.duration0 > (NEC_TIC-NEC_MARGIN)){ // Ignore Victor repeat
return "" ;
}else if((item.duration0 > (MB_LEADER_HIGH-MB_MARGIN))
&& (item.duration1 > (MB_LEADER_LOW-MB_MARGIN ))){
return "M"; // Mitsubishi
}
return "";
}
int16_t M5StackIRdecode::decodeBit(int16_t d0,int16_t d1, boolean dm){
int16_t tl,ts;
if((d0<TIC_MIN)||(d1<TIC_MIN)||(d0>TIC_MAX)){
return -1;
}
ts=(d0>d1) ? d1 : d0;
tl=(d0>d1) ? d0 : d1;
if(dm){ // mitsubishi
return (tl-ts)>(ts*4) ? 1 : 0;
}else{ // nec kaden sony
return (tl-ts)>(ts/2) ? 1 : 0;
}
}
boolean M5StackIRdecode::parseData(rmt_item32_t *item, size_t rxSize) {
int16_t index = 0;
int16_t dbit,i;
uint8_t dbyte;
boolean dmode;
rmttmp=checkLeader(item[0]);
if(rmttmp.equals("")){
return false;
}
if(rmttmp.equals("M")){
dmode=true;
index=0;
}else{
dmode=false;
index=1;
}
for(i=0, dbyte=0; item[index].duration1>0;index++){
dbit=decodeBit(item[index].duration0,item[index].duration1,dmode);
if(dbit<0){
return false;
}
dbyte=dbyte<<1|dbit;
i++;
if(i>7){
if(dbyte<0x10){
rmttmp.concat('0');
}
rmttmp.concat(String(dbyte,HEX));
dbyte=0;
i=0;
}
}
if(index==1){ // no data
return false;
}
if(i>2){ // for SONY format
rmttmp.concat(String(dbyte,HEX));
}
_ircode=rmttmp;
return true;
}
##サンプルアプリ
###rmt_sample1.ino
#include <M5Stack.h>
#include "freertos/task.h"
#include "time.h"
#include "M5stackIRdecode.h"
const rmt_channel_t channel = RMT_CHANNEL_0;
const gpio_num_t irPin = GPIO_NUM_21;
RingbufHandle_t buffer = NULL;
M5StackIRdecode irDec;
const String up = "N00ff18e7";
const String down = "N00ff4ab5";
const String left = "N00ff10ef";
const String right = "N00ff5aa5";
const String home = "N00ff38c7";
String ircode = "";
int16_t x=160, y=120;
uint16_t t=0;
void init_rmt(rmt_channel_t channel, gpio_num_t irPin){
rmt_config_t rmtConfig;
rmtConfig.rmt_mode = RMT_MODE_RX;
rmtConfig.channel = channel;
rmtConfig.clk_div = 80;
rmtConfig.gpio_num = irPin;
rmtConfig.mem_block_num = 1;
rmtConfig.rx_config.filter_en = 1;
rmtConfig.rx_config.filter_ticks_thresh = 255;
rmtConfig.rx_config.idle_threshold = 10000;
rmt_config(&rmtConfig);
rmt_driver_install(rmtConfig.channel, 2048, 0);
rmt_get_ringbuf_handle(channel, &buffer);
rmt_rx_start(channel, 1);
}
void setup() {
// put your setup code here, to run once:
M5.begin(true, false, true);
M5.Lcd.clear(BLACK);
M5.Lcd.setTextSize(2);
M5.Lcd.fillCircle(x, y, 10, WHITE);
init_rmt(channel, irPin);
xTaskCreate(rmt_task, "rmt_task", 4096, NULL, 10, NULL);
}
void loop() {
// put your main code here, to run repeatedly:
M5.update();
M5.Lcd.setCursor(0, 0);
M5.Lcd.printf("%05d",t++);
if(ircode.length()){
Serial.println(ircode);
M5.Lcd.fillCircle(x, y, 10, BLACK);
if(ircode.equals(up)){
y=(y>10) ? y-10 : y;
} else if(ircode.equals(down)){
y=(y<220) ? y+10 : y;
} else if(ircode.equals(left)){
x=(x>10) ? x-10 : x;
} else if(ircode.equals(right)){
x=(x<310) ? x+10 : x;
} else if(ircode.equals(home)){
x=160;
y=120;
t=0;
}
M5.Lcd.fillCircle(x, y, 10, WHITE);
ircode="";
}
}
void rmt_task(void *arg){
size_t rxSize = 0;
rmt_item32_t * item;
while(1){
item = (rmt_item32_t *)xRingbufferReceive(buffer, &rxSize, 10000);
if (item) {
if((irDec.parseData(item, rxSize))&&ircode==""){
ircode=irDec._ircode;
}
vRingbufferReturnItem(buffer, (void*) item);
}
}
}
サンプルアプリは、赤外線リモコンでLCDに表示された白丸を上下左右に移動させるというものです。
メインのタスクとは別タスクで赤外線受信タスクを動作させています。
画面左上の数字はメインタスクで動作しているループカウンタです。赤外線受信中でもカウンタは止まらないことが分かると思います。テストでは こちらのリモコンを使用しています。
コードはNECタイプです。
ボタン | コード | 動作 |
---|---|---|
1 | N00ff18e7 | ↑ |
8 | N00ff4ab5 | ↓ |
4 | N00ff10ef | ← |
6 | N00ff5aa5 | → |
5 | N00ff38c7 | Home |
シリアルポートには受信した赤外線リモコンのコマンドをデコード結果を出力しています。
他のリモコンで使用するときはにコードを確認することができます。
##注記
- Arduino初心者ですので見よう見まねで作ったところもあり、変なコードかもしれません。ご指摘いただければと思います。
- エアコン用のリモコンはデータ長がとても長く、受信しきれないようです。
##参考にさせていただいたサイト