一、前言

GD32H759 是 GigaDevice 推出的 Cortex‑M7 高性能 MCU,主频最高 600MHz,带 FPU 和丰富片上外设,非常适合跑 RTOS。 FreeRTOS 则是目前应用最广泛的轻量级实时操作系统,内核小巧、移植简单,官方已经提供了 Cortex‑M7 的标准移植层。

这篇文章记录把 FreeRTOS V202212.00 移植到 GD32H759 + Keil MDK(使用 ARM Compiler 5) 的完整过程,包括踩坑和调试经验,最终实现一个简单的 FreeRTOS 点灯任务,并通过串口打印调试信息。


二、工程准备

2.1 硬件与软件环境

  • MCU:GD32H759 系列(本文以 GD32H759I_start开发板为例)。

  • IDE:Keil MDK‑ARM 5.x

  • 编译器:ARM Compiler V5.06(armcc)

  • FreeRTOS:FreeRTOS‑Kernel V202212.00

  • 官方库:GD32H7xx 标准外设库

保证先有一个 裸机点灯 的 Keil 工程,这是移植的基础。

2.2 新建/拷贝基础工程

通常做法是:

  1. 使用 GigaDevice 提供的 GD32H759 示例工程,复制一份作为 FreeRTOS 工程模板。

浏览器搜索“兆易创新资料下载中心”,左边点击GD32H7,右边找到应用软件,找到GD32H7xx addon   ,如果自己的keil里面没得GD32H7的包,那么就得先安装这个包。

然后下载这官方个例程

自己先跑一下这个例程,能否在板子上正常的跑起来。没问题就可以。

移植第一个点灯例程作为模板。


三、添加 FreeRTOS 内核到 GD32 工程

3.1 拷贝 FreeRTOS 源码

从 FreeRTOS 官方仓库下载内核源码后,下载 FreeRTOS - FreeRTOS™

拷贝以下文件到工程根目录下的 FreeRTOS 文件夹。

自己创建FreeRTOS 文件结构如下:

解压后的freertos文件

复制一下文件到core

头文件:

  • 整个 FreeRTOS/Source/include/ 目录。

使用Keil AC5则需要移植RVDS路径的port.c,AC6则是GCC下的.

  • FreeRTOS/Source/portable/RVDS/ARM_CM7/r0p1/port.c

  • FreeRTOS/Source/portable/RVDS/ARM_CM7/r0p1/portmacro.h

内存管理:

  • FreeRTOS/Source/portable/MemMang/heap_4.c(常用,支持内存合并)

3.2 在 Keil 工程中加入文件

建议在 Keil 工程中建立如下的分组,结构更清晰:

3.3 配置头文件路径

在 Keil 中打开 Options for Target → C/C++ → Include Paths,加入freertos的头文件路径。

确保没有把 GCC\ARM_CM7 路径也加进来,否则容易和 AC5 产生语法冲突。


四、编写 FreeRTOSConfig.h(GD32H759 版)

在工程中新建 FreeRTOSConfig.h,核心配置如下,可以根据需要调整参数


#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

#include "gd32h7xx.h"
#include "bsp_uart.h"

/*-----------------------------------------------------------
 * Application specific definitions.
 *
 * These definitions should be adjusted for your particular hardware and
 * application requirements.
 *
 * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
 * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
 *
 * See http://www.freertos.org/a00110.html
 *----------------------------------------------------------*/
//针对不同的编译器调用不同的stdint.h文件
/* Ensure stdint is only used by the compiler, and not the assembler. */
#if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
	#include <stdint.h>
	extern uint32_t SystemCoreClock;
#endif

/* 置1:RTOS使用抢占式调度器;置0:RTOS使用协作式调度器(时间片)
 * 
 * 注:在多任务管理机制上,操作系统可以分为抢占式和协作式两种。
 * 协作式操作系统是任务主动释放CPU后,切换到下一个任务。
 * 任务切换的时机完全取决于正在运行的任务。
 */
#define configUSE_PREEMPTION			1

/*
 * 写入实际的CPU内核时钟频率,也就是CPU指令执行频率,通常称为Fclk
 * Fclk为供给CPU内核的时钟信号,我们所说的cpu主频为 XX MHz,
 * 就是指的这个时钟信号,相应的,1/Fclk即为cpu时钟周期;
 */
#define configCPU_CLOCK_HZ				( SystemCoreClock )
//RTOS系统节拍中断的频率。即一秒中断的次数,每次中断RTOS都会进行任务调度
#define configTICK_RATE_HZ				( ( TickType_t ) 1000 )
//可使用的最大优先级
#define configMAX_PRIORITIES			( 32 )
//空闲任务使用的堆栈大小
#define configMINIMAL_STACK_SIZE		( ( unsigned short ) 128 )
//任务名字字符串长度
#define configMAX_TASK_NAME_LEN			( 10 )

#define configUSE_16_BIT_TICKS			0
//空闲任务放弃CPU使用权给其他同优先级的用户任务
#define configIDLE_SHOULD_YIELD			1
//使用互斥信号量
#define configUSE_MUTEXES				1
/* 设置可以注册的信号量和消息队列个数 */
#define configQUEUE_REGISTRY_SIZE		8
//使用递归互斥信号量 
#define configUSE_RECURSIVE_MUTEXES		1

#define configUSE_APPLICATION_TASK_TAG	0
//为1时使用计数信号量
#define configUSE_COUNTING_SEMAPHORES	1

#define configGENERATE_RUN_TIME_STATS	0

/*****************************************************************
              FreeRTOS与内存申请有关配置选项                                               
*****************************************************************/
//支持动态内存申请
#define configSUPPORT_DYNAMIC_ALLOCATION        1    
//支持静态内存
#define configSUPPORT_STATIC_ALLOCATION			0					
//系统所有总的堆大小
#define configTOTAL_HEAP_SIZE					((size_t)(36*1024))   
/***************************************************************
             FreeRTOS与钩子函数有关的配置选项                                            
**************************************************************/
/* 置1:使用空闲钩子(Idle Hook类似于回调函数);置0:忽略空闲钩子
 * 
 * 空闲任务钩子是一个函数,这个函数由用户来实现,
 * FreeRTOS规定了函数的名字和参数:void vApplicationIdleHook(void ),
 * 这个函数在每个空闲任务周期都会被调用
 * 对于已经删除的RTOS任务,空闲任务可以释放分配给它们的堆栈内存。
 * 因此必须保证空闲任务可以被CPU执行
 * 使用空闲钩子函数设置CPU进入省电模式是很常见的
 * 不可以调用会引起空闲任务阻塞的API函数
 */

#define configUSE_IDLE_HOOK				0

/* 置1:使用时间片钩子(Tick Hook);置0:忽略时间片钩子
 * 
 * 
 * 时间片钩子是一个函数,这个函数由用户来实现,
 * FreeRTOS规定了函数的名字和参数:void vApplicationTickHook(void )
 * 时间片中断可以周期性的调用
 * 函数必须非常短小,不能大量使用堆栈,
 * 不能调用以”FromISR" 或 "FROM_ISR”结尾的API函数
 */
 /*xTaskIncrementTick函数是在xPortSysTickHandler中断函数中被调用的。因此,vApplicationTickHook()函数执行的时间必须很短才行*/
#define configUSE_TICK_HOOK				0

#define configUSE_MALLOC_FAILED_HOOK	0
/*
 * 大于0时启用堆栈溢出检测功能,如果使用此功能 
 * 用户必须提供一个栈溢出钩子函数,如果使用的话
 * 此值可以为1或者2,因为有两种栈溢出检测方法 */
#define configCHECK_FOR_STACK_OVERFLOW	1
/* 置1:启用堆栈溢出检测功能;置0:忽略堆栈溢出检测功能
 * 此值可以为1或者2,因为有两种栈溢出检测方法 */
//#define configCHECK_FOR_STACK_OVERFLOW  2
/* 置1:启用 malloc 失败钩子函数;置0:忽略 malloc 失败钩子函数
 * 当动态内存申请失败时,会调用此钩子函数
 * 此值可以为1或者2,因为有两种 malloc 失败检测方法 */
//#define configUSE_MALLOC_FAILED_HOOK    1

/***********************************************************************
                FreeRTOS与软件定时器有关的配置选项      
**********************************************************************/
/* Software timer definitions. */
#define configUSE_TIMERS				1
#define configTIMER_TASK_PRIORITY		 (configMAX_PRIORITIES-1)  
#define configTIMER_QUEUE_LENGTH		10
#define configTIMER_TASK_STACK_DEPTH	( configMINIMAL_STACK_SIZE * 2 )

/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#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_vTaskSuspend                          1
#define INCLUDE_xResumeFromISR                          1
#define configUSE_TRACE_FACILITY                        1
#define configUSE_STATS_FORMATTING_FUNCTIONS            1
// 硬件适配
//#define configSYSTICK_CLOCK_HZ      configCPU_CLOCK_HZ // SysTick 时钟源
//#define configSYSTICK_PERIPHERAL    SYSTICK_CLKSOURCE_HCLK // 使用内核时钟

// 中断优先级配置(Cortex-M4)
//#define configKERNEL_INTERRUPT_PRIORITY   0xF0 // 最低优先级(FreeRTOS 系统中断)
//#define configMAX_SYSCALL_INTERRUPT_PRIORITY 0x80 // 允许调用 FreeRTOS API 的最高中断优先级

/* 放在 FreeRTOSConfig.h 中 */
#define configENABLE_FPU    1
#define configENABLE_MPU    0

/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
	/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
	#define configPRIO_BITS       		__NVIC_PRIO_BITS
#else
	#define configPRIO_BITS       		4        /* 15 priority levels */
#endif

/* 在调用 “设置优先级” 函数时可以使用的最低中断优先级. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY			0xf

/* 任何调用中断安全的 FreeRTOS API 函数的中断服务程序所能使用的最高中断优先级。
不要从优先级高于此的任何中断中调用中断安全的 FreeRTOS API 函数!(优先级越高,数值越小) */
//系统可管理的最高中断优先级
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY	5

/* 内核端口层自身使用的中断优先级。这些优先级对所有 Cortex-M 端口都是通用的,并且不依赖任何特定的库函数。. */
#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

/* Normal assert() semantics without relying on the provision of an assert.h
header file. */
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }

/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
/****************************************************************
            FreeRTOS与中断服务函数有关的配置选项                         
****************************************************************/
#define vPortSVCHandler     SVC_Handler
#define xPortPendSVHandler  PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

#endif /* FREERTOS_CONFIG_H */

这里最关键的是:

  • configCPU_CLOCK_HZ 使用 SystemCoreClock,避免频率修改后忘记同步。

  • 中断优先级宏设置必须保证 configMAX_SYSCALL_INTERRUPT_PRIORITY 不为 0、且与芯片实际优先级位数匹配,否则会导致 HardFault。


五、处理 SysTick / SVC / PendSV 中断冲突

FreeRTOS 在 Cortex‑M 端口中会接管三个系统中断:SysTickPendSVSVC

GD32 的模板工程一般在 gd32h7xx_it.c 里已经给出了默认的中断实现,需要手动处理:

  1. 打开 gd32h7xx_it.c,注释或者删掉以下函数。

  1. 注意不要再手工调用 SysTick_Config() 或在应用中重新配置 SysTick,避免与 FreeRTOS 配置冲突。


六、Keil 中编译器与 FPU 设置(重点坑点)

6.1 选择 ARM Compiler 版本与端口文件匹配

  • 使用 ARM Compiler 5(armcc) 时,必须使用 portable\RVDS\ARM_CM7\r0p1 下的 port 文件;

  • 如果改用 ARM Compiler 6(armclang),则更适合使用 portable\GCC\ARM_CM7\r0p1 下的文件。

不能出现 AC5 + GCC 端口 或 AC6 + RVDS 端口 这种混搭,否则容易出现内联汇编语法错误或 A1944E 之类的报错。

6.2 FPU 启用与宏定义

在 Keil:

  • Options for Target → Target → Floating Point Hardware 选择 Double Precision(GD32H759 支持双精度 FPU)。

某些 FreeRTOS 移植文件在开头会检查:

#ifndef __TARGET_FPU_VFP #error This port can only be used when the project options are configured to enable hardware floating point support. #endif

如果工程设置正确却仍然报错,可以在 C/C++ → Preprocessor Symbols → Define 中手工添加:

__TARGET_FPU_VFP

确保 FPU 检查顺利通过。


七、实现第一个 FreeRTOS 点灯任务(PC9)

7.1 LED GPIO 初始化

以板载 PC9 ---led1为例,main.c文件如下:

/**
 * @file main.c
 * @author lingxiao (lingxiao@qq.com)
 * @brief  FreeRTOS在GD32H7xx上的简单应用示例
 * @version 1.0
 * @date 2026-01-23
 * 
 * @copyright Copyright (c) 2026
 * 
*/


#include "gd32h7xx.h"
#include "systick.h"
#include "bsp_uart.h"
#include "FreeRTOS.h"
#include "task.h"

#define TASK_STACK_SIZE 512      // 任务栈大小,字为单位
#define TASK_PRIORITY   6        // 任务优先级,最大32
TaskHandle_t start_task_handle ; // 任务句柄
void start_task(void *pvParameters);

/*!
    \brief      enable the CPU Chache
    \param[in]  none
    \param[out] none
    \retval     none
*/
static void cache_enable(void)
{
    /* Enable I-Cache */
    SCB_EnableICache();
   
    //指令缓存(Instruction Cache,ICache):用于存储CPU最近执行的指令,提高程序执行效率。
    //数据缓存(Data Cache,DCache):用于存储CPU最近访问的数据,提高数据访问效率。

    /* Enable D-Cache */
    SCB_EnableDCache();
}


/*!
    \brief      main function
    \param[in]  none
    \param[out] none
    \retval     none
*/
int main(void)
{
    /* enable the CPU Cache */
    cache_enable();
    /*串口调试打印初始化   */
	bsp_uart1_init();
    printf("\n");
    printf("FreeRTOS run in gd32h759i demo\r\n");
	printf("hello\n");
    /* 使能GPIOC时钟 配置PC9为输出模式 */
    rcu_periph_clock_enable(RCU_GPIOC);
    gpio_mode_set(GPIOC,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_PIN_9);
    gpio_output_options_set(GPIOC,GPIO_OTYPE_PP,GPIO_OSPEED_60MHZ,GPIO_PIN_9);
    gpio_bit_reset(GPIOC,GPIO_PIN_9);
    /* 创建启动任务 */
    xTaskCreate( (TaskFunction_t) start_task,
        (char *)  "start_task", //任务名称
        ( configSTACK_DEPTH_TYPE )TASK_STACK_SIZE,  //任务栈大小
        (void *)  NULL,
        (UBaseType_t) TASK_PRIORITY,//任务优先级
        (TaskHandle_t *) &start_task_handle);       //任务句柄
        
    vTaskStartScheduler(); //启动任务调度器会自动创建一个空闲任务
    while(1)
	{   

    }
}

/**
 * @brief 启动任务
 * 
 * @param pvParameters 
*/
void start_task(void *pvParameters)
{
	
    while(1)
    {
        gpio_bit_toggle(GPIOC, GPIO_PIN_9);  // 翻转 PC9
        vTaskDelay(500);      // 延时 500ms
        printf("led1 have lignting\n");
    }
   
}
/**
 * @brief 堆栈溢出钩子函数
 * 
 * @param xTask 发生堆栈溢出的任务句柄
 * @param pcTaskName 发生堆栈溢出的任务名称
*/
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
{
    // 堆栈溢出时会到这里
    printf("Stack Overflow: %s\r\n", pcTaskName);
    taskDISABLE_INTERRUPTS();
   while(1)
   {
   }   
}

/**
 * @brief malloc 失败钩子函数
 * @param  none
 * @retval none
 */
void vApplicationMallocFailedHook(void)
{
    printf("Malloc Failed!\r\n");
    taskDISABLE_INTERRUPTS();
    while(1)
   {
   }  
}









 

如果下载后 led1闪烁,说明 FreeRTOS 已经成功在 GD32H759 上跑起来了。


八、常见问题与排坑记录

  1. A1944E: Literal pool entries cannot be generated in execute-only sections

    • 原因:使用 AC6 编译器配 RVDS 端口文件或开启了执行保护(XOM)。

    • 解决:保持 AC5 + RVDS 或 AC6 + GCC 的正确组合,并关闭执行‑only 段。

  2. 端口文件语法错误(portmacro.h 内联汇编 expected ')')

    • 原因:用 AC5 编译 GCC 版本的 portmacro.h。

    • 解决:确认 port.c/portmacro.h 路径来自 RVDS\ARM_CM7\r0p1

  3. 程序运行但 FreeRTOS 任务不执行,LED 不闪

    • 检查 SysTick 中断是否被自己写的 SysTick_Handler 抢走;

    • 确认没有重复配置 SysTick;

    • 检查 configCPU_CLOCK_HZSystemCoreClock 是否一致。

  4. 在任务里加 printf 后串口卡死

    • 先增大任务栈并打开栈溢出钩子;

    • 检查 UART 发送函数是否在临界区或禁止中断状态下阻塞太久。


九、总结与后续扩展

通过以上步骤,可以在 GD32H759 上稳定跑起 FreeRTOS 内核,实现基本任务调度和  点灯示例。移植完成后,接下来可以逐步引入:

  • 任务间消息队列、二值/计数信号量。

  • 事件组、软件定时器。

  • 中断服务函数里使用 “FromISR” 版本的 FreeRTOS API,实现外设事件驱动。

  • 更复杂的多任务框架(如串口命令行、以太网协议栈等)。

在实际项目中,建议从“一个主任务 + 若干简单任务”起步,逐步引入复杂模块,同时配合串口日志和栈/堆监控,减少调试难度。


Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐