首页 理论教育 嵌入式系统原理与应用:ARM处理器移植步骤

嵌入式系统原理与应用:ARM处理器移植步骤

时间:2023-11-23 理论教育 版权反馈
【摘要】:所谓“移植”,就是使一个实时操作系统能够在某个微处理器平台上或微控制器上运行。在ARM 处理器核中,关中断和开中断是通过改变程序状态寄存器CPSR 中的相应控制位实现的。该函数仅仅在多任务启动时被执行一次,用来启动最高优先级的任务执行。移植该函数的原因是它涉及将处理器寄存器保存到堆栈的操作。这样做的目的主要是能够尽快地让高优先级的任务得到响应,保证系统的实时性能。

嵌入式系统原理与应用:ARM处理器移植步骤

所谓“移植”,就是使一个实时操作系统能够在某个微处理器平台上或微控制器上运行。由μC/OS-Ⅱ的文件系统可知,在移植过程中,用户所需要关注的就是与处理器相关的代码,这部分包括一个头文件(os_cpu. h)、一个汇编文件(os_cpu_a. asm)和一个C 代码文件(os_cpu_c.c)。

以下介绍当使用GNU 下的gcc 编译器时,移植μC/OS-Ⅱ的主要内容:

(1)基本的配置和定义

所有需要完成的基本配置和定义全部集中在os_cpu.h 头文件中。

1)定义与编译器相关的数据类型

为了保证可移植性,程序中没有直接使用C 语言中的short、int、long 等数据类型的定义,因为它们与处理器类型有关,隐含着不可移植性,而是自己定义了一套数据类型,如INT16U表示16 位无符号整型,对于ARM 这样的32 位内核,INT16U 是unsigned short 型,如果是16位的处理器,则是unsigned int 型。

2)定义允许和禁止中断宏

与所有的实时内核一样,μC/OS-Ⅱ需要先禁止中断再访问代码的临界段,并且在访问完毕后重新允许中断,这就使得μC/OS-Ⅱ能够保护临界段代码免受多任务或中断服务例程(ISR)的破坏。 中断禁止时间是商业实时内核公司提供的重要指标之一,因为它将影响到用户的系统对实时事件的响应能力。 虽然μC/OS-Ⅱ尽量使中断禁止时间达到最短,但是μC/OS-Ⅱ的中断禁止时间还主要依赖于处理器结构和编译器产生的代码的质量。 通常每个处理器都会提供一定的指令来禁止/允许中断,因此,用户的C 语言编译器必须要有一定的机制来直接从C 语言程序中执行这些操作。

μC/OS-Ⅱ定义了两个宏来禁止和允许中断:OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()。

程序如下:

程序中使用了一个变量OSEntersum,它保存关中断的次数(中断嵌套层数)。 在调用OS_ENTER_CRITICAL()时,它的值增加;同时关中断,在调用OS_EXIT_CRITICAL()时,它的值减少,并且仅在其值为“0”时开中断。 这样,关中断和开中断就可以嵌套了。 每个任务都有独立的变量OSEntersum,在任务切换时保存和恢复各自的OSEntersum。 各个任务开关中断的状态可以不同,任务不必过分考虑关中断对其他任务的影响。

在ARM 处理器核中,关中断和开中断是通过改变程序状态寄存器CPSR 中的相应控制位实现的。 由于使用了软件中断,程序状态寄存器CPSR 保存到程序状态保存寄存器SPSR中,软件中断退出时会将SPSR 恢复到CPSR 中。 因此,程序只要改变程序状态保存寄存器SPSR 中相应的控制位就可以了。 改变这些位是通过嵌入汇编实现的,代码很简单,这里不再说明。

3)定义栈的增长方向

μC/OS-Ⅱ使用结构常量OS_STK_GROWTH 来指定堆栈的生长方式:

①置OS_STK_GROWTH 为“0”,表示堆栈从下往上长。

②置OS_STK_GROWTH 为“1”,表示堆栈从上往下长。

虽然ARM 处理器核对于两种方式均支持,但gcc 的C 语言编译器仅支持一种方式,即从上往下增长,并且必须是满递减堆栈,所以,OS_STK_GROWTH 的值为“1”,它在OS_CPU. H中定义,用户规划好栈的增长方向后便定义了符号OS_STK_GROWTH 的值。

4)定义OS_TASK_SW 宏

OS_TASK_SW 宏是在μC/OS-Ⅱ从低优先级任务切换到最高优先级任务时被调用的,可以采用下面两种方式定义:如果处理器支持软中断,可以使用软中断将中断向量指向OSCtxSw函数或者直接调用OSCtxSw 函数。

μC/OS-Ⅱ的OSCtxsw 函数原型:

(2)移植用汇编语言编写的4 个与处理器相关的函数(OS_CPU_A.ASM)

1)OSStartHighRdy();运行优先级最高的就绪任务

OSStartHighRdy()函数是在OSStart()多任务启动之后,负责从最高优先级任务的TCB 控制块中获得该任务的堆栈指针sp,并通过sp 依次将CPU 现场恢复,这时系统就将控制权交给用户创建的该任务进程,直到该任务被阻塞或者被其他更高优先级的任务抢占CPU。 该函数仅仅在多任务启动时被执行一次,用来启动最高优先级的任务执行。 移植该函数的原因是它涉及将处理器寄存器保存到堆栈的操作。

2)OSCtxSw();任务级的任务切换函数

本函数由OS_TASK_SW 宏调用,OS_TASK_SW 由OSSched 函数调用,OSSched 函数负责任务之间的调度;OSCtxSw 函数的工作是先将当前任务的CPU 现场保存到该任务的堆栈中,然后获得最高优先级任务的堆栈指针,并从该堆栈中恢复此任务的CPU 现场,使之继续执行,该函数完成了一次任务切换。

3)OSIntCtxSw();中断级的任务切换函数

该函数由OSIntExit()调用。 由于中断可能会使更高优先级的任务进入就绪态,因此,为了让更高优先级的任务能立即运行,在中断服务子程序的最后,OSIntExit()函数会调用OSIntCtxSw()做任务切换。 这样做的目的主要是能够尽快地让高优先级的任务得到响应,保证系统的实时性能。 OSIntCtxSw()与OSCtxSw()都是用于任务切换的函数,其区别在于在OSIntCtxSw()中无须再保存CPU 寄存器,因为在调用OSIntCtxSw()之前已经发生了中断,OSIntCtxSw()已经将默认CPU 寄存器保存到被中断了的任务的堆栈中了。(www.xing528.com)

4)OSTickISR();时钟节拍中断服务函数

时钟节拍是特定的周期性中断,是由硬件定时器产生的,这个中断可以看作系统心脏的脉动。 时钟的节拍式中断使得内核可以将任务延时若干个整数时钟节拍,以及当任务等待事件发生时,提供等待超时的依据。 时钟节拍频率越快,系统的额外开销就越大,中断之间的时间间隔取决于不同的应用。

OSTickISR()首先将CPU 寄存器的值保存在被中断任务堆栈中,之后调用OSIntEnter();随后,OSTickISR()调用OSTimeTick(),检查所有处于延时等待状态的任务,判断是否有延时结束就绪的任务。 OSTickISR()的最后调用OSIntExit(),如果在中断中(或其他嵌套的中断)有更高优先级的任务就绪,并且当前中断为中断嵌套的最后一层。 OSIntExit()将进行任务调度。 注意如果进行了任务调度,OSIntExit()将不再返回调用者,而是用新任务的堆栈中的寄存器数值恢复CPU 现场,然后用IRET 实现任务切换。 如果当前中断不是中断嵌套的最后一层,或中断中没有改变任务的就绪状态,OSIntExit()将返回调用者OSTickISR(),最后OSTick-ISR()返回被中断的任务。

(3)移植C 语言编写的6 个与操作系统相关的函数(OS_CPU_C.C)

OSTaskStkInit()

OSTaskCreateHook()

OSTaskDelHook()

OSTaskSwHook()

OSTaskStatHook()

OSTimeTickHook()

这些文件中,唯一必须移植的是任务堆栈初始化函数OSTaskStkInit(),这个函数在任务创建时被调用,它负责初始化任务的堆栈结构并返回新堆栈的指针stk。 在ARM 体系结构下,任务堆栈空间由高至低依次保存着pc、lr、r12、r11、r10、…、r1、r0、CPSR、SPSR, OSTaskStkInit()初始化后的堆栈内容如图7.2 所示。 堆栈初始化工作结束后,返回新的堆栈栈顶指针。

图7.2 初始化堆栈后的堆栈内容

程序如下:

后面5 个HooK 函数又称为钩子函数,主要用来扩展μC/OS-Ⅱ功能,必须声明,它并不一定要包含任何代码。

1)OSTaskCreateHook()

当用OSTaskCreate()或OSTaskCreateExt()建立任务时,就会调用OSTaskCreateHook()。 当μC/OS-Ⅱ设置完了自己的内部结构后,会在调用任务调度程序之前调用OSTaskCreateHook(),该函数被调用时中断是禁止的。 因此,用户应尽量减少该函数中的代码,以缩短中断的响应时间。

2)OSTaskDelHook()

当任务被删除时,就会调用OSTaskDelHook(),该函数在将任务从μC/OS-Ⅱ的内部任务链表中解开之前被调用。 当OSTaskDelHook()被调用时,它会收到指向正被删除任务的OS_TCB 的指针,这样它就可以访问所有的结构成员。 OSTaskDelHook()可以用来检验TCB 扩展是否被建立了(一个非空指针)并进行一些清除操作,此函数不返回任何值。

3)OSTaskSwHook()

当发生任务切换时,调用OSTaskSwHook(),无论任务切换是通过OSCtxSw()还是OSIntCtxSw()来执行的都会调用该函数。 OSTaskSwHook()可以直接访问OSTCBCur 和OSTCBHighRdy,因为它们都是全局变量。 OSTCBCur 指向被切换出去的任务的OS_TCB,而OSTCBHighRdy 指向新任务的OS_TCB。 注意,在调用OSTaskSwHook()期间中断一直是被禁止的。因为代码的多少会影响到中断的响应时间,所以用户应尽量使代码简化。 此函数没有任何参数,也不返回任何值。

4)OSTaskStatHook()

OSTaskStatHook()每秒钟都会被OSTaskStat()调用一次。 用户可以用OSTaskStatHook()来扩展统计功能。 例如,用户可以保持并显示每个任务的执行时间,每个任务所占用的CPU份额,以及每个任务执行的频率,等等。 该函数没有任何参数,也不返回任何值。

5)OSTimeTickHook()

OSTaskTimeHook()在每个时钟节拍都会被OSTaskTick()调用。 实际上,OSTaskTime-Hook()是在节拍被μC/OS-Ⅱ真正处理,并通知用户的移植实例或应用程序之前被调用的。OSTaskTimeHook()没有任何参数,也不返回任何值。

注意:只用当OS_CFG.H 中的OS_CPU_HOOKS_EN 被置为“1”时,才会产生以上5 个钩子函数的代码。

其程序清单如下:

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈