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