Prism

2019年8月11日 星期日

積木式免焊接 記憶型空調紅外線轉譯器 - 04 紀錄 & 修正篇

< 本文請勿轉載,謝謝 ! >

本文紀錄網友回報的適用冷氣空調機型,另程式碼貼於下方,有問題可方便討論除錯修正。

適用機型:

1. 三菱電機:機型 GE25NA / 遙控器 KM10D


記憶型空調紅外線轉譯器 程式碼

24行:移除雙斜線,會進入除錯模式,可在Arduino IDE 監控視窗查看轉譯器訊號接收、發射狀況
25行:移除雙斜線,紅外線發射時主板會有閃燈及嗶聲,初期可監聽看是否穩定執行
26行:移除雙斜線,焊接的擴展板S1按鍵可重發行目前的紅外線信號,方便測試用(電子積木版勿開啟,會造成連續誤觸發)

程式下載 (密碼藍字 mygraphpaper.com https://www.mygraphpaper.com/download/MPG_TR_Translator.rar
/*
   IRremote
   An IR LED must be connected to Arduino PWM pin 3.
   Copyright Ken Shirriff
   http://arcfn.com

   MPG_IR_Translator Ver.1.0  2019.8.11 By MGP. 
   Only for Air conditioner.  https://www.mygraphpaper.com blog.  
   
   Do not reprint,please.
   This site is only for personal use, pay attention to safety when using, please.
   Disclaimer : This site will not bear any liability for any errors or omissions.
                If you agree, use the contents of this site.
   請勿轉載.
   本網站內容及程式僅提供個人非營利使用, 使用時請注意安全.
   免責聲明 : 本網站不會對任何錯誤或遺漏承擔任何賠償責任, 若您同意再使用本網站、程式內容。

   請注意執行本程式前, 請使用同壓縮檔內檔案覆蓋 IRremoteInt.h , 或手動修改參數值如下:
   Line 43 : #define RAWBUF  624    // 預設儲存信號長度為 101 個 , 但冷氣機編碼超過, 需加大
   Line 52 : int rawlen;            // 冷氣機編碼的大小超過 Byte 255計數, 改為 int 
   Line 98 : #define _GAP    30000  // 預設信號結束的空白時間 5000, 但太小導致取樣不到 RPT_SPACE 的間隔參數, 需加大
*/

//#define MPG_DEBUG       //Arduino監控視窗除錯用, 若您的紅外線信號記錄不到可打開監看取樣值.
//#define MPG_SendHit     //紅外線傳送時會發出提示音及閃燈, 但會延遲發射時間
//#define MPG_SOLDER      //焊接版本可打開以使用擴展板S1測試按鍵, 電子積木版勿開會造成誤觸發.

//各家紅外線編碼不同, 靈敏度調整用, 非必要請勿修改
#define Tolerance_scope       4       //MARK, SPACE 取樣誤差判斷容忍範圍, 4X50us=200us
#define MARK_EXCESS_Factor    2       //MARK, SPACE MARK_EXCESS 參數取樣誤差修正 100us/2=50us

//--------------------------------------------
#include <IRremote.h>
#include <EEPROM.h>

#define recvPin              2        //紅外線接收在 Pin2; 發射內定在 Pin3
#define Pin9_SW              9        //Pin9  擴展板S1按鍵 ,發射當前紅外線信號測試用
#define Recv_Enable_LED_Pin  10       //Pin10 擴展板啟動指示燈
#define LED_Buzzer_Pin       11       //Pin11 動作指示燈及聲音, Pin13同動作
#define khz                  38       //使用 38kHz 頻率發射 RAW
#define SMax                 10       //MARK, SPACE 取樣排序陣列大小

//米家APP內創維機頂盒 遙控器數字 "0~9 確定" 的NEC編碼
#define IR_NUM_SIZE  11           //使用 "0~9 確定" ,共11個按鍵 (注意 變更這個值會初始化歸零 EEPROM, 清除已記錄儲存的紅外線按鍵信號)
#define N00     0x80BFE11E        //數字盤 0
#define N01     0x80BF49B6
#define N02     0x80BFC936
#define N03     0x80BF33CC
#define N04     0x80BF718E
#define N05     0x80BFF10E
#define N06     0x80BF13EC
#define N07     0x80BF51AE
#define N08     0x80BFD12E
#define N09     0x80BF23DC
#define N10     0x80BF738C        //數字盤 確定

struct NUM_MAP {                  //將 "0~9 確定" 的紅外線編碼對應成數字 0-10 ,方便比對搜尋
  unsigned long IR_CODE;
  byte INDEX_NUM;
};
struct NUM_MAP IR_MAP[IR_NUM_SIZE] = {{N00, 0},  {N01, 1},  {N02, 2},  {N03, 3},  {N04, 4},  {N05, 5},  {N06, 6},  {N07, 7},  {N08, 8},  {N09, 9},  {N10, 10}};
      
struct IR_RAW_type {              //原生信號參數結構 (注意 變更這個結構大小會初始化歸零 EEPROM, 清除已記錄儲存的紅外線按鍵信號)
  char RAW_TYPE;                  //紅外線編碼類別 UNKNOWN, NEC, SONY ...
  unsigned int RAW_HDR_MARK ;     //初始信號 RAW_HDR_MARK + RAW_HDR_SPACE
  unsigned int RAW_HDR_SPACE ;
  unsigned int RAW_BIT_MARK ;     //0 與 1 的信號組合
  unsigned int RAW_ONE_SPACE ;    //1 = RAW_BIT_MARK + RAW_ONE_SPACE
  unsigned int RAW_ZERO_SPACE ;   //0 = RAW_BIT_MARK + RAW_ZERO_SPACE
  unsigned int rawData_Bits ;     //傳送位元數, 可能會過 255 (624/2=312)
  unsigned int rawHole[8][3] ;    //特殊長度的 位置,數值(MARK+SPACE), 如 PRT_MARK+RPT_SPACE
  byte rawHole_Count ;            //rawHole陣列大小
  byte rawData_Count ;            //rawData陣列大小
  byte rawData[39] ;              //可記錄至 39X16 = 624bit, 要記錄10組信號, EEPROM只有1024byte, 總結構大小不能超過 1024/10 = 102byte
};
struct IR_RAW_type IR_RAW;

bool EnterSetup = false;            //是否進入設定模式
byte SetupIndex = 0;                //目前的設定數字
byte IR_MAP_Index;                  //儲存找到對應的 0-9

IRrecv irrecv(recvPin);             //設定接收用 Pin
IRsend irsend;                      //發射用
decode_results results;             //儲存接收結果用


//以下為程式碼--------------------------------------------------------------------------------------------------------------------------


//控制 動作指示燈及聲音 Pin11、13
void ST_LED_Buzzer (int Send_Status) {
  while ( Send_Status > 0 ) {
    if (Send_Status >= 5) {
      digitalWrite(LED_Buzzer_Pin, HIGH);
      digitalWrite(13, HIGH);
      delay(400);
      digitalWrite(LED_Buzzer_Pin, LOW);
      digitalWrite(13, LOW);
      delay(400);
      Send_Status = Send_Status - 5;
    } else {
      digitalWrite(LED_Buzzer_Pin, HIGH);
      digitalWrite(13, HIGH);
      delay(150);
      digitalWrite(LED_Buzzer_Pin, LOW);
      digitalWrite(13, LOW);
      delay(150);
      Send_Status = Send_Status - 1;
    }
  }
}


//發送SONY紅外線編碼陣列 (SONY編碼較特殊, 變動MARK)
void SONYIRSend(byte SendRAWData[], unsigned int SendRAWData_Bits) {
  unsigned long SONYIRCode;
  SONYIRCode = ( (((unsigned long)SendRAWData[0]) << 24) | (((unsigned long)SendRAWData[1]) << 16) | (((unsigned long)SendRAWData[2]) << 8) | (((unsigned long)SendRAWData[3])) );
  irsend.sendSony(SONYIRCode , SendRAWData_Bits);
}

//發送紅外線編碼陣列, 位元由左至右發送, 資料共計 rawData_Bits 位元
//void RAWIRSend(發送陣列資料, 陣列大小, 發射位元數, 發射頻率)
void RAWIRSend(byte SendRAWData[], byte SendRAWData_Len, unsigned int SendRAWData_Bits, byte hz) {
  unsigned int rawData_Bits_Count =0;
  byte Mask = 1; 
  byte rawHole_ind = 0;
    
  irsend.enableIROut(hz);  // khz
  irsend.space(max(IR_RAW.RAW_HDR_MARK,IR_RAW.RAW_HDR_SPACE));  //Initial signal

  irsend.mark(IR_RAW.RAW_HDR_MARK);
  irsend.space(IR_RAW.RAW_HDR_SPACE);
  for (unsigned int i = 0; i < SendRAWData_Len; i++) {
    for (Mask = B10000000; Mask > 0; Mask >>= 1) {
      if (rawData_Bits_Count < SendRAWData_Bits) {
        if ((rawHole_ind < IR_RAW.rawHole_Count) && (rawData_Bits_Count == IR_RAW.rawHole[rawHole_ind][0])){
            //Special length
            irsend.mark(IR_RAW.rawHole[rawHole_ind][1]);
            irsend.space(IR_RAW.rawHole[rawHole_ind][2]);
            rawHole_ind ++ ;
        } else {
            if (SendRAWData[i] & Mask) {
            //Bit 1
            irsend.mark(IR_RAW.RAW_BIT_MARK);
            irsend.space(IR_RAW.RAW_ONE_SPACE);
            }
            else {
              //Bit 0
              irsend.mark(IR_RAW.RAW_BIT_MARK);
              irsend.space(IR_RAW.RAW_ZERO_SPACE);
            }
        }
        rawData_Bits_Count++;
      }
    }
  }
  irsend.space(max(IR_RAW.RAW_HDR_MARK,IR_RAW.RAW_HDR_SPACE));  //Stop signal
}


//取樣陣列右移排大小
unsigned int Probability[SMax], Temporary[SMax];  //取樣陣列 數值, 次數
void Shift_Val(unsigned int Val){
  byte i,j;
  
  for(i = 0; i < SMax; i++) {    //將較小數值向後移
    if ((i>0) && (Val == Probability[i-1])) break;
    if (Val > Probability[i]) {
      for(j = SMax-1; j >= i+1; j--) {
        Probability[j] = Probability[j-1];
        Temporary[j] = Temporary[j-1];
      }
      Probability[i] = Val;
      Temporary[j] = 0;
      break;
    } 
  }

  for(i = 0; i<SMax; i++) {    //記錄出現次數
    if (Val == Probability[i]) {
      Temporary[i]++;
      break;
    }
  }
}


//統計原生訊號出現次數
//FindMaxCount(原始編碼訊號, 處理編碼訊號陣列起始位置, 是否需比對 MARK 值, 判斷是ONE_SPACE的判斷值)
unsigned int FindMaxCount(decode_results *results, int StartPos, unsigned int Check_RAW_BIT_MARK, unsigned int MAX_RAW_ZERO_SPACE) {
  unsigned int Return_MAX_Value = 0;  //回傳值
  
  for (byte i=0 ; i < SMax ; i++) {
    Probability[i]=0; //初始陣列
    Temporary[i]=0;
  }
  
  for (int i = StartPos; i < results->rawlen; i+=2) {   //results->rawlen 為int
    if ((Check_RAW_BIT_MARK == 0) || (results->rawbuf[i-1] == Check_RAW_BIT_MARK)){ //是否需比對 MARK 值
      if ((MAX_RAW_ZERO_SPACE == 0) || (results->rawbuf[i] > MAX_RAW_ZERO_SPACE)){ //判斷 Space 長的判斷值     
        Shift_Val(results->rawbuf[i]);    //取樣陣列排大小
      }
    }
  }

  //從最小值找向連續的最大值, 出現最多次的
  unsigned int Last_Count = 0;
  for (int i = SMax-1; i>=0; i--) {
    if ((Probability[i] >= Tolerance_scope) && (Temporary[i] > 0)) {   //有信號
      if (Return_MAX_Value == 0) {
        Return_MAX_Value = Probability[i];
        Last_Count = Temporary[i];
      } else {
          if ((Probability[i] - Probability[i+1]) <= Tolerance_scope) { //連續誤差訊號
            if (Temporary[i] >= Last_Count) {
              Return_MAX_Value = Probability[i];
              Last_Count = Temporary[i];
            }
          } else {
            break;    //信號不連續     
          }
      }
    }
  }    
 
  return Return_MAX_Value;
}


//取得原生訊號參數及重新編碼
void EncodeRAWCode (decode_results *results) {
  IR_RAW.rawData_Count = 0;
  IR_RAW.rawHole_Count = 0 ;  
  IR_RAW.RAW_TYPE = results->decode_type;
  
#ifdef MPG_DEBUG  
  Serial.println(F(""));
  Serial.print(F("IR Struct Size (byte) : "));  
  Serial.println(sizeof(IR_RAW),DEC);
  Serial.print(F("IR TYPE : "));  
  Serial.println(IR_RAW.RAW_TYPE,DEC);   //4 SONY 
#endif
 
  //SONY使用40Khz, 且是改變 MARK的長度, SPACE不變, 直接使用內建函數解碼, 用38K可以接收但失敗率較大
  //SONY發送為 unsigned long 4Byte, 拆解入 IR_RAW.rawData 存放
  if (IR_RAW.RAW_TYPE == SONY) {
    IR_RAW.RAW_HDR_MARK = 2400;
    IR_RAW.RAW_HDR_SPACE = 600;
    IR_RAW.RAW_BIT_MARK = 600;
    IR_RAW.RAW_ZERO_SPACE = 600;
    IR_RAW.RAW_ONE_SPACE = 1200;    
    IR_RAW.rawData_Bits = results->bits;
    IR_RAW.rawData_Count = 4;
    IR_RAW.rawData[0] = (byte)((results->value & 0xff000000) >> 24);
    IR_RAW.rawData[1] = (byte)((results->value & 0x00ff0000) >> 16);
    IR_RAW.rawData[2] = (byte)((results->value & 0x0000ff00) >> 8);   
    IR_RAW.rawData[3] = (byte)((results->value & 0x000000ff));
  } else {
      IR_RAW.rawData_Bits = (results->rawlen-3)/2;  //扣除頭2尾1, 2個信號為 1bit
        
      IR_RAW.RAW_HDR_MARK = results->rawbuf[1];  //第一個訊號[0]無效,[1]為RAW_HDR_MARK
      IR_RAW.RAW_HDR_SPACE = results->rawbuf[2]; //[2]RAW_HDR_SPACE
      
    #ifdef MPG_DEBUG  
      Serial.println(F(""));
      Serial.println(F("// My Data Start , Mark & Ctrl-C Copy ---------------")); 
      Serial.println(F(""));  
      Serial.print(F("#define RAW_HDR_MARK    "));
      Serial.print(IR_RAW.RAW_HDR_MARK * USECPERTICK - MARK_EXCESS/MARK_EXCESS_Factor, DEC);  
      Serial.println(F(""));  
      Serial.print(F("#define RAW_HDR_SPACE   "));
      Serial.print(IR_RAW.RAW_HDR_SPACE * USECPERTICK + MARK_EXCESS/MARK_EXCESS_Factor, DEC); 
    #endif   
    
      //取得 BIT_MARK , 3開始的奇數位置為 RAW_BIT_MARK 值
      IR_RAW.RAW_BIT_MARK = FindMaxCount(results , 3, 0, 0);
      
    #ifdef MPG_DEBUG    
      Serial.println(F(""));   
      Serial.print(F("#define RAW_BIT_MARK    "));
      Serial.print(IR_RAW.RAW_BIT_MARK * USECPERTICK - MARK_EXCESS/MARK_EXCESS_Factor, DEC);
      Serial.print(F("     // "));
      for(byte i = 0; i < SMax; i++) {
        if (Probability[i] > 0 ) {
          Serial.print(F("( "));
          Serial.print(Probability[i] * USECPERTICK, DEC);
          Serial.print(F(" , "));
          Serial.print(Temporary[i] , DEC);    
          Serial.print(F(" ), "));     
        }
      }
      Serial.print(F(" times."));  
    #endif
    
      //取得 ZERO_SPACE , 最小值範圍中最多出現次數, 4開始的偶數位置為 RAW_ZERO_SPACE 值
      IR_RAW.RAW_ZERO_SPACE = FindMaxCount(results , 4, IR_RAW.RAW_BIT_MARK, 0);  
    
    #ifdef MPG_DEBUG  
      Serial.println(F("")); 
      Serial.print(F("#define RAW_ZERO_SPACE  "));
      Serial.print(IR_RAW.RAW_ZERO_SPACE * USECPERTICK + MARK_EXCESS/MARK_EXCESS_Factor, DEC);
      Serial.print(F("     // "));
      for(byte i = 0; i < SMax; i++) {
        if (Probability[i] > 0 ) {
          Serial.print(F("( "));
          Serial.print(Probability[i] * USECPERTICK, DEC);
          Serial.print(F(" , "));
          Serial.print(Temporary[i] , DEC);    
          Serial.print(F(" ), "));     
        }
      }
      Serial.print(F(" times. (in RAW_BIT_MARK)")); 
    #endif
    
      //取得 ONE_SPACE , 最小值範圍中最多出現次數, 4開始的偶數位置為 RAW_ONE_SPACE 值
      //ONE_SPACE 的下限值, 若仍太小可漸加大Tolerance_scope測試
      unsigned int MAX_RAW_ZERO_SPACE = IR_RAW.RAW_ZERO_SPACE + Tolerance_scope;
      IR_RAW.RAW_ONE_SPACE = FindMaxCount(results , 4, IR_RAW.RAW_BIT_MARK, MAX_RAW_ZERO_SPACE); 
    
    #ifdef MPG_DEBUG  
      Serial.println(F("")); 
      Serial.print(F("#define RAW_ONE_SPACE   "));
      Serial.print(IR_RAW.RAW_ONE_SPACE * USECPERTICK + MARK_EXCESS/MARK_EXCESS_Factor, DEC);
      Serial.print(F("     // "));
      for(byte i = 0; i < SMax; i++) {
        if (Probability[i] > 0 ) {
          Serial.print(F("( "));
          Serial.print(Probability[i] * USECPERTICK, DEC);
          Serial.print(F(" , "));
          Serial.print(Temporary[i] , DEC);    
          Serial.print(F(" ), "));     
        }
      }
      Serial.print(F(" times. (in RAW_BIT_MARK)"));  
    #endif  
      
      //將 Mark+Space 的 0 1 編碼為 Byte 型態存放
      byte rawTemp = 0; //暫存值
      byte Bits_Count = 0;  //目前 Bit 計數
      MAX_RAW_ZERO_SPACE = (IR_RAW.RAW_ONE_SPACE + IR_RAW.RAW_ZERO_SPACE) / 2;  //取中間值作 0 1 判斷
      for (int i = 4; i < results->rawlen; i+=2) {
        Bits_Count++;
        rawTemp = rawTemp << 1;
        if (results->rawbuf[i] > MAX_RAW_ZERO_SPACE) rawTemp = rawTemp | 1;
        if (Bits_Count==8) {    //完成 1 Byte 存放入rawData
          IR_RAW.rawData[IR_RAW.rawData_Count] = rawTemp;
          Bits_Count = 0;
          IR_RAW.rawData_Count ++;
          rawTemp = 0;
        }
      }
    
      if (Bits_Count != 0) {    //不足 1Byte 的補完
          for (byte i = (Bits_Count+1); i <= 8; i++) {
            rawTemp = rawTemp << 1;
          }
          IR_RAW.rawData[IR_RAW.rawData_Count] = rawTemp;
          IR_RAW.rawData_Count ++;
      }
    
      //掃描特殊長度
      byte Max_rawHole = sizeof(IR_RAW.rawHole)/sizeof(unsigned int)/3;    
      for (int i = 3; i < results->rawlen-1; i++) {
        if(i%2==1){   //MARK
              if (!((results->rawbuf[i]>=IR_RAW.RAW_BIT_MARK-Tolerance_scope) && (results->rawbuf[i]<=IR_RAW.RAW_BIT_MARK+Tolerance_scope))) {
                if (IR_RAW.rawHole_Count >= Max_rawHole) {
                  results->overflow = true;   //特殊編碼太多, 無空間可記錄, 報溢位錯誤
                  break;
                }
                IR_RAW.rawHole[IR_RAW.rawHole_Count][0] = (i-3)/2;  //0起算
                IR_RAW.rawHole[IR_RAW.rawHole_Count][1] = results->rawbuf[i] * USECPERTICK;
                IR_RAW.rawHole[IR_RAW.rawHole_Count][2] = results->rawbuf[i+1] * USECPERTICK;
                IR_RAW.rawHole_Count++;
                i++;
              }
        } else {    //SPACE
              if (results->rawbuf[i]>(IR_RAW.RAW_ONE_SPACE + Tolerance_scope)) {
                if (IR_RAW.rawHole_Count >= Max_rawHole) {
                  results->overflow = true;   //特殊編碼太多, 無空間可記錄, 報溢位錯誤
                  break;
                }
                IR_RAW.rawHole[IR_RAW.rawHole_Count][0] = (i-4)/2;   //0起算      
                IR_RAW.rawHole[IR_RAW.rawHole_Count][1] = results->rawbuf[i-1] * USECPERTICK;
                IR_RAW.rawHole[IR_RAW.rawHole_Count][2] = results->rawbuf[i] * USECPERTICK;
                IR_RAW.rawHole_Count++;
              }      
        }
      }

      //做硬體取樣延遲誤差修正
      IR_RAW.RAW_HDR_MARK    = IR_RAW.RAW_HDR_MARK * USECPERTICK - MARK_EXCESS/MARK_EXCESS_Factor;   //次數 X 間隔(50us) - 誤差
      IR_RAW.RAW_HDR_SPACE   = IR_RAW.RAW_HDR_SPACE * USECPERTICK + MARK_EXCESS/MARK_EXCESS_Factor;
      IR_RAW.RAW_BIT_MARK    = IR_RAW.RAW_BIT_MARK * USECPERTICK - MARK_EXCESS/MARK_EXCESS_Factor;
      IR_RAW.RAW_ONE_SPACE   = IR_RAW.RAW_ONE_SPACE * USECPERTICK + MARK_EXCESS/MARK_EXCESS_Factor; 
      IR_RAW.RAW_ZERO_SPACE  = IR_RAW.RAW_ZERO_SPACE * USECPERTICK + MARK_EXCESS/MARK_EXCESS_Factor;  
  }
#ifdef MPG_DEBUG  
  Serial.println(F(""));
  Serial.println(F(""));  
  Serial.print(F("int rawData_Bits = ")); 
  Serial.print(IR_RAW.rawData_Bits, DEC); 
  Serial.print(F(" ;"));   
  Serial.println(F(""));   
  Serial.print(F("byte rawData["));
  Serial.print(IR_RAW.rawData_Count, DEC);
  Serial.print(F("] = { "));

  for (byte i = 0;  i < IR_RAW.rawData_Count;  i++) {
    Serial.print(F("0x"));
    Serial.print(IR_RAW.rawData[i], HEX);
    if ( i < IR_RAW.rawData_Count-1 ) Serial.print(F(", "));
  }
  Serial.println(F(" };"));

  Serial.print(F("unsigned int rawHole["));
  Serial.print(IR_RAW.rawHole_Count, DEC);
  Serial.print(F("][3] = { "));

  for (byte i = 0;  i < IR_RAW.rawHole_Count;  i++) {
    Serial.print(F("{ "));
    for (byte j = 0;  j < 3;  j++) { 
      Serial.print(IR_RAW.rawHole[i][j], DEC); 
      if ( j < 2 ) Serial.print(F(", ")); 
    }
    Serial.print(F(" } "));
    if ( i < IR_RAW.rawHole_Count-1 ) Serial.print(F(", "));
  }
  Serial.println(F("};"));
  Serial.println(F(""));
    
  Serial.println(F("// My Data End --------------- By MGP."));  
  Serial.println(F(""));  
#endif
}


//初始設定
void setup() {
  Serial.begin(9600);                     //與電腦序列埠通訊連接用
  irrecv.enableIRIn();                    //充許接收紅外線信號

  SetupIndex = 0;  
  IR_RAW.rawData_Count = 0; 
  if ((EEPROM.read(1022) != IR_NUM_SIZE) || (EEPROM.read(1023) != sizeof(IR_RAW))) {   //第一次使用, 初始化歸零 EEPROM
    Serial.println(F("Wait for initial EEPROM ..."));     
    for(int i =0; i<1022; i++) EEPROM.write(i,0);
    EEPROM.write(1022,IR_NUM_SIZE);         //標記 EEPROM 結構個數
    EEPROM.write(1023,sizeof(IR_RAW));      //標記 EEPROM 結構大小      
  }

  pinMode(Pin9_SW, INPUT);                //初始 擴展板S1按鍵 腳位
  pinMode(Recv_Enable_LED_Pin, OUTPUT);   //初始 啟動指示燈 腳位
  pinMode(LED_Buzzer_Pin, OUTPUT);        //初始 動作指示燈及聲音 腳位
  pinMode(13, OUTPUT);                    //初始 開發板指示燈 腳位
   
  digitalWrite(Recv_Enable_LED_Pin, HIGH);//打開 啟動擴展板指示燈, 程式作用中, 要關閉請改 LOW
  digitalWrite(LED_Buzzer_Pin, LOW);      //關閉 動作指示燈及聲音
  digitalWrite(13, LOW);                  //關閉 主板指示燈, 相容電子積木版用, 同 LED_Buzzer_Pin

  ST_LED_Buzzer (1);                      //通知已啟動開始接收
  Serial.println(F("MPG IR-Translator is Ready."));   
}


//主程式迴圈
void loop() {
#ifdef MPG_SOLDER    //焊接擴展版 S1按鈕
  if (digitalRead(Pin9_SW) == LOW) {  
    Serial.println(sizeof(IR_RAW));
    delay(100);
    ST_LED_Buzzer (1);
    RAWIRSend(IR_RAW.rawData, IR_RAW.rawData_Count, IR_RAW.rawData_Bits, khz);
    #ifdef MPG_DEBUG
      show_RAW_IR();
    #endif
    irrecv.enableIRIn();
    delay(100);   
  }
#endif

  if (irrecv.decode(&results)) {         //是否偵測到紅外線信號
    IR_MAP_Index = IR_NUM_SIZE;          //預設IR_MAP_Index
    if (results.decode_type == NEC) {    //判斷為哪個數字鍵, 只偵測 NEC 編碼
#ifdef MPG_DEBUG
      Serial.print(F("NEC Code : "));       //提供序列埠監看
      Serial.print(results.value, HEX);
#endif      
      for (byte i = 0; i < IR_NUM_SIZE; i++) {
        if (results.value == IR_MAP[i].IR_CODE) {
          IR_MAP_Index = IR_MAP[i].INDEX_NUM;
          break;
        }
      }
 #ifdef MPG_DEBUG     
      Serial.print(F("  Map Index : "));
      Serial.println(IR_MAP_Index, DEC);
#endif     
      if (IR_MAP_Index < IR_NUM_SIZE) { //是否有找到對應的數字鍵
        delay(100);                     //收發間隔,穩定用
        switch (IR_MAP_Index) {         //不合併,保留每個數字對應的程式操作空間
          case 0:
            if(EnterSetup) {
              ST_LED_Buzzer (10);
              SetupIndex = IR_MAP_Index;
            } else {          
              #ifdef MPG_SendHit    
                ST_LED_Buzzer (1);     //蜂鳴器先叫, 以免和冷氣混在一起
              #endif
              EEPROM_readAnything(IR_MAP_Index*sizeof(IR_RAW), IR_RAW);
              if (IR_RAW.RAW_TYPE == SONY) SONYIRSend(IR_RAW.rawData, IR_RAW.rawData_Bits);
              else RAWIRSend(IR_RAW.rawData, IR_RAW.rawData_Count, IR_RAW.rawData_Bits, khz); 
              #ifdef MPG_DEBUG
                show_RAW_IR();
              #endif
            }
            break;
          case 1:
            if(EnterSetup) {
              ST_LED_Buzzer (IR_MAP_Index);
              SetupIndex = IR_MAP_Index;
            } else {            
              #ifdef MPG_SendHit    
                ST_LED_Buzzer (1);     //蜂鳴器先叫, 以免和冷氣混在一起
              #endif
              EEPROM_readAnything(IR_MAP_Index*sizeof(IR_RAW), IR_RAW); 
              if (IR_RAW.RAW_TYPE == SONY) SONYIRSend(IR_RAW.rawData, IR_RAW.rawData_Bits);
              else RAWIRSend(IR_RAW.rawData, IR_RAW.rawData_Count, IR_RAW.rawData_Bits, khz);              
              #ifdef MPG_DEBUG
                show_RAW_IR();
              #endif
            }
            break;
          case 2:
            if(EnterSetup) {
              ST_LED_Buzzer (IR_MAP_Index);
              SetupIndex = IR_MAP_Index;
            } else {            
              #ifdef MPG_SendHit    
                ST_LED_Buzzer (1);     //蜂鳴器先叫, 以免和冷氣混在一起
              #endif
              EEPROM_readAnything(IR_MAP_Index*sizeof(IR_RAW), IR_RAW);              
              if (IR_RAW.RAW_TYPE == SONY) SONYIRSend(IR_RAW.rawData, IR_RAW.rawData_Bits);
              else RAWIRSend(IR_RAW.rawData, IR_RAW.rawData_Count, IR_RAW.rawData_Bits, khz);              
              #ifdef MPG_DEBUG
                show_RAW_IR();
              #endif
            }
            break;
          case 3: 
            if(EnterSetup) {
              ST_LED_Buzzer (IR_MAP_Index);
              SetupIndex = IR_MAP_Index;
            } else {        
              #ifdef MPG_SendHit    
                ST_LED_Buzzer (1);     //蜂鳴器先叫, 以免和冷氣混在一起
              #endif
              EEPROM_readAnything(IR_MAP_Index*sizeof(IR_RAW), IR_RAW);              
              if (IR_RAW.RAW_TYPE == SONY) SONYIRSend(IR_RAW.rawData, IR_RAW.rawData_Bits);
              else RAWIRSend(IR_RAW.rawData, IR_RAW.rawData_Count, IR_RAW.rawData_Bits, khz);             
              #ifdef MPG_DEBUG
                show_RAW_IR();
              #endif
            }
            break;
          case 4:
            if(EnterSetup) {
              ST_LED_Buzzer (IR_MAP_Index);
              SetupIndex = IR_MAP_Index;

            } else {            
              #ifdef MPG_SendHit    
                ST_LED_Buzzer (1);     //蜂鳴器先叫, 以免和冷氣混在一起
              #endif
              EEPROM_readAnything(IR_MAP_Index*sizeof(IR_RAW), IR_RAW);              
              if (IR_RAW.RAW_TYPE == SONY) SONYIRSend(IR_RAW.rawData, IR_RAW.rawData_Bits);
              else RAWIRSend(IR_RAW.rawData, IR_RAW.rawData_Count, IR_RAW.rawData_Bits, khz);               
              #ifdef MPG_DEBUG
                show_RAW_IR();
              #endif
            }
            break;
          case 5:
            if(EnterSetup) {
              ST_LED_Buzzer (IR_MAP_Index);
              SetupIndex = IR_MAP_Index;
            } else {            
              #ifdef MPG_SendHit    
                ST_LED_Buzzer (1);     //蜂鳴器先叫, 以免和冷氣混在一起
              #endif
              EEPROM_readAnything(IR_MAP_Index*sizeof(IR_RAW), IR_RAW);              
              if (IR_RAW.RAW_TYPE == SONY) SONYIRSend(IR_RAW.rawData, IR_RAW.rawData_Bits);
              else RAWIRSend(IR_RAW.rawData, IR_RAW.rawData_Count, IR_RAW.rawData_Bits, khz);              
              #ifdef MPG_DEBUG
                show_RAW_IR();
              #endif
            }
            break;
          case 6:
            if(EnterSetup) {
              ST_LED_Buzzer (IR_MAP_Index);
              SetupIndex = IR_MAP_Index;
            } else {            
              #ifdef MPG_SendHit    
                ST_LED_Buzzer (1);     //蜂鳴器先叫, 以免和冷氣混在一起
              #endif
              EEPROM_readAnything(IR_MAP_Index*sizeof(IR_RAW), IR_RAW);              
              if (IR_RAW.RAW_TYPE == SONY) SONYIRSend(IR_RAW.rawData, IR_RAW.rawData_Bits);
              else RAWIRSend(IR_RAW.rawData, IR_RAW.rawData_Count, IR_RAW.rawData_Bits, khz);             
              #ifdef MPG_DEBUG
                show_RAW_IR();
              #endif
            }
            break;
          case 7:                          
            if(EnterSetup) {
              ST_LED_Buzzer (IR_MAP_Index);
              SetupIndex = IR_MAP_Index;
            } else {            
              #ifdef MPG_SendHit    
                ST_LED_Buzzer (1);     //蜂鳴器先叫, 以免和冷氣混在一起
              #endif
              EEPROM_readAnything(IR_MAP_Index*sizeof(IR_RAW), IR_RAW);              
              if (IR_RAW.RAW_TYPE == SONY) SONYIRSend(IR_RAW.rawData, IR_RAW.rawData_Bits);
              else RAWIRSend(IR_RAW.rawData, IR_RAW.rawData_Count, IR_RAW.rawData_Bits, khz);              
              #ifdef MPG_DEBUG
                show_RAW_IR();
              #endif
            }
            break;
          case 8:
            if(EnterSetup) {
              ST_LED_Buzzer (IR_MAP_Index);
              SetupIndex = IR_MAP_Index;
            } else {            
              #ifdef MPG_SendHit    
                ST_LED_Buzzer (1);     //蜂鳴器先叫, 以免和冷氣混在一起
              #endif
              EEPROM_readAnything(IR_MAP_Index*sizeof(IR_RAW), IR_RAW);              
              if (IR_RAW.RAW_TYPE == SONY) SONYIRSend(IR_RAW.rawData, IR_RAW.rawData_Bits);
              else RAWIRSend(IR_RAW.rawData, IR_RAW.rawData_Count, IR_RAW.rawData_Bits, khz);              
              #ifdef MPG_DEBUG
                show_RAW_IR();
              #endif
            }
            break;
          case 9:
            if(EnterSetup) {
              ST_LED_Buzzer (IR_MAP_Index);
              SetupIndex = IR_MAP_Index;
            } else {            
              #ifdef MPG_SendHit    
                ST_LED_Buzzer (1);     //蜂鳴器先叫, 以免和冷氣混在一起
              #endif
              EEPROM_readAnything(IR_MAP_Index*sizeof(IR_RAW), IR_RAW);              
              if (IR_RAW.RAW_TYPE == SONY) SONYIRSend(IR_RAW.rawData, IR_RAW.rawData_Bits);
              else RAWIRSend(IR_RAW.rawData, IR_RAW.rawData_Count, IR_RAW.rawData_Bits, khz);               
              #ifdef MPG_DEBUG
                show_RAW_IR();
              #endif
            }
            break;
          case 10:    //進入設定模式            
            if(EnterSetup) {
              ST_LED_Buzzer (20);
              EnterSetup = false;
            } else {
              ST_LED_Buzzer (15);
              EnterSetup = true;
            }
            break;            
        }
        irrecv.enableIRIn();    //IRRemote 在發射的時候會把接收功能停掉, 重開接收
      }
    } 

    if (IR_MAP_Index == IR_NUM_SIZE) {   //非數字鍵
      if(EnterSetup) {
        if (results.overflow)  {   //位元數太多, 無法記錄    
          #ifdef MPG_DEBUG    
            dumpInfo(&results);
            dumpRaw(&results);         
            show_RAW_IR();
          #endif
        } else {
          #ifdef MPG_DEBUG    
            dumpInfo(&results);
            dumpRaw(&results);
          #endif        
          EncodeRAWCode(&results);
          if ((IR_RAW.RAW_BIT_MARK >0) && (IR_RAW.RAW_ONE_SPACE >0) && (IR_RAW.RAW_ZERO_SPACE >0) && (results.overflow == false)) {
            EEPROM_writeAnything(SetupIndex*sizeof(IR_RAW), IR_RAW);
            ST_LED_Buzzer(2);     //提示已記錄按鍵紅外線值
          }
        }
      }          
    }    
    irrecv.resume();    //重新接收
    delay(100);         //穩定用
  }
}


//EEPROM 函數
template <class T> int EEPROM_writeAnything(int ee, const T& value) {
  const byte* p = (const byte*)(const void*)&value;
  unsigned int i;
  for (i = 0; i < sizeof(value); i++)
    EEPROM.write(ee++, *p++);
  return i;
}

template <class T> int EEPROM_readAnything(int ee, T& value) {
  byte* p = (byte*)(void*)&value;
  unsigned int i;
  for (i = 0; i < sizeof(value); i++)
    *p++ = EEPROM.read(ee++);
  return i;
}

//------- 以下為除錯用函數
//顯示目前RAW結構參數
void show_RAW_IR(){
  Serial.println(F(""));
  Serial.println(F("// My Data Start , Mark & Ctrl-C Copy ---------------")); 
  Serial.println(F("")); 
  Serial.print(F("#define RAW_HDR_MARK    "));
  Serial.print(IR_RAW.RAW_HDR_MARK, DEC);  
  Serial.println(F(""));  
  Serial.print(F("#define RAW_HDR_SPACE   "));
  Serial.print(IR_RAW.RAW_HDR_SPACE, DEC); 
  
  Serial.println(F(""));   
  Serial.print(F("#define RAW_BIT_MARK    "));
  Serial.print(IR_RAW.RAW_BIT_MARK, DEC);
  Serial.print(F("     // "));
  Serial.print(Probability[0] * USECPERTICK, DEC);
  Serial.print(F(" -> "));
  Serial.print(Temporary[0] , DEC);
  Serial.print(F(" times.  ")); 
  Serial.print(Probability[1] * USECPERTICK, DEC);
  Serial.print(F(" -> "));
  Serial.print(Temporary[1] , DEC);
  Serial.print(F(" times.  "));
  Serial.print(Probability[2] * USECPERTICK, DEC);
  Serial.print(F(" -> "));
  Serial.print(Temporary[2] , DEC);
  Serial.print(F(" times."));   

  Serial.println(F("")); 
  Serial.print(F("#define RAW_ONE_SPACE   "));
  Serial.print(IR_RAW.RAW_ONE_SPACE, DEC);
  Serial.print(F("     // "));
  Serial.print(Probability[0] * USECPERTICK, DEC);
  Serial.print(F(" -> "));
  Serial.print(Temporary[0] , DEC);
  Serial.print(F(" times. ")); 
  Serial.print(Probability[1] * USECPERTICK, DEC);
  Serial.print(F(" -> "));
  Serial.print(Temporary[1] , DEC);
  Serial.print(F(" times. "));
  Serial.print(Probability[2] * USECPERTICK, DEC);
  Serial.print(F(" -> "));
  Serial.print(Temporary[2] , DEC);
  Serial.print(F(" times."));  

  Serial.println(F("")); 
  Serial.print(F("#define RAW_ZERO_SPACE  "));
  Serial.print(IR_RAW.RAW_ZERO_SPACE, DEC);
  Serial.print(F("     // "));
  Serial.print(Probability[0] * USECPERTICK, DEC);
  Serial.print(F(" -> "));
  Serial.print(Temporary[0] , DEC);
  Serial.print(F(" times. ")); 
  Serial.print(Probability[1] * USECPERTICK, DEC);
  Serial.print(F(" -> "));
  Serial.print(Temporary[1] , DEC);
  Serial.print(F(" times. "));
  Serial.print(Probability[2] * USECPERTICK, DEC);
  Serial.print(F(" -> "));
  Serial.print(Temporary[2] , DEC);
  Serial.print(F(" times."));  
  Serial.println(F(""));

  Serial.println(F(""));  
  Serial.print(F("int rawData_Bits = ")); 
  Serial.print(IR_RAW.rawData_Bits, DEC); 
  Serial.print(F(" ;"));   
  Serial.println(F(""));   
  Serial.print(F("byte rawData["));
  Serial.print(IR_RAW.rawData_Count, DEC);
  Serial.print(F("] = { "));

  for (byte i = 0;  i < IR_RAW.rawData_Count;  i++) {
    Serial.print(F("0x"));
    Serial.print(IR_RAW.rawData[i], HEX);
    if ( i < IR_RAW.rawData_Count-1 ) Serial.print(F(", "));
  }
  Serial.println(F(" };"));

  Serial.print(F("unsigned int rawHole["));
  Serial.print(IR_RAW.rawHole_Count, DEC);
  Serial.print(F("][3] = { "));

  for (byte i = 0;  i < IR_RAW.rawHole_Count;  i++) {
    Serial.print(F("{ "));
    for (byte j = 0;  j < 3;  j++) { 
      Serial.print(IR_RAW.rawHole[i][j], DEC); 
      if ( j < 2 ) Serial.print(F(", ")); 
    }
    Serial.print(F(" } "));
    if ( i < IR_RAW.rawHole_Count-1 ) Serial.print(F(", "));
  }
  Serial.println(F("};"));
  Serial.println(F(""));
    
  Serial.println(F("// My Data End --------------- By MGP."));  
  Serial.println(F(""));
  Serial.println(F(""));  
}


//------- 以下為原紅外線程式庫範例內函數
void  encoding (decode_results *results)
{
  switch (results->decode_type) {
    default:
    case UNKNOWN:      Serial.print(F("UNKNOWN"));       break ;
    case NEC:          Serial.print(F("NEC"));           break ;
    case SONY:         Serial.print(F("SONY"));          break ;
    case RC5:          Serial.print(F("RC5"));           break ;
    case RC6:          Serial.print(F("RC6"));           break ;
    case DISH:         Serial.print(F("DISH"));          break ;
    case SHARP:        Serial.print(F("SHARP"));         break ;
    case JVC:          Serial.print(F("JVC"));           break ;
    case SANYO:        Serial.print(F("SANYO"));         break ;
    case MITSUBISHI:   Serial.print(F("MITSUBISHI"));    break ;
    case SAMSUNG:      Serial.print(F("SAMSUNG"));       break ;
    case LG:           Serial.print(F("LG"));            break ;
    case WHYNTER:      Serial.print(F("WHYNTER"));       break ;
    case AIWA_RC_T501: Serial.print(F("AIWA_RC_T501"));  break ;
    case PANASONIC:    Serial.print(F("PANASONIC"));     break ;
    case DENON:        Serial.print(F("Denon"));         break ;
  }
}


void  ircode (decode_results *results)
{
  // Panasonic has an Address
  if (results->decode_type == PANASONIC) {
    Serial.print(results->address, HEX);
    Serial.print(F(":"));
  }

  // Print Code
  Serial.print(results->value, HEX);
}


void  dumpInfo(decode_results *results)
{
  // Check if the buffer overflowed
  if (results->overflow) {
    Serial.println(F("IR code too long. Edit IRremoteInt.h and increase RAWBUF"));
    return;
  }

  // Show Encoding standard
  Serial.print(F("Encoding  : "));
  encoding(results);
  Serial.println(F(""));

  // Show Code & length
  Serial.print(F("Code      : "));
  ircode(results);
  Serial.print(F(" ("));
  Serial.print(results->bits, DEC);
  Serial.println(F(" bits)"));
}


//+=============================================================================
// Dump out the decode_results structure.
//
void  dumpRaw (decode_results *results)
{
  // Print Raw data
  Serial.print("Timing[");
  Serial.print(results->rawlen-1, DEC);
  Serial.println("]: ");

  for (int i = 1;  i < results->rawlen;  i++) {
    unsigned long  x = results->rawbuf[i] * USECPERTICK;
    if (!(i & 1)) {  // even
      Serial.print("-");
      if (x < 1000)  Serial.print(" ") ;
      if (x < 100)   Serial.print(" ") ;
      Serial.print(x, DEC);
    } else {  // odd
      Serial.print("     ");
      Serial.print("+");
      if (x < 1000)  Serial.print(" ") ;
      if (x < 100)   Serial.print(" ") ;
      Serial.print(x, DEC);
      if (i < results->rawlen-1) Serial.print(", "); //',' not needed for last one
    }
    if (!(i % 8))  Serial.println("");
  }
  Serial.println("");                    // Newline
}


2019.8.14 補充
使用於本製作內容的小米萬能遙控器是一代,後續有二代增強版、搭配小愛同學的版本等,介面會有所不同,若無創維機上盒的選項,可用長虹電視代替,可將 45-55 行替換如下,再上傳至晶片
#define N00  0xBD827D   //數字盤 0
#define N01  0xBDA857
#define N02  0xBD6897
#define N03  0xBDE817
#define N04  0xBD9867
#define N05  0xBD58A7
#define N06  0xBDD827
#define N07  0xBDB847
#define N08  0xBD7887
#define N09  0xBDF807
#define N10  0xBD50AF   //數字盤 確定

或者可使用其它電視(請先將 實體小米萬能遙控器  對準 轉譯器接收模組)
    A. 請將上方程式 24-25 行左方的雙斜線拿掉,將程式上傳至晶片進入除錯模式
    B. 選取米家APP內萬能遙控器中任一牌電視測試(按任一鍵均可),若此電視遙控器是NEC編碼的,在Arduino IDE監控視窗內會出現編碼代號,若無請更換其它牌電視繼續測試
    C. 找到的話請依序記錄 "0~9 確定" 共11個鍵的NEC編碼代號替換 45-55 行編碼代號 (0x 之後的16進位數字)
    D. 加回 24-25 行的雙斜線,將程式上傳至晶片即完成

03設定篇 請改用新的電視數字鍵盤操作即可。