打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
嵌入式实时操作系统RreeRTOS在ARM7上移植的实现

http://blog.sina.com.cn/s/blog_a092aeab0101ac0j.html

2012

转自:http://www.eefocus.com/html/09-11/86277s.shtml

 

7.6 嵌入式实时操作系统RreeRTOS在ARM7上移植的实现

作者:黄鹏程
福州大学 数学与计算机科学学院 福州 351002
摘要:
本文介绍了目前在嵌入式系统应用中一个流行的实时操作系统FreeRTOS,详细论述了FreeRTOS在基于ARM体系结构的嵌入式微控制器LPC2292上移植的实现过程,指出了在FreeRTOS的移植过程中的重点和难点问题,得出了FreeRTOS在ARM上的移植的一般性方法。
关键字:操作系统, FreeRTO, ARM, LPC2292, 移植

7.6.1 项目概述

FreeRTOS操作系统是一个源码公开的免费的嵌入式实时操作系统,具有可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种体系结构的微处理器上运行,其最新版本为5.2.0版。作为一个轻量级的操作系统,FreeRTOS提供的功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能等,可基本满足较小系统的需要。FreeRTOS内核支持优先级调度算法,每个任务可根据重要程度的不同被赋予一定的优先级,CPU总是让处于就绪态的、优先级最高的任务先运行。

FreeRTOS内核同时支持轮换调度算法,系统允许不同的任务使用相同的优先级,在没有更高优先级任务就绪的情况下,同一优先级的任务共享CPU的使用时间。在任务的组织实现方面,FreeRTOS内核支持传统的实现:各任务拥有各自的堆栈,支持完全的抢占式调度。FreeRTOS内核同时支持各任务共享同一个堆栈,使RAM的需求进一步减小。但正因如此,该方式的使用受到相对严格的限制。
本文移植的硬件平台是由恩智浦公司生产的基于ARM7TDMI核的微处理器LPC2292。开发调试平台是ARM ADS 1.2。

7.6.2 启动代码的编写

启动代码是芯片复位后进入C语言的main()函数前执行的一段代码,主要是为运行C语言程序提供基本的运行环境,如初始化存储系统等。为了能够进行系统初始化,采用一个汇编文件作为启动代码是常见的做法。初始化代码所完成的操作与具体的硬件平台相关,但一般包括如下内容:

(1)初始化异常向量表;
(2)初始化存储器系统;
(3)初始化堆栈;
(4)初始化有特殊要求的端口、设备;
(5)初始化应用程序的运行环境;
(6)改变处理器的运行模式;
(7)调用主应用程序。

需要注意的是,在对处理器每个模式的堆栈指针寄存器进行初始化的时候,用户模式下的堆栈寄存器必须最后进行初始化。因为在用户模式下,不能用MSR指令从用户模式切换到其它的特权模式,所以如果在其它特权模式的堆栈指针被初始化之前切换到用户模式,就无法对特权模式下的堆栈指针进行初始化。

第二个需要注意的是,程序使用编译器分配的空间作为堆栈,而不是按照通常的做法把堆栈分配到RAM的顶端。这样做有两个好处:

(1)不必知道RAM顶端的位置,移植更加方便

(2)编译器给出的占用RAM空间的大小就是实际占用的大小,便于控制RAM的分配。

7.6.3 FreeRTOS的移植

本次一直主要集中在3个文件里面:portmacro.h,port.c,port.s。其中portmacro.h主要包含于编译器相关的数据类型的定义、堆栈类型的定义以及几个宏定义和函数说明。而port.c中则包含与移植有关的C函数,包括堆栈的初始化函数、任务调度器启动函数、临界区的进入与退出、时钟中断服务程序等。port.s中则包含与移植有关的汇编语言函数,包括上下文切换、开/关中断、任务切换等。移植中关键的功能模块实现如下文所述。

1.开/关中断的实现
FreeRTOS使用函数portDISABLE_INTERRUPTS()和portENABLE_INTERRUPTS()分别实现关中断和开中断。这些代码与处理器有关,需要进行移植。在ARM处理器核中,关中断和开中断是通过改变程序状态寄存器CPSR中的相应控制位来实现的。开中断的汇编语言函数portENABLE_INTERRUPTS()的代码如下:
portENABLE_INTERRUPTS
STMDB SP!, {R0} ;
MRS R0, CPSR ;
BIC R0, R0, #0xC0 ;
MSR CPSR_cxsf, R0 ;
LDMIA SP!, {R0} ;
BX LR ;
用类似的方法可以实现关中断函数portDISABLE_INTERRUPTS()。

2.临界区的进入与退出
代码的临界段也称为临界区,指处理时不可分割的代码。一旦这部分代码开始执行,则不允许任何中断打断。开中断和关中断可以保护临界代码段,保证FreeRTOS的临界代码不会被多个任务和中断服务程序同时访问,避免造成共享数据的不一致性。为了保护临界区的资源,在进入临界区之前须关中断,而临界区代码执行完毕后,要立即开中断。临界区的退出函数vPortExitCritical()代码如下:
void vPortExitCritical( void )
{
if( ulCriticalNesting > portNO_CRITICAL_NESTING)
{
ulCriticalNesting--;

if( ulCriticalNesting == portNO_CRITICAL_NESTING )
{

portENABLE_INTERRUPTS();
}

}
}
用类似的方法可以实现临界区的进入函数vPortEnterCritical( )。

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;
*(--pxTopOfStack) = ( portSTACK_TYPE ) 0xaaaaaaaa;
*(--pxTopOfStack) = ( portSTACK_TYPE ) pxOriginalTOS;
*(--pxTopOfStack) = ( portSTACK_TYPE ) 0x12121212;
.................................................

*(--pxTopOfStack) = ( portSTACK_TYPE ) pvParameters;
*(--pxTopOfStack) = ( portSTACK_TYPE ) portINITIAL_SPSR;
*(--pxTopOfStack) = portNO_CRITICAL_SECTION_NESTING;
return pxTopOfStack;
}

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()宏,需要注意的是,该宏要严格按照保存上下文的相反的顺序恢复上下文。

5.启动任务调度
操作系统初始化之后,就可以开启系统时钟,运行系统内第1个最高优先级的就绪任务。对于第1个执行的任务,不需要进行上下文切换,而只要恢复上下文即可。第一个任务的执行是通过调用汇编函数portRESTORE_CONTEXT()恢复上下文来实现的。启动任务调度程序代码如下:
portBASE_TYPE xPortStartScheduler( void )
{
prvSetupTimerInterrupt();
vPortStartFirstTask();
return 0;
}

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 ; 恢复上下文 (宏)

7.时钟中断服务的实现
当时钟中断到来的时,处理器跳转到相应的时钟中断服务程序,时钟中断服务程序主要调用vTaskIncrementTick()函数,该处理函数主要处理跟系统时钟相关的工作,如将时钟节拍数加1,检查是否有更高优先级的任务就绪等等。系统时钟中断服务程序由vTickISR()函数实现。包括以下步骤:

(1)保存上下文。

(2)调用vTaskIncrementTick()函数,如果系统的调度策略配置为可抢占调度,则查找最高优先级的就绪任务。

(3)清除中断源,并通知中断控制器中断结束。

(4)恢复上下文。vTickISR()函数的代码如下:
void vTickISR( void )
{
portSAVE_CONTEXT();

vTaskIncrementTick();

#if configUSE_PREEMPTION == 1
vTaskSwitchContext();
#endif
T0IR= portTIMER_MATCH_ISR_BIT;
VICVectAddr = portCLEAR_VIC_INTERRUPT;

portRESTORE_CONTEXT();
}

7.6.4.结论

本文设计并实现了FreeRTOS5.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).DeveloperGuide[Z].1999.43-51;
[5] 周立功. ARM嵌入式系统基础教程[M].北京:北京航空航天大学出版社.2004.30-281;

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
FreeRTOS 在STM32上的移植 V1.0 - 伟研科技
FreeRTOS学习笔记 (2)堆栈
FreeRTOS
干货 | FreeRTOS学习笔记——中断与任务切换
FreeRTOS高级篇4
嵌入式操作系统FreeRTOS的原理与实现
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服