实时操作系统mC/OS-II在AT91上的移植
扫描二维码
随时随地手机看文章
mC/OS-II内核工作原理
实时嵌入式操作系统mC/OS-II内核的工作原理如图1所示。首先,在主程序中对操作系统进行初始化,完成mC/OS-II所有变量和数据结构的初始化,包括任务控制块(TCB)初始化,TCB优先级表初始化,TCB链表初始化,事件控制块(ECB)链表初始化以及空闲任务的创建等。
然后,根据应用程序的需要,用户可以调用OSTaskCreate()函数创建多个任务(至少1个)。该函数为新任务建立任务堆栈(OSTaskStkInit())以及初始化任务控制块TCB(OS_ TCBInit())。
最后,通过调用OSStart()启动多任务调度。程序将跳到就绪表中优先级最高的任务开始执行。这里假设启动多任务调度前创建了一个任务Task1()。
程序进入Task1()后,首先初始化时钟和启动时钟节拍源开始计时。此节拍源给系统提供周期性的时钟中断信号,实现延时和超时确认。然后根据应用程序要求,完成该任务的基本功能。最后,调用OSTimeDly(),将自己挂起,即从就绪表中删除。直到等待延时时间到,才将该任务恢复到就绪表中,并等待调度器的调度。
OSTimeDly()将任务挂起的同时,为其做好延时记号,然后调用OS_Sched()进行任务级的任务调度。若此时没有任何任务进入就绪态,就切换到空闲任务。
当时钟中断来临时,系统进入中断服务程序OSTickISR()。系统把当前正在执行的任务挂起,保护现场,进行中断处理OSTimeTick(),判断有无任务延时到期,若有,则使该任务进入就绪态。最后调用OSIntExit()进行中断级的任务调度,从而切换到优先级最高的任务,若没有别的任务进入就绪态,则恢复现场继续执行原任务。
AT91FR40162简介
AT91FR40162是ATMEL公司出品的一款基于ARM7TDMI内核的AT91 16/32位微控制器,其核心为高性能的32位RISC体系结构,并具有高密度的16位指令集和极低的功耗。
ARM处理器共有7种运行模式,处理器模式可以通过软件控制进行切换,也可以通过外部中断或异常处理过程进行切换。在每一种处理器模式中有一组相应的寄存器组。任意时刻(也就是任意的处理器模式下),可见的寄存器包括15个通用寄存器(R0~R14)、一个或两个状态寄存器及程序计数器(PC)。
CPSR(当前程序状态寄存器)可以在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。每一种处理器模式下都有一个专用的物理状态寄存器,称为SPSR(备份程序状态寄存器)。当特定的异常中断发生时,这个寄存器用于存放CPSR的内容。在异常中断程序退出时,可以用SPSR中保存的值来恢复CPSR。
mC/OS-II在
AT91FR40162上的移植
本文使用的内核是mC/OS-II v2.76,选用ADS1.2中的C编译器。
整个移植过程主要分为两个方面:与应用相关的mC/OS-II配置(OS_CFG.H,INCLUDES.H);与处理器相关的mC/OS-II移植(OS_CPU.H,OS_CPU_A.ASM,OS_CPU_C.C)。
OS_CPU.H
此文件中主要是定义了一些与处理器相关的常数、宏以及类型。根据ADS1.2的编译手册,char型变量为8位,short型为16位,int型为32位,堆栈为32位宽。另外,v2.76版本中新增加了一个全局变量OSIntCtxSwFlag,如果需要中断级的任务切换时,就令OSIntCtxSwFlag=TRUE。
OS_CPU_C.C
在这里需要编写两个函数:OSInitHookBegin()和OSTaskStkInit()。
OSInitHookBegin()在OSInit()中调用,用来初始化移植中出现的特殊变量。这里将OSIntCtxSwFlag初始化为0。
OSTaskStkInit()在OSTaskCreate()和OSTaskCreateExt()中调用,用来初始化任务的栈结构。图2显示了初始化后任务栈的结构。堆栈从上往下递减。mC/OS-II规定任务工作在SVC模式下。另外,根据ATPCS规定,子程序间通过R0~R3传递参数,所以参数p_arg放在R0里。
与以往的版本不同,这里OSIntCtxSw()(由OSIntExit()调用)并不完成任务的切换工作,仅将OSIntCtxSwFlag变量置1,标志需要进行任务切换。当OSIntExit()调用返回到ISR时,检查OSIntCtxSwFlag变量,如果为1,则跳到OS_IntCtxSw()函数执行任务切换。
OS_CPU_A.S
由于C语言不能对寄存器进行直接操作,所以必须用汇编语言编写以下函数。
OSStartHighRdy()由OSStart()调用。该函数使就绪态任务中优先级最高的任务开始运行。注意开始要将IRQ和FIQ中断禁止,最后从堆栈弹出所有寄存器时,必须按照OSTaskStkInit()中相反的顺序弹出。
OSCtxSw()由OSSched()调用,完成任务级的任务切换。任务级的任务切换包括将当前任务在SVC模式下的寄存器压入任务栈中,并且将新任务的堆栈弹出到SVC模式下的寄存器中。注意必须按照OSTaskStkInit()中任务栈的结构对任务栈进行操作。
OS_CPU_SR_Save()用于关中断,OS_CPU_SR_Restore()用于开中断。OS_CPU_SR_Save()将CPSR保存到R0中,通过将CPSR中I位和F位置1来禁止IRQ和FIQ中断。OS_CPU_SR_Restore()只需将R0中保存的CPSR恢复即可。
OS_IntCtxSw()完成中断级的任务切换。当检查OSIntCtxSwFlag变量为1时,就调用该函数。因为OS_IntCtxSw()是在ISR中被调用的,所以所有的处理器寄存器都已经被正确地保存到了被中断任务的堆栈中。
mC/OS-II 要求用户提供一个周期性的时钟源,来实现时间的延迟和超时功能。这里时钟节拍设为100次每秒,使用定时器0来完成该任务。时钟节拍中断OSTickISR()代码如下,此时系统进入IRQ模式。
STMFD SP!, {R0-R3, R12, LR}
BL OSIntEnter
BL Tmr_TickISR_Handler
BL OSIntExit
LDR R0,addr_OSIntCtxSwFlag
LDR R1, [R0]
CMP R1, #1
BEQ OS_IntCtxSw
LDMFD SP!, {R0-R3,R12,LR}
SUBS PC, LR, #4
Tmr_TickISR_Handler是中断处理程序,用C语言编写。如果是其他类型的中断,可以替换为相应的中断处理程序。这里定时器0中断处理程序要做的工作是首先清中断源,即读取TC_SR寄存器值,AIC_ICCR=0x00000010,然后告诉处理器中断结束(向AIC_EOICR写数据),最后调用OSTimeTick()函数,对延时记号进行处理。
定时器0的设置为:WAVE模式;TC_CMR中CPCTRG=1,表示RC比较将复位计数器并开启时钟;TC_RC=计时时间;TC_IER=AT91C_TC_CPCS,允许RC比较中断。[!--empirenews.page--]
移植中要注意的问题
由于各方面的原因,移植中编写的代码不一定完全正确,需要进行逐步调试。调试过程中,要善于根据具体现象来发现问题所在。根据笔者的调试经历,总结了一些移植调试过程中要注意的问题。
(1) 对mC/OS-II的内核机理要有充分理解。可以尝试对其内核进行调试,这样可以帮助自己从更深的层次来理解嵌入式实时操作系统。只有对mC/OS-II的内核有了清楚地认识,才能在移植过程中发现问题的本质。
(2) 对所使用的编译器要有深刻细致的了解。由于ADS1.2的C 编译器有很多优化编译选项和流水处理选项,在处理内核编译时很容易有冲突,所以,选择优化选项要慎重。加强对ADS1.2的C 编译器的理解,可以节约代码编写时间。
(3) 在移植过程中,最容易出问题的就是堆栈处理。堆栈处理是操作系统移植的关键部分。先分析ARM系统自身在进行现场保护时的堆栈处理的操作,然后模拟其过程。关键是要将需要的寄存器都保护到。由于我们在进行任务切换的时候采用了系统函数来进行现场保护,因此在堆栈初始化的时候,就应该按照这两个函数的操作来对任务堆栈进行初始化。
(4) 要详细分析ARM系统自自身处理中断时的压栈操作,必须将多余的信息从堆栈中清理干净。如果对编译器不熟悉,可以通过仔细调试中断,来确定编译器的具体操作。在对AT91FR40162的移植中,由于ARM处理器有7种运行模式,每种运行模式下都有自己独立的寄存器,所以在处理中断的过程中,要注意运行模式的切换,避免寄存器的内容无法正确保存和恢复。
(5) 注意程序的返回地址。异常中断发生时,程序计数器PC所指的位置对各种不同的异常中断是不同的,所以,返回地址也是不同的。需要根据不同的中断类型,确定不同的中断返回地址偏移量,否则程序将跑飞。
结语
本方案经过测试已证明移植成功。本文是以AT91FR40162为例,说明mC/OS-II的移植,对于ARM系列的处理器,只需稍加改动就可以实现。