首 页 本刊概况 出 版 人 发行统计 在线订阅 欢迎投稿 市场分析 1 组织交流 1 关于我们
 
1
   通信短波
1
   新品之窗
1
   优秀论文
1
   通信趋势
1
   特别企划
1
   运营商动态
1
   技术前沿
1
   市场聚焦
1
   通信视点
1
   信息化论坛
1
当前位置:首页 > 优秀论文
嵌入式实时操作系统FreeRTOS在ARM7上移植的实现
作者:黄鹏程 福州大学 数学与计算机科学学院 福州 351002
来源:不详
更新时间:2009/9/19 19:29:00
正文:

1 引言
FreeRTOS操作系统是一个源码公开的免费的嵌入式实时操作系统,具有可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种体系结构的微处理器上运行,其最新版本为5.2.0版。
作为一个轻量级的操作系统,FreeRTOS提供的功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能等,可基本满足较小系统的需要。FreeRTOS内核支持优先级调度算法,每个任务可根据重要程度的不同被赋予一定的优先级,CPU总是让处于就绪态的、优先级最高的任务先运行。FreeRTOS内核同时支持轮换调度算法,系统允许不同的任务使用相同的优先级,在没有更高优先级任务就绪的情况下,同一优先级的任务共享CPU的使用时间。在任务的组织实现方面,FreeRTOS内核支持传统的实现:各任务拥有各自的堆栈,支持完全的抢占式调度。FreeRTOS内核同时支持各任务共享同一个堆栈,使RAM的需求进一步减小。但正因如此,该方式的使用受到相对严格的限制。
本文移植的硬件平台是由恩智浦公司生产的基于ARM7TDMI核的微处理器LPC2292。开发调试平台是ARM ADS 1.2。

2 启动代码的编写
启动代码是芯片复位后进入C语言的main()函数前执行的一段代码,主要是为运行C语言程序提供基本的运行环境,如初始化存储系统等。为了能够进行系统初始化,采用一个汇编文件作为启动代码是常见的做法。初始化代码所完成的操作与具体的硬件平台相关,但一般包括如下内容:(1)初始化异常向量表;(2)初始化存储器系统;(3)初始化堆栈;(4)初始化有特殊要求的端口、设备;(5)初始化应用程序的运行环境;(6)改变处理器的运行模式;(7)调用主应用程序。
需要注意的是,在对处理器每个模式的堆栈指针寄存器进行初始化的时候,用户模式下的堆栈寄存器必须最后进行初始化。因为在用户模式下,不能用MSR指令从用户模式切换到其它的特权模式,所以如果在其它特权模式的堆栈指针被初始化之前切换到用户模式,就无法对特权模式下的堆栈指针进行初始化。
第二个需要注意的是,程序使用编译器分配的空间作为堆栈,而不是按照通常的做法把堆栈分配到RAM的顶端。这样做有两个好处:(1)不必知道RAM顶端的位置,移植更加方便。(2)编译器给出的占用RAM空间的大小就是实际占用的大小,便于控制RAM的分配。

3 FreeRTOS的移植
本次一直主要集中在3个文件里面:portmacro.h,port.c,port.s。其中portmacro.h主要包含于编译器相关的数据类型的定义、堆栈类型的定义以及几个宏定义和函数说明。而port.c中则包含与移植有关的C函数,包括堆栈的初始化函数、任务调度器启动函数、临界区的进入与退出、时钟中断服务程序等。port.s中则包含与移植有关的汇编语言函数,包括上下文切换、开/关中断、任务切换等。移植中关键的功能模块实现如下文所述。
3.1开/关中断的实现
FreeRTOS使用函数portDISABLE_INTERRUPTS() 和portENABLE_INTERRUPTS( )分别实现关中断和开中断。这些代码与处理器有关,需要进行移植。在ARM处理器核中,关中断和开中断是通过改变程序状态寄存器CPSR中的相应控制位来实现的。开中断的汇编语言函数portENABLE_INTERRUPTS()的代码如下:
portENABLE_INTERRUPTS
STMDB SP!, {R0} ;/* 把R0压入栈 */
MRS R0, CPSR ;/* 读取状态寄存器到R0 */
BIC R0, R0, #0xC0 ; /* 允许IRQ、FIQ中断 */
MSR CPSR_cxsf, R0 ; /* 回写R0的值到状态寄存器 */
LDMIA SP!, {R0} ;/* R0出栈 */
BX LR ;/* 函数返回 */
用类似的方法可以实现关中断函数portDISABLE_INTERRUPTS()。
3.2 临界区的进入与退出
代码的临界段也称为临界区,指处理时不可分割的代码。一旦这部分代码开始执行,则不允许任何中断打断。开中断和关中断可以保护临界代码段,保证FreeRTOS的临界代码不会被多个任务和中断服务程序同时访问,避免造成共享数据的不一致性。为了保护临界区的资源,在进入临界区之前须关中断,而临界区代码执行完毕后,要立即开中断。临界区的退出函数vPortExitCritical()代码如下:
void vPortExitCritical( void )
{
if( ulCriticalNesting > portNO_CRITICAL_NESTING )
{
ulCriticalNesting--; /* 中断嵌套计数器ulCriticalNesting自减一*/

/* 如果中断嵌套层数为零的话,则需要开中断. */
if( ulCriticalNesting == portNO_CRITICAL_NESTING )
{
/* 调用函数portENABLE_INTERRUPTS()来开中断. */
portENABLE_INTERRUPTS();
}
}
}
用类似的方法可以实现临界区的进入函数vPortEnterCritical( )。
3.3 堆栈的初始化

任务创建函数xTaskCreate()通过调用堆栈的初始化函数pxPortInitialiseStack()来初始化任务的栈结构;实际是定义了任务堆栈上下文(context)的内容。所有的寄存器都保存到堆栈中,堆栈看起来就像中断刚发生过一样。上下文的保存格式如图1所示。
在堆栈的上下文中,PC存放任务执行的第一条指令,LR保存的是任务的返回地址,SP保存的是任务的堆栈地址。R0存放的是传递给任务的参数。CPSR存放的是任务运行时处理器的初始状态。CriticalNesting存放的是中断嵌套计数器ulCriticalNesting的值。任务堆栈的上下文保存结构与任务切换的实现紧密相关,所以我们在设计上下文保存结构的时候,要重点考虑实现任务切换的便捷性。
PC
LR
SP
R12
.........
R1
R0
CPSR
CriticalNesting
图1 上下文的保存结构


portSTACK_TYPE *pxPortInitialiseStack( portSTACK_TYPE *pxTopOfStack,
pdTASK_CODE pxCode, void *pvParameters )
{
portSTACK_TYPE *pxOriginalTOS;

pxOriginalTOS = pxTopOfStack;
*pxTopOfStack = ( portSTACK_TYPE ) pxCode + portINSTRUCTION_SIZE; /* PC */
*(--pxTopOfStack) = ( portSTACK_TYPE ) 0xaaaaaaaa; /* R14 */
*(--pxTopOfStack) = ( portSTACK_TYPE ) pxOriginalTOS; /* SP */
*(--pxTopOfStack) = ( portSTACK_TYPE ) 0x12121212; /* R12 */
.................................................
*(--pxTopOfStack) = ( portSTACK_TYPE ) pvParameters; /* R0 */
*(--pxTopOfStack) = ( portSTACK_TYPE ) portINITIAL_SPSR; /* CPSR */
*(--pxTopOfStack) = portNO_CRITICAL_SECTION_NESTING; /* ulCriticalNesting */

return pxTopOfStack;
}
3.4 上下文的保存与恢复
本次移植分别定义了两个宏来实现上下文的保存和恢复,分别是:portSAVE_CONTEXT()和portRESTORE_CONTEXT()。portSAVE_CONTEXT()宏首先设置R0指向任务的堆栈,接着保存任务的返回地址,然后再保存其他的寄存器和CPSR,以及中断嵌套计数器。最后把新的栈顶保存在当前的任务控制块里面。其宏定义如下:
MACRO
portSAVE_CONTEXT
STMDB SP!, {R0} ; 设置R0指向任务堆栈.
STMDB SP, {SP}^
SUB SP, SP, #4
LDMIA SP!, {R0}
STMDB R0!, {LR}
MOV LR, R0
LDMIA SP!, {R0}
STMDB LR, {R0-LR}^ ;把R0-LR寄存器压入任务堆栈
SUB LR, LR, #60
MRS R0, SPSR ;把SPSR压入任务堆栈
STMDB LR!, {R0}
LDR R0, =ulCriticalNesting ;把中断嵌套计数器压入任务堆栈
LDR R0, [R0]
STMDB LR!, {R0}
LDR R1, =pxCurrentTCB ; 存储当前的任务堆栈栈顶到任务控制块中
LDR R0, [R1]
STR LR, [R0]
MEND
利用类似的方法可以实现portRESTORE_CONTEXT()宏,需要注意的是,该宏要严格按照保存上下文的相反的顺序恢复上下文。
3.5 启动任务调度
操作系统初始化之后,就可以开启系统时钟,运行系统内第1个最高优先级的就绪任务。对于第1个执行的任务,不需要进行上下文切换,而只要恢复上下文即可。第一个任务的执行是通过调用汇编函数portRESTORE_CONTEXT()恢复上下文来实现的。启动任务调度程序代码如下:
portBASE_TYPE xPortStartScheduler( void )
{
prvSetupTimerInterrupt(); /* 设置并启动Timer0. */
vPortStartFirstTask(); /* 启动第一个任务. */
return 0;
}
3.6 任务切换的实现
程序中portYIELD()函数的调用,会进行一次任务级的上下文切换。在本次移植中,使用软件中断指令SWI是处理器进入管理模式和ARM指令状态,并使用功能0实现portYIELD()的功能。portYIELD()(功能号0)最终使用程序vPortYieldProcessor实现。vPortYieldProcessor的汇编代码如下:
vPortYieldProcessor
ADD LR, LR, #4
portSAVE_CONTEXT ; 保存上下文 (宏)
LDR R0, =vTaskSwitchContext ; 选择就绪的优先级最高优先级的任务
MOV LR, PC
BX R0
portRESTORE_CONTEXT ; 恢复上下文 (宏)

3.7 时钟中断服务的实现
当时钟中断到来的时,处理器跳转到相应的时钟中断服务程序,时钟中断服务程序主要调用vTaskIncrementTick()函数,该处理函数主要处理跟系统时钟相关的工作,如将时钟节拍数加1,检查是否有更高优先级的任务就绪等等。系统时钟中断服务程序由vTickISR()函数实现。包括以下步骤:(1)保存上下文。(2)调用vTaskIncrementTick()函数,如果系统的调度策略配置为可抢占调度,则查找最高优先级的就绪任务。(3)清除中断源,并通知中断控制器中断结束。(4)恢复上下文。vTickISR()函数的代码如下:
void vTickISR( void )
{
portSAVE_CONTEXT(); /* 需要保存上下文 */

/* 增加xTickCount值,检查新的xTickCount值是否引起一个延迟周期过期,这个函数调用可导致一个任务变成准备运行. */
vTaskIncrementTick();

/*如果是系统配置为可抢占式调度,则检查是否要上下文切换。如果唤醒的任务比已经中断的任务有更高优先级,就需要切换 */
#if configUSE_PREEMPTION == 1
vTaskSwitchContext();
#endif
T0IR= portTIMER_MATCH_ISR_BIT; /* 清除中断源*/
VICVectAddr = portCLEAR_VIC_INTERRUPT; /* 通知中断控制器中断结束*/
/*恢复上下文.如果发生了上下文切换,这将恢复要继续运行的任务的上下文 */
portRESTORE_CONTEXT();
}

4结论
本文设计并实现了FreeRTOS 5.2.0操作系统到ARM7处理器上的移植。移植程序在福州大学工业控制研究所自行研制的ZD100终端上实现,经该环境的多任务运行结果表明,系统稳定可靠。同时,移植的方法在同类ARM架构的处理器上具有较强的通用性。


参考文献

[1] Richard Barry. Creating a New FreeRTOS.org Port[EB/OL].
http://www.freertos.org/FreeRTOS-porting-guide.html.2008.1-1
[2] NXP Semiconductors. LPC2292 USER MANUAL[Z]. 2004.14-77
[3] 刘滨,王琦,刘丽丽. 嵌入式操作系统FreeRTOS的原理与实现[J]. 单片机与嵌入式系统应用, 2005.7 : 8-11.
[4] ARM Limited. ARM Developer Suite(Version 1.2).Developer Guide[Z].1999.43-51
[5] 周立功. ARM嵌入式系统基础教程[M].北京:北京航空航天大学出版社.2004.30-281

作者简介
黄鹏程,男,1984年出生,福建莆田市人,福州大学数学与计算机科学学院硕士研究生,主要研究的方向:嵌入式系统。<

 
 
   
《通信市场》 中国·北京·复兴路49号通信市场(100036) 点击查看具体位置
电话:86-10-6820 7724, 6820 7726
京ICP备05037146号-8
建议使用 Microsoft IE4.0 以上版本 800*600浏览 如果您有什么建议和意见请与管理员联系