本帖最后由 杨为民 于 2024-3-1 00:15 编辑
一、前 言 uC/OS-II 实时多任务操作系统是最适合移植到STC的8051(包括32位的8051单片机STC32G/F系列)系列单片机上的RTOS。首先uC/OS-II是一款经典的有广泛生态基础的RTOS,其次是其程序结构是可裁剪和定制的,非常适合在STC这样有众多子型号的单片机上使用。比如从小到家电控制到大到工业控制的应用领域,只需要对uC/OS进行裁剪和设置就可以得到适合的RTOS平台。 STC32G/F系列单片机的内核是一款扩展了80251指令集的32位单片机,目前使用Keil的C251编译器。当C251编译器设置为“Xsmall”模式时,程序缺省的数据空间是EDATA空间,对于这个空间变量可以实现一次32位的存取,速度极快,运行RTOS,任务切换的时间可以达微秒量级。 STC官方推荐了一款资深专家提供的在STC8系列单片机上运行的uC/OS-II RTOS学习代码(以下简称uCOS学习版),与笔者上文介绍的陈是知先生的移植版相比较,uCOS学习版考虑更周全,也更适合RTOS的入门学习。 比如在uCOS学习版中,虽然C51项目的RAM存储设置已经是大模式了,但是移植者仍然在每个函数定义中都加上了“large reentrant”关键字,强制定义这个函数为使用XDATA内存的可重入的函数。 比如在uCOS学习版中,除了程序,还给出了“Protues 仿真”文件,在“DOC”的子目录里还给出了原始的官方的“uC/OS-II V2.52”开源代码和说明文件。 本系列文章将分步骤介绍把uCOS学习版移植到STC32G/F单片机上的全过程,希望读者从移植的过程中,深入了解宏晶公司的STC8位单片机与STC32位单片机的差别,深入了解Keil公司的C51与C251的差别以及深入了解RTOS任务调度的原理和实现方法。 二、提取移植源程序。 uCOS学习版压缩包(可以从STC官网上下载)中包含了很多资料,为了使读者将关注点集中在移植过程上,有必要将无关的参考资料删除,提取出一个“纯”的移植项目。另外原程序使用串口通讯作为多任务的例子,笔者也将会取消掉,改为LED这样直观的显示设备。这么做的原因是多个任务共同使用一个输出设备,存在一系列操作系统理论中碰到的防死锁的方法,解决的具体方法各有不同,因此不适合作为移植单片机RTOS的例子。 附件中“01_学习版_uCOSII_STC8”是原始的uCOS学习版程序资料,附件中“02_打狗棒_uCOSII_STC8H”是提取后的移植源程序。 (1)移植源与原始的比,主要有以下不同:去掉了供移植外的文件,去掉了与串口相关的程序,将所有程序重新划分了子目录,修改了包含文件路径。 三、整理移植源程序 每个程序员都有自己的程序风格,为了保持一致性,需要将uCOS学习版的移植源程序的修改为与笔者前一篇文章一致的风格。 附件中“03_整理移植源程序”是整理后的移植源程序。与整理前的比,主要有以下不同: (2)将单片机头文件改为“STC8H.H”,在“OS_CPU_A.A51”文件里直接定义“SP”、“PSW”等8051寄存器,以便将来与80251对接。 (3) 将主函数的定义从“int main(void) {”修改为“void main(void) {”,并且取消了“return OK;”语句。这是因为与DOS程序不同,单片机程序中的主函数是不允许退出的,强行退出会导致程序跑飞。 (4)修改了任务堆栈的大小。在uCOS学习版中堆栈的定义是: OS_STKAppStartStk[TASK_STK_SIZE]; OS_STKPowerLedStk[TASK_STK_SIZE]; OS_STKUartRcvStk[TASK_STK_SIZE]; OS_STKUartSendStk[TASK_STK_SIZE]; 其中:“TASK_STK_SIZE”的定义为“#define OS_TASK_STAT_STK_SIZE 512”,大小为512字节。 但是主程序在任务定义中使用了任务堆栈初始化函数“psp = OSTaskStkInit(task, p_arg, ptos, 0u);”,而在任务堆栈初始化函数中,函数重入指针的设置为: *stk++=(INT16U)(ptos+MAX_STK_SIZE)>> 8; /* ?C_XBP 仿真堆栈指针高8位 */ *stk++=(INT16U)(ptos+MAX_STK_SIZE) &0xFF; /* ?C_XBP 仿真堆栈低8位 */ 其中使用的堆栈长度为“MAX_STK_SIZE”,其定义为“#define MAX_STK_SIZE 1024”,与用户任务堆栈尺度“TASK_STK_SIZE=512”的定义无关,所有任务的堆栈都是1024个字节的。 在uCOS学习版中,用户定义的堆栈尺寸小于实际堆栈的定义“MAX_STK_SIZE=1024”,将产生“堆栈覆盖”的严重隐患。只是由于其范例的特殊性,凑巧运行时没有发生问题而已。 因此在整理时统一改为“MAX_STK_SIZE=1024”的大小: OS_STKTask_STK_A[MAX_STK_SIZE]; OS_STKTask_STK_B[MAX_STK_SIZE]; OS_STKTask_STK_C[MAX_STK_SIZE]; (5)uCOS学习版建立任务采用了PC版的动态方法,先建立一个用户启动任务“AppStart”,然后在这个任务中再建立其他的用户任务,建立完了就把启动任务自己删除了。使用这种方法的优点是有一个首先执行的任务,然后由这个任务进行用户系统软硬件的初始化和根据需要动态建立和运行任务。如果有DOS的支持,可以在运行中动态地分配和回收RAM内存,这确实是好方法。 但是像目前这样的STC8H和STC32G/F单片机,没有DOS的支持,采用裸机编程方法,编译好程序后,一次性将代码烧录到单片机情况,笔者认为还是采用静态建立任务的方法为好。 四、主程序说明 整理后的主程序“main.c”分为4个部分,下面分别介绍。 (6)实时任务定义: 本范例定义三个任务,任务函数必须声明为可重入的,这样其所有的变量都定义在任务堆栈中,在内存中建立起一个运行框架。
(7)硬件初始化: 由于本范例的实时任务只采用了系统中断进行任务调度,而总中断控制“EA”被用于临界区保护,所以第55行关闭定时器,就禁止了任务调度。这样避免了在下面的RTOS系统的初始化没有完成的时候突然产生中断发生意外。
(8)RTOS系统初始化部分: 第61到地63行定义了三个任务,任务的优先级直接定义为2、3、4,其中优先级2为最高优先级。 第66行开始任务调度后,最高优先级任务A开始执行。第76行让定时器开始工作,一旦第81行程序任务A放弃CPU执行权开始休眠,任务调度程序就会切换到任务B继续执行,等到定时器0产生的系统中断任务调度程序判断任务A休眠时间已到,就会让任务A从第82行开始执行下面程序。
(9)实时多任务部分: 虽然任务B和任务C在建立任务后已经处于就绪状态,但是与任务A不同,没有经过任务调度允许就不会自动执行。每个任务被调度执行有三个条件,首先是处于就绪状态,其次在处于就绪状态的多个任务,它的优先级最高,最后是需要任务调度器将任务切换给它。 附件一:是本程序运行时的效果:
五、主程序的遗憾 通常在主程序中应该明显地看到对整个程序运行的控制。除了系统定时器0中断以外,如果还有其他的中断,比如定时器1,除了要打开它本身的设备中断允许,还要打开中断允许标志“EA”。
在单片机程序设计中,明显关闭和打开中断“EA”,是一个基本的编程要求。但是在主程序中看不到“EA”在什么地方被打开, 也就不知道定时器1是在什么时候开始中断工作的,这是不能不说很大的遗憾。
|