前言
FreeRTOS是一个可以基于ROM运行的、可裁剪的、抢占式、实时多任务内核,具有高度可移植性,特点:公开源代码、可移植性、可固化、可裁剪、多任务、占先式,特别适合于微处理器和控制器,适合很多商业操作系统性能相当的实时操作系统(RTOS)。在使用GD32F103单片机项目移植过FreeRTOS,这里作为一个基础教学版简单记录一下移植过程,欢迎指正!
一、移植前的准备
<1>硬件平台:可运行软件程序的GD32单片机(本项目使用GD32F103VE硬件平台)
<2>软件平台:可直接下载运行的单片机基础工程,本例程是基于使用标准库GigaDevice.GD32F10x_DFP.2.0.3固件库编写

<3>源码获取:FreeRTOS源码(本例程使用源码版本为:FreeRTOS 202406.04 LTS)
下载链接:https://www.freertos.org/a00104.html
<4>ARM编译器版本:**ARM Compiler 6 (AC6后面均使用该缩写)。**AC6 编译速度更快,且是 Arm 未来的主流方向

二、移植
1.GD32 Keil工程
准备一个可以正常运行的GD32工程文件,这里使用的是自己建立的工程项目文件:

2. FreeRTOS源码

接下来我们开始移植。
3.FreeRTOS移植
1、在源码中我们主要关注下面几个FreeRTOS关键文件,后面都会用到:

2、首先在GD32工程文件中,找到合适的地方,新建"FreeRTOS"文件夹,并新建三个子文件夹"inc"、"src"、"port"用于存放上述文件:

3、将源码中".\FreeRTOS\FreeRTOS-Kernel\include"下所有.h文件拷贝到上面"./FreeRTOS/inc"文件夹下。

4、把下面7个.C文件拷贝到src目录下:

5、将配置文件的模板文件拷贝到"/FreeRTOS"目录下:

6、在接口层,我们主要适配内存管理部分和IC硬件上的接口部分,这两个位于".\FreeRTOS-Kernel\portable"下:
注意:"GCC"和"RVDS"是为编译器提供编译环境所需要的相关文件,我们编译器版本是AC6所以我们用"GCC"。如果使用的版本是AC5,选择"RVDS"。如果选错,编译时会上百个报错。删除后重新根据这个步骤添加一下就行(已踩坑),或者更改编译器版本。

"MemMang"中是FreeRTOS提供的5个内存分配方案:

我们需要根据自己的MCU类型去选择,例如我们使用的GD32F103系列是ARM架构、Cortex-M3的核、不带MPU的MCU,就仅保留"ARM_CM3"的文件夹:

最终我们操作后的相关文件如下:

接下来我们就要在编译器中将上述文件加载进来。
4. 工程操作
1、打开工程文件,新建工程资源文件夹"FreeRTOS/Src"和"FreeRTOS/Port",把C文件加载进来:

2、其中内存管理有5种方式,我们选择"heap_4":

3、将头文件包含进来

4、编译器使用V6:

到这里相关源码移植完成了,接下来就是代码操作部分,完成最后的接口适配工作
三. 代码操作
1、首先调整堆栈大小,打开启动文件"startup_gd32f10x_hd.s",我使用的GD32F103VE,堆栈设置为0x3000和0x1000,大家根据自己的项目需要和MCU能力自由分配:

2、打开载入配置文件,可以在main.c文件中导入"FreeRTOS.h",然后右键打开h文件:


3、就可以看到引用的"FreeRTOSConfig.h"文件,还是右键打开:

4、在"FreeRTOSConfig.h"文件中配置freeRTOS,官方模板文件中开启了很多的配置项,对于初学者,大部分我们并不太关心。针对gd32F103我做了如下修改,全部删除FreeRTOSConfig.h文件中的默认配置,直接复制粘贴以下内容替换(后续可根据自身需求更改):
bash
/*
* FreeRTOS Kernel V10.2.1
* Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* http://www.FreeRTOS.org
* http://aws.amazon.com/freertos
*
* 1 tab == 4 spaces!
*/
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
#include "systick.h"
//针对不同的编译器调用不同的stdint.h文件
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
#include <stdint.h>
#include <stdio.h>
extern uint32_t SystemCoreClock;
#endif
//断言
#define vAssertCalled(char,int) printf("Error:%s,%d\r\n",char,int)
#define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)
/***************************************************************************************************************/
/* FreeRTOS基础配置配置选项 */
/***************************************************************************************************************/
#define configUSE_PREEMPTION 1 //1使用抢占式内核,0使用协程
#define configUSE_TIME_SLICING 1 //1使能时间片调度(默认式使能的)
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 //1启用特殊方法来选择下一个要运行的任务
#define configUSE_TICKLESS_IDLE 0 //1启用低功耗tickless模式
#define configUSE_QUEUE_SETS 1 //为1时启用队列
#define configCPU_CLOCK_HZ (SystemCoreClock) //CPU频率
#define configTICK_RATE_HZ (1000) //时钟节拍频率,这里设置为1000,周期就是1ms
#define configMAX_PRIORITIES (32) //可使用的最大优先级
#define configMINIMAL_STACK_SIZE ((unsigned short)130) //空闲任务使用的堆栈大小
#define configMAX_TASK_NAME_LEN (16) //任务名字字符串长度
#define configUSE_16_BIT_TICKS 0 //系统节拍计数器变量数据类型,
//1表示为16位无符号整形,0表示为32位无符号整形
#define configIDLE_SHOULD_YIELD 1 //为1时空闲任务放弃CPU使用权给其他同优先级的用户任务
#define configUSE_TASK_NOTIFICATIONS 1 //为1时开启任务通知功能,默认开启
#define configUSE_MUTEXES 1 //为1时使用互斥信号量
#define configQUEUE_REGISTRY_SIZE 8 //不为0时表示启用队列记录,具体的值是可以
//记录的队列和信号量最大数目。
#define configCHECK_FOR_STACK_OVERFLOW 0 //大于0时启用堆栈溢出检测功能,如果使用此功能
//用户必须提供一个栈溢出钩子函数,如果使用的话
//此值可以为1或者2,因为有两种栈溢出检测方法。
#define configUSE_RECURSIVE_MUTEXES 1 //为1时使用递归互斥信号量
#define configUSE_MALLOC_FAILED_HOOK 0 //1使用内存申请失败钩子函数
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 1 //为1时使用计数信号量
/***************************************************************************************************************/
/* FreeRTOS与内存申请有关配置选项 */
/***************************************************************************************************************/
#define configSUPPORT_DYNAMIC_ALLOCATION 1 //支持动态内存申请
#define configTOTAL_HEAP_SIZE ((size_t)(10*1024)) //系统所有总的堆大小
/***************************************************************************************************************/
/* FreeRTOS与钩子函数有关的配置选项 */
/***************************************************************************************************************/
#define configUSE_IDLE_HOOK 0 //1,使用空闲钩子;0,不使用
#define configUSE_TICK_HOOK 0 //1,使用时间片钩子;0,不使用
/***************************************************************************************************************/
/* FreeRTOS与运行时间和任务状态收集有关的配置选项 */
/***************************************************************************************************************/
#define configGENERATE_RUN_TIME_STATS 0 //为1时启用运行时间统计功能
#define configUSE_TRACE_FACILITY 1 //为1启用可视化跟踪调试
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 //与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数
/***************************************************************************************************************/
/* FreeRTOS与协程有关的配置选项 */
/***************************************************************************************************************/
#define configUSE_CO_ROUTINES 0 //为1时启用协程,启用协程以后必须添加文件croutine.c
#define configMAX_CO_ROUTINE_PRIORITIES (2) //协程的有效优先级数目
/***************************************************************************************************************/
/* FreeRTOS与软件定时器有关的配置选项 */
/***************************************************************************************************************/
#define configUSE_TIMERS 1 //为1时启用软件定时器
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1) //软件定时器优先级
#define configTIMER_QUEUE_LENGTH 5 //软件定时器队列长度
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2) //软件定时器任务堆栈大小
/***************************************************************************************************************/
/* FreeRTOS可选函数配置选项 */
/***************************************************************************************************************/
#define INCLUDE_xTaskGetSchedulerState 1
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskCleanUpResources 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_eTaskGetState 1
#define INCLUDE_xTimerPendFunctionCall 1
/***************************************************************************************************************/
/* FreeRTOS与中断有关的配置选项 */
/***************************************************************************************************************/
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 //中断最低优先级
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 //系统可管理的最高中断优先级
#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/***************************************************************************************************************/
/* FreeRTOS与中断服务函数有关的配置选项 */
/***************************************************************************************************************/
#define xPortPendSVHandler PendSV_Handler
//#define xPortSysTickHandler SysTick_Handler
#define vPortSVCHandler SVC_Handler
#endif /* FREERTOS_CONFIG_H */
上述相关内容解释:
1.调整系统时钟

2.配置"configCPU_CLOCK_HZ"为"SystemCoreClock"

3."port.c"文件中适配接口函数

5、上面内容替换后,在工程中FreeRTOSConfig.h如下所示:

6、xPortSysTickHandler函数用于更新系统时基,我们把它放在系统滴答里,打开"gd32f10x_it.c"文件,首先添加头文件与函数声明:

7、滑到最后, port.c 和 gd32f10x_it.c 这两个文件中有重复定义的函数:PendSV_Handler()、SVC_Handler()和 Systick_Handler(),这里屏蔽掉 gd32f10x_it.c 中的 PendSV_Handler()、SVC_Handler()两个函数,然后在"SysTick_Handler"中添加新代码:

上述新代码:
bash
#if (INCLUDE_xTaskGetSchedulerState == 1 )
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
#endif
xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
}
#endif
8、注释掉bsp_timer.c中SysTick_Handler实现,这个函数原本用于裸机的时间计数和任务调度,现在将由 FreeRTOS 接管,确保只 gd32f10x_it.c 中有 SysTick_Handler。
(此处我的bsp_timer.c中有实现这个函数,如果你的代码没有相关内容,可以忽略该步骤)

9、配置并启动滴答定时器,打开systick.c文件,对void systick_config(void)做以下修改,如果提示未定义则添加#include "FreeRTOS.h"和#include "task.h"包含头文件

上述代码:
bash
/*!
\brief configure systick
\param[in] none
\param[out] none
\retval none
*/
void systick_config(void)
{
/* setup systick timer for 1000Hz interrupts*/
if (SysTick_Config(rcu_clock_freq_get(CK_SYS) / configTICK_RATE_HZ)){
/* capture error */
while (1){
}
}
/* configure the systick handler priority */
NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
至此移植相关代码修改完成。
编译运行0警告0错误:

接下来就是修改任务调度器,让FreeRTOS全权托管了
四、Hello FreeRTOS,创建任务启动任务
1、任务优先级,堆栈和句柄
bash
//test
#define tesTask_PRIO (tskIDLE_PRIORITY + 1)
#define tesTask_STK_SIZE 256
TaskHandle_t tesTask_Handler;
//
#define LedTask_PRIO (tskIDLE_PRIORITY + 1)
#define LedTask_STK_SIZE 256
TaskHandle_t LedTask_Handler;
2、任务创建函数
bash
/**********************************************************************************************************
* 函数名: RTOS_Init
* 功能说明: RTOS初始化,用于创建任务
* 形参:无
* 返回值: 无
**********************************************************************************************************/
void RTOS_Init(void)
{
taskENTER_CRITICAL(); //进入临界区
//测试任务
xTaskCreate((TaskFunction_t )tesTask,
(const char* )"tesTask",
(uint16_t )tesTask_STK_SIZE,
(void* )NULL,
(UBaseType_t )tesTask_PRIO,
(TaskHandle_t* )NULL);
//LED任务
xTaskCreate((TaskFunction_t )LedTask,
(const char* )"LedTask",
(uint16_t )LedTask_STK_SIZE,
(void* )NULL,
(UBaseType_t )LedTask_PRIO,
(TaskHandle_t* )NULL);
//扩展任务模块
//。。。。。。
taskEXIT_CRITICAL(); //退出临界区
vTaskStartScheduler(); //开启任务调度
}
3、两个任务实现部分
bash
#include "FreeRTOS.h"
#include "task.h"
/*
* Description : Test
* Paramter : None
* Return : None
*/
void tesTask()
{
while(1)
{
printf("Hello FreeRTOS\r\n");
vTaskDelay(1000);
}
}
/*
* Description : LED
* Paramter : None
* Return : None
*/
void LedTask(void)
{
static U08 count1 = 0;
static U08 count2 = 0;
while(1)
{
if (SYS_FLT == 1)
{
count1 = (count1 + 1) % 10;
count2 = 0;
// 系统故障,红灯闪烁
if (count1 <= 3)
{
LED_GREEN_OFF();
LED_RED_ON();
}
else
{
LED_GREEN_OFF();
LED_RED_OFF();
}
}
else if (SYS_RUN == 1)
{
count1 = 0;
count2 = (count2 + 1) % 10;
// 运行状态,绿灯闪烁
if (count2 <= 3)
{
LED_GREEN_ON();
LED_RED_OFF();
}
else
{
LED_GREEN_OFF();
LED_RED_OFF();
}
}
else
{
count1 = 0;
count2 = 0;
// 空闲状态
LED_GREEN_ON();
LED_RED_OFF();
}
vTaskDelay(100);
}
}
4、在主函数中完成调用
bash
/*
* Description : Main
* Parameter : None
* Return : None
*/
int main(void)
{
System_Init(); //自定义函数,内部包含其他初始化模块
RTOS_Init(); //RTOS任务创建
}
最后编译通过,0警告0错误。
接下来大家就可以嗨皮的学习FreeRTOS的各类组件了。