周兆豐,侯向鋒,魯池梅,李蓮英
(1. 湖北師范學院 電工電子實驗教學示范中心,湖北 黃石 435002;2. 湖北師范學院 物理與電子科學學院,湖北 黃石 435002;3.許昌學院,河南 許昌 461000)
在基于ARM公司最新的Cortex-M架構(gòu)的處理器中,ST(意法半導體)以占有45%銷售份額的絕對優(yōu)勢領先于其它廠商,因而其STM32系列的處理器一直是被關注的焦點。
CMSIS是獨立于供應商的Cortex-M處理器系列硬件抽象層,為芯片廠商和中間件供應商提供了連續(xù)的、簡單的處理器軟件接口,簡化了軟件復用,降低了Cortex-M3上操作系統(tǒng)的移植難度,并縮短了新入門的微控制器開發(fā)者的學習時間和新產(chǎn)品的上市時間。
ST針對這個標準開發(fā)了自己的標準外設庫,也叫固件庫,最新版本為3.5.固件庫包括了所有的片上外設驅(qū)動函數(shù),穩(wěn)定性高且易于開發(fā),因而基于固件庫的開發(fā)已經(jīng)成為了主流,絕對優(yōu)勢領先于傳統(tǒng)的基于寄存器的開發(fā)。
μC/OS是一種可移植的、可植入ROM的、可裁剪的、搶占式的、實時多任務操作系統(tǒng)內(nèi)核,因其對于學校研究完全免費,代碼風格良好,功能強大穩(wěn)定,所以被廣泛應用,且特別適合用來學習、研究和開發(fā)。μC/OS-II已經(jīng)成為世界上最流行的免費實時內(nèi)核,最新版為μC/OS-III.
基于上述原因,μC/OS-II在STM32處理器上的移植早已成為研究的熱點,移植的文檔多是研究μC/OS-II與STM32處理器關系的傳統(tǒng)方法,傳統(tǒng)方法分析了μC/OS-II中與設備相關的文件的修改方法,卻沒有很好的利用固件庫,因此本文提出了基于固件庫開發(fā)的移植新方法。本文的移植主要是針對有固件庫開發(fā)經(jīng)驗的學生和技術(shù)人員,并且選用了STM32F103RB處理器和2.86版的μC/OS-II.
所謂移植,就是使一個實時內(nèi)核能在某個微處理器或微控制器上運行。為了解決好μC/OS-II與固件庫及處理器之間的關系,先介紹相關的知識。
該架構(gòu)主要分為以下4層:用戶應用層、操作系統(tǒng)及中間件接口層、CMSIS層、硬件寄存器層。CMSIS分為3個基本功能層:核內(nèi)外設訪問層、中間件訪問層和設備訪問層。CMSIS層起著承上啟下的作用:一方面該層對硬件寄存器層進行統(tǒng)一實現(xiàn),屏蔽了不同廠商對Cortex-M系列微處理器核內(nèi)外設寄存器的不同定義;另一方面又向上層的操作系統(tǒng)及中間件接口層和應用層提供接口,簡化了應用程序開發(fā)難度,使開發(fā)人員能夠在完全透明的情況下進行應用程序開發(fā)。
ST的固件庫,就是一個典型的無操作系統(tǒng)的基于CMSIS標準開發(fā)的軟件架構(gòu)。如圖1(b)所示startup為基于匯編的啟動代碼文件,USER為用戶應用層,BSP是與開發(fā)板相關的硬件驅(qū)動文件(如led.c),F(xiàn)Wlib為標準的核內(nèi)外設驅(qū)動函數(shù),CMSIS為如上所述的CMSIS層。uC-CPU與uC-LIB是廠商自帶的移植文件完全可以被標準CMSIS及函數(shù)取代,Scatter離散加載文件與uC-Probe(方便監(jiān)控與控制的實用工具)本次移植不需要。
μ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 工程(移植后)
針對上述介紹,本文移植的主要內(nèi)容有:
1)圖1(b)需要圖1(a)中的μC/OS-II源碼(uC-OS-II/Source和uC-OS-II/Port)及相關配置文件;
2)兩者功能重復的文件選擇圖1(b),如啟動文件和Systick相關的時鐘節(jié)拍函數(shù);
3)該變量大的自己重新寫如includes.h和一些配置文件;
4)需自己重新編寫的文件主函數(shù)main.c和任務函數(shù)等。
打開Micrium官方網(wǎng)站的下載中心(網(wǎng)址http://micrium.com/downloadcenter/),在Browse by Semiconductor Vendor下點擊STMicroelectronics進去,找到如圖2所示文件,下載第一個壓縮文件包,即Micrium_STM32xxx_uCOS-II工程,解壓并重新編譯后,沒有錯誤和警告。
圖2 相關程序包
拷貝uCOS-II文件夾至STM32_LED工程根目錄,此時uCOS-II目錄下有Doc,Ports和Source文件夾,在此文件夾下創(chuàng)建Port與Config文件夾并拷貝PortsARM-Cortex-M3GenericRealView下所有文件到Port文件夾下,然后刪除此文件夾??截恑nclude.h(需重寫)與os_cfg.h文件到Config文件夾下。Port文件中包含了OS_CPU.H,OS_CPU_C.C與OS_CPU_A.ASM文件,是移植中最重要的文件,但是底層函數(shù)已經(jīng)寫好,因為我們下載的就是針對這款處理器的,但是需要對其做一些針對性的修改,另外針對重要的函數(shù)運行過程在后面會重點介紹。
在uCOS-II根目錄創(chuàng)建BSP文件夾并創(chuàng)建BSP.C,BSP.H文件,用來放置系統(tǒng)初始化相關函數(shù),然后再拷貝led.c與led.h文件到此目錄。
編譯時會出現(xiàn)如下錯誤,.OutputSTM32_LED.axf: Error: L6218E: Undefined symbol OS_CPU_SysTickClkFreq (referred from os_cpu_c.o).這是由于沒找到OS_CPU_SysTickClkFreq()函數(shù)引起的,因為ST庫函數(shù)中有更直觀的SysTick函數(shù),所以uCOS-II中與SysTick相關的函數(shù)我們需要屏蔽掉。具體如下:
1)此時先把Port文件夾下文件的只讀屬性去除,然后屏蔽OS_CPU_C.C中OS_CPU_SysTickHandler()與OS_CPU_SysTickInit()函數(shù);
2)在BSP.C中添加SysTick_init()函數(shù)
void SysTick_init(void)
{ SysTick_Config(SystemFrequency/OS_TICKS_PER_SEC); }
//此處需要修改優(yōu)先級為14,目的是大于OS_CPU_PendSV_Handler的優(yōu)先級15。
3)在stm32f10x_it.c文件中的SysTick_Handler()中斷函數(shù)中添加OSIntEnter()、OSTimeTick()及OSIntExit()。用#include "includes.h" 替換掉所有的頭文件包含語句。
4)修改啟動代碼文件startup_stm32f10x_hd.s
在啟動文件中修改三處PendSV_Handler為OS_CPU_PendSVHandler,因為PendSV異常函數(shù)在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
禁用信號量、互斥信號量、郵箱、隊列、信號量集、定時器、內(nèi)存管理等,并關閉調(diào)試模式,禁用鉤子函數(shù)和多重事件控制,用#define OS_TICKS_PER_SEC 100;來設定時鐘節(jié)拍頻率,用#define OS_LOWEST_PRIO 40;來定義最低優(yōu)先級,可根據(jù)實際需要修改。
7)添加task.c與task.h
將此任務文件添加到USER文件夾下,給每個任務分配堆棧,并編寫3個簡單的LED閃爍任務。
8)編寫主函數(shù)main.c文件,并調(diào)試運行
#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;
}
了解此函數(shù)需要明白下面幾點:
1)用于在創(chuàng)建任務時初始化任務堆棧,也就是需要去模擬進入中斷時的現(xiàn)場,總共R0-R15,xPSR共16個寄存器。
2)堆棧屬性:滿遞減。
3)入棧順序:首先是xPSR,PC,LR,R12,R3,R2,R1,R0,是發(fā)生中斷時由硬件自動壓棧的,其次是R11,R10,R9,R8,R7,R6,R5,R4,是手動入棧的。
4)內(nèi)部入棧順序如圖3所示。
圖3 內(nèi)部入棧序列
CM3內(nèi)核在內(nèi)部打亂了入棧順序[1],其原因是:
a)PC與xPSR先入棧就可以更早地啟動服務例程指令的預取(因為此時需要修改PC),同時也做到了在早期就可以更新xPSR中IPSR位段的值;
b)先R0-R3,最后是R12,為的是更容易地使用SP基址來索引尋址(如LDM);
c) 先xPSR,PC,LR,R0-R3,R12后R4-R11為的是遵守C函數(shù)標準調(diào)用約定《AAPCS,Ref5》,這個約定使得中斷服務函數(shù)可以用C編寫,編譯器優(yōu)先使用先入棧的寄存器保存中間結(jié)果。
5)*(stk) = (INT32U)0x01000000L; /* xPSR */,xPSR的T位置1,否則一開始就會產(chǎn)生Fault。
6)*(--stk) = (INT32U)task;/* Entry Point */,PC指向任務端口。
7)*(--stk) = (INT32U)0xFFFFFFFEL;/*LR,last bit 0*/ 非法值,目的是不讓任務返回。
8)*(--stk) = (INT32U)p_arg; /* R0 */ 目的是傳遞任務函數(shù)的參數(shù)。
因為在OS_CPU_A.ASM中,四個關鍵匯編函數(shù)OSStartHighRdy(),OSCtxSw()與OSIntCtxSw()和OSTickISR()[2],其中前三個都需要出發(fā)PendSV異常,所以深入理解OS_CPU_PendSVHandler函數(shù)是理解底層運行過程的關鍵。
了解此函數(shù)需要理解以下幾點:
1)PendSV是用來進行上下文切換的。
2)對比由Systick,用PendSV做上下文切換可以解決在SysTick中啟動上下文切換所帶來的ISR被延遲得不到及時響應的問題的。原因是PendSV異常會自動延遲上下文的請求直到其它的ISR都完成了處理任務后才放行,因此PendSV的優(yōu)先級是被設置為最低的。
3)PendSV異常的執(zhí)行過程
PendSV,的異常處理函數(shù)具體工作是:
a)禁止中斷,獲取當前堆棧指針(PSP),如果為0(認為是第一次進行上下文切換)則不保存R4-R11,否則將堆棧指針減0x20,手動保存余下的R4-R11,再將SP保存到當前的任務控制塊(TCB)中。
b)壓棧R14(LR),調(diào)用OSTaskSwHook(),獲取最高優(yōu)先級任務堆棧指針,手動存入R4-R11,然后將堆棧指針加0x20,調(diào)整LR,使能中斷,然后進行異常返回。取最高優(yōu)先級任務堆棧指針,LDM PSP {R4-R11},然后指針-0x20,原理同上,將新指針存入PSP。此時內(nèi)容切換基本完成,再手動設置下LR值,使得返回時使用PSP指針。
4)注意上述過程中對堆棧指針加0x20和減0x20的原因在于Cortex-M3在發(fā)生異常中斷時會對8個寄存器進行自動壓棧和出棧。
雖然從Micrium官方網(wǎng)站上可以下載到移植好的程序包,但是此程序包與基于CMSIS標準軟件架構(gòu)的固件庫還有很大的區(qū)別。因為不兼容,所以讓長期習慣與固件庫開發(fā)的學生和技術(shù)人員直接用此程序包,還是很不適應的。本文正是基于這個原因為相關技術(shù)人員提供了新的移植方法,讓他們能夠在自己熟悉的固件庫環(huán)境下很好的使用μC/OS-II操作系統(tǒng)。
參考文獻:
[1]Joseph Yiu.ARM Cortex-M3 權(quán)威指南[M].北京:航空航天大學出版社,2009.
[2]Jean J Labrosse.嵌入式實時操作系統(tǒng)μCOS-Ⅱ(第2版)[M].北京:航空航天大學出版社,2003.