少花錢多辦事——這句話抓住了巴克明斯特·富勒(Buckminster Fuller)的短期化概念,在20世紀90年代在嵌入式開發領域中引起了熱議,但似乎從未過時。管理者們不斷地擠壓預算和時間表,以更快、更便宜地交付產品,結果往往是質量受損。與富勒不斷提高質量和解決方案的愿景不同,這種方法通常會縮短產品生命周期的測試階段,以滿足積極的計劃目標,有時意味著從最終產品中刪除功能(可能會在以后的版本更新中添加)。
讓我們來探索一些技術,這些技術將幫助開發人員更快地發現和修復缺陷,幫助節省構建材料的資金,并可能避免短期化的挑戰。雖然主要關注基于Arm的內核,但由于許多嵌入式設備中存在類似的功能,因此許多這些技術可直接應用于其他內核。
量化節約的最簡單方法之一是BML:成本較低的零件需要公司花費較少的資金來制造產品。在大多數嵌入式設計中,最昂貴的兩個部分通常是屏幕(如果設備有一個;大多數物聯網設備沒有)和處理器。當你向處理器添加更多內存(閃存和RAM)時,處理器的成本會增加。雖然各半導體公司的成本增加的具體程度各不相同,但粗略的經驗法則是,每增加一倍內存,處理器的單位成本就會增加一美元左右。
更糟糕的是,嵌入式開發工程師通常不太擅長在應用程序的設計階段預測內存需求。這些對所需內存量的最佳“估計”是處理器選擇的關鍵因素。鑒于每年的生產量都在數十萬或數百萬臺,給BML增加不必要的美元會對公司的底線產生不利影響。
結果,無數項目“資源緊張”,這是“我們沒有正確預測我們的內存需求”的代碼。加劇這一問題的是,在項目開始時,BML經常被分配給高層管理層。一旦發生這種情況,成本就變得不可侵犯。這使得人們爭先恐后地減少內存占用,或者依靠采購,通過談判其他組件的更好價格來保持BML成本與管理層預期的相同。為了減少內存占用,團隊通常會使用編譯器的優化引擎來減少生成的代碼的大小。
提高編譯器優化的標準
一些工程師非常不愿意啟動優化,因為他們認為優化會給系統帶來錯誤。這種情況很少發生,根據我的經驗,大約5%的優化器問題是優化器的問題。
當優化級別提高時,編譯器會對C和C++語言的語義非常挑剔。優化決策基于對語言規則的嚴格解釋。通常,嵌入式開發工程師們并沒有完全意識到語言和代碼的所有細微差別,這對他們來說是很自然的。
例如,如果函數調用是這樣編寫的:
myFunc(varA、varB、varC、varD);
自然的假設是從左到右讀取變量:從內存中讀取varA,然后是varB,依此類推。
然而,在C或C++中并沒有說一定要這樣。如果有意或無意地將內存布置在varB緊挨著varD的位置,那么高度優化可能會使用索引寄存器來讀取連續的內存空間,以節省代碼大小和速度。
在大多數情況下,這不會對代碼產生影響。但是,如果你在從左到右編寫變量時依賴于被訪問的變量,那么可能會出現這樣的情況:代碼在較低的優化水平下運行良好,但在較高的優化水平上運行不佳。在這種情況下,工具供應商提供的良好支持結構可以幫助你發現這些類型的問題,并重寫代碼部分,以更好地進行優化并正確工作,而不依賴于優化設置。
此外,如果你的代碼可以在高度優化時同樣工作,那么它的編寫是正確的,并且經過了更好的測試。在嵌入式開發中,如果代碼不能在更高的優化下工作,那么很有可能潛在的缺陷正在等待“咬你”。
好的工具在設置為大規模優化時可以節省10-40%的代碼大小。然而,并非所有優化轉換都是任何一段代碼的好選擇——某些轉換實際上可能會增加某些類型代碼的代碼大小。
目前,有一些資源可用于解決“從編譯器中獲取最少”的問題,這意味著最小的代碼和最短的執行時間。節省這么多的代碼空間可能是在剝離功能以保持設備大小、由于手動優化代碼而錯過計劃或超出BML預算之間的區別。
雖然好的代碼可以在任何級別的優化中運行相同的代碼,但調試高度優化的代碼是非常棘手的。例如,整個代碼段可以在完全不同的位置合并到其他代碼段中。這就是為什么必須在低優化或無優化的情況下調試代碼,并在增加優化以運行全部測試之前驗證代碼是否正常運行。
BML中的調試成本
讓嵌入式調試變得困難的部分原因是,大多數嵌入式開發人員根本不知道他們所有調試工具。它們傾向于默認使用printf語句和代碼斷點。這些默認值在試圖隔離硬故障、查找堆棧溢出發生的位置或查找變量不斷被阻塞的原因時沒有幫助。好消息是,有一些特殊的工具可以幫助發現這些類型的問題。
處理硬件故障
許多現代MCU具有實時指令跟蹤功能,允許你跟蹤指令流。在基于Arm的設備上,用于實現這一點的技術是嵌入式跟蹤宏單元(ETM)。參考手冊將顯示設備是否支持ETM。如果是這樣,請將跟蹤引腳連接到調試標頭,并使用支持跟蹤的調試器,例如IAR I-jet trace,它可以捕獲實時指令流并將其顯示在調試器窗口中。
要查找導致硬件故障的原因,只需滾動跟蹤窗口,找到在進入故障處理程序之前執行的指令。如果可以可靠地重現錯誤,請在故障處理程序處設置斷點,并消除跟蹤窗口中的所有滾動–罪魁禍首是跟蹤窗口中倒數第二條指令。現在原因已經知道了,因此可以在問題上設置斷點,并再次運行測試用例,以查看導致異常的代碼有什么問題。
但如果你沒有ETM呢?大多數基于Arm的設備具有串行線輸出(SWO),允許采樣、低速跟蹤。雖然你沒有得到每一條指令,但這可以提供足夠的跟蹤信息來縮小范圍并找到問題。此外,在嵌入式開發中,嘗試降低MCU時鐘和/或調整SWO設置,以便從調試器中獲得更精細的跟蹤信息,以便了解問題發生的位置。
其他設備架構具有與ETM或SWO類似的功能。因此,使用高質量的工具可以利用這些信息,快速隔離和消除問題。此外,可用的支持資源有助于提高SWO的性能,以保護更多的跟蹤數據。
停止堆棧溢出
堆棧溢出或找出變量神秘丟失內容的原因如何?使用相同的技術診斷這兩種情況。
在Arm世界中,大多數處理器的調試接口中都有一個數據監視點和跟蹤(DWT)模塊,可用于快速隔離這些類型的問題。在這種情況下,使用一個數據觀察點來找出不好的事情發生在哪里。每當接觸到一段數據時,這個觀察點本質上就是一個斷點。
將選項配置為僅在數據被讀取、寫入或同時讀取和寫入時中斷執行。此外,如果數據是具有特定位掩碼的特定值,甚至將其限制為僅斷開,這在避免每次訪問數據時停止時非常方便。
在堆棧溢出的情況下,嵌入式開發人員可以在堆棧頂部設置一個數據觀察點。讀取或寫入該值并不重要,因為堆棧在代碼中的該點已經損壞。處理器將在堆棧頂部停止執行,提供一個完全保留的調用堆棧,允許查看哪段代碼正在刷新堆棧以及到達該點的方式,這是確定如何修復錯誤的關鍵。
清理破壞的數據
對于被破壞的數據,我們使用基本相同的技術,只是在該變量經歷寫入時設置數據觀察點。如果它總是被相同的值阻塞,則進一步縮小斷點,使其僅在將該值寫入變量時才觸發。然后,再次運行我們的測試用例,找出導致問題的代碼。
同樣,許多其他架構(如Renesas RL78、RX和許多其他芯片供應商的設備)具有類似的功能,可用于實現相同的結果。有了高質量的工具,發現這些類型的問題變得更容易,并增加了滿足積極的時間表和截止日期的可能性。
讓采購了解你的關心
用更少的資源做更多的事情似乎是一個矛盾,但使用正確的工具可以很容易地實現。通過使用編譯器優化,你可以將代碼壓縮到盡可能小的空間,以便為應用程序使用最便宜的設備。
優化還可以幫助桌面檢查代碼,看看它是否在高優化下運行,從而在你將代碼簽入構建之前找到潛在的代碼缺陷(從而根據發布度量計算每個缺陷)。還可以通過使用完整的工具箱更快地發現bug,從而縮短測試和修復周期,更快地完成項目,從而幫助你更高效地進行調試。如果嵌入式開發人員知道工具箱里有什么工具(以及如何正確使用它們),就可以為企業節省每一分錢。