Skip to main content

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.00GitHub 下载地址

开发环境
#

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.hFreeRTOS\

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_HandlerSVC_Handler已经实现了,对应port.c中的xPortPendSVHandlervPortSVCHandler。所以我们现在要做的是:

  1. 注释掉 CW32 官方实现的PendSV_HandlerSVC_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