原创 uC/OS-II的简单应用

2009-9-11 19:02 3074 2 2 分类: MCU/ 嵌入式

文章目录   


          1、uC/OS-II简介
              (1)、MAIN函数的结构
              (2)、任务的结构
              (3)、重要uC/OS-II API函数介绍
              (4)、多任务的机制
           2、代码分析
              (1)、启动代码分析
              (2)、相关函数和结构体的介绍
              (3)、任务的创建
              (4)、S3C44B0X定时器介绍
           3、邮箱的实现
              (1)、邮箱的建立
              (2)、程序流程分析
              (3)、仿真演示实验
           4、事件的实现
              (1)、事件的建立
              (2)、程序流程分析
              (3)、仿真演示实验
           5、uC/OS-II实现AD对LM35温度采集
              (1)、uC/OS-II文件目录介绍
              (2)、程序讲解
              (3)、超级终端显示温度
              (4)、编写程序更改超级终端上显示字体和背景的颜色
           6、uCOSII_uCGUI实现彩色液晶显示


 


(一) uC/OS-II 简介


      uC/OS-II是一种基于优先级的可抢先的硬实时内核。自从92年发布以来,在世界各地都获得了广泛的应用,它是一种专门为嵌入式设备设计的内核,目前已经被移植到40多种不同结构的CPU上,运行在从8位到64位的各种系统之上。尤其值得一提的是,该系统自从2.51版本之后,就通过了美国FAA认证,可以运行在诸如航天器等对安全要求极为苛刻的系统之上。鉴于uC/OS-II可以免费获得代码,对于嵌入式RTOS而言,选择uC/OS无疑是最经济的选择。
 
(二) uC/OS-II应用程序基本结构


      应用uC/OS-II,自然要为它开发应用程序,下面论述基于uC/OS-II的应用程序的基本结构以及注意事项。


      kernel提供给用户一些功能函数,使得用户的系统建立更加方便,但是kernel内部不会处理用户的工作,对于整个系统的具体应用工作还得需要用户自己去考虑,如何利用好这些功能服务函数就成为一个比较重要的问题.


 


1. main函数的结构


    void main (void)
    {
    初始化系统的硬件;
    OSInit();
    任务的建立,消息机制的建立;
    OSStart();
    }
      这里需要的是在OSStart()执行之前不得启动中断,硬件系统还不能工作.必
须先让软件系统进入工作状态后才行.


 


2 .任务的结构


    每一个uC/OS-II应用至少要有一个任务。而每一个任务必须被写成无限循环的形式。以下是推荐的结构:
      void task ( void* pdata )
              {
  INT8U err;
  InitTimer(); // 可选
  For( ;; )
  {
      // 你的应用程序代码
      …….
      ……..
      OSTimeDly(1); // 可选
  }
 }
      以上就是基本结构,在任务启动函数执行完后,系统会切换到最高优先级
的任务去执行,此时,可以将系统硬件部分的启动放在该任务的最前边,仅仅是启动时执行一次,主要是启动系统的节拍中断,或者一些必须在多任务系统调度后
才能初始化的部分,使系统的真正开始工作,达到软件硬件的基本同步.
      至于为什么要写成无限循环的形式?那是因为系统会为每一个任务保留一
个堆栈空间,由系统在任务切换的时候换恢复上下文,并执行一条reti 指令返回。如果允许任务执行到最后一个花括号(那一般都意味着一条ret指令)的话,很可能会破坏系统堆栈空间从而使应用程序的执行不确定。换句话说,就
是“跑飞”了。所以,每一个任务必须被写成无限循环的形式。
      现在来谈论上面程序中的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也就大多和多任务息息相关。主要的有以下几类:
 1. 任务类
 2. 同步和消息类
 3. 时间类
 4. 临界区



1. 任务类
 
1) 函数


 

OSTaskCreate函数
      这个函数应该至少再main函数内调用一次,在OSInit函数调用之后调用。
作用就是创建一个任务。目前有四个参数,分别是任务的入口地址,任务的参数,任务堆栈的首地址和任务的优先级。调用本函数后,系统会首先从TCB空闲列表内申请一个空的TCB指针,然后将会根据用户给出参数初始化任务堆栈,并在内部的任务就绪表内标记该任务为就绪状态。最后返回,这样一个任务就创建成功了。


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


 


OSTaskResume函数
  这个函数和上面的函数正好相反,它用于将指定的已经挂起的函数恢复成
就绪状态。如果恢复任务的优先级高于当前任务,那么还为引发一次任务切换。其参数类似OSTaskSuspend函数,为指定任务的优先级。需要特别说明是,本函数并不要求和OSTaskSuspend函数成对使用。


 


2)任务状态


 


睡眠态:
      任务驻留在ROM或RAM中但是还没有交给uC/OS-II来管理。


 


就绪态:
      任务一旦建立就立即进入就绪态,准备运行。任务的建立可以是多任务运
行之前,也可以是多任务运行中。任务中也可以建立另一个任务,如果被建立的任务的优先级高于建立它的任务,它立刻进入运行态。


 


运行态:
      多任务建立后,可以调用OSStart()开始运行多任务,该函数只能在启动
时调用一次。CPU只有一个,任何时刻只有一个任务处于运行态(掌握CPU的使用权)。uC/OS-II是基于优先级调度的,所以要一个任务处于运行态,那么就需要所有优先级高于该任务的任务处于等待状态或者这些高优先级任务被删除了。


 


等待态:
      一种情况是为了避免高优先级的任务称霸CPU,那么就需要周期性的把这
个任务挂起让其他较低的优先级的任务有机会被执行。可以通过调用
OSTimeDly()和OSTimeDlyHMSM()来将任务自身延迟一段时间。
      另外一种情况是当运行着的任务需要等待某一个事件的发生的时候,那么
此时它占着CPU什么也不干太浪费,趁着等待的时刻让其他低优先级的任务运行运行就非常不错了。那么这个高优先级的任务可以根据自己具体的需要调用以下函数是自己处于等待状态:OSFlagPend()、OSSemPend()、OSMutexPend()、OSMboxPend()或OSQPend()。总结一下,都是一些Pend函数。


 


中断服务态:


        正在运行的任务被中断打断的状态。这里需要注意的是,当中断返回后不一定就返回到被打断的任务,因为在uC/OS-II中始终都是以任务的优先级来判定哪个任务该执行。


 


2. 同步和消息类


 


1)信号量


 


      μC/OS-II中的信号量由两部分组成:一个是信号量的计数值,它是一个16位的无符号整数(0 到65,535之间);另一个是由等待该信号量的任务组成的等待任务表。用户要在OS_CFG.H中将OS_SEM_EN开关量常数置成1,这样μC/OS-II才能支持信号量。
      建立一个信号量, OSSemCreate(INT16U cnt )
      等待一个信号量, OSSemPend(OS_EVENT *pevent, INT16U timeout,    
INT8U *err )
      发送一个信号量, OSSemPost(OS_EVENT *pevent )
      无等待地请求一个信号量, OSSemAccept(OS_EVENT *pevent )
      查询一个信号量的当前状态, OSSemQuery()


 


邮箱
      邮箱是μC/OS-II中另一种通讯机制,它可以使一个任务或者中断服务子程
序向另一个任务发送一个指针型的变量。该指针指向一个包含了特定“消息”的数据结构。为了在μC/OS-II中使用邮箱,必须将OS_CFG.H中的OS_MBOX_EN
常数置为1。
       建立一个邮箱,OSMboxCreate()
       等待一个邮箱中的消息,OSMboxPend()
       发送一个消息到邮箱中,OSMboxPost()
       无等待地从邮箱中得到一个消息, OSMboxAccept()
       查询一个邮箱的状态, OSMboxQuery()
       如果用户只需要二值信号量和邮箱,这样做可以节省代码空间。这时可
以将OS_SEM_EN设置为0,只使用邮箱就可以了。


 


3)消息队列


     消息队列实际上是邮箱的集合
     建立一个消息队列 OS_EVENT *OSQCreate (void **start, INT16U size)
     等待一个消息队列中的消息,OSQPend()
     向消息队列发送一个消息(FIFO),OSQPost()
     向消息队列发送一个消息(后进先出LIFO),OSQPostFront()
     无等待地从一个消息队列中取得消息, OSQAccept()
     查询一个消息队列的状态,OSQQuery()


 


3. 时间类


 


OSTimeDly函数


    这应该调用最多的一个函数了,这个函数完成功能很简单,就是先挂起当起当前任务,然后进行任务切换,在指定的时间到来之后,将当前任务恢复为就绪状态,但是并不一定运行,如果恢复后是优先级最高就绪任务的话,那么运行之。简单点说,就是可以任务延时一定时间后再次执行它,或者说,暂时放弃CPU的使用权。一个任务可以不显式的调用这些可以导致放弃CPU使用权的API,但那样多任务性能会大大降低,因为此时仅仅依靠时钟机制在进行任务切换。一个好的任务应该在完成一些操作主动放弃使用权!


 


4. 中断类


 


OS_ENTER_CRITICAL宏


    很多人都以为它是个函数,其实不然,仔细分析一下OS_CPU.H文件,它和下面马上要谈到的OS_EXIT_CRITICAL都是宏。他们都是涉及特定CPU的实现。一般都被替换为一条或者几条嵌入式汇编代码。由于系统希望向上层程序员隐藏内部实现,故而一般都宣称执行此条指令后系统进入临界区。其实,它就是关个中断而已。这样,只要任务不主动放弃CPU使用权,别的任务就没有占用CPU的机会了,相对这个任务而言,它就是独占了。所以说进入临界区了。这个宏能少用还是少用,因为它会破坏系统的一些服务,尤其是时间服务。并使系统对外界响应性能降低。


 


OS_EXIT_CRITICAL宏


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


 


(四)多任务机制 


 


      前面已经说过,uC/OS-II是一种基于优先级的可抢先的多任务内核。那么,它的多任务机制到底如何实现的呢?了解这些原理,可以帮助我们写出更加健壮的代码来。
      首先我们来看看为什么多任务机制可以实现?其实在单一CPU的情况下,
是不存在真正的多任务机制的,存在的只有不同的任务轮流使用CPU,所以本质上还是单任务的。但由于CPU执行速度非常快,加上任务切换十分频繁并且切换的很快,所以我们感觉好像有很多任务同时在运行一样。这就是所谓的多
任务机制。  
      由上面的描述,不难发现,要实现多任务机制,那么目标CPU必须具备一
种在运行期更改PC的途径,否则无法做到切换。不幸的使,直接设置PC指针,目前还没有哪个CPU支持这样的指令。但是一般CPU都允许通过类似JMP,CALL这样的指令来间接的修改PC。我们的多任务机制的实现也正是基于这个出发点。事实上,我们使用CALL指令或者软中断指令来修改PC,主要是软中
断。
       回想一下你在微机原理课程上学过的知识,当发生中断的时候,CPU保存
当前的PC和状态寄存器的值到堆栈里,然后将PC设置为中断服务程序的入口地址,再下来一个机器周期,就可以去执行中断服务程序了。执行完毕之后,一般都是执行一条RETI指令,这条指令会把当前堆栈里的值弹出恢复到状态寄存器和PC里。这样,系统就会回到中断以前的地方继续执行了。那么设想一下?如果再中断的时候,人为的更改了堆栈里的值,那会发生什么?或者通过更改当前堆栈指针的值,又会发生什么呢?如果更改是随意的,那么结果是无法预料的错误。因为我们无法确定机器下一条会执行些什么指令,但是如果更改是计划好的,按照一定规则的话,那么我们就可以实现多任务机制。事实上,这就是目前几乎所有的OS的核心部分。不过他们的实现不像这样简单罢
了。
      下面,我们来看看uC/OS-II再这方面是怎么处理的。在uC/OS-II里,每个
任务都有一个任务控制块(Task Control Block),这是一个比较复杂的数据结构。在任务控制快的偏移为0的地方,存储着一个指针,它记录了所属任务的专用堆栈地址。事实上,再uC/OS-II内,每个任务都有自己的专用堆栈,彼此之间不能侵犯。这点要求程序员再他们的程序中保证。一般的做法是把他们申明成静态数组。而且要申明成OS_STK类型。当任务有了自己的堆栈,那么就可以将每一个任务堆栈再那里记录到前面谈到的任务控制快偏移为0的地方。以后每当发生任务切换,系统必然会先进入一个中断,这一般是通过软中断或者时钟中断实现。然后系统会先把当前任务的堆栈地址保存起来,仅接着恢复要切换的任务的堆栈地址。由于哪个任务的堆栈里一定也存的是地址(还记得我们前面说过的,每当发生任务切换,系统必然会先进入一个中断,而一旦中断CPU就会把地址压入堆栈),这样,就达到了修改PC为下一个任务的地址的目
的。



——摘自天祥电子学ARM和学单片机一样简单
PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
我要评论
0
2
关闭 站长推荐上一条 /1 下一条