許明宇,王宜懷,汪 恒
(蘇州大學(xué) 計算機科學(xué)與技術(shù)學(xué)院,江蘇 蘇州 215006)
RT-Thread是上海睿賽德公司于2006年開始發(fā)布的一款面向嵌入式人工智能與物聯(lián)網(wǎng)國產(chǎn)實時操作系統(tǒng)(Real Time Operating System,RTOS),具有自主知識產(chǎn)權(quán)?;趦?yōu)先級、可搶占的調(diào)度算法,該系統(tǒng)服務(wù)包括線程管理、實時調(diào)度管理、中斷管理和系統(tǒng)時鐘管理等,已在國內(nèi)逐步推廣應(yīng)用。
基于微控制器進(jìn)行RTOS應(yīng)用程序開發(fā)時,操作系統(tǒng)內(nèi)核源程序與用戶程序作為一個整體進(jìn)行編譯、鏈接生成機器碼,具有編譯時間較長、功能分割模糊、程序調(diào)試不便等缺點。若能實現(xiàn)RTOS的機器碼與用戶程序的物理隔離、有機連接并先后下載到FLASH中,則可以縮短應(yīng)用開發(fā)編譯時間,降低編程難度,方便用戶程序的調(diào)試。已有學(xué)者進(jìn)行了初步探索,文獻(xiàn)[4-5]在PC機上實現(xiàn)了XP系統(tǒng)在EPROM中固化;文獻(xiàn)[6]在VxWorks653操作系統(tǒng)下實現(xiàn)了一種分區(qū)啟動的方案。但對于嵌入式實時操作系統(tǒng)的駐留方法研究少見報道。RTOS的駐留可以實現(xiàn)RTOS與應(yīng)用程序的物理隔離,對降低RTOS下應(yīng)用開發(fā)門檻十分有益,可以有效降低嵌入式人工智能與物聯(lián)網(wǎng)終端的開發(fā)難度。
要實現(xiàn)某一種實時操作系統(tǒng)在一類微控制器芯片上的駐留,首先要將該實時操作系統(tǒng)移植到相應(yīng)的芯片上。關(guān)于實時操作系統(tǒng)的移植方法已有較多的文獻(xiàn)可供借鑒,文獻(xiàn)[7]基于Cortex-M3內(nèi)核的STM32F103移植了μC/OS-Ⅱ,并設(shè)計實現(xiàn)了蛇形機器人。文獻(xiàn)[8]將mbed OS移植到Cortex M0+內(nèi)核的MKL36Z64VLH4以及Cortex M4內(nèi)核的MSP432上。此外,文獻(xiàn)[9]實現(xiàn)了μC/OS-Ⅲ的移植,文獻(xiàn)[10]實現(xiàn)了MQX的移植。
ARM Cortex-M是安謀公司推出的面向微控制器領(lǐng)域的微處理器,被廣泛應(yīng)用于嵌入式系統(tǒng)領(lǐng)域。本文以ARM Cortex-M系列的STM32L431微控制器為藍(lán)本研究RT-Thread的駐留方法。
為了增加普適性,本文研究基于通用嵌入式計算機(General Embeded Computer,GEC)架構(gòu),把RT-Thread駐留于基本輸入/輸出系統(tǒng)(Basic Input/Output System,BIOS)中,實現(xiàn)BIOS與User程序的物理隔離。首先給出通用嵌入式計算機架構(gòu)簡介;然后給出在GEC架構(gòu)下將RT-Thread駐留在BIOS中的方法,主要包括存儲區(qū)域的分割、BIOS到User的銜接以及操作系統(tǒng)服務(wù)接口映射機制等;最后給出實踐樣例,檢驗該方法的可行性。
為了提高嵌入式應(yīng)用的編程顆粒度并提高可移植性,借鑒通用計算機的概念與做法,將基本輸入輸出系統(tǒng)(BIOS)與用戶程序(User)分離,構(gòu)建出通用嵌入式計算機(GEC),如圖1所示,為實現(xiàn)RT-Thread的駐留提供技術(shù)基礎(chǔ)。在硬件方面,通用嵌入式計算機由MCU硬件最小系統(tǒng)及其功能組件構(gòu)成;在軟件方面,其軟件由BIOS與User兩個部分構(gòu)成,類似于通用計算機。本文以此為基礎(chǔ)進(jìn)行RT-Thread駐留方法研究,將RTThread包含在BIOS內(nèi),以提高其可移植性和用戶程序的可復(fù)用性。
圖1 GEC架構(gòu)
BIOS是先于User固化在MCU中的一段程序,主要包括硬件抽象層、設(shè)備描述、RT-Thread內(nèi)核和API調(diào)用接口。
硬件抽象層將GPIO、UART、SPI等抽象成與硬件無關(guān)的驅(qū)動構(gòu)件供設(shè)備描述。
設(shè)備描述是在硬件驅(qū)動構(gòu)件的基礎(chǔ)上,對具體開發(fā)板上的小燈、傳感器等設(shè)備進(jìn)行功能描述,形成對應(yīng)的設(shè)備驅(qū)動構(gòu)件。
RT-Thread內(nèi)核即駐留的操作系統(tǒng),包含內(nèi)存管理、線程管理、同步與互斥等操作系統(tǒng)服務(wù)。
API調(diào)用接口將硬件驅(qū)動構(gòu)件、設(shè)備驅(qū)動構(gòu)件和操作系統(tǒng)服務(wù)的實現(xiàn)隱藏起來,將其以接口的形式供User程序調(diào)用。
User程序主要包括設(shè)備描述、軟件構(gòu)件和用戶程序。設(shè)備描述是對未在BIOS中進(jìn)行描述的設(shè)備進(jìn)行補充,通過API調(diào)用接口調(diào)用硬件驅(qū)動構(gòu)件來實現(xiàn)。軟件構(gòu)件是與硬件無關(guān)的C庫函數(shù),比如整數(shù)轉(zhuǎn)字符串。用戶程序即最終實現(xiàn)的嵌入式應(yīng)用,主要通過調(diào)用軟件構(gòu)件、設(shè)備驅(qū)動構(gòu)件以及通過API調(diào)用接口調(diào)用BIOS中的操作系統(tǒng)服務(wù)、設(shè)備驅(qū)動構(gòu)件和硬件驅(qū)動構(gòu)件來實現(xiàn)。
在GEC架構(gòu)下,芯片上電后先運行BIOS程序,然后由BIOS跳轉(zhuǎn)到User程序運行。BIOS程序從Reset_Handler開始運行,首先將全局變量拷貝到RAM中并初始化,然后初始化系統(tǒng)時鐘,接著進(jìn)入BIOS的main函數(shù)進(jìn)行外設(shè)模塊初始化,最后跳轉(zhuǎn)到User程序。User程序也從Reset_Handler開始運行,User的Reset_Handler先將API表的地址賦值給一個全局函數(shù)指針數(shù)組(初始化API向量表),然后進(jìn)入User的main函數(shù),最后在main函數(shù)中啟動RT-Thread。GEC架構(gòu)啟動流程如圖2所示。
圖2 GEC架構(gòu)啟動流程
要實現(xiàn)RT-Thread在GEC架構(gòu)下的駐留,需要對存儲空間進(jìn)行合理劃分,并設(shè)計符合軟件工程的可復(fù)用、可移植的接口函數(shù)。
ARM Cortex-M系列的微控制器使用的是哈佛結(jié)構(gòu),代碼和數(shù)據(jù)使用2個不同的存儲器,分別為FLASH和RAM。FLASH中存放代碼、常量和默認(rèn)的中斷向量表,RAM中存放全局變量、靜態(tài)變量和局部變量。要實現(xiàn)RT-Thread的駐留,需要合理劃分BIOS和User的存儲空間,使其代碼不重疊,變量不沖突。
2.1.1 FLASH空間劃分
在非GEC框架下,MCU的FLASH空間通常劃分為中斷向量表段(.vector)、代碼段(.text)和常量段(.rodata)三段。中斷向量表段存放中斷處理程序的入口地址,代碼段存放程序的機器碼,常量段存放程序中使用到的常量數(shù)據(jù)。GEC框架中將FLASH劃分為BIOS和User兩部分,User部分按上述的三段式劃分,BIOS部分則在三段式基礎(chǔ)上又切分出API向量表段,用來存放向User提供的接口函數(shù)地址,也就是七段式劃分。BIOS部分的FLASH按緊湊原則分配,即需要多少就分配多少空間,這樣可以盡可能多地把空間留給User,從而裝入更大空間需求的應(yīng)用層程序,實現(xiàn)更多的應(yīng)用層功能。FLASH空間的劃分如圖3所示,括號內(nèi)是以STM32L431RC芯片為例劃分地址的示例。
圖3 FLASH空間劃分
BIOS程序的FLASH空間應(yīng)遵循緊湊原則,避免分配過大或過小,過大會導(dǎo)致FLASH空間浪費甚至犧牲User程序功能來滿足程序的需要,過小會使得程序編譯不通過。
2.1.2 RAM空間劃分
RAM也需要劃分成BIOS和User兩部分,一般來說,它們都由已初始化的全局靜態(tài)變量數(shù)據(jù)段(data段)、未初始化的全局靜態(tài)變量數(shù)據(jù)段(bss段)、堆空間(heap段)和??臻g(stack段)組成。由于RT-Thread使用靜態(tài)全局變量定義了操作系統(tǒng)的線程堆空間,這一部分占用了BIOS的bss段,所以BIOS的RAM需要劃分足夠大的空間供線程的創(chuàng)建。因為BIOS占用了較大的RAM空間導(dǎo)致User可用空間減少,因此在資源緊湊的MCU上考慮使用共享棧分配方式對RAM進(jìn)行劃分,即BIOS和User使用相同的棧空間但其他部分不共享,如圖4所示,起始地址和結(jié)束地址后括號內(nèi)的數(shù)表示STM32L431RC芯片的RAM地址分配。
圖4 RAM共享棧分配方式
由于BIOS和User使用共享??臻g進(jìn)行RAM分配,所以User實際的RAM空間是與BIOS的堆和棧重疊的。BIOS中的功能應(yīng)盡可能少地使用new或malloc申請空間,避免數(shù)據(jù)沖入User的data段和bss段。此外,由于RT-Thread的線程堆定義在BIOS的bss段,所以需要給BIOS分配較多的RAM空間。
合理劃分存儲空間后,RT-Thread就可以駐留在BIOS中,但要在User中使用操作系統(tǒng)提供的內(nèi)存管理、線程管理等功能,還需要獲得對應(yīng)功能的函數(shù)地址。為此,在BIOS中設(shè)計了API向量表來登記接口函數(shù)的入口地址并固化在FLASH的指定地址,User通過讀取該地址獲取API向量表。生成API向量表主要包括接口函數(shù)定義、接口函數(shù)聲明和接口函數(shù)登記三部分。
1)接口函數(shù)定義。該方式與一般的函數(shù)沒有區(qū)別,主要包含函數(shù)名、返回值類型、參數(shù)和函數(shù)體等,并且除了RT-Thread相關(guān)的函數(shù)外還可以包含各類硬件驅(qū)動構(gòu)件函數(shù)和軟件構(gòu)件函數(shù)。
2)接口函數(shù)聲明。接口函數(shù)定義完成之后,需要在頭文件中聲明該函數(shù),通常頭文件名稱與其所在源文件同名。函數(shù)聲明需要給出改接口的函數(shù)名、返回值、參數(shù)和函數(shù)功能說明,從而提高代碼可讀性和可維護(hù)性。
3)接口函數(shù)登記。接口函數(shù)完成定義和聲明后,需要將其入口地址登記到API向量表中。登記的方法借鑒中斷向量表的定義,對所有的接口函數(shù)進(jìn)行編號,并將函數(shù)名按編號有序地存放在指定區(qū)域中,這個區(qū)域就是API向量表。API向量表通常使用數(shù)組來表示(如BIOS_API),接口函數(shù)的編號與數(shù)組的下標(biāo)一致,即0號函 數(shù) 對 應(yīng)BIOS_API[0],1號 對 應(yīng)BIOS_API[1],以 此類推。
在BIOS跳轉(zhuǎn)到User程序運行后,需要獲取API向量表首地址并對其記錄的接口函數(shù)地址進(jìn)行重映射后,才能調(diào)用BIOS提供的操作系統(tǒng)服務(wù)接口。
2.3.1 獲取API向量表首地址
API向量表首地址以宏定義的形式在User程序中給出,當(dāng)BIOS程序跳轉(zhuǎn)到User程序后,會在User的Reset_Handler中將宏定義的值賦值給一個全局函數(shù)指針數(shù)值變量,從而實現(xiàn)獲取BIOS中API向量表的地址。
2.3.2 接口函數(shù)重映射
獲取到API向量表后,還不能直接調(diào)用API,因為表中的每一條記錄都只是一個函數(shù)地址,并沒有與之對應(yīng)的返回值和參數(shù)類型信息,因此需要對這些地址記錄進(jìn)行重映射。重映射使用宏定義的方式將地址記錄映射成函數(shù)指針,并且函數(shù)指針的參數(shù)和返回值類型必須與登記的相同,從而保證程序調(diào)用時的正確性。表1給出了部分RT-Thread的系統(tǒng)服務(wù)接口函數(shù)的重映射,重映射形式如下:
表1 部分對外接口函數(shù)重定向表
格式:
#define函數(shù)名((接口函數(shù)指針表達(dá)形式)(API向量表數(shù)組[接口函數(shù)序號]))
示例:
實驗使用STM32L431RC芯片在STM32CubeIDE開發(fā)環(huán)境下進(jìn)行測試。測試程序分為BIOS和User兩個部分,BIOS中實現(xiàn)了RT-Thread的駐留并先固化到FLASH中,User調(diào)用駐留的操作系統(tǒng)服務(wù)接口和硬件驅(qū)動接口實現(xiàn)具體應(yīng)用。
STM32L431RC片內(nèi)FLASH大小為256 KB,共128個扇區(qū),每個扇區(qū)大小為2 KB。在駐留了RT-Thread后,BIOS共分配14個扇區(qū),其中2個扇區(qū)分配給API向量表,另外12個扇區(qū)分配給BIOS的vector段、text段和rodata段。片內(nèi)RAM大小為64 KB,前16 KB分配給BIOS的data段、bss段和heap段,后48 KB分配給User和共享??臻g。具體的空間分配如表2所示。
表2 STM32L431RC中BIOS和User空間劃分表
駐留測試實驗利用三色燈和串口輸出對RT-Thread的互斥量、事件機制、信號量和延時函數(shù)等系統(tǒng)服務(wù)接口進(jìn)行測試。實驗中設(shè)計紅、綠、藍(lán)三色小燈線程,分別控制三色燈的三種顏色,并且藍(lán)燈線程優(yōu)先級小于紅燈和綠燈,4個系統(tǒng)服務(wù)的測試設(shè)計如下:
1)互斥量
每個小燈線程使用串口向PC機發(fā)送點亮或熄滅狀態(tài),并且使用互斥量控制串口輸出,即必須有一個線程發(fā)送完消息后其他線程才可以占用串口。
2)事件機制
三個小燈按紅、綠、藍(lán)順序依次進(jìn)入就緒隊列,綠燈線程設(shè)置了綠燈事件,必須等待事件觸發(fā)才能執(zhí)行點亮小燈操作,綠燈事件由藍(lán)燈線程點亮藍(lán)燈后觸發(fā)。
3)信號量
每個小燈線程在執(zhí)行點亮操作前,需要獲取一個最大數(shù)量為2的三色燈信號量,該信號量使得只能有兩個小燈同時點亮,即只能是紅藍(lán)、紅綠或藍(lán)綠合成色小燈。
4)延時函數(shù)
每個線程點亮小燈后會等待5 s,然后熄滅小燈再嘗試獲取三色燈信號量點亮小燈。
按線程進(jìn)入點亮、等待和阻塞狀態(tài)的順序進(jìn)行分析,點亮狀態(tài)是獲得三色燈信號量點亮小燈,等待狀態(tài)是暫未獲得三色燈信號量進(jìn)入等待,阻塞是綠燈線程等待綠燈事件。從上電后每隔5 s的理論情況如圖5所示。
圖5 三色燈混合實驗運行效果
上電后第一個5 s情況與后面不同,5 s后是5~15 s情況的循環(huán),整體上是三色燈在紫色和黃色之間切換。
測試程序上電后,按GEC架構(gòu)啟動流程進(jìn)行,首先執(zhí)行BIOS程序,然后跳轉(zhuǎn)到User程序啟動操作系統(tǒng),接著啟動紅、綠、藍(lán)三個小燈線程。小燈線程啟動后各自執(zhí)行對應(yīng)顏色燈的點亮,并從串口向PC機發(fā)送消息。PC機使用串口調(diào)試助手接收到的消息如圖6所示。
圖6 測試實驗運行流程
在實驗中使用STM32CubeIDE編譯該測試程序,與非GEC架構(gòu)下不使用駐留技術(shù)的工程相比,耗時減少了約12,編譯時間節(jié)約效果顯著。
本文以Arm Cortex-M內(nèi)核的STM32L431芯片為例,給出了在通用嵌入式計算機GEC架構(gòu)下RT-Thread實時操作系統(tǒng)的駐留方法,可有效降低RTOS下應(yīng)用程序的開發(fā)難度,解決了在微控制器上使用RTOS編寫應(yīng)用的編譯時間較長、功能分割模糊、程序調(diào)試不便等問題,為嵌入式人工智能與物聯(lián)網(wǎng)終端的快速開發(fā)提供了技術(shù)基礎(chǔ)。駐留RT-Thread的關(guān)鍵要點主要有:FLASH空間的合理劃分,RAM的合理應(yīng)用,利用API接口向量表實現(xiàn)RTOS服務(wù)函數(shù)映射、BIOS及User的有機銜接等。實驗在蘇州大學(xué)自主研發(fā)的集成開發(fā)環(huán)境AHLGEC-IDE下進(jìn)行,得到了良好效果,測試用例可以在蘇州大學(xué)嵌入式學(xué)習(xí)社區(qū)官網(wǎng)獲得。本文方法可以為其他實時操作系統(tǒng)的駐留提供借鑒。后續(xù)將在此基礎(chǔ)上進(jìn)行RTOS下用戶程序統(tǒng)一性研究,以實現(xiàn)不同RTOS下應(yīng)用程序的可移植性。