摘要:在单片机中,一想到定时器可能就会想到通用定时器(TIM2 ~ TIM5 和 TIM9 ~ TIM14)或者高级定时器(TIM1和TIM8)。这些定时器的功能很强大,除了基本的功能就是定时,还可以可以测量输入信号的脉冲宽度,可以生产输出波形。

当然使用起来相对也比较复杂。如果我们的项目只想要定时的功能,使用这些定时器可能就有点不必要了,其实系统定时器SysTick也可以实现软件定时,只不过在裸机中我们大多是只是把他当做延时功能使用。

一、SysTick简介

SysTick—系统定时器是属于CM4内核中的一个外设,内嵌在NVIC中。一般我们叫他系统定时器或者滴答定时器。是一个24bit的向下递减的计数器,计数器每计数一次的时间为1/SYSCLK,当重装载数值寄存器的值递减到 0的时候,系统定时器就产生一次中断,以此循环往复。系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。比如RTOS的心跳就是SysTick产生的。

二、SysTick寄存器

SysTick—系统定时有4个寄存器,简要介绍如下。在使用SysTick产生定时的时候,只需要配置前三个寄存器,最后一个校准寄存器不需要使用。

处理正在生成的sysprep插件要多久(使用SysTick实现多组软件定时)(1)

SysTick—系统定时器是属于 CM4内核中的一个外设,所以在core_cm4.h文件中可以看对它对应结构体的介绍。

处理正在生成的sysprep插件要多久(使用SysTick实现多组软件定时)(2)

三、配置SysTick寄存器

Systick是一个 24 位的递减计数器,我们仅需掌握ARM的CMSIS软件提供的一个函数

SysTick_Config即可,原代码如下:

__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; // 设置系统定时器的时钟源为 AHBCLK=180M // 使能系统定时器中断 // 使能定时器 SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; return (0UL); }

处理正在生成的sysprep插件要多久(使用SysTick实现多组软件定时)(3)

-- SystemCoreClock / 1000 表示定时频率为 1000Hz, 也就是定时周期为 1ms。 -- SystemCoreClock / 500 表示定时频率为 500Hz, 也就是定时周期为 2ms。 -- SystemCoreClock / 2000 表示定时频率为 2000Hz, 也就是定时周期为 500us。 注:SystemCoreClock 是 Stm32F407 的系统主频 168MHz。

用固件库编程的时候我们只需要调用库函数SysTick_Config()即可,形参ticks用来设置重装载寄存器的值,当重装载寄存器的值递减到0的时候产生中断,然后重装载寄存器的值又重新装载往下递减计数,以此循环往复。紧随其后设置好中断优先级,最后配置系统定时器的时钟,使能定时器和定时器中断,这样系统定时器就配置好了,一个库函数搞定。

四、SysTick实现延时功能

实现延时功能就很简单了,调用SysTick_Config函数,将定时器的重装载值传递进去就可以了,然后结合时钟就可以定时中断。

处理正在生成的sysprep插件要多久(使用SysTick实现多组软件定时)(4)

那这个参数到底是怎么设置的呢?

答:SystemCoreClock是固件中定义的系统内核时钟,对于STM32F4xx,一般为 168MHz,不要跟我说你的SystemCoreClock是160MHz。

SysTick定时器的计数器是向下递减计数的,计数一次的时间,当重装载寄存器中的值减到0的时候,产生中断,可知中断一次的时间

=,

处理正在生成的sysprep插件要多久(使用SysTick实现多组软件定时)(5)

3年嵌入式物联网学习资源整理分享:C语言、Linux开发、数据结构;软件开发,STM32单片机、ARM硬件开发、物联网通信开发、综合项目开发教程资料;笔试面试真题。点击下方插件免费领取↓↓↓嵌入式物联网学习资料(头条)

其中 SystemCoreClock 。如果设置为168,那中断一次的时间=。不过 的中断没啥意义,整个程序的重心都花在进出中断上了,根本没有时间处理其他的任务。所以我们设置重装载值为SystemCoreClock / 1000 =168 0000,那中断一次的时间T_INT = 168 0000/168MHz = 1ms。因此也就不难理解:

SystemCoreClock / 1000 表示定时频率为 1000Hz, 也就是定时周期为 1ms SystemCoreClock / 500 表示定时频率为 500Hz, 也就是定时周期为 2ms SystemCoreClock / 2000 表示定时频率为 2000Hz, 也就是定时周期为 500us

对于常规的应用,我们一般取定时周期1ms。对于低速CPU或者低功耗应用,可以设置定时周期为 10ms。

然后定时周期设置好了以后,等到时间到了,就会跳转到SysTick定时器中断服务函数中,这个函数在stm32fxxx.it.c文件中。如果你想改变这里面的代码,或者在别的地方重写,就要注释掉这个函数。

void SysTick_Handler(void) { TimingDelay_Decrement(); }

五、SysTick实现多组软件定时

前面是铺垫,讲的是SysTick的基础知识。下面才是重点,既然是一个定时器,我们就不能简单是使用他的延时功能。在单片机中,一想到定时器可能就会想到通用定时器(TIM2 ~ TIM5 和 TIM9 ~TIM14)或者高级定时器(TIM1 和 TIM8)。这些定时器的功能很强大,除了基本的功能就是定时,还可以可以测量输入信号的脉冲宽度,可以生产输出波形。当然使用起来也比较麻烦,如果我们的项目只想要定时的功能,使用这些定时器可能就有点不必要了,其实系统定时器SysTick也可以实现软件定时,只不过在裸机中我们大多是只是把他当做延时功能使用。

既然要实现定时功能,我们肯定需要定时不同的时间,比如定时500ms,LED灯翻转一次。定时200ms,蜂鸣器响一次等等。所以为了实现多组延时我们就需要一个结构体。

1、定时器结构体

/* 定时器结构体,成员变量必须是 volatile, 否则C编译器优化时可能有问题 */ typedef struct { volatile uint8_t Mode; /* 计数器模式,1次性 */ volatile uint8_t Flag; /* 定时到达标志 */ volatile uint32_t Count; /* 计数器 */ volatile uint32_t PreLoad; /* 计数器预装值 */ }SOFT_TMR;

然后定义一个结构体数组变量,因为是多组软件定时。

#define TMR_COUNT 4 /* 软件定时器的个数 (定时器ID范围 0 - 3) */ /* 定于软件定时器结构体变量 */ static SOFT_TMR s_tTmr[TMR_COUNT];

在此定义若干个软件定时器全局变量。这里必须增加__IO即volatile,因为这个变量在中断和主程序中同时被访问,有可能造成编译器错误优化。TMR_COUNT是定时器的个数,你可以设置为其他值,实现多个定时器。

2、定时器初始化

定时器初始化主要是清空结构体变量的值,然后只需要调用SysTick_Config();函数即可,这在前面已经详细的介绍过了。

void Soft_TimerInit(void) { uint8_t i; /* 清零所有的软件定时器 */ for (i = 0; i < TMR_COUNT; i ) { s_tTmr[i].Count = 0; s_tTmr[i].PreLoad = 0; s_tTmr[i].Flag = 0; s_tTmr[i].Mode = TMR_ONCE_MODE; /* 缺省是1次性工作模式 */ } SysTick_Config(SystemCoreClock / 1000);/*SystemCoreClock / 1000是重装载寄存器的值LOAD*/ }

3、启动定时器

这个函数主要给定时器赋重装载值。结构体变量赋值前后做了开关中断操作,因为此结构体变量在滴答定时器中断里面也要调用,防止变量赋值出问题。重装载值赋值完成了程序就可以周期性的进行定时中断了,也就是到定时器中断服务函数中执行相应代码了。

void StartTimer(uint8_t _id, uint32_t _period) { DISABLE_INT(); /* 关中断 */ s_tTmr[_id].Count = _period; /* 实时计数器初值 */ s_tTmr[_id].PreLoad = _period; /* 计数器自动重装值,仅自动模式起作用 */ s_tTmr[_id].Flag = 0; /* 定时时间到标志 */ s_tTmr[_id].Mode = TMR_ONCE_MODE; /* 1次性工作模式 */ ENABLE_INT(); /* 开中断 */ }

void SysTick_Handler(void) { SysTick_ISR(); /* 滴答定时中断服务程序 */ }

4、定时器中断服务函数

比如我们设置定时器的定时周期为1ms,那么每隔1ms程序就会进入SysTick_Handler中一次,在SysTick_Handler函数中调用SysTick_ISR函数来对软件定时器的计数器进行减一操作,因为这里设置了TMR_COUNT组软件定时,就需要对每一组的count进行减一操作,如果定时器变量减到1则设置定时器到达标志,表示定时结束。

void SysTick_ISR(void) { uint8_t i; /* 每隔1ms,对软件定时器的计数器进行减一操作 */ for (i = 0; i < TMR_COUNT; i ) { SoftTimerDec(&s_tTmr[i]); } } static void SoftTimerDec(SOFT_TMR *_tmr) { if (_tmr->Count > 0) { /* 如果定时器变量减到1则设置定时器到达标志 */ if (--_tmr->Count == 0) { _tmr->Flag = 1; /* Flag = 1 在检查定时器时间中会用到 */ } } }

5、检测定时器是否超时

前面已经打开了软件定时器,那么在程序中就需要来检测定时时间是否到达。比如我定时了500ms,500ms时间到了我要干什么,就需要有一个函数来检测定时器是否超时,如果没有超时无操作,如果时间已经到Flag就会等于1,需要重新将其清0。简单来说这个函数就是清0标志位的。

uint8_t CheckTimer(uint8_t _id) { /*判断时间到标志值 Flag 是否置位,如果置位表示时间已经到,如果为 0,表示时间还没有到*/ if (s_tTmr[_id].Flag == 1) { s_tTmr[_id].Flag = 0; return 1; } else { return 0; } }

至此使用SysTick滴答定时器做软件定时器就已经完成,下面看一下如何使用。

六、实验例程

int main(void) { HAL_Init(); //初始化HAL库 Stm32_Clock_Init();//初始化系统时钟 Soft_TimerInit(); //初始化软件定时器 Bsp_Init();//初始化底层硬件 StartAutoTimer(0, 1000); /* 启动1个1000ms的自动重装的定时器 */ StartAutoTimer(1, 500); /* 启动1个500ms的自动重装的定时器 */ StartAutoTimer(2, 200); /* 启动1个200ms的自动重装的定时器 */ StartAutoTimer(3, 100); /* 启动1个100ms的自动重装的定时器 */ while(1) { if (CheckTimer(0)) { /* 每隔1000ms 进来一次. */ ......执行相应代码....... } if (CheckTimer(1)) { /* 每隔500ms 进来一次. */ ......执行相应代码....... } if (CheckTimer(2)) { /* 每隔200ms 进来一次.*/ ......执行相应代码....... } if (CheckTimer(3)) { /* 每隔100ms 进来一次.*/

原文作者:果果小师弟

原文标题:使用SysTick实现多组软件定时,你知道吗?

原文链接:https://mp.weixin.qq.com/s/OKWzOC66tedQQSXqvKLO5g

,