M5Stack 적외선 리모컨 디코더

8822 단어 ArduinoM5stack

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


시리얼 포트에는 수신한 적외선 리모컨의 커맨드를 디코드 결과를 출력하고 있습니다.
다른 리모컨과 함께 사용할 때 코드를 확인할 수 있습니다.


노트


  • Arduino 초보자이므로 보자 보지 않고 만든 곳도 있어, 이상한 코드일지도 모릅니다. 지적해 주시면 좋겠습니다.
  • 에어컨용 리모컨은 데이터 길이가 매우 길고 수신할 수 없는 것 같습니다.

  • 참고로 한 사이트


  • 【ESP32】NEC 방식으로 적외선 통신할 수 있는 프로그램을 만들어 보았다
  • 적외선 리모컨의 통신 형식
  • PIC16F84A로 만드는 AQUOS의 리모컨 제작
  • M5Stack에서 멀티태스킹 처리 수행
  • 기타 여러 가지
  • 좋은 웹페이지 즐겨찾기