uCos在ARM上的移植.docx

上传人:b****5 文档编号:8005458 上传时间:2023-01-27 格式:DOCX 页数:13 大小:54.27KB
下载 相关 举报
uCos在ARM上的移植.docx_第1页
第1页 / 共13页
uCos在ARM上的移植.docx_第2页
第2页 / 共13页
uCos在ARM上的移植.docx_第3页
第3页 / 共13页
uCos在ARM上的移植.docx_第4页
第4页 / 共13页
uCos在ARM上的移植.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

uCos在ARM上的移植.docx

《uCos在ARM上的移植.docx》由会员分享,可在线阅读,更多相关《uCos在ARM上的移植.docx(13页珍藏版)》请在冰豆网上搜索。

uCos在ARM上的移植.docx

uCos在ARM上的移植

uC/OS-II在ARM系统上的移植与实现

0 引言

  在开发嵌入式系统时,一般选择基于ARM和uC/OS-II的嵌入式开发平台,因为ARM微处理器具有处理速度快、超低功耗、价格低廉、应用前景广泛等优点[1].将uC/OS-II移植到ARM系统之后,可以充分结合两者的优势.如果一个程序在一个环境里能工作,我们经常希望能将它移植到另一个编译系统、处理器或者操作系统上,这就是移植技术.移植技术可以使一种特定的技术在更加广泛的范围使用,使软件使用更加灵活,不局限于某一条件.uC/OS-II是由JeanJ.Labrosse先生编写的完整的可移植、固化、裁剪的占先式实时多任务内核.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-II的文件系统结构包括核心代码部分、设置代码部分、与处理器相关的移植代码部分[4].结构如图1所示.其中最上边的软件应用层是uC/OS-II上的代码.核心代码部分包括7个源代码文件和1个头文件.功能分别是内核管理、事件管理、消息队列管理、存储管理、消息管理、信号量处理、任务调度和定时管理.设置代码部分包括2个头文件,用来配置事件控制块的数目以及是否包含消息管理相关代码.而与处理器相关的移植代码部分则是进行移植过程中需要更改的部分,包括1个头文件OSCPU.H,1个汇编文件OSCPUA.S和1个C代码文件.实际上将uC/OS-II移植到ARM处理器上,需要完成的工作主要是以下三个与体系结构相关的文件:

OSCPU.H,OSCPU.C以及OSCPUA.S[5].

1.1 OSCPU.H的移植

  文件OSCPU.H中包括了用#define语句定义的与处理器相关的常数、宏以及类型.移植时主要修改的内容有:

与编译器相关的数据类型的设定;用#define语句定义2个宏开关中断;根据堆栈的方向定义OSSTKGROWTH等.

  在将uC/OS-II移植到ARM处理器上时,首先进行基本配置和数据类型定义.重新定义数据类型是为了增加代码的可移植性,因为不同的编译器所提供的同一数据类型的数据长度并不相同,例如int型,在有的编译器中是16位,而在另外一些编译器中则是32位.所以,为了便于移植,需要重新定义数据类型,如INT32U代表无符号32位整型.typedefunsignedintINT8U,就是定义一个8位的无符号整型数据类型.其次就是对ARM处理器相关宏进行定义,如ARM处理器中的退出临界区和进入临界区的宏定义,退出临界区宏定义[5]:

#defineOSEXITCRITICAL()ARMDisableInt()//关中断,进入临界区宏定义#defineOSENTERCRITICAL()AR2MEnableInt()//开中断.最后就是堆栈增长方向的设定.当进行函数调用时,入口参数和返回地址一般都会保存在当前任务的堆栈中,编译器的编译选项和由此生成的堆栈指令就会决定堆栈的增长方向[6],定义为#defineOSSTKGROWTH1.

  图2 堆栈增长方向

  1.2 OSCPU.C的移植

  OSCPU.C的移植包括任务堆栈初始化和相应函数的实现.在这里,共有6个函数:

OSTaskStkInit(),OSSTaskCreateHook(),OSTaskDelHook(),OS2TaskSwHook(),OSTaskStatHook(),OSTimeTickHook().其中后面的5个HOOK函数又称为钩子函数,主要是用来对uC/OS-II进行功能扩展.这些函数为用户定义函数,由操作系统调用相应的HOOK函数去执行,在一般情况下,他们都没有代码,所以实现为空函数即可.而函数OSTaskStkInit()对堆栈进行初始化,在ARM系统中,任务堆栈空间由高到低依次为PC,LR,R12,R11,⋯,R1,R0,CPSR,SPSR.在进行堆栈初始化以后,OSTaskStkInit()返回新的堆栈栈顶指针.

1.3 OSCPUA.S的移植

  OSCPUA.S文件的移植需要对处理器的寄存器进行操作,所以必须用汇编语言来编写.这个文件的实现集中体现了所要移植到处理器的体系结构和uC/OS-II的移植原理[6].它包括4个子函数:

OSStartHighRdy(),OSCtxSw(),OSIntCtxSw(),OSTick2ISR().其中难点在于OSIntCtxSw()和OSTickISR()函数的实现,因为这两个函数的实现与移植者的移植思路以及相关硬件定时器、中断寄存器的设置有关.在实际的移植工作中,这两处也是比较容易出错的地方.

  OSIntCtxSw()函数由OSIntExit()函数调用,而OSIntExit()函数又由OSTickISR()调用.OSIntCtxSw()函数最重要的作用就是它完成在中断ISR中直接进行任务切换,从而提高了实时响应的速度.它发生的时机是在ISR执行到OSIntExit()时,如果发现有高优先级的任务因为等待timetick的到来获得了执行•72•第4期李学桥等:

uC/OS-II在ARM系统上的移植与实现的条件,就可以马上被调度执行,而不用返回被中断的那个任务之后再进行任务切换.实现OSIntCtxSw()的方法大致也有两种情况[7]:

一是通过调整SP堆栈指针的方法,根据所用的编译器对于函数嵌套的处理,通过精确计算出所需要调整的SP位置来使得进入中断时所作的保护现场的工作可以被重用.二是设置需要切换标志位的方法,在OSIntCtxSw()里面不发生切换,而是设置一个需要切换的标志,等函数嵌套从进入OSIntExit()=>OSENTERCRITI2CAL()=>OSIntCtxSw()=>OSEXITCRITICAL()=>OSIntExit()退出后,再根据标志位来判断是否需要进行中断级的任务切换.

  其次是对OSTickISR()修改.OSTickISR()首先在被中断任务堆栈中保存CPU寄存器的值,然后调用OSIntEnter().随后调用OSTimeTick(),检查所有处于延时等待状态的任务,判断是否有延时结束就绪的任务.最后调用OSIntExit().如果在中断中(或其他嵌套的中断)有更高优先级的任务就绪,并且当前中断为中断嵌套的最后一层,OSIntExit()将进行任务调度.如果进行了任务调度,OSIntExit()将不再返回调用者,而是用新任务的堆栈中的寄存器数值恢复CPU现场,然后实现任务切换.如果当前中断不是中断嵌套的最后一层,或中断中没有改变任务的就绪状态,OSIntExit()将返回调用者OSTickISR(),OSTickISR()返回被中断的任务.最后就是退出临界区和进入临界区函数.进入临界区时,必须关闭中断,用ARMDisableInt()函数实现.在退出临界区的时候恢复原来的中断状态,通过ARMEnableInt()函数来实现[7].至于进行任务级上下文切换,则是由汇编子程序OSCtxSw实现.

2 在ARM系统上的实现

  以跑马灯和数码管为例,说明uC/OS-II的移植过程:

跑马灯是4个小灯轮流变明变暗,很方便看出效果.跑马灯在日常中使用很多,比如状态栏跑马灯、文字跑马灯、图片跑马灯、单片机跑马灯等[8].本文采用的是单片机跑马灯.实现单片机跑马灯的程序中,只有地址口为低电平(接地)时,发光管才会亮.所以只要循环控制地址口的各个引脚的电平高低变化就可使LED循环点亮:

首先是全不亮,接着第1个灯亮,第2个灯亮,第3个灯亮,第4个灯亮,第5个灯亮,最后所有的灯一起亮.

  笔者使用6个共阳极LED数码管来实现在7段数码管上循环显示0~9,A~F字符.每个显示位的段选线与一个8位并行口线对应相连,只要在显示位上的段选线上保持段码电平不变,则该位就能保持相应的显示字符.这里的8位并行口可以直接采用并行I/O口,也可以采用串入/并出的移位寄存器或是其他具有三态功能的锁存器等.当采用动态显示接口时,在多位LED显示时,为了简化电路,降低成本,将所有位的段选线并联在一起,由一个8位I/O口控制.而共阴(或共阳)极公共端分别由相应的I/O线控制,实现各位的分时选通.由于各个数码管是共用同一个段码输出口分时轮流通电的,从而大大简化了硬件线路,降低了成本.

  对于数码管的实现分为3个步骤:

  1)制作LED字符与码段对应表

  2)扫描控制

  3((U83)0x02000006)=0x3E;/3使能第一个数码管

  3/

  3)段码输出

  ((U83)0x02000004)=seg7table[0];

  根据上面的LED字符与码段对应表,控制相应的数字进行输出.数码管扫描控制地址为0x02000006,8位访问,比如Bit0控制数码管0,并且低电平有效,Bit5控制数码管5,低电平有效,数码管显示试验系统中采用的是动态显示接口,其中数码管扫描控制地址为0x02000006,位0—5分别对应一个数码管,将其中每位清0来选择相应的数码管;地址0x02000004为数码管的数据寄存器,控制数码管的段码输出.

3 多任务应用程序

  uC/OS-II的移植及跑马灯和数码管的实现如下[9]:

首先是C语言入口函数Main(所有C程序的入口).它里面包括调用函数ARMTargetInit()初始化ARM处理器,调用OSInit()进行uC/OS-II操作系统初始化,然后调用OSTaskCreate()函数创建任务TaskLED和TaskSEG,最后调用ARMTargetStart()函数启动时钟节拍中断,并且调用OSStart()启动系统任务调度,由于在程序当中使用for(;;),这是一个永无止境的回路,所以装置可以一直进行下去,直到关闭装置.

  voidMain(void)

  {ARMTargetInit();

  uHALrprintf(″uC/OS-II#n″);

  OSInit();

  Sem1=OSSemCreate(0);

  Sem2=OSSemCreate

(1);

  OSTaskCreate(TaskLED,(void3)&IdLED,(OSSTK3)

&StackLED[STACKSIZE-1],5);

  OSTaskCreate(TaskSEG,(void3)&IdSEG,(OSSTK3)

&StackSEG[STACKSIZE-1],6);

  ARMTargetStart();OSStart();

  return;}

  4 结语

  使用创建好的模板Temp新建一个工程Temp,并将模板中的Core和Assemble文件夹中的文件加入到工程Temp中.1)新建一个文件Temp.c,并将其添加到Temp工程的App文件夹中.2)打开Temp.c文件,添加两个任务,它们的任务处理函数分别为TaskLED()和TaskSEG().3)在TaskLED()函数中每隔50个时钟节拍使所有跑马灯闪烁一次(即按顺序亮,然后全亮,最后全灭,顺序循环).4)在TaskSEG()函数中每隔50个时钟节拍切换一次数码管显示(循环从0~F显示).5)编译工程Temp,如果出错,则进行修改后再编译.6)将Temp下载并运行,看结果.正确的结果是将每隔1/2s切换一次数码管显示,每隔1/2s使所有跑马灯闪烁一次.经持续了2h试验,没有出现错误,跑马灯和数码管正常运转,结果证明移植成功.

μC/OS-Ⅱ的移植集中在OS_CPU.h,OS_CPU_A.s,OS_CPU.c这三个文件上,下面分别详细介绍三个文件中的函数和需要修改或者编写的代码。

1.OS_CPU.h的移植

该文件定义了和处理器及编译器相关的定义及一些全局函数声明。

由于ARM7处理器字长为32位,半字长为16位,字节为8位,因此在OS_CPU.h文件修改与编译器相关的定义如下:

typedefunsignedcharBOOLEAN;

typedefunsignedcharINT8U;

typedefsignedcharINT8S;

typedefunsignedshortINT16U;/*某些编译器中int是32位的,故统一用short表示*/

typedefsignedshortINT16S;

typedefunsignedlongINT32U;

typedefsignedlongINT32S;

typedeffloatFP32;

typedefdoubleFP64;

typedefunsignedlongOS_STK;/*堆栈宽度为32位,即ARM7种的字对齐方式*/

/*下面是与处理器相关的代码*/

#defineOS_CRITICAL_METHOD2/*使用方式2保护临界代码*/

#defineOS_ENTER_CRITICAL()ARMDisableInt()/*临界段代码保护宏定义*/

#defineOS_EXIT_CRITICAL()ARMEnableInt()

#defineOS_STK_GROWTH1/*定义堆栈生长方向为向下生长*/

#defineOS_TASK_SWOSCtxSw/*宏定义,用于非中断级的任务切换*/

/*下面开始声明全局函数声明,均是OS_CPU_A.S中需要编写的函数*/

externvoidOSCtxSw(void);/*声明任务级任务切换函数*/

externvoidOSIntCtxSw(void);/*声明中断级任务切换函数*/

externvoidARMDisableInt(void);/*声明中断禁止函数*/

externvoidARMEnableInt(void);/*声明中断恢复函数*/

externvoidOSTickISR(void);/*声明时钟中断服务函数*/

2.OS_CPU_C.C文件

移植OS_CPU_C.C文件时,需要编写的是任务堆栈初始化函数OSTaskStkInit和时钟节拍中断服务钩子函数OSTimeTickHook。

在μC/OS-II中,每一个任务都有自己的任务堆栈,当发生任务切换或者中断时,其CPU使用权被剥脱,为了任务能被再次运行,那么这个被打断的任务所用到的处理器的寄存器内容均应得到保存,按照ARM7处理器的压栈和入栈指令的特点,设计任务堆栈如下图2:

CPSR

R0

R1

……

R12

LR(R14)

PC(R15)

图2任务堆栈的结构

根据任务堆栈结构示意图,OS_STK函数编写如下:

#defineSVCMODE0x13/*定义svc模式的命令字,用户任务运行在svc模式下*/

OS_STK*OSTaskStkInit(void(*task)(void*pd),void*pdata,OS_STK*ptos,INT16Uopt){

OS_STK*stk;/*定义堆栈指针*/

opt=opt;

stk=(OS_STK)ptos;/*保存任务堆栈栈顶指针*/

*--stk=(OS_STK)task;/*用来保存PC,初始化成任务入口地址在被保护存现场的则是该任务运行时被中断时的地址*/

*--stk=(OS_STK)task;/*用来保存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=(SVC32MODE|0x40);/*用来保存CPSR,并禁止FIQ,由于任务和操作系统均运行在svc模式,被中断到其他模式再返回后仍然回到svc模式,故SPSR没有用到*/

return((OS_STK*)stk);/*返回任务堆栈的指针*/

}

说明:

用户创建任务时,OSTaskCreat()会调用OSTaskStkInit函数初始化该任务的堆栈,并把返回的堆栈指针保存到该任务的TCB结构中的最前面的参数OSTCBStkPtr中,当该任务要被恢复时,任务切换函数从其TCB块中取得其任务堆栈指针,依次将堆栈内容弹到处理器对应的CPSR、r0,r1,…,r12,lr,pc的寄存器中,完成现场的恢复和程序指针PC的返回。

另一个需要编写的函数是OSTimeTickHook,该函数被时钟节拍中断服务函数OSTickISR中的OSTimeTick函数调用,用来清除时钟节拍中断发生设备的请求。

本移植方案使用S3C44B0X处理器的RTC模块的tick中断作为时钟节拍中断,该函数编写如下:

voidOSTimeTickHook(void){

rI_ISPC=((INT32U)0x01)<<20;/*清RTC模块的tick中断*/

}

注意:

用户也可不修改此函数,但是必须在OSTickISR中执行清除发生节拍中断的设备的中断请求标志,为便于说明,本文将利用内核提供给用户的OSTimeTickHook函数来完成清中断的任务。

另外几个hook函数不必去改它们。

至此,OS_CPU.C编写完成。

3.OS_CPU_A.S文件的移植

该文件是移植过程中唯一需要用汇编语言来实现的文件,也是移植的重点和难点所在。

在这个文件里,需要编写的函数有OSStartHighRdy,OSCtxSW,OSIntCtxSW,OSTickISR,ARMDisableInt,ARMEnableInt几个。

下面先结合us/os的任务切换的过程分析一下这几个函数的作用。

1)OSStartHighRdy()函数

当程序执行内核的OSStart函数时,表示多任务系统开始启动,OSStart函数将调用OSStartHighRdy函数从最高优先级任务的TCB块中获得该任务的堆栈指针,通过该指针,依次从该任务的任务堆栈中恢复CPU的现场。

由于任务在堆栈初始化时,已经设定了弹出到程序指针寄存器PC的是该任务函数的入口地址,因此,OSStartHighRdy函数只需依次弹出任务栈内容到处理起寄存器,该任务便将得以运行。

2)OSCtxSw()函数

该函数是任务级的上下文切换函数,当任务被阻塞而主动请求CPU开始任务调度时执行,其过程是将当前任务的的CPU现场保存到该任务堆栈中去,然后从OSTCBHighRdy中获得更高优先级任务的堆栈指针,再从该指针指向的堆栈中恢复此任务的CPU现场,使之继续执行,从而完成一次任务级别的切换。

表2为OSCtxSw函数的伪代码。

voidOSCtxSw(void){

保存处理器寄存器;/*将欲挂起的任务的CPU寄存器压入当前堆栈*/

OSTCBCur->OSTCBStkPtr=sp;/*OSTCBCur目前指向的是被打断的任务TCB,此操作

将该任务的栈顶指针保存到其OSTCBStkPtr中去,便于下次恢复时从这里获取栈顶指针*/

OSTCBCur=OSTCBHighRdy;/*OSTCBHighRdy指向的是就绪的高优先级任务的TCB,

将其装载到OSTCBCur中来*/

SP=OSTCBHighRdy->OSTCBStkPtr;/*取得就绪的高优先级任务的栈顶指针*/

恢复该任务的现场();/*于是便可通过刚取得的栈顶指针恢复该任务*/

执行中断返回指令;/*若OSCtxSw含有软中断指令则需中断返回,本移植不使用软中断*/

}

表2OSCtxSw函数的伪代码

3)OSIntCtxSw()函数

该函数用于中断级的上下文切换。

由于CPU响应时钟节拍中断后,处理器从svc进入了irq模式,并进入时钟节拍中断服务函数OSTickISR,OSTickISR函数发现若有高优先级任务需要运行,则系统不返回中断前的任务,而直接调度就绪的高优先级任务使之尽快得到执行,以保证实时性能。

但是由于OSTickISR函数一开始已经保存过任务中断前的CPU现场,因此OSIntCtxSW()不需要再进行类似的操作。

当OSTickISR调用OSIntExit函数找出需要运行的更高优先级任务后,OSIntExit会将该任务的TCB指针放在OSTCBHighRdy中,然后OSIntExit在最后调用OSIntCtxSW函数来从OSTCBHighRdy中获取堆栈指针然后恢复该高优先级任务的现场,使得其继续执行,并不再返回时钟节拍中断服务程序。

显然,OSIntCtxSW函数的过程和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