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)

posted @ 2024-03-21 14:56  FBshark  阅读(1609)  评论(0编辑  收藏  举报