老驾上豪车,不踩一脚油门你都觉得对不起它。STC8H8K64U单片机上能承载几个uCOS II的多任务,不试试怎么知道。本文介绍在8051单片机的天问51开发板上运行8个任务的uCOS II程序框架。
(一)uCOS II系统存储空间的设置
为了支持多任务中函数重入的要求,Keil用模拟堆栈的方法来解决。模拟堆栈是从XDATA的高端地址向下生长的,因此对于uCOS II系统运行,设置XDATA的大小至关重要。
Keil对XDATA的设置是在“STARTUP.A51”汇编语言启动文件中进行,见下图:
(1)第59行设置XDATA空间的大小,是最高可寻址地址。对于STC8H8K64U单片机是8KB,所以设置为0x1FFF。
(2)第50行是设置IDATA空间的大小,Keil在单片机启动时从这个最高地址向下将IDATA中的数据清0。对于STC单片机,STC的硬件启动程序将一些重要的参数保存在其中:
因此第50行程序将其设置为0xE0,保留这些数据。
8051单片机的堆栈是向上生长的,所以这个保留不影响使用,但是用户若要使用这些参数,应该在main函数的一开始就读取它们,保存起来。
(3)对于可重入函数,Keil编译器在调用该函数时为该函数在XDATA空间中生成一个虚拟堆栈,将该函数的参数和局部变量都放在这个堆栈中,待调用完成后,撤销这个堆栈,收回函数调用时所占用的XDATA空间。
当用户程序同时多次调用该函数(包括递归调用)时,每次都生成新的虚拟堆栈,这样每次被调用产生的函数实例的参数和局部变量都可以不同,就实现了函数重入。
下图是初始时Keil虚拟堆栈顶的设置程序:
其中第89行等于1表示XDATA空间有虚拟堆栈,第92行是虚拟堆栈顶,因为堆栈向下增长,栈顶是达不到的,所以对于8KB的XDATA空间是0x1FFF+1。
出于特殊目的,用户可以改变这个虚拟栈顶,在XDATA空间的上部保留一块数据空间。虚拟堆栈顶以上的空间,Keil软件编译生成的代码自己不会去访问它,必须用户自己编程去访问。
(4)下图是Keil的系统堆栈设置和启动用户程序的代码。
第194行到第199行是设置虚拟栈顶。在Keil程序中,虚拟堆栈指针变量的名称是“?C_XBP”,它是一个16位的整数,位于DATA空间,可以用MOV指令存取。
第206行是设置8051内核的系统堆栈指针。Keil从低地址分配DATA类型变量,分配完后的将剩余的部分分配给系统堆栈。系统堆栈用来存放每个任务访问函数的返回地址,以及存放中断时保留的程序现场。
因此,系统堆栈中的数据长度(堆栈深度)最多为256字节,这个数很重要。
最后,引导程序用“LJMP”指令跳转到main入口处执行,结束引导过程。因此main既没有参数,也没有返回值,用户也不应该从main函数中退出。
(二)uCOS II系统中多任务的设置。
本文以8个任务,每个任务独立控制一个LED闪烁为例来讨论多任务的规模问题。下面是用户主程序,由于8个任务高度雷同,中间省去了部分程序。
(1)第13行程序定义用户任务的任务堆栈尺寸为512个字节。本范例所有任务都用同一大小,在实际应用时用户可以针对自己的每个任务情况在这个基础上进行调整。
任务堆栈中存放两个内容,一个是任务运行时以“?C_XBP”为指针的函数访问虚拟堆栈,另一个是以“SP”为指针的系统堆栈(最多为256字节)。
本文选择512字节,相当于每个任务为STC89C52单片机的水平。
(2)第15行到第18行是第一个任务定义。
第16行定义任务1的优先级。“TASK_START_PRIO”是uCOS II提供的可使用的最高优先级数,优先级数越小,任务的优先级就越高。本范例规划任务1的优先级最高,任务8的优先级最低。
第17行定义任务1的任务堆栈,它是一个静态的公共数组变量,数组的类型“OS_STK”在8051单片机上是“unsigned char”类型。
第18行是任务1的任务函数声明。函数的参数是一个任意类型的指针。由于uCOS II不只在单片机上实现,也可以在操作系统上实现,因此都采用了统一的任务函数形式。理论上可以在任务初始化时传一个参数给任务函数,但是对于8051版的uCOS II,最好不要去试。
对于Keil编译器,每个任务函数都必须是大模式和函数可重入的,这里给出显示的声明形式,是避免Keil项目设置的影响。
(3)以下第20行到第53行是其余任务2到任务8的定义。uCOS II的任务调度方式要求每个任务的优先级必须不同,所以这里采取了将优先级数依次加1的方法。
(4)main函数中,第64行是UCOS II系统的初始化函数。它初始化整个系统,建立一个核心任务,然后将任务表中的其他任务设置为空任务。
(5)第66行到84行是芯片级和板级的系统初始化程序,为uCOS II建立起合适的运行环境,详细见笔者的上一篇文章介绍。
(6)main函数的第87行到第94行依次为用户建立起任务1到任务8。
任务建立可以不必按次序进行,因为第4个参数优先级数就是该任务在任务表数组中的下标。
理论上第2个参数就是传给该任务函数的参数,但是对于8051版的uCOS II,最好不要去试,所以全部填了0。
(7)main函数的第100行启动uCOS II系统运行。对一般的应用而言,程序从这个函数执行到优先级最高的程序中去了,程序不会从这个函数中返回,因此程序执行不到第103行。
(三)编写用户多任务程序
(1)第107行到114行定义了每个任务对应闪烁的LED灯位端口。
(2)第117行到128行是任务1的任务函数,任务的声明形式与第18行一样。8个任务都采用了最简单的uCOS II用户任务形式。
第119行是必须声明参数又避免Keil检查语法是出错的套路。
第123行翻转LED的电平。
“OSTimeDlyHMSM(”是uCOS II操作系统任务调度接口。第124行的函数调用让操作系统挂起任务1,将执行权让给其他任务,然后等待600毫秒后再唤醒任务1,将执行权交回任务1,程序从第124行函数退出,继续执行下一条语句。
(3)从第130行到第226行为其余7个任务的任务函数,它们的结构雷同,只是闪烁的LED不同和延迟的时间不同。
(4)将这个程序烧录到天问51开发板后,可以看到8个LED灯按不同的周期在闪烁。
(四)总结
下图是Keil编译器输出的RAM内存分配图
(1)图中“?XD?MAIN”是main函数占用的XDATA空间,正好是8个任务的公共任务堆栈8x512=4096所占用的4KB空间。
(2)图中“?XD?OS_CORE”是uCOS II操作系统占用的XDATA空间,正好是有13个任务的任务表公共变量数组的13x64=832所占用的832字节的空间。由于所有函数都是可重入函数,所以这些函数的局部变量是放在虚拟堆栈,不分配固定的内存空间。
(3)整个程序使用了0x1368个(4968)字节的XDATA空间,使用了0x2401个(9217)字节的CODE空间。
(4)STC89C52是12T的8051单片机,具有512字节的RAM空间(256的IDATA+256的XDATA)和8KB的CODE程序空间。目前市面上有很多使用它开发板和实验箱,有很多实验的例子,许多单片机教学和培训都在使用,可以作为评价STC8H8K64U上运行uCOS II的性能指标。
本范例的每个任务堆栈也是512字节的,大体上说能够在STC89C52运行的程序,也可以在本范例的一个uCOS II任务中运行。因此在一个STC8H8K64U上运行uCOS II相当于同时运行8到10个STC89C52的任务。
STC8H是1T的指令,本范例使用40MHz的主频,从速度上说是STC89C52的40倍,因此在STC8H8K64U上运行uCOS II对于同时运行8到10个STC89C52任务是绰绰有余的。