1、uCos在ARM上的移植uC/OS-II在ARM系统上的移植与实现0 引言在开发嵌入式系统时,一般选择基于ARM 和uC/ OS - II 的嵌入式开发平台,因为ARM 微处理器具有处理速度快、超低功耗、价格低廉、应用前景广泛等优点1 . 将uC/ OS - II 移植到ARM 系统之后,可以充分结合两者的优势. 如果一个程序在一个环境里能工作,我们经常希望能将它移植到另一个编译系统、处理器或者操作系统上,这就是移植技术.移植技术可以使一种特定的技术在更加广泛的范围使用,使软件使用更加灵活,不局限于某一条件.uC/OS - II 是由Jean J . Labrosse 先生编写的完整的可移植、
2、固化、裁剪的占先式实时多任务内核.uC/ OS - II 的源代码完全开放,这是其他商业实时内核无法比拟的2 . 它是针对嵌入式应用设计的,在设计之初就充分考虑了可移植性,它的大部分源代码都是用高可移植性的ANSIC 编写的3 . uC/ OS - II可以移植到从8 位到64 位的不同类型、不同规模的嵌入式系统,并能在大部分的8 位、16 位、32 位、甚至64 位的微处理器和DSP 上运行. 由于uC/ OS - II是一个实时操作系统,所以如果将它嵌入到ARM处理器上,就能够进一步简化ARM系统的开发.图1 uC/ OS - II 文件体系结构1 uC/ OS - II 的移植uC/OS
3、 - II 的文件系统结构包括核心代码部分、设置代码部分、与处理器相关的移植代码部分4 . 结构如图1 所示.其中最上边的软件应用层是uC/ OS - II 上的代码. 核心代码部分包括7 个源代码文件和1 个头文件. 功能分别是内核管理、事件管理、消息队列管理、存储管理、消息管理、信号量处理、任务调度和定时管理. 设置代码部分包括2 个头文件,用来配置事件控制块的数目以及是否包含消息管理相关代码. 而与处理器相关的移植代码部分则是进行移植过程中需要更改的部分,包括1 个头文件OS CPU. H ,1 个汇编文件OS CPU A. S 和1 个C 代码文件.实际上将uC/ OS - II 移植
4、到ARM 处理器上,需要完成的工作主要是以下三个与体系结构相关的文件:OS CPU. H ,OS CPU. C 以及OS CPU A. S5 .1. 1 OS CPU. H 的移植文件OS CPU. H 中包括了用# define 语句定义的与处理器相关的常数、宏以及类型. 移植时主要修改的内容有:与编译器相关的数据类型的设定;用#define 语句定义2 个宏开关中断;根据堆栈的方向定义OS STK GROWTH等.在将uC/ OS - II 移植到ARM 处理器上时,首先进行基本配置和数据类型定义. 重新定义数据类型是为了增加代码的可移植性,因为不同的编译器所提供的同一数据类型的数据长度并
5、不相同,例如int型,在有的编译器中是16 位,而在另外一些编译器中则是32 位. 所以,为了便于移植,需要重新定义数据类型,如INT32U 代表无符号32 位整型. typedefunsigned int INT8U ,就是定义一个8 位的无符号整型数据类型. 其次就是对ARM 处理器相关宏进行定义,如ARM处理器中的退出临界区和进入临界区的宏定义,退出临界区宏定义5 : # define OS EXITCRITICAL () ARMDisable Int ( ) / / 关中断,进入临界区宏定义# define OS ENTER CRITICAL ( ) AR2MEnableInt ()
6、/ / 开中断. 最后就是堆栈增长方向的设定. 当进行函数调用时,入口参数和返回地址一般都会保存在当前任务的堆栈中,编译器的编译选项和由此生成的堆栈指令就会决定堆栈的增长方向6 ,定义为# define OS STK GROWTH 1.图2 堆栈增长方向1. 2 OS CPU. C 的移植OS CPU. C 的移植包括任务堆栈初始化和相应函数的实现. 在这里,共有6 个函数:OSTaskStkInit( ) , OSSTaskCreateHook ( ) , OSTaskDelHook ( ) , OS2TaskSwHook( ) ,OSTaskStatHook ( ) , OSTimeTic
7、kHook () . 其中后面的5 个HOOK函数又称为钩子函数,主要是用来对uC/ OS - II 进行功能扩展. 这些函数为用户定义函数,由操作系统调用相应的HOOK函数去执行,在一般情况下,他们都没有代码,所以实现为空函数即可. 而函数OSTaskStkInit ( ) 对堆栈进行初始化,在ARM 系统中,任务堆栈空间由高到低依次为PC ,LR ,R12 ,R11 , ,R1 ,R0 ,CPSR ,SPSR. 在进行堆栈初始化以后,OSTaskStkInit ( ) 返回新的堆栈栈顶指针.1. 3 OS CPU A. S 的移植OS CPU A. S 文件的移植需要对处理器的寄存器进行操
8、作,所以必须用汇编语言来编写. 这个文件的实现集中体现了所要移植到处理器的体系结构和uC/ OS - II 的移植原理6 . 它包括4 个子函数:OSStartHighRdy() , OSCtxSw() , OSIntCtxSw() ,OSTick2ISR() . 其中难点在于OSIntCtxSw() 和OSTickISR() 函数的实现,因为这两个函数的实现与移植者的移植思路以及相关硬件定时器、中断寄存器的设置有关.在实际的移植工作中,这两处也是比较容易出错的地方.OSIntCtxSw( ) 函数由OSIntExit ( ) 函数调用,而OSIntExit () 函数又由OSTickISR(
9、) 调用. OSIntCtxSw()函数最重要的作用就是它完成在中断ISR 中直接进行任务切换,从而提高了实时响应的速度. 它发生的时机是在ISR 执行到OSIntExit ( ) 时,如果发现有高优先级的任务因为等待time tick 的到来获得了执行 7 2 第4 期李学桥等:uC/ OS - II 在ARM系统上的移植与实现的条件,就可以马上被调度执行,而不用返回被中断的那个任务之后再进行任务切换. 实现OSIntCtxSw() 的方法大致也有两种情况7 :一是通过调整SP 堆栈指针的方法,根据所用的编译器对于函数嵌套的处理,通过精确计算出所需要调整的SP 位置来使得进入中断时所作的保护
10、现场的工作可以被重用. 二是设置需要切换标志位的方法,在OSIntCtxSw( ) 里面不发生切换,而是设置一个需要切换的标志,等函数嵌套从进入OSIntExit ( ) = OS ENTER CRITI2CAL() = OSIntCtxSw( ) = OS EXIT CRITICAL() = OSIntExit ( ) 退出后,再根据标志位来判断是否需要进行中断级的任务切换.其次是对OSTickISR() 修改.OSTickISR() 首先在被中断任务堆栈中保存CPU 寄存器的值,然后调用OSIntEnter () . 随后调用OSTimeTick() ,检查所有处于延时等待状态的任务,判断
11、是否有延时结束就绪的任务. 最后调用OSIntExit ( ) . 如果在中断中(或其他嵌套的中断) 有更高优先级的任务就绪,并且当前中断为中断嵌套的最后一层,OSIntExit ( ) 将进行任务调度. 如果进行了任务调度,OSIntExit () 将不再返回调用者,而是用新任务的堆栈中的寄存器数值恢复CPU 现场,然后实现任务切换. 如果当前中断不是中断嵌套的最后一层,或中断中没有改变任务的就绪状态, OSIntExit ( ) 将返回调用者OSTickISR ( ) ,OSTickISR() 返回被中断的任务. 最后就是退出临界区和进入临界区函数. 进入临界区时,必须关闭中断,用ARMD
12、isableInt () 函数实现. 在退出临界区的时候恢复原来的中断状态,通过ARMEnableInt ( ) 函数来实现7 . 至于进行任务级上下文切换,则是由汇编子程序OSCtxSw 实现.2 在ARM系统上的实现以跑马灯和数码管为例,说明uC/ OS - II 的移植过程:跑马灯是4 个小灯轮流变明变暗,很方便看出效果. 跑马灯在日常中使用很多,比如状态栏跑马灯、文字跑马灯、图片跑马灯、单片机跑马灯等8 . 本文采用的是单片机跑马灯. 实现单片机跑马灯的程序中,只有地址口为低电平(接地) 时,发光管才会亮. 所以只要循环控制地址口的各个引脚的电平高低变化就可使LED 循环点亮:首先是全
13、不亮,接着第1 个灯亮,第2 个灯亮,第3 个灯亮,第4 个灯亮,第5 个灯亮,最后所有的灯一起亮.笔者使用6 个共阳极LED 数码管来实现在7 段数码管上循环显示09 ,AF 字符. 每个显示位的段选线与一个8 位并行口线对应相连,只要在显示位上的段选线上保持段码电平不变,则该位就能保持相应的显示字符. 这里的8 位并行口可以直接采用并行I/ O 口,也可以采用串入/ 并出的移位寄存器或是其他具有三态功能的锁存器等. 当采用动态显示接口时,在多位LED 显示时,为了简化电路,降低成本,将所有位的段选线并联在一起,由一个8 位I/ O口控制. 而共阴(或共阳) 极公共端分别由相应的I/ O 线
14、控制,实现各位的分时选通. 由于各个数码管是共用同一个段码输出口分时轮流通电的,从而大大简化了硬件线路,降低了成本.对于数码管的实现分为3 个步骤:1) 制作LED 字符与码段对应表2) 扫描控制3 ( (U8 3 ) 0x02000006) = 0x3E; / 3 使能第一个数码管3 /3) 段码输出( (U8 3 ) 0x02000004) = seg7table0 ;根据上面的LED 字符与码段对应表,控制相应的数字进行输出. 数码管扫描控制地址为0x02000006 ,8 位访问,比如Bit0 控制数码管0 ,并且低电平有效,Bit5 控制数码管5 ,低电平有效,数码管显示试验系统中采
15、用的是动态显示接口,其中数码管扫描控制地址为0x02000006 ,位0 5 分别对应一个数码管,将其中每位清0 来选择相应的数码管;地址0x02000004 为数码管的数据寄存器,控制数码管的段码输出.3 多任务应用程序uC/OS - II 的移植及跑马灯和数码管的实现如下9 :首先是C 语言入口函数Main (所有C 程序的入口) . 它里面包括调用函数ARMTargetInit () 初始化ARM处理器,调用OSInit ( ) 进行uC/ OS - II 操作系统初始化,然后调用OSTaskCreate ( ) 函数创建任务TaskLED 和TaskSEG,最后调用ARMTargetS
16、tart () 函数启动时钟节拍中断,并且调用OSStart ( ) 启动系统任务调度,由于在程序当中使用for ( ; ;) ,这是一个永无止境的回路,所以装置可以一直进行下去,直到关闭装置.void Main(void)ARMTargetInit () ;uHALr printf (uC/ OS - II # n) ;OSInit () ;Sem1 = OSSemCreate(0) ;Sem2 = OSSemCreate(1) ;OSTaskCreate(TaskLED , (void 3 ) &IdLED , (OS STK 3 )&StackLED STACKSIZE - 1 , 5)
17、 ;OSTaskCreate(TaskSEG, (void 3 ) &IdSEG, (OS STK 3 )&StackSEG STACKSIZE - 1 , 6) ;ARMTargetStart () ;OSStart () ;return ;4 结语使用创建好的模板Temp 新建一个工程Temp ,并将模板中的Core 和Assemble 文件夹中的文件加入到工程Temp 中. 1) 新建一个文件Temp. c ,并将其添加到Temp 工程的App 文件夹中. 2) 打开Temp. c文件,添加两个任务,它们的任务处理函数分别为TaskLED() 和TaskSEG() . 3) 在TaskL
18、ED( ) 函数中每隔50 个时钟节拍使所有跑马灯闪烁一次(即按顺序亮,然后全亮,最后全灭,顺序循环) . 4) 在TaskSEG() 函数中每隔50 个时钟节拍切换一次数码管显示(循环从0F 显示) . 5) 编译工程Temp ,如果出错,则进行修改后再编译. 6) 将Temp 下载并运行,看结果. 正确的结果是将每隔1/ 2 s 切换一次数码管显示,每隔1/ 2 s使所有跑马灯闪烁一次. 经持续了2 h试验,没有出现错误,跑马灯和数码管正常运转,结果证明移植成功.C/OS-的移植集中在OS_CPU.h,OS_CPU_A.s,OS_CPU.c这三个文件上,下面分别详细介绍三个文件中的函数和需
19、要修改或者编写的代码。1. OS_CPU.h的移植该文件定义了和处理器及编译器相关的定义及一些全局函数声明。由于ARM7 处理器字长为32位,半字长为16位,字节为8位,因此在OS_CPU.h文件修改与编译器相关的定义如下:typedef unsigned char BOOLEAN;typedef unsigned char INT8U;typedef signed char INT8S;typedef unsigned short INT16U; /*某些编译器中int是32位的,故统一用short表示*/typedef signed short INT16S;typedef unsigne
20、d long INT32U;typedef signed long INT32S;typedef float FP32;typedef double FP64;typedef unsigned long OS_STK; /*堆栈宽度为32位,即ARM7种的字对齐方式*/*下面是与处理器相关的代码*/#define OS_CRITICAL_METHOD 2 /*使用方式2保护临界代码*/#define OS_ENTER_CRITICAL() ARMDisableInt() /*临界段代码保护宏定义*/#define OS_EXIT_CRITICAL() ARMEnableInt()#define
21、 OS_STK_GROWTH 1 /*定义堆栈生长方向为向下生长 */#define OS_TASK_SW OSCtxSw /*宏定义,用于非中断级的任务切换*/*下面开始声明全局函数声明,均是OS_CPU_A.S中需要编写的函数*/extern void OSCtxSw(void); /*声明任务级任务切换函数*/extern void OSIntCtxSw(void); /*声明中断级任务切换函数*/extern void ARMDisableInt(void); /*声明中断禁止函数*/extern void ARMEnableInt(void); /*声明中断恢复函数*/extern
22、void OSTickISR(void); /*声明时钟中断服务函数*/2. OS_CPU_C.C文件移植OS_CPU_C.C文件时,需要编写的是任务堆栈初始化函数OSTaskStkInit和时钟节拍中断服务钩子函数OSTimeTickHook。在C/OS-II中,每一个任务都有自己的任务堆栈,当发生任务切换或者中断时,其CPU使用权被剥脱,为了任务能被再次运行,那么这个被打断的任务所用到的处理器的寄存器内容均应得到保存,按照ARM7 处理器的压栈和入栈指令的特点,设计任务堆栈如下图2:CPSRR0R1R12LR(R14)PC(R15)图2 任务堆栈的结构根据任务堆栈结构示意图,OS_STK函
23、数编写如下:#define SVCMODE 0x13 /*定义svc模式的命令字,用户任务运行在svc模式下*/OS_STK * OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)OS_STK *stk; /*定义堆栈指针*/opt = opt;stk = (OS_STK) ptos; /*保存任务堆栈栈顶指针*/*-stk = (OS_STK) task; /* 用来保存PC,初始化成任务入口地址在被保护存现场的则是该任务运行时被中断时的地址*/*-stk = (OS_STK) task;
24、 /* 用来保存LR*/*-stk = 0; /* r12 */*-stk = 0; /* r11 */*-stk = 0; /* r10 */*-stk = 0; /* r9 */*-stk = 0; /* r8 */*-stk = 0; /* r7 */*-stk = 0; /* r6 */*-stk = 0; /* r5 */*-stk = 0; /* r4 */*-stk = 0; /* r3 */*-stk = 0; /* r2 */*-stk = 0; /* r1 */*-stk = (INT32U) pdata; /* 在ARM的编译建议中,r0用于参数传递 */*-stk =
25、(SVC32MODE|0x40); /*用来保存CPSR,并禁止FIQ,由于任务和操作系统均运行在svc模式,被中断到其他模式再返回后仍然回到svc模式,故SPSR没有用到*/return (OS_STK *)stk); /*返回任务堆栈的指针*/说明:用户创建任务时,OSTaskCreat()会调用OSTaskStkInit函数初始化该任务的堆栈,并把返回的堆栈指针保存到该任务的TCB结构中的最前面的参数OSTCBStkPtr中,当该任务要被恢复时,任务切换函数从其TCB块中取得其任务堆栈指针,依次将堆栈内容弹到处理器对应的 CPSR、r0,r1,r12,lr,pc的寄存器中,完成现场的恢复
26、和程序指针PC的返回。另一个需要编写的函数是OSTimeTickHook,该函数被时钟节拍中断服务函数OSTickISR中的OSTimeTick函数调用,用来清除时钟节拍中断发生设备的请求。本移植方案使用S3C44B0X处理器的RTC模块的tick中断作为时钟节拍中断,该函数编写如下:void OSTimeTickHook(void)rI_ISPC =(INT32U)0x01) OSTCBStkPtr = sp; /*OSTCBCur目前指向的是被打断的任务TCB,此操作将该任务的栈顶指针保存到其OSTCBStkPtr中去,便于下次恢复时从这里获取栈顶指针*/OSTCBCur = OSTCBH
27、ighRdy; /*OSTCBHighRdy 指向的是就绪的高优先级任务的TCB,将其装载到OSTCBCur 中来*/SP = OSTCBHighRdy-OSTCBStkPtr; /*取得就绪的高优先级任务的栈顶指针*/恢复该任务的现场(); /*于是便可通过刚取得的栈顶指针恢复该任务 */执行中断返回指令; /*若OSCtxSw含有软中断指令则需中断返回,本移植不使用软中断*/表2 OSCtxSw函数的伪代码3) OSIntCtxSw() 函数该函数用于中断级的上下文切换。由于CPU响应时钟节拍中断后,处理器从svc进入了irq模式,并进入时钟节拍中断服务函数OSTickISR, OSTic
28、kISR函数发现若有高优先级任务需要运行,则系统不返回中断前的任务,而直接调度就绪的高优先级任务使之尽快得到执行,以保证实时性能。但是由于OSTickISR函数一开始已经保存过任务中断前的CPU现场,因此OSIntCtxSW()不需要再进行类似的操作。当OSTickISR调用 OSIntExit函数找出需要运行的更高优先级任务后,OSIntExit会将该任务的TCB指针放在OSTCBHighRdy中,然后 OSIntExit在最后调用OSIntCtxSW函数来从OSTCBHighRdy中获取堆栈指针然后恢复该高优先级任务的现场,使得其继续执行,并不再返回时钟节拍中断服务程序。显然,OSIntC
29、txSW函数的过程和OSCtxSW函数的后半部分操作相同,因此,OSCtxSW可以借用 OSIntCtxSW的代码。4) OSTickISR()函数在CPU响应时钟节拍中断后,程序指针PC发生跳转后进入该函数,由于OSTickISR调用OSTimeTick函数使得所有的延时节拍不为0的任务延时节拍数减1,并调用OSIntExit函数来找出就绪的高优先级任务,若需要切换,则最后由OSIntCtxSw来完成新任务的调度,否则仍然返回到被时钟节拍中断的任务。OSTickISR函数的伪码和注释见表3。5) ARMDisableInt和ARMEnableInt函数ARMDisableInt是用来暂时禁止FIQ及IRQ中断的函数,ARMEnableInt则是恢复ARMDisableInt执行前的中断使能状态,二者成对使用,用来保护临界
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1