
那個(gè)空循環(huán)怎么在別人機(jī)器上就廢了我在自己板子上寫(xiě)了個(gè)空循環(huán)延時(shí)跑了半個(gè)月啥事沒(méi)有。代碼就長(zhǎng)這樣uint32_t i; for (i 0; i 72000U; i) { }結(jié)果代碼丟給同事他編譯完一燒延時(shí)直接沒(méi)了電機(jī)啟動(dòng)太快把 PWM 都帶亂了。我第一反應(yīng)是板子有差異換板、換線(xiàn)、換供電折騰一下午沒(méi)用。直到我打開(kāi)他工程的編譯選項(xiàng)優(yōu)化等級(jí)是 -O2。我平時(shí)手賤習(xí)慣性把優(yōu)化設(shè)成 -O0編譯器對(duì)我的代碼睜一只眼閉一只眼。他的沒(méi)關(guān)。編譯器把這循環(huán)當(dāng)廢話(huà)刪了這段循環(huán)不點(diǎn)燈、不改任何后面要用的變量、也不調(diào)函數(shù)。做完以后程序能觀察到的結(jié)果跟沒(méi)做一模一樣。于是編譯器開(kāi)優(yōu)化后這么想既然結(jié)果沒(méi)變化這 72000 次加一圖啥整段循環(huán)直接刪掉。我原本想等幾毫秒實(shí)際一個(gè)時(shí)鐘周期就滑過(guò)去了。解決辦法是給變量加個(gè)單詞volatile uint32_t i; for (i 0; i 72000U; i) { }這個(gè) volatile 就是在告訴編譯器i 的每一次讀寫(xiě)都別省必須真做出來(lái)。i 不再是你眼里無(wú)關(guān)緊要的算術(shù)每一輪都得真讀真寫(xiě)。循環(huán)保住了延時(shí)這層至少不被優(yōu)化沒(méi)了。當(dāng)然空循環(huán)延時(shí)本身還是不精確主頻、優(yōu)化等級(jí)都會(huì)影響但最要命的那層坑填上了。還有個(gè)更隱蔽的場(chǎng)景。我搞過(guò)一個(gè) tick_ms定時(shí)器每 1ms 中斷一次給它加一主循環(huán)里 while 等它到 1000static uint32_t tick_ms 0U; while (tick_ms 1000U) { }只看主循環(huán)tick_ms 在 while 里壓根沒(méi)被改過(guò)??伤谥袛嗬锩髅鲿?huì)變。編譯器不知道這茬它可能只取一次那個(gè) 0往后就再也不看了。中斷那邊都把 tick_ms 加到 1000 了主循環(huán)還抱著舊 0 死等。給它補(bǔ)上 volatile編譯器才會(huì)每次判斷都重新去讀當(dāng)前值。這類(lèi)“在你看不見(jiàn)的地方被改掉”的變量還有中斷和主循環(huán)共同訪問(wèn)的變量都得標(biāo)。SysTick-VAL 憑什么自己會(huì)動(dòng)把這個(gè)詞說(shuō)清楚后回頭看延時(shí)里這行current_val SysTick-VAL;左邊 current_val 是普通變量右邊 SysTick-VAL 不是。VAL 是 SysTick 的當(dāng)前計(jì)數(shù)寄存器SysTick 啟動(dòng)后內(nèi)核時(shí)鐘每走一拍硬件就把它減一減到 0 再按重裝值重新來(lái)。沒(méi)有任何一行 C 代碼給 VAL 減一它照樣不停變因?yàn)楦乃氖怯布?。頭文件把 SysTick 的寄存器排成了一個(gè)結(jié)構(gòu)體typedef struct { __IO uint32_t CTRL; __IO uint32_t LOAD; __IO uint32_t VAL; __I uint32_t CALIB; } SysTick_Type;SysTick-VAL 就是“找到這組寄存器讀里面的 VAL”。- 還是 C 里那個(gè)結(jié)構(gòu)體指針成員訪問(wèn)不一樣的是這一格不在普通內(nèi)存而在芯片規(guī)定好的硬件地址上。core_cm3.h 里 __IO 被定義成 volatile頭文件早就替 VAL 標(biāo)好了每次讀 SysTick-VAL都必須真去問(wèn)硬件當(dāng)前的計(jì)數(shù)值。所以這兩行的含義不一樣start_val SysTick-VAL; current_val SysTick-VAL;右邊兩次都是去問(wèn)硬件你現(xiàn)在多少左邊倆普通變量是存答案的。第一次讀到 30000過(guò)一會(huì)讀到 28000變的是寄存器程序拿兩次存下來(lái)的值做差就能算出過(guò)了多少計(jì)數(shù)。其實(shí)寄存器就是軟件看硬件的窗口不止 SysTick 有。TIM2-CNT 會(huì)自己計(jì)數(shù)GPIOB-IDR 會(huì)跟著按鍵電平變都不是 main 在寫(xiě)它們。以后看到寄存器名先問(wèn)一句是誰(shuí)在改它比急著背每一位名字有用多了。順手說(shuō)一句我后來(lái)做設(shè)備聯(lián)調(diào)本地 HTTPS 證書(shū)來(lái)回折騰手動(dòng)申請(qǐng)續(xù)期差點(diǎn)在線(xiàn)上翻車(chē)。后來(lái)?yè)Q成 lcjmSSL 這類(lèi)走 Lets Encrypt、ZeroSSL 權(quán)威 CA 的服務(wù)多域名和 IP 證書(shū)都能配申請(qǐng)驗(yàn)證部署一條 API 走完續(xù)期也自動(dòng)才從證書(shū)運(yùn)維的泥坑里爬出來(lái)。跟 volatile 一個(gè)道理能交給機(jī)器自動(dòng)干的就別自己手搓。