M5Stack 적외선 리모컨 디코더
M5Stack용 적외선 리모컨 디코더
꽤 늦은 화제일지도 모르지만, M5Stack Arduino의 적외선 리모콘 디코더를 소개하겠습니다. 한 앱에서 적외선 리모컨을 사용하고 싶었는데, M5Stack에서 잘 사용할 수 있는 것을 찾을 수 없었기 때문에, 여러분 참고해 주셔서 자작했습니다.
NEC 형식, 가전협 형식, SONY 형식, 유니덴 형식 등을 디코딩할 수 있습니다. 정확한 디코드보다 코드를 구별할 수 있는 것을 주안으로 하고 있기 때문에, 약간 까다로운 디코드 방법이 되고 있습니다.
적외선 리모컨의 명령은 형식에 따라 길이가 다르므로 디코드 결과는 String 형식으로 얻을 수 있도록 했습니다.
적외선 센서 접속 방법
M5Stack의 윗면 커넥터에 적외선 센서를 연결합니다. +3.3V,GND,GPIO21을 사용하고 있습니다.
소스 코드
M5StackIRdecode.h
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
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
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
시리얼 포트에는 수신한 적외선 리모컨의 커맨드를 디코드 결과를 출력하고 있습니다.
다른 리모컨과 함께 사용할 때 코드를 확인할 수 있습니다.
노트
#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
#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;
}
#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);
}
}
}
참고로 한 사이트
Reference
이 문제에 관하여(M5Stack 적외선 리모컨 디코더), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/tt-yawata/items/92adb5ddb19ff1d07178텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)