【LiteOS】STM32F103-LiteOS移植教程(详细篇)【华为云技术分享】

总览
本文基于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宏定义代码。
#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+]; //定义串口接收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=;
Uart1Rx.RxBuff[Uart1Rx.RxLen] = ;
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)==);
while((Uart->SR&UART_FLAG_TC)==);
}
void UART_SendData(USART_TypeDef * Uart,uint8_t *buff,uint16_t size)
{
while(size--)
{
Uart->DR = *(buff++);
while((Uart->SR&UART_FLAG_TXE)==);
}
while((Uart->SR&UART_FLAG_TC)==);
}
///重定向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)==);
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 ()
{
/* USER CODE END WHILE */ /* USER CODE BEGIN 3 */
LED1_ON();
LED2_ON();
LED3_ON();
HAL_Delay();
LED1_OFF();
LED2_OFF();
LED3_OFF();
HAL_Delay();
printf("This is the uart test!\r\n");
if(Uart1Rx.RxFlag){
Uart1Rx.RxFlag = ;
UART_SendData(USART1,Uart1Rx.RxBuff,Uart1Rx.RxLen);
}
}
编译下载代码,程序正常运行,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分组,添加以下文件:
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 提供的静态内存算法,与动态内存算法不冲突。
4、配置头文件
如下图所示,依次点击1、2、3,打开头文件配置窗口:

头文件配置如下图所示:

需要添加的头文件路径为:
arch\arm\arm-m\include kernel\include kernel\base\include kernel\extended\include 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。以下是代码实现:
/* 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() {
LED1_ON();
LOS_TaskDelay();
LED1_OFF();
LOS_TaskDelay();
}
}
static void Led2Task(void)
{
while() {
LED2_ON();
LOS_TaskDelay();
LED2_OFF();
LOS_TaskDelay();
}
}
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 = ;
task_init_param.pcName = "RxTask";
task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Led1Task;
task_init_param.uwStackSize = ;
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 = ;
task_init_param.pcName = "TxTask";
task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Led2Task;
task_init_param.uwStackSize = ;
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 ()
{
/* USER CODE END WHILE */ /* USER CODE BEGIN 3 */
//code below are used to verify the hardware.
LED1_ON();
LED2_ON();
LED3_ON();
HAL_Delay();
LED1_OFF();
LED2_OFF();
LED3_OFF();
HAL_Delay();
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 = {};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {}; /** 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
HDC.Cloud 华为开发者大会2020 即将于2020年2月11日-12日在深圳举办,是一线开发者学习实践鲲鹏通用计算、昇腾AI计算、数据库、区块链、云原生、5G等ICT开放能力的最佳舞台。

【LiteOS】STM32F103-LiteOS移植教程(详细篇)【华为云技术分享】的更多相关文章
- Python正则表达式,看完这篇文章就够了...#华为云·寻找黑马程序员#【华为云技术分享】
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...
- webpack4.0各个击破(6)—— Loader篇【华为云技术分享】
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...
- Spring Boot 最流行的 16 条实践解读!【华为云技术分享】
置顶:华为云618大促火热进行中,全场1折起,免费抽主机,消费满额送P30 Pro,点此抢购. Spring Boot是最流行的用于开发微服务的Java框架.在本文中,将与大家分享自2016年以来笔者 ...
- 挑战10个最难的Java面试题(附答案)【上】【华为云技术分享】
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/devcloud/article/deta ...
- 华为云·寻找黑马程序员#【代码重构之路】如何“消除”if/else【华为云技术分享】
1. 背景 if/else是高级编程语言中最基础的功能,虽然 if/else 是必须的,但滥用 if/else,特别是各种大量的if/else嵌套,会对代码的可读性.可维护性造成很大伤害,对于阅读代码 ...
- AIOps产品与架构浅析【华为云技术分享】
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...
- Python开发GUI工具介绍,实战:将图片转化为素描画!【华为云技术分享】
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...
- 成为高手前必懂的TCP干货【华为云技术分享】
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...
- 【我的物联网成长记6】由浅入深了解NB-IoT【华为云技术分享】
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...
随机推荐
- NOIP模拟18 T2
不知道为什么很多人拒绝这题打搜索...其实搜索在充分剪枝后时间是非常优秀的,不管数据怎样基本都可跑出 首先一个显然结论:对于某种状态,他抓到的小精灵一定是一个连续的区间. 因此我们可以枚举这个区间的左 ...
- 什么是"双活"
什么是"双活" 主备数据中心之间一般有热备.冷备.双活三种备份方式. 热备 热备的情况下,只有主数据中心承担用户的业务,此时备数据中心对主数据中心进行实时的备份,当主数据中心挂掉以 ...
- AutoCad 二次开发 .net 之创建Table
我使用了COM对象来在cad2018中创建table表格,需要的ObjectArx开发包可以在官网上下载,并且需要使用.netframework4.6的库才行. 项目里除了引用常规的Cad开发dll, ...
- activemq 的延迟队列和幂等性检查
一. 延迟消息队列 1. 在提交支付之后,可以发送一个延迟检查的队列,来主动查询用户在支付宝上的支付状态 在mq的配置/config/activeMq.xml的broker实例上配置 schedule ...
- 大宇java面试系列(二):jvm组成部分
1. 说一下 JVM 的主要组成部分?及其作用? 类加载器(ClassLoader) 运行时数据区(Runtime Data Area) 执行引擎(Execution Engine) 本地库接口(Na ...
- python 豆瓣top250电影的爬取
我们先看一下豆瓣的robot.txt 然后我们查看top250的网页链接和源代码 通过对比不难发现网页间只是start数字发生了变化. 我们可以知道电影内容都存在ol标签下的 div class属性为 ...
- tensorflow中的学习率调整策略
通常为了模型能更好的收敛,随着训练的进行,希望能够减小学习率,以使得模型能够更好地收敛,找到loss最低的那个点. tensorflow中提供了多种学习率的调整方式.在https://www.tens ...
- 通过javascript 执行环境理解她
古往今来最难的学的武功(javascript)算其一. 欲练此功必先自宫,愿少侠习的此功,笑傲江湖. 你将了解 执行栈(Execution stack) 执行上下文(Execution Context ...
- java编程思想第四版第十三章字符串 习题
fas 第二题 package net.mindview.strings; import java.util.ArrayList; import java.util.List; /** * 无限循环 ...
- Python3.7.1学习(三)求两个list的差集、并集与交集
在python3.7.1对列表的处理中,会经常使用到Python求两个list的差集.交集与并集的方法. 下面就以实例形式对此加以分析. # 求两个list的差集.并集与交集# 一.两个list差集# ...