楊淇善,韓德強
(北京工業大學 信息學部,北京 100124)
物聯網(Internet of Things,IoT)是一個通過信息技術將各種物體與網絡相連,以幫助人們獲取所需物體相關信息的巨大網絡[1]。在這個網絡中,存在數量巨大的各類嵌入式設備。這些設備在計算能力、可用內存、實時性、通信、功耗等諸多方面都有限制或要求[2]。通用操作系統對硬件系統的性能要求較高,物聯網系統內的絕大多數嵌入式設備無法運行。傳統的嵌入式操作系統具有內核小、實時性高等優點,適用于性能較低的嵌入式設備。但是,僅有一個輕量級內核并無法解決物聯網應用中繁多的通信協議、硬件接口等造成的“碎片化”問題。因此,致力于解決“碎片化”問題、并具有終端安全保障和統一管理技術支撐的物聯網操作系統應運而生。
AliOS Things 是面向物聯網領域的輕量級物聯網操作系統,包含物聯網領域需用的組件和接口,具備極致性能、極簡開發、云端一體、安全防護等特點,可廣泛應用于智能家居、智慧城市、新出行等物聯網領域[3]。
TI 公司的MSP432 系列微控制器采用了ARM Cortex-M4F 內核,具有浮點運算單元(Float Point Unit,FPU)和存儲器保護單元。MSP432P401R 作為該系列的其中一款產品,內置256 KB 的Flash 和64 KB 的SRAM,最大工作頻率48 MHz,工作功耗和待機功耗分別僅為80 μA/MHz 和660 nA。此外,還集成了多個SPI、UART 和I2C 接口,以及密碼編譯加速器等模塊[4]。因此,高性能的特點可充分發揮AliOS Things 操作系統的優勢,超低功耗的特點使其可廣泛應用于物聯網感知層的設備上。兩者的結合在物聯網領域中具有良好的應用價值。
AliOS Things 物聯網操作系統除了提供輕量級的內核以外,還具有網絡通信與組網、安全加密、物聯網中間件等多種功能與服務組件,其層次架構和組件結構如圖1所示。

圖1 AliOS Things 操作系統架構圖
BSP(Board Support Package,板級支持包)是介于硬件平臺與操作系統之間的一層,封裝了訪問和使用硬件資源的功能函數。對于微控制器而言,可參考芯片數據手冊開發或使用廠商提供的函數庫實現。對于硬件平臺中的其他外設,則根據實際使用的通信接口和控制方式進行函數封裝。
HAL(Hardware Abstraction Layer,硬件適配層)屏蔽了硬件平臺,使其上層代碼不直接訪問硬件資源且在不改變功能的情況下可移植到其他平臺中,增強了代碼的穩定性與移植性。實現該層的函數可根據實際的功能需求調用BSP 中的函數。
內核層是AliOS Things 的核心部分,包含Rhino 實時操作系統內核以及異步事件框架Yloop、VFS(Virtual File System,虛擬文件系統)等功能組件。其中,Rhino 內核是整個操作系統的基礎部分,提供任務調度、內存分配、時間管理和中斷控制等一系列功能。移植工作的基本任務即實現操作系統內核在目標平臺上正常運行。
協議棧提供了物聯網設備連接網絡或組網的多種協議。對于面向IP 的設備,AliOS Things 集成了基于LwIP 的TCP/IP 協議棧以及提供標準Socket 功能的SAL(Socket Abstraction Layer,套接字抽象層)。對于非IP設備,集成了BLE(Bluetooth Low Energy,低功耗藍牙)協議和LoRa 協議。同時,協議棧中還包含可支持不同物聯網設備自組織異構網絡的uMesh 技術。
安全組件中集成了TLS(Transport Layer Security,安全傳輸層協議)和TEE(Trusted Execution Environment,可信執行環境),并支持ID2(Internet Device ID,聯網設備身份標識)認證與相關密鑰管理的功能。
該部分可稱為操作系統應用程序編程接口層,針對系統內核以及協議組件進行了封裝,將應用層與內核層進行了隔離。上層應用使用操作系統時,僅需調用AOS API 的接口函數即可,內核代碼對其完全透明。因此,只要不改變AOS API 的接口,修改內核代碼或更換協議組件均不影響應用層,提高了系統的可迭代性和維護性。
AliOS Things 具有諸多物聯網系統可用的中間組件,更好地為整個應用系統提供可靠、便捷的服務。主要包括FOTA(Firmware Over-The-Air,無線固件升級)、uData框架、AT 命令解析和常用的物聯網云端連接協議MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸)、CoAP(Constrained Application Protocol,受限型應用協議)等。
本文的AliOS Things 移植工作主要涉及系統架構中的以下六個部分:與微控制器架構相關的接口文件、板級支持包、硬件適配層、內核層、AOS API 和SysTick 定時器中斷處理函數。
AliOS Things 的源代碼中提供了多種主流微控制器體系架構相關的移植接口文件,以供開發者參考或使用。適用于MSP432P401R 的Cortex-M4F 內核和IAR for ARM開發環境的相關文件存儲于源代碼目錄platformarcharmarmv7miccarmm4 中,其包含4 個文件:k_types.h、port.h、port_c.c 和port_s.S。
k_types.h 對堆棧溢出控制的幻數值、強制內聯關鍵字進行了宏定義。另外,根據微控制器內核的位數,重新定義操作系統中所需用的堆棧、定時器、信號量、互斥量和上下文切換量等數據類型。重新定義上述數據類型時,應使用C99 標準的頭文件stdint.h 中所定義的數據類型,避免直接使用C 語言中char、int 等基本數據類型,以提高代碼的可移植性。以sem_count_t 和ctx_switch_t 為例:
typedef uint32_t sem_count_t;
typedef uint64_t ctx_switch_t;
port.h 對任務處理中有關寄存器操作的函數進行了宏定義或聲明,這些函數均在port_c.c 和port_s.S 兩個文件中實現。
port_c.c 使用C 語言實現了任務堆棧初始化函數cpu_task_stack_init()。該函數的編寫應遵循微控制器內核的相關特性。Cortex-M4F 內核的堆棧增長方式為高地址向低地址遞減。此外,當系統產生中斷時,寄存器R0-R3、R12、LR(鏈接寄存器)、PSR(程序狀態寄存器)、S0-S15 和FPSCR(浮點數狀態控制寄存器)均由硬件控制保存與恢復,而寄存器R4-R11 以及S16-S31 則需要由軟件控制保存與恢復。因此,函數cpu_task_stack_init()在完成棧指針變量stk 的初始化之后,可按照以下順序完成整個任務堆棧的構建。
(1)PSR 的值置為0x01000000,使能Thumb 指令集。
*(--stk)=(cpu_stack_t)0x01000000L;
(2)PC(程序計數器)的值置為任務函數的入口地址。
*(--stk)=(cpu_stack_t)entry;
(3)LR 的值置為krhino_task_deathbed()函數指針,該函數用于刪除當前意外跳出的任務。
*(--stk)=(cpu_stack_t)krhino_task_deathbed;
(4)依次入棧R12、R3-R1 的初值,其數值可根據寄存器編號確定,以便于檢查堆棧初始化是否正確。
*(--stk)=(cpu_stack_t)0x12121212L;
*(--stk)=(cpu_stack_t)0x03030303L;
*(--stk)=(cpu_stack_t)0x02020202L;
*(--stk)=(cpu_stack_t)0x01010101L;
(5)R0 的值置為任務函數傳入參數。
*(--stk)=(cpu_stack_t)arg;
(6)若使用FPU,則依次入棧寄存器S31-S16 的初值,其數值與上述通用寄存器類似。
*(--stk)=(cpu_stack_t)0x31uL;
*(--stk)=(cpu_stack_t)0x30uL;
……
*(--stk)=(cpu_stack_t)0x16uL;
(7)EXC_RETURN 的值置為0xFFFFFFFD。
*(--stk)=(cpu_stack_t)0xFFFFFFFDL;
(8)依次入棧R11-R4 的初值,其數值與上述通用寄存器一致。
*(--stk)=(cpu_stack_t)0x11111111L;
*(--stk)=(cpu_stack_t)0x10101010L;
……
*(--stk)=(cpu_stack_t)0x04040404L;
除任務堆棧初始化函數以外,port.h 中聲明的其他函數使用匯編語言在port_s.S 中實現。cpu_intrpt_save()和cpu_intrpt_restore()負責CPSR(程序狀態寄存器)狀態的保存和恢復,以及中斷的禁止和使能。這兩個函數在臨界區代碼前、后被調用。

另外,還應實現涉及上下文切換功能的部分函數。在任務切換時,需調用cpu_task_switch()。在中斷結束時,需調用cpu_intrpt_switch()。任務切換和中斷結束并非同一種情況,但兩者均可利用觸發PendSV 異常處理的方式完成相應的功能。因此,可采用相同的方法處理。
LDR R0,=SCB_ICSR
LDR R1,=ICSR_PENDSVSET
STR R1,[R0]
BX LR
cpu_first_task_start()則用于多任務系統的初始化階段,由優先級最高的就緒任務開始執行。首先,該函數將PendSV 優先級設為14 級(PRI_14),Systick 優先級設為15 級(PRI_15)。
LDR R0,=SHPR3_PRI_14
LDR R1,=PRI_LVL_PENDSV
STRB R1,[R0]
LDR R0,=SHPR3_PRI_15
LDR R1,=PRI_LVL_SYSTICK
STRB R1,[R0]
隨后,PSP(進程堆棧指針)賦值為0,以8 字節對齊MSP(主堆棧指針)。
MOVS R0,#0
MSR PSP,R0
MRS R0,MSP
LSRS R0,R0,#3
LSLS R0,R0,#3
MSR MSP,R0
最后,觸發PendSV 異常并開啟中斷。
LDR R0,=SCB_ICSR
LDR R1,=ICSR_PENDSVSET
STR R1,[R0]
CPSIE I
B .
PendSV 異常處理函數PendSV_Handler()也在port_s.S中實現。通過PSP 是否為0 可判斷觸發該異常處理的函數。
MRS R0,PSP
CBZ R0,_pendsv_handler_nosave
若由cpu_task_switch()和cpu_intrpt_switch()觸發,則保存通用寄存器的值,并將狀態寄存器賦值給對應的任務模塊。反之,若由cpu_first_task_start()觸發異常,則跳過上述過程,直接從代碼段_pendsv_handler_nosave 繼續執行。然后,比較任務的優先級并執行當前最高級就緒的任務。
對于移植工作,BSP 需要對時鐘、中斷、SysTick、GPIO等功能實現支持。MSP432P401R 片內32 KB ROM 內集成了TI 官方的MSP432 外設驅動程序庫,僅需將相關頭文件加入相關源文件中即可。庫函數均以ROM_ 作為前綴,以全局中斷使能函數為例:
ROM_Interrupt_enableMaster();
AliOS Things 的源代碼中提供了HAL 函數聲明的參考,相關文件存儲于源代碼目錄includehalsoc 中。移植工作根據實際情況,重新聲明并實現HAL 的函數。相關函數均以HAL_ 作為前綴,以SysTick 初始化和系統初始化兩個函數為例:


開發者不應更改Rhino 內核的源代碼,即以krhino_為前綴的函數,避免出現不可預知的錯誤。但是,需要根據實際硬件平臺和系統需求,利用諸多宏定義配置和剪裁操作系統內核功能。AliOS Things 源代碼中的頭文件k_default_config.h 給出了用于配置和剪裁內核功能的相關宏定義的默認值,該文件存儲于源代碼目錄kernel hinocoreinclude 中。該文件中對微控制器內核相關的特性進行了配置,默認采用小端模式,由高地址向低地址增長的堆棧方向等。
#define RHINO_CONFIG_LITTLE_ENDIAN 1
#define RHINO_CONFIG_CPU_STACK_DOWN 1
除此之外,該文件中的幾十個宏定義還配置了操作系統的時標頻率、信號量、消息隊列、互斥量等功能特性,以及動態內存分配、中斷堆棧檢查、鉤子函數等系統功能的默認值。
若修改這些宏定義的值,應在同目錄下創建一個名為k_config.h 的頭文件,而非直接修改k_default_config.h,以保持代碼的可移植性和正確性。由于k_default_config.h中采用了#ifndef…#define…預編譯,則k_config.h 中僅對要修改的默認值重新定義即可。
AliOS Things 源代碼中提供了AOS API 函數聲明的參考,該層函數以aos_ 作為前綴,相關頭文件存儲于源代碼目錄includeaos 中。開發者不必逐一實現所有頭文件中聲明的函數,根據系統情況實現所需函數即可。本文的移植工作主要利用Rhino 內核相應的函數完成本層函數的實現,以aos_init()和aos_task_new_ext()兩個函數為例:


SysTick 定時器為操作系統提供了“心跳”功能,即操作系統調度所需要的最小基本定時單元。根據不同的任務調度策略,當SysTick 中斷觸發時,操作系統會采取相應的措施進行多任務的切換。Rhino 內核中提供了平臺無關的入口函數krhino_tick_proc(),以供SysTick 中斷處理函數調用。在執行過程中,krhino_tick_proc()應受臨界段代碼保護。

移植工作基于MSP-EXP432P401R LaunchPad 開發平臺實現,其具有兩個LED 可供測試工作使用,提供直觀的視覺指示。同時,該平臺自帶XDS110 板載調試器,可通過USB 接口直接與上位機相連,實現程序的下載和調試功能。軟件開發環境采用了IAR Embedded Workbench for ARM 8.22,通過配置相關參數即可支持MSP432P401R 和XDS110 的開發和調試,如圖2 所示。

圖2 IAR 開發環境配置
測試程序中,通過查看多個任務的運行結果,以確定操作系統移植后能否正常運行。首先,調用HAL 和AOS API 的初始化函數,分別對硬件資源和操作系統內核進行初始化。然后,創建兩個測試任務LED1_Blink_Task和LED2_Blink_Task。其中,LED2_Blink_Task 的優先級高于LED1_Blink_Task。最后,調用aos_start()函數啟動操作系統內核,使系統進入運行狀態。主函數流程圖如圖3所示。

圖3 測試程序主函數流程圖
兩個測試任務分別使開發平臺的LED1和LED2以300ms和2000ms的周期翻轉。以LED1_Blink_Task 為例:
HAL_LED1_Toggle();
aos_msleep(300);
此外,測試程序中設置了全局數組gSysTime[],通過調用aos_now_ms()函數在該數組中記錄每次任務開始執行的時間點。在LED1_Blink_Task 中,gSysTime[]直接記錄aos_now_ms()函數返回的時間值。
gSysTime[gArrayCount++]=aos_now_ms();
而在LED2_Blink_Task 中,將aos_now_ms()函數的返回值加1 后存入gSysTime[]中。
gSysTime[gArrayCount++]=aos_now_ms()+1;
由于兩個任務的執行周期均為100 ms 的整數倍,則某個時間點上兩個任務同時就緒并相繼執行時,以此可區分數組中時間值的來源。
測試程序運行后,可看到開發平臺的LED1 和LED2各自按照一定的周期閃爍。同時,通過IAR 開發環境中的Watch 窗口,查看測試程序運行一段時間后數組gSys-Time[]的狀態,結果如圖4 所示。
由gSysTime[0]=1 和gSysTime[1]=0 可知,系統啟動后,即系統時間0 ms 時,兩個任務均處于就緒狀態并先執行了優先級更高的LED2_Blink_Task。此后的2 000 ms內,gSysTime[2]至gSysTime[7]的數值表明LED1_Blink_Task以300 ms 的周期正確執行。圖4 中的gSysTime[8]、gSys-Time[16]和gSysTime[23] 的數值表明LED2_Blink_Task 以2 000 ms 的周期正確執行。此外,在系統時間6 000 ms 時,兩個任務再次同時就緒,gSysTime[23]=6001 和gSysTime[24]=6000 表明操作系統以優先級執行了兩個任務。

圖4 數組gSysTime 的測試結果
綜上所述,完成AliOS Things 操作系統移植后,多個任務能夠正確地創建、運行和切換。
物聯網操作系統起源于TinyOS 和Contiki 項目,它們對IoT 系統的發展產生了深遠的影響[5]。本文給出了AliOS Things 操作系統移植至MSP432 微控制器的方法,以操作系統架構中由底層至頂層的順序詳細描述了各層中需要完成的工作。通過相關測試,AliOS Things 能夠正常、穩定地運行在MSP432 微控制器上,驗證了移植方法的正確性,對于其他平臺具有借鑒意義和參考價值。