SysTick是属于Cortex-M内核的一个外设,嵌套在NVIC中,系统定时器是一个24位的递减计数器,每次计数事件位1/SYSCLK,在F429中之前配置的SYSCLK配置的是180MHz,当SysTick的重装载寄存器的值递减到0的时候,系统定时器就会产生一次中断。SysTick属于Cortex-M内核外设,因此只要是CM内核的,都具有这个外设,移植起来就很容易了。
SysTick系统定时器一共有4个寄存器,其中CTRL、LOAD、VAL这三个需要配置,CALIB校准计时器不需要进行配置
寄存器名称 | 寄存器描述 | 读写类型 |
---|---|---|
CTRL | SysTick控制及状态寄存器 | R/W |
LOAD | SysTick重装载数值寄存器 | R/W |
VAL | SysTick当前数值寄存器 | R/W |
CALIB | SysTick校准数值寄存器 | RO |
SysTick以SYSCLK驱动下,递减计数器在每接收到一个时钟信号,就会自减1,当递减计数器数值减到0的时候,就会触发一个中断信号,同时重装载寄存器的值会重新载入递减计数器,以此往复循环。
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
16 | COUNTFLAG | R/W | 0 | 如果在上次读取本寄存器后, SysTick 已经计到 了 0,则该位为 1。 |
2 | CLKSOURCE | R/W | 0 | 时钟源选择位, 0=AHB/8, 1=处理器时钟 AHB |
1 | TICKINT | R/W | 0 | 1=SysTick 倒数计数到 0 时产生 SysTick 异常请 求 , 0= 数 到 0 时无动作 。也 可以通 过读 取 COUNTFLAG 标志位来确定计数器是否递减到 0 |
0 | ENABLE | R/W | 0 | SysTick 定时器的使能位 |
其中第2位可以选择计数器时基来源,可选择为处理器时钟SYSCLK(即我们之前配置的180MHz)或者处理器时钟8分频后的时钟。这里我在看了正点原子的教程,里面提到这里其实是选择分频系数,0为8分频,1为1分频。
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
23:0 | RELOAD | R/W | 0 | 当倒数计数至零时,将被重装载的值 |
重装载数值寄存器是当计数器递减计数到0的时候,触发中断后会将这个寄存器的数值重新加载到计数器中,从而开始新一轮级数,这个寄存器一般不用改,使用默认值即可。
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
23:0 | CURRENT | R/W | 0 | 读取时返回当前倒计数的值,写它则使之清零,同时还会清除在 SysTick 控制及状态寄存器中的 COUNTFLAG 标志 |
系统定时器为24位,如果要获取当前计数器的值,可以访问VAL当前数值寄存器。
这个寄存器翻看了一下官方数据手册,也没看明白,野火的教程中也没有详细讲解,实际使用中也很少使用到,等后期找到相关资料了在补充一下相关知识。
typedef struct
{
__IO uint32_t CTRL; /*!< Offset: 0x000 (R/W) CTRL控制及状态寄存器 */
__IO uint32_t LOAD; /*!< Offset: 0x004 (R/W) LOAD重装载数值寄存器 */
__IO uint32_t VAL; /*!< Offset: 0x008 (R/W) VAL当前数值寄存器 */
__I uint32_t CALIB; /*!< Offset: 0x00C (R/ ) CALIB数值校准计数器 */
} SysTick_Type;
这里看一下关于SysTick配置函数,函数原型在core_cm4.h中
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk) { return (1UL); } /* 确定传入的值不超过重装载寄存器的上限 */
SysTick->LOAD = (uint32_t)(ticks - 1UL); /* 将传入值写入重装载寄存器 */
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* 设置中断优先级 */
SysTick->VAL = 0UL; /* 将当前数值寄存器置零 */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | /* 设置定时器时钟源,我选择的是SYSCLK不分频 */
SysTick_CTRL_TICKINT_Msk | /* 使能计数器 */
SysTick_CTRL_ENABLE_Msk; /* 使能定时器 */
return (0UL);
}
参数:
ticks
:用来设定重装载计数器的值,前面看到重装载计数器为24位寄存器,因此该值不能超过2^24。
这里需要注意,在SysTick_Config
函数中,调用了NVIC_SetPriority
函数用来设置SysTick定时器的优先级,看一下NVIC_SetPriority
__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if((int32_t)IRQn < 0) //判断中断是否位内核中断
{
SCB->SHP[(((uint32_t)(int32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
else //外设中断
{
NVIC->IP[((uint32_t)(int32_t)IRQn)] = (uint8_t)((priority << (8 - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
}
函数体内判断了中断优先级是否为负数,设置内核优先级和设置外设优先级操作的寄存器是不同的,内核的优先级设置的是SCB的SHP寄存器,而外设优先级则是在NVIC的IP寄存器,其中SCB寄存器中就包含SysTick的优先级设置。(注:并不是所有的中断都可以配置寄存器,详细看驱动代码那节)
在SysTick中,优先级配置为1UL << __NVIC_PRIO_BITS) - 1UL
,其中__NVIC_PRIO_BITS
为宏定义,值为4,1左移4位再减1,则为15,那么SysTick的优先级配置为最低(内核中)。
学到这里,就可以开始编程实操了。实操内容为:用SysTick产生1s的定时,让LED以1s的频率进行闪烁。
创建bsp_systick.c和bsp_systick.h文件,并将其所在路径加入编译环境中。
/***********************bsp_systick.c*************************/
#include "bsp_systick.h"
static __IO u32 TimingDelay;
void SysTick_Init(void)
{
/* SystemCoreClock = 180000000,即180M,那么1秒就是一共数了180000000次,那么一次就是1/180000000 s
*1s = 1000000 us,那么1us = 1/1000000 s = (1/180000000)* (1/1000000) = 1/180,也就是1us定时器计数180次,因此可以得出如下
* SystemCoreClock/1000000: 1us 中断一次
* SystemCoreClock/100000: 10us 中断一次
* SystemCoreClock/1000: 1ms 中断一次
*/
if(SysTick_Config(SystemCoreClock/100000))
{
while(1);
}
}
/*
180000000/100000 * 1/180MHz = 10us,机选下来每产生一个中断耗时为1us
因此如果要产生1s的中断,则是在此基础上*100000,所以在调用Delay_us中传参为100000即可延时1s
*/
//延时程序,10us为一个单位
void Delay_us(__IO u32 nTime) //这里我们在调用的时候以1S作为延时,将会传入100000
{
TimingDelay = nTime; //100000将写入TimingDelay中,TimingDelay会在SysTick_Handler中不停的去递减
while(TimingDelay != 0);
}
//获取节拍程序
void TimingDelay_Decrement()
{
/*每产生一次中断,TimingDelay就递减1次,上面配置的SysTick会在10us触
* 发一次中断,因此TimingDelay就需要根据Delay_us传入的值进行递减
*/
if(TimingDelay != 0x00)
{
TimingDelay--;
}
}
/***********************bsp_systick.h*************************/
#ifndef __BSP_SYSTICK_H__
#define __BSP_SYSTICK_H__
#include "stm32f4xx.h"
void SysTick_Init(void);
void Delay_us(__IO u32 nTime);
extern void TimingDelay_Decrement(void); //使用extern修饰,用作外部文件访问
#endif /* __BSP_SYSTICK_H__ */
/***********************stm32f4xx_it.h中对SysTick中断服务程序进行编写*************************/
void SysTick_Handler(void)
{
TimingDelay_Decrement();
}
/***********************mian.c*************************/
#include "stm32f4xx.h"
#include "bsp_led.h"
#include "bsp_exti_key.h"
#include "bsp_systick.h"
int main(void)
{
LED_Config(); //调用LED的GPIO配置函数
SysTick_Init(); //调用SysTick初始化函数
while(1)
{
LED_RED; //红灯亮,宏定义,查看LED操作那节内容
Delay_us(100000); //延时1s
LED_GREEN;
Delay_us(100000);
LED_BLUE;
Delay_us(100000);
}
}
这里我想了一下,针对于SysTick定时器有做了一些优化,将原子哥和火哥的结合了一下,做了如下改动:
/* 对于SysTick_Init函数不做修改,默认初始化时,仍然以10us为计时单位 */
void SysTick_Init(void)
{
if(SysTick_Config(SystemCoreClock/100000))
{
while(1);
}
}
/* 对于Delay_us函数进行修改,调用时,将SysTick的LOAD重载计数器设置为10us */
void Delay_us(__IO u32 uTime)
{
SysTick->LOAD = SystemCoreClock/100000; //重新设置重装计时器
SysTick->VAL = 0x00; //清空计数器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; //开始倒数
TimingDelay = uTime; //将传入时间写入TimingDelay中,TimingDelay会在SysTick_Handler中不停的去递减
while(TimingDelay != 0);
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL = 0X00; //清空计数器
}
/* 增加ms延时,调用时,将SysTick的LOAD重载计数器设置为1ms */
void Delay_ms(__IO u32 mTime)
{
SysTick->LOAD = SystemCoreClock/1000; //重新设置重装计时器
SysTick->VAL = 0x00; //清空计数器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; //开始倒数
TimingDelay = mTime; //将传入时间写入TimingDelay中,TimingDelay会在SysTick_Handler中不停的去递减
while(TimingDelay != 0);
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL = 0X00; //清空计数器
}
//获取节拍程序,不做更改
void TimingDelay_Decrement()
{
if(TimingDelay != 0x00)
{
TimingDelay--;
}
}
/****************************** main.c *****************************************/
#include "stm32f4xx.h"
#include "bsp_led.h"
#include "bsp_exti_key.h"
#include "bsp_systick.h"
int main(void)
{
LED_Config();
EXTI_Key_Config();
SysTick_Init();
while(1)
{
/* 这里交替调用Delay_ms和Delay_us验证效果 */
LED_RED;
Delay_ms(1000);
LED_GREEN;
Delay_us(100000);
LED_BLUE;
Delay_ms(1000);
}
}
1、初始化LED所在的GPIO;
2、配置SysTick;
3、编写中断服务函数SysTick_Handler
;
4、最终呈现效果。
注:这里需要注意的是,在重新设置SysTick的LOAD寄存器时,需要保证设置数值不超过2^24,所以我在上面做测试的时候,想按照毫秒的方式去实现1s的延时,当时认为SystemCoreClock/1000
是作为1s装入SysTick重装载寄存器,所以就想1s=1000ms,那么直接将SystemCoreClock
写入SysTick重装载寄存器不就可以实现了嘛,结果后来测试发现跟预期的不同,在反过来看的时候,才想起来,SysTick的LOAD最大到 2^24 = 16,777,216,,而SystemCoreClock
= 180000000,写入后已经溢出了,所以这里各位一定要加判定的哈。