本文的目的是希望读者能够通过本文的内容掌握移植uCOS-II 的规范方法。如果只是需要移植文件,可以直接去Micriμm的官网上下载。
移植uCOS-II,主要的移植工作是编写如下三个文件:
OS_CPU.H
OS_CPU_C.C
OS_CPU_A.ASM
下面就按照这三个文件的顺序来介绍。本文以STM32F107+RealView Compiler 开发环境为例。如果使用的其他的开发环境,个别代码可能需要做些小修改。
OS_CPU.H 的第一部分是定义了一个宏OS_CPU_EXT。这一部分暂时可以先不去管。
#ifdef OS_CPU_GLOBALS
#define OS_CPU_EXT
#else
#define OS_CPU_EXT extern
#endif
接下来是一系列的类型定义。这一部分的移植需参考RealView Compiler Reference Guide的如下章节:
RealView Compiler Reference Guide->C and C++ Implementation Details->Basic data types
从这里可以得到如下信息。
Type
Size in bits
Natural alignment in bytes
char
8
1 (byte-aligned)
short
16
2 (halfword-aligned)
int
32
4 (word-aligned)
long
long long
64
8 (doubleword-aligned)
float
double
long double
All pointers
_Bool
根据上面的信息,形成下面的代码:
上面代码中OS_STK 表示堆栈出栈、入栈的基本数据长度。我们知道Cortex-M3 的所有堆栈操作都是以字为单位的,所以这里为 unsigned int 型。OS_CPU_SR 对应的是程序状态寄存器PSRs,自然也是unsigned int 型。然后是关于临界区的处理,一般来说我们都喜欢使用第三种方法来实现临界区,这里也不例外。这里多说几句,第一种直接开关中断的实现临界区的方法很少采用,因为这种方法可能将原本关闭了的中断意外的打开。第二种方法是最高效的实现方法,但是这种方法调整了堆栈指针,对于需要利用堆栈指针间接寻址局部变量的系统并不适用。(x86通常采用第二种方法,因为它有单独的寄存器来做局部变量的寻址)第三种方法最保险,虽然效率比第二种方法略低一点。
这两个函数可以用汇编(OS_CPU_A.ASM)来编写:
也可以通过C代码(OS_CPU_C.C)中插入汇编的方式来实现:
上面的代码利用的RealView Compiler 的特殊功能(Embedded assembler),如需进一步的信息,可以参考RealView Compiler Reference Guide中Using the Inline and Embedded Assemblers这一章的内容。然后是堆栈增长方向,ARM Cortex-M3 的堆栈是倒生的:
任务切换,OSCtxSw()在OS_CPU_A.ASM 中定义:
最后是一些函数原型声明:
在原本uCOS-II 的移植代码中是没有这个文件的。由于下面这9个函数的函数体基本都是空的,并且移植时几乎不需要更改,所以我就将其拿出到一个单独的文件中来了。OSInitHookBegin()OSInitHookEnd()OSTaskCreateHook()OSTaskDelHook()OSTaskIdleHook()OSTaskStatHook()OSTaskSwHook()OSTCBInitHook()OSTimeTickHook()这9个函数的代码都很简单,下面是代码,不多介绍。
重要的移植工作都在这两个文件中提供,由于RealView Compiler 支持在C文件中插入汇编代码,所以OS_CPU_A.ASM 文件实际上可以去掉。所有的函数都在OS_CPU_C.C 中实现。下面分别介绍。
OSTaskStkInit 的移植是比较有难度的。这个函数是用来初始化各个任务的堆栈,使各个任务的堆栈就像是刚才中断处理函数中返回那样。
想要理解上面的代码需要知道Cortex-M3在响应外部中断时对寄存器的压栈顺序,还需知道函数的第一个参数是通过R0来传递的。建议阅读ARM Cotex M3 权威指南,里面有详细的介绍。这里我只说一处,就是OS_TaskReturn 位置对应的是任务的返回地址。我们知道,uCOS-II 中任务就是简单的函数。普通的函数执行完成后会返回到调用它的地方的下一条语句处继续执行。这个位置就记录在堆栈中,也就是OS_TaskReturn所在的位置。uCOS-II要求任务必须是无限的循环,不允许退出。所以理论上永远不会跳转到OS_TaskReturn处执行。OS_TaskReturn的作用是当程序异常退出时不至于程序跑飞。在现在的移植代码中OS_TaskReturn 也是个简单的函数,没有加入额外的保护代码。
这个函数只被OSStart()调用。用来运行最高优先级的任务。代码如下。
其中如下三行代码是用来设置PendSV异常的优先级为最低。
相当于如下的C代码:
下面两行代码的作用是使线程堆栈指针 PSP = 0。PendSV_Handler 中需要根据它来判断是否是OSStartHighRdy 引起的PendSV,因为这时要特殊处理一下。
最后四行的作用是引起一次 PendSV。相当于下面的C代码:
在其他处理器的移植代码中,这两个函数还是有些工作要做的。但是对于Cortex-M3 就简单的多了,只要引起一次PendSV 就行了,具体的任务切换由PendSV来处理。
也可以写为汇编代码,写为汇编的好处是两个函数可以共用一个函数体:
SysTick 用来处理操作系统的计时。代码很简单,无需多说。
最终的任务切换工作都在这里完成。下面先给出伪代码。从这里就可以看出OSStartHighRdy 中将PSP 写为0 的作用了。
下面给出真实的代码,可以看出与伪代码是对应的:
至此,移植工作完成。uCOS-II 在 Cortex-M3上的移植与其他单片机上移植代码的最大区别在于所有的任务切换工作都放到了 PendSV 中进行,而PendSV 中断的优先级被设为最低,这样就能保证更高优先级的中断能够及时被处理。不过,在PendSV 中断处理代码中第一条语句就是关中断,这时如果来了更高优先级的中断,也是无法响应的。能否进一步改善中断响应性能还需再思考。个人认为应该还有进一步优化的可能,不过具体该如何优化,暂时还没有头绪。