μ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.