APP下载

μC/OS-II在STM32F103上移植的新方法

2013-11-19周兆丰侯向锋鲁池梅李莲英

关键词:堆栈固件指针

周兆丰,侯向锋,鲁池梅,李莲英

(1. 湖北师范学院 电工电子实验教学示范中心,湖北 黄石 435002;2. 湖北师范学院 物理与电子科学学院,湖北 黄石 435002;3.许昌学院,河南 许昌 461000)

0 引言

在基于ARM公司最新的Cortex-M架构的处理器中,ST(意法半导体)以占有45%销售份额的绝对优势领先于其它厂商,因而其STM32系列的处理器一直是被关注的焦点。

CMSIS是独立于供应商的Cortex-M处理器系列硬件抽象层,为芯片厂商和中间件供应商提供了连续的、简单的处理器软件接口,简化了软件复用,降低了Cortex-M3上操作系统的移植难度,并缩短了新入门的微控制器开发者的学习时间和新产品的上市时间。

ST针对这个标准开发了自己的标准外设库,也叫固件库,最新版本为3.5.固件库包括了所有的片上外设驱动函数,稳定性高且易于开发,因而基于固件库的开发已经成为了主流,绝对优势领先于传统的基于寄存器的开发。

μC/OS是一种可移植的、可植入ROM的、可裁剪的、抢占式的、实时多任务操作系统内核,因其对于学校研究完全免费,代码风格良好,功能强大稳定,所以被广泛应用,且特别适合用来学习、研究和开发。μC/OS-II已经成为世界上最流行的免费实时内核,最新版为μC/OS-III.

基于上述原因,μC/OS-II在STM32处理器上的移植早已成为研究的热点,移植的文档多是研究μC/OS-II与STM32处理器关系的传统方法,传统方法分析了μC/OS-II中与设备相关的文件的修改方法,却没有很好的利用固件库,因此本文提出了基于固件库开发的移植新方法。本文的移植主要是针对有固件库开发经验的学生和技术人员,并且选用了STM32F103RB处理器和2.86版的μC/OS-II.

1 移植相关背景

所谓移植,就是使一个实时内核能在某个微处理器或微控制器上运行。为了解决好μC/OS-II与固件库及处理器之间的关系,先介绍相关的知识。

1.1 基于CMSIS标准的软件架构

该架构主要分为以下4层:用户应用层、操作系统及中间件接口层、CMSIS层、硬件寄存器层。CMSIS分为3个基本功能层:核内外设访问层、中间件访问层和设备访问层。CMSIS层起着承上启下的作用:一方面该层对硬件寄存器层进行统一实现,屏蔽了不同厂商对Cortex-M系列微处理器核内外设寄存器的不同定义;另一方面又向上层的操作系统及中间件接口层和应用层提供接口,简化了应用程序开发难度,使开发人员能够在完全透明的情况下进行应用程序开发。

ST的固件库,就是一个典型的无操作系统的基于CMSIS标准开发的软件架构。如图1(b)所示startup为基于汇编的启动代码文件,USER为用户应用层,BSP是与开发板相关的硬件驱动文件(如led.c),FWlib为标准的核内外设驱动函数,CMSIS为如上所述的CMSIS层。uC-CPU与uC-LIB是厂商自带的移植文件完全可以被标准CMSIS及函数取代,Scatter离散加载文件与uC-Probe(方便监控与控制的实用工具)本次移植不需要。

1.2 μC/OS架构

μC/OS主要分为应用层,与设备无关的代码层,配置层和与设备有关的硬件层。

针对图1(a)所示,应用层为APP文件,与设备无关的代码层为uC-OS-II/Source文件,配置层包括os_cfg.h,app_cfg.h,includes.h等文件,与设备有关的硬件层为uC-OS-II/Port文件,这也是移植的核心所在。

(a) Micrium_STM32xxx_uCOS-II工程 (b)LED流水灯工程 (c)uCOS_BF 工程(移植后)

针对上述介绍,本文移植的主要内容有:

1)图1(b)需要图1(a)中的μC/OS-II源码(uC-OS-II/Source和uC-OS-II/Port)及相关配置文件;

2)两者功能重复的文件选择图1(b),如启动文件和Systick相关的时钟节拍函数;

3)该变量大的自己重新写如includes.h和一些配置文件;

4)需自己重新编写的文件主函数main.c和任务函数等。

2 移植主要步骤

2.1 下载Micrium_STM32xxx_uCOS-II工程

打开Micrium官方网站的下载中心(网址http://micrium.com/downloadcenter/),在Browse by Semiconductor Vendor下点击STMicroelectronics进去,找到如图2所示文件,下载第一个压缩文件包,即Micrium_STM32xxx_uCOS-II工程,解压并重新编译后,没有错误和警告。

图2 相关程序包

2.2 完善uCOS-II文件

拷贝uCOS-II文件夹至STM32_LED工程根目录,此时uCOS-II目录下有Doc,Ports和Source文件夹,在此文件夹下创建Port与Config文件夹并拷贝PortsARM-Cortex-M3GenericRealView下所有文件到Port文件夹下,然后删除此文件夹。拷贝include.h(需重写)与os_cfg.h文件到Config文件夹下。Port文件中包含了OS_CPU.H,OS_CPU_C.C与OS_CPU_A.ASM文件,是移植中最重要的文件,但是底层函数已经写好,因为我们下载的就是针对这款处理器的,但是需要对其做一些针对性的修改,另外针对重要的函数运行过程在后面会重点介绍。

2.3 完善BSP文件

在uCOS-II根目录创建BSP文件夹并创建BSP.C,BSP.H文件,用来放置系统初始化相关函数,然后再拷贝led.c与led.h文件到此目录。

2.4 修改SysTick定时相关函数

编译时会出现如下错误,.OutputSTM32_LED.axf: Error: L6218E: Undefined symbol OS_CPU_SysTickClkFreq (referred from os_cpu_c.o).这是由于没找到OS_CPU_SysTickClkFreq()函数引起的,因为ST库函数中有更直观的SysTick函数,所以uCOS-II中与SysTick相关的函数我们需要屏蔽掉。具体如下:

1)此时先把Port文件夹下文件的只读属性去除,然后屏蔽OS_CPU_C.C中OS_CPU_SysTickHandler()与OS_CPU_SysTickInit()函数;

2)在BSP.C中添加SysTick_init()函数

void SysTick_init(void)

{ SysTick_Config(SystemFrequency/OS_TICKS_PER_SEC); }

//此处需要修改优先级为14,目的是大于OS_CPU_PendSV_Handler的优先级15。

3)在stm32f10x_it.c文件中的SysTick_Handler()中断函数中添加OSIntEnter()、OSTimeTick()及OSIntExit()。用#include "includes.h" 替换掉所有的头文件包含语句。

4)修改启动代码文件startup_stm32f10x_hd.s

在启动文件中修改三处PendSV_Handler为OS_CPU_PendSVHandler,因为PendSV异常函数在OS_CPU_A.ASM中的名称就是OS_CPU_PendSVHandler。

5)重写includes.h

只要包含stm32f10x.h,ucos_ii.h,BSP.h,tasks.h,led.h几个头文件就够了。

6)修改os_cfg.h

禁用信号量、互斥信号量、邮箱、队列、信号量集、定时器、内存管理等,并关闭调试模式,禁用钩子函数和多重事件控制,用#define OS_TICKS_PER_SEC 100;来设定时钟节拍频率,用#define OS_LOWEST_PRIO 40;来定义最低优先级,可根据实际需要修改。

7)添加task.c与task.h

将此任务文件添加到USER文件夹下,给每个任务分配堆栈,并编写3个简单的LED闪烁任务。

8)编写主函数main.c文件,并调试运行

#include "includes.h"

OS_STK TASK_START_STK[START_STK_SIZE]; //定义栈大小

int main(void)

{

BSP_Init();

OSInit();

OSTaskCreate(TaskStart,(void *)0,

(OS_STK*)&TASK_START_STK[START_STK_SIZE-1], START_TASK_Prio);

OSStart();

return 0;

}

3 移植的关键函数分析

3.1 OS_CPU_C.C中的堆栈初始化函数OSTaskStkInt().

了解此函数需要明白下面几点:

1)用于在创建任务时初始化任务堆栈,也就是需要去模拟进入中断时的现场,总共R0-R15,xPSR共16个寄存器。

2)堆栈属性:满递减。

3)入栈顺序:首先是xPSR,PC,LR,R12,R3,R2,R1,R0,是发生中断时由硬件自动压栈的,其次是R11,R10,R9,R8,R7,R6,R5,R4,是手动入栈的。

4)内部入栈顺序如图3所示。

图3 内部入栈序列

CM3内核在内部打乱了入栈顺序[1],其原因是:

a)PC与xPSR先入栈就可以更早地启动服务例程指令的预取(因为此时需要修改PC),同时也做到了在早期就可以更新xPSR中IPSR位段的值;

b)先R0-R3,最后是R12,为的是更容易地使用SP基址来索引寻址(如LDM);

c) 先xPSR,PC,LR,R0-R3,R12后R4-R11为的是遵守C函数标准调用约定《AAPCS,Ref5》,这个约定使得中断服务函数可以用C编写,编译器优先使用先入栈的寄存器保存中间结果。

5)*(stk) = (INT32U)0x01000000L; /* xPSR */,xPSR的T位置1,否则一开始就会产生Fault。

6)*(--stk) = (INT32U)task;/* Entry Point */,PC指向任务端口。

7)*(--stk) = (INT32U)0xFFFFFFFEL;/*LR,last bit 0*/ 非法值,目的是不让任务返回。

8)*(--stk) = (INT32U)p_arg; /* R0 */ 目的是传递任务函数的参数。

3.2 OS_CPU_A.ASM中的PendSV异常函数OS_CPU_PendSVHandler

因为在OS_CPU_A.ASM中,四个关键汇编函数OSStartHighRdy(),OSCtxSw()与OSIntCtxSw()和OSTickISR()[2],其中前三个都需要出发PendSV异常,所以深入理解OS_CPU_PendSVHandler函数是理解底层运行过程的关键。

了解此函数需要理解以下几点:

1)PendSV是用来进行上下文切换的。

2)对比由Systick,用PendSV做上下文切换可以解决在SysTick中启动上下文切换所带来的ISR被延迟得不到及时响应的问题的。原因是PendSV异常会自动延迟上下文的请求直到其它的ISR都完成了处理任务后才放行,因此PendSV的优先级是被设置为最低的。

3)PendSV异常的执行过程

PendSV,的异常处理函数具体工作是:

a)禁止中断,获取当前堆栈指针(PSP),如果为0(认为是第一次进行上下文切换)则不保存R4-R11,否则将堆栈指针减0x20,手动保存余下的R4-R11,再将SP保存到当前的任务控制块(TCB)中。

b)压栈R14(LR),调用OSTaskSwHook(),获取最高优先级任务堆栈指针,手动存入R4-R11,然后将堆栈指针加0x20,调整LR,使能中断,然后进行异常返回。取最高优先级任务堆栈指针,LDM PSP {R4-R11},然后指针-0x20,原理同上,将新指针存入PSP。此时内容切换基本完成,再手动设置下LR值,使得返回时使用PSP指针。

4)注意上述过程中对堆栈指针加0x20和减0x20的原因在于Cortex-M3在发生异常中断时会对8个寄存器进行自动压栈和出栈。

4 结论

虽然从Micrium官方网站上可以下载到移植好的程序包,但是此程序包与基于CMSIS标准软件架构的固件库还有很大的区别。因为不兼容,所以让长期习惯与固件库开发的学生和技术人员直接用此程序包,还是很不适应的。本文正是基于这个原因为相关技术人员提供了新的移植方法,让他们能够在自己熟悉的固件库环境下很好的使用μC/OS-II操作系统。

参考文献:

[1]Joseph Yiu.ARM Cortex-M3 权威指南[M].北京:航空航天大学出版社,2009.

[2]Jean J Labrosse.嵌入式实时操作系统μCOS-Ⅱ(第2版)[M].北京:航空航天大学出版社,2003.

猜你喜欢

堆栈固件指针
偷指针的人
嵌入式软件堆栈溢出的动态检测方案设计*
为什么表的指针都按照顺时针方向转动
基于堆栈自编码降维的武器装备体系效能预测
基于固件的远程身份认证
基于改进Hough变换和BP网络的指针仪表识别
提取ROM固件中的APP
ARM Cortex—MO/MO+单片机的指针变量替换方法
一种通过USB接口的可靠固件升级技术
奥林巴斯XZ—2新固件升级