赵宇科,高红亮,胡惠敏,李小玲
(湖北师范大学 电气工程与自动化学院,湖北 黄石 435002)
当今的单片机种类繁多,发展颇为迅速。而传统的单片机虽然功能完备,但效率偏低,不适用于多任务系统的研发。多任务系统较单任务系统而言,宏观上可同时执行多个任务,但微观上CPU在一段时间内只处理一个任务[1]。目前,多任务处理机制已经成为微控制器的一个基本功能需求。为研究多任务系统的设计,需要选用一款操作简单、性能强大的微控制器。STM32是当今主流的微控制器之一,其运行效率高且能耗较低,故本系统基于STM32进行分析和设计[2]。多任务系统可以给用户良好的体验感,在一定程度上满足用户的需求,故基于STM32实现多任务机制有重要意义。
在STM32上移植μC/OS-II操作系统后,用户可以在STM32上进行多任务系统的设计开发。μC/OS-II作为应用软件运行的平台,用于调度各个任务和协调任务之间的通信[3]。本系统的设计过程主要包括建立项目工程模板、配置LED和按键的GPIO、移植μC/OS-II、编写项目代码。
本系统使用Keil μVision 5作为编程软件建立工程模板。建立项目时需要注意一些细节。例如,计算机硬件的物理层面和Keil软件的逻辑层面应同时建立对应的文件和正确的文件目录;文件路径的正确性;程序的简洁性。
GPIO是最基本的一类I/O,其每个I/O端口可并行传输数据。STM32数据手册中包含了每个I/O特性的详细说明,据此,可使用Keil5将GPIO配置为多种功能模式。使用GPIO_Configuration函数可以配置GPIO,其中定义结构体变量用以描述GPIO的功能。
μC/OS-II操作系统占用空间小,但性能强大。基于这样的系统,开发人员将屏蔽硬件底层代码,用高级程序设计语言编写代码,从而提高开发效率。μC/OS-II可按某种策略合理地切换各个任务,使CPU在执行多个任务时效率更高。μC/OS-II根据用户编写的程序对任务进行调度,改变任务的状态。多任务的任务状态转换图如图1所示。
图1 任务状态转换图
本系统由C语言程序编写,设计3个LED任务,以3个LED的闪烁状态来模拟多任务的管理。其中1号、2号LED用于系统的验证,3号LED作为对照。系统设计的结果通过LED能直观表现,即LED的状态间接反映任务的状态,包括任务的创建、挂起、删除和恢复[4]。同时,设计KEY任务,使STM32开发板上的按键与这些状态分别对应。
STM库函数功能齐全,内容丰富,需要在建立项目工程时引入库函数文件。这些文件使程序的设计更加便捷,只需编写USER文件夹中的main.c文件即可。其中,main.c的部分代码如下:
Delay_Init ();
RCC_Configuration();
程序运行时,主函数执行Delay_Init函数。在移植了μC/OS-II的前提下,该函数会初始化μC/OS-II的时钟节拍,一般设置为系统时钟的1/8.RCC_Configuration函数初始化系统时钟。
在工程模板的基础上直接编写程序,配置GPIO.为了提高微处理器的执行效率,不在未使用的端口上配置时钟。在库文件中添加.c文件后,要在STM32的配置文件中声明对应的.h文件。配置时要注意,在初始化GPIO时,先声明GPIO结构体变量,再使能I/O端口时钟,最后配置端口方向和时钟频率[5]。
μC/OS-II的系统文件要保证其正确性和完整性,成功移植μC/OS-II的关键在于项目文件[6,7]。在物理层面导入μC/OS-II的系统文件后,需要在工程项目中新加入三个组,分别为uCOS-II/CONFIG、uCOS-II/PORT、uCOS-II/CORE.每组加入指定的文件并设置文件路径,最后加入中断文件。Keil 5中项目新建组的逻辑层面文件结构如图2所示。
图2 新建组的逻辑层面文件结构
设计μC/OS-II多任务程序的三个关键操作包括:给定任务的优先级别、设置任务的堆栈大小和创建任务的堆栈空间。在设置任务优先级别和堆栈时,均使用#define定义一个标识符来表示常量。这样定义的常量不会占用资源,它只是一个标识,用于标记优先级高低和堆栈大小。μC/OS-II可供使用的优先级别有62个,即优先级从高到低排序有0至61.
主函数的程序流程图如图3所示。其开始部分初始化延迟函数和系统时钟,并配置LED和按键的GPIO.LED的GPIO函数声明结构体变量,配置管脚和传输速度。按键的GPIO函数与之类似[8]。此外,定义LED的GPIO模式为推挽输出,按键的GPIO模式为上拉输入。OSInit函数初始化μC/OS-II,OSTaskCreate函数声明指向任务代码的指针和分配给任务堆栈的栈顶指针,随后分配该任务的优先级。编程时,OSTaskCreate函数参数列表中的start_task用于创建三个LED任务和KEY任务。主函数在最后调用OSStart函数启动操作系统。OSTaskCreate函数的参数列表如下:
OSTaskCreate(start_task, (void *)0,
(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],
START_TASK_PRIO);
start_task函数初始化统计任务,并标记任务进入临界区。进入临界区的任务只有在退出临界区后才可以被中断打断。使用start_task函数创建KEY任务和每个LED任务时,与上文类似,也需要OSTaskCreate函数声明任务代码指针和任务堆栈的栈顶指针,并指定优先级别。这里假定任务的优先级均相同。start_task函数的程序流程图如图4所示。
图3 主函数程序流程图
定义STM32开发板上的按键分别为复位键、删除键、挂起键和恢复键。新建一个表示按键的key.c文件,在文件中定义KEY任务函数key_task,其中对每个按键均编写条件判断语句。例如,若停止1号LED的闪烁,则要挂起LED1任务,执行的条件判断代码如下:
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)==Bit_RESET){
OSTaskSuspend(LED1_TASK_PRIO); }
OSTaskSuspend 函数将无条件挂起LED1任务,其参数列表为被挂起任务的优先级。LED1任务被挂起后,系统将重新对任务进行调度,CPU优先运行下一个优先级别最高的任务。这里要注意,当LED1任务被挂起时,只有其他任务才可以唤醒LED1任务。
OSTaskResume函数将唤醒已经被挂起的任务。若需要恢复LED1任务,则按下开发板上的恢复键。程序判断恢复键所对应的条件语句为真时,由当前任务调用OSTaskResume(LED1_TASK_PRIO)以恢复LED1任务。此时1号LED开始闪烁,LED1任务恢复。条件判断的代码如下:
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_1)==Bit_RESET){
OSTaskResume(LED1_TASK_PRIO); }
若在本系统中删除一个任务,则需要该任务自己删除自己。OSTaskDelReq和OSTaskDel函数均可删除一个任务,但二者有很大区别。例如,若按下删除键删除LED2任务,即利用key_task函数直接调用OSTaskDel函数来删除LED2任务,则LED2任务所占用的资源将不会被释放。这会使内存使用率下降,浪费系统的资源。解决这一问题的合理方法是,在按下删除键后,key_task函数调用OSTaskDelReq函数来请求LED2任务删除它自己。当CPU执行到LED2任务时,程序将判断是否存在一个OSTaskDelReq请求。若存在该请求,则调用OSTaskDel函数来彻底删除自己,并释放已占用的资源和内存。若不存在该请求,则LED2任务正常执行。LED2任务led2_task代码如下:
void led2_task(void *pdata){
pdata=pdata;
while(1){
if(OSTaskDelReq(OS_PRIO_SELF)==OS_ERR_TASK_DEL_REQ){
OSTaskDel(OS_PRIO_SELF); }
//LED2任务代码块
}
}
现运行本系统的程序并验证上述实例。按下STM32开发板上的复位键,系统自动创建3个LED进程,3个LED均保持同步的闪烁,这代表系统已经初始化,可以开始验证。验证环节把3号LED作为参照,用按键对1、2号LED进行试验。按下挂起键后,系统挂起LED1任务,此时开发板上的1号LED熄灭,其余两个LED保持同步闪烁。按下恢复键,LED1任务恢复,并可以获取CPU的执行权。此时1号LED虽恢复闪烁,但与其余两个不同步。按下删除键,2号LED熄灭,这表示LED2任务删除了它自己并释放了已占用的资源和内存。按下恢复键,LED2开始闪烁,此时3个LED的闪烁均不同步。而这种不同步现象发生的原因是,此前在每个LED任务中均设计了延迟时间为1秒的延迟函数,当某个LED任务被中断后又再次被CPU执行时,LED会发生闪烁不同步的现象。
上述实例的系统文件完整,所有代码在编译时未出现警告或异常。程序在STM32开发板上可正常运行,每个LED在按键控制下均可以按预期正常工作,实现了多任务系统的分析和设计。
基于STM32微处理器和μC/OS-II操作系统,本文分析了多任务系统的设计原理,研究了设计过程中的一些关键问题,实现了一个多任务程序系统。该系统使原本抽象的任务管理可以通过LED状态的改变而直观表现出来。系统文件结构清晰,程序使用C语言编写,代码简洁,可读性高,是分析多任务系统的良好实例。同时,可以设计其他类型的任务来替换这些LED任务,使系统更加灵活,更具可移植性、可扩展性。