令人著迷的 Nixie Clock 製作 (三)
電路的部份差不多都完成了, 接下來我們來看看 MCU 控制和軟體的設計. 不過, 寫程式真的不是我的強項啊…
扮豬吃老虎的 Cortex-M
我選了 NXP 的 LPC1343 當做 Nixie2 的 MCU. 這是一顆 72MHz 的 ARM Cortex-M3 MCU, 裡面有 32KB 的 flash 和 8KB 的 RAM. 它還有 full-speed 的 USB device controller, 支援最多 10 個 endpoints. 其它像是硬體的 UART, SPI, I2C 這種基本的介面是一定有的, 另外還有 10-bit 的 ADC 附贈 multiplexer. 以上這些強大的功能統統擠在這小小的 TQFP48 包裝裡, 根本就是隻披著羊皮的狼啊.
在接觸 Cortex-M 之前, 我大部份會用到 MCU 的設計都使用 8051. 小時候用的是開窗戶的 EPROM 版 8751/8752, 工作後有了 Atmel 的 flash 版 AT89C51 和精簡版的 AT89C2051, 最近幾年用的是國產的 Nuvoton W78/N78系列.
二十多年下來, 我對 8051 已經非常熟悉, 熟到不管是畫電路圖還是寫程式都不太需要翻 reference, 所以上次修那台 Hymnus 350 管風琴時, 一打開看到 8032 就像遇到了好朋友一樣. 不管是工作還是興趣上的設計, 我一直都用 8051. 直到遇見了 Cortex-M…
第一次接觸 Cortex-M 是在五年前的一個 LCD module 的背光模組設計案. 那時 LCD panel 的 local dimming 正夯, 我們需要一顆 MCU 來做 backlight control 的 protocol bridge. 背光的控制訊號速度很快, 8051 完全不夠力, 後來因為協力廠商的推薦, 我們用了一顆 NXP 的 Cortex-M0 MCU 來做 protocol bridge.
我當時對於這顆單價跟 8051 差不多, 計算能力和功能卻遠高於 8051 的 Cortex-M0 印象非常深刻, 所以接下來又用這顆 LPC1114 陸續做了幾個設計.
某次, 有個專案需要 USB device 的功能, NXP 的人就推薦我用同系列的 Cortex-M3 產品. NXP 的 Cortex-M3 裡, 有顆 LPC1343 支援 USB device 端, 而且可以用 USB 燒 firmware. 用 USB 燒 firmware 不算很稀奇, 但它燒錄 firmware 的程序非常優雅. 要燒錄時, 把 MCU 插到 PC 上, 同時用按鈕或 jumper 啟動 ISP bootloader, 之後 PC 會看到一個 mass storage class 的磁碟機, 它的大小會是 MCU 的 flash memory 大小. 接下來呢, 只要用檔案總管把要燒錄的 binary 檔拖~進這個磁碟機, 就燒好了! 是不是很優雅? 不需要燒錄程式, 不需要特別的工具, 就這麼把檔案拖~進去, 就燒好了! 是不是很優雅? 是不是很優雅?
光是這個燒錄的動作, 就讓我深深愛上 LPC1343. 但除了 mass storage class 外, USB 介面還有另一個讓我更激賞的 class driver: communication device class, 簡稱 CDC.
除非真的遇到極度難解的問題, 我不是很愛用 ICE 來 debug. 一般在 debug MCU 程式時, 我會大量依賴 UART 輸出各式各樣的 message 來確認程式的動作. 以前總是要把 UART 拉線出來, 再用 UART 轉 USB 的小板子把訊號拉進電腦, 用 terminal 程式來觀察. 但 LPC1343 的 USB firmware framework 裡, 有支援 USB composite device, 也就是說一顆 LPC1343 可以在 host 端列舉出兩個以上的 USB device. 於是, 除了原來需要的 USB device 外, 我還可以加一個 CDC device 進去, 然後 PC 上就會跑出一個 virtual serial port, 只要在程式裡把要印的 message 都往 CDC device 丟, 就可以在 PC 上收到跟 UART 一樣的內容, 而這一切全部都在 USB 上完成, 再也不需要拉 UART 的線.
LPC1343 的核心速度高達 72MHz, 根據 ARM 的說法, Cortex-M3 的核心有 1.25DMIPS/MHz 的計算能力, 也就是說 LP1343 在 72MHz 的 clock 下會有 90DMIPS 的計算能力, 已經超過了 1992 年的 486DX2-66. 雖然現在手機裡面的處理器已經動不動就上 1GHz, 但擺顆跟 486 差不多的處理器來控制 nixie tube 還是覺得有點… 扮豬吃老虎.
電路
來看一下實際的電路吧.
以前第一次用 8051 的時候, 覺得它的電路真是簡潔到令人激賞, 只要加上 crystal 跟 RC reset 電路, 再給電, CPU 就可以開始跑程式了. 但現在像 LPC1343 這樣的 MCU 更猛, 內建 RC oscillator, 內建 power-on reset 電路, 什麼外部零件都不用加, 只要給電 CPU 就開始跑了.
有些內建 clock source 的 MCU 是用燒錄參數的方式來決定使用內部還是外部的 clock source,因此我還犯過一個很蠢的錯誤:電路設計時決定使用 internal clock,所以板子上沒有留 crystal 的位置,結果打件完成後發現代理商出給我們的 IC 是燒成外部 clock 的設定。因為板子上沒有 crystal,所以 MCU 動不起來。但更糟糕的是,那顆MCU 的 ISP 需要 clock 才跑得起來,沒有 clock 就沒辦法改 clock 的設定。最後我們用訊號產生器直接給 clock 到 MCU 的接腳上,才能跑 ISP改設定。
而 Cortex-M 對於 clock source 的選擇就設計得比較聰明。Reset完成之後,MCU一律用 internal clock 開始跑,如果要使用外部 clock,user 必須在 user code 裡自己將外部的振盪電路打開,把 PLL 設定好。等到 PLL 鎖定之後,再把 clock 切過去。
LPC1343 內建的 RC oscillator是 8MHz,出廠前已經校正到1%的精度。晶片上有兩個 PLL,分別供應 core 跟 USB PHY 的 clock,兩個 PLL 都是 fractional PLL,可以乘也可以除,所以 crystal 頻率的選擇非常有彈性,不必侷限於特定的頻率。
原廠說要使用 USB 功能的話,一定要使用精度足夠的外部 clock source,但我試過直接把 8MHz 的 internal clock乘以六,得到 USB PHY 需要的 48MHz clock,USB 居然也會動。不過這畢竟不是正規的做法,而且 USB bootloader 會強制選擇外部 clock,如果沒有放 crystal 的話前面講的那招超炫的 USB 隨身碟燒錄法就不能用了。所以這片板子上還是乖乖加了 crystal。
USB 通訊協定利用一個 1.5K 的 pull-up 電阻接在 D+ 或 D- 上來識別 full-speed 或 low-speed, 大部份的 USB controller 都會把這個部份的電路內建, 但不知道為什麼 LPC1343 需要用外部電路實作這一塊. Pull-up 電阻啟動的訊號來自一根 GPIO 腳, 這樣可以讓 USB protocol stack 決定何時要通知 USB hub 有裝置 attach 到 bus 上. 對於 bus-powered 的設計來說, 這種做法可以把 CPU 開機跟 USB enumeration 的時間分開, 不必一插上電 USB 就得急著 enumeration.
至於 Nixie2 上面需要用到的功能, 其實就只有 GPIO 而已. 如同上一篇說的, 我們需要 11 隻腳來控制陰極, 6 隻腳來控制陽極, 所以最少就只需要 17 隻 I/O 腳. 但這可是一顆有 42 隻 GPIO 的 Cortex-M3 啊, 而且 GPIO 是接在 AHB 上而非 APB 上, 是貨真價實的高速 I/O 呢. 但其實真的沒有什麼別的地方用得到 GPIO, 我只好再拉了幾個按鍵, 以便用來調時間; 拉了兩顆 LED, 方便軟體 debug; 又順手留了 38KHz CIR 的紅外線遙控接收器訊號, 預留用遙控器操作的界面.
以前做 nixie tube 時鐘, 我都直接用 MCU 來算時間, 以我們常用 100ppm 的 crystal 來說, 一天最多大概會有萬分之一的誤差, 也就是 8 到 9 秒, 其實還算可以接受. 但最麻煩的是它記不住時間, 每次斷電後再重開就要重設時間. 這次我決定試試看有備份電源的 RTC 晶片, 把計時的工作叫給 RTC, 而本來就已經夠閒的 MCU 則更加閒到不行, 只負責從 RTC 讀時間然後顯示到管子上.
我選了 TI 的 BQ32000 當做 RTC 晶片. TI 的 RTC 產品線並不廣 , 這是裡面唯一一顆用 serial 界面存取的 RTC, 所以它只有八隻腳. RTC 為了待機功耗的關係, 都會用 32.768K 的 crystal 當 clock source. 這種 crystal 的特性跟一般幾 MHz 的 AT-cut crystal 有很大的不同, 它的 driving power 非常低, 通常不會超過 1uW, 而一般常用的 AT-cut crystal 則需要 100uW 以上的 driving level. 因為這種 crystal 的驅動功率很低, 所以它上面的訊號很難測量. 如果你企圖用示波器去看它有沒有振起來, 光是示波器探棒本身的負載就足以讓它的訊號消失得無影無蹤, 結果就是什麼也量不到. 如果一定要量, 得用那種 0.1pF 超低負載的主動式探棒來量, 才不會破壞這微弱的訊號.
除了 crystal 外, RTC 另一個重要的零件就是備用電源. BQ32000 支援兩種備用電源: 一次性鋰電池或是超級電容. 它在電源電壓低到無法維持 RTC 運作時, 會自動切換到備用電源. 它也有由軟體控制的充電電路, 用來幫超級電容補充電荷. 開機時所有的充電路徑預設都是關閉的, 以免使用者接了一次性鋰電池而被誤充造成危險. 我在板子幫兩種電源的零件都留了位置, 要裝哪個都可以.
另外有一點很重要的是, 我們需要一個 1Hz 訊號來決定什麼時候更新 nixie tube 的顯示. 這個訊號必需跟 RTC 同步, 而不能用 MCU 自己的 clock domain, 否則時間久了之後兩邊會有累積性的誤差. 雖然 RTC 的時間並不會不準, 但如果更新顯示的時間跟 RTC 不同步的話, 當誤差累積夠多時, 有可能會出現秒數停頓一秒或跳過一秒的現象. 當然還有別的解決方法, 就是讓 MCU 不斷地去讀 RTC 的時間暫存器, 然後不斷地更新顯示, 只要更新的速度夠快, 就可以保證 RTC 的秒數每次更新後可以立即反應到 nixie tube 上, 但這種做法會讓 I2C bus 非常忙碌. 雖然 MCU 很閒, 這樣做並不會花掉 MCU 太多的計算能力, 但就是不優雅. 所以我決定打開 RTC 的 1Hz IRQ 輸出, 並用這個訊號去觸發 display 的更新.
掃描顯示
很閒的 Cortex-M3 MCU 所負責最重要的工作就是 nixie tube 的掃描顯示. Nixie tube 點亮和熄滅的速度都很快, 根據經驗, 掃瞄的頻率大概要 40Hz 以上, 看起來才不會閃.
Cortex-M3 裡面共有 4 個 general-purpose 的 timer/counter, 我用其中一個 16-bit 的 timer CT16B0 來產生管子掃瞄顯示所需要的時基: 先把系統的 clock 除到 1MHz 後, 餵給 CT16B0, 再把它的 match counter 設成 10, 如此一來, CT16B0 就會以 100KHz 的頻率發生 overflow. 再把它的中斷打開, 我們就得到一個每秒會發生十萬次的 ISR 了.
有了這樣一個定時發生的 ISR, 就可以把所有需要定時處理的 I/O 全部放在裡面做, 像管子的掃瞄, 背光 LED 的 PWM 控制, 或是其它要閃要亮的 LED 等. 而且這些事情時間到了就會自動發生, 不會受主程式的流程或是其它因素影響, 甚至主程式當掉了, 如果中斷還持續發生, 這些 I/O 仍然會持續進行.
這種利用定時中斷將許多需要同時執行的工作串起來, 是 MCU firmware 設計中非常重要的技巧. 很多人寫慣了有作業系統的程式, 一旦沒有作業系統的幫忙, 就不知道要怎麼設計類似多工的程式. 即使到了 MCU 上, 一定要搞個 real-time OS 在底層才有辦法同時處理很多件事, 但其實靠一個妥善規劃的中斷就可以把這些事處理好.
這個技巧是以前在 8051 上磨練出來的. 8051 的堆疊很淺, 巢狀呼叫跑深一點就很容易發生 stack overflow, 整個系統就爆掉了, 因此要在 8051 上掛 RTOS 是很困難的事, 即使是 Keil 內附的 RTX51 也不是那麼容易駕馭, 因此我就習慣這些事情自己來.
我把 CPU core 的頻率設在 50MHz, 也就是說如果以 100KHz 的週期發生中斷的話, 每一次進入 ISR 可以有 500 個 machine cycle 用來處理所有 ISR 裡的事.
在掃瞄管子的時候, 有件事情要特別注意:
當我們要結束一根管子的顯示時, 要先把現在這根管子的陽極關掉, 讓它不顯示, 才可以切換陰極. 陰級切過去後, 再把下一根管子的陽極打開, 讓新設定的陰極顯示在下一根管子上.
這個做法是為了避免管子跟管子之間發生 cross talk. 如果不把陽極關掉再切換陰極, 不管是先變陰極再變陽極, 或是反過來先變陽極再變陰級, 都會讓相鄰一隻管子的顯示漏一點點到現在顯示的管子上. 以 duty cycle 來看也許不明顯, 但就是會有個淡淡的影子在那邊.
最後, 來看一下掃瞄顯示的效果吧!
Bird您好:
我想請問下要如何將程式燒入到ARM M0的晶片裡,不需使用到燒入板,可以使用PC直接對晶片進行燒入。
NXP 的 Cortex-M0 晶片有兩個不用 JTAG 的燒錄法: 如果是有 USB 的晶片如 LPC1343, 可以用 USB bootloader 燒錄. Reset 的時候用 ISP 腳啟動 bootloader 然後把 USB 插著, 電腦會認到一個 mass storage class device, 把 BIN 檔拖進去就燒進去了. 如果不是 USB 的晶片像 LPC1114, 就用 UART 燒. PC 端的軟體叫 FlashMagic.
那如果我是用LPC1114這顆M0 chip,我希望的是可以將LPC1114 Chip植入到自己設計的電路板,而燒錄想經由PC端拉線碰觸電路板上特別設計的PAD(由LPC1114拉出的)進行燒錄,那有這種燒錄法嗎?須拉哪些腳出來? 謝謝你
找mcu相關知識找到這來….看了幾篇,不只超強,根本是人生勝利組阿~~佩服又羨慕~~
Bird您好: 請問你用Cortex-M做Local dimming backlight control bridge 時, bridge的意思是此顆Cortex-M有處裡影像信號,然後將運算結果丟到LED driver?
它只負責做 protocol bridge,背光該怎麼亮已經由 Sony 的影像處理晶片算好了