STM32的幾種延時方法(基於MDK固件庫3.0,晶振8M)
單片機編程過程中經常用到延時函數,最常用的莫過於微秒級延時delay_us( )和毫秒級delay_ms( )。
1.普通延時法
這個比較簡單,讓單片機做一些無關緊要的工作來打發時間,經常用循環來實現,不過要做的比較精准還是要下一番功夫。下面的代碼是在網上搜到的,經測試延時比較精准。
//粗延時函數,微秒
void delay_us(u16 time)
{
u16 i=0;
while(time--)
{
i=10; //自己定義
while(i--) ;
}
}
//毫秒級的延時
void delay_ms(u16 time)
{
u16 i=0;
while(time--)
{
i=12000; //自己定義
while(i--) ;
}
}
2.SysTick 定時器延時
CM3 內核的處理器,內部包含了一個SysTick 定時器,SysTick 是一個24 位的倒計數定時器,當計到0 時,將從RELOAD 寄存器中自動重裝載定時初值。只要不把它在SysTick 控制及狀態寄存器中的使能位清除,就永不停息。SysTick 在STM32 的參考手冊里面介紹的很簡單,其詳細介紹,請參閱《Cortex-M3 權威指南》。
這里面也有兩種方式實現:
a.中斷方式
如下,定義延時時間time_delay,SysTick_Config()定義中斷時間段,在中斷中遞減time_delay,從而實現延時。
volatile unsigned long time_delay; // 延時時間,注意定義為全局變量
//延時n_ms
void delay_ms(volatile unsigned long nms)
{
//SYSTICK分頻--1ms的系統時鍾中斷
if (SysTick_Config(SystemFrequency/1000))
{
while (1);
}
time_delay=nms;//讀取定時時間
while(time_delay);
SysTick->CTRL=0x00; //關閉計數器
SysTick->VAL =0X00; //清空計數器
}
//延時nus
void delay_us(volatile unsigned long nus)
{
//SYSTICK分頻--1us的系統時鍾中斷
if (SysTick_Config(SystemFrequency/1000000))
{
while (1);
}
time_delay=nus;//讀取定時時間
while(time_delay);
SysTick->CTRL=0x00; //關閉計數器
SysTick->VAL =0X00; //清空計數器
}
//在中斷中將time_delay遞減。實現延時
void SysTick_Handler(void)
{
if(time_delay)
time_delay--;
}
b.非中斷方式
主要仿照原子的《STM32不完全手冊》。SYSTICK 的時鍾固定為HCLK 時鍾的1/8,在這里我們選用內部時鍾源72M,所以SYSTICK的時鍾為9M,即SYSTICK定時器以9M的頻率遞減。SysTick 主要包含CTRL、LOAD、VAL、CALIB 等4 個寄存器,
SysTick->CTRL
位段 |
名稱 |
類型 |
復位值 |
描述 |
16 |
COUNTFLAG |
R |
0 |
如果在上次讀本寄存器后systick已為0,則該位為1,若 讀該位自動清零 |
2 |
CLKSOURCE |
RW |
0 |
0:外部時鍾源 1:內部時鍾 |
1 |
TICKINT |
RW |
0 |
0:減到0無動作;1:減到0產生systick異常請求 |
0 |
ENABLE |
RW |
0 |
systick定時器使能位 |
SysTick-> LOAD
位段 |
名稱 |
類型 |
復位值 |
描述 |
23:0 |
RELOAD |
RW |
0 |
減到0時被重新裝載的值 |
SysTick-> VAL
位段 |
名稱 |
類型 |
復位值 |
描述 |
23:0 |
CURRENT |
RW |
0 |
讀取時返回當前倒計數的值,寫則清零,同時還會清除在systick控制及狀態寄存器中的COUNTFLAG 標志 |
SysTick-> CALIB 不常用,在這里我們也用不到,故不介紹了。
程序如下,相當於查詢法。
//仿原子延時,不進入systic中斷
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD = 9*nus;
SysTick->VAL=0X00;//清空計數器
SysTick->CTRL=0X01;//使能,減到零是無動作,采用外部時鍾源
do
{
temp=SysTick->CTRL;//讀取當前倒計數值
}while((temp&0x01)&&(!(temp&(1<<16))));//等待時間到達
SysTick->CTRL=0x00; //關閉計數器
SysTick->VAL =0X00; //清空計數器
}
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD = 9000*nms;
SysTick->VAL=0X00;//清空計數器
SysTick->CTRL=0X01;//使能,減到零是無動作,采用外部時鍾源
do
{
temp=SysTick->CTRL;//讀取當前倒計數值
}while((temp&0x01)&&(!(temp&(1<<16))));//等待時間到達
SysTick->CTRL=0x00; //關閉計數器
SysTick->VAL =0X00; //清空計數器
}
三種方式各有利弊,第一種方式容易理解,但不太精准。第二種方式采用庫函數,編寫簡單,由於中斷的存在,不利於在其他中斷中調用此延時函數。第三種方式直接操作寄存器,看起來比較繁瑣,其實也不難,同時克服了以上兩種方式的缺點,個人感覺比較好用。