STM32延时函数的三种方法及各自的优缺点——最好掌握第三种
简短不看版
直接使用下面代码:
u16 fac_us = 0; //全局变量,代表1us,SysTick的VAL寄存器减的数目
u16 fac_ms = 0; //全局变量,代表1ms,SysTick的VAL寄存器减的数目
/*
*@func:根据系统时钟频率,计算fac_us,fac_ms的数值,为后续的使用做准备
*@desc:在调用延时前必须调用此函数
*/
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟:HCLK/8(为系统时钟的1/8)
fac_us=SystemCoreClock/(int)8e6;//(本质:计算1us,SysTick的VAL寄存器减的数目, 1e6=1M )
/*详细解释:假设系统时钟为72M,那么Systick为系统时钟的八分频——9MHz,也就是每1s,VAL寄存器减去9M
又因为1us=1/(1M)s,因此每1us,VAL寄存器减去fac_us = 9M/1M = 9
即fac_us = SystemCoreClock/8/(int)1e6 = SystemCoreClock/(int)8e6;
*/
fac_ms=(u16)fac_us*1000; //代表每个ms需要的Systick时钟数(本质:每毫秒SysTick的VAL寄存器减的数目)
}
/*
*@func:仿原子延时,不进入systic中断
*@param:nus 延时的微妙数
*@notice:nus 参数有上限,对于STM32F103,其不得超过1864000
*/
void delay_us(u32 nus)
{
u32 temp;
//SysTick->LOAD = 9*nus;
SysTick->LOAD = fac_us*nus; //注意这里的fac_us 是全局变量,需要提前初始化!!!!!!!!
SysTick->VAL=0X00;//清空计数器
SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
do
{
temp=SysTick->CTRL;//读取当前倒计数值
}while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
/*
*@func:仿原子延时,不进入systic中断
*@param:nus 延时的微妙数
*@notice:nus 参数有上限,对于STM32F103,其不得超过1864
*/
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD = fac_ms*nms; //注意这里的fac_us 是全局变量,需要提前初始化!!!!!!!!
//SysTick->LOAD = 9000*nms; //在STM32F103 72MHz情况下,为fac_us = 9000
SysTick->VAL=0X00;//清空计数器
SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
do
{
temp=SysTick->CTRL;//读取当前倒计数值
}while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
一、 三种延时办法的介绍
单片机编程过程中经常用到延时函数,最常用的莫过于微秒级延时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 个寄存器,
CTRL: SysTick控制和状态寄存器
LOAD: SysTick重装载值寄存器
VAL: SysTick当前值寄存器
CALIB:SysTick校准值寄存器
SysTick-> CALIB 不常用,在这里我们也用不到,故不介绍了。
程序如下,相当于查询法。
//仿原子延时,不进入systic中断
void delay_us(u32 nus)
{
u32 temp;
//SysTick->LOAD = 9*nus;
SysTick->LOAD = fac_us*nus; //注意这里的fac_us 是全局变量,需要提前初始化!!!!!!!!
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 = fac_ms*nms; //注意这里的fac_us 是全局变量,需要提前初始化!!!!!!!!
//SysTick->LOAD = 9000*nms; //在STM32F103 72MHz情况下,为fac_us = 9000
SysTick->VAL=0X00;//清空计数器
SysTick->CTRL=0X01;//使能,减到零是无动作,采用外部时钟源
do
{
temp=SysTick->CTRL;//读取当前倒计数值
}while((temp&0x01)&&(!(temp&(1<<16))));//等待时间到达
SysTick->CTRL=0x00; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
三种方式各有利弊:第一种方式容易理解,但不太精准. 第二种方式采用库函数,编写简单,由于中断的存在,不利于在其他中断中调用此延时函数。
第三种方式直接操作寄存器,看起来比较繁琐,其实也不难,同时克服了以上两种方式的缺点,个人感觉比较好用,但是有下面的使用限制.
二. 第三种延时办法的限制——参数有上限
注意:delay_ms 函数参数u32 i不能超过1800,举例,想定时一分钟,可以通过for循环让delay_ms(1000)走60次,而不能使用delay_ms(60000),不然程序就出错了。
为了程序可移植性更好,因此需要下面的 delay_init() 函数设置fac_us和fac_ms:
首先是delay_init(),延时初始化函数。利用Syst_CLKSourceConfig()函数选择SysTick时钟源,选择外部时钟(HCLK的1/8);同时初始化fac_us和fac_ms两个变量。
void delay_init()
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
fac_us=SystemCoreClock/8e6; //为系统时钟的1/8,实际上也就是在计算1usSysTick的VAL减的数目
fac_ms=(u16)fac_us*1000; //代表每个ms需要的systick时钟数,即每毫秒SysTick的VAL减的数目
}
这里的fac_ms代表每个ms需要的systick时钟数,即每毫秒SysTick的VAL减的数目 。
再回头看自定义代码段 delay_ms() 中的SysTick->LOAD = fac_ms*nms;
其中nms 是用户传入参数,类型为u32.
而LOAD 是 SysTick 的一个寄存器,为24位计数器,也就是它并不是无限大的,因此这就对 nms 参数设定了一个上限!!!
对于72Mhz的STM32F103而言,其SysTick 频率默认为 9MHz(SysTick对STM32HCLK进行8分频),
也就是 九分之一us 机器数减1,因此其 fac_us 值为9(fac_us代表每个us需要的systick时钟数),而fac_ms 的值为9000。
而对于120MHz的STM32F207,其主频为120MHz,SysTick频率为 15MHz, 此时的fac_us 就应该为15, fac_ms = 15000了。
假如在fac_ms 的值为9000(STM32F103), 那么nms_max = SysTick->LOAD
/fac_ms
= 0xffffff/9000 = 1864, 也就是 nms 应该小于1864.
并且频率越高,这个值就越低。(对于STM32F207而言,nms_max = 0xffffff/15000 = 1118)