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