printf 不用它-MCU照樣能除錯debug

寫程式的人對於 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呢?

//printf可使用不定個數的參數, 舉例如下
printf("%d",a);  //1個參數可以使用
printf("%d, %x",a,b);  //2個參數, 也可以

Simulator用不上

一定有人會想,若printf不能用那就改用模擬器Simulator的step run單步執行就好了,只是如果一切都如此美好就好了。因為開發環境內的模擬器它再厲害也只能模擬正常情況。

但我要處理的是偶發性的異常事件,必須要抓到問題才能避免MCU當機。例如故意將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訊號,可以塞進數個由high/low組合而成的脈衝,請參考下面的寫法,用來觀察程式是否有跑到預期的地方。這裡是以CY7C68013A為例,我利用GPIO PortC的PC.0的腳位來輸出電壓變化。

如下範例,我寫了三種脈衝,分別會產生1根pulse、2根pulse以及3根pulse,將它們放在程式中的不同位置,來代表程式中的不同階段,萬一有function當掉,看示波器的波形就馬上能知道是哪個函式出問題。

void test(void)
{

    func1();
    //產生一個脈衝
    IOC=(IOC&0xFE)| 0;
    IOC=(IOC&0xFE)| 1; //只有這裡是1, 其他都是0
    IOC=(IOC&0xFE)| 0;

    func2();
    //產生2個脈衝
    IOC=(IOC&0xFE)| 0;
    IOC=(IOC&0xFE)| 1; //這裡是1
    IOC=(IOC&0xFE)| 0;
    IOC=(IOC&0xFE)| 1; //這裡是1
    IOC=(IOC&0xFE)| 0;

    func3();
    //產生3個脈衝
    IOC=(IOC&0xFE)| 0;
    IOC=(IOC&0xFE)| 1; //這裡是1
    IOC=(IOC&0xFE)| 0;
    IOC=(IOC&0xFE)| 1; //這裡是1
    IOC=(IOC&0xFE)| 0;
    IOC=(IOC&0xFE)| 1; //這裡是1
    IOC=(IOC&0xFE)| 0;
}

光看程式可能很難想像結果,實際在示波器上看到的GPIO波形會是甚麼樣子呢?

就是如下圖這個樣子,框起來的地方分別是三種脈衝訊號的波形。當然如果你擔心脈衝訊號彼此太靠近,會容易搞不清楚到底誰跟誰是一組的話,就在脈衝前後多加幾個0(也就是low),就能把距離拉開了。

沒有 printf 就改用GPIO做為Debug訊號
沒有 printf 就改用GPIO做為Debug訊號

那也許你說為何不用for loop來寫脈衝? 這樣程式不是更省? 其實並沒有,我確實有試過用for loop來取代hard coding,但我發現只要用了for這個關鍵字,程式就會變胖,但我的code size已經在臨界點了,這個for會變成壓垮駱駝的最後一根稻草,因此我全部用hard codeing。

想當年只靠一顆LED除錯

依稀記得多年前正在研究一個MCU的時候,由於它體積很小pin腳也少,少到連UART都不見了,這下可好,我變成瞎子了。所以當時我只能先用一根GPIO接上LED確認程式會動之後,再慢慢接出其他的GPIO來輔助debug,於是就變成了用一顆LED來除錯的工程師,現在想起來都覺得很瘋狂。但是面對時程的壓力,身為工程師當然是要想盡辦法達標,才能擦亮自己的招牌啊。

2 Comments

Leave a Reply

Your email address will not be published.


*