51微控制器的延時及定時器
2.軟體延時:軟體延時有時候不能夠做到非常精確地延時,主要靠迴圈體或是一些無意義的指令來完成。
微控制器都有一個屬於自己的晶振頻率:11.0592Mhz(主要是為了設定波特率的方便),12Mhz,6Mhz等(後面的例子全都用12M晶振)。對於12Mhz的晶振頻率,一個機器週期為1us,對於51微控制器的庫函式就有nop()這個函式(呼叫時需要#include<intrins.h>),實現延時一個機器週期。那麼就有了簡單的軟體延時程式。可以有delay5us,delay10us等程式,只需要在程式裡用nop()就可以了,但是要注意呼叫該函式需要有一個呼叫指令(2us),結束後也有個結束指令(2us),而且在函式裡呼叫該函式,只有最內層的函式有結束指令。
例如:
void Delay5us( ) {
_NOP_( );
}
void Delay10us()
{
_NOP_( );
_NOP_( );
_NOP_( );
_NOP_( );
_NOP_( );
_NOP_( );
}分別是兩個不同的延時函式。
再就是我們會經常使用的for迴圈延時程式了,我現在也是在學微控制器,在郭天祥老師的程式裡經常會有
void delay(unsigned int i)
{
while(i--);
}
在這個程式裡,如果沒有中斷完全可以用模擬模擬的方法並自己調整,直到自己想要的延時時間,因為在後面中斷,串列埠,模擬時序的時候並沒有那麼精確的延時,都是一個比較大的時間段,但是學了就儘量弄得精確一些。(如果是大神完全可以摳彙編,用示波器,當然我現在都不會。),因為這是c語言程式設計,不是彙編,彙編的一條指令(也就是機器指令)機器週期是一定的,也就是說可以很精確,但是c不行,需要取決於很多東西(如編譯器,cpu等等)。
繼續說這個程式,他就是用不斷迴圈的做一些無意義的事,達到延時的目的。因為你不能準確的知道一條c指令確是多少時間(或者說會有誤差),在上面的程式裡,當i=1時,大約延時10us。下面再給出幾個延時函式,僅供參考。
200ms延時子程式
程式:
void delay200ms(void)
{
unsigned char i,j,k;
for(i=5;i>0;i--)
for(j=132;j>0;j--)
for(k=150;k>0;k--);
}
10ms延時子程式
程式:
void delay10ms(void)
{
unsigned char i,j,k;
for(i=5;i>0;i--)
for(j=4;j>0;j--)
for(k=248;k>0;k--);
}
關於函式延時以及一些基本的程式,我也只瞭解這些,再來說說定時器實現精確延時。
定時器有4種模式,一般較為常用的為方式1,因為一旦溢位就會申請中斷,因此一次溢位共需要65536us,約等於63.5ms,若定時器工作在方式2,則可實現極短時間的精確延時;如使用其他定時方式,則要考慮重灌定時初值的時間(重灌定時器初值佔用2個機器週期)。
在實際應用中,定時常採用中斷方式,如進行適當的迴圈可實現幾秒甚至更長時間的延時。使用定時器/計數器延時從程式的執行效率和穩定性兩方面考慮都是最佳的方案。
在這裡,用定時器中斷服務程式中,需要給定時器重灌初值,完成定時器中斷服務程式就回到主程式,但是要注意,若是沒有關閉中斷,在執行中斷服務程式(進入中斷服務程式需要時間)而且沒有到給它重新賦值的語句前,定時器也在計數中,只有在重新賦初值後的瞬間,又開始從新的值處開始計時,因此,這是一個誤差,解決誤差的辦法就是賦值初值的時候加上它當前的值。TH0=TH0+初值,TL0=TL0+初值。
另外,中斷服務程式不要過長,或者有一個或多個延時程式(不是說不能,是不建議),否則中斷服務程式還沒結束就又進入中斷,會造成崩潰。
下面給出一個程式,實現數碼管每隔1s迴圈顯示0-F,實現準確延時,當然不是絕對的準確。(我就直接把我學習微控制器開發板上面的程式拷過來了)。這個程式重新賦值的時候沒有向我上面說的那樣,中斷服務程式也略顯贅餘,可以把if放在主函式while中。
/**************************************************************************************
* 定時器1實驗 *
實現現象:下載程式後數碼管最後一位間隔一秒迴圈顯示0-F。使用微控制器內部定時器可以實現準確延時。
***************************************************************************************/
#include "reg52.h" //此檔案中定義了微控制器的一些特殊功能暫存器
typedef unsigned int u16; //對資料型別進行宣告定義
typedef unsigned char u8;
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
u8 code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//顯示0~F的值
u8 n=0;
/*******************************************************************************
* 函 數 名 : Timer1Init
* 函式功能 : 定時器1初始化
* 輸 入 : 無
* 輸 出 : 無
*******************************************************************************/
void Timer1Init()
{
TMOD|=0X10;//選擇為定時器1模式,工作方式1,僅用TR1開啟啟動。
TH1=0XFC; //給定時器賦初值,定時1ms
TL1=0X18;
ET1=1;//開啟定時器1中斷允許
EA=1;//開啟總中斷
TR1=1;//開啟定時器
}
/*******************************************************************************
* 函 數 名 : main
* 函式功能 : 主函式
* 輸 入 : 無
* 輸 出 : 無
*******************************************************************************/
void main()
{
LSA=0;
LSB=0;
LSC=0;
Timer1Init(); //定時器1初始化
while(1);
}
/*******************************************************************************
* 函 數 名 : void Timer1() interrupt 3
* 函式功能 : 定時器0中斷函式
* 輸 入 : 無
* 輸 出 : 無
*******************************************************************************/
void Timer1() interrupt 3
{
static u16 i;
TH1=0XFC; //給定時器賦初值,定時1ms
TL1=0X18;
i++;
if(i==1000)
{
i=0;
P0=smgduan[n++];
if(n==16)n=0;
}
}
注:這裡數碼管用的是138譯碼器。
這就是我的一些理解,我現在也在學習中,想著花點時間總結會有更好的脈絡。之後會再來完善。有錯歡迎指出。