寫 code 可以叫 “編程 programming”,但我覺得叫 “程式設計 software design” 更理想,屬於設計過程,由明確目的開始入手,而唔係類似教科書咁順向學習或者研究,限制於唯一造法。由寫鐘開始學寫 code 係一個唔錯嘅選擇,因為目標明確,只要達到目的就可以,可能性可以有好多;例如主流用 Arduino UNO R3 制作一個鐘需要加需要入電芯嘅時鐘模块,但咁図嘅一個鐘同市面上可以買倒嘅商品原理差唔多,大路唔代表有商業價值。
設計係由目的開始,DIY 造一個鐘絶對稱唔上慳錢或者美觀,要揾倒同造到市面上嘅產品無類似嘅功能,先有針對性嘅存在價值。我用一塊長期插電又無電芯儲存時間嘅 Wemos D1 R2 造鐘,靠每次駁通 wifi 之後由 internet 取得國際時間,換黎唔駛校時間嘅方便。上網揾 code 黎抄唔容易,主流係用 udp 問 time server 攞時間,另外有一種方法係由 gateway 嘅 http header 裏面擇時間資訊;到後期我先揾倒 call TimeLib.h struct tm 嘅寫法啲 code 最簡洁,試出 delay 幾耐同改良到 while 一段時間就要放棄先係真正要自己寫嘅 code。
#include <TimeLib.h> struct tm time_tm; time_t time_ntp = 0; ... void fn_ntp_sync(int8_t timeZone) { configTime(0, 0, "pool.ntp.org", "asia.pool.ntp.org"); uint32_t beginWait = millis(); while ((millis() - beginWait < 1000) && (time_ntp < 16 * 60 * 60)) { delay(200); time_ntp = time(nullptr); if (time_ntp > 16 * 60 * 60) { time_ntp = time_ntp + 1 + (timeZone * 60 * 60); setTime(time_ntp); gmtime_r(&time_ntp, &time_tm); Serial.print("time: "); Serial.println(pad2digit(hour()) + ":" + pad2digit(minute()) + ":" + pad2digit(second())); } } } String pad2digit(uint8_t number) { if (number < 10) { String s = String(number); return '0' + s; } else { String s = String(number); return s; } }
我認為比較唔同方法之後,培養懂得分辯好壞嘅能力相當重要,相當於抄功課要抄得叻嘅能力,修正教育界長期灌輸要由零開始靠自己造功課嘅觀念,只會限制於個人能力而無法解決復雜問題。
淘宝有 4位 LED 時鐘顯示模块,配閑 SPI 嘅 MAX7219 LED 驅動;但我希望閑 I2C 整合其他功能,只能夠就要靠 I2C port expander 增加 GPIO 数目到 12 個以上,然後靠 Wemos D1 R2 嘅運算能力驅動。SX1509 係我認為最好但唔係主流嘅 I2C port expander,有 PWM 功能可以將 LED 光度調到日頭睇唔倒咁暗,適合用黎造床頭鐘;好好彩仲有一款面臨絶跡嘅正方形版,啲窿位啱啱好督得入 LED 顯示模块果 12 隻腳,透過共陰或者共陽串通 4*7 粒 LED 加冒號。腳位無統一標準,邊度買就睇番邊份 pinout 先知邊隻腳著邊粒燈,相對 I2C 係通用嘅協定,可以將腳数減至四隻,而且可以同其他元件同時共用,於是我就可以用四腳插頭將 LED 顯示模块連 SX1509 轉為可裝拆嘅模組。
一個跳字鐘嘅原理離唔開逐個字輪流著燈,只需要對番晒啲 GPIO 就可以,需要由零開始自己去諗邏輯寫番出黎,對我黎講 function 開幾多個 parameter 位就相當於幾多支針腳去理解。我覺得依個習作相當唔錯,好接近學校造功課訓練邏輯思維嘅性質,絶對比中學程度数學 solve equation 更實用,起碼明白到日常接觸到嘅電子設傋內裏嘅運作其實一啲都唔簡單。我用左 task library 造到 multi task 效果,避免用 delay,只係用 delayMicroseconds 著 1/1000ms 燈,refresh rate 係 50Hz 先順暢,越快粒 mcu 就越辛苦。
完成之後相當於以軟件形式取代市價大約只值幾蚊雞嘅 LED 硬件驅動,有光暗唔穏定問題,如果遇到某啲 lbrary 有用 delay 就會疾,形成浪費算力,帶出寫 code 有好壞之分唔係閑倒就算,質素唔同数量難以測量,寫 code 係零成本明顯唔可以由金錢去衡量。
//******************************************************************* // setup(): fn_ledclock_begin(); // fn_loop_20ms(): fn_ledclock_display(); //******************************************************************* const uint8_t pin_digit[] = { 5, 2, 1, 15 }; //common anode const uint8_t pin_segment[] = { 4, 0, 13, 11, 10, 3, 14, 12 }; //A,B,C,D,E,F,G,DP const uint8_t ledclock_brightness = 5; //0-255 bool ledclock_ready = false; void segment_display(bool a, bool b, bool c, bool d, bool e, bool f, bool g); void segment_on(uint8_t i); void segment_off(uint8_t i); void digi_on(uint8_t i); void digi_display(char n); void digi_clear(); //******************************************************************* // led_clock & sx1509 //******************************************************************* void fn_ledclock_begin() { if (sx.begin(sx_add)) { for (uint8_t j = 0; j < 8; j++) { sx.pinMode(pin_segment[j], ANALOG_OUTPUT); } for (uint8_t i = 0; i < 4; i++) { sx.pinMode(pin_digit[i], OUTPUT); } ledclock_ready = true; } } void fn_ledclock_display() { if (ledclock_ready) { static String time_string = ""; static uint8_t minute_o = 0; if (minute_o != minute()) { time_string = pad2digit(hour()) + pad2digit(minute()); minute_o = minute(); } for (uint8_t time_slot = 0; time_slot < 4; time_slot++) { digi_display(time_string.charAt(time_slot)); if (time_slot == 1) { second() % 2 ? segment_off(7) : segment_on(7); //handle dp } else { segment_off(7); } yield(); digi_on(time_slot); delayMicroseconds(1); digi_clear(); yield(); } } } void digi_display(char n) { if (n == '0') { segment_display(1, 1, 1, 1, 1, 1, 0); } else if (n == '1') { segment_display(0, 1, 1, 0, 0, 0, 0); } else if (n == '2') { segment_display(1, 1, 0, 1, 1, 0, 1); } else if (n == '3') { segment_display(1, 1, 1, 1, 0, 0, 1); } else if (n == '4') { segment_display(0, 1, 1, 0, 0, 1, 1); } else if (n == '5') { segment_display(1, 0, 1, 1, 0, 1, 1); } else if (n == '6') { segment_display(1, 0, 1, 1, 1, 1, 1); } else if (n == '7') { segment_display(1, 1, 1, 0, 0, 0, 0); } else if (n == '8') { segment_display(1, 1, 1, 1, 1, 1, 1); } else if (n == '9') { segment_display(1, 1, 1, 1, 0, 1, 1); } } void segment_display(bool a, bool b, bool c, bool d, bool e, bool f, bool g) { a ? segment_on(0) : segment_off(0); b ? segment_on(1) : segment_off(1); c ? segment_on(2) : segment_off(2); d ? segment_on(3) : segment_off(3); e ? segment_on(4) : segment_off(4); f ? segment_on(5) : segment_off(5); g ? segment_on(6) : segment_off(6); } void digi_clear() { for (uint8_t i = 0; i < 4; i++) { sx.digitalWrite(pin_digit[i], LOW); } } void segment_on(uint8_t i) { sx.analogWrite(pin_segment[i], ledclock_brightness); } void segment_off(uint8_t i) { sx.analogWrite(pin_segment[i], 0); } void digi_on(uint8_t i) { sx.digitalWrite(pin_digit[i], HIGH); }