本文討論了使用printf會發生的一些基本問題,並提出了使用printf函式達到最好性能的一些竅門。

與使用printf有關的問題

開發人員經常忽視使用printf帶來的一些問題。首先這種技術要求開發人員在軟體中嵌入標準C函式庫,這無疑會增加ROM和RAM的使用量。

第二個問題是每次使用printf函式時,系統會進入阻塞狀態,直到所有字元發送完畢。這種阻塞會導致即時性能顯著下降。舉個例子,以9,600bps透過UART列印輸出諸如「Hello World!」這個簡單的字串(這是很常見的事)。我在STM32處理器上進行了簡單的時序測量,如圖1所示,該字串完成格式化並列印到終端上需要花12.5ms的時間,但在這段時間內,系統什麼事也不能做。

20170615TA01P1 圖1 列印「Hello World!」。

增加任何字串格式化的工作會使情況變得更加糟糕!使用printf函式將系統狀態列印到終端上(「The system state is %d」, State)會導致21ms的應用程式延遲,這個時間都用在了字串的格式化和發送上面。有人可能會說以9,600bps的速率運作太荒唐了,但即使提高到115,200bps,發送這兩條訊息仍會導致1.05ms和1.75ms的延遲。也就是說,即便對於使用程度最低的資訊,仍要浪費很多的處理器頻寬和潛在的即時性能。

現在我們來看看如何解決這些問題。

性能竅門#1—創建非阻塞printf

目前為止我遇到的每一個printf版本都是阻塞類型。一旦開始調用printf,應用程式都會停止執行,直到成功發送完每個字元,這太沒有效率了!另外一種替代方法是創建非阻塞版本的printf,且非阻塞printf版本將能:

˙格式化字串; ˙將格式化後的字串填充進發送緩衝區; ˙啟動發送第一個字元; ˙讓插斷服務常式處理發送緩衝區中剩下的字元; ˙繼續執行代碼。

非阻塞printf的關鍵在於建立時間,在STM32處理器上以9,600bps速度執行時的建立時間在0.8~1.8ms之間。在初始建立時間後,大約每隔1ms產生一次發送中斷,中斷常式隨即僅需35μs就能將下一個字元填充進UART的發送寄存器,然後就可以返回執行有用的任務。圖2顯示了週期性的中斷和中斷執行時間。記住,這個執行時間不包括中斷開銷,在這個案例中,中斷開銷不到25個時脈週期。

20170615TA01P2 圖2 非阻塞型printf性能。

性能竅門#2—提高串列傳輸速率

令我感到奇怪的是,即使今天的串列硬體可以處理1Mbps甚至更高的串列傳輸速率,仍有許多開發人員將他們的UART設置在預設的9,600bps!偶爾我也會遇到膽大的開發人員將串列傳輸速率設在115,200。提高時脈速率可能會發生電氣或硬體相關的問題,除此之外,將串列傳輸速率設為1Mbps並盡可能快地輸出調變消息不會有什麼問題,這樣可以最大限度地減少即時性能問題。當串列傳輸速率設為1Mbps時,用最初的阻塞printf函式輸出「Hello World!」只阻塞120μs,這要比12.5ms容易接受多了。

性能竅門#3—使用SWD

現代微控制器(MCU)的設計者在開發晶片時都知道存在printf性能問題。例如那些想要利用ARM Cortex-M調變功能的開發人員會完全跳過UART,使用內部調變模組將printf消息透過調變器發送回整合式開發環境。以這種方式跳過UART不僅節省了建立時間,其內部硬體機制也可最大限度減少軟體開銷,內部緩衝區被填滿消息,調變硬體自動處理調變探測器的消息發送,因此能夠大幅減少對應用程式即時性能的影響。

總結

很少有開發人員會完全拋棄他們喜愛而且非常有用的printf調變技術。在今天先進的微處理器硬體中,有諸多選項可以用來提高printf的性能和效率,最大限度地減少對即時性能的影響。對於想要親自體驗這些改進的開發人員,我提供了一個可在STM32上運行的Keil專案,該專案演示了如何使用這些技巧。