Prism

2019年7月22日 星期一

生活小物-B7000 萬用膠 (米動手錶 螢幕貼合)

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

B-7000 萬用膠 (米動手錶 螢幕貼合)


一年半前幫家人買的 AMAZFIT 米動手錶 青春版,年初左右拿給我看說螢幕翻開脫落了,看似脫膠,還好螢幕有接著一條排線才沒遺失,而這個時間點已經過一年保固了,看看手錶還能正常運行,只好自己想辦法修了,上網查了一下,這種 B-7000的多功能膠可以貼合螢幕,就買個 15ml 來試用了 (不到台幣 50元)。

處理的步驟如下 (當時忘了要拍圖了,可參考 Mobile01網友的圖 ):
1. 將原手錶上四周及錶蓋上的殘膠清除乾淨 (可用 鑷子、刀片、酒精)
2. 均勻的在錶體上方四周圍內抹上一圈 B-7000 膠水,我是怕漏水上了二圈,再檢查有沒有沒塗到的死角
3. 錶蓋對準壓回錶座
4. 在錶蓋上方放個重物壓了一天
5. 等膠乾了後,錶蓋上會有溢膠,透明軟軟的,可拿個尖鑷子清除大部份,再用酒精擦拭乾淨即可。

用到現在半年多了沒有什麼問題,手錶是拿來用的,生活的痕跡難免就是了。

註:這瓶萬用膠開封後,就算有蓋緊,過一段時間還是會全部硬掉,建議買夠用的大小就好





2019年7月18日 星期四

小米空氣淨化器當精油擴香機

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

小米空氣淨化器當精油擴香機


當初購買 小米空氣淨化器 2S,看到正上方那圓圓的位置時,我就決定了,它要當房間內的大型精油擴香器~~

之前想買無印良品的香氛機,但家中溼度經常在70左右,再用水霧的話就要霉到家了。
網上有各式各樣的 香薰儀 擴香機 芳香噴霧 精油擴香器等,這個替代方式好不好就看看吧。

上網拍找了好久,沒有看到合適搭配的精油擴香用品,只好轉到日本的 Amazon 去看看,終於發現好像合適的東西了,共買了下列三個試試看:

価格: ¥389 



第一種是霧狀的半透明塑料盤子,質料還好,有附方型的上下蓋,不用的時候可以蓋起來或半蓋,減少精油味道散出或積塵,盤子表面有細細的紋路,看說明是類似毛細現象幫助精油揮發。這個睡覺的時候放在旁邊,效果還不錯,偶然傳來悠悠的香味~~

第二種和第三種是相同的,只是印刷不同,是白色的陶器,看說明是陶器內有細微的小孔,可加大面積幫助精油揮發。



後來考慮有時候會更換不同的精油,第一種很方便清洗,而第二種跟第三種就先收起來了,如果是使用固定精油的話,第二種和第三種會有點像在養陶器的感覺,久了之後,應該會有不同的感受。

若想試試看的話,家中沾醬的小圓盤可以拿來用看看,不一定要買上面的擴香盤。

滴上3滴檸檬複合精油,放上清淨機上的圓座,配合微風,不用多久房內就充滿了檸檬清新微甜的味道,要放鬆心情的時候很好用,可維持個 1 ~ 2 小時左右~~

若像是覺得空氣有異味、有客人要來家裏的時候,這個擴香的效果很快,算滿實用的。

小提醒一下,可在擴香盤下貼個薄的雙面膠帶固定可避免盤子走位或打翻。

使用小米智能插座開啟公寓一樓大門

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

使用「米家智能插座」開啟公寓一樓大門


※ 以下操作,無電工經驗者需請有經驗的朋友幫忙協助

前陣子朋友家中的公寓大門對講機故障了找我去看看,拆下對講機檢查線路已經老舊,且機型簡單建議可自己換一台 (俞氏牌 室內對講機 LT-380A),而對講機的下方有設插座,朋友家中也有裝設小米物聯網的設備,在這個地利的環境下,我就問要不要順便用米家APP去開啟樓下大門呢?

當初的構想是 :
  1. 遠端操作米家智慧插座打開電源
  2. 啟動計時電路及繼電器
  3. 透過繼電器模擬按下對講機的開門鍵
  4. 一秒左右後電路自動關閉繼電器
  5. 米家APP再關閉小米智能插座

先 關閉電路的繼電器 再 關閉小米插座 是為了保險起見,避免不要意外的一直開啟一樓的門。

本想DIY製作簡單的計時電路,在找零件的時候,發現已有現成經濟的模組可用(台幣 100元左右),只要小改一下就符合需求了,借網拍模組照片如下 ( 延時繼電器模組 ):

網拍賣家在商品說明中有詳細的參數資料,主要使用 NE555計時器 + 繼電器
1. 下方 S1 是一個小按鈕,為低電位觸發開關,S1 右方接點與 電路板左側 V- 腳位相通,均接地
2. S1 左方接點與 電路板左側 TRIG 腳位相通,再連接 R2電阻 2.4K歐姆,再接電源

使用時在電路板左側焊上一個 100u的電解電容模擬一送電時即做低電位觸發:
1. 接腳 TRIG  接電解電容+極
2. 接腳 V-       接電解電容-極
電容正負、極接反會有危險,電容會爆掉的,請特別注意,且耐壓最好選電源的2倍以上,這邊電源使用USB 5V,所以最好使用耐電壓10伏特以上。我手邊現有的為耐壓 25 伏特,就直接拿來用了。

使用電源 5V,電阻 2.4K歐姆,電容 100u,根據RC充電計算約送電 0.24 秒後會自 低電位 到達 高電位,下方網站有 電阻電容 (RC) 時間常數計算器 ,可套用數值去概算。
https://www.digikey.tw

利用電路上方藍色可變電阻的旋鈕,可以調整繼電器開啟時間,我是調為1秒左右(可按下 S1 開關去測試),電路動作為:
1. Micro USB 5V供電  一送電時電路因電容低電位觸發,而啟動繼電器:右側 下方(常開端NO)、中間(公共端COM) 會短路相通
2. 一秒後,NE555計時的時間到 ,電路關閉繼電器:右方 下方(常開端NO)、中間(公共端COM) 會斷開不通

再設定米家APP智慧連動 (小米插座 Turn on 後延遲的時間不要設太短,有時小米插座啟動會有時間差)

設定好後,用麵包板接上LED模擬繼電器導通狀態:


小米插座斷電後,電路上的 100u 電容約 3 秒左右才會放完電,也就是說再次使用請在 3 秒以後,如果使用的電容大小不同,可重新使用的時間也會不同。

模組測試好後,從對講機的開門按鈕開關二端接點,焊接拉二條單芯線(不用太粗)鎖到模組的繼電器中:下方(常開端NO)、中間(公共端COM)

電鎖開門的電路動作為 ( 以下顏色不一定與自家接線顏色相同,請留意)
1. 電從電源供應器(棕 A, AC 24V)送出,到達電鎖 A 點
2. 電鎖內 A、EL 間有電磁鐵線圈,導通時會拉入門栓開門
3. 電鎖 EL 線路連接至自家對講機 (紅 EL1) 接點
4. 自家對講機按下 開門按鈕 的話,(紅 EL1) 和 (黑 -) 會短路導通,導通後會形成電路迴圈
5. 自家對講機的 (黑 -) 與電源供應器的地線 (黑 -) 連接,走完電流迴圈
6. 電鎖開門後,門栓已彈出,此時電鎖內 A 和 PT 會導通,自家對講機的 開門指示燈 就會亮起

整理一下牆壁佈線即完成 (我是把這個模組塞進對講機的牆壁洞中,再拿一條夠長的 Micro USB 線接到小米智能插座上的 5V USB 手機充電器上),朋友家中已弄好一段時間了,不好再去拍照,何況還要拆開對講機啊...

之後可將米家APP智慧設定建一個捷徑到手機桌面,這樣不用開米家APP只要按一下捷徑圖示就能開門,當然小愛同學也能幫忙。

這種透過繼電器隔離兩方電源,模擬"按一下"開關的動作,有些時候會有需要 (請注意不要用在可能造成危險的地方,如鐵捲門),利用這種方式也可將某些電器控制加入自家的物聯網之中。

2019年7月16日 星期二

冷氣機紅外線遙控訊號連動 - 06 紅外線訊號轉譯程式

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

※ 2019.8 後補「積木式免焊接 記憶型空調紅外線轉譯器」一文,可供快速製作成品

紅外線訊號轉譯程式


程式製作流程概要:

1. 錄製米家APP內小米萬能遙控器內建的任一個NEC編碼遙控器 0~9 數字鍵的紅外線編碼
2. 錄製自家冷氣空調的 10組常用信號,對應上 1 的 0~9 數字鍵,10組的話日常使用應足夠
3. 程式基本流程為
    A. 讀取紅外線接收器的編碼信號
    B. 比對符合電視遙控器數字鍵(上 1 )中的那個數字
    C. 發射與數字對應的自家空調紅外線錄製信號(上 2)
    D. 過程中 LED 、蜂鳴器會有閃光及聲音作提示


錄製米家APP內小米萬用遙控器的 0~9 數字鍵的紅外線編碼

請先將前篇 04中修改好的 IRrecvDumpV2 程式編譯上傳至 Arduino 開發板內,並將實體的小米萬能遙控器對準電路板上的紅外線接收器,準備接收記錄信號。

PS: 購買的實體小米萬能遙控器請先確認已完成綁定米家APP ( 以下使用小米萬能遙控器一代 )
打開手機中米家APP內的萬能遙控器圖示,選擇 添加遙控器 -> 點選機頂盒 -> 點選衛星/機頂盒 -> 點選 創維 Skyworth (隨機挑選的NEC編碼遙控器,配合下方紅外線轉譯程式使用NEC 編碼判別,若要用別的遙控編碼協定請再自行修改程式碼) 


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


請依序按下數字鍵的 0~9 ,並複製紀錄下各數字的紅外線編碼,如數字 0,對應的NEC編碼如下 0x80BFE11E (0x 表示16進位)

創維機頂盒 遙控器數字 0~9 的NEC編碼紀錄如下:
0     0x80BFE11E
1     0x80BF49B6
2     0x80BFC936
3     0x80BF33CC
4     0x80BF718E
5     0x80BFF10E
6     0x80BF13EC
7     0x80BF51AE
8     0x80BFD12E
9     0x80BF23DC


錄製自家冷氣空調常用設定的紅外線編碼

請先構思自家空調常用的設定有那些,將之編號為 0~9 (沒那麼多組也沒關係,日後可再添加修改),舉例如下:
0     關機 (不用考慮其它參數設定) 
1     保留
2     保留
3     保留
4     保留
5     快速冷房:開機 , 冷氣 , 25度 , 風速4級(最大) , 風向自動
6     一般使用:開機 , 冷氣 , 26度 , 風速自動, 風向自動
7     一般使用:開機 , 冷氣 , 27度 , 風速自動, 風向自動
8     一般使用:開機 , 冷氣 , 28度 , 風速自動, 風向自動
9     搭配風扇:開機 , 冷氣 , 29度 , 風速自動, 風向自動

請按出構思的空調設定,並同上方式複製紀錄下紅外線編碼參數,如 關機,對應的編碼資料如下

錄製好所有的編碼後,可能發現每個設定編碼其下方藍色參數部份可能會不盡相同,可以直接套用任何一組使用,應該都還在取樣誤差範圍內,或可以取出現最多次的數值作為較佳參數使用。
下方的 byte rawData 陣列,在其上方一行請手動為其加上設定的註解以方便日後判讀了解( // 後為註解符號,不會被編譯),並為每個 rawData 陣列加上數字編號以供程式識別 (紅色部份),全部取樣整理如下:

#define RAW_HDR_MARK    3350
#define RAW_HDR_SPACE   1800
#define RAW_BIT_MARK       400     // 450 -> 17 times.  400 -> 66 times.  350 -> 55 times.
#define RAW_ONE_SPACE   1350     // 1350 -> 11 times. 1300 -> 11 times. 500 -> 65 times.
#define RAW_ZERO_SPACE  500     // 500 -> 65 times. 450 -> 40 times. 400 -> 2 times.
#define RAW_RPT_MARK     400      //Repeat Gap
#define RAW_RPT_SPACE    16950

int rawData_Bits = 144 ;

//0     關機 (不用考慮其它參數設定) 
byte rawData_0[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x0, 0x18, 0xB0, 0x6C, 0x2, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8 };

//5     快速冷房:開機 , 冷氣 , 25度 , 風速4級(最大) , 風向自動
byte rawData_5[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x4, 0x18, 0x90, 0x6C, 0x22, 0x46, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C };

//6     一般使用:開機 , 冷氣 , 26度 , 風速自動, 風向自動
byte rawData_6[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x4, 0x18, 0x50, 0x6C, 0x2, 0x46, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF4 };

//7     一般使用:開機 , 冷氣 , 27度 , 風速自動, 風向自動
byte rawData_7[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x4, 0x18, 0xD0, 0x6C, 0x2, 0x46, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC };

//8     一般使用:開機 , 冷氣 , 28度 , 風速自動, 風向自動
byte rawData_8[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x4, 0x18, 0x30, 0x6C, 0x2, 0x46, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8C };

//9     搭配風扇:開機 , 冷氣 , 29度 , 風速自動, 風向自動
byte rawData_9[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x4, 0x18, 0xB0, 0x6C, 0x2, 0x46, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C };


完成紅外線轉譯程式

請注意執行本程式前
1. 需將前篇03、04中修改的 IRremoteInt.h 檔案內的 RAWBUF、_GAP 二個參數改回預設值,以維持程式穩定:
    #define RAWBUF  101
    #define _GAP    5000

2. 再來貼上對應的創維遙控器編碼
N00~N09必須有值,沒有的部份請補 0,若使用相同的遙控器則不用修改
#define N00     0x80BFE11E
#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

3. 貼上自家的冷氣空調錄製編碼
    A. 從第35行 #define RAW_HDR_MARK 行數開始對應貼上
    B. rawData_0[XX] ~ rawData_9[XX] 必須有值,沒有的部份請改成
        byte rawData_X[1] = { 0 };      //X為對應的數字, 可參考 48 ~ 58 行中的語法

其它就不用修改了,測試正常後可再依自己的需求去修改程式內容,上面已有提過程式基本流程為
    A. 讀取紅外線接收器的編碼信號
    B. 比對符合電視遙控器數字鍵中的那個數字
    C. 發射與數字對應的自家空調紅外線錄製信號
    D. 過程中 LED 、蜂鳴器會有閃光及聲音作提示

完成的程式碼如下:
/*
   IRremote
   An IR LED must be connected to Arduino PWM pin 3.
   Copyright Ken Shirriff
   http://arcfn.com

   2019.7.16 By MGP.
   Only for Air conditioner.  https://www.mygraphpaper.com blog.
*/

#include <IRremote.h>

//請注意執行本程式前, 需將 IRremoteInt.h 內 RAWBUF, _GAP 改回預設值
//IRremoteInt.h
//#define RAWBUF  101
//#define _GAP    5000


//創維機頂盒 遙控器數字 0~9 的NEC編碼
//N00~N09必須有值,沒有的請補 0
#define N00     0x80BFE11E
#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


//自家的冷氣空調編碼參數
//所有的 rawData_X 陣列必須有值,沒有的請補 byte rawData_X[1] = { 0 }; //(X為對應的數字)
#define RAW_HDR_MARK    3350
#define RAW_HDR_SPACE   1800
#define RAW_BIT_MARK    400     // 450 -> 17 times.  400 -> 66 times.  350 -> 55 times.
#define RAW_ONE_SPACE   1350     // 1350 -> 11 times. 1300 -> 11 times. 500 -> 65 times.
#define RAW_ZERO_SPACE  500     // 500 -> 65 times. 450 -> 40 times. 400 -> 2 times.
#define RAW_RPT_MARK    400      //Repeat Gap
#define RAW_RPT_SPACE   16950

int rawData_Bits = 144 ;

//0     關機 (不用考慮其它參數設定) 
byte rawData_0[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x0, 0x18, 0xB0, 0x6C, 0x2, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8 };

//1     保留 不使用的數字請用下行宣告做預設值
byte rawData_1[1] = { 0 };

//2     保留 不使用的數字請用下行宣告做預設值
byte rawData_2[1] = { 0 };

//3     保留 不使用的數字請用下行宣告做預設值
byte rawData_3[1] = { 0 };

//4     保留 不使用的數字請用下行宣告做預設值
byte rawData_4[1] = { 0 };

//5     快速冷房:開機 , 冷氣 , 25度 , 風速4級(最大) , 風向自動
byte rawData_5[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x4, 0x18, 0x90, 0x6C, 0x22, 0x46, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C };

//6     一般使用:開機 , 冷氣 , 26度 , 風速自動, 風向自動
byte rawData_6[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x4, 0x18, 0x50, 0x6C, 0x2, 0x46, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF4 };

//7     一般使用:開機 , 冷氣 , 27度 , 風速自動, 風向自動
byte rawData_7[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x4, 0x18, 0xD0, 0x6C, 0x2, 0x46, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC };

//8     一般使用:開機 , 冷氣 , 28度 , 風速自動, 風向自動
byte rawData_8[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x4, 0x18, 0x30, 0x6C, 0x2, 0x46, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8C };

//9     搭配風扇:開機 , 冷氣 , 29度 , 風速自動, 風向自動
byte rawData_9[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x4, 0x18, 0xB0, 0x6C, 0x2, 0x46, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C };

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


#define IR_NUM_SIZE  10             //使用0-9,共10個按鍵

int recvPin = 2;                    //紅外線接收在 Pin2; 發射內定在 Pin3
//int Pin9_SW = 9;                  //Pin9 按鈕保留
int Recv_Enable_LED_Pin = 10;       //Pin10 啟動指示燈
int LED_Buzzer_Pin = 11;            //Pin11 動作指示燈及聲音
int khz = 38;                       //使用 38kHz 頻率發射

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

uint8_t IR_MAP_Index;               //儲存找到對應的 0-9 數字
struct NUM_MAP {                    //將0-9的紅外線編碼對應成數字 0-9 ,方便比對搜尋
  unsigned long IR_CODE;
  uint8_t INDEX_NUM;
};
static 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}};


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


//發送紅外線編碼陣列, 位元由左至右發送, 資料共計 rawData_Bits 位元
//void RAWIRSend(發送陣列資料, 陣列大小, 發射位元數, 發射頻率)
void RAWIRSend(byte SendRAWData[], unsigned int SendRAWData_Len, unsigned int SendRAWData_Bits, unsigned int hz)
{
  int Rep_Temp = 0; 
  unsigned int rawData_Bits_Count =0;
  byte Mask = 1; 

  if (RAW_RPT_SPACE > 0) Rep_Temp = 2;
  else Rep_Temp = 1;
    
  irsend.enableIROut(hz);  // khz
  irsend.space(max(RAW_HDR_MARK,RAW_HDR_SPACE));  //Initial signal
  
  for (int j = 0; j < Rep_Temp; j++) {  // if Repeat
    //Start Single
    irsend.mark(RAW_HDR_MARK);
    irsend.space(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 (SendRAWData[i] & Mask) {
            //Bit 1
            irsend.mark(RAW_BIT_MARK);
            irsend.space(RAW_ONE_SPACE);
          }
          else {
            //Bit 0
            irsend.mark(RAW_BIT_MARK);
            irsend.space(RAW_ZERO_SPACE);
          }
          rawData_Bits_Count++;
        }
      }
    }
    if (Rep_Temp > 1) {
      //Send Repeat Single
      irsend.mark(RAW_RPT_MARK);
      irsend.space(RAW_RPT_SPACE);      
    }
    irsend.space(max(RAW_HDR_MARK,RAW_HDR_SPACE));  //Stop Single
  }  
}

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

  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, HIGH);                 //打開 主板指示燈, 要關閉請改 LOW
}

//主程式迴圈
void loop() {
  if (irrecv.decode(&results)) {         //是否偵測到紅外線信號
    if (results.decode_type == NEC) {    //判斷為哪個數字鍵, 只偵測 NEC 編碼
      Serial.print("NEC Code : ");       //提供序列埠監看
      Serial.print(results.value, HEX);
      IR_MAP_Index = IR_NUM_SIZE;
      for (uint8_t i = 0; i < IR_NUM_SIZE; i++) {
        if (results.value == IR_MAP[i].IR_CODE) {
          IR_MAP_Index = IR_MAP[i].INDEX_NUM;
          break;
        }
      }
      Serial.print("  Map Index : ");
      Serial.println(IR_MAP_Index, DEC);
      
      if (IR_MAP_Index < IR_NUM_SIZE) { //是否有找到對應的數字鍵
        delay(100);                     //收發間隔,穩定用
        switch (IR_MAP_Index) {         //保留每個數字對應的程式操作空間
          case 0:
            ST_LED_Buzzer (10);     //蜂鳴器先叫, 以免和冷氣混在一起
            RAWIRSend(rawData_0, sizeof(rawData_0) / sizeof(rawData_0[0]), rawData_Bits, khz);
            break;
          case 1:
            ST_LED_Buzzer (1);     //蜂鳴器先叫, 以免和冷氣混在一起
            RAWIRSend(rawData_1, sizeof(rawData_1) / sizeof(rawData_1[0]), rawData_Bits, khz);
            break;
          case 2:
            ST_LED_Buzzer (2);     //蜂鳴器先叫, 以免和冷氣混在一起
            RAWIRSend(rawData_2, sizeof(rawData_2) / sizeof(rawData_2[0]), rawData_Bits, khz);
            break;
          case 3: 
            ST_LED_Buzzer (3);     //蜂鳴器先叫, 以免和冷氣混在一起
            RAWIRSend(rawData_3, sizeof(rawData_3) / sizeof(rawData_3[0]), rawData_Bits, khz);
            break;
          case 4:
            ST_LED_Buzzer (4);     //蜂鳴器先叫, 以免和冷氣混在一起
            RAWIRSend(rawData_4, sizeof(rawData_4) / sizeof(rawData_4[0]), rawData_Bits, khz);
            break;
          case 5:
            ST_LED_Buzzer (5);     //蜂鳴器先叫, 以免和冷氣混在一起
            RAWIRSend(rawData_5, sizeof(rawData_5) / sizeof(rawData_5[0]), rawData_Bits, khz);
            break;
          case 6:
            ST_LED_Buzzer (6);     //蜂鳴器先叫, 以免和冷氣混在一起
            RAWIRSend(rawData_6, sizeof(rawData_6) / sizeof(rawData_6[0]), rawData_Bits, khz);
            break;
          case 7:                          
            ST_LED_Buzzer (7);     //蜂鳴器先叫, 以免和冷氣混在一起
            RAWIRSend(rawData_7, sizeof(rawData_7) / sizeof(rawData_7[0]), rawData_Bits, khz);
            break;
          case 8:
            ST_LED_Buzzer (8);     //蜂鳴器先叫, 以免和冷氣混在一起
            RAWIRSend(rawData_8, sizeof(rawData_8) / sizeof(rawData_8[0]), rawData_Bits, khz);
            break;
          case 9:
            ST_LED_Buzzer (9);     //蜂鳴器先叫, 以免和冷氣混在一起
            RAWIRSend(rawData_9, sizeof(rawData_9) / sizeof(rawData_9[0]), rawData_Bits, khz);
            break;
        }
        irrecv.enableIRIn();    //IRRemote 在發射的時候會把接收功能停掉, 重開接收
      } else {
        irrecv.resume();    //沒收到定義的 0-9 信號, 重新接收
      }
    }
    irrecv.resume();    //沒收到NEC信號, 重新接收
  }
}

請將程式編譯上傳,再次提醒測試時請一定要將紅外線發射LED的正上方圓頂對準冷氣機紅外線接收器的位置,測試時按下上述中米家APP內創維遙控器的 0-9 數字鍵,順利的話冷氣機就會做出對應的動作了。

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

剩下的就是依各人需求將這個加入米家APP的智慧連動了,這樣小愛同學也能輕鬆幫忙。

想做這個東西很久了,現在終於完成,實在是說不出的高興,補完了心中的一塊拼圖。

謝謝耐心看完這份小專題。



Xiaomi Mijia universal infrared ray remote control for Unsupported air conditioner.

2019年7月15日 星期一

冷氣機紅外線遙控訊號連動 - 05 紅外線接收、發射電路板製作

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

※ 2019.8 後補「積木式免焊接 記憶型空調紅外線轉譯器」一文,可供快速製作成品

紅外線接收、發射電路板製作


電路板焊接所需工具:

 1. 電烙鐵 (最好配有 基座, 海棉...)
 2. 焊錫
 3. 吸錫器
 4. 單芯細電線
 5. 斜口鉗
 6. 若有三用電表的話,可用來檢測是否有短路,非必備

DIY 製作請注意:
 1. 安全第一
 2. 無電工相關經驗者,請有經驗的朋友幫忙協助,或請人代工
 3. 請在通風良好的環境下進行零件電路焊接作業
 4. 請注意電烙鐵溫度,不要被燙偒
 5. 焊接完畢後,請務必仔細檢查焊錫接點是否有短路
 6. 焊接完畢後,第一次上電時請不要接到電腦的USB上,也不要接到外接行動電源上,請接在電壓5伏特的USB充電器上,並請隨時準備手動關閉電源,以免若有焊接點短路時會造成危險

電路板佈線

在前篇 02中,有購買 Arduino UNO R3 ProtoShield 原型擴展板,依其電路板參考佈線如下,另為方便調整紅外線發射LED和接收器的方向,建議保留零件原始接腳長度立於電路板上:
1. 不同廠商或版本的擴展板PCB原電路走線和放置元件可能不太相同,焊接前請先確認
2. 紅外線發射LED直立, + 極(長腳)在下圖右方,正負極接反會無作用,且有可能會燒毀
3. 紅外線接收器正面面向下方 (直立正面向PCB外,Out 腳位在左方,),接錯腳位有可能會燒毀
4. 若要更改走線,請注意 紅外線發射LED 不能有朝向 紅外線接收器 正面,會干擾收訊
6. 有源蜂鳴器 + 極(長腳)接在Pin連接座上11位置,- 極接在Pin連接座上 GND,直接插在Pin連接座上即可,怕吵的話日後可直接拆下,僅作聲音提示功用
7. 下圖紅外線發射LED 的 +極在右方,保留的腳位長度可向前、後彎曲,若有需要為左、右彎曲的話請將 +極改至 -極的下方及對應走線
8. 下方有實際完成圖可參照,請完全無疑問後再開始焊接

各腳位功用如下:
腳 2 連接紅外線接收器 Out 腳位,接收信號
腳 3 連接紅外線發射LED +極(長腳),發射信號
腳 9 連接擴展板上的空置按鈕 (下方中間位置),可依程式控制使用,預設電位在 High (5V)
腳 10 連接擴展板附下方 LED,可依程式控制亮暗指示燈使用
腳 11 連接擴展板上方 LED,可依程式控制亮暗指示燈使用
腳 11 Pin座連接有源蜂鳴器 +極(長腳),其左方 GND Pin座連接有源蜂鳴器 -極,依程式控制聲音指示使用
PS : 腳 11 同時接 蜂鳴器 與 LED 控制使用

擴展板左下方有附按鈕 Reset,可重置程式執行。



電路板焊接完成

正上方

正下方

四週



連接合體 Arduino 開發板 與 擴展板 時請確認有對準 Pin 腳座 (二個板子上皆有印刷腳位編號,對齊編號腳位壓緊到底即可)

Arduino 開發板、擴展板 連接完成圖

PS: 下方的壓克力板,原本來是組合的 開發板壓克力外殼,用來保護Arduino開發板的,但因接擴展板會接不起來,只好拆掉只保留底座;一般找4根螺絲組墊高底部保護下方電路即可,有興趣的話可至網拍找找,借網拍圖如下。

完成後,請先接在電壓5伏特的USB充電器上做送電測試,並請隨時準備手動關閉電源,以免若有焊接點短路時會造成危險。確認沒問題後請重做前篇 04 ,確認接收、發射電路運作正常。

所有準備都就緒了,接下來最後就是撰寫和 小米萬用遙控器 連動控制冷氣空調的程式了...


冷氣機紅外線遙控訊號連動 - 06 紅外線訊號轉譯程式


冷氣機紅外線遙控訊號連動 - 04 紅外線發射程式測試

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

※ 2019.8 後補「積木式免焊接 記憶型空調紅外線轉譯器」一文,可供快速製作成品

紅外線發射程式測試


使用 Arduino IDE 內含之 IRremote程式庫 進行測試

PS : IRsendRawDemo 方式不適用冷氣機,本範例僅供參考不需實作,是因這個過程才發現解決的方式。
開啟讀取程式庫中的範例檔 IRsendRawDemo.ino ( 範例檔案位置在「文件」下的Arduino\libraries\IRremote\examples\IRsendRawDemo\IRsendRawDemo.ino ),將前篇 03 中的陣列參數 ( unsigned int rawData[ ... ) 代入貼上

測試結果:
1. 冷氣機接收狀況不穩定,有時有作用,有時沒作用 --> 可能信號多,解錯的機率也變多,這樣實務上不行啊...
2. 前篇 03 中 IRrecvDumpV2 每次取樣到的的資料都不盡相同 --> 取樣會有誤差範圍
3. 另測試DVD遙控器訊號,發現沒什麼問題,但僅在偶數次時才會有作用 --> 一般遙控器信號較少,相對發生解碼錯誤的機率也較少(也有可能容錯率較大);偶數次時才會有作用的問題,應是相同信號要接收二次比對確認,關於這點我查到有的冷氣機也是要求發送二次相同的信號才能有作用。但是從 IRrecvDumpV2 得出的陣列中沒看到有發送二次相同訊號?


紅外線接收程式修改

經過好幾天的失敗、嘗試與查證後,還好找到了解決方法:
1. 關於因取樣數量較多產生誤差機率變多,而使接收變得不穩定的問題,從冷氣機紅外線編碼的時序圖來看

需要找出下列參數:
1. HDR_MARK          38kHz 載波的持續時間 (PS : 上圖黃色部份皆為載波的持續時間
2. HDR_SPACE        無訊號時間,1+2 構成了起始訊號,通知冷氣機有訊號要開始傳送
3. BIT_MARK            通知有資料訊號要傳送
4. ZERO_SPACE      資料位元 0 的無訊號時間, 3+4 構成了位元 0 的資料訊號
5. ONE_SPACE        資料位元 1 的無訊號時間, 3+5 構成了位元 1 的資料訊號
6. RPT_MARK          通知有資料訊號要傳送
7. RPT_SPACE        長間隔無訊號時間,6+7 後準備發送第二次相同資料

上述 7個參數的時間長度是固定的,但Arduino取樣時因有誤差而不相同,分析前篇 03 中 IRrecvDumpV2 取得的紅外線陣列參數,可判讀出上列參數:
1. HDR_MARK          陣列的第 1 個數值
2. HDR_SPACE        陣列的第 2 個數值
3. BIT_MARK            統計從第 3 個開始的奇數位置,找出出現最多次的數值
4. ZERO_SPACE      統計從第 4 個開始的偶數位置,找出出現最多次的短間隔
5. ONE_SPACE        統計從第 4 個開始的偶數位置,找出出現最多次的長間隔
6. RPT_MARK          找尋 1+2 是否有重覆出現,有的話其出現位置的 -2位置為 RPT_MARK
7. RPT_SPACE        找尋 1+2 是否有重覆出現,有的話其出現位置的 -1位置為 RPT_SPACE

然後將 3+4 , 3+5 的 0、1 信號編碼為 Byte (8位元),陣列的長度約減少16倍,且資料型態由 unsigned int (16位元) 變為 Byte (8位元),整個資料空間約共減少了 32 倍,日後若有需要放多組訊號抽樣資料的話也不用擔心記憶體不夠用的問題。

而之前提到沒看到DVD遙控器訊號送出二次相同信號,猜想可能因 IRremote 程式庫的預設取樣停止判斷時間太短,而未去收第二組訊號,找到該參數為 _GAP ,位於 IRremoteInt.h (同修改 RAWBUF 參數的檔案),修改如下:
// Minimum gap between IR transmissions
#define _GAP            30000 //預設 5000
#define GAP_TICKS       (_GAP/USECPERTICK)

根據以上方法,修改前篇 03中 IRrecvDumpV2 檔案(同樣要修改第9行 int recvPin = 2; ),將 163 行以後取代如下:
//+=============================================================================
// 取得原生訊號參數及重新編碼 (By MGP. 2019.7.13  https://mygraphpaper.blogspot.com/)
// 需修改 IRremoteInt.h
// Line 43 #define RAWBUF  400    // 預設儲存信號長度為 101 個 , 但冷氣機編碼超過, 需加大供測試
// Line 52 int rawlen; //上的大小超過 Byte 255計數, 改為 int 
// Line 98 #define _GAP    30000  // 預設信號結束的空白時間 5000, 但太小導致取樣不到 RAW_RPT_SPACE 的間隔參數, 需加大供測試
// 以上參數, 僅本程式測試時修改用, 不執行本取樣程式請改回預設值
//
unsigned int Probability[3],Temporary[3],CheckCount[3];   //取樣陣列

//FindMaxCount(原始編碼訊號, 處理編碼訊號陣列起始位置, 是否需比對 MARK 值, 判斷 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 (unsigned int i=0 ; i < (sizeof(Probability) / sizeof(Probability[0])) ; i++) {
    Probability[i]=0; //初始陣列
    Temporary[i]=0;
  }
  
  for (int i = StartPos; i < results->rawlen; i+=2) {
    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 短的判斷值     
        if (results->rawbuf[i] > Probability[0]){
          Probability[2] = Probability[1];      //暫存 RAW 值, 並排列大小
          Probability[1] = Probability[0];      
          Probability[0] = results->rawbuf[i];  //該 RAW 值出現次數
          Temporary[2] = Temporary[1];
          Temporary[1] = Temporary[0];
          Temporary[0] = 0;        
        } else {
          if (results->rawbuf[i] != Probability[0]) {
            if (results->rawbuf[i] > Probability[1]) {
              Probability[2] = Probability[1];
              Probability[1] = results->rawbuf[i];
              Temporary[2] = Temporary[1];
              Temporary[1] = 0;    
            } else {
              if (results->rawbuf[i] != Probability[1]) {
                if (results->rawbuf[i] > Probability[2]) {
                  Probability[2] = results->rawbuf[i];
                  Temporary[2] = 0;    
                }
              }
            }
          }
        }
      }
    }
    if (results->rawbuf[i] == Probability[0]) Temporary[0]++;         //RAW 值出現次數
    else if (results->rawbuf[i] == Probability[1]) Temporary[1]++;
         else if(results->rawbuf[i] == Probability[2]) Temporary[2]++;  
  }

  for (unsigned int i=0 ; i < (sizeof(Probability) / sizeof(Probability[0])) ; i++) {
    CheckCount[i] = Temporary[i];
    if (CheckCount[i] < 3) CheckCount[i]=0;     //RAW 值出現次數太小,忽略取樣
  }
  
  if ((Probability[1] - Probability[2]) != 1) CheckCount[2] = 0;    //取樣值相差太多,忽略取樣
  if ((Probability[0] - Probability[1]) != 1) {
    CheckCount[2] = 0;
    CheckCount[1] = 0;
  }

  if ( (CheckCount[0]+CheckCount[1]+CheckCount[2]) >= 3 ) {     //取得相近取樣值中, 出現最多次的回傳
    if((CheckCount[2] > CheckCount[1]) && (CheckCount[2] > CheckCount[0]) ) Return_MAX_Value = Probability[2];
    else if ((CheckCount[1] > CheckCount[0])) Return_MAX_Value = Probability[1];
         else Return_MAX_Value = Probability[0];  
  }
  
  return Return_MAX_Value;
}


void EncodeRAWCode (decode_results *results)
{
  unsigned int RAW_HDR_MARK    = 0;   //初始信號 RAW_HDR_MARK + RAW_HDR_SPACE
  unsigned int RAW_HDR_SPACE   = 0;
  unsigned int RAW_BIT_MARK    = 0;   //0 與 1 的信號組合,
  unsigned int RAW_ONE_SPACE   = 0;   //1 = RAW_BIT_MARK + RAW_ONE_SPACE
  unsigned int RAW_ZERO_SPACE  = 0;   //0 = RAW_BIT_MARK + RAW_ZERO_SPACE
  unsigned int RAW_RPT_MARK    = 0;   //冷氣機訊號通常會發射2次相同編碼以做比對, RAW_RPT_MARK + RAW_RPT_SPACE 為間隔訊號
  unsigned int RAW_RPT_SPACE   = 0;


  RAW_HDR_MARK = results->rawbuf[1];  //第一個訊號[0]無效,[1]為RAW_HDR_MARK
  RAW_HDR_SPACE = results->rawbuf[2]; //[2]RAW_HDR_SPACE
  Serial.println("");
  Serial.println(F("// My Data Start , Mark & Ctrl-C Copy ---------------")); 
  Serial.println(""); 
  Serial.print(F("#define RAW_HDR_MARK    "));
  Serial.print(RAW_HDR_MARK * USECPERTICK, DEC);  //次數 X 間隔(50us)
  Serial.println("");  
  Serial.print(F("#define RAW_HDR_SPACE   "));
  Serial.print(RAW_HDR_SPACE * USECPERTICK, DEC);  


  //取得重覆訊號的位置
  int Tolerance_scope = 4;    //取樣會有誤差, 若有需要可嘗試修改
  for (int i = 3; i < results->rawlen; i+=2) {    //找出 RAW_HDR_MARK+RAW_HDR_SPACE 重覆出現的位置
    if ((results->rawbuf[i]>=RAW_HDR_MARK-Tolerance_scope) && (results->rawbuf[i]<=RAW_HDR_MARK+Tolerance_scope)) {
      if ((results->rawbuf[i+1]>=RAW_HDR_SPACE-Tolerance_scope) && (results->rawbuf[i+1]<=RAW_HDR_SPACE+Tolerance_scope)){
        //檢查 重覆訊號的位置 是否正確
        if(i==(((results->rawlen-4)/2)+3)) {  //總長度-1頭-1尾-2RAW_RPT, 取半為一次傳送長度, 再+2RAW_RPT之後的位置
          RAW_RPT_MARK = results->rawbuf[i-2];      //該位置的前2個為 RAW_RPT_MARK
          RAW_RPT_SPACE = results->rawbuf[i-1];     //該位置的前1個為 RAW_RPT_SPACE
          results->rawlen = i-2;    //只保留第一次完整編碼的長度, 第二次重覆部份不使用
        } 
      }
    }
  }


  //取得找 BIT_MARK , 最大值範圍中最多出現次數 , 3開始的奇數位置為 RAW_BIT_MARK 值
  RAW_BIT_MARK = FindMaxCount(results , 3, 0, 0);
  Serial.println("");   
  Serial.print(F("#define RAW_BIT_MARK    "));
  Serial.print(RAW_BIT_MARK * USECPERTICK, DEC);
  Serial.print("     // ");
  Serial.print(Probability[0] * USECPERTICK, DEC);
  Serial.print(" -> ");
  Serial.print(Temporary[0] , DEC);
  Serial.print(" times.  "); 
  Serial.print(Probability[1] * USECPERTICK, DEC);
  Serial.print(" -> ");
  Serial.print(Temporary[1] , DEC);
  Serial.print(" times.  ");
  Serial.print(Probability[2] * USECPERTICK, DEC);
  Serial.print(" -> ");
  Serial.print(Temporary[2] , DEC);
  Serial.print(" times.");    

 
  //取得找 ONE_SPACE , 最大值範圍中最多出現次數, 4開始的偶數位置為 RAW_ONE_SPACE 值
  RAW_ONE_SPACE = FindMaxCount(results , 4, RAW_BIT_MARK, 0); 
  Serial.println(""); 
  Serial.print(F("#define RAW_ONE_SPACE   "));
  Serial.print(RAW_ONE_SPACE * USECPERTICK, DEC);
  Serial.print("     // ");
  Serial.print(Probability[0] * USECPERTICK, DEC);
  Serial.print(" -> ");
  Serial.print(Temporary[0] , DEC);
  Serial.print(" times. "); 
  Serial.print(Probability[1] * USECPERTICK, DEC);
  Serial.print(" -> ");
  Serial.print(Temporary[1] , DEC);
  Serial.print(" times. ");
  Serial.print(Probability[2] * USECPERTICK, DEC);
  Serial.print(" -> ");
  Serial.print(Temporary[2] , DEC);
  Serial.print(" times.");  


  //取得找 ZERO_SPACE , 最大值範圍中最多出現次數, 4開始的偶數位置為 RAW_ONE_SPACE 值
  unsigned int MAX_RAW_ZERO_SPACE = RAW_ONE_SPACE/2 + 2;    //自訂ZERO_SPACE最大值的上限, 若有需要可嘗試修改
  RAW_ZERO_SPACE = FindMaxCount(results , 4, RAW_BIT_MARK, MAX_RAW_ZERO_SPACE);  
  Serial.println(""); 
  Serial.print(F("#define RAW_ZERO_SPACE  "));
  Serial.print(RAW_ZERO_SPACE * USECPERTICK, DEC);
  Serial.print("     // ");
  Serial.print(Probability[0] * USECPERTICK, DEC);
  Serial.print(" -> ");
  Serial.print(Temporary[0] , DEC);
  Serial.print(" times. "); 
  Serial.print(Probability[1] * USECPERTICK, DEC);
  Serial.print(" -> ");
  Serial.print(Temporary[1] , DEC);
  Serial.print(" times. ");
  Serial.print(Probability[2] * USECPERTICK, DEC);
  Serial.print(" -> ");
  Serial.print(Temporary[2] , DEC);
  Serial.print(" times.");  
  Serial.println("");

  Serial.print(F("#define RAW_RPT_MARK    "));
  Serial.print(RAW_RPT_MARK * USECPERTICK, DEC);
  Serial.println(F("      //Repeat Gap"));

  Serial.print(F("#define RAW_RPT_SPACE   "));
  Serial.print(RAW_RPT_SPACE * USECPERTICK, DEC);


  //將 Mark+Space 的 0 1 編碼為 Byte 型態存放
  byte rawData[(results->rawlen - 2)/8+1] ; //計算陣列長度, 前三個非資料扣掉
  byte rawTemp = 0; //暫存值
  unsigned int Bits_Count = 0;  //目前 Bit 計數
  unsigned int rawData_Count = 0;   //目前陣列位置計數
  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
      rawData[rawData_Count] = rawTemp;
      Bits_Count = 0;
      rawData_Count ++;
      rawTemp = 0;
    }
  }

  if (Bits_Count != 0) {    //不足 1Byte 的補完
      for (unsigned int i = (Bits_Count+1); i <= 8; i++) {
        rawTemp = rawTemp << 1;
      }
      rawData[rawData_Count] = rawTemp;
      rawData_Count ++;
  }
  
  Serial.println("");
  Serial.println("");  
  Serial.print(F("int rawData_Bits = ")); 
  Serial.print((results->rawlen-3)/2, DEC); 
  Serial.print(" ;");   
  Serial.println("");   
  Serial.print("byte rawData[");
  Serial.print(rawData_Count, DEC);
  Serial.print("] = { ");

  for (unsigned int i = 0;  i < rawData_Count;  i++) {
    Serial.print("0x");
    Serial.print(rawData[i], HEX);
    if ( i < rawData_Count-1 ) Serial.print(", ");
  }
  Serial.print(" };");
  Serial.println("");
  Serial.println("");
  Serial.println(F("// My Data End --------------- By MGP."));  
  Serial.println("");
  Serial.println("");  
}
//=============================================================================


//+=============================================================================
// The repeating section of the code
//
void  loop ( )
{
  decode_results  results;        // Somewhere to store the results

  if (irrecv.decode(&results)) {  // Grab an IR code
    dumpInfo(&results);           // Output the results
    dumpRaw(&results);            // Output the results in RAW format
    dumpCode(&results);           // Output the results as source code
    EncodeRAWCode(&results);      // 取得冷氣編碼 (By MGP.)
    Serial.println("");           // Blank line between entries
    irrecv.resume();              // Prepare for the next value
  }
}

編譯上傳後,取樣冷氣機紅外線遙控器信號如下:(同一按鍵信號可多按幾次,找多次出現的參數增加準確率)

因收到紅外線編碼資料二次,RAWBUF 400 個也不夠用,只取第一次資料做分析判斷。
紅外線發射所需資料為虛線之間的程式碼
------------------------------------------------
#define RAW_HDR_MARK    3400
#define RAW_HDR_SPACE   1700
#define RAW_BIT_MARK    400     // 450 -> 29 times.  400 -> 93 times.  350 -> 22 times.
#define RAW_ONE_SPACE   1300     // 1350 -> 7 times. 1300 -> 19 times. 500 -> 30 times.
#define RAW_ZERO_SPACE  450     // 500 -> 30 times. 450 -> 74 times. 400 -> 6 times.
#define RAW_RPT_MARK    400      //Repeat Gap
#define RAW_RPT_SPACE   17000

int rawData_Bits = 144 ;
byte rawData[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x0, 0x18, 0xB0, 0x6C, 0x2, 0x71, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7C };
------------------------------------------------


紅外線發射程式測試

讀取本頁開頭的 IRsendRawDemo.ino 範例,要增加解碼發射程式,整個檔案修改如下,另虛線之間的程式碼請依自家測得的冷氣機參數代入

#include <IRremote.h>

IRsend irsend;

// My Data Start , Mark & Ctrl-C Copy ---------------

#define RAW_HDR_MARK    3400
#define RAW_HDR_SPACE   1700
#define RAW_BIT_MARK    400     // 450 -> 29 times.  400 -> 93 times.  350 -> 22 times.
#define RAW_ONE_SPACE   1300     // 1350 -> 7 times. 1300 -> 19 times. 500 -> 30 times.
#define RAW_ZERO_SPACE  450     // 500 -> 30 times. 450 -> 74 times. 400 -> 6 times.
#define RAW_RPT_MARK    400      //Repeat Gap
#define RAW_RPT_SPACE   17000

int rawData_Bits = 144 ;
byte rawData[18] = { 0xC4, 0xD3, 0x64, 0x80, 0x0, 0x0, 0x18, 0xB0, 0x6C, 0x2, 0x71, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7C };

// My Data End --------------- By MGP.


//發送上方編碼陣列,由左至右, 資料共計 rawData_Bits = 144 Bits
//void RAWIRSend(發送陣列資料, 陣列大小, 發射位元數, 發射頻率)
void RAWIRSend(byte SendRAWData[], unsigned int SendRAWData_Len, unsigned int SendRAWData_Bits, unsigned int hz)
{
  int Rep_Temp = 0; 
  unsigned int rawData_Bits_Count =0;
  byte Mask = 1; 

  if (RAW_RPT_SPACE > 0) Rep_Temp = 2;
  else Rep_Temp = 1;
    
  irsend.enableIROut(hz);  // 38khz
  irsend.space(max(RAW_HDR_MARK,RAW_HDR_SPACE));  //Zero Single
  
  for (int j = 0; j < Rep_Temp; j++) {  // Repeat
    //Start Single
    irsend.mark(RAW_HDR_MARK);
    irsend.space(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 (SendRAWData[i] & Mask) {
            //Bit 1
            irsend.mark(RAW_BIT_MARK);
            irsend.space(RAW_ONE_SPACE);
          }
          else {
            //Bit 0
            irsend.mark(RAW_BIT_MARK);
            irsend.space(RAW_ZERO_SPACE);
          }
          rawData_Bits_Count++;
        }
      }
    }
    if (Rep_Temp > 1) {
      //Send Repeat Single
      irsend.mark(RAW_RPT_MARK);
      irsend.space(RAW_RPT_SPACE);      
    }
    irsend.space(max(RAW_HDR_MARK,RAW_HDR_SPACE));  //Stop Single
  }  
}


void setup()
{

}

void loop() {
  int khz = 38; // 38kHz carrier frequency
  
  //冷氣機 打開
  RAWIRSend(rawData, sizeof(rawData) / sizeof(rawData[0]), rawData_Bits, khz);

  //延遲 10 分鐘
  delay(600000); 
}


測試結果成功且穩定!!

測試時請注意,紅外線發射LED的正上方有聚光的作用,請一定要將上方圓頂對準冷氣機紅外線接收器的位置,另按下 Arduino 開發板上的 Reset 鍵,可重覆發射紅外線測試訊號。

接下來將麵包板測試電路做到電路板上...


冷氣機紅外線遙控訊號連動 - 05 紅外線接收、發射電路板製作

Xiaomi Mijia universal infrared ray remote control for Unsupported air conditioner.

2019年7月14日 星期日

冷氣機紅外線遙控訊號連動 - 03 紅外線接收程式測試

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

※ 2019.8 後補「積木式免焊接 記憶型空調紅外線轉譯器」一文,可供快速製作成品

紅外線接收程式測試


使用 Arduino IDE 內含之 IRremote程式庫 進行測試

IRremote 程式庫在本類別 01 篇中已安裝好,請執行桌面上的Arduino應用程式,點選視窗左上 檔案->開啟,讀取程式庫中的範例檔 IRrecvDumpV2.ino ( 範例檔案位置在「文件」下的Arduino\libraries\IRremote\examples\IRrecvDumpV2\IRrecvDumpV2.ino )

請將第9行改為 int recvPin = 2;  (麵包板的紅外線接收器腳位 Out , 接在編號 2的腳位)
// Tell IRremote which Arduino pin is connected to the IR Receiver (TSOP4838)
//
int recvPin = 2;
IRrecv irrecv(recvPin);

執行 "驗證/編譯" 本程式
下方黑色顯示視窗捲動時發現有警告訊息,使用記事本程式 (或建議使用 Notepad++ 程式) 修改有問題的 IRremote.cpp 檔案內容 (檔案位置在「文件」下的Arduino\libraries\IRremote 目錄),有相同的三段程式碼要修改,各在107、81、56 行,原始碼為
  if (passed)
    DBG_PRINTLN(F("?; passed"));
  else
    DBG_PRINTLN(F("?; FAILED")); 

修改為
#if DEBUG    
  if (passed)
    DBG_PRINTLN(F("?; passed"));
  else
    DBG_PRINTLN(F("?; FAILED")); 
#endif

存檔後再重新編譯,無問題後執行 "上傳" ,將程式碼寫入 Arduino 開發板內。

打開 序列埠監控視窗,先拿一般電視遙控器對準紅外線接收器測試,有正確抓到編碼解碼,接收線路及接收程式測試正常

使用冷氣機遙控器測試時出現了 紅外線編碼太長 的問題,需要修正檔案

使用記事本程式修改有問題的 IRremoteInt.h 檔案內容 (檔案位置在「文件」下的Arduino\libraries\IRremote 目錄),修改43、52 行,加大能接收的訊號長度,程式碼修正如下
#define RAWBUF  400      //加大紅外線編碼取樣個數
int          rawlen;                 //編碼取樣個數的計數超過255,型態改為 int 整數,可計數至 32767
//------------------------------------------------------------------------------
// Information for the Interrupt Service Routine
//
#define RAWBUF  400  // Maximum length of raw duration buffer   修正為 400

typedef
 struct {
 // The fields are ordered to reduce memory over caused by struct-padding
 uint8_t       rcvstate;        // State Machine state
 uint8_t       recvpin;         // Pin connected to IR data from detector
 uint8_t       blinkpin;
 uint8_t       blinkflag;       // true -> enable blinking of pin on IR processing
 int       rawlen;          // counter of entries in rawbuf  修改型態為 int
 unsigned int  timer;           // State timer, counts 50uS ticks.
 unsigned int  rawbuf[RAWBUF];  // raw data
 uint8_t       overflow;        // Raw buffer overflow occurred
 }
irparams_t;

存檔後再重新 編譯、上傳,將程式碼寫入 Arduino 開發板內。
我家中的冷氣遙控器紅外線編碼取樣個數為 291個,使用手機上的 遙控精靈APP 選擇日立空調機型,取樣個數為 371 個,比程式庫預設的長度 RAWBUF 101 個多出不少,也超過一個 Byte 能計數的大小 255個,而如日立冷氣的紅外線編碼資料大小就要 371 X 32 bits(unsigned int) / 8 = 1484 Bytes,Arduino開發板內建的記憶體空間才 2048 Bytes,這樣會不太夠用吧...

實驗到這裏,覺得這是否就是小米萬能遙控器不能成功完整複製冷氣機紅外線編碼的類似原因...

接下來測試發射錄製到的紅外線信號是否可正常動作,複製視窗中最下方的一行陣列參數 ( unsigned int rawData[ ... ) ,拿來測試紅外信號線發射。

接下來進行紅外線發射程式測試...

冷氣機紅外線遙控訊號連動 - 04 紅外線發射程式測試