一文掌握SysTick定时器 | 文末赠书

文摘   2024-09-08 07:01   山东  

1 SysTick定时器概述

ARM Cortex-M4处理器内部包含一个简单的定时器。因为所有的Cortex-M4芯片都带有这个定时器,软件在不同Cortex-M4器件间的移植工作得以化简。该定时器的时钟源可以是内部时钟(FCLKCortex-M4处理器上的自由运行时钟),也可以是外部时钟(Cortex-M4处理器上的STCLK信号)。不过,STCLK的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能会大不相同,需要查阅芯片的器件手册来决定选择什么作为时钟源。
2 SysTick定时器寄存器

4个寄存器控制SysTick定时器,如表1~4所示。

1 SysTick控制及状态寄存器STK_CTRL

位段

名称

类型

复位值

描述

16

COUNTFLAG

R

0

如果在上次读取本寄存器后,SysTick 已经数到了0,则该位为1。如果读取该位,该位将自动清零

2

CLKSOURCE

R/W

0

0=外部时钟源(STCLK)AHB时钟8分频

1=内核时钟(FCLK)HCLK(内核)时钟

1

TICKINT

R/W

0

1=SysTick倒数到0时产生SysTick异常请求

0=SysTick数到0时无动作

0

ENABLE

R/W

0

SysTick定时器的使能位

2 SysTick重装载数值寄存器STK_LOAD

位段

名称

类型

复位值

描述

23:0

RELOAD

R/W

0

SysTick倒数至0时,将被重装载的值

3 SysTick当前数值寄存器STK_VAL

位段

名称

类型

复位值

描述

23:0

CURRENT

R/Wc

0

读取时返回当前倒计数的值,写它则使之清零,同时还会清除在SysTick 控制及状态寄存器中的COUNTFLAG标志

4 SysTick校准数值寄存器STK_CALIB

位段

名称

类型

复位值

描述

31

NOREF

R

1=没有外部参考时钟(STCLK 不可用)

0=外部参考时钟可用

30

SKEW

R

1=校准值不是准确的 10ms

0=校准值是准确的 10ms

23:0

TENMS

R/W

0

10ms的时间内倒计数的格数。芯片设计者应该通过 Cortex‐M4的输入信号提供该数值。若该值读回零,则无法使用校准功能

3 延时函数HAL_Delay

HAL库当中提供了一个十分方便的利用SysTick定时器实现的延时函数HAL_Delay(),为更好地理解和应用这一函数,下面对其实现过程进行详细讲解。

HAL_Delay()函数位于stm32f4xx_hal.c文件当中,其原型为:

__weak void HAL_Delay(uint32_t Delay)

{

   uint32_t tickstart = HAL_GetTick();

   uint32_t wait = Delay;

   /* Add a freq to guarantee minimum wait */

   if (wait < HAL_MAX_DELAY)  // HAL_MAX_DELAY=0xFFFF FFFF

   {

       wait += (uint32_t)(uwTickFreq);

   }

   while((HAL_GetTick() - tickstart) < wait)

   {

   }

}

延时函数首先调用HAL_GetTick()函数,并把它的返回值赋给变量tickstart,同时把函数的形参(Delay延时的毫秒数)赋给变量wait,随后使用if语句判断wait的值是否小于HAL_MAX_DELAY宏定义值为0xFFFF FFFF),如果是,wait变量增加uwTickFreq(枚举类型,ms延时为1)。最后进入while循环中,while循环执行的时间即为延时的时间。

继续打开HAL_GetTick()函数,其代码比较简单,仅返回变量uwTick值。

__weak uint32_t HAL_GetTick(void)

{

   return uwTick;

}

由此可见变量uwTick在延时实现中起关键作用,我们进一步观察与这一变量数值变化有关的函数,其中一个为HAL_IncTick()函数,其代码也较为简单,只是将uwTick数值加1

__weak void HAL_IncTick(void)

{

   uwTick += uwTickFreq;

}

通过代码跟踪发现HAL_IncTick()函数被SysTick中断服务程序SysTick_Handler()调用,其位于stm32f4xx_it.c文件中,代码如下:

void SysTick_Handler(void)

{

   HAL_IncTick();

}

综上所述HAL_Delay()延时函数实现原理为,定义一个无符号整型变量uwTick,通过中断服务程序每1ms其数值加1。进入延时函数,首先记录下uwTick初始值,然后不断读取当前的uwTick变量值,如果二者差值小于延时数值,则一直等待,直至延时完成。

由上述分析可知,使用延时函数需要注意以下几点:

 

(1)SysTick定时器使用内部时钟作为时钟源。(2)SysTick定时器延时是阻塞运行的,延时过程独占CPU,无法执行其他任务。(3)SysTick定时器要想跳出延时函数,必须保证中断服务程序能够被执行,否则将会导致系统死机。

4 HAL_Delay延时实例

上一节LED流水灯控制程序中延时程序是通过软件延时的方法实现的,这个时间很不精确,只能大概估计。根据上述分析,本节利用HAL_Delay()函数来实现精确的1秒延时,操作较为简单,只需要将原程序中的delay(24000000)替换为HAL_Delay(1000)即可。main()函数的参考代码如下:

int main(void)

{

HAL_Init();

SystemClock_Config();

MX_GPIO_Init();

/* USER CODE BEGIN WHILE */

while (1)

{

GPIOF->ODR=0xFE; HAL_Delay(1000);

GPIOF->ODR=0xFD; HAL_Delay(1000);

GPIOF->ODR=0xFB; HAL_Delay(1000);

GPIOF->ODR=0xF7; HAL_Delay(1000);

GPIOF->ODR=0xEF; HAL_Delay(1000);

GPIOF->ODR=0xDF; HAL_Delay(1000);

GPIOF->ODR=0xBF; HAL_Delay(1000);

GPIOF->ODR=0x7F; HAL_Delay(1000);

/* USER CODE END WHILE */

}

}

5 微秒延时实现

HAL_Delay()函数无需用户设计代码,在工程任何位置可以直接调用,十分方便。但它也存在一些缺点,一是使用中断方式,如果在其他中断服务程序中使用,则需重新配置中断优先级,否则将会导致死机。二是无法实现微秒级延时,因为配置的中断服务程序是1ms执行一次,只能进行毫秒级延时。本节将使用查询方式实现SysTick定时器微秒级的延时,这在某些传感器或数据通信中是经常需要使用的,其参考程序如下。

void delay_us(uint32_t nus)

{

uint32_t ticks;

uint32_t told,tnow,tcnt=0;

uint32_t reload=SysTick->LOAD+1; //计数个数为重装载值加1

ticks=nus*(SystemCoreClock/1000000); //需要的节拍数

told=SysTick->VAL;         //初始计数器值

while(1)

{

tnow=SysTick->VAL;

if(tnow!=told)

{     

if(tnow<told) tcnt+=told-tnow;  //Systick递减的计数器

else tcnt+=reload-tnow+told;     

told=tnow;

if(tcnt>=ticks)  break; //延时时间已到,退出

}  

}

}

 
分析delay_us()函数的实现代码可知,函数使用重装载值参与节拍数的累加,但并没有更改寄存器的数值。根据系统变量SystemCoreClock计算1μs定时所需要节拍数。上述两点使得延时函数可以应用于任意内核频率系统且和SysTick定时器重装载值无关。

使用delay_us(1000)可以实现1ms的延时,基于查询方式的毫秒延时函数就是延时若干个1ms,其参考程序如下:

void delay_ms(uint32_t nms)

{

uint32_t i;

for(i=nms;i>0;i--)

delay_us(1000) ;

}

6 综合延时程序实例

至此,本章共介绍了4种延时函数的实现方式,还是以LED流水灯为例进行项目实验,采用四种方式分别延时,参考代码如下:

int main(void)

{

   HAL_Init();

   SystemClock_Config();

   MX_GPIO_Init();

   /* USER CODE BEGIN WHILE */

   while (1)

   {

       GPIOF->ODR=0xFE;

       delay_us(1000000);    //SysTick查询方式us延时

       GPIOF->ODR=0xFD;

       delay_us(1000000);

       GPIOF->ODR=0xFB;

       delay_ms(1000);      // SysTick查询方式ms延时

       GPIOF->ODR=0xF7;

       delay_ms(1000);

       GPIOF->ODR=0xEF;

       HAL_Delay(1000);    // SysTick中断方式ms延时

       GPIOF->ODR=0xDF;

       HAL_Delay(1000);

       GPIOF->ODR=0xBF;

       delay(24000000);      //软件延时,空循环等待

       GPIOF->ODR=0x7F;

       delay(24000000);

       /* USER CODE END WHILE */

   }

}

 
需要说明的是上述代码中4种延时方式混合使用,只为测试方便,这种程序设计风格是不推荐的。测试表明4种延时函数可以交替使用,相互之间没有影响,除软件延时时间精度难以保证而外,其余三种方式可以实现精确的延时。

这么多的延时方式应该如何选择呢?软件延时函数delay()简单方便、易于实现,在短延时或不需要精确延时场合使用更加高效。基于中断方式的HAL_Delay()延时函数由官方提供,且已经初始化完成,在任何文件中都可以直接使用,在大部分场合,使用该函数实现毫秒级延时。如果要实现微秒级延时,则可以使用作者编写的delay_us()延时函数,令人欣喜的是,该函数与官方延时函数共用系统初始化,可以交替使用。如果需要采用基于查询方式的毫秒级延时,则可以使用delay_ms()延时函数。

6 使用教材
7 SysTick定时器寄存器项目可复制文件下载

一文掌握SysTick定时器.docx


抽奖(ARM Cortex-M4嵌入式系统原理及应用)
(1) 关注公众号,将本篇文章转发朋友圈;
(2) 在公众号对话框界面输入“7,弹出抽奖二维码(抽奖截至时间为9月9日20:00)
(3) 扫描二维码参与抽奖,中奖者请于开奖当日填写邮寄地址。

人工智能科学与技术
分享教学成果 | 传播前沿科技| 推荐优秀图书
 最新文章