本文介绍了如何在芯源的 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 下载地址
开发环境 #
代码移植 #
通用层 #
当前项目结构如下:
├─App
├─BSP
├─Core
├─MDK-ARM
│ ├─Listings
│ ├─Objects
│ └─RTE
│ ├─CMSIS
│ └─Device
│ └─CW32F030F8
│
└─Middlewares
└─Third_Party
- 在工程文件夹
Middlewares\Third_Party\下新建文件夹,命名为FreeRTOS - 将
FreeRTOSv202411.00\FreeRTOS\Source\中所有 .c 文件复制到FreeRTOS\中 - 将
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 系列
- 在
FreeRTOS\目录下新建文件夹portable - 复制
FreeRTOSv202411.00\FreeRTOS\Source\portable中的MemMang\文件夹到FreeRTOS\portable\下 - 复制
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。所以我们现在要做的是:
- 注释掉 CW32 官方实现的
PendSV_Handler与SVC_Handler函数,因为我们要使用 FreeRTOS 的对应的函数。 - 自己实现一个
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 文件添加到工程中,此时你的项目看上去应该是这样的:
并添加头文件路径:
测试代码 #
在 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 成功: