2022年uC-OS-II系统开发 .pdf
ucos 初级程序员指南(转)本文面向首次接触uC/OS-II的程序员, 为他们介绍一下这个系统的一些基本特征和编程上的注意事项,并介绍几个值得了解的API。本文作者已经成功的将uC/OS-II移植到几种不同 CPU之上。包括EPSON S1C33 和 Sunplus unSP?等,积累了丰富的经验,现在愿意和朋友们分享这些经历。希望本文的资料对于希望使用这个系统来开发的朋友有所帮助,作者乐意与您分享任何您成功的喜悦。This passage is written for the basic programmers who are first developed with the uC/OS-II real time OS 。I will talk about the basic structure of this system 。And I will discuss how to use some of the useful API。I will also discuss the imp of the mutilty-tasking in uC/OS-II。(一) uC/OS-II 简介uC/OS-II是一种基于优先级的可抢先的硬实时内核。自从年发布以来,在世界各地都获得了广泛的应用,它是一种专门为嵌入式设备设计的内核,目前已经被移植到多种不同结构的CPU上,运行在从位到位的各种系统之上。尤其值得一提的是,该系统自从 .51 版本之后,就通过了美国FAA认证,可以运行在诸如航天器等对安全要求极为苛刻的系统之上。鉴于uC/OS-II可以免费获得代码,对于嵌入式RTOS 而言,选择uC/OS无疑是最经济的选择。(二) uC/OS-II 应用程序基本结构应用 uC/OS-II ,自然要为它开发应用程序,下面论述基于uC/OS-II的应用程序的基本结构以及注意事项。每一个 uC/OS-II应用至少要有一个任务。而每一个任务必须被写成无限循环的形式。以下是推荐的结构:void task ( void* pdata ) INT8U err; InitTimer(); / 可选For( ; ) / 你的应用程序代码名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 6 页 - - - - - - - - - ,. ,. OSTimeDly(1); / 可选 以上就是基本结构,至于为什么要写成无限循环的形式呢?那是因为系统会为每一个任务保留一个堆栈空间,由系统在任务切换的时候换恢复上下文,并执行一条reti 指令返回。如果允许任务执行到最后一个花括号(那一般都意味着一条ret指令)的话,很可能会破坏系统堆栈空间从而使应用程序的执行不确定。换句话说,就是“跑飞”了。所以,每一个任务必须被写成无限循环的形式。程序员一定要相信,自己的任务是会放弃CPU使用权的,而不管是系统强制(通过ISR)还是主动放弃( 通过调用 OS API) 。现在来谈论上面程序中的InitTimer()函数,这个函数应该由系统提供,程序员有义务在优先级最高的任务内调用它而且不能在for 循环内调用。 注意, 这个函数是和所使用的 CPU相关的,每种系统都有自己的Timer 初始化程序。在uC/OS-II的帮助手册内,作者特地强调绝对不能在OSInit()或者 OSStart()内调用 Timer 初始化程序, 那会破坏系统的可移植性同时带来性能上的损失。所以,一个折中的办法就是象上面这样,在优先级最高的程序内调用,这样可以保证当OSStart()调用系统内部函数OSStartHighRdy()开始多任务后,首先执行的就是Timer 初始化程序。 或者专门开一个优先级最高的任务,只做一件事情,那就是执行Timer 初始化,之后通过调用OSTaskSuspend() 将自己挂起来,永远不再执行。不过这样会浪费一个TCB空间。对于那些RAM 吃紧的系统来说,还是不用为好。(三)一些重要的uC/OS-II API介绍任何一个操作系统都会提供大量的API 供程序员使用,uC/OS-II也不例外。由于uC/OS-II面向的是嵌入式开发,并不要求大而全,所以内核提供的API 也就大多和多任务息息相关。主要的有以下几类:)任务类)消息类)同步类)时间类)临界区与事件类名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 6 页 - - - - - - - - - 我个人认为对于初级程序员而言,任务类和时间类是必须要首先掌握的两种类型的 API。下面我就来介绍比较重要的:) OSTaskCreate函数这个函数应该至少再main 函数内调用一次,在OSInit函数调用之后调用。作用就是创建一个任务。目前有四个参数,分别是任务的入口地址,任务的参数,任务堆栈的首地址和任务的优先级。调用本函数后, 系统会首先从TCB空闲列表内申请一个空的TCB指针,然后将会根据用户给出参数初始化任务堆栈,并在内部的任务就绪表内标记该任务为就绪状态。最后返回,这样一个任务就创建成功了。) OSTaskSuspend 函数这个函数很简单,一看名字就该明白它的作用,它可以将指定的任务挂起。如果挂起的是当前任务的话,那么还会引发系统执行任务切换先导函数OSShed来进行一次任务切换。这个函数只有一个参数,那就是指定任务的优先级。那为什么是优先级呢?事实上在系统内部, 优先级除了表示一个任务执行的先后次序外,还起着分别每一个任务的作用,换句话说,优先级也就是任务的ID。所以 uC/OS-II不允许出现相同优先级的任务。) OSTaskResume函数这个函数和上面的函数正好相反,它用于将指定的已经挂起的函数恢复成就绪状态。如果恢复任务的优先级高于当前任务,那么还为引发一次任务切换。其参数类似OSTaskSuspend 函数,为指定任务的优先级。需要特别说明是,本函数并不要求和OSTaskSuspend 函数成对使用。) OS_ENTER_CRITICAL宏很多人都以为它是个函数,其实不然,仔细分析一下OS_CPU.H文件,它和下面马上要谈到的OS_EXIT_CRITICAL都是宏。他们都是涉及特定CPU的实现。一般都被替换为一条或者几条嵌入式汇编代码。由于系统希望向上层程序员隐藏内部实现,故而一般都宣称执行此条指令后系统进入临界区。其实,它就是关个中断而已。这样,只要任务不主动放弃CPU使用权,别的任务就没有占用CPU的机会了,相对这个任务而言,它就是独占了。所以说进入临界区了。这个宏能少用还是少用,因为它会破坏系统的一些服务,尤其是时间服务。并使系统对外界响应性能降低。) OS_EXIT_CRITICAL 宏这个是和上面介绍的宏配套使用另一个宏,它在系统手册里的说明是退出临界区。其实它就是重新开中断。需要注意的是,它必须和上面的宏成对出现,否则会带来意想不到的后果。最坏的情况下,系统会崩溃。我们推荐程序员们尽量少使用这两个宏调用,因为他名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 3 页,共 6 页 - - - - - - - - - 们的确会破坏系统的多任务性能。) OSTimeDly 函数这应该程序员们调用最多的一个函数了,这个函数完成功能很简单,就是先挂起当起当前任务,然后进行任务切换,在指定的时间到来之后,将当前任务恢复为就绪状态,但是并不一定运行,如果恢复后是优先级最高就绪任务的话,那么运行之。简单点说,就是可以任务延时一定时间后再次执行它,或者说, 暂时放弃CPU的使用权。 一个任务可以不显式的调用这些可以导致放弃CPU使用权的API,但那样多任务性能会大大降低,因为此时仅仅依靠时钟机制在进行任务切换。一个好的任务应该在完成一些操作主动放弃使用权,好东西要大家分享嘛!(四) uC/OS-II 多任务实现机制分析前面已经说过,uC/OS-II是一种基于优先级的可抢先的多任务内核。那么,它的多任务机制到底如何实现的呢?了解这些原理,可以帮助我们写出更加健壮的代码来。由于我们面向的初级程序员,本文不打算写成又一篇uC/OS-II的源码分析, 那样的文章太多了,本文打算从实现原理的角度探讨这个问题。首先我们来看看为什么多任务机制可以实现?其实在单一CPU的情况下,是不存在真正的多任务机制的,存在的只有不同的任务轮流使用CPU ,所以本质上还是单任务的。但由于 CPU执行速度非常快,加上任务切换十分频繁并且切换的很快,所以我们感觉好像有很多任务同时在运行一样。这就是所谓的多任务机制。由上面的描述,不难发现,要实现多任务机制,那么目标CPU必须具备一种在运行期更改PC的途径,否则无法做到切换。不幸的使,直接设置PC指针,目前还没有哪个CPU支持这样的指令。 但是一般CPU都允许通过类似JMP , CALL这样的指令来间接的修改PC 。我们的多任务机制的实现也正是基于这个出发点。事实上,我们使用CALL指令或者软中断指令来修改PC ,主要是软中断。但在一些CPU上,并不存在软中断这样的概念,所以,我们在那些CPU上,使用几条PUSH 指令加上一条CALL指令来模拟一次软中断的发生。回想一下你在微机原理课程上学过的知识,当发生中断的时候,CPU保存当前的PC和状态寄存器的值到堆栈里,然后将PC设置为中断服务程序的入口地址,再下来一个机器周期,就可以去执行中断服务程序了。执行完毕之后,一般都是执行一条RETI 指令,这条指令会把当前堆栈里的值弹出恢复到状态寄存器和PC里。这样,系统就会回到中断以前的地方继续执行了。那么设想一下?如果再中断的时候,人为的更改了堆栈里的值,那会发生什么?或者通过更改当前堆栈指针的值,又会发生什么呢?如果更改是随意的,那么结果是无法预料的错误。因为我们无法确定机器下一条会执行些什么指令,但是如果更改是计划好的,按照一定规则的话,那么我们就可以实现多任务机制。事实上,这就是目前几乎所有的 OS的核心部分。不过他们的实现不像这样简单罢了。下面,我们来看看uC/OS-II再这方面是怎么处理的。再uC/OS-II里,每个任务名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 4 页,共 6 页 - - - - - - - - - 都有一个任务控制块(Task ControlBlock) ,这是一个比较复杂的数据结构。在任务控制快的偏移为0 的地方,存储着一个指针,它记录了所属任务的专用堆栈地址。事实上,再uC/OS-II内,每个任务都有自己的专用堆栈,彼此之间不能侵犯。这点要求程序员再他们的程序中保证。一般的做法是把他们申明成静态数组。而且要申明成OS_STK类型。当任务有了自己的堆栈, 那么就可以将每一个任务堆栈再那里记录到前面谈到的任务控制快偏移为0 的地方。以后每当发生任务切换,系统必然会先进入一个中断,这一般是通过软中断或者时钟中断实现。然后系统会先把当前任务的堆栈地址保存起来,仅接着恢复要切换的任务的堆栈地址。 由于哪个任务的堆栈里一定也存的是地址(还记得我们前面说过的,每当发生任务切换,系统必然会先进入一个中断,而一旦中断CPU就会把地址压入堆栈),这样,就达到了修改PC为下一个任务的地址的目的。以上就是uC/OS-II的多任务实现机制,我们在这里大费笔墨谈论这个问题,是希望我们的程序员们可以善加利用这个机制,写出更健壮,更富有效率的代码来。uCOS-II移植的一点心得uCOS-II 是一种十分优秀实时操作系统,其在NASA 的认证通过直接说明了其优秀及稳健的性能,同时由于其完全open,所以受到广大开源爱好者的喜爱。uCOS-II 简单明了,同时绝大部分代码都采用ANSI C 编写(除了与 CPU 相关代码外),所以学习起来十分容易,是嵌入式学习乃至操作系统学习最好的入门OS之一。我主要想讲一下自己最近移植uCOS-II 的心得,因为最近也在学习操作系统,所以这段日子对于 uCOS-II 的学习的确也让我对于操作系统有了一个实际深刻的认识。uCOS-II 移植其实十分简单。对于一个处理器,需要做的工作只有:修改三个文件os_cpu_c.c 、os_cpu.h 、os_cpu_a.asm(ASM文件根据编译器不同而又有一些不同)。用另一种方式说,需要做的工作就是修改五个函数:1、os_cpu_c.c :OSTaskStkInit;2、os_cpu_a.asm :OSStartHighRdy、OSCtxSw、OSIntCtxSw、OSTickISR;OSTaskStkInit 函数是针对 CPU 压栈的函数,需要模仿出CPU 初始化后的寄存器状况。也使需要修改的唯一一个C 语言函数。其他的都是汇编函数。如果我们可以从 uCOS-II 官方网站上找到相同CPU或是相似的同一家族的CPU 移植代码,那么我们的移植工作将会简单得多。因为至少我们可以只用了解这个处理器的内部结构,而不用细致的了解其汇编指令等很多繁琐而没有意义的事情(有的处理器你可能一辈子不再用它,了解得太细致只是在浪费时间)。譬如,此次我要做的是将uCOS-II 移植到瑞萨 M16C/62A 上,而官方网站上只有其62P的移植代码,于是乎我就将二者的datasheet在 CPU 的寄存器、中断部分仔细比对,发现二者区别很小。最大的区别恐怕就在CPU 内部寄存器中的 INTBL 和 PC寄存器二者顺序相反吧,这只要在相关部分注意就可以了,所以很容易就搞定了CPU 相关代码部分。总结一下移植中浪费我时间的几个小错误吧,这完全是个人粗心导致的失误:1、在 os_cpu.h文件中需要用宏定义将OS_TASK_SW 指向 OSCtxSw 函数,而我开始像以前一样直接将 OSCtxSw函数与 0 号软终端链接起来,结果发现函数调用不成功。后来直接用宏定义将 OS_TASK_SW define 为 OSCtxSw 函数,初步调试通过, 即验证 OSCtxSw 函数正确,名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 5 页,共 6 页 - - - - - - - - - 但是到后来调用任务的时候却发现任务切换不正常,不得不重新将函数与中断结合起来,当然就不能还是 0 号中断了,而是改为一个比较保险的软终端,这个问题纠结了很长时间。2、在看书的时候不仔细直接导致我犯了一个大错误。起初以为task只要是能够达到功能的死循环即可。所以每个task函数都是 while(1)或者 for(;) ,但是我没有注意到一点就是每个task 里面都应该有 OSTimeDly()函数,否则将导致任务之间不能跳转。所以最初的实验现象是永远只有一个任务在运行,但是任务不能切换,3、 未注意到版本之间的区别。 我们知道在新版本的uCOS-II 中,添加了一个文件 os_tmr.c,主要是在 timer 上面做了很大的调整, 但是我没有注意到这一点, 仍旧按照老版本的方法调试,导致函数调用让我完全不知所措。最后注意到os的源代码的不同,仔细阅读源代码之后知道了其用法,其实如果不需要timer 太强大的功能,只要在os_cfg.h文件中将 OS_TMR_EN 设置为 0 即可。这在习惯老版本调试方法的同学而言是很好的方法。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页,共 6 页 - - - - - - - - -