總覽
本文基於STM32F103C8T6,詳細講述華為LiteOS的移植過程。開發工具是MDK5。LiteOS官方已經適配過cortex M系列內核的單片機,因此移植過程非常簡單。
LiteOS有兩種移植方案:OS接管中斷和非接管中斷方式。接管中斷的方式,是由LiteOS創建很管理中斷,需要修改stm32啟動文件,移植比較復雜。STM32的中斷管理做的很好,用不着由LiteOS管理中斷,所以我們下邊的移植方案,都是非接管中斷的方式的。中斷的使用,跟在裸機工程時是一樣的。
在target_config.h 中將 LOSCFG_PLATFORM_HWI 宏定義為 NO,即為不接管中斷方式。該值默認為NO 。
移植的主要步驟如下:
1、添加內核文件
2、配置頭文件
3、移除systick和pendsv中斷
4、修改target_config.h
5、重定向printf函數(一般在裸機工程中就會實現)
說明:內核運行過程中會通過串口打印一些錯誤信息。如果日志功能開啟、而又沒有重定向printf函數的話,則會導致日志打印出錯,程序異常卡死。之前我就是沒有重定向printf函數,結果出了莫名其妙的問題,程序異常卡死在創建任務的地方。
下邊我們通過新建一個裸機工程,一步步講解如何進行移植。以下是詳細過程。
一、創建裸機工程
我們這次使用的是一個STM32F103C8T6的最小系統板,板載有三個LED、一個串口。LED連接引腳為(PB5\PB6\PB7),低電平點亮;串口為USART1(PA9,PA10),采用DMA+空閑中斷的方式接收數據。我們利用STM32CubeMX來生成裸機工程(STM32CubeMX的使用本文不詳細描述),設置如下:
1、引腳配置
- 配置PB5\PB6\PB7為推挽輸出方式;
- 配置PA9\PA10為USART1復用功能;
- 配置PA13為SWDIO功能,PA14為SWCLK功能(下載及調試)
- 使能串行調試功能
2、時鍾配置
3、串口配置
4、生成代碼
勾選生成對應外設驅動的‘.c/.h’文件,生成代碼。
打開工程,加入LED開關狀態的宏定義和串口空閑中斷接收的代碼,具體如下(當然,如果你不使用DMA+空閑中斷的方式,也可以不進行下邊2中的修改,但是一定要重定向printf函數):
1、在main.h中加入LED宏定義代碼。
1 #define LED1_ON() HAL_GPIO_WritePin(GPIOB, LED1_Pin, GPIO_PIN_RESET) 2 #define LED1_OFF() HAL_GPIO_WritePin(GPIOB, LED1_Pin, GPIO_PIN_SET) 3 4 #define LED2_ON() HAL_GPIO_WritePin(GPIOB, LED2_Pin, GPIO_PIN_RESET) 5 #define LED2_OFF() HAL_GPIO_WritePin(GPIOB, LED2_Pin, GPIO_PIN_SET) 6 7 #define LED3_ON() HAL_GPIO_WritePin(GPIOB, LED3_Pin, GPIO_PIN_RESET) 8 #define LED3_OFF() HAL_GPIO_WritePin(GPIOB, LED3_Pin, GPIO_PIN_SET)
2、實現串口空閑中斷接收
在usart.h中加入如下代碼:
1 #define UART1_BUFF_SIZE 256 //串口接收緩存區長度 2 typedef struct 3 { 4 uint8_t RxFlag; //空閑接收標記 5 uint16_t RxLen; //接收長度 6 uint8_t *RxBuff; //DMA接收緩存 7 }USART_RECEIVETYPE; 8 extern USART_RECEIVETYPE Uart1Rx; 9 void USART1_ReceiveIDLE(void); 10 void UART_SendData(USART_TypeDef * Uart,uint8_t *buff,uint16_t size); 11 在usart.c中加入如下代碼 12 static uint8_t Uar1tRxBuff[UART1_BUFF_SIZE+1]; //定義串口接收buffer 13 USART_RECEIVETYPE Uart1Rx = { 14 .RxBuff = Uar1tRxBuff, 15 }; 16 17 void USART1_ReceiveIDLE(void) 18 { 19 uint32_t temp; 20 if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET)) 21 { 22 __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE); 23 temp = huart1.Instance->SR; 24 temp = huart1.Instance->DR; 25 HAL_UART_DMAStop(&huart1); 26 temp = huart1.hdmarx->Instance->CNDTR; 27 Uart1Rx.RxLen = UART1_BUFF_SIZE - temp; 28 Uart1Rx.RxFlag=1; 29 Uart1Rx.RxBuff[Uart1Rx.RxLen] = 0; 30 HAL_UART_Receive_DMA(&huart1,Uart1Rx.RxBuff,UART1_BUFF_SIZE); 31 } 32 } 33 void UART_SendByte(USART_TypeDef * Uart,uint8_t data) 34 { 35 Uart->DR = data; 36 while((Uart->SR&UART_FLAG_TXE)==0); 37 while((Uart->SR&UART_FLAG_TC)==0); 38 } 39 void UART_SendData(USART_TypeDef * Uart,uint8_t *buff,uint16_t size) 40 { 41 while(size--) 42 { 43 Uart->DR = *(buff++); 44 while((Uart->SR&UART_FLAG_TXE)==0); 45 } 46 while((Uart->SR&UART_FLAG_TC)==0); 47 } 48 ///重定向c庫函數printf到USART1 49 int fputc(int ch, FILE *f) 50 { 51 /* 發送一個字節數據到USART1 */ 52 UART_SendByte(USART1, (uint8_t) ch); 53 return (ch); 54 } 55 56 ///重定向c庫函數scanf到USART1 57 int fgetc(FILE *f) 58 { 59 /* 等待串口1輸入數據 */ 60 while((USART1->SR&UART_FLAG_RXNE)==0); 61 return (int)USART1->DR&0xff; 62 }
修改void MX_USART1_UART_Init(void),在最后加入以下代碼:
1 //add for DMA.Idle interrupt 2 __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE); 3 __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_TC); 4 HAL_UART_Receive_DMA(&huart1, Uart1Rx.RxBuff, UART1_BUFF_SIZE); //開啟DMA接收 5 __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能空閑中斷
在stm32f1xx_it.c中聲明USART1_ReceiveIDLE,並在串口中斷中調用該函數:
1 void USART1_ReceiveIDLE(void); 2 3 void USART1_IRQHandler(void) 4 { 5 /* USER CODE BEGIN USART1_IRQn 0 */ 6 USART1_ReceiveIDLE(); 7 /* USER CODE END USART1_IRQn 0 */ 8 HAL_UART_IRQHandler(&huart1); 9 /* USER CODE BEGIN USART1_IRQn 1 */ 10 11 /* USER CODE END USART1_IRQn 1 */ 12 }
3、在main.c的main中添加代碼驗證裸機工程
1 while (1) 2 { 3 /* USER CODE END WHILE */ 4 5 /* USER CODE BEGIN 3 */ 6 LED1_ON(); 7 LED2_ON(); 8 LED3_ON(); 9 HAL_Delay(300); 10 LED1_OFF(); 11 LED2_OFF(); 12 LED3_OFF(); 13 HAL_Delay(300); 14 printf("This is the uart test!\r\n"); 15 if(Uart1Rx.RxFlag){ 16 Uart1Rx.RxFlag = 0; 17 UART_SendData(USART1,Uart1Rx.RxBuff,Uart1Rx.RxLen); 18 } 19 }
編譯下載代碼,程序正常運行,LED閃爍,同時打印字符串。
經過上述操作,我們已經完成了裸機工程的准備工作。
二、內核移植
1、下載LiteOS
LiteOS 開源代碼路徑:https://github.com/LiteOS/LiteOS
注:LiteOS 最新特性都存放在 develop 分支中,建議取該分支代碼進行學習。本文的代碼即為 develop分支代碼。
點擊鏈接進入LiteOS代碼倉庫首頁,切換至develop分支,點擊右側“Clone or download”按鈕,選擇Download ZIP,下載代碼,如下圖所示:
LiteOS內核代碼目錄結構如下圖所示:
2、拷貝內核代碼
在工程目錄下新建LiteOS文件夾(文件夾名稱個人自定義),從上一步下載的LiteOS內核源碼中,將arch、kernel、targets\STM32F103VET6_NB_GCC\OS_CONFIG 拷貝至LiteOS文件夾內,如下圖所示:
arch 中是CPU架構相關的代碼;kernel是LiteOS內核代碼;OS_CONFIG中是配置內核功能的頭文件,可用於裁剪內核功能,我們從官方提供的例程中拷貝過來(可從target文件夾給出的例子中任意拷貝一個)。
3、向MDK工程添加內核文件
打開MDK工程,打開Mange Project Items。
- 添加arch分組
在Groups添加 LiteOS/Arch分組,添加以下文件:
1 arch\arm\arm-m\src 目錄下的全部文件: 2 los_hw.c 3 los_hw_tick.c 4 los_hwi.c 5 arch\arm\arm-m\cortex-m3\keil 目錄下的: 6 los_dispatch_keil.S
如下圖所示:
注:點擊AddFiles時,MDK默認添加.c類型的文件。los_dispatch_keil.S是匯編文件,因此在添加時,需要將文件類型選擇為All files。
- 添加kernel分組
在Groups添加 LiteOS/kernel分組,添加以下文件:
1 kernel\base\core 下面全部 .c 文件 2 kernel\base\ipc 下面全部 .c 文件 3 kernel\base\mem\bestfit_little 下面全部 .c 文件 4 kernel\base\mem\common 下面全部 .c 文件 5 kernel\base\mem\membox 下面全部 .c 文件 6 kernel\base\misc 下面全部 .c 文件 7 kernel\base\om 下面全部 .c 文件 8 kernel\extended\tickless 下面全部 .c 文件 (如不使用tickless,可不添加) 9 kernel 下面的 los_init.c
說明:liteos提供三套動態內存算法,位於kernel/base/mem目錄下,分別為bestfit、bestfit_little、tlsf,我們本次移植的是bestfit_little.可根據需求移植其他的算法。kernel\base\mem\membox目錄下是 LiteOS 提供的靜態內存算法,與動態內存算法不沖突。
4、配置頭文件
如下圖所示,依次點擊1、2、3,打開頭文件配置窗口:
頭文件配置如下圖所示:
需要添加的頭文件路徑為:
1 arch\arm\arm-m\include 2 3 kernel\include 4 5 kernel\base\include 6 7 kernel\extended\include 8 9 OS_CONFIG
5、移除Systick和pendsv中斷
打開stm32f1xx_it.c,找到 SysTick_Handler 和 PendSV_Handler
將這兩個中斷處理函數屏蔽掉。否則會出現如下編譯錯誤。
說明:liteos內核使用到了systick和pendsv這兩個中斷,並在內核代碼中有對應實現
6、修改target_config.h
OS_CONFIG/target_config.h 文件,該文件主要用於配置MCU驅動頭文件、RAM大小、內核功能等,需要根據自己的環境進行修改。
我們主要需要修改以下兩處:
- MCU驅動頭文件
根據使用的MCU,包含對應的頭文件。
- SRAM大小
根據使用的MCU芯片SRAM大小進行修改。
這里我們使用的是STM32F103C8T6,其SRAM為20KB。
- 不接管中斷
設置LOSCFG_PLATFORM_HWI 宏定義為 NO(該值默認為NO,一般無需修改,出於謹慎,移植過來還是要檢查下)
target_config.h 文件還有很多其他宏定義,主要是配置內核的功能。比如是否使用隊列、軟件定時器、是否使用時間片、信號量等。
經過以上的操作,LiteOS的移植就完成了。點擊編譯。
7、創建一個任務
經過前面的操作,移植工作就完成了,這里我們可以創建一個任務,使用LiteOS。在下邊的例子中,我們創建了兩個任務,一個任務按照2S的周期點亮LED1,另外一個任務按照400毫秒的周期點亮LED2。以下是代碼實現:
1 /* Includes ------------------------------------------------------------------*/ 2 #include "main.h" 3 #include "dma.h" 4 #include "usart.h" 5 #include "gpio.h" 6 7 /* Private includes ----------------------------------------------------------*/ 8 /* USER CODE BEGIN Includes */ 9 #include "los_sys.h" 10 #include "los_task.ph" 11 #include "los_memory.ph" 12 /* USER CODE END Includes */ 13 /* Private function prototypes -----------------------------------------------*/ 14 void SystemClock_Config(void); 15 /* USER CODE BEGIN PFP */ 16 17 /* USER CODE END PFP */ 18 19 /* Private user code ---------------------------------------------------------*/ 20 /* USER CODE BEGIN 0 */ 21 static void Led1Task(void) 22 { 23 while(1) { 24 LED1_ON(); 25 LOS_TaskDelay(1000); 26 LED1_OFF(); 27 LOS_TaskDelay(1000); 28 } 29 } 30 static void Led2Task(void) 31 { 32 while(1) { 33 LED2_ON(); 34 LOS_TaskDelay(200); 35 LED2_OFF(); 36 LOS_TaskDelay(200); 37 } 38 } 39 UINT32 RX_Task_Handle; 40 UINT32 TX_Task_Handle; 41 static UINT32 AppTaskCreate(void) 42 { 43 UINT32 uwRet = LOS_OK; 44 TSK_INIT_PARAM_S task_init_param; 45 46 task_init_param.usTaskPrio = 4; 47 task_init_param.pcName = "RxTask"; 48 task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Led1Task; 49 task_init_param.uwStackSize = 512; 50 uwRet = LOS_TaskCreate(&RX_Task_Handle, &task_init_param); 51 if (uwRet != LOS_OK) 52 { 53 printf("Led1Task create failed,%X\n",uwRet); 54 return uwRet; 55 } 56 57 task_init_param.usTaskPrio = 4; 58 task_init_param.pcName = "TxTask"; 59 task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Led2Task; 60 task_init_param.uwStackSize = 512; 61 uwRet = LOS_TaskCreate(&TX_Task_Handle, &task_init_param); 62 if (uwRet != LOS_OK) 63 { 64 printf("Led2Task create failed,%X\n",uwRet); 65 return uwRet; 66 } 67 return LOS_OK; 68 } 69 /* USER CODE END 0 */ 70 71 /** 72 * @brief The application entry point. 73 * @retval int 74 */ 75 int main(void) 76 { 77 /* USER CODE BEGIN 1 */ 78 UINT32 uwRet = LOS_OK; 79 80 /* USER CODE END 1 */ 81 82 83 /* MCU Configuration--------------------------------------------------------*/ 84 85 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ 86 HAL_Init(); 87 88 /* USER CODE BEGIN Init */ 89 90 /* USER CODE END Init */ 91 92 /* Configure the system clock */ 93 SystemClock_Config(); 94 95 /* USER CODE BEGIN SysInit */ 96 97 /* USER CODE END SysInit */ 98 99 /* Initialize all configured peripherals */ 100 MX_GPIO_Init(); 101 MX_DMA_Init(); 102 MX_USART1_UART_Init(); 103 /* USER CODE BEGIN 2 */ 104 LOS_KernelInit(); 105 uwRet = AppTaskCreate(); 106 if(uwRet != LOS_OK) { 107 printf("LOS Creat task failed\r\n"); 108 //return LOS_NOK; 109 } 110 LOS_Start(); 111 /* USER CODE END 2 */ 112 113 /* Infinite loop */ 114 /* USER CODE BEGIN WHILE */ 115 while (1) 116 { 117 /* USER CODE END WHILE */ 118 119 /* USER CODE BEGIN 3 */ 120 //code below are used to verify the hardware. 121 LED1_ON(); 122 LED2_ON(); 123 LED3_ON(); 124 HAL_Delay(300); 125 LED1_OFF(); 126 LED2_OFF(); 127 LED3_OFF(); 128 HAL_Delay(300); 129 printf("This is the uart test!\r\n"); 130 } 131 /* USER CODE END 3 */ 132 } 133 134 /** 135 * @brief System Clock Configuration 136 * @retval None 137 */ 138 void SystemClock_Config(void) 139 { 140 RCC_OscInitTypeDef RCC_OscInitStruct = {0}; 141 RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; 142 143 /** Initializes the CPU, AHB and APB busses clocks 144 */ 145 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; 146 RCC_OscInitStruct.HSIState = RCC_HSI_ON; 147 RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; 148 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; 149 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2; 150 RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16; 151 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) 152 { 153 Error_Handler(); 154 } 155 /** Initializes the CPU, AHB and APB busses clocks 156 */ 157 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK 158 |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; 159 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; 160 RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; 161 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; 162 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; 163 164 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) 165 { 166 Error_Handler(); 167 } 168 } 169 170 /* USER CODE BEGIN 4 */ 171 172 /* USER CODE END 4 */ 173 174 /** 175 * @brief This function is executed in case of error occurrence. 176 * @retval None 177 */ 178 void Error_Handler(void) 179 { 180 /* USER CODE BEGIN Error_Handler_Debug */ 181 /* User can add his own implementation to report the HAL error return state */ 182 183 /* USER CODE END Error_Handler_Debug */ 184 } 185 186 #ifdef USE_FULL_ASSERT 187 /** 188 * @brief Reports the name of the source file and the source line number 189 * where the assert_param error has occurred. 190 * @param file: pointer to the source file name 191 * @param line: assert_param error line source number 192 * @retval None 193 */ 194 void assert_failed(uint8_t *file, uint32_t line) 195 { 196 /* USER CODE BEGIN 6 */ 197 /* User can add his own implementation to report the file name and line number, 198 tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ 199 /* USER CODE END 6 */ 200 } 201 #endif /* USE_FULL_ASSERT */
附件為移植好的工程代碼。
(代碼中有串口空閑中斷+DMA的樣例代碼,可參考。利用串口空閑中斷,可以很好的實現數據分幀)
作者:llb90
HDC.Cloud 華為開發者大會2020 即將於2020年2月11日-12日在深圳舉辦,是一線開發者學習實踐鯤鵬通用計算、昇騰AI計算、數據庫、區塊鏈、雲原生、5G等ICT開放能力的最佳舞台。