Prism

2019年8月12日 星期一

生活小物-藥錠取出器 & 切藥器



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

隨著年齡的增長,父母親也漸漸高齡,身體難免出現一些慢性病需長期服藥控制,大醫院給付的藥錠都是沒有分裝的,看到父母親小心翼翼的從片片的包裝中擠壓取藥,偶爾還是會不小心掉落桌上或地上,而醫院給的藥量都是剛好的,掉到地上的話當月就少一天的藥量,心想難道沒有較好的方法嗎?

上網看了國內不少的取藥器,覺得都不是很好用(純粹個人觀感),想想日本也是一個高齡化的社會,可能會有什麼比較好的發明吧,看了看選擇的樣式還是不多,但是有一款,看到的瞬間我就很想買來用用看...


TOP-001 お薬取出器 トリダス (定價約日幣 2000円,樂天有賣到七折左右)

這個取藥器我已經用了一年多了,真是方便又耐用,除了有時候藥物較小的話會卡一點(晃一晃就會掉下來了),真是好物,藥錠壓出較多時可搭配小鑷子取出。



Fullicon 護立康 集屑切藥器

另外有時候藥劑量是半錠,就需要使用切藥器,剛開始買的是不透明的切藥器,有時藥錠放好切下去時不小心走位,導致切出大小邊,前後也買了好幾個不同的試用,而下面這個雖然定價較其它切藥器貴了一些,但物有所值,推薦使用,我是去其經銷藥局買的(非廣告喔)。


買了 藥錠取出器 和 切藥器 後,也順便買了 一週七日份藥盒,直接幫父母親將一週會用的藥物分裝好,這樣用藥部份就較沒問題了。一週藥盒我是買28格便宜的那種,直觀好拿取,但這種盒蓋接合處久了後(半年左右)會變軟或斷裂,算是消耗品,也沒什麼較好可推薦的,多買一個備用就好。


PS:藥錠取出器有出一個 Pro 專業版,有需要的可參考看看

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設定篇 請改用新的電視數字鍵盤操作即可。

積木式免焊接 記憶型空調紅外線轉譯器 - 03 設定 記憶型空調紅外線轉譯器

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

設定 記憶型空調紅外線轉譯器


添加 米家APP遙控器,作為和轉譯器中繼使用

以下使用實體小米萬能遙控器一代
打開手機(Android)  米家APP -> 萬能遙控器 -> 添加遙控器 -> 點選機頂盒 -> 點選衛星/機頂盒 -> 點選 創維 Skyworth (NEC編碼)




接著請依螢幕中的按鍵測試,均選"有響應"以完成建立步驟 -> 點選 數字 -> 出現數字鍵盤

錄製自家冷氣空調紅外線編碼

請將實體小米萬能遙控器對準轉譯器的接收模組
1. 按下上方圖內創維數字鍵盤的右下"確定",會聽見 長嗶3聲,進入設定狀態
2. 按下上方圖內創維數字鍵盤的 0~9 任一個數字鍵,會聽見對應的嗶聲
3. 將家中的空調遙控器對準轉譯器的接收模組按下所需按鍵,若聽見 短嗶2聲 即完成錄製,若無聲表示錄製失敗。
    ( 若按錯鍵可重按,只保留最後錄製的信號 )
4. 初次使用時請重覆上 2-3 步驟錄製3個數字做測試即可。
5. 再按下上方圖內創維數字鍵盤的右下"確定",會聽見 長嗶4聲,結束設定狀態

測試錄製信號

初次測試時,第1點非常重要,紅外線是指向性的,沒對準是不能用的。
1. 請儘量將轉譯器發射模組的LED的正上方圓頂接近並對準冷氣機紅外線接收器的位置
2. 轉譯器能確實收到實體小米萬能遙控器的信號
3. 再按下在上述設定步驟中有使用到的3個 米家APP創維遙控器的 0-9 數字鍵,看結果如何

若有1個以上成功的話表示轉譯器應可適用,失敗的 自家紅外線設定 紀錄再錄製一次可能就可用,我錄製信號的成功率保守估記是 80%。

接著錄製測試完全部所需的設定,再來擺放調整自家適合的位置角度,測試定位後的轉譯器接收發射都正常。

測試完成後就不需要再用到手機去操作了,日後操作使用的方式為:
小愛同學 (或遠端操作) -> 連動實體小米萬用遙控器 -> 透過此電路轉譯成自家冷氣紅外線信號發送 -> 讓自家冷氣空調動作

剩下的就是依各人需求將這個加入米家APP的智慧連動了,希望你也能順利補完這塊拼圖。



PS:若網友完成可用的話,煩請將 冷氣機、遙控器型號 回報在留言板上,我再彙整提供其他網友參考。



註:若不順利3組錄製信號都不行的話,簡易檢查如下:
1. 按下 米家APP創維遙控器的 0-9 數字鍵 時,轉譯器的接收模組無閃燈
A:實體小米萬能遙控器沒對準轉譯器。

2. 轉譯器發射信號時,發射模組有無閃燈
A:發射模組接線錯誤,或尚未錄製信號供發射

3. 轉譯器發射模組的LED的正上方圓頂有對準冷氣機紅外線接收器的位置
A:紅外線是指向性的,我初次測試時也以為失敗了,後來才發現紅外線發射模組無輔助聚光處(如自家遙控器發射端有凹陷處),所以角度非常重要,初次測試時請儘量接近冷氣機紅外線接收處,可用後再拉長距離調整角度。

若再三檢查都不行的話,則要進入程式的除錯模式,查看接收到的紅外線信號是如何,各家紅外線編碼不同,再客製修正程式調整。

最後是 紀錄篇,紀錄網友的回報狀況及修正。


積木式免焊接 記憶型空調紅外線轉譯器 - 04 紀錄 & 修正篇
Xiaomi Mijia universal infrared ray remote control for Unsupported air conditioner.

積木式免焊接 記憶型空調紅外線轉譯器 - 02 軟體 程式寫入篇

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

軟體 程式寫入篇


Arduino 開發程式及紅外線程式庫安裝

首先安裝Arduino 開發板的編寫程式,請至 Arduino官網(如下連結)下載 IDE開發環境 主程式
如我的電腦是 Windows 10,下載 Windows Installer, for Windows XP and up 版本
https://www.arduino.cc/en/Main/Software


下載時會詢問是否要贊助,點選下方 JUST DOWNLOAD 可免費下載主程式。


下載完畢後執行EXE檔主程式安裝,一步一步如下圖即可完成:
待 Completed 出現後,Close 關閉安裝程式,Arduino IDE開發環境 程式即安裝完成。


接下來安裝紅外線程式庫 (IR remote by shirriff)

請打開桌面上安裝好的 Arduino 圖式程式,一步一步如下圖即可完成:

安裝好的 紅外線程式庫 儲存在使用者的「文件」目錄下。


Arduino 開發版晶片程式寫入 (程式與 擴展版 通用)

0. 請先關閉任何開啟的 Arduino IDE開發環境程式
1. 將Arduino 開發板的USB連接線插入電腦任一 USB 2.0 埠上進行供電及準備傳輸。
2. 請下載以下壓縮檔 (解壓縮密碼藍字 mygraphpaper.com 解壓縮

https://www.mygraphpaper.com/download/MPG_TR_Translator.rar

3. 請將檔案內的 IRremoteInt.h、IRremote.cpp 複製至如上圖 使用者「文件」下的Arduino\libraries\IRremote\ 目錄內,要覆蓋原本檔案。
4. 雙擊打開下載檔案內 MPG_IR_Translator 目錄下的 MPG_IR_Translator.ino 程式檔
5. 準備上傳晶片程式:Arduino IDE應用程式開啟後,請調整一下偏好設定


接著確認開發板的型號選至 Arduino/Genuino Uno

接著選擇序列埠 COMX (Arduino/Genuino Uno),COM埠的編號每台電腦皆不同,確認括號內的型號即可。

接著打開序列埠監控視窗,這個視窗可監控Audrino開發板回傳的訊息,是非常常用的視窗。

執行 "驗證/編譯" 本程式,結果應如下圖,請注意Arduino IDE下方黑色顯示視窗有無出現其它錯誤訊息 (有出現 可用記憶體低下 的警告訊息表示上方 步驟3 的檔案才有覆蓋成功)

接著請執行"上傳",將程式寫入Arduino 開發板晶片內。上傳後請等Arduino IDE下方黑色顯示視窗出現如下圖示,請注意驗證是否成功。(若出現 Verification error 表示這張 Arduion開發板的晶片有問題,需向店家更換良品)

上傳成功後:
1. 蜂鳴器會 Beep 一聲 (蜂鳴器上的貼紙請勿撕掉,若撕掉會變很大聲)
2. 監控視窗會出現:MPG IR-Translator is Ready.

軟體部份完成,接下來進行設定部份。


積木式免焊接 記憶型空調紅外線轉譯器 - 03 設定 記憶型空調紅外線轉譯器

積木式免焊接 記憶型空調紅外線轉譯器 - 01 硬體 電子積木篇

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

積木式免焊接 記憶型空調紅外線轉譯器


本節文章將以如何快速實作可使用的空調紅外線轉譯器為主,較細節理論的部份就不多詳述,若有興趣的話可參考之前的實驗紀錄「冷氣機紅外線遙控訊號連動」。

硬體 電子積木篇

硬體使用如下的電子積木方式製作的話可完全不用焊接,只需 4個零件、7條線連接,再整線一下就可以了。


所需硬體材料

為方便採買,直接複製網拍賣家的商品名稱(本站不販賣零件,也沒有合作的店家,請自行採購)
1. 紅外線發射模塊 紅外線 發射 模塊 模組 發射傳感器模塊 小車 電子積木  (請注意模組上要有焊 黑色電阻 的)
2. 紅外線傳感器接收模組 1838
3. Arduino 全相容 UNO R3 開發板 ATMega16U2【送USB線】
4. 蜂鳴器 有源蜂鳴器 5V 採用SOT塑封管 電磁式蜂鳴器                     (規格為 有源)
5. 彩色排線 連接線 杜邦線 10公分 公對公 公對母 母對母 10條1單位  (規格為 公對母)
6. 保護殼-UNO R3 開發版壓克力外殼 Arduino 全相容                       (新版的外款不用螺絲)

全部材料清單及價格如下,請注意以下紅色所指規格,不要買錯了。(2019年8月)


接線圖如下:
總共零件材料如下,拿到時請先檢查所有零件外觀有無問題,再將Arduino 開發版接上 5V USB充電器,確認電源燈有正常亮起。(實驗時千萬不要用行動電源供電,以免零件短路造成行動電源電池爆炸危險)


接線步驟: (以下接線前請確認 Arduino 開發版 沒有 接上任何電源)
PS:杜邦接線母頭的那方若多次插拔易鬆動不牢,要整線請拔公頭(和開發板連接那邊)。
1. 將 Arduino 開發版放進壓克力保護殼內,蓋回上殼
2. 參照上方接線圖連接 紅外線接收模組 - 3條線
3. 參照上方接線圖連接 紅外線發射模組 - 2條線
4. 參照上方接線圖連接 有源蜂鳴器 - 2條線
5. 整理一下接線,可用薄雙面膠或膠帶暫固定 (請依放置需要,調整模組左右邊,但請注意接收模組要背對發射模組)


最後請再確認一次所有針腳有無鬆掉及接對位置,若接法有疑問請先確認,不要冒然送電以免零件燒毀。

沒問題後請將 Arduino 開發版接上 5V USB充電器,確認電源燈有正常亮起,各零件沒有異樣即可,硬體部份即完成,接下來進行軟體部份。


積木式免焊接 記憶型空調紅外線轉譯器 - 02 軟體 程式寫入篇