返回
Featured image of post STM32F103-鸿蒙 3.0 LiteOS-m 内核移植教程

STM32F103-鸿蒙 3.0 LiteOS-m 内核移植教程

0x00 前言

​ 本教程从零开始把 OpenHarmony 3.0 LiteOS-m 内核移植到 STM32F103 芯片上。开发工具是 MDK5。由于 LiteOS 官方已经适配过 Cortex-M 系列内核的单片机,因此移植过程非常简单。

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

注:代码中的 /* USER CODE BEGIN 0 */ 等类似的注释是 STM32CubeMX 生成的祼机工程里的代码,新增加的代码可以通过搜索这行注释找到修改的位置。

0x01工具

  • STM32CubeMX - ST 官方免费工具,可以快速构建祼机工程
  • MDK - 老牌 ARM 开发工具

0x02 构建祼机工程

​ 打开 STM32CubeMX ,在主窗口中间的 New Project 下点击 Start My Project from MCU 下面的按钮。

​ 在窗口左侧的 Part Number 处输入 STM32F103C8 (我手上的开发板是 STM32F103C8T6 芯片,如果你使用的芯片是其他型号,这里输入你的芯片型号,如 STM32F103CB)。在右下方的列表中选择 STM32F103C8 ,点击右上角的 Start Project 按钮。

0x02.01 打开串口调试

0x02.02 打开异步接收并启用全局中断

0x02.03 启用 DMA 功能

0x02.04 配置时钟

​ 点击 Clock Configuration 直接在 HCLK (MHz) 下面输入 64 并按回车键,软件会自动更新其他参数。

0x02.05 生成代码

​ 最后选上生成外设初始化文件并且修改工程信息,就可以点击右上角的 GENERATE CODE 按钮生成祼机工程的代码了。

​ 如果是第一次生成祼机工程,STM32CubeMX 会先下载一些组件,耐心等待就好了。生成成功后点击 Open Project 直接打开工程。

0x03 修改祼机工程

STM32CubeMX 重成的原始代码已经可以正常编译并运行,我们稍微修改一下以方便我们移植。

0x03.01 实现串口空闲中断接收

​ 打开 usart.h 文件,在 USER CODE BEGIN PrototypesUSER CODE END Prototypes 之间插入以下代码:

/* USER CODE BEGIN Prototypes */

#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);

/* USER CODE END Prototypes */

​ 打开 usart.c 文件,在 USER CODE BEGIN 1USER CODE END 1 之间插入以下代码:

/* USER CODE BEGIN 1 */

// 实现串口空闲中断接收开始
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);
  }
}
// 实现串口空闲中断接收结束

/* USER CODE END 1 */

0x03.02 重定向 printfscanf 函数

​ 打开 usart.c 文件,在 USER CODE END 1 上面插入以下代码:

// 实现串口空闲中断接收结束

// 重定向 printf 和 scanf 函数开始
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);
}

#include <stdio.h>

// 重定向 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;
}
// 重定向 printf 和 scanf 函数结束

/* USER CODE END 1 */

0x03.03 打开 DMA 接收

修改 usart.c 文件中的 MX_USART1_UART_Init 函数,在 USER CODE BEGIN USART1_Init 2USER CODE END USART1_Init 2 之间插入以下代码:

  /* USER CODE BEGIN USART1_Init 2 */

  __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);                    //使能空闲中断

  /* USER CODE END USART1_Init 2 */

​ 在 stm32f1xx_it.c 文件中声明 USART1_ReceiveIDLE 函数,并在串口中断函数 USART1_IRQHandler 中调用该函数:

/* USER CODE BEGIN 0 */

// 声明函数
void USART1_ReceiveIDLE(void);

/* USER CODE END 0 */
  /* USER CODE BEGIN USART1_IRQn 0 */

  // 调用函数
  USART1_ReceiveIDLE();

  /* USER CODE END USART1_IRQn 0 */

0x03.04 在 main.c 文件里的 main 函数中添加代码,验证裸机工程

    /* USER CODE BEGIN 3 */
      HAL_Delay(500);
      printf("This is output by printf!\r\n");
      if(Uart1Rx.RxFlag){
        Uart1Rx.RxFlag = 0;
        UART_SendData(USART1,Uart1Rx.RxBuff,Uart1Rx.RxLen);
      }

​ 编译下载程序,就可以看到 串口在不停的打印 This is output by printf! 了。

0x04 使用 MDK 的模拟器

​ 当然,我们也可以使用 MDK 的模拟器运行并调试我们的代码。点击 Project->Options... 菜单,并修改以下参数:

​ 点击 OK 关闭对话框,然后点击下面的按钮启动调试:

​ 如果点击了第三个按钮之后它还没有变灰色,就需要多按两次。代码运行后就可以在 UART #1 窗口看到串口输出的内容了。

​ 至此祼机工程已经创建成功并且验证完毕。

0x05 添加 LiteOS-m 内核代码

​ 在前面,我们已经创建好祼机工程并验证成功。现在我们可以往祼机工程里添加 LiteOS-m 的代码进行移植工作了。

0x05.01 下载 LiteOS-m 代码

​ 打开 https://gitee.com/openharmony/kernel_liteos_m 进入 LiteOS-m 代码托管网站。在左边的分支下拉框选择 OpenHarmony-3.0-LTS (我们进行移植的是 3.0 版本,目前在网上找到的教程都是基于 1.0 版本进行移植的,1.0 版本比较老旧,而且有还有部分代码没有开源),点击右边的 克隆/下载 ,再点击 下载ZIP

​ 把下载到的 zip 压缩包解压到工程目录的 Core\LiteOS 目录下:

05.02 下载 C++ 公共基础库

​ 打开 https://gitee.com/openharmony/utils_native 进入 C++ 公共基础库代码托管网站。在左边的分支下拉框选择 OpenHarmony-3.0-LTS ,点击右边的 克隆/下载 ,再点击 下载ZIP

​ 把下载到的 zip 压缩包解压到工程目录的 Core\utils_native 目录下:

0x05.03 添加代码到 MDK 工程

​ 回到 MDK 点击 Project->Manage->Project Items... 菜单,把以下代码添加到 MDK 工程:

Core\LiteOS\kernel\arch\arm\cortex-m3\keil\
    *.c
    los_dispatch.S
    los_exc.S

Core\LiteOS\kernel\src
    *.c
Core\LiteOS\kernel\src\mm
    *.c

Core\LiteOS\components\power
    *.c
Core\LiteOS\components\exchook
    *.c

Core\utils_native\base\src
    memcpy_s.c
    memset_S.C
    strcpy_s.c
    strncpy_s.c

Core\LiteOS\utils
    los_debug.c
    los_error.c

0x05.04 下载 target_config.h

​ 把 https://gitee.com/rtos_lover/stm32f103_simulator_keil/raw/master/target_config.h 文件保存到 Core\target_config 目录。

​ 并在 Core\target_config 目录新建一个空的 ARMCM3.h 文件。

0x05.05 添加包含目录

​ 点击 Project->Options... 菜单,切换到 C/C++ 标签。

​ 把以下目录添加到列表里:

..\Core\LiteOS\kernel\arch\arm\cortex-m3\keil
..\Core\LiteOS\kernel\arch\include
..\Core\LiteOS\kernel\include
..\Core\LiteOS\utils
..\Core\LiteOS\utils\internal
..\Core\LiteOS\components\power
..\Core\LiteOS\components\exchook
..\Core\utils_native\base\include
..\Core\target_config

05.06 修改 target_config.h

​ 在打开 target_config.h 文件,在 #include "ARMCM3.h" 下面添加 #include "stm32f1xx.h"

#include "ARMCM3.h"

#include "stm32f1xx.h"  // 增加此行代码

#ifdef __cplusplus

​ 搜索 g_memStart 并注释掉以下 4 行代码:

/*=============================================================================
                                       Memory module configuration
=============================================================================*/
/* 注释下面 4 行代码
extern unsigned char g_memStart[];
#define LOSCFG_SYS_EXTERNAL_HEAP                            1
#define LOSCFG_SYS_HEAP_ADDR                                (&g_memStart[0])
#define LOSCFG_SYS_HEAP_SIZE                                0x00013000
*/
#define LOSCFG_MEM_MUL_POOL                                 1
#define OS_SYS_MEM_NUM                                      20

​ 保存文件后按 F7 或点击 Project->Build Target 菜单,如果你没有操作错误,应该可以编译通过了。

0x06 完成 LiteOS-m 移植

​ 前面我们已经把 LiteOS-m 内核的代码和相关依赖的代码都添加到工程里面并编译通过,移植工作已经完成过半了。最后只剩下初始化和启动 LiteOS 系统了。

​ 祼机工程最后会进入到 main.c 文件的 main 函数,因此我们需要在 main 函数来初始化并启动 LiteOS-m 内核。

0x06.01 在 main.c 引入头文件

/* USER CODE BEGIN Includes */

#include <stdio.h>
#include "los_task.h"

/* USER CODE END Includes */

0x06.02 添加任务函数

/* USER CODE BEGIN 0 */

static void Task1(void)
{
  while(1) {
    printf("Running Task1...\r\n");
    LOS_TaskDelay(1000);
  }
}

static void Task2(void)
{
  while(1) {
    printf("Running Task2...\r\n");
    LOS_TaskDelay(3000);
  }
}

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 = 6;
    task_init_param.pcName = "Task1";
    task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Task1;
    task_init_param.uwStackSize = 2048;    
    uwRet = LOS_TaskCreate(&RX_Task_Handle, &task_init_param);
  if (uwRet != LOS_OK)
  {
    printf("Task1 create failed, %X\n", uwRet);
    return uwRet;
  }

  task_init_param.usTaskPrio = 4;    
  task_init_param.pcName = "Task2";
  task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Task2;
  task_init_param.uwStackSize = 2048;
  uwRet = LOS_TaskCreate(&TX_Task_Handle, &task_init_param);
  if (uwRet != LOS_OK)
  {
    printf("Task2 create failed, %X\n", uwRet);
    return uwRet;
  } 

    return LOS_OK;
}

/* USER CODE END 0 */

0x06.03 在 main 函数初始化并启动 LiteOS-m 内核

  /* USER CODE BEGIN 2 */

  LOS_KernelInit(); // 初始化 LiteOS-m 内核
  UINT32 uwRet = AppTaskCreate(); // 创建任务
  if(uwRet != LOS_OK) {
    printf("LOS Creat task failed\r\n");
  }
  LOS_Start();      // 启动 LiteOS-m 内核

  /* USER CODE END 2 */

0x06.04 使用分散加载

​ 上面修改后的代码已经可以编译通过,但是你会发现最后链接的时候会报以下错误:

stm32f103_liteos\stm32f103_liteos.axf: Error: L6406E: No space in execution regions with .ANY selector matching los_memory.o(.bss).

​ 那是因为 LiteOS 的代码已经超出祼机工程默认的链接配置了,我们需要使用自定义的链接脚本。

​ 把 https://gitee.com/rtos_lover/stm32f103_simulator_keil/raw/master/project/los_demo.sct 文件保存到 MDK-ARM 目录。

​ 打开 los_demo.sct 修改成以下内容:

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00200000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00200000  {  ; load address = execution address
   *.o (RESET, +First)               ; 修改这行
   *(InRoot$$Sections)
   * (+RO)
  }
  RW_IRAM1 0x20000000 0x00200000  {  ; RW data
      * (.data, .bss, .data.init)
      * (+RW +ZI)                    ; 增加这行
  }
    VECTOR 0x20200000 0x400         ; Vector
  {
    * (.vector)
  }

  ARM_LIB_STACKHEAP 0x08100000 EMPTY 0x1000 
  {

  }
}

​ 最后配置 MDK 使用 los_demo.sct 链接就可以了。点击 Project->Options... 菜单,切换到 Linker 标签,按下面的图片修改设置:

​ 编译并启动模拟器后可以看到 LiteOS 已经可以正常运行了。

0x07 代码下载

​ 完整的代码可以点击 stm32f103_liteos.7z 下载。

0x08 链接

https://blog.51cto.com/u_15214399/2809850

https://gitee.com/openharmony/kernel_liteos_m

https://gitee.com/openharmony/utils_native

https://gitee.com/rtos_lover/stm32f103_simulator_keil

https://www.st.com/zh/development-tools/stm32cubemx.html

https://www.keil.com/mdk5

Built with Hugo
Theme Stack designed by Jimmy