发布时间:2025-12-10 11:22:24 浏览次数:14
本文基于STM32F103C8T6,详细讲述华为LiteOS的移植过程。开发工具是MDK5。LiteOS官方已经适配过cortex M系列内核的单片机,因此移植过程非常简单。
LiteOS有两种移植方案:OS接管中断和非接管中断方式。接管中断的方式,是由LiteOS创建很管理中断,需要修改stm32启动文件,移植比较复杂。STM32的中断管理做的很好,用不着由LiteOS管理中断,所以我们下边的移植方案,都是非接管中断的方式的。中断的使用,跟在裸机工程时是一样的。
在target_config.h 中将 LOSCFG_PLATFORM_HWI 宏定义为 NO,即为不接管中断方式。该值默认为NO 。
移植的主要步骤如下:
说明:内核运行过程中会通过串口打印一些错误信息。如果日志功能开启、而又没有重定向printf函数的话,则会导致日志打印出错,程序异常卡死。之前我就是没有重定向printf函数,结果出了莫名其妙的问题,程序异常卡死在创建任务的地方。
下边我们通过新建一个裸机工程,一步步讲解如何进行移植。以下是详细过程。
我们这次使用的是一个STM32F103C8T6的最小系统板,板载有三个LED、一个串口。LED连接引脚为(PB5\PB6\PB7),低电平点亮;串口为USART1(PA9,PA10),采用DMA+空闲中断的方式接收数据。我们利用STM32CubeMX来生成裸机工程(STM32CubeMX的使用本文不详细描述),设置如下:
配置PB5\PB6\PB7为推挽输出方式;
配置PA9\PA10为USART1复用功能;
配置PA13为SWDIO功能,PA14为SWCLK功能(下载及调试)
使能串行调试功能
勾选生成对应外设驱动的‘.c/.h’文件,生成代码。
打开工程,加入LED开关状态的宏定义和串口空闲中断接收的代码,具体如下(当然,如果你不使用DMA+空闲中断的方式,也可以不进行下边2中的修改,但是一定要重定向printf函数):
1、在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)2、实现串口空闲中断接收
在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]; //定义串口接收bufferUSART_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到USART1int fputc(int ch, FILE *f){/* 发送一个字节数据到USART1 */UART_SendByte(USART1, (uint8_t) ch);return (ch);}///重定向c库函数scanf到USART1int 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 */}3、在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.clos_hw_tick.clos_hwi.carch\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 提供的静态内存算法,与动态内存算法不冲突。
如下图所示,依次点击1、2、3,打开头文件配置窗口:
头文件配置如下图所示:
需要添加的头文件路径为:
arch\arm\arm-m\includekernel\includekernel\base\includekernel\extended\includeOS_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的样例代码,可参考。利用串口空闲中断,可以很好的实现数据分帧)
LiteosPorting.rar
作者:llb90
如果让你手写个栈和队列,你还会写吗?
挑战10个最难的Java面试题(附答案)【上】
javascript基础修炼(13)——记一道有趣的JS脑洞练习题
【我的物联网成长记3】如何开发物联网应用?
【HC资料合集】2019华为全联接大会主题资料一站式汇总,免费下载!
对你没有看错!不到 10 行代码完成抖音热门视频的爬取!
Python面试的一些心得,与Python练习题分享