本文基於STM32F103C8T6,詳細講述華爲LiteOS的移植過程。開發工具是MDK5。LiteOS官方已經適配過cortex M系列內核的單片機,所以移植過程很是簡單。javascript
LiteOS有兩種移植方案:OS接管中斷和非接管中斷方式。接管中斷的方式,是由LiteOS建立很管理中斷,須要修改stm32啓動文件,移植比較複雜。STM32的中斷管理作的很好,用不着由LiteOS管理中斷,因此咱們下邊的移植方案,都是非接管中斷的方式的。中斷的使用,跟在裸機工程時是同樣的。java
在target_config.h 中將 LOSCFG_PLATFORM_HWI 宏定義爲 NO,即爲不接管中斷方式。該值默認爲NO 。git
移植的主要步驟以下:github
說明:內核運行過程當中會經過串口打印一些錯誤信息。若是日誌功能開啓、而又沒有重定向printf函數的話,則會致使日誌打印出錯,程序異常卡死。以前我就是沒有重定向printf函數,結果出了莫名其妙的問題,程序異常卡死在建立任務的地方。面試
下邊咱們經過新建一個裸機工程,一步步講解如何進行移植。如下是詳細過程。算法
咱們此次使用的是一個STM32F103C8T6的最小系統板,板載有三個LED、一個串口。LED鏈接引腳爲(PB5\PB6\PB7),低電平點亮;串口爲USART1(PA9,PA10),採用DMA+空閒中斷的方式接收數據。咱們利用STM32CubeMX來生成裸機工程(STM32CubeMX的使用本文不詳細描述),設置以下:緩存
配置PB5\PB6\PB7爲推輓輸出方式;架構
配置PA9\PA10爲USART1複用功能;app
配置PA13爲SWDIO功能,PA14爲SWCLK功能(下載及調試)less
使能串行調試功能
勾選生成對應外設驅動的‘.c/.h’文件,生成代碼。
打開工程,加入LED開關狀態的宏定義和串口空閒中斷接收的代碼,具體以下(固然,若是你不使用DMA+空閒中斷的方式,也能夠不進行下邊2中的修改,可是必定要重定向printf函數):
一、在main.h中加入LED宏定義代碼。
#define LED1_ON() HAL_GPIO_WritePin(GPIOB, LED1_Pin, GPIO_PIN_RESET) #define LED1_OFF() HAL_GPIO_WritePin(GPIOB, LED1_Pin, GPIO_PIN_SET) #define LED2_ON() HAL_GPIO_WritePin(GPIOB, LED2_Pin, GPIO_PIN_RESET) #define LED2_OFF() HAL_GPIO_WritePin(GPIOB, LED2_Pin, GPIO_PIN_SET) #define LED3_ON() HAL_GPIO_WritePin(GPIOB, LED3_Pin, GPIO_PIN_RESET) #define LED3_OFF() HAL_GPIO_WritePin(GPIOB, LED3_Pin, GPIO_PIN_SET)
二、實現串口空閒中斷接收
在usart.h中加入以下代碼:
#define UART1_BUFF_SIZE 256 //串口接收緩存區長度 typedef struct { uint8_t RxFlag; //空閒接收標記 uint16_t RxLen; //接收長度 uint8_t *RxBuff; //DMA接收緩存 }USART_RECEIVETYPE; extern USART_RECEIVETYPE Uart1Rx; void USART1_ReceiveIDLE(void); void UART_SendData(USART_TypeDef * Uart,uint8_t *buff,uint16_t size); 在usart.c中加入以下代碼 static uint8_t Uar1tRxBuff[UART1_BUFF_SIZE+1]; //定義串口接收buffer USART_RECEIVETYPE Uart1Rx = { .RxBuff = Uar1tRxBuff, }; void USART1_ReceiveIDLE(void) { uint32_t temp; if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) != RESET)) { __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE); temp = huart1.Instance->SR; temp = huart1.Instance->DR; HAL_UART_DMAStop(&huart1); temp = huart1.hdmarx->Instance->CNDTR; Uart1Rx.RxLen = UART1_BUFF_SIZE - temp; Uart1Rx.RxFlag=1; Uart1Rx.RxBuff[Uart1Rx.RxLen] = 0; HAL_UART_Receive_DMA(&huart1,Uart1Rx.RxBuff,UART1_BUFF_SIZE); } } void UART_SendByte(USART_TypeDef * Uart,uint8_t data) { Uart->DR = data; while((Uart->SR&UART_FLAG_TXE)==0); while((Uart->SR&UART_FLAG_TC)==0); } void UART_SendData(USART_TypeDef * Uart,uint8_t *buff,uint16_t size) { while(size--) { Uart->DR = *(buff++); while((Uart->SR&UART_FLAG_TXE)==0); } while((Uart->SR&UART_FLAG_TC)==0); } ///重定向c庫函數printf到USART1 int fputc(int ch, FILE *f) { /* 發送一個字節數據到USART1 */ UART_SendByte(USART1, (uint8_t) ch); return (ch); } ///重定向c庫函數scanf到USART1 int fgetc(FILE *f) { /* 等待串口1輸入數據 */ while((USART1->SR&UART_FLAG_RXNE)==0); return (int)USART1->DR&0xff; }
修改void MX_USART1_UART_Init(void),在最後加入如下代碼:
//add for DMA.Idle interrupt __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE); __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_TC); HAL_UART_Receive_DMA(&huart1, Uart1Rx.RxBuff, UART1_BUFF_SIZE); //開啓DMA接收 __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能空閒中斷
在stm32f1xx_it.c中聲明USART1_ReceiveIDLE,並在串口中斷中調用該函數:
void USART1_ReceiveIDLE(void); void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ USART1_ReceiveIDLE(); /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */ }
三、在main.c的main中添加代碼驗證裸機工程
while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ LED1_ON(); LED2_ON(); LED3_ON(); HAL_Delay(300); LED1_OFF(); LED2_OFF(); LED3_OFF(); HAL_Delay(300); printf("This is the uart test!\r\n"); if(Uart1Rx.RxFlag){ Uart1Rx.RxFlag = 0; UART_SendData(USART1,Uart1Rx.RxBuff,Uart1Rx.RxLen); } }
編譯下載代碼,程序正常運行,LED閃爍,同時打印字符串。
通過上述操做,咱們已經完成了裸機工程的準備工做。
LiteOS 開源代碼路徑:https://github.com/LiteOS/LiteOS
注:LiteOS 最新特性都存放在 develop 分支中,建議取該分支代碼進行學習。本文的代碼即爲 develop分支代碼。
點擊連接進入LiteOS代碼倉庫首頁,切換至develop分支,點擊右側「Clone or download」按鈕,選擇Download ZIP,下載代碼,以下圖所示:
LiteOS內核代碼目錄結構以下圖所示:
在工程目錄下新建LiteOS文件夾(文件夾名稱我的自定義),從上一步下載的LiteOS內核源碼中,將arch、kernel、targets\STM32F103VET6_NB_GCC\OS_CONFIG 拷貝至LiteOS文件夾內,以下圖所示:
arch 中是CPU架構相關的代碼;kernel是LiteOS內核代碼;OS_CONFIG中是配置內核功能的頭文件,可用於裁剪內核功能,咱們從官方提供的例程中拷貝過來(可從target文件夾給出的例子中任意拷貝一個)。
打開MDK工程,打開Mange Project Items。
添加arch分組
在Groups添加 LiteOS/Arch分組,添加如下文件:
arch\arm\arm-m\src 目錄下的所有文件: los_hw.c los_hw_tick.c los_hwi.c arch\arm\arm-m\cortex-m3\keil 目錄下的: los_dispatch_keil.S
以下圖所示:
注:點擊AddFiles時,MDK默認添加.c類型的文件。los_dispatch_keil.S是彙編文件,所以在添加時,須要將文件類型選擇爲All files。
添加kernel分組
在Groups添加 LiteOS/kernel分組,添加如下文件:
kernel\base\core 下面所有 .c 文件 kernel\base\ipc 下面所有 .c 文件 kernel\base\mem\bestfit_little 下面所有 .c 文件 kernel\base\mem\common 下面所有 .c 文件 kernel\base\mem\membox 下面所有 .c 文件 kernel\base\misc 下面所有 .c 文件 kernel\base\om 下面所有 .c 文件 kernel\extended\tickless 下面所有 .c 文件 (如不使用tickless,可不添加) kernel 下面的 los_init.c
說明:liteos提供三套動態內存算法,位於kernel/base/mem目錄下,分別爲bestfit、bestfit_little、tlsf,咱們本次移植的是bestfit_little.可根據需求移植其餘的算法。kernel\base\mem\membox目錄下是 LiteOS 提供的靜態內存算法,與動態內存算法不衝突。
以下圖所示,依次點擊一、二、3,打開頭文件配置窗口:
頭文件配置以下圖所示:
須要添加的頭文件路徑爲:
arch\arm\arm-m\include kernel\include kernel\base\include kernel\extended\include OS_CONFIG
打開stm32f1xx_it.c,找到 SysTick_Handler 和 PendSV_Handler
將這兩個中斷處理函數屏蔽掉。不然會出現以下編譯錯誤。
說明:liteos內核使用到了systick和pendsv這兩個中斷,並在內核代碼中有對應實現
OS_CONFIG/target_config.h 文件,該文件主要用於配置MCU驅動頭文件、RAM大小、內核功能等,須要根據本身的環境進行修改。
咱們主要須要修改如下兩處:
MCU驅動頭文件
根據使用的MCU,包含對應的頭文件。
SRAM大小
根據使用的MCU芯片SRAM大小進行修改。
這裏咱們使用的是STM32F103C8T6,其SRAM爲20KB。
不接管中斷
設置LOSCFG_PLATFORM_HWI 宏定義爲 NO(該值默認爲NO,通常無需修改,出於謹慎,移植過來仍是要檢查下)
target_config.h 文件還有不少其餘宏定義,主要是配置內核的功能。好比是否使用隊列、軟件定時器、是否使用時間片、信號量等。
通過以上的操做,LiteOS的移植就完成了。點擊編譯。
通過前面的操做,移植工做就完成了,這裏咱們能夠建立一個任務,使用LiteOS。在下邊的例子中,咱們建立了兩個任務,一個任務按照2S的週期點亮LED1,另一個任務按照400毫秒的週期點亮LED2。如下是代碼實現:
/* Includes ------------------------------------------------------------------*/ #include "main.h" #include "dma.h" #include "usart.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "los_sys.h" #include "los_task.ph" #include "los_memory.ph" /* USER CODE END Includes */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ static void Led1Task(void) { while(1) { LED1_ON(); LOS_TaskDelay(1000); LED1_OFF(); LOS_TaskDelay(1000); } } static void Led2Task(void) { while(1) { LED2_ON(); LOS_TaskDelay(200); LED2_OFF(); LOS_TaskDelay(200); } } UINT32 RX_Task_Handle; UINT32 TX_Task_Handle; static UINT32 AppTaskCreate(void) { UINT32 uwRet = LOS_OK; TSK_INIT_PARAM_S task_init_param; task_init_param.usTaskPrio = 4; task_init_param.pcName = "RxTask"; task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Led1Task; task_init_param.uwStackSize = 512; uwRet = LOS_TaskCreate(&RX_Task_Handle, &task_init_param); if (uwRet != LOS_OK) { printf("Led1Task create failed,%X\n",uwRet); return uwRet; } task_init_param.usTaskPrio = 4; task_init_param.pcName = "TxTask"; task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Led2Task; task_init_param.uwStackSize = 512; uwRet = LOS_TaskCreate(&TX_Task_Handle, &task_init_param); if (uwRet != LOS_OK) { printf("Led2Task create failed,%X\n",uwRet); return uwRet; } return LOS_OK; } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ UINT32 uwRet = LOS_OK; /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ LOS_KernelInit(); uwRet = AppTaskCreate(); if(uwRet != LOS_OK) { printf("LOS Creat task failed\r\n"); //return LOS_NOK; } LOS_Start(); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ //code below are used to verify the hardware. LED1_ON(); LED2_ON(); LED3_ON(); HAL_Delay(300); LED1_OFF(); LED2_OFF(); LED3_OFF(); HAL_Delay(300); printf("This is the uart test!\r\n"); } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the CPU, AHB and APB busses clocks */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB busses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */
附件爲移植好的工程代碼。
(代碼中有串口空閒中斷+DMA的樣例代碼,可參考。利用串口空閒中斷,能夠很好的實現數據分幀)
做者:llb90
javascript基礎修煉(13)——記一道有趣的JS腦洞練習題