文章目錄
- 概述
- HelloWorld 工程
- C/C++配置
- 編譯器主配置
- Makefile腳本
- 燒錄器主配置
- 運行結果
- 程序調用棧
- 任務管理實驗
- 實驗結果
- osal 系統適配層
- osal_task_create
- 其他實驗
- 實驗源碼
- 內存管理實驗
- 互斥鎖實驗
- 信號量實驗
- CMISIS接口實驗
- 還是得JlINK
- CMSIS 簡介
- LiteOS->CMSIS
- 任務間消息交互
- 執行結果
- 其他
概述
本實驗基于LiteOS Studio 工具進行物聯網終端的開發,使用LiteOS操作系統進行物聯網開發板的控制。實驗主要目的:
- 掌握LiteOS Studio的使用
- 掌握LiteOS操作系統任務的使用
- 掌握LiteOS操作系統內存管理的使用
- 掌握LiteOS操作系統互斥量和信號量使用
熟悉LCD屏幕的使用(在實驗4中)熟悉開發板的LED和按鍵使用(在實驗4中)- OSAL接口使用
- CMSIS接口使用(CMSIS任務和消息隊列接口使用等)
@History
在進行本實驗前,請先閱讀 #<IDE/IoT/搭建物聯網(LiteOS)集成開發環境,基于 LiteOS Studio + GCC + JLink>#,文中較詳細的介紹了LiteOS Studio的搭建和使用方法,文中我們也提及了LiteOS工程(LiteOS_Lab_HCIP),但沒有使用它,而是直接使用了BearPi-IoT_Std_LiteOS 源碼。為了貼合實驗指導書的步驟,我們在這里選用 LiteOS_Lab_HCIP源碼。
在寫本文前,我已經嘗試了不同形式的物聯網開發IDE,分別如下幾篇文章中:
#<IDE/IoT/搭建物聯網(LiteOS)集成開發環境,基于 LiteOS Studio + GCC + JLink>#
#<IDE/IoT/搭建物聯網(LiteOS)集成開發環境,基于 VSCode + IoT Link 插件>#
#<IDE/IoT/搭建物聯網(LiteOS)集成開發環境,基于 VSCode + GitBash + GCC工具>#
如果主要目標是為了完成HCIP-IOT實驗,我是建議選用指導書中指定的LiteOS Studio開發環境,不僅是因為貼合指導書中的步驟,減少不必要的麻煩。華為云IoTDA中提及的VSCode + IoT Link 模式,由于要使用低版本的VSCode,讓人很不爽,配置和調試操作都體驗不咋地。相比較而言,LiteOS Studio 雖然是基于VSCode的,但其與你已經安裝的VSCode不會產生環境變量或軟件安裝層次的沖突,算是優勢吧。
HelloWorld 工程
請參見 #<IDE/IoT/搭建物聯網(LiteOS)集成開發環境,基于 LiteOS Studio + GCC + JLink># 完成此部分實驗。
C/C++配置
c_cpp_properties.json 是 VSCode 中 C/C++ 擴展的核心配置文件,主要用于配置代碼分析和開發環境,確保 IntelliSense(智能代碼補全、錯誤檢查等)能夠準確理解項目結構和編譯器行,需要作出如下修改,
編譯器主配置
關于目標板卡配置、組件配置、編譯器,參見前文提到的 #<IDE/IoT/搭建物聯網(LiteOS)集成開發環境,基于 LiteOS Studio + GCC + JLink>#,不再贅述。編譯器配置下的Makefile腳本選擇H:\HuaWeiYun\LiteOS_Lab_HCIP\targets\STM32L431_BearPi\GCC下的Makefile文件。
Makefile腳本
這塊在HCIP-IoT實驗手冊中并沒有提及。Makefile(腳本)最終編譯哪個示例工程,是由LiteOS_Lab_HCIP\targets\STM32L431_BearPi.config文件決定的。
最簡單的改變該文件的方法是,從\Demos各示例程序文件夾下拷貝defaults.sdkconfig全部內容,當然,也可以借助menuconfig和genconfig生成,那就稍微復雜些了,可以參考本專欄下其他文章。
燒錄器主配置
燒錄器、調試器配置,參見前文提到的 #<IDE/IoT/搭建物聯網(LiteOS)集成開發環境,基于 VSCode + IoT Link 插件>#,采用ST-Link+OpenOCD模式。燒錄文件要編譯成功后才有哦。但還是建議使用JLink模式。
但還是建議使用JLink模式。這一節使用ST-Link+OpenOCD,體驗很差勁。
運行結果
小熊派開發板的這個串口,感覺不太頂用,我就拔插過兩三次,感覺就有接觸不良的情況偶發啦。
程序調用棧
LiteOS_Lab_HCIP\targets\STM32L431_BearPi\Src\main.c
int main(void)
{UINT32 uwRet = LOS_OK;HardWare_Init();uwRet = LOS_KernelInit();if (uwRet != LOS_OK) {return LOS_NOK;}extern void shell_uart_init(int baud);shell_uart_init(115200);link_test(); //第一步(void)LOS_Start();return 0;
}static int link_test() {int ret = -1;UINT32 uwRet = LOS_OK;UINT32 handle;TSK_INIT_PARAM_S task_init_param;memset (&task_init_param, 0, sizeof (TSK_INIT_PARAM_S));task_init_param.uwArg = (unsigned int)NULL;task_init_param.usTaskPrio = 2;task_init_param.pcName =(char *) "link_main";task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)link_main; //第二步task_init_param.uwStackSize = 0x1000;uwRet = LOS_TaskCreate(&handle, &task_init_param);if(LOS_OK == uwRet){ret = 0;}return ret;
}
LiteOS_Lab_HCIP\iot_link\link_main.c
int link_main(void *args) {...
#ifdef CONFIG_LINKDEMO_ENABLEextern int standard_app_demo_main(void);(void) standard_app_demo_main(); //第三步
#endif...
}
LiteOS_Lab_HCIP\targets\STM32L431_BearPi\Demos\hello_world_demo\hello_world_demo.c
//任務入口函數
static int app_hello_world_entry() {while (1) {printf("Hello World! This is BearPi!\r\n");osal_task_sleep(4*1000);}
}//第四步
int standard_app_demo_main() {osal_task_create("helloworld",app_hello_world_entry,NULL,0x400,NULL,2);return 0;
}
任務管理實驗
新添加任務入口函數并創建任務
void *task1 = NULL, *task2 = NULL; int num = 0;
//添加任務2
static int hcip_iot_task(void) {while (1) {printf("This is task2!\r\n");#if 1if (num == 3) {osal_task_kill(task1);}#endifosal_task_sleep(4*1000);}
}//宏控制的本項目下的任務創建接口/每個示例下該函數聲明相同
int standard_app_demo_main() {task1 = osal_task_create("helloworld",app_hello_world_entry,NULL,0x400,NULL,2);//新創建任務task2 = osal_task_create("task2",hcip_iot_task,NULL,0x400,NULL,2);return 0;
}
實驗結果
任務添加測試, 兩個任務同時運行,
如上,任務1和任務2同時執行,同時打印輸出。
任務刪除測試,
如上,任務1被關閉后,周任務2在執行。
幾個注意事項:
1、在編譯或重新編譯前前,要先關閉串口,否則可能使得IDE卡主,不能執行編譯過程,必須得重啟軟件。
2、在燒錄器配置燒錄.hex文件時,遇到過顯示燒錄成功(且復位過)但是不生效的情況,可以多嘗試幾次或配置燒錄.bin文件。
osal 系統適配層
在這里我們簡單看看osal適配層的實現方法,后期我們會單獨講解。
個人覺得osal與cmsis有些類似,在上述os目錄下,osal和linux/ucos_ii等系統目錄平級,cmsis目錄在liteos之下,如下圖,
OSAL定義統一的系統調用接口(如線程管理、通信),使應用無需關注底層內核(如LiteOS與Linux的差異)。例如,鴻蒙通過OSAL層兼容Linux與LiteOS內核。 CMSIS 為Cortex處理器提供標準外設訪問API(如寄存器映射)及RTOS接口(CMSIS-RTOS2),確保代碼可跨RTOS(如FreeRTOS、RTX5)復用。
osal_task_create
//osal 操作系統適配層函數
void* osal_task_create(const char *name,int (*task_entry)(void *args), void *args,int stack_size,void *stack,int prior) {void *ret = NULL;if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->task_create)) {ret = s_os_cb->ops->task_create(name, task_entry,args,stack_size,stack,prior);}return ret;
}
osal_task_create 函數調用堆棧分析,
其他實驗
互斥鎖、內存管理、信號量實驗,統一參見如下代碼。
實驗源碼
#define SWITCH_TEST_HELLO 0
#define SWITCH_TEST_TASK 0
#define SWITCH_TEST_MUTEX 1
#define SWITCH_TEST_MEM 0
#define SWITCH_TEST_SEMP 0//任務句柄
void *task1 = NULL, *task2 = NULL; int num = 0;#if SWITCH_TEST_MUTEX
//需要保護的公共資源
uint32_t public_value = 0;
//互斥鎖
osal_mutex_t public_value_mutex;
#endif
//信號量實驗
osal_semp_t sync_semp = NULL;#if SWITCH_TEST_HELLO
static int app_hello_world_entry()
{while (1){printf("Hello World! This is BearPi!\r\n");osal_task_sleep(4*1000);}
}
#endif#if SWITCH_TEST_TASK
static int hcip_iot_task(void) {while (1) {printf("This is task2, num:%d \r\n", ++num);#if 1if (num == 3) {osal_task_kill(task1);}#endifosal_task_sleep(4*1000);}
}
#endif#if SWITCH_TEST_MUTEX
//互斥鎖/任務1入口
static int mutex_task1_entry() {while (1) {if (true == osal_mutex_lock(public_value_mutex)) {printf("task1: lock a mutex.\r\n");public_value += 10;printf("task1: public_value = %ld.\r\n", public_value);printf("task1: sleep...\r\n");osal_task_sleep(10); //msprintf("task1: continue...\r\n");printf("task1: unlock a mutex.\r\n");osal_mutex_unlock(public_value_mutex);if (public_value > 60) break; } //if} //whilereturn 0;
}//互斥鎖/任務2入口
static int mutex_task2_entry() {while (1) {if (true == osal_mutex_lock(public_value_mutex)) {printf("task2: lock a mutex.\r\n");public_value += 5;printf("task2: public_value = %ld.\r\n", public_value);printf("task2: unlock a mutex.\r\n");osal_mutex_unlock(public_value_mutex);if (public_value > 50) break; #if 0 //task2 not sleeposal_task_sleep(10); //ms#endif} //if} //whilereturn 0;
}
#endif#if SWITCH_TEST_MEM
//內存管理實驗/任務1
static int mem_access_task_entry() {uint32_t i = 0; //for looksize_t mem_size = 0; //uint8_t *mem_ptr = NULL; //內存塊指針//loopwhile (1) {//每次循環申請的塊大小擴一倍mem_size = 1 << i++;//執行申請操作mem_ptr = osal_malloc(mem_size);//successif (NULL != mem_ptr) {printf("access %d bytes memory success!\r\n", mem_size);osal_free(mem_ptr);mem_ptr = NULL;printf("free memory success!\r\n");}else {printf("access %d bytes memory failed!\r\n", mem_size);return 0;}}return 0;
}
#endif//信號量實驗
#if SWITCH_TEST_SEMP
//信號量實驗/任務1
static int semp_task1_entry() {printf("task1: post a semp.\r\n");osal_semp_post(sync_semp);printf("task1: end.\r\n");
}//信號量實驗/任務1
static int semp_task2_entry() {printf("task2: watting for a semp...\r\n");osal_semp_pend(sync_semp);printf("task2: access a semp.\r\n");
}
#endif//示例初始化函數
int standard_app_demo_main() {
//原HelloWorld
#if SWITCH_TEST_HELLOosal_task_create("helloworld",app_hello_world_entry,NULL,0x400,NULL,2);
#endif//互斥鎖實驗
#if SWITCH_TEST_MUTEX//創建互斥鎖osal_mutex_create(&public_value_mutex);//創建任務 //const char *name,int (*task_entry)(void *args), void *args,int stack_size,void *stack,int priortask1 = osal_task_create("mutex_task1", mutex_task1_entry, NULL, 0x400, NULL, 12);//創建任務 //const char *name,int (*task_entry)(void *args), void *args,int stack_size,void *stack,int priortask2 = osal_task_create("mutex_task2", mutex_task2_entry, NULL, 0x400, NULL, 11);
#endif//內存實驗
#if SWITCH_TEST_MEM//創建任務task2 = osal_task_create("mem_task", mem_access_task_entry, NULL, 0x400, NULL, 11);
#endif//信號量實驗
#if SWITCH_TEST_SEMP//創建信號量 /數量1初始值0osal_semp_create(&sync_semp, 1, 0);//任務1優先級低,負責釋放信號量task1 = osal_task_create("semp_task1", semp_task1_entry, NULL, 0x400, NULL, 12);//任務2優先級高,先進入等待/申請信號量的狀態task2 = osal_task_create("semp_task2", semp_task2_entry, NULL, 0x400, NULL, 11);
#endifreturn 0;
}
內存管理實驗
互斥鎖實驗
如上實驗結果,如果高優先級的任務不睡眠,則低優先級任務必要要等到高優先級任務退出后才有機會執行。
信號量實驗
這里,semp_task2_entry優先級高,會先執行,進入pend等待信號量狀態,函數會使得當前任務進入阻塞狀態,從而讓出 CPU 資源給其他任務使用,如這里優先級稍低的semp_task1_entry任務。待task1釋放信號量后,task2被喚醒繼續執行。
CMISIS接口實驗
與FreeRTOS一樣,LiteOS也支持CMSIS,這簡直是福利。在以下目錄 LiteOS_Lab_HCIP\iot_link\os\liteos\cmsis
還是得JlINK
前面幾個小實驗,純粹是為了體驗OpenOCD模式,但真的很難用啊。在進行CMSIS實驗時,哈哈也不知道是咋鼓搗的,基于STLink+OpenOCD的調試環境,它之間罷工了。在配置文件、OpenOCD版本等方向嘗試修復無果。
于是乎,我又乖乖的將板載的STLink刷成了JLink,該過程參考 #<IDE/IoT/搭建物聯網(LiteOS)集成開發環境,基于 LiteOS Studio + GCC + JLink># 文章。這個二次燒錄Jlink固件的過程,也很崎嶇…
1、在 #<IDE/IoT/搭建物聯網(LiteOS)集成開發環境,基于 VSCode + IoT Link 插件>#文中,提到了使用 STLinkReflash 將JLink 刷回 STLink 也不順利。
2、在第一步中,通過STM32 ST-LINK Utility升級了調試器固件。
3、在上述兩步基礎上,再嘗試使用 STLinkReflash 將STLink刷成JLink是失敗的。后來,我重新安裝了 STlink驅動、更換了usb接口、重啟過電腦,等一系列組合拳下來,竟然成功啦。哈哈,不想再嘗試了。就按照Studio官方建議,這么用吧。
4、J-Link 固件內置 J-Link GDB Server,可直接與調試工具(如 LiteOS Studio 的 GDB 客戶端)通信,無需中間層協議轉換。這種直接集成減少了調試鏈路的復雜性,提高運行效率。刷寫后的 ST-Link(J-Link OB)可實現高達 1.8MHz 的下載速率,顯著快于 OpenOCD + ST-Link 的組合。ST-Link沒有內置GDB服務,因此要借助外部的openocd.exe做GDB服務器。
CMSIS 簡介
隨著 32 位處理器在嵌入式市場需求量逐漸增多,各家芯片公司推出新型芯片,伴隨而
來的是開發工具、軟件兼容以及代碼移植等問題。在這種情況下,各個硬件平臺的供應商都
尋求易于使用且高效的解決方案,其中,ARM 與 Atmel、IAR、KEIL、SEGGER 和 ST 等諸
多芯片和軟件工具廠商合作,發布了一套 CMSIS 標準。
CMSIS(Cortex Microcontroller Software Interface Standard),即 ARM Cortex 微控制器軟
件接口標準。CMSIS 標準提供了內核和外圍設備、實時操作系統和中間組件之間的通用 API
接口,從而簡化了軟件的重復使用,縮短了微控制器開發人員的學習時間,并縮短了新設備
的上市時間。下圖是 ARM 公司的 CMSIS 標準結構框圖:
其中,CMSIS-CORE 層定義了 Cortex-M 以及 Cortex-A 處理器(Cortex-A5/A7/A9)內核
和外圍設備的標準化 API。CMSIS-Pack 層包含了 CMSIS-Driver 驅動框架、CMSIS-DSP 相關
庫、CMSIS-RTOS 操作系統 API、中間件 API 和 Peripheral HAL 層 API 等。根據 CMSIS 的標準,ARM 公司整合并提供了 CMSIS 軟件包模板。基于 ARM 提供的 CMSIS 軟件包模板,ST 官方結合自己芯片的差異進行了修改,并將其整合到了 STM32Cube 固件包中的 CMSIS 文件夾里。
LiteOS->CMSIS
除了ST的HAL支持外,LiteOS也要提供支持,以osThreadNew為例,
//cmsis_liteos2.c /定義在cmsis_os2.h中
osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr) {...uwRet = LOS_TaskCreate(&uwTid, &stTskInitParam);...
}
上述 LOS_TaskCreate 實現在 LiteOS_Lab_HCIP\iot_link\os\liteos\base\core 內核中。在osal層的任務創建函數 osal_task_create,其最后也要調用上述 LOS_TaskCreate 內核實現。 該函數的實現,我們不再深入。
任務間消息交互
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <osal.h>
#include <cmsis_os.h> //CMSIS_OS_VER==2#define SWITCH_TEST_HELLO 0
#define SWITCH_TEST_TASK 0
#define SWITCH_TEST_MUTEX 0
#define SWITCH_TEST_MEM 0
#define SWITCH_TEST_SEMP 0
#define SWITCH_TEST_CMSIS 1//cmsis接口
#if SWITCH_TEST_CMSIS
//消息隊列句柄 /void*
osMessageQueueId_t cmsis_queue;
//消息隊列消息
typedef struct cmsis_msg {int a;int b;
} TMsg;///typedef void (*osThreadFunc_t) (void *argument);
//任務1 /發送消息
static void cmsis_task1_entry(void *argument) {TMsg tMsg = {0, 0};while (1) {//tMsg.a += 1;tMsg.b += 2;//(osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout)osMessageQueuePut(cmsis_queue, &tMsg, 0, 10);//打印發送的消息printf("Send Msg a:%d b:%d\r\n", tMsg.a, tMsg.b);//睡眠osal_task_sleep(1*1000);//任務退出if (tMsg.a > 100) break;}
}///typedef void (*osThreadFunc_t) (void *argument);
//任務2 /接收消息
static void cmsis_task2_entry(void *argument) {TMsg tMsg; uint8_t msg_prio = 0;while (1) {//osStatus_t osMessageQueueGet (osMessageQueueId_t mq_id, void *msg_ptr, uint8_t *msg_prio, uint32_t timeout)osMessageQueueGet(cmsis_queue, (void*)&tMsg, &msg_prio, osWaitForever);//打印收到的消息printf("Recv Msg a:%d b:%d\r\n", tMsg.a, tMsg.b);}
}
#endifint standard_app_demo_main() {
//cmsis接口
#if SWITCH_TEST_CMSIS//創建消息隊列/osMessageQueueId_t osMessageQueueNew (uint32_t msg_count, uint32_t msg_size, const osMessageQueueAttr_t *attr);cmsis_queue = osMessageQueueNew (5, sizeof(TMsg), NULL);const osThreadAttr_t thread_attr1 = {.name = "MyThread1", // 線程名稱(調試用).stack_size = 1024, // 棧大小(字節).priority = osPriorityAboveNormal5 };const osThreadAttr_t thread_attr2 = {.name = "MyThread2", // 線程名稱(調試用).stack_size = 1024, // 棧大小(字節).priority = osPriorityAboveNormal5};//任務1/發送消息 /osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr)osThreadId_t thread1 = osThreadNew (cmsis_task1_entry, NULL, &thread_attr1); //任務2接收消息 /osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr)osThreadId_t thread2 = osThreadNew (cmsis_task2_entry, NULL, &thread_attr2);//printf("Hello cmsis!\r\n");
#endifreturn 0;
}
執行結果
其他
怎么說呢?挺不順的,一定要有耐心。