作为我们UNITEC的第一篇分享,想把我们经常用到单片机程序中架构做一个简要说明,再以STM32为例介绍如何实现这种方式;
我们日常控制电路设计中,会经常用到各种品牌单片机,如STM32 、AVR、PIC及国产STC等等,其中从8位、16位到32位,都要进行软件设计,所以我们有必要和大家一起探讨软件架构及具体实现方式;
单片机应用程序架构大致分为三种,第一种是前后台程序顺序执行;第二种时间片轮询法,是介于顺序执行与操作系统之间一种方法;第三种是操作系统;
下面具体分析三种方法:
第一种:前后台程序顺序执行,大多数编程者使用该方法,不需要思考程序架构,直接顺序执行程序,这种对简单程序、实时性要求不高的情况还是不错的,但若后续程序复杂程度提高的情况下,顺序向后添加,会严重影响实时性,复杂点程序不推荐使用;
第二种:时间片轮询法,会让每个任务等待一段时间执行,而不是顺序执行,比如:有四个任务 Task_A, Task_B, Task_C, Task_D ,从函数上看也是顺序在执行,只是每一次轮询,不是执行全部的任务,是按照我们设定的时间来选择任务来执行,假设Task_A任务1ms执行一次,Task_B任务10ms执行一次,Task_B任务100ms执行一次,Task_D任务1000ms执行一次,就能明显看出各个任务优先级,Task_A,等待时间极短,可以用于检测实时性要求比较高的事件,对于Task_D任务1000ms执行一次,优先级很低,可以用于显示等功能,而不是每次按照Task_A~D顺序执行,提高程序运行效率,这个是我们推荐方法;
第三种:操作系统本身比较复杂,占用MCU资源也比较多,像51单片机之类MCU资源有限,很难运行操作系统,而且是牺牲部分实时性来换取任务管理、调用,所以如果我们可以通过上述时间片轮询法能够实现的程序,不建议用操作系统;
对以上大致了解后,非必须用操作系统的话,我们推荐使用任务轮询法,因为是对每个任务计时来实现任务轮询选择,所以只要有定时器,功能即可实现,下面以STM32F1为例来讲述实现过程:
STM32 有一个Systick定时器,是专门为操作系统设计,中断优先级高于其它定时器的,可以设置1ms中断一次,在“stm32f10x_it.c” 中,systick中断函数为:
u32 SystemTickTime10MS = 0;
u32 SystemTickCounter = 0;
void SysTick_Handler(void)
{
if ((++SystemTickCounter) >= SYSTEMTICK_PERIOD_MS)
{
SystemTickCounter = 0;
SystemTickTime10MS += SYSTEMTICK_PERIOD_MS;
}
}
程序说明如下:
SysTick_Handler()为Systick中断入口函数,前面我们在“delay.c”中已经设置为1ms进入中断;
SystemTickCounter :每进入一次中断,此变量累加1,用于和“时基”周期比较,当达到时基周期时,此变量清零,SystemTickTime10MS将累加时基周期,比如:第一次0,第二次10ms, 第三次20ms…,周而复始运行下去;
在主函数中
main()
{
While(1)
{
Input_Output_Processing_Subprogram(); //输入/输出处理子程序
Trans_Rec_Processing_Subprogram(); //发送/接收处理子程序
Display_Processing_Subprogram(); //显示处理子程序
}
}
其中以Display_Processing_Subprogram()为例来说明任务等待原理,其内部程序如下:
void Display_Processing_Subprogram(void)
{
if ( Display_Task_Execute_Pending == SystemTickTime10MS )
{
return;
}
Display_Task_Execute_Pending = SystemTickTime10MS;
System_LED_Indicator();
}
程序说明:
进入函数,先进行判断“Display_Task_Execute_Pending”与“SystemTickTime10MS”是否相等,当两个数不等的时候,要进行一次函数执行,如若相等则退出函数,不等的时刻为10ms、20ms、30ms…,所以任务执行时间点也就是10ms、20ms、30ms…,即每10ms执行一次函数,这个就是任务时间轮询原理;“SystemTickTime10MS”在头文件中已经定义的常量(10ms),这个变量即为函数等待执行的时间;
“System_LED_Indicator()”为系统指示灯的闪烁函数,函数为:
void System_LED_Indicator(void)
{
if ((++System_LED_Flicker_Timer) >= SYSTEM_LED_FLICKER_LEN)
{
System_LED_Flicker_Timer = 0;
System_LED_Run_Flag = ~System_LED_Run_Flag;
if (System_LED_Run_Flag)
{
SYSTEM_LED_ON ;
}
else
{
SYSTEM_LED_OFF ;
}
}
}
程序说明:
void System_LED_Indicator(void) : 内部又是一个延时,“SYSTEM_LED_FLICKER_LEN”为需要延时时长,宏定义里预先设置,此处设置50,“System_LED_Flicker_Timer”为一个函数执行等待变量;此函数在void Display_Processing_Subprogram(void)中被调用, 可以计算得出执行函数要等待时间为:SYSTEM_LED_FLICKER_LEN* SYSTEMTICK_PERIOD_MS,此处为50*10 = 500ms,经过500ms会进入函数一次,执行LED显示翻转动作;
以上为任务轮询编程思想,程序设计中经常用到的,这里仅为抛转引玉,大家可以一起讨论,谢谢大家。