由於方格紙網站廣告收入微薄, 一直以來都架設在免費的GCP上服務, 目前GCP已不再提供免費架站, 且網站函式庫需使用PHP7, 已不符目前PHP8為主流的其它免費架站環境, 雖然不捨, 還是決定停止運作...
Prism
2025年10月20日 星期一
2020年8月28日 星期五
小米手環5 - 02 錶盤製作
< 本文請勿轉載,謝謝 ! >
做了一些小米手環5 的錶盤,將作的錶盤做成一篇比較好整理,修改部份請仍參考 前一篇文章。
目前做的錶盤如下網頁連結,有興趣的話可以下載用用看,AmazFaces 在 Google Play
和 Apple Store 都有APP,安裝後請搜尋 MyGraphPaper 可在手機查看錶盤:
全部錶盤 https://amazfitwatchfaces.com/search/mi-band-5/authorID/324908
1. 大數字:為了有點老花眼的自己做了一個大數字的錶盤
PS:電量指示
綠色 100% ~ 91% ,灰色 90% ~ 51%,黃色 50% ~ 31%,紅色 30%以下
AmazFaces
下載 Big&Simple - https://amazfitwatchfaces.com/mi-band-5/view/295
2. 窗戶:下載後可以參考前篇文章換成自己喜歡的圖片
AmazFaces 下載
The Windows - https://amazfitwatchfaces.com/mi-band-5/view/377
3. 出發:看若步數點亮如跑道的燈光,有著成就也有美麗...
AmazFaces
下載 Set off - https://amazfitwatchfaces.com/mi-band-5/view/390
4. 步數拼圖:實驗性作品...
AmazFaces 下載 Jigsaw Puzzle -
AERITH - https://amazfitwatchfaces.com/mi-band-5/view/406
5. 挑戰迷宮:你能走出迷宮嗎?
AmazFaces 下載 Challenge the
maze - https://amazfitwatchfaces.com/mi-band-5/view/411
6. 紙娃娃 - 智美
AmazFaces 下載 Paper doll - TOMOMI - https://amazfitwatchfaces.com/mi-band-5/view/473
7. 紙娃娃 - 公主
AmazFaces 下載 Every girl is a
Princess ! - https://amazfitwatchfaces.com/mi-band-5/view/474
8. 運動中用錶盤
AmazFaces 下載 Sport - https://amazfitwatchfaces.com/mi-band-5/view/498
9. 步數拼圖 FFVII remake:之前實驗性拼圖的實用化...
AmazFaces 下載 Jigsaw Puzzle - FFVII remake - https://amazfitwatchfaces.com/mi-band-5/view/546
10. 心率區間
AmazFaces 下載 Heart rate Zone - https://amazfitwatchfaces.com/mi-band-5/view/554
AmazFaces 下載 Street - https://amazfitwatchfaces.com/mi-band-5/view/677
2020.9.17 更新:
新版本的錶盤製作程式 MiBandWFTool_2.1.6 已能生成錶盤隨機 ID,AmazFaces 上傳至小米手環5的錶盤不會再互相覆蓋,下述方法不需再用了,已經使用新版程式更新站內所有錶盤,以後切換下載的錶盤方便很多。
在小米手環5的"更多->設定"中啟用"長按錶盤",就可長按螢幕快速切換錶盤。
多個 AmazFaces 錶盤共存
做了這麼多錶盤,但 AmazFaces 只能留一個錶盤在手環內,好不方便...
小米手環5 內可容納上傳 3 個自訂錶盤,但 AmazFaces下載的錶盤由於皆經網站提供的程式 ( MiBandWFTool_2.1.4 ) 所產生,每個錶盤的 ID 都是相同的,造成小米手環5 內就算有空間容納新錶盤,新上傳的錶盤還是因 ID 相同會蓋掉舊的 AmazFaces 錶盤,試了好久還好找到更改錶盤 ID 的方法,讓小米手環5 可容納多個 AmazFaces 錶盤,切換上方便很多。
1. 電腦請先至下列網站安裝 檔案修改軟體 HxD Version 2.4.0.0
https://mh-nexus.de/en/hxd/
2. 請先在小米運動APP內安裝上傳一個官方錶盤至手環,如 竹林熊貓,參考 上篇 進入錶盤目錄
"本機\(手機名稱)\內部儲存空間\Android\data\com.xiaomi.hm.health\files\watch_skin_file\58\edfde78987046f3738a8b562acb5731d"
將其 edfde78987046f3738a8b562acb5731d.bin 檔案下載至電腦中。
3.
打開HxD,將上之bin檔案拖放至HxD中,再複製如下圖反白區域的16進位資料
4. 同樣用 HxD 打開 AmazFaces 下載的錶盤,將上的反白區域資料貼上覆蓋 AmazFaces Bin 檔同樣的位置後存檔即可。(請注意,修改後的檔案大小是不變的)
這樣修改後的 AmazFaces 錶盤 ID 就是 竹林熊貓 的 ID,上傳至手環內就不會蓋掉其它 AmazFaces 錶盤,也不會被其它 AmazFaces 錶盤蓋掉(前提手環內要至少要保留 1個可上傳的空間),如法泡製至其它錶盤,就可在手環內保留多個 AmazFaces 錶盤。找到喜歡的錶盤,自己動手換上 朋友、小孩、寵物、動漫...等照片,看時間感覺也會不同喔,祝大家身體健康!
2020年8月14日 星期五
小米手環5 - 01 錶盤修改
< 本文請勿轉載,謝謝 ! >
小米運動APP中 小米手環5 內建的錶盤不少,網上還有第三方提供的錶盤市場,加起來也有上百個可選,還再持續增加中,真的可以找到喜歡的錶盤,但有的錶盤想改 一週的語系、日期中日和月的前後順序、背景、人物更換...等等,讓錶盤更接近自己的喜好和習慣,看了錶盤市場AmazFaces討論區的說明,可以簡單的自己動手更改錶盤或自製錶盤...
錶盤市場 AmazFaces - https://amazfitwatchfaces.com/mi-band-5/fresh
一、下載 解封、封裝錶盤程式
請至下方連結 AmazFaces 討論區下載 解封、封裝錶盤程式
MiBandWFTool_2.1.6.zip,將其解壓縮在電腦目錄下即可
(討論區中有說到,解壓後若防毒軟體警告 WFPreview.exe
有木馬,可將其刪除,留下其它檔案即可)
https://amazfitwatchfaces.com/forum/viewtopic.php?f=29&t=720
二、解封錶盤
這邊找一個簡單的錶盤作說明:
1. 手機選擇小米運動APP中的錶盤
"交錯空間",並將"交錯空間"上傳至小米手環5作錶盤,則在手機上會甶留存"交錯空間"的本機錶盤資料。
"本機\(手機名稱)\內部儲存空間\Android\data\com.xiaomi.hm.health\files\watch_skin_file\58"
依下載錶盤的多寡,內有一至數個長串英數字的目錄,查看各目錄內的 Png 圖檔可知該目錄是那一個錶盤資料,找到"交錯空間"錶盤圖後,將該目錄下副檔名為 bin 的檔案複製至上面MiBandWFTool_2.1.4解壓縮的目錄內。
3. 將上 bin檔案的檔名更改為易記的英文字 (這裏更改為
Sample.bin,方便後面說明使用)。
4. 將
Sample.bin檔案用滑鼠拖拉至 WatchFace.exe
,則會自動解封錶盤至一個新的Sample目錄。
三、修改錶盤
修改錶盤分為二個方面進行
1. Sample.json:
解封下的目錄有一個副檔名為 json
的版面配置檔案,為文字檔,紀錄各個物件圖檔的座標位置和執行方式,如要修改圖標數值的顯示位置,可修改這裏的文字座標。
2. Png圖檔:
目錄下有很多Png圖檔,錶盤上的每個數字、文字、圖示都是由這些圖檔組成,其經由
json
指定座標顯示,如要將英文的星期改為中文的話,可修改星期Png圖檔內容,共有七個Png檔需修改。
json內的格式,依其英文說明大致都可理解各個物件是做什麼的,對照下方Png縮圖簡單介紹如下:
{
"Background": { //背景圖 (尺寸 126X294px 96dpi)
"Image": {
"X": 0, //背景圖 左上坐標 X,Y 為 0,0 位置
"Y": 0,
"ImageIndex": 0 //使用 0000.Png 圖檔
},
"Preview1": { //預覽圖 3 張
"X": 18,
"Y": 17,
"ImageIndex": 2
},
"Preview2": {
"X": 18,
"Y": 17,
"ImageIndex": 3
},
"Preview3": {
"X": 18,
"Y": 17,
"ImageIndex": 1
}
},
"Time": { //時間數字部份
"Hours": { //小時
"Tens": { //十位數部份
"X": 22, //左上坐標 X,Y 位置
"Y": 54,
"ImageIndex": 4, //使用 0004.png 開始的數字圖檔
"ImagesCount": 10 //共10 張 (可參考下方圖例)
},
"Ones": { //小時的個位數部分, 格式同上
"X": 68,
"Y": 54,
"ImageIndex": 4,
"ImagesCount": 10
}
},
"Minutes": { //分鐘部份, 格式同小時
"Tens": {
"X": 22,
"Y": 119,
"ImageIndex": 4,
"ImagesCount": 10
},
"Ones": {
"X": 68,
"Y": 119,
"ImageIndex": 4,
"ImagesCount": 10
}
},
"TimeDelimiterImage": { //小時和分鐘間的符號
"X": 82,
"Y": 154,
"ImageIndex": 14
},
"UnknownV11": 0 //不明, 都有加上這行
},
"Activity": { //活動圖標
"Steps": { //步數
"Number": { //用數字表示
"TopLeftX": 0, //顯示區塊的左上坐標
"TopLeftY": 244,
"BottomRightX": 126, //顯示區塊的右下坐標
"BottomRightY": 258,
"Alignment": "TopCenter", //步數數字在區塊中對齊的方式
"SpacingX": 0, //數字的間距
"SpacingY": 0, //數字的行距
"ImageIndex": 15, //數字使用 0015.png 開始的圖檔
"ImagesCount": 10 //共10張
}
},
"UnknownV7": 0
},
"Date": { //日期
"MonthAndDayAndYear": {
"OneLine": { //一行模式, 另有日、月分開的模式 Separate
"Number": {
"TopLeftX": 22, //顯示區塊語法同上
"TopLeftY": 184,
"BottomRightX": 68,
"BottomRightY": 198,
"Alignment": "Center",
"SpacingX": 0,
"SpacingY": 0,
"ImageIndex": 15,
"ImagesCount": 10
},
"DelimiterImageIndex": 25
},
"TwoDigitsMonth": true, //是否要雙位數顯示月份
"TwoDigitsDay": true //是否要雙位數顯示日期
},
"DayAmPm": { //顯示上午AM、下午PM
"X": 20,
"Y": 35,
"ImageIndexAMCN": 47,
"ImageIndexPMCN": 48,
"ImageIndexAMEN": 49,
"ImageIndexPMEN": 50
},
"ENWeekDays": { //星期 英文語系
"X": 74,
"Y": 183,
"ImageIndex": 26,
"ImagesCount": 7
},
"CNWeekDays": { //星期 繁體中文語系
"X": 74,
"Y": 183,
"ImageIndex": 40,
"ImagesCount": 7
},
"CN2WeekDays": { //星期 簡體中文語系
"X": 74,
"Y": 183,
"ImageIndex": 33,
"ImagesCount": 7
}
},
"StepsProgress": { //步數圓形圖標的設定
"CircleScale": {
"CenterX": 63, //中心點坐標
"CenterY": 245,
"RadiusX": 36, //半徑
"RadiusY": 36,
"StartAngle": 0, //起始角度
"EndAngle": 360, //結束角度
"Width": 4, //線的寬度
"Color": "0x1EDF9B" //線的顏色
}
},
"Status": {
"DoNotDisturb": { //勿擾模式
"Coordinates": {
"X": 10,
"Y": 10
},
"ImageIndexOn": 52
},
"Lock": { //鎖定模弍
"Coordinates": {
"X": 42,
"Y": 10
},
"ImageIndexOff": 53
},
"Bluetooth": { //藍芽狀態
"Coordinates": {
"X": 26,
"Y": 10
},
"ImageIndexOff": 51
}
},
"Battery": { //電池顯示
"BatteryText": {
"Coordinates": {
"TopLeftX": 60,
"TopLeftY": 10,
"BottomRightX": 96,
"BottomRightY": 22,
"Alignment": "TopRight",
"SpacingX": 0,
"SpacingY": 0,
"ImageIndex": 54,
"ImagesCount": 10
},
"SuffixImageIndex": 64 //附加在電量後的 % 符號
},
"BatteryIcon": { //電池圖標
"X": 96,
"Y": 10,
"ImageIndex": 65,
"ImagesCount": 10 //10張,10段
}
}
}
舉例如要修改
A. 底圖:更換 0000.png 即可,如將其改為藍色
(PS: Png 圖檔必須設為 96dpi 才能使用)
B. 預覽圖 (手環更換錶盤時用來選擇的圖示):更換 0001.png ~0003.png 即可
C. 日期中日、月的順序:
若為 OneLine 語法 需改為 Separate
語法,如原錶盤即為Separate 語法,更換日、月的坐標即可
D. 一週文字語系更改:
一般內建有多國語系,若沒有的話,如上
0026.png~0032.png 的圖,需用繪圖軟體重新用中文建立
四、封裝錶盤
修改完後,將 Sample.json 檔用滑鼠拖拉至 WatchFace.exe ,則會自動封裝錶盤為 Sample_packed.bin 檔,及幾個錶盤預覽圖片。
五、上傳錶盤(手機請先連接電腦)
1. 手機請先關閉小米運動APP,電腦檔案總管移至手機"交錯空間"的目錄位置
"本機\(手機名稱)\內部儲存空間\Android\data\com.xiaomi.hm.health\files\watch_skin_file\58\b7c4c3e35ca8b10b1ca8e835653580c2"
2. 將上四中封裝好的 Sample_packed.bin檔,檔名改為上 1 內 bin的檔名 b7c4c3e35ca8b10b1ca8e835653580c2.bin
3. 將上四中 Sample_packed_static.Png檔,檔名改為 1內 Png的檔名 fae484120a0f79f234837d1b793af0361585454996816.png
4. 將2、3二個檔案複製回 1的目錄中,覆蓋手機"交錯空間"原始錶盤檔案。
5. 手機打開小米運動APP,至錶盤管理的 本機錶盤 就會看到修改好後的錶盤,就可上傳至小米手環5作錶盤使用。
從網站 錶盤市場AmazFaces下載的錶盤,一樣是同這個步驟更改檔名,再用電腦複製至手機內覆蓋舊錶盤作更換。
AmazFaces也有提供手機APP:https://amazfitwatchfaces.com/awapp
不過現階段好像還不能直接在Android APP內直接更換錶盤,透過上述電腦連線更換是比較方便操作的...
找到喜歡的錶盤,自己動手換上 朋友、小孩、寵物、動漫...等照片,看時間感覺也會不同喔,祝大家身體健康!
2019年8月12日 星期一
生活小物-藥錠取出器 & 切藥器
隨著年齡的增長,父母親也漸漸高齡,身體難免出現一些慢性病需長期服藥控制,大醫院給付的藥錠都是沒有分裝的,看到父母親小心翼翼的從片片的包裝中擠壓取藥,偶爾還是會不小心掉落桌上或地上,而醫院給的藥量都是剛好的,掉到地上的話當月就少一天的藥量,心想難道沒有較好的方法嗎?
上網看了國內不少的取藥器,覺得都不是很好用(純粹個人觀感),想想日本也是一個高齡化的社會,可能會有什麼比較好的發明吧,看了看選擇的樣式還是不多,但是有一款,看到的瞬間我就很想買來用用看...
TOP-001 お薬取出器 トリダス (定價約日幣 2000円,樂天有賣到七折左右)
這個取藥器我已經用了一年多了,真是方便又耐用,除了有時候藥物較小的話會卡一點(晃一晃就會掉下來了),真是好物,藥錠壓出較多時可搭配小鑷子取出。Fullicon 護立康 集屑切藥器
另外有時候藥劑量是半錠,就需要使用切藥器,剛開始買的是不透明的切藥器,有時藥錠放好切下去時不小心走位,導致切出大小邊,前後也買了好幾個不同的試用,而下面這個雖然定價較其它切藥器貴了一些,但物有所值,推薦使用,我是去其經銷藥局買的(非廣告喔)。買了 藥錠取出器 和 切藥器 後,也順便買了 一週七日份藥盒,直接幫父母親將一週會用的藥物分裝好,這樣用藥部份就較沒問題了。一週藥盒我是買28格便宜的那種,直觀好拿取,但這種盒蓋接合處久了後(半年左右)會變軟或斷裂,算是消耗品,也沒什麼較好可推薦的,多買一個備用就好。
PS:藥錠取出器有出一個 Pro 專業版,有需要的可參考看看
2019年8月11日 星期日
積木式免焊接 記憶型空調紅外線轉譯器 - 04 紀錄 & 修正篇
適用機型:
記憶型空調紅外線轉譯器 程式碼
程式下載 (密碼藍字 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聲,結束設定狀態
測試錄製信號
積木式免焊接 記憶型空調紅外線轉譯器 - 02 軟體 程式寫入篇
軟體 程式寫入篇
Arduino 開發程式及紅外線程式庫安裝
首先安裝Arduino 開發板的編寫程式,請至 Arduino官網(如下連結)下載 IDE開發環境 主程式
如我的電腦是 Windows 10,下載 Windows Installer, for Windows XP and up 版本
https://www.arduino.cc/en/Main/Software
下載時會詢問是否要贊助,點選下方 JUST DOWNLOAD 可免費下載主程式。
接下來安裝紅外線程式庫 (IR remote by shirriff)
請打開桌面上安裝好的 Arduino 圖式程式,一步一步如下圖即可完成:Arduino 開發版晶片程式寫入 (程式與 擴展版 通用)
1. 將Arduino 開發板的USB連接線插入電腦的任一 USB 2.0 埠上進行供電及準備傳輸。
2. 請下載以下壓縮檔 (解壓縮密碼藍字 mygraphpaper.com ) 解壓縮
https://www.mygraphpaper.com/download/MPG_TR_Translator.rar
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 設定 記憶型空調紅外線轉譯器



























