今天讲解的是stm32的系统定时器——SysTick定时器。
一、SysTick定时器简介
《Cortex-M3权威指南》中对SysTick的描述,SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。它是一个24位的递减定时器,当计数到 0 时,将从RELOAD 寄存器中自动重装载定时初值,开始新一轮计数。大多数操作系统需要一个硬件定时器来产生滴答中断,作为整个系统的时基。例如,为多个任务许以不同数目的时间片,确保没有一个任务霸占系统;或者把每个定时器周期的某个时间范围赐予特定的任务等,还有提供各种定时功能,都与滴答定时器有关。因此,需要一个定时器产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统的“心跳”的节奏。该定时器的时钟源可以是内部时钟(FCLK),或者是外部时钟(CM3处理器上的STCLK信号)。SysTick定时器能产生中断,异常中断。使用内核的SysTick定时器来实现延时,可以不占用系统定时器,由于和MCU外设无关,所以代码的移植,在不同厂家的Cortex-M内核MCU之间,可以很方便的实现。
二、SysTick相关寄存器介绍
SysTick一共有4个寄存器,在core_cm3.h这个头文件里定义了以这4个寄存器为成员的结构体指针。
typedef struct
{
__IO uint32_t CTRL; /*!< Offset: 0x00 SysTick Control and Status Register */
__IO uint32_t LOAD; /*!< Offset: 0x04 SysTick Reload Value Register */
__IO uint32_t VAL; /*!< Offset: 0x08 SysTick Current Value Register */
__I uint32_t CALIB; /*!< Offset: 0x0C SysTick Calibration Register */
} SysTick_Type;
各个寄存器功能如下
1、SysTick控制及状态寄存器
2、SysTick重装载数值寄存器
3、SysTick当前数值寄存器
4、SysTick校准数值寄存器
通常只需要用到前3个寄存器,第四个寄存器在定时实验中不需要用到,有关各个位的描述在英文文档里比较晦涩难懂,这个寄存器的用途有待研究。
了解了它的定时器作用之后,接下来是如何通过编程得到准确的延时。
三、SysTick定时器配置步骤
SysTick定时器的操作可以分为 4 步:
(1)设置SysTick定时器的时钟源。
(2)设置SysTick定时器的重装初始值(如果要使用中断的话,就将中断使能打开)。
(3)清零SysTick定时器当前计数器的值。
(4)打开SysTick定时器。
代码实现
void SysTick_Init(u8 SYSCLK)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //9M
fac_us=SYSCLK/8;
fac_ms=(u16)fac_us*1000;
}
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
上面有几个地方需要注意
①SysTick的时钟来源可以是AHB时钟或者是AHB时钟除以8。具体可以参照stm32固件库说明。
②在访问结构体成员时,(.)和(->)的区别。访问结构成员的运算符有两种,一种是结构成员运算符“·”,也称为“圆点运算符”,另一种是结构指针运算符“->”,也称“箭头运算符”。
点是用于结构体变量访问成员,箭头是用于结构体指针访问成员。
补充说明:圆点运算符是比较古老的写法,不能访问结构体指针变量成员,现在都推荐使用箭头运算符,即(->)。
③计数值的计算
计数值=计数总时间/每次计数所需时间。
每次计数所需时间=1/SYSCLK。
所以
计数值=计数总时间*SYSCLK。
同时还要注意单位的统一性。比如假设需要计时的时间为500us,定时器时钟源为系统时钟72MHz,那么计数值为500*72。
当定时单位为ms时,与kHz相乘才能将单位消掉,因此,假设定时500ms,定时器时钟源为9MHz,那么计数值为500*9*1000。
这就是为什么上面定义fac_ms=fac_us*1000的原因。
编写好上面两个延时函数之后就可以方便的调用了。调用时要注意不能超过它的最大值。
比如调用void delay_us(u32 nus),假设时钟源选用72MHz,
那么nus的最大值为0xFFFFFF/72=233016us。
比如调用void delay_ms(u32 nms),假设时钟源选用9MHz,
那么mus的最大值为0xFFFFFF/9000=1864ms。
总结:
SysTick定时器主要是要知道如何通过它得到准确定时,并且编写延时函数,另外也可以在定时结束时产生的中断里面编写中断响应函数。配置上也比较简单,基本上就是配置时钟来源,装载计数值。
另外,在一些编译器里,有时不能调用库函数SysTick_SetReload(),大概是因为官方库函数在不断更新,需要下载更高版本的库函数。在上面的程序中,直接操作了结构体,所以没有这个问题。
有了精确延时函数,那么使用通用GPIO软件模拟一些通信协议,如IIC、SPI等串行协议,就可以驱动很多硬件设备了,如EEPROM、温湿度传感器、显示屏等等。
*部分资料来源于网络,如有侵权请联系删除