【STM32】SysTick

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、普通延时

普通延时是大多数人最开始接触51单片机时所使用的延时方式,其实现原理是让单片机进入while循环,做一些无关紧要的事儿,直到满足条件退出循环。

//粗延时函数,微秒
void delay_us(int time)
{    
   int i=0;  
   while(time--)
   {
      i=5;  //自己定义
      while(i--) ;    
   }
}
//毫秒级的延时
void delay_ms(int time)
{    
   int i=0;  
   while(time--)
   {
      i=6000;  //自己定义
      while(i--) ;    
   }
}

以上代码在STM32H750VBT6中运行,晶振25MHz,主频480M。经测试,**delay_us(1);**实际延时1.24us,**delay_ms(1);**实际延时0.96048ms。若想提高延时精度,需要进一步调整i参数。总之普通延时方式准确度较低,适用于对延时精度要求不高的场合。

二、SysTick延时

SysTick即系统定时器,是Crotex-M内核上的一个外设,内嵌在NVIC中。它是一个24位的向下递减的计数器。在这里插入图片描述该定时器由三部分构成,如下所示:在这里插入图片描述
个人理解运行机制(需要进一步验证):重装载数值寄存器记录最大计数值,当前数值寄存器从重装载数值寄存器加载最大计数值后开始在SYSCLK的驱动下倒计时,每经过1/SYSCLK的时间,当前数值寄存器的值减1,当减到0之后,系统定时器就会产生一次中断,同时当前数值寄存器再次从重装载数值寄存器加载最大计数值并重新开始递减。

以当前运行环境为例,主频为480M,若将重装载数值寄存器全部写1,则产生一次中断用时:(2^24-1)*1/480M=0.03495s。

若要产生一次中断用时1ms,设重装载数值寄存器应写入t,则 t*1/480M=0.001s。得t=480000。

另外系统定时器还包括4个寄存器:
一是上面所述的重装载数值寄存器;
二是上面所述的当前数值寄存器;
三是校准数值寄存器,该寄存器用不上不用管。
四是控制及状态寄存器,其有用的4个位描述如下:

所在位名称功能
0ENABLE定时器使能
1TICKINT1:倒计时到0产生异常请求
0:计数到0无动作
2CLKSOURCE1:AHB
0:AHB/8
16COUNTFLAG计数到0该位置1

1.中断方式

在了解以上知识的基础上,就可以开始学习第二种延时方式。代码如下:

static __IO u32 TimingDelay;//用于中断计数,每进入一次中断,该变量减1
//static将全局变量TimingDelay的作用域限制在当前文件中,在其他文件中无法调用
//_IO表示该变量是易变的,要求编译器不要优化

 
/**
  * @brief  启动系统滴答定时器 SysTick
  * @param  无
  * @retval 无
  */
void SysTick_Init(void)
{
	/* SystemFrequency / 1000    1ms中断一次
	 * SystemFrequency / 100000	 10us中断一次
	 * SystemFrequency / 1000000 1us中断一次
	 */
	 //HAL_SYSTICK_Config函数的形参传入的就是重装载计数器的值+1,我们将在后面的库函数中看到
	if (HAL_SYSTICK_Config(SystemCoreClock / 1000))
	{ 
		/* Capture error */ 
		while (1);
	}
}

/**
  * @brief  ms延时程序,1ms为一个单位
  * @param  
  *		@arg nTime: Delay_ms( 1 ) 则实现的延时为 1 * 1ms = 1ms,因为产生一次中断需要1ms,nTime参数其实就是产生多少次中断
  * @retval  无
  */
void Delay_ms(__IO u32 nTime)
{ 
	TimingDelay = nTime;	

	while(TimingDelay != 0);
}

/**
  * @brief  获取节拍程序
  * @param  无
  * @retval 无
  * @attention  在 SysTick 中断函数 SysTick_Handler()调用
  */
  //该函数在stm32h7××_it.c中调用,即中断服务函数
void TimingDelay_Decrement(void)
{
	if (TimingDelay != 0x00)
	{ 
		TimingDelay--;
	}
}

程序的整体运行过程是:首先初始化定时器,写入数值到重装载数值寄存器,让系统定时器1ms产生一次中断;然后调用Delay_ms函数,给TimingDelay赋值,只要TimingDelay!=0,就停留在while中不出来;每次中断都会进入TimingDelay_Decrement中断服务函数,将TimingDelay减1;只有当TimingDelay==0时,程序才能从Delay_ms中跳出,延时结束。

下面的函数为HAL库中的系统定时器配置函数,我们上面自定义的SysTick_Init函数就是调用该函数,传入的参数只有一个,即重装载计数器的值+1。

__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
  if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)//判断ticks是否大于0xFFFFFF,即最大值
  {
    return (1UL);                                                   /* Reload value impossible */
  }

  SysTick->LOAD  = (uint32_t)(ticks - 1UL);                         /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
  SysTick->VAL   = 0UL;                                             /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |        //时钟源为AHB
                   SysTick_CTRL_TICKINT_Msk   |       //计数到0产生异常请求
                   SysTick_CTRL_ENABLE_Msk;        //使能定时器      /* Enable SysTick IRQ and SysTick Timer */
  return (0UL);                                                     /* Function successful */
}

HAL_Delay()就是采用中断方式进行延时的,其产生一次中断耗时1ms,但需要微秒级别延时时,HAL_Delay()就满足不了要求了,如果将产生一次中断耗时设置为1us,那单片机基本上就一直工作在进出中断上了,就没有时间去做其他事情了,这是得不偿失的。故微秒级别延时最好不要用中断方式。

2.非中断方式

代码如下:

void SysTick_Delay_Us( __IO uint32_t us)
{
	uint32_t i;
	HAL_SYSTICK_Config(SystemCoreClock/1000000);//配置1us产生一次中断
	//也产生中断,但没有中断服务函数,什么都不做
	
	for(i=0;i<us;i++)//延时几微秒就循环几次
	{
	    //每次循环都要等CTRL的第16位置1
		// 当计数器的值减小到0的时候,CRTL寄存器的位16会置1	
		while( !((SysTick->CTRL)&(1<<16)) );
	}
	// 关闭SysTick定时器(用完即关闭)
	SysTick->CTRL &=~SysTick_CTRL_ENABLE_Msk;
}

void SysTick_Delay_Ms( __IO uint32_t ms)
{
	uint32_t i;	
	HAL_SYSTICK_Config(SystemCoreClock/1000);
	
	for(i=0;i<ms;i++)
	{
		// 当计数器的值减小到0的时候,CRTL寄存器的位16会置1
		// 当置1时,读取该位会清0
		while( !((SysTick->CTRL)&(1<<16)) );
	}
	// 关闭SysTick定时器
	SysTick->CTRL &=~ SysTick_CTRL_ENABLE_Msk;
}

非中断方式延时利用的是系统定时器自带的控制及状态寄存器,当计数到0时,其第16位会置1,且读取该位的值可以清0。首先配置定时器(假如配置完成后1us产生一次中断);然后进入for循环,延时几微秒就循环几次,每次循环都要等CTRL寄存器的第16位置1,否则就不出while循环;最后跳出for循环,将定时器关闭,延时完成。

总结

普通方式最简单,但定时不精确,在精确度要求不高的场合可以使用。

中断方式为HAL库自带的延时方式,即HAL_Delay,可以直接使用,但只能实现毫秒级别延时,虽然也可以通过修改重装载值实现微秒级别延时,但会大量消耗单片机资源,使单片机利用效率大大降低,故该方式 仅适用于毫秒级别延时。

非中断方式延时相对于中断方式的优点是随用随开,一点都不造成资源浪费,要知道中断方式一旦开启定时器,不管有没有调用delay函数,中断一直在发生。而非中断方式延时结束后就关闭定时器了。可实现微秒级别延时。

首推非中断方式延时!!!

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值