【LiteOS】STM32F103-LiteOS移植教程(详细篇)

摘要:
总览本文基于STM32F103C8T6,详细讲述华为LiteOS的移植过程。LiteOS官方已经适配过cortexM系列内核的单片机,因此移植过程非常简单。接管中断的方式,是由LiteOS创建很管理中断,需要修改stm32启动文件,移植比较复杂。STM32的中断管理做的很好,用不着由LiteOS管理中断,所以我们下边的移植方案,都是非接管中断的方式的。

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第1张

总览

本文基于STM32F103C8T6,详细讲述华为LiteOS的移植过程。开发工具是MDK5。LiteOS官方已经适配过cortex M系列内核的单片机,因此移植过程非常简单。

LiteOS有两种移植方案:OS接管中断和非接管中断方式。接管中断的方式,是由LiteOS创建很管理中断,需要修改stm32启动文件,移植比较复杂。STM32的中断管理做的很好,用不着由LiteOS管理中断,所以我们下边的移植方案,都是非接管中断的方式的。中断的使用,跟在裸机工程时是一样的。

在target_config.h 中将 LOSCFG_PLATFORM_HWI 宏定义为 NO,即为不接管中断方式。该值默认为NO 。

移植的主要步骤如下:

1、添加内核文件

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第2张

2、配置头文件

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第3张

3、移除systick和pendsv中断

4、修改target_config.h

5、重定向printf函数(一般在裸机工程中就会实现)

说明:内核运行过程中会通过串口打印一些错误信息。如果日志功能开启、而又没有重定向printf函数的话,则会导致日志打印出错,程序异常卡死。之前我就是没有重定向printf函数,结果出了莫名其妙的问题,程序异常卡死在创建任务的地方。

下边我们通过新建一个裸机工程,一步步讲解如何进行移植。以下是详细过程。

一、创建裸机工程

我们这次使用的是一个STM32F103C8T6的最小系统板,板载有三个LED、一个串口。LED连接引脚为(PB5PB6PB7),低电平点亮;串口为USART1(PA9,PA10),采用DMA+空闲中断的方式接收数据。我们利用STM32CubeMX来生成裸机工程(STM32CubeMX的使用本文不详细描述),设置如下:

1、引脚配置

  • 配置PB5PB6PB7为推挽输出方式;

  • 配置PA9PA10为USART1复用功能;

  • 配置PA13为SWDIO功能,PA14为SWCLK功能(下载及调试)

  • 使能串行调试功能

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第4张

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第5张

2、时钟配置

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第6张

3、串口配置

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第7张

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+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 */
}

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!
");
      if(Uart1Rx.RxFlag){
          Uart1Rx.RxFlag = 0;
          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】STM32F103-LiteOS移植教程(详细篇)第8张

LiteOS内核代码目录结构如下图所示:

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第9张

2、拷贝内核代码

在工程目录下新建LiteOS文件夹(文件夹名称个人自定义),从上一步下载的LiteOS内核源码中,将arch、kernel、targetsSTM32F103VET6_NB_GCCOS_CONFIG 拷贝至LiteOS文件夹内,如下图所示:

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第10张

arch 中是CPU架构相关的代码;kernel是LiteOS内核代码;OS_CONFIG中是配置内核功能的头文件,可用于裁剪内核功能,我们从官方提供的例程中拷贝过来(可从target文件夹给出的例子中任意拷贝一个)。

3、向MDK工程添加内核文件

打开MDK工程,打开Mange Project Items。

  • 添加arch分组

在Groups添加 LiteOS/Arch分组,添加以下文件:

archarmarm-msrc 目录下的全部文件:
    los_hw.c
    los_hw_tick.c
    los_hwi.c
archarmarm-mcortex-m3keil 目录下的:
    los_dispatch_keil.S

如下图所示:

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第11张

注:点击AddFiles时,MDK默认添加.c类型的文件。los_dispatch_keil.S是汇编文件,因此在添加时,需要将文件类型选择为All files。

  • 添加kernel分组

在Groups添加 LiteOS/kernel分组,添加以下文件:

kernelasecore  下面全部 .c 文件
kernelaseipc   下面全部 .c 文件
kernelasememestfit_little 下面全部 .c 文件
kernelasememcommon 下面全部 .c 文件
kernelasememmembox 下面全部 .c 文件
kernelasemisc 下面全部 .c 文件
kernelaseom 下面全部 .c 文件
kernelextended	ickless 下面全部 .c 文件 (如不使用tickless,可不添加)
kernel 下面的 los_init.c

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第12张

说明:liteos提供三套动态内存算法,位于kernel/base/mem目录下,分别为bestfit、bestfit_little、tlsf,我们本次移植的是bestfit_little.可根据需求移植其他的算法。kernelasememmembox目录下是 LiteOS 提供的静态内存算法,与动态内存算法不冲突。

4、配置头文件

如下图所示,依次点击1、2、3,打开头文件配置窗口:

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第13张

头文件配置如下图所示:

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第14张

需要添加的头文件路径为:

archarmarm-minclude
 
kernelinclude
 
kernelaseinclude
 
kernelextendedinclude
 
OS_CONFIG

5、移除Systick和pendsv中断

打开stm32f1xx_it.c,找到 SysTick_Handler 和 PendSV_Handler

将这两个中断处理函数屏蔽掉。否则会出现如下编译错误。

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第15张

说明:liteos内核使用到了systick和pendsv这两个中断,并在内核代码中有对应实现

6、修改target_config.h

OS_CONFIG/target_config.h 文件,该文件主要用于配置MCU驱动头文件、RAM大小、内核功能等,需要根据自己的环境进行修改。

我们主要需要修改以下两处:

  • MCU驱动头文件

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第16张

根据使用的MCU,包含对应的头文件。

  • SRAM大小

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第17张

根据使用的MCU芯片SRAM大小进行修改。

这里我们使用的是STM32F103C8T6,其SRAM为20KB。

  • 不接管中断

设置LOSCFG_PLATFORM_HWI 宏定义为 NO(该值默认为NO,一般无需修改,出于谨慎,移植过来还是要检查下)

【LiteOS】STM32F103-LiteOS移植教程(详细篇)第18张

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(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
",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
",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
");
      //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!
");
  }
  /* 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
", 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练习题分享

免责声明:文章转载自《【LiteOS】STM32F103-LiteOS移植教程(详细篇)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Calender怎么用深入理解Faiss 原理&源码 (一) 编译下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

一文解读裸金属云 (转)

裸金属云,也叫裸机云,顾名思义就是同时拥有裸机的性能和云的弹性。在公有云出现之前,裸机云的前生是物理服务器托管。随着云概念的兴起,2010年和2012年 ,Softlayer和Rackspace相继推出“裸金属云”服务,支持自定义硬件基础,让托管服务器有了“云”的雏形。 裸金属云 拥有物理机同样的性能和安全 拥有云主机同样的灵活和弹性 取得了性能和灵活性很...

Cubieboard2裸机开发之(一)点亮板载LED

前言         CUbieboard2板载两个LED,一个绿色的,一个蓝色的,其中绿色LED通过三极管与PH20管脚连接,蓝色LED通过三极管与PH21管脚连接,这里只以蓝色LED为例,电路原理图如图1和图2所示。                                       图1 LED所使用的管脚                ...