用C51編寫單片機延時函數

2021-01-07 電子產品世界

參考了51單片機 Keil C 延時程序的簡單研究,自己也親身測試和計算了一些已有的延時函數

這裡假定單片機是時鐘頻率為12MHz,則一個機器周期為:1us.
參考了51單片機 Keil C 延時程序的簡單研究後,我們可知道, 在Keil C中獲得最為準確的延時函數將是

voiddelay(unsignedchart)

{

while(--t);

}

反彙編代碼如下:

執行DJNZ指令需要2個機器周期,RET指令同樣需要2個機器周期,根據輸入t,在不計算調用delay()所需時間的情況下,具體時間延時如下:
tDelay Time (us)12×1+2 =422×2+2=6N2×N+2=2(N+1)

當在main函數中調用delay(1)時, 進行反彙編如下:

調用delay()時,多執行了兩條指令,其中MOV R, #data需要1個機器周期,LJMP需要2個機器周期,即調用delay()需要3us.

Keil C仿真截圖與計算過程:



加上調用時間,準確的計算時間延時與Keil C仿真對比如下:(可見,仿真結果和計算結果是很接近的)
tDelay Time (us)仿真11.0592Mhz時鐘(us)13+2×1+2 =7 | 7.7(實際)7.6023+2×2+2=9 | 9.99.76N3+2×N+2=2N+5 | (2N+5)*1.1/311 | 12.111.941535 | 38.537.98100205 | 225.5222.44255515 | 566.5558.81
也就是說,這個延時函數的精度為2us,最小的時間延時為7us,最大的時間延時為3+255×2+2=515us.
實際中使用11.0592MHz的時鐘,這個延時函數的精度將為2.2us,最小時間延時為7.7us, 最大時間延時為566.5us.
這個時間延時函數,對於與DS18B20進行單總線通信,已經足夠準確了。

現在,我們將時鐘換成11.0592MHz這個實際用到的頻率,每個機器周期約為1.1us.
現在讓我們來分析一下這個之前用過的延時函數:

//延時函數,對於11.0592MHz時鐘,例i=10,則大概延時10ms.

voiddelayMs(unsignedinti)

{

unsignedintj;

while(i--)

{

for(j=0;j<125;j++);

}

}


它的反彙編代碼如下:

分析: T表示一個機器周期(調用時間相對於這個ms級的延時來說,可忽略不計)

1C:0000MOVA,R7;1T
2DECR7;1T低8位字節減1
3MOVR2,0x06;2T
4JNZC:0007;2T若低8位字節不為0,則跳到C:0007
5DECR6;1T低8位字節為0,則高8位字節減1
6C:0007ORLA,R2;1T
7JZC:001D;2T若高8位也減為0,則RET
8CLRA;1TA清零
9MOVR4,A;1TR4放高位
10MOVR5,A;1TR5放低位
11C:000DCLRC;1TC清零
12MOVA,R5;1T
13SUBBA,#0x7d;1TA=A-125
14MOVA,R4;1T
15SUBBA,#0x00;1TA
16JNCC:0000;2TA為零則跳到C:0000
17INCR5;1TR5增1
18CJNER5,#0x00,C:001B;2TR5>0,跳轉到C:000D
19INCR4;1T
20C:001BSJMPC:000D;2T
21C:001DRET


對於delayMs(1), 執行到第7行就跳到21行, 共需時12T, 即13.2us
對於delayMs(2), 需時9T+13T+124×10T+7T+12T = 9T+13T+1240T+7T+12T =1281T =1409.1us.
對於delayMs(3), 需時9T×(3-1)+(13T+124×10T+7T)×(3-1)+12T
=1269T×(3-1)+12T=2550T=2805us.
對於delayMs(N),N>1, 需時1269T×(N-1)+12T = 1269NT-1257T=(1395.9N-1382.7)us.

利用Keil C仿真delayMs(1) = 0.00166558s = 1.67ms 截圖如下:




由分析可知具體的計算延時時間與Keil C仿真延時對比如下:
iTime Delay仿真延時113.2us1.67ms21409.1us3.31ms32805us4.96msN(1395.9N-1382.7)us1012.6ms16.50ms2026.5ms32.98ms3040.5ms49.46ms5068.4ms82.43ms100138.2ms164.84ms200277.8ms329.56ms500696.6ms824.13ms10001394.5ms1648.54ms15002092.5ms2472.34ms20002790.4ms3296.47ms55.6ms8.26ms73100.5ms120.34ms7201003.7ms = 1s1186.74ms


計算delayMs(10)得到延時時間為:12576.3us約等於12.6ms,接近我們認為的10ms。

本文引用地址:http://www.eepw.com.cn/article/201611/316096.htm計算結果和仿真結果只要delayMs(1)有很大出入, 其它都接近, 在接受範圍內.

經過以上分析,可見用C語言來做延時並不是不太準確,只是不容易做到非常準確而已,若有一句語句變了,延時時間很可能會不同,因為編譯程序生成的彙編指令很可能不同。


PS:
對於每條51單片機彙編指令的字長和所需機器周期匯總如下:轉自:http://bbs.mcustudy.com/printpage.asp?BoardID=2&ID=1454
Appendix E - 8051 Instruction Set

Arithmetic Operations

MnemonicDescriptionSizeCycles
ADD A,Rn Add register to Accumulator (ACC).11
ADD A,direct Add direct byte to ACC.21
ADD A,@Ri Add indirect RAM to ACC.11
ADD A,#data Add immediate data to ACC.21
ADDC A,Rn Add register to ACC with carry.11
ADDC A,direct Add direct byte to ACC with carry.21
ADDC A,@Ri Add indirect RAM to ACC with carry.11
ADDC A,#data Add immediate data to ACC with carry.21
SUBB A,Rn Subtract register from ACC with borrow.11
SUBB A,direct Subtract direct byte from ACC with borrow21
SUBB A,@Ri Subtract indirect RAM from ACC with borrow.11
SUBB A,#data Subtract immediate data from ACC with borrow.21
INC A Increment ACC.11
INC Rn Increment register.11
INC direct Increment direct byte.21
INC @Ri Increment indirect RAM.11
DEC A Decrement ACC.11
DEC Rn Decrement register.11
DEC direct Decrement direct byte.21
DEC @Ri Decrement indirect RAM.11
INC DPTR Increment data pointer.12
MUL AB Multiply A and B Result: A DIV AB Divide A by B Result: A DA A Decimal adjust ACC.11
Logical Operations

MnemonicDescriptionSizeCycles
ANL A,Rn AND Register to ACC.11
ANL A,direct AND direct byte to ACC.21
ANL A,@Ri AND indirect RAM to ACC.11
ANL A,#data AND immediate data to ACC.21
ANL direct,A AND ACC to direct byte.21
ANL direct,#data AND immediate data to direct byte.32
ORL A,Rn OR Register to ACC.11
ORL A,direct OR direct byte to ACC.21
ORL A,@Ri OR indirect RAM to ACC.11
ORL A,#data OR immediate data to ACC.21
ORL direct,A OR ACC to direct byte.21
ORL direct,#data OR immediate data to direct byte.32
XRL A,Rn Exclusive OR Register to ACC.11
XRL A,direct Exclusive OR direct byte to ACC.21
XRL A,@Ri Exclusive OR indirect RAM to ACC.11
XRL A,#data Exclusive OR immediate data to ACC.21
XRL direct,A Exclusive OR ACC to direct byte.21
XRL direct,#data XOR immediate data to direct byte.32
CLR A Clear ACC (set all bits to zero).11
CPL A Compliment ACC.11
RL A Rotate ACC left.11
RLC A Rotate ACC left through carry.11
RR A Rotate ACC right.11
RRC A Rotate ACC right through carry.11
SWAP A Swap nibbles within ACC.11
Data Transfer

MnemonicDescriptionSizeCycles
MOV A,Rn Move register to ACC.11
MOV A,direct Move direct byte to ACC.21
MOV A,@Ri Move indirect RAM to ACC.11
MOV A,#data Move immediate data to ACC.21
MOV Rn,A Move ACC to register.11
MOV Rn,direct Move direct byte to register.22
MOV Rn,#data Move immediate data to register.21
MOV direct,A Move ACC to direct byte.21
MOV direct,Rn Move register to direct byte.22
MOV direct,direct Move direct byte to direct byte.32
MOV direct,@Ri Move indirect RAM to direct byte.22
MOV direct,#data Move immediate data to direct byte.32
MOV @Ri,A Move ACC to indirect RAM.11
MOV @Ri,direct Move direct byte to indirect RAM.22
MOV @Ri,#data Move immediate data to indirect RAM.21
MOV DPTR,#data16 Move immediate 16 bit data to data pointer register.32
MOVC A,@A+DPTR Move code byte relative to DPTR to ACC (16 bit address).12
MOVC A,@A+PC Move code byte relative to PC to ACC (16 bit address).12
MOVX A,@Ri Move external RAM to ACC (8 bit address).12
MOVX A,@DPTR Move external RAM to ACC (16 bit address).12
MOVX @Ri,A Move ACC to external RAM (8 bit address).12
MOVX @DPTR,A Move ACC to external RAM (16 bit address).12
PUSH direct Push direct byte onto stack.22
POP direct Pop direct byte from stack.22
XCH A,Rn Exchange register with ACC.11
XCH A,direct Exchange direct byte with ACC.21
XCH A,@Ri Exchange indirect RAM with ACC.11
XCHD A,@Ri Exchange low order nibble of indirect RAM with low order nibble of ACC.11
Boolean Variable Manipulation

MnemonicDescriptionSizeCycles
CLR C Clear carry flag.11
CLR bit Clear direct bit.21
SETB C Set carry flag.11
SETB bit Set direct bit.21
CPL C Compliment carry flag.11
CPL bit Compliment direct bit.21
ANL C,bit AND direct bit to carry flag.22
ANL C,/bit AND compliment of direct bit to carry.22
ORL C,bit OR direct bit to carry flag.22
ORL C,/bit OR compliment of direct bit to carry.22
MOV C,bit Move direct bit to carry flag.21
MOV bit,C Move carry to direct bit.22
JC rel Jump if carry is set.22
JNC rel Jump if carry is not set.22
JB bit,rel Jump if direct bit is set.32
JNB bit,rel Jump if direct bit is not set.32
JBC bit,rel Jump if direct bit is set & clear bit.32
Program Branching

MnemonicDescriptionSizeCycles
ACALL addr11 Absolute subroutine call.22
LCALL addr16 Long subroutine call.32
RET Return from subroutine.12
RETI Return from interrupt.12
AJMP addr11 Absolute jump.22
LJMP addr16 Long jump.32
SJMP rel Short jump (relative address).22
JMP @A+DPTR Jump indirect relative to the DPTR.12
JZ rel Jump relative if ACC is zero.22
JNZ rel Jump relative if ACC is not zero.22
CJNE A,direct,rel Compare direct byte to ACC and jump if not equal.32
CJNE A,#data,rel Compare immediate byte to ACC and jump if not equal.32
CJNE Rn,#data,rel Compare immediate byte to register and jump if not equal.32
CJNE @Ri,#data,rel Compare immediate byte to indirect and jump if not equal.32
DJNZ Rn,rel Decrement register and jump if not zero.22
DJNZ direct,rel Decrement direct byte and jump if not zero.32
Other Instructions

MnemonicDescriptionSizeCycles
NOP No operation.11
其它可查看《單片機基礎》-李廣弟等編著,P70 「MCS-51單片機指令匯總」

相關焦點

  • 編寫延時函數的簡單方法
    如果從keil裡看了c語言的反彙編代碼然後根據晶振和指令計算延時的時間這樣雖然非常的準確但是相當的麻煩而且容易搞錯,我這裡介紹一個最簡單的方法.可以驗證你的延時函數這裡用一個例程詳細介紹一下。編寫一段關於延時的函數,主要利用for循環,代碼如下:void delay_ms(unsigned int ms){unsigned int i;unsigned char
  • 單片機C語言教程:C51函數
    有了函數C 語言就有了模塊化的優點,一般功能較多的程序,會在編寫程序時把每項單獨的功能分成數個子程序模塊,每個子 程序就能用函數來實現。函數還能被反覆的調用,因此一些常用的函數能做成函數庫以供在編寫程序時直接調用,從而更好的實現模塊化的設計,大大提高編程工作的效率。
  • 單片機延時方法小結
    2 軟體延時與時間計算在很多情況下,定時器/計數器經常被用作其他用途,這時候就只能用軟體方法延時。下面介紹幾種軟體延時的方法。通過修改基本延時函數和適當的組合調用,上述方法可以實現不同時間的延時。2.2 在C51中嵌套彙編程序段實現延時在C51中通過預處理指令#pragma asm和#pragma endasm可以嵌套彙編語言語句。用戶編寫的彙編語言緊跟在#pragma asm之後,在#pragma endasm之前結束。
  • AVR單片機微秒級和毫秒級延時函數
    在用單片機IO口模擬總線時序時老是碰到問題,自己總結了一下大多數是因為我們的延時不準確造成的,所以自己調了兩個延時函數>,我板子上用的是AT Mega128的單片機和16MHz的晶振,用示波器看了這兩個函數產生的波形還挺準確的,希望大家能用得上 ^_^本文引用地址:http://www.eepw.com.cn/article/201611/321603.htm//16MHz晶振
  • 單片機兩大延時方法總結
    2 、軟體延時與時間計算在很多情況下,定時器/計數器經常被用作其他用途,這時候就只能用軟體方法延時。下面介紹幾種軟體延時的方法。通過修改基本延時函數和適當的組合調用,上述方法可以實現不同時間的延時。2.2 在C51中嵌套彙編程序段實現延時在C51中通過預處理指令#pragma asm和#pragma endasm可以嵌套彙編語言語句。用戶編寫的彙編語言緊跟在#pragma asm之後,在#pragma endasm之前結束。
  • 單片機c語言教程:C51循環語句
    如一個 12M 的 51 晶片應用電路中要求實現 1 毫秒的延時,那麼就要執行 1000 次空語句 才能達到延時的目的(當然能使用定時器來做,這裡就不討論),如果是寫 1000 條空語 句那是多麼麻煩的事情,再者就是要佔用很多的存儲空間。
  • 在進行C51程序設計時如何精確延時的常見方法介紹
    單片機因具有體積小、功能強、成本低以及便於實現分布式控制而有非常廣泛的應用領域。單片機開發者在編制各種應用程式時經常會遇到實現精確延時的問題,比如按鍵去抖、數據傳輸等操作都要在程序中插入一段或幾段延時,時間從幾十微秒到幾秒。
  • 單片機延時程序經驗
    但在單片機的C語言編程中,經常需要用幾個空指令產生短延時的效果。這在彙編語言中很容易實現,寫幾個nop就行了。在keil C51中,直接調用庫函數:#include // 聲明了void _nop_(void);_nop_(); // 產生一條NOP指令作用:對於延時很短的,要求在us級的,採用「_nop_」函數,這個函數相當彙編NOP指令,延時幾微秒。
  • 談談51單片機延時子程序
    、相關指令的用法等用圖解法的形式詳盡的回答讀者我們知道程序設計是單片機開發最重要的工作,而程序在執行過程中常常需要完成延時的功能。現在的單片機有很多種型號,但在每個型號的單片機器件手冊中都會詳細說明執行各種指令所需的機器周期,了解以上概念後,那麼可以依據單片機器件手冊中的指令執行周期和單片機所用晶振頻率來完成需要精確延時時間的延時程序。
  • 單片機這個知識點一定要會!兩大延時方法總結
    如延時10 μs的延時函數可編寫如下: void Delay10us() {_NOP_( );_NOP_( );可以把這一函數當作基本延時函數,在其他函數中調用,即嵌套調用[4],以實現較長時間的延時;但需要注意,如在Delay40us( )中直接調用4次Delay10us( )函數,得到的延時時間將是42 μs,而不是40 μs。
  • 單片機c語言中nop函數的使用方法和延時計算
    但在單片機的C語言編程中,經常需要用幾個空指令產生短延時的效果。這在彙編語言中很容易實現,寫幾個nop就行了。,要求在us級的,採用「_nop_」函數,這個函數相當彙編NOP指令,延時幾微秒。同樣對於更長時間的延時,可以採用多重循環來完成。只要在程序設計循環語句時注意以上幾個問題。下面給出有關在C51中延時子程序設計時要注意的問題1、在C51中進行精確的延時子程序設計時,儘量不要或少在延時子程序中定義局部變量,所有的延時子程序中變量通過有參函數傳遞。
  • 單片機彙編延時程序的理解
    單片機彙編實現延遲的程序代碼:本文引用地址:http://www.eepw.com.cn/article/171019.htmDELAY:
  • PIC單片機C語言程序實例
    要學會用C函數實現所需功能的方法。實際上,每個C函數都相當於一個功能模塊,一個C函數便可實現一種功能。      此外,用C語言開發PIC單片機應用產品,必須具有PIC單片機彙編語言知識。如果你已經掌握了用彙編語言編寫PIC單片機源程序的方法,最好將編寫成功的PIC單片機彙編語言源程序,逐個用C語言源文件代換,從中體會C語言的優越性。
  • 結合單片機學習板學習c語言之流水燈製作--intrins.h頭文件
    本文引用地址:http://www.eepw.com.cn/article/201608/294948.htm  源程序:  /*本程序結合STC89C51使用,晶振12M,中間用到串口中斷子程序是利用STC單片機的自定義ISP下載功能,自定義下載命令是FEH,關於自定義下載請參考《用51單片機就用STC51,手把手教你STC51的ISP
  • PIC單片機C語言程序設計(2)
    為了避免所編寫的源文件能被所有C編譯器認可,可將標識符的長度限定在8個字符以內(即1個字節)。  標識符的命名(即自定義),最好簡捷、含意清晰、便於閱讀,如用deIay表示延時;用sum表示求和;用loop表示循環等。
  • 第二節:delay()延時實現LED燈的閃爍
    鴻哥教給大家的就是如何編寫這個簡單的作業系統。在main函數循環中用switch語句實現多任務並行處理的任務切換,再外加一個定時器中斷,這兩者的結合就是鴻哥多年來所有實戰項目的核心。鴻哥的程序結構看似簡單,實際上就是那麼簡單。大家不用著急,本篇連載文章現在才正式開始,這一節我要教會大家兩個知識點:第一點:鴻哥首次提出的「三區一線」理論。
  • 一種基於C51單片機的非搶佔式的作業系統架構
    本文引用地址:http://www.eepw.com.cn/article/201612/341756.htm  關鍵詞:51單片機 實時作業系統 任務重八調度  目前,大多數的產品開發是在基於一些小容量的單片機上進行的。51系列單片機,是我國目前使用最多的單片機系列之一,有非常廣大的應用環境與前景,多年來的資源積累,使51系列單片機仍是許多開發者的首選。
  • 單片機C語言延時需注意的問題
    但在單片機的C語言編程中,經常需要用幾個空指令產生短延時的效果。這在彙編語言中很容易實現,寫幾個nop就行了。  在keil C51中,直接調用庫函數:  #include // 聲明了void _nop_(void);  _nop_(); // 產生一條NOP指令  作用:對於延時很短的,要求在us級的,採用「_nop_」函數,這個函數相當彙編NOP指令,延時幾微秒。NOP指令為單周期指令,可由晶振頻率算出延時時間,對於12M晶振,延時1uS。
  • 這裡有一款比單片機更好用的微處理器,那就是Arduino
    在幾年前,鑲嵌式的開發使用的微控制器用的較多的,較為傳統的一般就是單片機了,但是單片機相對於一些鑲嵌式開發的入門者或者是沒有編程基礎卻要簡單使用鑲嵌式的工作者來說,學習和使用單片機起來,還是相當的麻煩和困難的。
  • Proteus 與 kilec51聯機調試入門實驗
    聯機調試,可就是不行;問題是kile運行中不會出現startup.a51這個東東好鬱悶,換到3.0就行了;應該是我下的那個版本不行;今天晚上終於把聯機小實驗調通了,以後就可以全心全意的進行單片機仿真了;以下是我的實驗,至於具體Proteus 與 kilec51的具體設置,咱會在下一篇說明