裸机单片机软件架构选择指南

单片机通常分为三种工作模式,分别是

1、前后台顺序执行法                        2、操作系统                         3、时间片轮询法              

1、前后台顺序执行法

        利用单片机的中断进行前后台切换,然后进行任务顺序执行,但其实在很多时候都是处于一种浪费资源的使用,因为大部分时候都要去查看事件是否发生,并且其实时性低,由于每个函数或多或少存在毫秒级别的延时,即使是1ms,也会造成其他函数间隔执行时间的不同,虽然可通过定时器中断的方式,但是前提是中断执行函数花的时间必须短。当程序逻辑复杂度提升时,会导致后来维护人员的大脑混乱,很难理清楚该程序的运行状态。

其也就是大轮训进行执行while里面的函数

2、操作系统

        嵌入式操作系统EOS(Embedded OperatingSystem)是一种用途广泛的系统软件,过去它主要应用于工业控制和国防系统领域,而对于单片机来说,比较常用的有UCOS、FreeRTOS、RT-Thread Nano和RTX 等多种抢占式操作系统(其他如Linux等操作系统不适用于单片机)

        操作系统和“时间片轮询法”,在任务执行方面来说,操作系统对每个任务的耗时没有过多的要求,需要通过设置每个任务的优先级,在高优先级的任务就绪时,会抢占低优先级的任务;操作系统相对复杂,因此这里没有详细介绍了。

3、时间片轮询法 

      时间片轮询法是介于前后台顺序执行法和操作系统之间的一种程序架构设计方案。任务函数无需时刻执行,存在间隔时间(比如按键,一般情况下,都需要软件防抖,初学者的做法通常是延时10ms左右再去判断,但10ms极大浪费了CPU的资源,在这段时间内CPU完全可以处理很多其他事情)。

        该设计方案需要使用一个定时器,一般情况下定时1ms即可(定时时间可随意定,但中断过于频繁效率就低,中断太长,实时性差),因此需要考虑到每个任务函数的执行时间,建议不能超过1ms(能通过程序优化缩短执行时间则最好优化,如果不能优化的,则必须保证该任务的执行周期必须远大于任务所执行的耗时时间),同时要求主循环或任务函数中不能存在毫秒级别的延时。

时间片轮询法的实现(指针方式)

首先定义一个结构体,用于存储一个任务的所有信息,如下所示:

typedef struct
{
	uint8_t  u8_runflag;		/*程序是否运行标志    TASK_OFF_RUN:不运行   TASK_ON_RUN:运行*/
	uint16_t u16_timer;			/*计时器*/
	uint16_t u16_itvTime;		/*运行间隔时间,也就是多久运行一次*/
	void (*p_TaskHook)(void);	/*任务*/
}Task_InfoType;

然后创建一个具有绑定关系的任务数组:

static Task_InfoType Task_Info[TASK_MAX] = {
	{TASK_OFF_RUN,TASK_200ms,TASK_200ms,logic_task},//逻辑任务
	{TASK_OFF_RUN,TASK_1000ms,TASK_1000ms,led_task}	//LED任务
};

        从上图中可以看出,这里创建了两个任务,一个逻辑任务200ms执行一次,另外一个则是1000ms执行一次的LED任务,根据任务的情况,可以自己添加自己需要的。

        有了关系之后,那么就需要让这个小系统跑起来了,下面两个函数分别是函数标志位修改函数和函数执行函数。其实这个小系统也就是一个计时,到时间,则打开标志位,去执行对应的函数,标志位没有打开则跳过。

/*
	任务标志处理函数
	定时器1ms中断处理
*/
void Task_Remarks(void)
{
	for(int i=0;i<TASK_MAX;i++)
    {
		if(Task_Info[i].u16_timer)
		{
			Task_Info[i].u16_timer--;
			if(TASK_TIME_ON == Task_Info[i].u16_timer)
			{//定时器计数到后,打开函数运行标志位
				Task_Info[i].u16_timer = Task_Info[i].u16_itvTime;
				Task_Info[i].u8_runflag = TASK_ON_RUN;
			}
		}
    }
}
/*
	任务函数运行处理
	放入主函数的循环中
*/
void Task_Process(void)
{
	for(int i=0;i<TASK_MAX;i++)
    {
		if(Task_Info[i].u8_runflag == TASK_ON_RUN)
		{
			Task_Info[i].p_TaskHook();
			Task_Info[i].u8_runflag = TASK_OFF_RUN;
		}
    }
}

弄完这些后,只需要去配置一个1ms定时器,将计时函数放入其中,即可

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *hitm)
{
	if(hitm->Instance == TIM7)
	{
		Task_Remarks();
	}
}

到这里,这个小系统就完成了一大半了,在这里还需要添加一个打印函数,但是如何做到控制这个打印函数,只需要改变宏定义就可以实现关闭所以的打印呢,在实际的项目中,都会使用这种方式来进行系统的调试。

宏定义控制printf-CSDN博客

STM32关于UART的接收方式_stm32中huart1-CSDN博客

关于STM32CubeIDE使用printf串口打印_stm32cubeide printf-CSDN博客

这些都是跟调试有关的一些文章,可以查看。

物联沃分享整理
物联沃-IOTWORD物联网 » 裸机单片机软件架构选择指南

发表评论