第八章C/OS嵌入式操作系统(C/OS)软件设计一、嵌入式操作系统的移植二、SDT工具的使用移植的概念和目的C/OS-II的移植软件移植一、嵌入式操作系统的移植移植的概念和目的移植:程序或应用软件从一个系统平台移动另一个系统平台,其功能、结构、执行结果保持不变。OS移植:使一个实时内核能够在某个微处理器或微控制器上运行。移植的目的:1、硬件平台的升级2、实现软件重用3、实现软件/硬件并行设计移植的要求:移植对象具有硬件无关性移植对象具有系统无关性移植对象采用标准语言编程C/OS-II的移植C/OS-II的软硬件体系结构C/OS-II的移植需要满足的要求C/OS-II移植的主要工作C/OS–II的软硬件体系结构µC/OS-II目录结构与代码组织可从µC/OS-II网站下载;\SOFTWARE\uCOS-II与µC/OS-II相关的文件都放在这个目录下。\SOFTWARE\uCOS-II\SOURCE这个目录里包括与处理器类型无关的源代码。这些代码完全可移植到其它架构的处理器上。\SOFTWARE\uCOS-II\ARM这个目录下包括依赖于处理器类型的代码。C/OS-II移植需要满足的要求处理器的C编译器可以产生可重入代码;处理器支持中断,并且能产生定时中断(通常为10~100Hz);用C语言就可以开/关中断;处理器需要能够容纳一定数据的硬件堆栈;处理器需要有能够在CPU寄存器与内存和堆栈交换数据的指令。开/关中断:在C/OS-II中,可以通过:OS_ENTER_CRITICAL()OS_EXIT_CRITICAL()宏来控制系统关闭或者打开中断。这需要处理器的支持。在ARM7TDMI的处理器上,可以设置相应的寄存器来关闭或者打开系统的所有中断。处理器支持中断并能产生定时中断:C/OS-II是通过处理器产生的定时器的中断来实现多任务之间的调度的。ARM7TDMI的处理器上可以产生定时器中断。本系统工作在60MHz的主频下,定时器的中断的频率为1000Hz。也就是系统的响应时间为1ms。处理器支持硬件中断:C/OS-II进行任务调度的时候,会把当前任务的CPU寄存器存放到此任务的堆栈中,然后,再从另一个任务的堆栈中恢复原来的工作寄存器,继续运行另一个任务。所以,寄存器的入栈和出栈是C/OS-II多任务调度的基础。ARM7处理器中有专门的指令处理堆栈,可以灵活的使用堆栈。开发工具:针对所使用的CPU的标准C交叉编译器C编译器支持汇编语言程序C编译器还须提供一种机制,能在C语言中开中断和关中断。C/OS-II移植的主要工作移植工作包括以下几个内容:▪用#define设置一个常量的值(OS_CPU.h)▪声明10个数据类型(OS_CPU.h),数据类型与CPU与编译器有关▪用#define声明三个宏(OS_CPU.h)▪编写四个汇编语言函数(OS_CPU_A.asm)▪编写六个C语言函数(OS_CPU_C.c)可以从C/OS–II网站:中查找一些移植实例。编写与处理器相关的代码测试移植代码与处理器相关的代码有:OS_CPU.HOS_CPU_A.ASMOS_CPU_C.COS_CPU.H包含了用#define语句定义的、与处理器相关的常数、宏以及类型。OS_CPU.H中定义了与编译器相关的数据类型,比如:INT8U、INT8S等。与ARM处理器相关的代码,使用OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()宏开启/关闭中断设施堆栈的增长方向:堆栈由高地址向低地址增长用C语言编写十个操作系统相关的函数(OS_CPU_C.C)OSTaskStkInit()OSTaskCreatHook()OSTaskDelHook()OSTaskSwHook()OSTaskIdleHook()OSTaskStatHook()OSTimeTickHook()OSInitHookBegin()OSInitHookEnd()OSTCBInitHook()其中唯一必要的函数是OSTaskStkInit(),其他9个函数必须声明,但不一定要包含任何代码。C/OS-II中每个任务都有自己的任务堆栈,在任务创建初期由初始化OSTaskStkInit()初始化。初始化堆栈的目的就是模拟一次中断。任务堆栈中保存了任务代码的起始地址和一些CPU寄存器(初值是无关紧要的),这样一旦条件满足,就可以执行该任务了。用汇编语言编写四个与处理器相关的函数(OS_CPU.ASM)OSStartHighRdy()OSCtxSw()OSIntCtxSw()OSTickISR()在每个硬件时钟到来后,C/OS-II会在中断服务例程中调用OSIntCtxSw()进行任务调度;另外,当某个任务因等待资源而被挂起时,没有必要等到自己的时间片全都用完,可以自己主动放弃CPU,这可以通过调用一个任务级的任务调度函数OSCtxSw()来实现。其中相对复杂的是OSIntCtxSw()。由于OSTickISR()调用了OSIntExit(),OSIntExit()又再次调用了OSIntCtxSw(),如果进行任务切换,那么两次调用都不会返回,而不同的C编译器、不同的编译选项处理C调用时对堆栈的使用也不尽相同。因此OSIntCtxSw()是编译器相关的。测试移植代码确保C编译器、汇编编译器及链接器正常工作验证OSTaskStkInit()和OSStartHighRdy()函数验证OSCtxSw()函数验证OSIntCtxSw()和OSTickISR()函数向嵌入式平台移植软件大部分嵌入式开发人员选用的软件开发模式是先在PC机上编写软件,再进行软件的移植工作。在PC机上编写软件时,要注意软件的可移植性。1、选用具有较高移植性的编程语言(如C语言)2、尽量少调用操作系统函数3、注意屏蔽不同硬件平台带来的字节顺序、字节对齐等问题。字节顺序字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有小端、大端两种字节顺序。小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处;大端字节序是高字节数据存放在低地址处,低字节数据存放在高地址处。基于X86平台的PC机是小端字节序的,而有的嵌入式平台则是大端字节序的。因而对int、uint16、uint32等多于1字节类型的数据,在这些嵌入式平台上应该变换其存储顺序。通常我们认为,在空中传输的字节的顺序即网络字节序为标准顺序,考虑到与协议的一致以及与同类其它平台产品的互通,在程序中发数据包时,将主机字节序转换为网络字节序,收数据包处将网络字节序转换为主机字节序。字节对齐有的嵌入式处理器的寻址方式决定了在内存中占2字节的int16、uint16等类型数据只能存放在偶数内存地址处,占4字节的int32、uint32等类型数据只能存放在4的整数倍的内存地址处;占8字节的类型数据只能存放在8的整数倍的内存地址处;而在内存中只占1字节的类型数据可以存放在任意地址处。由于这些限制,在这些平台上编程时有很大的不同。首先,结构体成员之间会有空洞,比如这样一个结构:typedefstructtest{chara;uint16b;}TEST结构TEST在单字节对齐的平台上占内存三个字节,而在以上所述的嵌入式平台上有可能占三个或四个字节,视成员a的存储地址而定。当a存储地址为偶数时,该结构占四个字节,在a与b之间存在一个字节的空洞。对于通信双方都是对结构成员操作的,这种情况不会出错,但如果有一方是逐字节读取内容的(通信协议大都如此),就会错误地读到其它字节的内容。其次,若对内存中数据以强制类型转换的方式读取,字节对齐的不同会引起数据读取的错误。因为假如指针指在基数内存地址处,我们想取得占内存两个字节的数据存放在uint16型的变量中,强制类型转换的结果是取得了该指针所指地址与前一地址处的数据,并没有按照我们的愿望取该指针所指地址与后一地址处的数据,这样就导致了数据读取的错误。代码优化的问题嵌入式系统对应用软件的质量要求更高,因而在嵌入式开发中尤其须注意对代码进行优化,尽可能地提高代码的效率,减少代码的大小。虽然现代C和C++编译器都提供了一定程度的代码优化,但大部分由编译器执行的优化技术仅涉及执行速度和代码大小的平衡,不可能使程序既快又小,因而必须在编写嵌入式软件时采取必要的措施。(1)提高代码的效率switch-case语句。在程序中经常会使用switch-case语句,每一个由机器语言实现的测试和跳转仅仅是为了决定下一步要做什么,就浪费了处理器时间。为了提高速度,可以把具体的情况按照它们发生的相对频率排序。即把最可能发生的情况放在第一,最不可能发生的情况放在最后,这样会减少平均的代码执行时间。全局变量。使用全局变量比向函数传递参数更加有效率,这样做去除了函数调用前参数入栈和函数完成后参数出栈的需要。当然,使用全局变量会对程序有一些负作用。(2)减小代码的大小嵌入式系统编程应避免使用标准库例程,因为很多大的库例程设法处理所有可能的情况,所以占用了庞大的内存空间,因而应尽可能地减少使用标准库例程。(3)避免内存泄漏用户内存空间(堆)为RAM中全局数据和任务堆栈空间都分配后的剩余空间,为了使程序能有足够的内存运行,必须在申请的内存不用后及时地将其释放,以确保再次申请时能有空间。如果程序中存在内存泄漏(即申请内存后没有及时释放)的情况,程序最终会因为没有足够的内存空间而无法运行。通过案例说明编程模型:指示灯控制程序。创建2个任务,任务1是让灯亮,任务2让灯灭//任务1定义//任务2定义voidTask1(void)voidTask2(void){{while(1)while(1){{led_on();led_off();//μC/OS-Ⅱ系统调用//μC/OS-Ⅱ系统调用OSTimeDly(25);OSTimeDly(50);}}}}μC/OS-Ⅱ编程模型//主函数voidmain(){OSInit();//μC/OS-Ⅱ系统调用,系统初始化OSTaskCreate(Task1,(void*)&Task1Data,(void*)&Task1Stk[TASK_STK_SIZE],Task1prio);OSTaskCreate(Task2,(void*)&Task2Data,(void*)&Task2Stk[TASK_STK_SIZE],Task2prio);OSStart();}