打印

ucOS学习笔记

[复制链接]
5360|36
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wangdezhi|  楼主 | 2014-2-25 17:23 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
1.操作系统可以显著降低开发难度。操作系统帮我们协调多个程序之间的耦合关系,比如我们需要将串口的接收到的数据显示在一块LCD上。传统做法是如图1所示 :


图1.无操作系统流程

该流程中当串口接收到数据再调用LCD显示程序将数据显示出来,而图2展示了一种基于操作系统的方法。在该方法中串口接收数据和LCD显示程序均以为自己独占CPU,各自都只是执行自己相关部分的工作,而什么时候显示到LCD屏幕,什么时候又继续接收串口数据这个协调工作将由操作系统完成。


图2 操作系统流程




沙发
wangdezhi|  楼主 | 2014-2-25 17:24 | 只看该作者
从以上比较可以看出操作系统协调了不同功能程序以让他们共同完成同一个工作。同时操作系统又隔离了各个功能程序让它们的耦合程度降低。这样就方便设计人员编写各个功能模块,同时整个系统的结构也更加清晰。特别是系统逻辑结构复杂,功能模块较多的情况下操作系统的这一优点体现的更加明显。

2.操纵系统让每一个任务都认为自己独占CPU,方便代码编写。
同样采用上边的例子,无操作系统情况下我么需要在适当的时候分别调用串口接收数据程序和LCD显示程序,而有操作系统时我们只需要完成两个功能模块代码然后加入到操作系统就可以了。两个功能代码都是以无限循环的方式执行,结构显得就很简单。

3.操作系统增加代码的移植性。
这一点我认为在ucOS系统上体现得并不明显,对于其他系统例如android,linux等系统可以这样讲,因为他们基本上应该算是一个中等复杂系统,而ucOS是一个简单嵌入式系统。对于一个中等复杂系统它的底层硬件设备是有一定要求的,例如android设备,根据系统特性它就需要有GPS支持,显示屏支持,电子罗盘支持,摄像头支持这些支持都被列入android设备的系统内,它们以驱动的形式存在。而上层的应用程序通过调用底层的支持进而实现复杂的功能。而ucOS我认为它仅仅只是一个调度器,他的工作就是协调多个应用程序在单个MCU上"同时"运行而已,它几乎是没有驱动这个概念的。

使用特权

评论回复
板凳
wangdezhi|  楼主 | 2014-2-25 17:24 | 只看该作者
多任务是如何实现的  

使用特权

评论回复
地板
wangdezhi|  楼主 | 2014-2-25 17:24 | 只看该作者
ucOS是一个抢占式多任务操作系统,其核心就是人物调度机制,该机制保证了多个任务在一个MCU上并发执行。
关于多任务实现我们就不得不谈一谈一段程序运行的上下文。所谓程序运行的上下文就是指一段代码(一般以函数为基本单位)运行过程中需要使用到的资源,这个资源被我称之为上下文。这些资源包括当前系统的基本工作寄存器,函数使用到的零时变量,全局变量等等。当这些资源被给定后我们无论在何时去执行一个给定的地址开始的代码都将得到完全相同的结果。下边以具体代码为例讲解该过程,以下代码get_val为一个计算1到10累加和的函数,C代码如下:
unsigned char get_val(void)
{
  unsigned char i = 0;
  unsigned char temp = 0;

  for(i = 0; i < 10; i++)
    temp += i;

  return temp;
}
要了解上下文信息我们需要查看汇编代码,汇编代码如下:          get_val:
C:0x0003    E4       CLR      A
C:0x0004    FF       MOV      R7,A
C:0x0005    FE       MOV      R6,A
C:0x0006    EF       MOV      A,R7
C:0x0007    2E       ADD      A,R6
C:0x0008    FE       MOV      R6,A
C:0x0009    0F       INC      R7
C:0x000A    BF0AF9   CJNE     R7,#0x0A,C:0006
C:0x000D    AF06     MOV      R7,0x06
C:0x000F    22       RET  

使用特权

评论回复
5
wangdezhi|  楼主 | 2014-2-25 17:25 | 只看该作者
查看汇编代码我们发现该函数使用的系统资源仅仅为累加器A,寄存器R7,R6。因此在任何时间我们离开该函数,只要在重新执行函数时恢复离开时的上下文,那么函数执行完毕都能够得到正确的结果。例如当执行到C:0x0008处(0008指令还没有被执行),此时我们由于某种原因改变了PC的值指向了代码段Q(操作系统称这一过程为任务切换),当Q代码段执行完毕后再次返回到0008处执行,此时如果恢复R7,R6,和A那么我们将得到一致的结果。操作系统就是通过在改变PC时保存当前上下文并装载目标PC处的上下文的方式实现了多任务并发执行。当然,操作系统所进行的上下文保存就并不只是保存R6,R7了,它不会根据哪个具体的任务是用了哪些具体资源而保存这个任务,它会采用一个统一的方式把任何任务可能是用到的任何资源都保存起来,虽然这样浪费了系统的存储空间但是它缺保持了任务切换的统一性和简单性。

在保存上下文时有几个需要注意的问题:
1.是否保存独占资源的上下文?比如系统当前一共有20个任务,只有任务8使用定时器2,那么在保存上下文时需要保存定时器2的相关配置么?
2.保存上下文时不会保存全局变量,因为全局变量本身就是用来传递信息用的,允许在其他地方被改变从而带给当前程序信息。
3.函数内部的零时变量不需要保存上下文,因为该变量时被存储到函数自身的栈上的。

使用特权

评论回复
6
wangdezhi|  楼主 | 2014-2-25 17:25 | 只看该作者
这样系统根据某一种机制在特定时刻保存当前上下文切换的目标代码执行实现了多任务,这种机制被称为任务调度算法。 ucOS通过两种途径实现任务调度:中断和任务调用等待函数。 中断又包括系统tick中断和其他任务中断。

使用特权

评论回复
7
wangdezhi|  楼主 | 2014-2-25 17:25 | 只看该作者
ucOS的数据结构中最核心的一个数据结构就是任务控制块数据结构,其他的数据类型都是围绕该数据结构展开的,任务切换,代码调度也都是以该数据结构为基础来完成的。认清了该数据结构就了解了ucOS的运行机制。任务控制块数据结构如下:

typedef struct os_tcb {
    OS_STK          *OSTCBStkPtr;           /* Pointer to current top of stack                         */

#if OS_TASK_CREATE_EXT_EN > 0
    void            *OSTCBExtPtr;           /* Pointer to user definable data for TCB extension        */
    OS_STK          *OSTCBStkBottom;        /* Pointer to bottom of stack                              */
    INT32U           OSTCBStkSize;          /* Size of task stack (in number of stack elements)        */
    INT16U           OSTCBOpt;              /* Task options as passed by OSTaskCreateExt()             */
    INT16U           OSTCBId;               /* Task ID (0..65535)                                      */
#endif

    struct os_tcb   *OSTCBNext;             /* Pointer to next     TCB in the TCB list                 */
    struct os_tcb   *OSTCBPrev;             /* Pointer to previous TCB in the TCB list                 */

#if (OS_EVENT_EN) || (OS_FLAG_EN > 0)
    OS_EVENT        *OSTCBEventPtr;         /* Pointer to          event control block                 */
#endif

#if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0)
    OS_EVENT       **OSTCBEventMultiPtr;    /* Pointer to multiple event control blocks                */
#endif

使用特权

评论回复
8
wangdezhi|  楼主 | 2014-2-25 17:26 | 只看该作者

#if ((OS_Q_EN > 0) && (OS_MAX_QS > 0)) || (OS_MBOX_EN > 0)
    void            *OSTCBMsg;              /* Message received from OSMboxPost() or OSQPost()         */
#endif

#if (OS_FLAG_EN > 0) && (OS_MAX_FLAGS > 0)
#if OS_TASK_DEL_EN > 0
    OS_FLAG_NODE    *OSTCBFlagNode;         /* Pointer to event flag node                              */
#endif
    OS_FLAGS         OSTCBFlagsRdy;         /* Event flags that made task ready to run                 */
#endif

    INT16U           OSTCBDly;              /* Nbr ticks to delay task or, timeout waiting for event   */
    INT8U            OSTCBStat;             /* Task      status                                        */
    INT8U            OSTCBStatPend;         /* Task PEND status                                        */
    INT8U            OSTCBPrio;             /* Task priority (0 == highest)                            */

    INT8U            OSTCBX;                /* Bit position in group  corresponding to task priority   */
    INT8U            OSTCBY;                /* Index into ready table corresponding to task priority   */
#if OS_LOWEST_PRIO <= 63
    INT8U            OSTCBBitX;             /* Bit mask to access bit position in ready table          */
    INT8U            OSTCBBitY;             /* Bit mask to access bit position in ready group          */
#else
    INT16U           OSTCBBitX;             /* Bit mask to access bit position in ready table          */
    INT16U           OSTCBBitY;             /* Bit mask to access bit position in ready group          */
#endif

使用特权

评论回复
9
wangdezhi|  楼主 | 2014-2-25 17:26 | 只看该作者
#if OS_TASK_DEL_EN > 0
    INT8U            OSTCBDelReq;           /* Indicates whether a task needs to delete itself         */
#endif

#if OS_TASK_PROFILE_EN > 0
    INT32U           OSTCBCtxSwCtr;         /* Number of time the task was switched in                 */
    INT32U           OSTCBCyclesTot;        /* Total number of clock cycles the task has been running  */
    INT32U           OSTCBCyclesStart;      /* Snapshot of cycle counter at start of task resumption   */
    OS_STK          *OSTCBStkBase;          /* Pointer to the beginning of the task stack              */
    INT32U           OSTCBStkUsed;          /* Number of bytes used from the stack                     */
#endif

#if OS_TASK_NAME_SIZE > 1
    INT8U            OSTCBTaskName[OS_TASK_NAME_SIZE];
#endif
} OS_TCB;
其中
OSTCBStkPtr--任务栈的指针,在任务中使用到的所有零时变量(包括任务切换时需要保存的上下文)都保存在OSTCBStkPtr指向的存储区域
OSTCBExtPtr--任务扩展数据信息指针,目前我理解这一部分主要用于保存任务名字的ASCII码
OSTCBStkBottom--任务栈底指针
OSTCBStkSize--任务栈的大小
OSTCBNext--任务控制块链表中的下一任务指针
OSTCBPrev--任务控制块链表中的上一任务指针
OSTCBEventPtr--任务等待时间指针,该数据结构本身是属于一个事件的,任务也只是属于事件的一个候选资源
OSTCBEventMultiPtr--互斥信号量指针
OSTCBMsg--邮箱指针

使用特权

评论回复
10
wangdezhi|  楼主 | 2014-2-25 17:26 | 只看该作者
任务私有栈--是任务执行过程或者任务数据切换过程用于存放零时数据的一片内存区域
任务链表--为了便于管理ucOS将多个任务用链表的方式串接起来,这个链表称为任务链表
空任务链表--也是为了便于内存分配,ucOS在编译的时候就决定了整个系统最多支持多少个任务,同时就为这些任务预留了相应的任务控制块,这部分任务控制块就被称为空任务链表。在真正需要使用任务的时候,将从空任务链表中获取任务控制块到任务控制链表中
当前运行任务指针--指向当前正在运行的任务控制块
任务链表索引--为了更快的查找任务链表,ucOS建立了一个指向所有任务控制块的数组,这个数组就是任务链表索引。该索引以优先级排序
任务就绪表--已经就绪的任务表
任务就绪表索引
任务就绪表辅助登记表--一下三张表都是ucOS为了查找任何一个任务都使用相同的查找时间使用的额外表,它们的唯一作用就是保证系统的时效性
任务就绪表辅助注销表
任务就绪表辅助查找表


全局变量不完全统计:
OSLockNesting
OSIntNesting
OSCtxSwCtr
OSPrioHighRdy
OSPrioCur
OSTCBHighRdy
OSTCBCur
OSTaskCtr
OSRunning

使用特权

评论回复
11
wangdezhi|  楼主 | 2014-2-25 17:27 | 只看该作者
准备工作:
1.到micrium官网下载最新的 OS在stm32上的移植资料。下载地址为:http://micrium.com/download/Micrium-ARM-OS-II-Cortex-M3.exe

2.平台搭建:
a.将1下载得到的文件解压得到micrium文件夹,并在Micrium\Software\ OS-II下用UV4创建一个 OS工程,配置CPU为STM32F101C8

b.建立如图1所示的工程目录结构。其中APP层用于放置应用程序, OS用于放置所有 OS与处理器无关的源码,PORT用于放置移植 OS需要改动的文件,而BSP则用于放置系统的驱动程序,LIB为系统调用的库支持。该目录组织依据来源于micrium公布的 OS移植文档,图2是该移植文档的系统软件结构图供参考。

使用特权

评论回复
12
wangdezhi|  楼主 | 2014-2-25 17:27 | 只看该作者

图1 OS的目录结构

使用特权

评论回复
13
wangdezhi|  楼主 | 2014-2-25 17:27 | 只看该作者



图2 OS系统软件结构

使用特权

评论回复
14
wangdezhi|  楼主 | 2014-2-25 17:28 | 只看该作者
c.工程建立完毕以后将Micrium\Software\ OS-II\Source目录下的所有文件(包括h文件)添加到 OS组,将Micrium\Software\ OS-II\Ports\ARM-Cortex-M3\Generic\RealView目录下的所有文件添加到PORT组

d.完成以上工作后进行一次编译,我们可以发现出现错误提示
.Source\os_core.c(26): error:  #5: cannot open source input file " os_ii.h": No s h file or directory
该问题是包含文件路径引起的, os_ii.h文件实际上存在于Micrium\Software\ OS-II\Source目录下。因此我们需要在工程设置中的C/C++选项卡手动增加头文件路径,针对以上错误我们应该增加的头文件路径未.\Source

e.再次编译发生错误
.\Source\ os_ii.h(44): error:  #5: cannot open source input file "app_cfg.h": No s h file or directory
这个问题应该不是原生 OS导致的,是micrium移植系统的时候修改了源文件,在其中增加了一些配置选项,这个配置选项保存在app_cfg.h中,但是micrium发布移植文件包的时候该文件又没有包含在内,因此需要我们自己写一个配置文件。本文为了方便就不细致研究文件内容,直接从micrium的开发板STM3210B-EVAL源码包中拷贝该文件放置于工程目录下的APP文件夹中,并设置相应的包含路径。

使用特权

评论回复
15
wangdezhi|  楼主 | 2014-2-25 17:28 | 只看该作者
f.再次编译发生错误
.\Source\ os_ii.h(45): error:  #5: cannot open source input file "os_cfg.h": No s h file or directory
在micrium的移植文件包中也没有这个文件,但是在目录Micrium\Software\ OS-II\Source下存在一个文件名为os_cfg_r.h的文件,将该文件名后的r去掉解决。

g.再次编译发生错误
.\Source\ os_ii.h(46): error:  #5: cannot open source input file "os_cpu.h": No s h file or directory
该文件实际上存在于Micrium\Software\ OS-II\Ports\ARM-Cortex-M3\Generic\RealView目录下,因此可以通过修改文件包含路径解决。

h.再次编译发生错误
.\OUTPUT\ OSII.sct(7): error: L6236E: No section matches selector - no section to be FIRST/LAST.
点击该错误提示出现以下内容:
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00010000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00010000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
  }
  RW_IRAM1 0x20000000 0x00002800  {  ; RW data
   .ANY (+RW +ZI)
  }
}

使用特权

评论回复
16
wangdezhi|  楼主 | 2014-2-25 17:28 | 只看该作者
实际上这个错误发生在连接阶段,从点击错误得到的信息看表示连接的时候找不到一个标号为RESET的段,而keil默认连接是从RESET段开始的,因此出现了这个错误。对于这个错误的解决方法我们可以通过e的方式,将开发板软件包内的init.s文件拷贝到APP目录,并添加到工程中,同时把init.s文件中第一个段改名由INIT为RESET解决。

i.再次编译发现在连接的时候很多以hook结尾的代码找不到实体,但是有声明。错误示例如下:
.\OUTPUT\ OSII.axf: Error: L6218E: Undefined symbol App_TCBInitHook (referred from os_cpu_c.o).
这些函数实际上都是 OS为了方便用户监视系统运行过程而保留的钩子函数, OS内部的函数名实际上是OSTCBInitHook,micrium在移植过程中为了层次清晰,在 OS钩子函数内部增加了相应的APP***HOOK调用,同时通过功能开关OS_APP_HOOKS_EN来控制是否启用这些功能。我们当前的代码除了 OS系统代码,其他什么应用代码都还没有,因此编译会出错。这种情况下我们需要暂时关闭这个功能将在OS_CFG.h中的OS_APP_HOOKS_EN配置为0。

j.再次编译发现系统提示没有main函数,增加一个main.c文件解决。

使用特权

评论回复
17
wangdezhi|  楼主 | 2014-2-25 17:29 | 只看该作者
k.再次编译发生错误:
.\OUTPUT\ OSII.axf: Error: L6218E: Undefined symbol OS_CPU_SysTickClkFreq (referred from os_cpu_c.o).
这个错误时系统找不到OS_CPU_SysTickClkFreq这个函数实体。查阅相关资料发现micrium增加这个函数的目的是获取系统当前的时钟频率,于是我们在工程中的BSP组增加一个bsp.c文件,同时实现这个OS_CPU_SysTickClkFreq函数即可。未测试需要,本函数简单写为
INT32U     OS_CPU_SysTickClkFreq(void)
{
  return 8000000;
}

l.再次编译已经没有错误发生,只有一个警告信息
.\OUTPUT\ OSII.axf: Warning: L6305W: Image does not have an entry point. (Not specified or not set d to multiple choices.)
通过查阅资料,大致意思就是说keil不知道整个代码的入口在哪儿,根据init.s中的代码
ENTRY
ResetHndlr      

;******************************************************************************
;                              SETUP STACK POINTERS
;******************************************************************************
               

;******************************************************************************
;                                   MOVE TO MAIN
;******************************************************************************
                ldr     r0, =__main
                bx      r0                                     ; Save this in register for possible long jump              ;

                ALIGN
                END

使用特权

评论回复
18
wangdezhi|  楼主 | 2014-2-25 17:29 | 只看该作者
可以看到我们的入口在标号为ResetHndlr的地方,因此我们通过指定入口解决该问题。指定入口的方法是在编译器选项的linker页增加命令
--entry ResetHndlr

使用特权

评论回复
19
wangdezhi|  楼主 | 2014-2-25 17:30 | 只看该作者
1.关于AREA--AREA是arm汇编中的段标志,它代表一个段的开始。所谓段是指一个独立的,被命名的,不可分割一组代码或者数据,一个单独的代码段是一个应用功能的最小单元。原文如下“sections are independent, named, indivisible seqnces of code or data. A single code section is the minimum required to prod? an application”。
2.关于ENTRY--ENDTRY英文原意是入口的意思,在汇编语言中它代表了一个代码段中被执行的第一条指令的位置。如果代码中有C语言代码,C的初始化库就已经包含了entry,一般这个entry就是main函数的入口,转换为标识符就是__main所在的位置。在一个文件中如果包含了多个ENTRY连接过程将报错。这也就解释了学习笔记4中编译报错的问题。知道原因我们可以直接去掉init.s中的ENTRY,再编译就直接OK了。

OS学习笔记(4)中已经可以完成编译了,但这个代码是无**常运行的。因为一般的ARM代码都由四个部分组成,第一部分为初始化代码,包括堆栈初始化等操作,第二部分是中断向量表,第三部分是函数主体,这一部分一般是用C或者C++写成,最后一部分为中断代码。前边的移植编译工作基本上把第一部分,第三部分做了,还有中断向量表和中断函数没有完成。于是接下来的工作就是将这两部分代码添加到工程中。

使用特权

评论回复
20
wangdezhi|  楼主 | 2014-2-25 17:30 | 只看该作者
添加中断向量表
按照之前的习惯我们直接找到ST开发板源码包中的中断向量文件vectors.s添加进当前工程,编译发生以下错误:
1..\OUTPUT\ OSII.axf: Error: L6218E: Undefined symbol OSPendSV (referred from vectors.o).
没有找到OSPendSV,这个函数是用于任务切换时保存堆栈相关数据的,该函数实际上已经在os_cpu_a.asm中被micrium实现了。只是名字变为了OS_CPU_PendSVHandler,因此我们只需要将vectors.s中的这个OS_CPU_PendSV改为OS_CPU_PendSVHandler就可以了。

2..\OUTPUT\ OSII.axf: Error: L6218E: Undefined symbol Tmr_TickISR_Handler (referred from vectors.o).
这个错误表示没有找到system tick函数,而这个函数实际上夜市在os_cpu_c.c中被micrium实现了,名字是OS_CPU_SysTickHandler。现在只需要将Tmr_TickISR_Handler改为OS_CPU_SysTickHandler即可。

3..\OUTPUT\ OSII.axf: Error: L6218E: Undefined symbol Image$$ARM_LIB_STACK$$ZI$$Limit (referred from vectors.o).
这个问题是表明连接器找不到ARM_LIB_STACK这个段造成的,由于micrium在移植 OS到STM32平台的时候自己写了分散加载文件,在分散加载文件中增加了一个ARM_LIB_STACK段,而我们没有使用它的分散加载文件导致找不到该段。micrium的分散加载文件如下:
LR_IROM1 0x08000000 0x00020000                                     ;; Load region
{
    ER_IROM1 0x08000000    0x08020000
    {
        vectors.o (VECT, +First)
        init.o (INIT)
        * (+RO)
    }

    RW_IRAM1 0x20000000 0x00004C00
    {
        * (+RW,+ZI)
    }                                                           ;; The following declarations select the "two region model" ;
                                                                ;; A default __user_initial_stackheap() will be used        ;
    ARM_LIB_HEAP  0x20004700 EMPTY  0x00000200   {}
    ARM_LIB_STACK 0x20004B00 EMPTY -0x00000200   {}
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

204

主题

7256

帖子

8

粉丝