8051系列單片機軟件精確延時研究(一)


前言

  最近自學STC公司的8051系列單片機,編程中如流水燈等非精確延時多用軟件延時實現,寫了幾個類似DelayX10us(unsigned char x)的函數方便調用,函數內部的語句多是用STC官方延時程序再自己套一個for或者do..while循環改造而成,像這樣:

//非精確延時10*Xus
//
@12.000MHz 12T模式 void DelayX10us(unsigned char x) { unsigned char i; for (; x > 0; x--) { _nop_(); i = 2; while (--i); } }

  由於不懂匯編,所以對代碼的實際延時時間一直沒有深究,每次都是憑感覺摸索個大概。今天突然心血來潮在keil仿真中執行了一下以上代碼,觀察了一下延時時間,得到結果如下:

X 延時目標(us) 實際延時(us) 誤差
1 10 24 140%
10 100 150 50%
100 1000 1410 41%

 

 

 

  

  OMG,100us誤差達到50%,延時1000us誤差也有41%,這還真是“非(常的)精確”啊。

  突然覺得有必要研究一下匯編代碼,搞懂這個延時是怎么誤差這么大的。學習嘛,就不該留盲點,也正好借此機會了解一下匯編語言,對理解單片機底層應該有一定幫助。如果編程人員對自己寫的代碼底層如何實現一清二楚,那溢出、內存泄漏什么的bug就絕不會存在了。當然,要達到這個理想情況是很難的,只能朝着這個方向多努力了。

  寫了一段代碼做研究用,如下:

#include <reg52.h>
#include <intrins.h>

void DelayX10us(unsigned char x);
void main()
{
    DelayX10us(1);
    DelayX10us(10);
    DelayX10us(100);
    while (1);
}

//@12.000MHz 12T
void DelayX10us(unsigned char x)
{
    unsigned char i;
    for (; x > 0; x--)
    {
        _nop_();
        i = 2;
        while (--i);
    }
}
View Code

  


 

反匯編代碼

  順便說一下,軟件環境:Keil uvison 4。

  上述代碼編譯完后,點擊"Start Debug"開始調試,Disassembly窗口中就顯示出了相應的反匯編代碼,還顯示了C語言與匯編代碼的對應關系,比在Linux環境下調試方便多了。

main()函數:

DelayX10us()函數

 

  查芯片手冊中指令系統部分內容可知,上述代碼中LCALL、SJMP、JC、DJNZ、RET這幾個指令是2機器周期指令,其余是1機器周期指令。現在開始來計算延時時間:

  x=1: 

  main()中 for循環 返回 總  計
機器周期    1+2 (1+1+1+2   +1+1+2*2   +1+2)*1 +1+1+1+2  2   24

 

 

  說明:1、main()中傳值和跳轉兩個操作周期為1+2。

       2、0x0016  SUBB A,0x00 為執行借位減法,可以簡單理解為將A-0x00-Cy(進位借位標識,也就是上一句中的C)的結果裝入A,並判斷如果夠減(結果>=0),Cy=0(未產生借位);如果不夠減(結果<0),Cy=1(產生借位)。所以當A>=1時,都夠減,Cy=0,下一句JC不會跳轉,直到A=0不夠減時才跳轉。(A就是X的值)

     3、for循環中,第一次從0x0014到0x0020執行完,周期數為1+1+1+2   +1+1+2*2   +1+2,此時R7寄存器中存儲的x值為0;此時已跳轉到0x0014繼續執行,直到0x0018,跳轉到0x0022,周期數為1+1+1+2。返回main()函數又花兩個周期。所以main()中"DelayX10us(1);"共耗費24個,12M/12T模式下即為24us。

  同理,x=10:

  main()中 for循環 返回 總  計
機器周期    1+2 (1+1+1+2   +1+1+2*2   +1+2)*10 +1+1+1+2  2   150

 

 

  x=100時同理1+2  +(1+1+1+2+1+1+2*2+1+2)*100  +1+1+1+2  +2 = 1410

小結

    綜上可看出,單純的在官方延時函數基礎上套for循環而得到的延時相當不精確。分析誤差原因可知,main()中的3個周期、子函數返回的2個周期、for循環末尾的(1+1+1+2)個周期,這10個機器周期是固定誤差值,最關鍵的在於塗黃部分共14個周期,超出了預期的10us倍增的延時。把這部分稍微改一下,使括號內塗黃部分變為10個機器周期,這樣子就能使所有的x倍延時的誤差值都為固定誤差10us了。更改后的代碼如下:

//非精確延時10*X us,固定誤差10us
//@12.000MHz 12T模式
void DelayX10us(unsigned char x)        
{
    unsigned char i;
    for (; x > 0; x--)
    {
        _nop_();
        _nop_();
    }
}

  更改后的延時機器周期數=1+2  +(1+1+1+2  +1+1 +1+2)*X  +1+1+1+2  +2 = 10*X+10。X在1~255取值范圍內,誤差均為固定10us。

 

PS:本文所有延時都是在12MHz晶振、12T模式下計算,1個機器周期=1us。

   反匯編代碼為Keil軟件內代碼優化等級level 8下編譯后的反匯編。不同優化等級編譯的代碼反匯編后有稍許差別,再次不做論述。

 

  后篇:8051系列單片機軟件精確延時研究(二)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM