L​P​C​2​1​X​X​移​植​U​C​O​S​-​I​I​小​结

 1.在uC/OS-II的帮助手册内,作者特地强调绝对不能在OSInit()或者OSStart()内调用Timer初始化程序,那会破坏系统的可移植性同时带来性能上的损失。所以,一个折中的办法就是:在优先级最高的程序内调用,这样可以保证当OSStart()调用系统内部函数OSStartHighRdy()开始多任务后,首先执行的就是Timer初始化程序。或者专门开一个优先级最高的任务,只做一件事情,那就是执行 Timer初始化,之后通过调用OSTaskSuspend()将自己挂起来,永远不再执行。不过这样会浪费一个TCB空间。对于那些RAM吃紧的系统来说,还是不用为好。

    在uC/OS-II里,每个任务都有一个任务控制块(Task Control Block),这是一个比较复杂的数据结构。在任务控制快的偏移为0的地方,存储着一个指针,它记录了所属任务的专用堆栈地址。事实上,再uC/OS-II内,每个任务都有自己的专用堆栈,彼此之间不能侵犯。这点要求程序员再他们的程序中保证。一般的做法是把他们申明成静态数组。而且要申明成OS_STK类型。当任务有了自己的堆栈,那么就可以将每一个任务堆栈再那里记录到前面谈到的任务控制快偏移为0的地方。以后每当发生任务切换,系统必然会先进入一个中断,这一般是通过软中断或者时钟中断实现。然后系统会先把当前任务的堆栈地址保存起来,仅接着恢复要切换的任务的堆栈地址。由于哪个任务的堆栈里一定也存的是地址(还记得我们前面说过的,每当发生任务切换,系统必然会先进入一个中断,而一旦中断CPU就会把地址压入堆栈),这样,就达到了修改PC为下一个任务的地址的目的。

    2.(三) 一些重要的uC/OS-II API介绍

    μC/OS 和μC/OS-II 是专门为计算机的嵌入式应用设计的, 绝大部分代码是用C语言编写的。CPU 硬件相关部分是用汇编语言编写的、总量约200行的汇编语言部分被压缩到最低限度,为的是便于移植到任何一种其它的CPU 上。

    任何一个操作系统都会提供大量的API供程序员使用,uC/OS-II也不例外。由于uC/OS-II面向的是嵌入式开发,并不要求大而全,所以内核提供的API也就大多和多任务息息相关。

    主要的有以下几类:

    1)任务类

    2)消息类

    3)同步类

    4)时间类

    5)临界区与事件类

    我个人认为对于初级程序员而言,任务类和时间类是必须要首先掌握的两种类型的API.下面我就来介绍比较重要的:

    1)OSTaskCreate函数

    这个函数应该至少再main函数内调用一次,在OSInit函数调用之后调用。作用就是创建一个任务。目前有四个参数,分别是任务的入口地址,任务的参数,然后将会根据用户给出参数初始化任务堆栈,并在内部的任务就绪表内标记该任务为就绪状态。最后返回,这样一个任务就创建成功了。

    2)OSTaskSuspend函数

    这个函数很简单,一看名字就该明白它的作用,它可以将指定的任务挂起。那为什么是优先级呢?事实上在系统内部,优先级除了表示一个任务执行的先后次序外,还起着分别每一个任务的作用,换句话说,优先级也就是任务的ID.所以uC/OS-II不允许出现相同优先级的任务。

    3)OSTaskResume函数

    这个函数和上面的函数正好相反,它用于将指定的已经挂起的函数恢复成就绪状态。其参数类似 OSTaskSuspend函数,为指定任务的优先级。需要特别说明是,本函数并不要求和OSTaskSuspend函数成对使用。

    4)OS_ENTER_CRITICAL宏

    很多人都以为它是个函数,其实不然,仔细分析一下OS_CPU.H文件,它和下面马上要谈到的OS_EXIT_CRITICAL都是宏。他们都是涉及特定 CPU的实现。由于系统希望向上层程序员隐藏内部实现,故而一般都宣称执行此条指令后系统进入临界区。这个宏能少用还是少用,因为它会破坏系统的一些服务,尤其是时间服务。并使系统对外界响应性能降低。

    5)OS_EXIT_CRITICAL宏

    这个是和上面介绍的宏配套使用另一个宏,它在系统手册里的说明是退出临界区。其实它就是重新开中断。需要注意的是,它必须和上面的宏成对出现,否则会带来意想不到的后果。最坏的情况下,系统会崩溃。我们推荐程序员们尽量少使用这两个宏调用,因为他们的确会破坏系统的多任务性能。

    6)OSTimeDly函数

    这应该程序员们调用最多的一个函数了,这个函数完成功能很简单,就是先挂起当起当前任务,然后进行任务切换,在指定的时间到来之后,将当前任务恢复为就绪状态,如果恢复后是优先级最高就绪任务的话,因为此时仅仅依靠时钟机制在进行任务切换。一个好的任务应该在完成一些操作主动放弃使用权,好东西要大家分享嘛!

    3.我们推荐程序员们尽量少使用OS_ENTER_CRITICAL宏和 OS_EXIT_CRITICAL宏两个宏调用,因为他们的确会破坏系统的多任务性能。why??

    4.在以uC/OS为操作系统的项目中,系统可能要处理各种不同的中断请求,如果某个中断处理程序需要调用uC/OS的各种Post函数向任务发出消息,那么uC/OS建议中断服务程序的写法是:

    1、保存全部CPU寄存器

    2、调用OSIntEnter或OSIntNesting直接加1

    3、执行用户代码做中断服务

    4、调用OSIntExit

    5、恢复所有CPU寄存器

    6、执行中断返回指令

    暂且称为"标准中断"方式,这种方式实际上是将这个中断处理加入了任务调度系统,也就是说这个中断可以引起任务的切换。如果在中断处理中没有调用各种Post函数的话,则可以用一般的、象原来没有操作系统时的

    写法:

    1、保存中断处理程序需要用到的CPU寄存器

    2、执行中断处理

    3、恢复保存了的CPU寄存器

    4、执行中断返回指令

    暂且称为"快中断"方式,按照这种方法定义的中断永远不会引起任务切换。在uC/OS系统中,每个任务都要定义独立的栈空间,一个栈空间的使用包括5个部分:

    1、任务包括的各个函数的调用返回地址

    2、任务包括的各个函数中可能在栈上分配的局部变量

    3、发生了"标准中断"方式定义的中断或任务被挂起时,所要保存的任务上下文

    4、发生了"快中断"方式定义的中断时,中断处理程序所需要的栈空间

    5、中断嵌套时,所要保存的中断嵌套上下文

    在这些使用的部分中,1,2,3,4的内存占用量是比较容易估算的,最精确和保险的确定方法是:查看由C生成的asm文件,并计算各个函数的栈使用量。为每个任务都定义一个充分大的栈空间,这在某些内存稀缺的小项目中是非常痛苦的,

    有时不得不增扩内存,这就会使成本增加。我深入研究了uC/OS后,这样将会大大的降低整个系统对内存的需求。C/OS的任务调度是靠OS_Sched和 OSIntExit来完成的,这两个函数中都要先判断一个叫OSIntNesting的系统变量,如果OSIntNesting不为0,则不进行任务切换。也就是说:在OSIntNesting为1时起,那么在所有嵌套的中断一层一层地都返回直到 OSIntNesting再次为1时止,任务栈是不会切换的。

    据此,我们可以这样改动:设置一个缓冲区OSInterruptStk,作为嵌套中断的栈空间

    由所有任务共享,中断服务程序改为:

    1、保存全部CPU寄存器

    2、调用OSIntEnter或OSIntNesting直接加1

    增加:2.1、判断OSIntNesting是否等于1,如果不是则转到3

    增加:2.2、将栈指针SP保存到OSTCBCur->OSTCBStkPtr

    增加:2.3、将SP指向OSInterruptStk的栈顶(注意栈增长的方向)。

    3、执行用户代码做中断服务

    4、调用OSIntExit

    增加:4.1、判断OSIntNesting是否等于0,如果不是则转到5

    增加:4.2、从OSTCBCur->OSTCBStkPtr中恢复栈指针SP

    5、恢复所有CPU寄存器

    6、执行中断返回指令

    并且要修改OSIntCtxSw函数,原始的OSIntCtxSw函数的写法是:

    1、调整栈指针来去掉在调用:OSIntExit,OSIntCtxSw过程中入栈的多余内容

    2、将当前任务栈指针保存到OSTCBCur中(OSTCBCur->OSTCBStkPtr = __SP__)

    3、如果需要则调用OSTaskSwHook

    4、OSTCBCur = OSTCBHighRdy

    5、OSPrio = OSPrioHighRdy

    6、从OSTCBCur中恢复栈指针(__SP__ = OSTCBCur->OSTCBStkPtr)

    7、恢复保存了的CPU寄存器

    8、执行中断返回指令

    5.注意arm Image for uCOSII for lpc213x 模板中的TargetInit()对于很多使用ZLG arm Image for uCOSII for lpc213x 模板的初学者,常常会置疑使用该模板后自动生成的target.c文件,和在程序中调用的TargetInit()函数,我和 Zgpswh都是如此,这个问题当初困扰了很久:当用户程序中不调用TargetInit()时,发现内核能运行,但是等待机制失灵,调用 TargetInit(),很多硬件中断打不开,后来,在很多热心人的指点下解决了,现重新

    总结如下:

    请仔细察看ZLG模板里的target.c文件,这里的TargetInit()如下:

    void TargetInit(void)

    {

    OS_ENTER_CRITICAL();

    srand((uint32)TargetInit);

    VICInit();

    Timer0Init();

    OS_EXIT_CRITICAL();

    }

    其中的Timer0Init();用于硬件定时器0的初始化,事实上,ZLG的移植代码的μC/OS-Ⅱ的时钟节拍是通过定时器0提供的,不在主程序里调用这个函数,μC/OS-Ⅱ的时钟源就无法打;没有开启时钟源的μC/OS-Ⅱ是同样能运行的,系统虽能将就运行,但因没调用TargetInit()而使内核功能不健全。请注意,TargetInit()中的另一个函数VICInit ()是用来中断的初始化,它其中含有对UART0中断的分配,在用户程序里需要根据使用的硬件中断修改这部分代码,否则,这些硬件中断无法开启;这也就是Zgpswh提到的现象:不调用TargetInit()内核运行异常,调用了却开不了UART0的中断。

    解决的方法如下:

    这在《arm嵌入式系统基础教程》的430页7.4.3节中论述的很清楚:……关键在于把程序与芯片相关中断源挂接,使芯片在产生相应的中断后会调用相应的处理程序。这需要做两方面事情:

    1. 增加汇编接口的支持。……

    2. 初始化向量中断控制器。……

    按照一下方法完成中断源的的挂接:

    1、增加汇编接口的支持。方法是修改IRQ.s文件,在末尾添加本句代码:

    UART0_Handler HANDLER UART0_Exception

    追加定义了通用串口0 中断句柄。

    2、初始化向量中断控制器。将target.c文件中的VICInit()修改如下:

    void VICInit(void)

    { extern void IRQ_Handler(void);

    extern void Timer0_Handler(void);

    extern void UART0_Handler(void);

    VICIntEnClr = 0xffffffff;

    VICDefVectAddr =(uint32)IRQ_Handler;

    VICVectAddr0 =(uint32)Timer0_Handler;

    VICVectCntl0 =(0x20 | 0x04);

    VICIntEnable = 1《4;

    VICVectAddr14 =(uint32)UART0_Handler;

    VICVectCntl14 =(0x20 | 0x06);

    VICIntEnable = 1《6;

    }

    此为初始化向量中断控制器。包括定时器0和串口0,特别要注意的是,一定不可以省略对定时器0的初始化中断里,不可以调用延时请注意,中断里面是不支持等待机制的。问题就可以解决了。中断源挂接正确是没问题的。

    6.问

    void TargetInit(void)

    {

    OS_ENTER_CRITICAL();

    srand((uint32)TargetInit);

    VICInit();

    Timer0Init();

    OS_EXIT_CRITICAL();

    }

    在此单独用srand()函数有什么作用(用了有什么好处,不用又会怎样),一般srand()用于给rand()设定种子(即srand给定rand运算式子的第一个值)。查了FAQ(P22)仅仅说明 seed的译文。

    答2

    你说的没有错,他就是用来设置随机数的种子。

    每次编译一次,void TargetInit(void)函数在Ram或者Flash中的地址都不一样(即种子也不一样)。如果你在程序中不用随机函数rand(),那么srand()在这里对你来说是没有意义的,如果你要用rand(),那么每次编译程序后你的rand()产生的随机数就不一样。当然,你也可以自己种种子咯。

    srand((uint32) TargetInit);是初始化uc/os-ii随机数函数rand();也就是说给随机数一个基值,以后调用相关随机数函数时,随机数就在此基值的基础上改变。只要不调用rand()函数,就可以去掉这句。

    在arm上移植操作系统有一点需要注意:建立任务的任务,最好不要做复杂的工作。频繁的访问其他硬件或者做时序要求比较严的工作容易造成系统死机,希望大家多多注意。我的做法是:将建立任务的任务,闲置起来,但是不能进入死循环。方法

    是:利用一个空邮箱,让任务无限期的等待,这样可以实现与其他任务的切换。

    8.//定义与编译器无关的数据类型

    typedef unsigned char BOOLEAN;//布尔变量

    typedef unsigned char INT8U;//无符号8位整型变量

    typedef signed char INT8S;//有符号8位整型变量

    typedef unsigned short INT16U;//无符号16位整型变量

    typedef signed short INT16S;//有符号16位整型变量

    typedef unsigned int INT32U;//无符号32位整型变量

    typedef signed int INT32S;//有符号32位整型变量

    typedef float FP32;//单精度浮点数(32位长度)

    typedef double FP64;//双精度浮点数(64位长度)

    typedef INT32U OS_STK;//堆栈是32位宽度

    注:这里为什么用typedef,因为如果用#define,那么代码中的每一个相应的类型都会被替代,很有可能会出现问题,毕竟他只是一个替代的关系,且编译时间会增加,而用typedef则不会,它就相当于我们C++里面的引用,一样的思维,在这里面就是说多了一个名称。还有要注意的是我们怎么知道unsigned char 就是无符号8位整型变量,可以arm公司里面下载ADS_CompilerGuide_D.PDF文件,或者在你所装的ADS1.2目录里面有一个文件夹叫PDF,打开它就可

你可能感兴趣的:(L​P​C​2​1​X​X​移​植​U​C​O​S​-​I​I​小​结)