基于SysTick Timer的非阻塞延时设计
- 什么是SysTick Timer
- SysTick Timer如何应用
- 阻塞和非阻塞的定义
- 阻塞和非阻塞延时的应用场景
- 可注册非阻塞延时的代码实现
SysTick定时器
SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。以前大多操作系统需要一个硬件定时器来产生操作系统需要的滴答中断,作为整个系统的时基。例如,为多个任务许以不同数目的时间片,确保没有一个任务能霸占系统;或者把每个定时器周期的某个时间范围赐予特定的任务等,还有操作系统提供的各种定时功能,都与这个滴答定时器有关。因此,需要一个定时器来产生周期性的中断,而且最好还让用户程序不能随意访问它的寄存器,以维持操作系统“心跳”的节律。
Cortex‐M3处理器内部包含了一个简单的定时器。因为所有的CM3芯片都带有这个定时器,软件在不同 CM3器件间的移植工作得以化简。该定时器的时钟源可以是内部时钟(FCLK,CM3上的自由运行时钟),或者是外部时钟( CM3处理器上的STCLK信号)。不过,STCLK的具体来源则由芯片设计者决定,因此不同产品之间的时钟频率可能会大不相同,你需要检视芯片的器件手册来决定选择什么作为时钟源。SysTick定时器能产生中断,CM3为它专门开出一个异常类型,并且在向量表中有它的一席之地。它使操作系统和其它系统软件在CM3器件间的移植变得简单多了,因为在所有CM3产品间对其处理都是相同的(摘自Cortex‐M3权威指南中文手册)。
SysTick定时器应用
- 维持操作系统“心跳”节拍
- 为裸机提供精准延时
先来了解下SysTick的配置,我们需要从其有关的寄存器来入手:
表1 控制及状态寄存器
表2 重装载数值寄存器
表3 当前数值寄存器
表4 校准数值寄存器
SysTick Timer主要有以上的几个寄存器,其实在常见的应用中我们只需要用到控制及状态寄存器和重装载数值寄存器即可,首先需要选择时钟配置,这是第一步也是最关键的一步,清空计数器里面的值,然后写入重装载值来设置触发中断的时间,最后使能异常中断配置就算完成了,来看看代码实现:
/********************************************************************
Program Function: Systick timer delay init function
Program Version: 1.0
Create Date: 2020-10-26
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/
void DelayInit(void){
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //select the external clock
fac_us = SystemCoreClock / 8000000; //system clock config
NVIC_SetPriority(SysTick_IRQn, (1<<__nvic_prio_bits>1); //interrupt priority config
fac_ms = (u16)fac_us * 1000;
BaseSysTickConfg(); //clock base config
SysTickRegTabInit(); //register table init
}/********************************************************************
Program Function: BaseSysTick config function
Program Version: 1.0
Create Date: 2020-10-27
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/static void BaseSysTickConfg(void){
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; //enable systick interrupt
SysTick->LOAD = (u32)fac_ms * BASE_CLOCK; //load the time value(SysTick->LOAD is 24bit)
SysTick->VAL = 0x00; //clear the count
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //start the count down
}
这里需要注意的是SysTick也是可以配置中断优先级的,因为它和NVIC是绑定的,其中断类型为内核中断,优先级默认为是中断分组中最低的,但是如果有其他中断同样设置为中断分组中最低的一个优先级分组,此时SysTick会打断该中断优先运行,可以通过上面的NVIC_SetPriority()函数来进行设置,下面来看看中断处理函数:
/********************************************************************
Program Function: Set the SysTick flag function
Program Version: 1.0
Create Date: 2020-10-26
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/
static void SetSysTickFlg(void){
u8 i;
for(i = 0; i {
Tab.TimeFlag[i] = 0x01;
}
}
/********************************************************************
Program Function: Systick interrupt handle function
Program Version: 1.0
Create Date: 2020-10-27
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/
void SysTick_Handler(void){
SetSysTickFlg();
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //stop the count down
SysTick->VAL = 0x00; //clear the count
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; //disable the systick interrupt
}
这里其实是有个坑儿的,不知道有没有踩过坑的小伙伴,中断函数名是固定的,需要和中断向量表中的名字保持一致,否则就无法触发中断,其实也很好理解,函数名对应了一个地址,在中断向量表中给此中断配置了一个特定的地址,如果你编写的中断函数名却是另外的一个函数名,那就无法完成索引了,中断向量表代码如下:
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; Heap Configuration
; Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>;
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1 & ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
阻塞和非阻塞定义
阻塞:当Task1为阻塞调用时,其他的Task都将被挂起,只能等待Task1执行完成后(不管当前Task1运行条件是否满足),其他的满足运行条件Task才可以从挂起态转换成运行态,简而言之,阻塞就是只能等CPU或者MCU把当前的事件处理完,才能释放出资源来处理下一个事件。
非阻塞:当Task1的运行条件还未满足时,CPU或者MCU此时不需要死等,依然可以继续去处理其他的Task。
阻塞和非阻塞延时的应用场景(裸机)
阻塞延时应用场景:
①外设芯片时序,比如485芯片CS引脚高低电平设置;
②数据接收,比如Modbus通讯中,需先把数据读取完才可以进行写操作;
③单任务运行,系统实时性不高,比如只有一个LED在跑,阻塞延时对其他的也不会有影响。
非阻塞延时应用场景:
①报警装置,例如LED呼吸灯、Beep这些;
②多任务运行,系统实时性较高的场景。
可注册非阻塞延时代码实现
其实代码也比较简单,利用了数据结构来实现了可注册的功能,最大可注册数为64,就裸机而言任务数一般都是比较少的,所以最大可注册数也是可以满足一般的需求的,提供给用户的只有两个接口,一个注册任务的接口和一个实现非阻塞延时功能的接口,其他的都封装在模块内了,一般是不需要更改的,好了来看看源码:
/****************************SysTickTimer head file********************************/
#ifndef __DELAY_H
#define __DELAY_H
#include "sys.h"
#define SysTickTskMax 64
#define BASE_CLOCK 10
#define NULL 0
typedef struct
{
u8 TimeCunt[SysTickTskMax];
u8 TimeFlag[SysTickTskMax];
u8 TaskFlag[SysTickTskMax];
}DelayRegTable;
void DelayInit(void);
u8 SysTickRegTask(void);
void SysTick_Handler(void);
void SysTickHandleFunc(void (*CallBack_Func)(void), u8 TskName, u8 DelayTime);
#endif
/**************************@Copyright 2020 reserved by Huang Cheng**************************/
/******************************SysTickTimer source file*********************************/
#include "delay.h"
/******************************Data definition array***********************************/
static u8 fac_us=0;
static u16 fac_ms=0;
static u8 TaskIndex = 0;
DelayRegTable Tab;
/******************************End of Data definition array***************************/
/******************************Function declaration array*****************************/
static void BaseSysTickConfg(void);
static void SysTickRegTabInit(void);
static void SetSysTickFlg(void);
static void DelayHandle(u8 DelayTime, u8 TskName);
static void ClrSysTickTaskFlg(u8 TskType);
static u8 GetSysTickTaskFlg(u8 TskType);
/******************************End of Function declaration array**********************/
/********************************************************************
Program Function: Systick timer delay init function
Program Version: 1.0
Create Date: 2020-10-26
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/
void DelayInit(void){
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //select the external clock
fac_us = SystemCoreClock / 8000000; //system clock config
NVIC_SetPriority(SysTick_IRQn, (1<<__nvic_prio_bits>1); //interrupt priority config
fac_ms = (u16)fac_us * 1000;
BaseSysTickConfg(); //clock base config
SysTickRegTabInit(); //register table init
}/********************************************************************
Program Function: System tick RegTable data init function
Program Version: 1.0
Create Date: 2020-10-26
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/static void SysTickRegTabInit(void){
u8 i;
u8 *pData = (u8*)	
TaskIndex = 0;for(i = 0; i sizeof(DelayRegTable)/sizeof(u8); i++)
{
pData[i] = 0;
}
}/********************************************************************
Program Function: Task delay register function
Program Version: 1.0
Create Date: 2020-10-26
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/u8 SysTickRegTask(void){
u8 Rtn = 0xFF;
TaskIndex++;if(TaskIndex > SysTickTskMax)
{
TaskIndex = 0;
Rtn = 0xFF; //Register failed
}else
{
Rtn = TaskIndex - 1; //Register success
}return Rtn;
}/********************************************************************
Program Function: Get the SysTick task flag function
Program Version: 1.0
Create Date: 2020-10-26
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/static u8 GetSysTickTaskFlg(u8 TskType){return Tab.TaskFlag[TskType];
}/********************************************************************
Program Function: Clear the SysTick task flag function
Program Version: 1.0
Create Date: 2020-10-26
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/static void ClrSysTickTaskFlg(u8 TskType){
Tab.TaskFlag[TskType] = 0;
}/********************************************************************
Program Function: Set the SysTick flag function
Program Version: 1.0
Create Date: 2020-10-26
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/static void SetSysTickFlg(void){
u8 i;for(i = 0; i {
Tab.TimeFlag[i] = 0x01;
}
}/********************************************************************
Program Function: SysTick handle callback function
Program Version: 1.0
Create Date: 2020-10-27
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/void SysTickHandleFunc(void (*CallBack_Func)(void), u8 TskName, u8 DelayTime){
DelayHandle(DelayTime, TskName);if(NULL != CallBack_Func)
{if(0x01 == (GetSysTickTaskFlg(TskName)))
{
(*CallBack_Func)();
ClrSysTickTaskFlg(TskName);
}else
{//do nothing
}
}else
{return;
}
} /********************************************************************
Program Function: Delay handle function
Program Version: 1.0
Create Date: 2020-10-27
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/static void DelayHandle(u8 DelayTime, u8 TskName){if (0x01 == Tab.TimeFlag[TskName])
{
Tab.TimeCunt[TskName]++;if (Tab.TimeCunt[TskName] >= DelayTime) //time = DelayTime * 10ms;
{
Tab.TaskFlag[TskName] = 0x01;
Tab.TimeCunt[TskName] = 0x00;
}else
{//do nothing;
}
BaseSysTickConfg();
Tab.TimeFlag[TskName] = 0x00;
}else
{//do nothing;
}
}/********************************************************************
Program Function: BaseSysTick config function
Program Version: 1.0
Create Date: 2020-10-27
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/static void BaseSysTickConfg(void)
{
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; //enable systick interrupt
SysTick->LOAD = (u32)fac_ms * BASE_CLOCK; //load the time value(SysTick->LOAD is 24bit)
SysTick->VAL = 0x00; //clear the count
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //start the count down
}/********************************************************************
Program Function: Systick interrupt handle function
Program Version: 1.0
Create Date: 2020-10-27
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/void SysTick_Handler(void){
SetSysTickFlg();
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //stop the count down
SysTick->VAL = 0x00; //clear the count
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; //disable the systick interrupt
}/**************************@Copyright 2020 reserved by Huang Cheng**************************/
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "stm32f10x.h"
/********************************************************************
Program Function: system init
Program Version: 1.0
Create Date: 2020-10-27
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/
void SystmInit(void){
//edit code;
TestLedInit();
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
}
/********************************************************************
Program Function: main function
Program Version: 1.0
Create Date: 2020-10-27
Author : Huang Cheng
Modify Instrodtn: None
********************************************************************/
int main(void){
u8 Task1 = 0;
u8 Task2 = 0;
SystmInit();
Task1 = SysTickRegTask();
Task2 = SysTickRegTask();
while(1)
{
//example code;
SysTickHandleFunc(Led0CallBack, Task1, 10); //10*10ms
SysTickHandleFunc(Led1CallBack, Task2, 50); //50*10ms
}
//return 0;
}
上面的task注册其实也可以封装成一个函数用于统一注册,源码到时候我会传到码云gitee上去,感兴趣的可以自行下载!欢迎一起讨论和优化!