嵌入式常用算法:时间触发下的嵌入式软件设计模式

时间:2021-05-05 23:34:19

在嵌入式软件开发当中,常常第一步就是设计整个系统的架构。有基于OS的,也有裸机的。我们先拿裸机说事儿,我想很多人在做单片机的裸机开发(甚至基于OS)时脑海里根本没有设计架构的想法,从来都是while(1)循环到底,能写点状态机已经算是基础扎实的人物了。那么问题来了,while(1)是什么?在嵌入式设计模式里是怎么给while(1)定义的。《C嵌入式编程设计模式》一书中将while(1)称之为“超循环模式”,也就是嵌入式的入门傻瓜模式。在此模式下即使有前台监听也势必会导致系统实时性傻逼,从硬件上分析:while(1)的高CPU占用率也使得功耗大大提高。当然存在即合理,超循环模式在安全性和可靠性上是非常占优势的。结构简单也易于理解,这对于安全性能要求较高的系统上还是非常具有吸引力的。

说了这么多不上代码真没意思,具体的看书吧,强烈推荐《C嵌入式编程设计模式》,在此也做一个小广告:朋友埋头2年编写的关于嵌入式设计架构书籍(基于飞思卡尔系列MCU)也即将在明年年初由清华大学出版社出版,爱好飞思卡尔的同学可以关注一下哦。

/**
******************************************************************************
* @file cx_sch.c
* @author CX
* @version V1.0.0.1
* @date 2016-7-26
* @brief 1.0.0.1
修改任务数据结构,增加一级抢占任务
加入RunNum预编译选项,限制是否开启任务执行次数
1.0.0.0
完成基础架构搭建
******************************************************************************
* @attention
*
* 项目 :None
* 官网 : None
* 实验室 :None
******************************************************************************
*/


#include "cx_sch.h"



sTask_Typedef SCH_tasks_G[SCH_MAX_TASKS];



/**
* @brief 增加任务
* @param pFunction 任务指针, Delay 延迟时标, Peroid 任务执行周期, RunNum 任务执行次数, ModeEnum 任务模式
* @retval 任务队列索引
* @notice None
*/
uint8_t SCH_Add_Task(void(*pFunction)(), uint32_t Delay, uint32_t Peroid, uint8_t RunNum, TaskMode_Enum ModeEnum)
{
uint8_t Index = 0;
while((SCH_tasks_G[Index].pTask != 0) && (Index < SCH_MAX_TASKS))
{
Index++;
}
if(Index == SCH_MAX_TASKS)
{
return SCH_MAX_TASKS;
}
SCH_tasks_G[Index].pTask = pFunction;
SCH_tasks_G[Index].Delay = Delay;
SCH_tasks_G[Index].Peroid = Peroid;
SCH_tasks_G[Index].RunMe = 0;
#if RunNum_ON
SCH_tasks_G[Index].RunNum = RunNum;
#endif
SCH_tasks_G[Index].ModeEnum = ModeEnum;
return Index;
}



/**
* @brief 删除任务
* @param Index, 任务队列索引
* @retval None
* @notice None
*/
void SCH_Delete_Tasks(uint8_t Index)
{
SCH_tasks_G[Index].pTask = 0;
SCH_tasks_G[Index].Delay = 0;
SCH_tasks_G[Index].Peroid = 0;
SCH_tasks_G[Index].RunMe = 0;
#if RunNum_ON
SCH_tasks_G[Index].RunNum = 0;
#endif
}



/**
* @brief 更新任务
* @param None
* @retval None
* @notice 需要心跳支持, 抢占任务执行时长必须小于任务调度器的时标
*/
void SCH_Update_Tasks(void)
{
u8 Index;
for(Index = 0;Index < SCH_MAX_TASKS;Index++)
{
if(SCH_tasks_G[Index].pTask)
{
if(SCH_tasks_G[Index].Delay == 0)
{
switch(SCH_tasks_G[Index].ModeEnum)
{
case SEIZ_Enum: //抢占任务立即运行
SCH_tasks_G[Index].pTask();
if(SCH_tasks_G[Index].RunMe > 0)
{
SCH_tasks_G[Index].RunMe--;
#if RunNum_ON
if(SCH_tasks_G[Index].RunNum > 0)
{
SCH_tasks_G[Index].RunNum--;
if(SCH_tasks_G[Index].RunNum == 0)
{
SCH_Delete_Tasks(Index);
}
}
#endif
}
break;
case COOP_Enum:
SCH_tasks_G[Index].RunMe++;
break;
default:break;
}
if(SCH_tasks_G[Index].Peroid)
{
SCH_tasks_G[Index].Delay = SCH_tasks_G[Index].Peroid;
}
}
else
{
SCH_tasks_G[Index].Delay--;
}
}
}
}



/**
* @brief 任务调度器
* @param None
* @retval None
* @notice None
*/
void SCH_Dispatch_Tasks(void)
{
u8 Index;
while(1)
{
for(Index = 0;Index < SCH_MAX_TASKS;Index++)
{
if(SCH_tasks_G[Index].RunMe > 0)
{
SCH_tasks_G[Index].pTask();
SCH_tasks_G[Index].RunMe--;
#if RunNum_ON
if(SCH_tasks_G[Index].RunNum > 0)
{
SCH_tasks_G[Index].RunNum--;
if(SCH_tasks_G[Index].RunNum == 0)
{
SCH_Delete_Tasks(Index);
}
}
#endif
if(SCH_tasks_G[Index].Peroid == 0)
{
SCH_Delete_Tasks(Index);
}
}
}
}
}



/**
* @brief 任务初始化
* @param None
* @retval None
* @notice None
*/
void SCH_Init(void)
{
TIM2Base_Config(10);
}


/**
* @brief 启动任务调度
* @param None
* @retval None
* @notice None
*/
void SCH_Start(void)
{
TIM_Cmd(TIM2, ENABLE);
}
#ifndef __CX_SCH_H#define __CX_SCH_H#include "stm32f10x.h"#include "cx_timbase.h"#define      RunNum_ON          0typedef enum{	COOP_Enum = 0x0,	SEIZ_Enum = 0x1}TaskMode_Enum;typedef struct{	void (*pTask)(void); 	uint32_t Delay;			       	uint32_t Peroid;		     	uint8_t RunMe;		               //标记任务就绪#if RunNum_ON	uint8_t RunNum;                        //指定任务执行次数,执行完自动销毁#endif	TaskMode_Enum ModeEnum;}sTask_Typedef;#define      SCH_MAX_TASKS         10void SCH_Init(void);void SCH_Start(void);uint8_t SCH_Add_Task(void(*pFunction)(), uint32_t Delay, uint32_t Peroid, uint8_t RunNum, TaskMode_Enum ModeEnum);void SCH_Update_Tasks(void);void SCH_Dispatch_Tasks(void);void SCH_Delete_Tasks(uint8_t Index);#endif

老规矩,不分析代码了,代码加起来也没几行,数据结构也很简单,静下心来一定看得懂的。这段代码在51单片机(12MHZ,12T)上实际测试在1ms的时标下,满载12个任务下的CPU使用率仅为89%,依然有11%的空闲。如果使用国产STC15系列的1T单片机情况估计刚好相反(没实际测试过,仅仅猜测)。

在看懂代码后,也许有人要问我循环调度下响应事件就迟滞了,实时性就降低了呀!那么针对这样的疑问我想说以下几点:

1:迟滞是相对的,比起超循环系统的while(1)+中断标志位+阻塞延时,轮询调度的响应速度足够快了。

2:循环调度一般情况下还是添加短任务的比较好,长任务忽略的好,至于为什么,自己去琢磨吧。

3:这段代码不是在写操作系统,只能作为嵌入式系统代码中的一部分,不要吹毛求疵,当你能1s读完红楼梦时再来喷吧。

4:任务抖动是无法避免的,除非你是抢占式核,可以抢占CPU使用权。本人汇编菜鸟,写不出来。

5:说的很明确,本代码仅限于裸机开发。

感恩,感谢...   ...

使用过程中出现任何BUG请联系我本人QQ:951253606,说明bug现象以及重现过程。

寻求MCU产品开发请联系我本人QQ:951253606。