前言
- 博文基于STM32F103ZET6和标准固件库V3.5.0在MDK5环境下开发;
- 本博文讨论的是芯片不运行操作系统的情况下完成1s的延时功能;
- 如有不足之处还请多多指教;
SysTick—系统滴答定时器是什么?
是一个24位的硬件倒计数定时器;
SysTick的功能是什么?(分两种情况)
- 芯片运行操作系统(UCOS)情况下做:为操作系统(例如UCOS)提供硬件上的定时中断(滴答中断),作为整个系统的时基,为多个任务分配不同的时间片,确保不会出现一个任务霸占系统的情况;或者把每个定时器周期的某个时间范围赐予特定的任务等;还有操作系统提供的各种定时功能;
- 不运行操作系统,单纯做定时器:提供精准的定时功能;
SysTick的特点
- 和以往的外设定时器不同,SysTick这个定时器以及相关寄存器在CM3内核里。《STM32使用手册》里对并没有涉及SysTick的内容,只能去《Cortex-M3权威指南》看(PDF的133页);寄存的可寻址,且寄存器被定义在程序包中的core_m3.c 中,涉及相关的编程时一定要注意;
- 可以工作在芯片的睡眠状态下;
- SysTick被捆绑在NVIC(中断向量控制器)中,可以产生异常(异常号:15),这是在操作系统下为系统提供时间基准的必要条件;
- 每个CM3内核都含有一个SysTick,这样方便程序移植;
- SysTick时钟源的选择可以来自于外部,也可以来自于内部;使用来自外部的时钟源的时候要根据具体芯片生产厂商的手册;(本博文采用外部时钟源)
SysTick的四个相关寄存器(图片摘取《Cortex-M3权威指南》)
CTRL控制及状态寄存器
描述中解释的都很清楚,这里强调两个重要的点:
- 位[16]:只能读,不能写,算是一个状态位;如果计数器达到0,则读入为1;当读取或清除当前计数器值时,将自动清除为0;
- 地址:0xE000E010 ,这是本寄存器地址,一会编程的时候要用到;
LOAD重装载数值寄存器
VAL当前值寄存器
CALIB校准数值寄存器(这个寄存器在本博客中用不到)
SysTick时钟源的选择(两个)
在红框处,其实我是有疑问的:红框处提供的有3个时钟源HCLK,HCLK/8,FCLK;从名字上来看,FCLK似乎就是上面提到的SysTick的CTRL寄存器中时钟源可选择的内核时钟;但是从标准库函数提供的函数名上来看,提供内核时钟的是HCLK时钟源,提供外部时钟的是HCLK/8时钟源;看如下代码:
/*此段为我自己写的,为初始化SysTick*/
void delay_Init(void)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //设置SysTick时钟源为外部时钟源--HCLK/8;
fac_us=SystemCoreClock/8000000; //系统内核时钟,即CM3的时钟;
fac_ms=(u16)fac_us*1000;
}
/*此段代码为库文件 misc.c中关于配置SysTick时钟源的函数*/
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
/* Check the parameters */
assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
{
SysTick->CTRL |= SysTick_CLKSource_HCLK; //0x00000004 选择时钟源为内部时钟源
}
else
{
SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8; //0xFFFFFFFB 选择时钟源为外部时钟源
}
}
/*此段代码是misc.h文件中的关于时钟源的选项*/
#define SysTick_CLKSource_HCLK_Div8 ((uint32_t)0xFFFFFFFB)
#define SysTick_CLKSource_HCLK ((uint32_t)0x00000004)
#define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
((SOURCE) == SysTick_CLKSource_HCLK_Div8))
从以上三段代码来看: SysTick_CLKSource_HCLK_Div8 和 SysTick_CLKSource_HCLK是配置CTRL时钟源选择的值,由于这两个符号里都包含HCLK,所以我认为,SysTick的时钟源就来自于上面时钟框图中的HCLK和HCLK/8(先这么理解着,即使理解的不对,并不影响这个程序的进行,因为HCLK和FCLK的时钟频率是一样的,只是名字不同而已)
好了,关系都捋清了,只差代码了;
SysTick的配置和1s延时
配置步骤:(这一个函数的执行从头到尾就是1s)
- 配置SysTick时钟源;
- 获取SysTick计数器递减周期(每个周期计数器减一),这个需要计算;
- 根据上面的递减周期配置重载寄存器LOAD;
- 清零当前值寄存器VAL;
- 使能定时器(使能CTRL寄存器使能位);
- 等待VAL递减到0,从而CTRL寄存器的计数标志位COUNTFLAG被置1;
- 延时完成,函数的最后关闭定时器(关闭CTRL寄存器使能位);
- 函数结束;
所以综合叙述一下就是,先配置时钟源,有了时钟源就能确定当前值寄存器VAL递减周期,然后根据递减周期与1s之间的算数关系配置重载寄存器,然后使能寄存器后只需检测CTRL寄存器的计数标志位即可,
1s延时代码:
#include <stm32f10x.h>
u32 fac_us;
u32 fac_ms;
void delay_Init(void)
{ /*
配置时钟源为外部HCLK/8,经标准库初始化后的HCLK = 72MHz,
即SysTick计数器的时钟源频率为72MHz/8 = 9MHz,即每9M个方波就是1s(秒基准),
每9000个脉冲1ms(毫秒基准),每9个秒冲就是1us(微妙基准);
所以我们可以认为当前值寄存器VAL的递减周期就是:每(1/9)us就减1;即每经过1us,经过了9个方波;
*/
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SystemCoreClock/8000000; //最终fac_us=9;
fac_ms=fac_us*1000; //fac_ms = 9000;
}
void delay_us(u32 nus)
{ //注意这里的SysTick变量是结构体指针变量;
SysTick->LOAD=nus*fac_us; //将要定时的时间(nus*fac_us)装载重载寄存器;
SysTick->VAL =0x00; //清零当前值寄存器;
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //使能控制寄存器(CTRL),开启定时器;
while((SysTick->CTRL) >> 16 != 1); //等待被重新转载后的当前值寄存器递减到0,从而使得控制寄存器的计数标志位被置1;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Pos; //关闭计数器(注意运算符是|=,这一点一定要特别注意,因为此处配置的是CTRL寄存器,小心把其他位也给配置了,下面的程序也是一样)
}
void delay_ms(u32 nms) //含义和上面一样; 当nms=1000是即可获得1s延时;
{
SysTick->LOAD=nms*fac_ms;
SysTick->VAL =0x00;
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;
while((SysTick->CTRL) >> 16 != 1);
SysTick->CTRL |= SysTick_CTRL_ENABLE_Pos;
}