1、此次博客的灵感由来:
最近在短视频平台刷到关于状态机的视频,然后STM32中有一个滴答计时器叫systick,恰好他又有中断功能,那么是不是可以用systick做一个任务状态机,来让任务实现最基本的“挂起态,就绪态,执行态”,从而摆脱平日使用传统delay()函数造成的阻塞问题,提高系统响应效率。
2、什么是状态机
状态机按照我的理解就是,检测某一样东西他当前的状态,可以是按键的按下与抬起,也可以是RTOS中任务函数的等待与执行,又或者是中断函数等待中断信号出现的状态。
3、在stm32平台上实现状态机
由于此次状态机的实现涉及到了RTOS任务调度的基础概念,觉得阅读困难的可以去稍微了解一下关于这方面的知识点。
在平日对systick的使用,大部分都是将他用于定时功能,其实systick的全称是“滴答定时计数器”,而我们用的时候大多都忽略他的本质是个计数器。本次项目我们需要将他配置为定时1ms的计数功能,看到这个1ms是不是想起了RTOS中的心跳时钟,所以结合systick+RTOS任务调度的基本概念,就可以实现状态机。
3.1 systick配置
static uint8_t us = 0;
static uint16_t ms = 0;
void SysTickConfig(void)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
us = SystemCoreClock / 8000000;
ms = (uint16_t )us *1000;
}
void osTickIinit(void)
{
SysTick->LOAD = 1 * ms;
SysTick->VAL = 0x00;
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
}
3.1.1 时钟源选择
默认SysTick_CLKSource_HCLK_Div8即可
3.1.2 主要配置
自动重装载值赋予systick走完1ms所需的值,然后打开计数完成中断,并使能systick。即systick将会每 1ms 触发一次中断,这我喜欢称他为 状态机的心跳时钟。
3.2 状态机配置
#define MAX_TASK 10 //任务最大堆栈
uint32_t sysTickCount = 0;
typedef void (*osTaskHandle)(void* parm);
typedef struct {
char* taskName; //任务函数名字
uint8_t firstPriority; //优先级
uint32_t funcTick; //任务心跳
uint32_t lastFunckTick; //上一次心跳时间
void* parm; //参数
osTaskHandle func; //需要执行函数
}_systemSM;
_systemSM taskStack[MAX_TASK]={0};
//这个函数放在.h文件里面
__inline uint32_t getOsTickHeart()
{
return sysTickCount;
}
uint8_t osTaskCreat(char* name,uint8_t fp, uint32_t delay, osTaskHandle func, void* parm)
{
_systemSM systemSM;
if(fp < MAX_TASK)
{
systemSM.taskName = name;
systemSM.firstPriority = fp;
systemSM.funcTick = delay;
systemSM.func = func;
systemSM.lastFunckTick = getOsTickHeart();
systemSM.parm = parm;
taskStack[fp] = systemSM;
}
return 1;
}
void osTaskPolling(void)
{
static uint32_t tickNow;
tickNow = getOsTickHeart();
for(uint8_t i=0;i<MAX_TASK;i++)
{
if(taskStack[i].taskName != 0) //检测任务函数区块是否有效
{
if(tickNow-taskStack[i].funcTick == taskStack[i].lastFunckTick)//检测当前系统心跳是否大于上一次任务执行心跳+任务延时
{
taskStack[i].lastFunckTick = tickNow;//更新任务执行心跳
taskStack[i].func(taskStack[i].parm);//执行任务
}
}
}
}
void GPIO_Toggle_Bit(void* parm)
{
static uint8_t status;
status =~status;
PCout(13) = status;
}
void printout1(void* parm)
{
STM_LOG(parm,"task in 1");
}
void printout2(void* parm)
{
STM_LOG(parm,"task in 2");
}
int main (void)
{
SysTickConfig();
osTickIinit();
usart_init(115200);
LedConfig();
//清除挂起
SCB->ICSR = SCB_ICSR_PENDSVCLR_Msk;
//将pendSV配置最低优先级
NVIC_SetPriority(PendSV_IRQn,0xFF);
osTaskCreat("led",2,500,GPIO_Toggle_Bit,NULL);
osTaskCreat("p1",0,1000,printout1,"printout1");
osTaskCreat("p2",1,1000,printout2,"printout2");
while(1);
}
void SysTick_Handler(void)
{
sysTickCount+=1;
//检测pendSV是否挂起状态 这很重要
if(!(SCB->ICSR & SCB_ICSR_PENDSVSET_Msk))
SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;
}
void PendSV_Handler(void)
{
osTaskPolling();
}
业务逻辑:通过systick每一秒产生的中断去软件触发pendSV中断,让pendSV中断函数来轮询检测当前是否有任务到达用户设定的延时时长,如果有则执行。当同一时间内有多个任务同时满足延时时长,则按任务初始化时的优先级按顺序执行。
3.2.1 任务初始化
uint8_t osTaskCreat(char* name,uint8_t fp, uint32_t delay, osTaskHandle func, void* parm)
name:任务名字,用于任务区块检测
fp:优先级,同一优先级只能配置一个任务
delay:任务延时,即任务每delay多少ms执行一次
func:任务执行函数
parm:传递参数
3.3 实现效果


3.4 pendSV
可能有些人会好奇,为什么非要通过pendSV中断来实现轮询查表,而不是直接在systick中断里面直接轮询,其实这与stm32的中断优先级有关,在中断向量表中SysTick_Handler的优先级是要高于外设中断的,比如说按键中断,串口中断等等,而systick中断又关乎于整个系统心跳稳定性的函数,一但因为某个函数执行时间>1ms,这对于整个系统来说是灾难性的。而为什么不在main函数while循环中执行,我个人的见解就是,在嵌入式开发中,能用中断尽量用中断,能用回调尽量用回调,什么都往main里面塞,代码执行效率不高。而且我用pendSV轮询的话,while循环中又可以在加一个函数。

4796

被折叠的 条评论
为什么被折叠?



