作者:實作派Lab
寫程式的人對於 printf( )應該不陌生,用它印變數真是再方便不過了,除了印文字還可以印數字,其中包含了各種格式,整數、浮點、十六進位等一應俱全。但若遇到沒辦法使用 printf( )的情況時,該怎麼辦呢?
有種情況是在微處理器(MCU, Micro Controller Unit)的flash memory很小只有幾K下發生,當程式碼已經寫到瀕臨memory的邊界,只要再多寫幾行code記憶體就會爆掉,尤其是 printf( )只要一加進去,記憶體馬上就爆了,這真的很令人傷腦筋。
printf 很佔空間
平常寫PC的程式,記憶體動輒以Giga Byte來計算,大家也許不會感覺到memory空間的限制;而MCU的程式,由於記憶體空間只有4K~16KByte等級,因此程式大小錙銖必計能省則省。每呼叫一次函式,就會產生一堆的push/pop指令,如果參數很多則占用的記憶體數量就很可觀,不僅如此printf還用了動態參數,讓參數的數量可以任意變動(如下方範例),為了彈性便需要吃掉更多空間,在記憶體捉襟見肘的情況下,若沒有printf該如何來做debug呢?

參數數量(圖片來源:實作派提供)
Simulator用不上
一定有人會想若printf不能用,那就改用模擬器Simulator的step run單步執行就好了,但如果一切都如此美好就好了。因為開發環境內的模擬器,它再厲害也只能模擬正常情況,而我這裡是要處理偶發性的異常事件,必須要抓到問題才能避免MCU當機。例如故意將USB或I2C短路,造成程式異常,這可是模擬器沒辦法模擬的。
那為何要故意造成USB或I2C短路呢?是因為實際在run的情況下,這些周邊通訊電路,就是有可能因為EMI而有機會發生異常。由於,不是每次EMI都會發生干擾,但我們並沒有太多時間守株待兔,所以直接將I2C短路讓它異常,效果直接而且狀況跟被EMI干擾的結果相同。
GPIO要好好利用
既然我們需要某種output來做為debug的工具,若不能用printf那還有甚麼方法可以做輸出呢?還好微處理器的IO(Input/Output)很多,只要設定得當,就可以利用GPIO(General Purpose Input Output)來輸出電壓當作debug訊號,之後再接上示波器就能看到電壓的變化,所以可以預先自行定義GPIO的high與low各是甚麼意義,這就可以當作debug的輸出。
由於GPIO只有High與Low兩種狀態,若想要在超過兩個不同地方安插debug訊號,可以使用下面的寫法,用來觀察程式是否有跑到預期的地方。這裡我以CY7C68013A為例,利用GPIO PortC的PC.0的腳位來輸出電壓變化。
如下方範例,我寫了三種脈衝,分別會產生1根pulse、2根pulse以及3根pulse,將它們放在程式中的不同位置,來代表程式中的不同階段,萬一有function當掉,看示波器的波形就馬上能知道是哪個函式出問題。

範例程式(圖片來源:實作派提供)
光看程式可能很難想像結果,那實際在示波器上看到的GPIO波形會是甚麼樣子呢?呈現的波形就會如下圖的樣子,框起來的地方分別是三種脈衝訊號的波形。當然如果你擔心脈衝訊號彼此太靠近,會容易搞不清楚到底誰跟誰是一組的話,就在脈衝前後多加幾個0(也就是low),就能把距離拉開了。

沒有 printf 就改用GPIO做為Debug訊號(圖片來源:實作派提供)
那也許大家會想為何不用for loop來寫脈衝?這樣程式不是更省? 但其實事實不然,我確實有試過用for loop來取代hard coding,但我發現只要用了for這個關鍵字,程式就會變胖,可是code size已經在臨界點了,這個for就會變成壓垮駱駝的最後一根稻草,因此我全部用hard codeing。
小結
依稀記得多年前正在研究一個MCU的時候,由於它體積很小pin腳也少,少到連UART都不見了。所以,當時我只能先用一根GPIO接上LED確認程式會動之後,再慢慢接出其他的GPIO來輔助debug,於是就變成了用一顆LED來除錯的工程師,現在想起來都覺得很瘋狂。但是面對時程的壓力,身為工程師當然是要想盡辦法達標,才能擦亮自己的招牌啊。
- 【實作實驗室】分配器效能測試 - 2024/12/30
- 【實作實驗室】頻道功率怎麼測?有線電視頻譜長這樣! - 2024/12/12
- 【實作實驗室】無線滑鼠游標定格,電源開關更換 DIY - 2024/11/12
訂閱MakerPRO知識充電報
與40000位開發者一同掌握科技創新的技術資訊!