上一篇文章寫了一個延時函數,是這樣的:
void Delay(uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
為了延時1秒,設置了一個值:1600000。
為什么取這樣一個值,這是我實測出來的一個值,是通過多次累計閃燈次數,對應電腦時間,計算出來的。
看見這個值之后,我有一個推測:
1.6M=8M/5
我沒有使用外部晶振HSE,使用了默認的內部晶振HSI,主頻為8M。
所以,可能這個延時函數循環一次所需要的機器周期數就是5!
怎么驗證?
可以看下匯編代碼來進行分析。
具體操作步驟:
在成功編譯程序后,點擊工具欄上一個紅色的"D",進入調試狀態,再把鼠標點到c代碼處,右鍵查看匯編代碼,就可以看到所有c代碼編譯后的匯編代碼了。
延時函數的匯編是這樣的:
53: for(; nCount != 0; nCount--);
0x08000206 E000 B 0x0800020A
0x08000208 1E40 SUBS r0,r0,#1
0x0800020A 2800 CMP r0,#0x00
0x0800020C D1FC BNE 0x08000208
可以看到有4條匯編指令:
B,跳轉
SUBS,減
CMP,比較
BNE,根據標志跳轉
大體理解,就是這樣:進入循環后,先跳轉去進行比較。比較后,查看比較結果,若不相等,則跳轉去執行減操作。
對于循環次數很多的情況,可以忽略第一次的跳轉,所以一般情況下的循環一次,就是執行3條指令:
0x08000208 1E40 SUBS r0,r0,#1
0x0800020A 2800 CMP r0,#0x00
0x0800020C D1FC BNE 0x08000208
3條指令,5個機器周期,能對應上么?
我上網查了一下,大概的結論是:
stm32 屬于ARM ,ARM都是精簡指令集,大部分的指令(除STM、LDM、BNE等外)都是單周期指令。
對于跳轉指令,需要增加兩個指令周期。
按這種方式來估算:
SUBS,1
CMP,1
BNE,3
則 1+1+3=5 ,應該是符合的。
但是我心里不太踏實,畢竟沒有誰明確說哪個指令是幾個周期,這中間還是有部分猜測的。是否有更靠譜的方式呢?
然后又找到一個好方法:
具體步驟參見:
https://blog.csdn.net/qq_41092963/article/details/82759097
大概說下步驟:
進keil的調試模式,單步調試,記錄時間。
查看時間:Logic Analyzer窗口
匯編代碼:Disassembly窗口
另外,還可以開一個Watch窗口,查看我們關心的變量值。
其樣式見下圖:
通過幾次單步操作,記錄下時間,然后我們可以列出一個表來:
累計(ns) 指令耗時(ns)
初始 247750
CMP 247875 125
BNE 248250 375
SUBS 248375 125
125ns,一個機器周期,即頻率的倒數:1/8M,完全吻合。
這樣,就確實驗證了之前的猜測:
SUBS,1個機器周期(125ns)
CMP,1個機器周期(125ns)
BNE,3個機器周期(375ns)
這樣,我們就可以從微觀的循環周期向宏觀的時間進行計算了:
循環一次所需機器周期數: 1+1+3=5 。
5個機器周期,耗時就是125ns*5=625ns
循環1.6M次的耗時:625ns*1600000=625*1.6ms=1000ms=1s
如此,心里就踏實了。
如果試試不同的延時函數的寫法呢?
寫成while循環,結果會怎樣呢?
看看匯編,基本上是一樣的:
0x080012D8 E000 B 0x080012DC
0x080012DA 1E40 SUBS r0,r0,#1
368: while(TimeDelay > 0){
369: TimeDelay--;
370: }
0x080012DC 2800 CMP r0,#0x00
0x080012DE D1FC BNE 0x080012DA
也是這樣3個指令:SUBS,CMP,BNE。還是5個機器周期,沒有問題。
如果選擇不一樣的編譯方式呢?
之前使用的是o0優化,現在使用o3優化(Options for Target --> C/C++ -->Optimization),看看:
42: void Delay(uint32_t nCount)
43: {
0x0800048C 4C03 LDR r4,[pc,#12] ; @0x0800049C
0x0800048E 4620 MOV r0,r4
0x08000490 1E40 SUBS r0,r0,#1
44: for(; nCount != 0; nCount--);
0x08000492 D1FD BNE 0x08000490
主循環中只有2個指令了:SUBS,BNE。居然只有4個機器周期了!
現在來推測一下,需要循環多少次:
4*125*n=1000,000,000ns
n=2000,000
來放心的驗證吧!
對比上面兩種循環的差異,就是少了一個CMP指令。
可是,這樣也有點奇怪吧,難道,cmp指令,是可有可無的嗎?
這里需要理解下BNE指令。
BNE指令,是個條件跳轉,即:是“不相等(或不為0)跳轉指令”。如果不為0就跳轉到后面指定的地址,繼續執行。
而“不相等(或不為0)”,是什么不相等,什么不為0?其實它判斷的是CPSR中的 Z 標記。
具體關于 Z 標記怎么設置的?那就依賴于上一個執行指令了。
再看上一條指令,兩種優化模式下編譯的結果不同,分別是CMP與SUBS。
CMP比較指令,用于把一個寄存器的內容和另一個寄存器的內容或一個立即數進行比較,同時更新CPSR中條件標志位的值。
對于減法,本來有一個SUB。而SUBS的差異,就是多了一個S,它的作用就是會根據執行結果來更新CPSR中的 N、Z、C 和 V 標記。
簡單總結一下,BNE的判斷依賴于Z標志,而CMP與SUBS會影響標志位,具體是如何影響的,我也不想再深入研究了,大體了解到這里,基本能滿足我的好奇心了。
再補充一句,就是關于BNE指令的耗時。
前面我們知道,跳轉時它的耗時是3個機器周期。后面我又測試了一下,若不跳轉,例如是最后一次比較,結果恰好為0,則順序往下執行,此時,BNE指令的耗時是1個機器周期。
可見,匯編里面的內容是很深奧的,所幸,我不需要花太多時間去研究,在此,只是大體了解下,解解幾個小疑惑即可。
上一篇:STM32-仿真調試時的SystemInit陷阱
下一篇:STM32-點燈程序
推薦閱讀
史海拾趣
設計資源 培訓 開發板 精華推薦
- 讓你的娛樂體驗更升一級,三星Galaxy Tab S5e 即將發售
- 群聯搭上PCIe 4.0快車,研發新SSD主控速度可達6.5GB/s
- 三星宣布推出The Wall Luxury電視:可擴展至292英寸 8K分辨率
- 助力邊緣化趨勢,2019中國光網絡研討會上Microchip怎么說
- 抓住變革的機遇,三星電子戰略調整初見成效
- 廣汽埃安發布彈匣電池系統安全技術,重新定義三元鋰電池安全標準
- 德賽西威攜手BlackBerry推一機雙屏虛擬智能座艙域控制器
- 激光雷達量產腳步再加快,國內首條產線已落成
- CTEK推出新型便攜式電池充電器和維護器 搭載自適應增強技術
- 韓國開發無鈷正極材料 經過500次循環保持83%的容量