↓ Skip to main content
Background Image

CW32 移植 FreeRTOS

·3240 words·7 mins
Kydin
Author
Kydin
自由のために戦え
Table of Contents

本文介绍了如何在芯源的 CW32F03 系列 MCU 中移植 FreeRTOS。

先说一个暴论:所有主频在 100MHz 下的 MCU,都没有必要使用 FreeRTOS,直接跑裸机程序即可。

但折腾是一种乐趣,所以我们就开始吧!

本次使用的芯片是武汉芯源的 CW32F030F8,规格如下:

  • 内核:ARM® Cortex®-M0+,最高主频 64MHz
  • Flash:64KB 片上存储
  • SRAM:8KB

由于只是移植 FreeRTOS,其它的外设就不过多介绍了,有需要了解的可以去看看数据手册。

⚠️注意:本文旨在用最少的步骤快速进行 FreeRTOS 的移植,不涉及 FreeRTOS 的参数优化

本文完整的代码已经上传 GitHub:CW32F03-FreeRTOS

前置准备
#

下载 FreeRTOS 代码

我使用的版本是FreeRTOSv202411.00,GitHub 下载地址

开发环境
#

Keil5 版本信息

代码移植
#

通用层
#

当前项目结构如下:

├─App
├─BSP
├─Core
├─MDK-ARM
│  ├─Listings
│  ├─Objects
│  └─RTE
│      ├─CMSIS
│      └─Device
│          └─CW32F030F8
│
└─Middlewares
    └─Third_Party
  1. 在工程文件夹Middlewares\Third_Party\下新建文件夹,命名为FreeRTOS
  2. 将FreeRTOSv202411.00\FreeRTOS\Source\中所有 .c 文件复制到FreeRTOS\中
  3. 将FreeRTOSv202411.00\FreeRTOS\Source\include整个文件夹复制到FreeRTOS\中

此时项目结构如下:

├─App
├─BSP
├─Core
├─MDK-ARM
│  ├─Listings
│  ├─Objects
│  └─RTE
│      ├─CMSIS
│      └─Device
│          └─CW32F030F8
│
└─Middlewares
    └─Third_Party
        └─FreeRTOS
            │  croutine.c
            │  event_groups.c
            │  FreeRTOSConfig.h
            │  list.c
            │  queue.c
            │  stream_buffer.c
            │  tasks.c
            │  timers.c
            │
            └─include
                    atomic.h
                    croutine.h
                    deprecated_definitions.h
                    event_groups.h
                    FreeRTOS.h
                    list.h
                    message_buffer.h
                    mpu_prototypes.h
                    mpu_syscall_numbers.h
                    mpu_wrappers.h
                    newlib-freertos.h
                    picolibc-freertos.h
                    portable.h
                    projdefs.h
                    queue.h
                    semphr.h
                    StackMacros.h
                    stack_macros.h
                    stream_buffer.h
                    task.h
                    timers.h

移植层
#

查阅 MCU 手册可以得知,我使用的 CW32F030x6/x8 属于 ARM Cortex-M0 系列

  1. 在FreeRTOS\目录下新建文件夹portable
  2. 复制FreeRTOSv202411.00\FreeRTOS\Source\portable中的MemMang\文件夹到FreeRTOS\portable\下
  3. 复制FreeRTOSv202411.00\FreeRTOS\Source\portable中的RVDS\ARM_CM0\文件夹到FreeRTOS\portable\下

此时项目结构如下:

├─App
├─BSP
├─Core
├─MDK-ARM
│  ├─Listings
│  ├─Objects
│  └─RTE
│      ├─CMSIS
│      └─Device
│          └─CW32F030F8
└─Middlewares
    └─Third_Party
        └─FreeRTOS
            │  croutine.c
            │  event_groups.c
            │  FreeRTOSConfig.h
            │  list.c
            │  queue.c
            │  stream_buffer.c
            │  tasks.c
            │  timers.c
            │
            ├─include
            │      atomic.h
            │      croutine.h
            │      deprecated_definitions.h
            │      event_groups.h
            │      FreeRTOS.h
            │      list.h
            │      message_buffer.h
            │      mpu_prototypes.h
            │      mpu_syscall_numbers.h
            │      mpu_wrappers.h
            │      newlib-freertos.h
            │      picolibc-freertos.h
            │      portable.h
            │      projdefs.h
            │      queue.h
            │      semphr.h
            │      StackMacros.h
            │      stack_macros.h
            │      stream_buffer.h
            │      task.h
            │      timers.h
            │
            └─portable
                ├─MemMang
                │      heap_1.c
                │      heap_2.c
                │      heap_3.c
                │      heap_4.c
                │      heap_5.c
                │
                └─RVDS
                    └─ARM_CM0
                            port.c
                            portmacro.h

内存分配策略的选择
#

上一步我们将FreeRTOSv202411.00\FreeRTOS\Source\portable\MemMang\ 复制过来,这个文件夹用于实现 FreeRTOS 的不同内存分配策略,它们的区别如下:

特性 heap_1 heap_2 heap_3 heap_4 heap_5
碎片化 无 有 无 (外部管理) 低 低 (多区域)
内存释放 不支持 支持 支持 支持 支持
内存合并 不支持 不支持 不支持 支持 支持
多区域 不支持 不支持 不支持 不支持 支持
确定性 高 中等 依赖外部 中等 中等
代码复杂度 简单 中等 简单 中等 较复杂
适用场景 简单应用 较小项目 与标准库集成 通用 复杂内存布局

大部分情况,选择 heap_4

添加配置文件
#

从FreeRTOSv202411.00\FreeRTOS\Source\examples\template_configuration\中,复制FreeRTOSConfig.h到FreeRTOS\下

FreeRTOSConfig.h 是 FreeRTOS 的核心配置文件,它允许用户在不修改 FreeRTOS 内核源代码的情况下,自定义系统的所有关键参数和行为。

修改点 1
#

在 44 行后添加

#include "cw32f030.h" // 根据 MCU 型号添加主头文件

// 针对不同的编译器调用不同的 stdint.h 文件
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
    #include <stdint.h>
    extern uint32_t SystemCoreClock; // 声明 SystemCoreClock 变量已存在,修改点 2 需要用到
#endif
修改点 2
#

将 54 行的

#define configCPU_CLOCK_HZ    ( ( unsigned long ) 20000000 )

改为

#define configCPU_CLOCK_HZ    ( SystemCoreClock )

含义:系统主频。一般 SystemCoreClock 会和系统主频相等,不直接设置主频数。

修改点 3
#

将 78 行的

#define configTICK_RATE_HZ                         100

改为

#define configTICK_RATE_HZ                         1000

含义:嘀嗒计时频率,原本的 100Hz 太慢了,改为 1000 后也就是 1ms 的节拍频率。

修改点 4
#

将 135 行的

#define configTICK_TYPE_WIDTH_IN_BITS              TICK_TYPE_WIDTH_64_BITS

改为

#define configTICK_TYPE_WIDTH_IN_BITS              TICK_TYPE_WIDTH_32_BITS

含义:ARM Cortex-M0 (CW32F030) 是一个 32 位架构,它只支持 16 位或 32 位的 tick 类型,不支持 64 位。使用 64 位编译时报错:

..\Middlewares\Third_Party\FreeRTOS\portable\RVDS\ARM_CM0\portmacro.h(73): error: #35: #error directive: configTICK_TYPE_WIDTH_IN_BITS set to unsupported tick type width.
修改点 5
#

将 357 行的

#define configCHECK_FOR_STACK_OVERFLOW        2

改为

#define configCHECK_FOR_STACK_OVERFLOW        0

含义:堆栈溢出检测功能,0 为关闭堆栈溢出检测功能,如果要开启,还需要自己实现对应的钩子函数,这里就直接关掉了,反正暂时也没什么用。如果不关闭会报错:

.\Objects\freertos.axf: Error: L6218E: Undefined symbol vApplicationStackOverflowHook (referred from tasks.o).

中断服务函数(重点)
#

SysTick 中断服务函数是一个非常重要的函数,FreeRTOS 所有跟时间相关的事情都在里面处理,SysTick 就是 FreeRTOS 的一个心跳时钟,驱动着 FreeRTOS 的运行。

FreeRTOS 接管了如下三个中断:SVC_Handler、PendSV_Handler、SysTick_Handler

其中PendSV_Handler与SVC_Handler已经实现了,对应port.c中的xPortPendSVHandler与vPortSVCHandler。所以我们现在要做的是:

  1. 注释掉 CW32 官方实现的PendSV_Handler与SVC_Handler函数,因为我们要使用 FreeRTOS 的对应的函数。
  2. 自己实现一个SysTick_Handler函数

注释原有的官方函数
#

CW32 的中断函数定义都在interrupts_cw32f030.c中,只需要找到以下函数直接注释掉即可

/**
  * @brief This function handles System service call via SWI instruction.
  * @note
  */
void SVC_Handler(void)
{
     /* USER CODE BEGIN SVCall_IRQn */
     /* USER CODE END SVCall_IRQn */
}

/**
  * @brief This function handles Pendable request for system service.
  * @note
  */
void PendSV_Handler(void)
{
     /* USER CODE BEGIN PendSV_IRQn */
     /* USER CODE END PendSV_IRQn */
}

⚠️注意,在 CW32F03 中,启动文件定义了向量表中的函数名,因此需要修改 startup_cw32f030.s 中的相关字段,否则会导致程序在SysTick_Handler中卡死

修改点 1
#

修改startup_cw32f030.s的 53 行

DCD     SVC_Handler               ;< -5 System Service Call via SVC instruction

改为

DCD     vPortSVCHandler           ;< -5 System Service Call via SVC instruction (FreeRTOS)
修改点 2
#

修改startup_cw32f030.s的 56 行

DCD     PendSV_Handler            ;< -2 Pendable request for system service

改为

DCD     xPortPendSVHandler        ;< -2 Pendable request for system service (FreeRTOS)
修改点 3
#

将startup_cw32f030.s的 105 行

IMPORT  SystemInit
IMPORT  __main

LDR     R1, =0x0

改为

IMPORT  SystemInit
IMPORT  __main
IMPORT  vPortSVCHandler
IMPORT  xPortPendSVHandler

LDR     R1, =0x0

实现 SysTick_Handler 函数
#

在一个你认为合适的地方写上以下代码,我写在BSP/clock/clock.c中

#include "FreeRTOS.h"
#include "task.h"

extern void xPortSysTickHandler(void);

/**
 * @brief This function handles System tick timer.
 */
void SysTick_Handler(void)
{
#if (INCLUDE_xTaskGetSchedulerState == 1)
    if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
    {
#endif
        xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1)
    }
#endif
}

添加到工程
#

建议在 keil 中新建一个分组,将 FreeRTOS\*.c FreeRTOS\portable\MemMang\heap_4.c FreeRTOS\portable\RVDS\ARM_CM0\port.c 这些 c 文件添加到工程中,此时你的项目看上去应该是这样的:

项目结构

并添加头文件路径:

添加头文件路径到 Keil 工程

测试代码
#

在 main.c 中写了一段简单的测试代码:创建两个任务,一个任务每隔 300ms 通过串口发送一段字符串,一个任务每隔 500ms 通过串口发送一段字符串。其中系统初始化和串口发送是根据我使用的 CW32F03 写的,这里要灵活变通。

// FreeRTOS 头文件
#include "FreeRTOS.h"
#include "task.h"

// 开发板硬件 bsp 头文件
#include "bsp.h"
#include "uart.h"

#define MAX_TASK 2

static TaskHandle_t AppTask_Handle[MAX_TASK]; // 任务句柄
static BaseType_t xReturn[MAX_TASK];          // 创建信息返回值

static void AppTask1(void *parameter)
{
    char data[] = "task1 running";

    while (1)
    {
        uart_send((uint8_t *)data, sizeof(data));
        vTaskDelay(300);
    }
}

static void AppTask2(void *parameter)
{
    char data[] = "task2 running";

    while (1)
    {
        uart_send((uint8_t *)data, sizeof(data));
        vTaskDelay(500);
    }
}

int main(void)
{
    /* 开发板硬件初始化 */
    bsp_init();

    uint8_t msg[] = "hello world";
    uart_send(msg, sizeof(msg));

    /* 创建两个任务 */
    xReturn[0] = xTaskCreate((TaskFunction_t)AppTask1,            // 任务入口函数
                             (const char *)"AppTask1",            // 任务名字
                             (uint16_t)128,                       // 任务栈大小
                             (void *)NULL,                        // 任务入口函数参数
                             (UBaseType_t)2,                      // 任务的优先级 (0~最大值 -1 有效)
                             (TaskHandle_t *)&AppTask_Handle[0]); // 任务控制块指针

    xReturn[1] = xTaskCreate((TaskFunction_t)AppTask2,            // 任务入口函数
                             (const char *)"AppTask2",            // 任务名字
                             (uint16_t)128,                       // 任务栈大小
                             (void *)NULL,                        // 任务入口函数参数
                             (UBaseType_t)2,                      // 任务的优先级 (0~最大值 -1 有效)
                             (TaskHandle_t *)&AppTask_Handle[1]); // 任务控制块指针

    /* 启动任务调度 */
    for (int i = 0; i < MAX_TASK; i++)
    {
        if (xReturn[i] != pdPASS)
        {
            return -1; // 如果有创建失败的任务,就结束程序
        }
    }
    vTaskStartScheduler(); // 启动任务,开启调度

    while (1)
        ; // 正常不会执行到这里
}

编译后 0 警告 0 错误,直接烧写到板上

编译结果

烧写后,程序运行效果如下,自此,CW32F03 移植 FreeRTOS 成功:

运行效果

参考链接
#

HC32F460 freeRTOS 移植

超详细的 FreeRTOS 移植全教程——基于 srm32