手头正好在调一块GD32H759I-EVAL,又要跑RT-Thread,还要用Keil5做日常开发。折腾了一圈之后,把从RT-Thread官方库拉到Keil5能编译、能下载、能跑通的完整流程和踩过的坑都整理出来了。这板子本身挺能打,Cortex-M7内核,主频拉到600MHz,外设也全,但真要把它当主力开发平台,BSP那块还是有不少细节要处理,尤其是官方BSP默认用GCC/SCons构建,而国内不少工程师又习惯用MDK,这个转换过程就是本篇文章要聊的重点。
如果你是第一次接触GD32H7系列,或者对RT-Thread的BSP结构不熟悉,这篇文章建议从头看;如果你已经能把官方BSP跑起来,只是想在Keil5里复现一遍,可以直接跳到第3、4节,里面有工程生成、关键配置和烧录验证的完整操作。我的目标是让你照着走一遍,就能从零得到一个能在Keil5里单步调试的RT-Thread工程。
1. 项目概述与方案选型思路
1.1 GD32H759I-EVAL开发板到底强在哪
GD32H759I-EVAL是兆易创新GD32H7系列里的旗舰评估板,核心芯片GD32H759,ARM Cortex-M7内核,最高主频600MHz,自带硬件浮点单元,还支持DSP指令集。存储方面,内置Flash容量和SRAM容量在同级MCU里相当能打,这一点对跑RTOS、做GUI、挂网络协议栈都很重要。
板载资源也够全,LCD接口、以太网、USB、多路UART、CAN、SDIO、摄像头接口等基本都有,而且板子上集成了调试器,一根USB线就能供电加下载调试,不用额外买J-Link。我把这块板子当作一个“移动实验室”,大部分外设评估和驱动验证都能在这块板上完成。
1.2 RT-Thread官方BSP的工程结构
RT-Thread官方仓库的BSP目录里,已经包含了gd32h759i-eval这套板级支持包,路径在bsp/gd32/arm/gd32h759i-eval下。里面有applications(应用层入口)、board(板级初始化)、drivers(外设驱动)等目录,还有Kconfig、SConscript、rtconfig.h这些构建相关文件。
这套BSP本质上是一个“半成品”工程,提供了最小的内核启动流程和板级初始化代码,但默认的构建链是基于SCons的,生成的工程文件优先面向GCC工具链。如果你用env工具在BSP目录下执行编译命令,它会调用arm-none-eabi-gcc来构建固件,整个过程和Keil5没有直接关系。
1.3 官方BSP能用为什么要再折腾一遍
很多人会问,官方BSP都已经写好了,直接编译下载不就行了吗?问题出在团队协作和调试习惯上。GCC工具链的命令行方式对一部分工程师来说不够直观,Keil5的MDK界面、断点调试、变量监视窗口,在嵌入式老手的日常开发中仍然不可替代。另外,有些外设调试、Flash烧录、性能分析插件只能在Keil5里跑,单纯用命令行工具链会很别扭。
所以我选择了“官方BSP为基底,SCons生成MDK5工程,Keil5做日常调试”的路线。这样既能吃到RT-Thread官方维护的驱动代码,又能保留Keil5的开发体验,算是一个折中且务实的方案。这个过程中遇到的问题和解决思路,就是这篇文章的干货所在。
2. 开工前的环境准备
2.1 Keil5 MDK安装与GD32芯片支持包
Keil5本身是个大框架,要支持GD32H759,必须安装对应的Device Pack。打开Keil5的Pack Installer,在搜索框输入GD32H7,找到GigaDevice相关的系列包安装即可。如果网络不行,也可以去GD官网手动下载.pack文件,再双击导入。
安装完成后,新建工程时Device选择GD32H759I系列,编译器配置里会识别到对应的芯片型号。这一步一定要注意Pack版本,我用过一版旧Pack,SVD文件不全,调试时外设寄存器显示直接乱掉,后来换了新版本才正常。
XLINK等组件如果缺失,编译大型工程时可能会报莫名错误,建议安装MDK时直接全选组件,反正占空间也不大。装完以后,建议检查一下C:\Keil_v5\ARM\PACK\GigaDevice目录,确认芯片包确实被放入。
2.2 RT-Thread构建环境:scons与env工具
RT-Thread官方推荐的构建工具是env,它把Python、SCons、GCC工具链都打包进了一个命令行环境。你可以从RT-Thread官网下载env工具压缩包,解压后运行env.exe,它会自动配置好相关环境变量。
如果不想用env,也可以自己装Python,然后pip install scons,再配好环境变量。scons就是RT-Thread的构建引擎,通过读取工程目录下的SConscript和SConstruct文件,把源码列表、头文件路径、编译选项拼装成不同IDE的工程文件。
我自己用的是env方案,因为它内置了menuconfig的依赖,执行图形化配置时不需要额外装kconfiglib,方便很多。
2.3 源码获取与BSP目录确认
从GitHub克隆RT-Thread仓库,或者下载官方release包。我推荐拉release分支,代码相对稳定,避免master分支上偶发的构建问题。以v5.0.0为例,解压后进入bsp/gd32/arm/gd32h759i-eval目录,先看一下目录结构。
这里有个小建议,整个RT-Thread目录路径不要带中文,不要带空格,盘符也最好在根目录下。Windows下的长路径和中文文件头有时会让SCons的路径处理出幺蛾子,SSD时代没必要给自己添堵。
确认一下仓库里的README.md,官方会写明这个BSP的编译说明、默认外设配置、串口映射等关键信息。不同版本的BSP可能因为内核API变化而行为不同,所以固定版本号也是一件值得养成习惯的事。
3. 核心流程:从官方BSP生成Keil5工程
3.1 menuconfig裁剪与rtconfig.h生成
在BSP目录下打开env命令行,先执行scons --menuconfig,进入类似Linux内核配置的界面。这里可以打开或关闭RT-Thread的组件,比如内核、FinSH、设备驱动框架、文件系统等。我要跑一个基础系统,就默认配置再加大一点线程栈即可。
菜单配置退出时,SCons会根据你的勾选项更新根目录下的rtconfig.h,这是整个RT-Thread的内核配置文件,后续所有源文件都会依据它来决定编译哪些功能模块。新手容易忽略的一点是:rtconfig.h里的宏和你的实际需求必须匹配,否则你开了某个驱动但没开对应的设备框架,编译能过,运行却可能直接断言失败。
配置完以后,直接执行scons --target=mdk5。这条命令会扫描当前BSP目录下的所有SConscript文件,把源码路径、头文件路径、宏定义、链接脚本等全部收集起来,生成一个MDK5工程文件,通常是project.uvprojx。
3.2 用scons生成mdk5工程的前置检查
--target=mdk5依赖一个叫armcc的配置检测,其实它只是生成一个Keil能打开的模板,真正的编译还需要Keil里的RVCT编译器或AC5/AC6编译器配合。在生成工程前,最好先确认rtconfig.py里指定的工具链前缀是arm-none-eabi-,这个参数会在后续生成的工程文件中体现为编译器路径。
如果生成的project.uvprojx有问题,常见反正是Keil版本差异导致XML节点不完全兼容。这时候可以直接在Keil里手动添加源码目录,或者在命令行里先试一次pkgs --update,把在线软件包下载完整,再重新生成工程。
要注意,SCons生成的工程会默认把applications目录下所有.c文件加进来,也会把board和drivers下的源码自动挂载。如果你自己建了新目录,必须在SConscript里显示声明,否则SCons不会自动帮你加进去,Keil工程里也不会出现这些文件。
3.3 Keil5工程里的关键配置项
用Keil5打开生成的.uvprojx工程后,第一件事不是急着编译,而是检查几个关键设置。
第一是Device型号,要确保选择的是GD32H759I系列的具体型号,而不是其他GD32型号。型号选不对,启动文件和链接脚本可能会错。
第二是编译器的选择。GD32H7的外设库和RT-Thread源码对AC5和AC6的兼容性都还行,但如果你用了某些老旧的第三方库,建议用AC5兼容模式。AC6编译速度更快、C99/C11支持更好,但偶尔会有底层汇编或者内联汇编的兼容性问题。
第三是浮点单元设置。GD32H759的Cortex-M7支持双精度浮点,Keil里Target选项卡的Floating Point Hardware建议选Double Precision。如果FPU没开对,浮点运算虽然能编译过,但运行起来可能性能拉胯甚至直接HardFault。
第四是宏定义。RT-Thread生成的工程会自带一些宏,比如USE_STDPERIPH_DRIVER、GD32H7XX等,不要手动删除,如果提示找不到某些头文件,优先检查宏定义是否完整。
第五是链接脚本。工程里的分散加载文件通常由BSP提供,一般叫linker_scripts相关内容。如果RAM、Flash配置和芯片实际容量不匹配,烧录后可能出现启动异常,每次在Debug下跳转都跳到HardFault_Handler。
4. 编译、下载与最小系统验证
4.1 首次编译的完整流程
在Keil5里点了Build按钮后,首次编译会比想象中慢,因为要编几百个源文件。我这里实测,工程默认配置完整编译大概要一分钟左右,如果开了编译器优化和所有组件,时间会更长。
如果中间报错,优先看是不是头文件路径问题。SCons生成的工程一般会带相对路径,但也可能因为目录层级变化出现file not found。解决办法是去Options for Target的C/C++选项卡里,把Include Paths手动补一下,指向rt-thread/include、bsp/gd32/arm/gd32h759i-eval、GD32标准库根目录等。
编译通过后,会生成一个.axf文件。如果你用AC5,还会看到.hex和.bin,但如果没勾选生成输出的选项,只会有.axf。可以在Output选项卡里勾选Create HEX File,方便后续独立烧录。
4.2 烧录配置与调试器选择
GD32H759I-EVAL板载调试器,插上USB后Windows会识别为一个串口和一个调试接口。在Keil5的Options for Target -> Debug里,选择CMSIS-DAP Debugger,并在Settings里确认能读到目标芯片IDCODE。
如果Keil提示找不到芯片或无法连接,先检查USB是否被其他程序占用,或者板子上有没有跳线把调试接口禁用。然后把下载速度调低一点,我习惯从1MHz开始试,能连上再逐步提高。
烧录前还有一个关键点:Flash下载算法。如果烧录时报No Algorithm found或者Error: Flash Download failed,就需要在Utilities -> Settings -> Flash Download里勾选正确的编程算法。GD32H759对应的FLM文件一般由GD芯片Pack提供,如果没有出现,手动添加Pack目录下的.flm路径即可。
4.3 串口验证RT-Thread启动流程
系统烧录完成后,打开串口助手,波特率115200,数据位8,停止位1,无校验。按下复位键之后,串口应该会输出RT-Thread的启动Logo信息,包括内核版本、编译时间、内存使用情况等。
如果串口完全没输出,先检查调试串口号和板子的跳线设置,确认是不是接到了非调试串口。如果输出了乱码,大概率是波特率对不上或者系统时钟配置和调试串口外设时钟不一致,这种问题我后面会详细说。
看到RT-Thread启动Logo后,按回车应该能进入FinSH控制台,输入help能看到支持的命令列表。到这一步,最核心的移植验证已经完成,说明内核、中断、时钟、串口驱动都跑通了。
5. 避坑指南:实战问题与排查思路
5.1 启动与时钟类问题
我最初移植时遇到的第一个坑,就是系统启动后串口乱码。原因是BSP默认的外部晶振频率和我实际板子的晶振不一致。RT-Thread的board.c里一般用一个宏来定义外部高速晶振频率,像是BOARD_EXT_CRYSTAL_HZ之类,如果这个值和板子实际焊接的晶振不匹配,PLL倍频出来的系统主频就会偏离,串口波特率自然就不准。
排查方法是先看启动Logger或者调试器里读出的SystemCoreClock是不是等于600MHz,如果不等于,就修改对应的晶振定义值。
再有就是某些情况下程序卡在启动文件的SystemInit里,原因可能是外部晶振未稳定。这时候不要急着改软件,先确认板上晶振起振正常,用示波器看波形,或者用调试器单步看状态寄存器的锁相环锁定标志。
5.2 内存、链接脚本与运行异常
RT-Thread的堆栈是由链接脚本和启动代码共同决定的。如果出现“线程栈溢出”的断言,除了在创建线程时加大栈空间,也要检查编译器的栈大小设置。Keil里默认的Stack Size太小的话,早期的系统初始化就容易爆栈。
链接脚本里Flash和RAM的划分必须和芯片型号一致。GD32H759内置Flash和RAM有具体容量,如果你用的是别的变种型号,却沿用同一套链接脚本,烧录会有问题。这种问题最隐蔽,因为编译不报错,运行起来却随机死机。
我在调试时遇到过一种情况:程序下载后全速跑没问题,一旦在Keil里单步调试,过一会儿就HardFault。后来发现是JTAG/SWD调试时会话和低功耗模式冲突,在__WFI指令处停下再唤醒就容易出问题,解决办法是关掉低功耗相关配置,或者调试时禁用Tickless模式。
5.3 编译错误与工具链冲突速查表
下面这张表是我从几次踩坑里整理出来的,覆盖了最常见的一些报错场景和对应处理方式。看到报错后不要慌,对着表先排查。
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| 找不到gd32h7xx.h | 头文件路径缺失或者芯片包未安装 | 检查Pack安装,检查C/C++ Include Paths |
| 编译器不识别某个内联汇编 | 使用了AC6但代码是AC5风格 | 切回AC5,或改写为__ASM/CMSIS版本 |
| 烧录时No Algorithm found | 没有正确的Flash下载算法 | 在Flash Download里添加正确的FLM |
| 链接时Section overflow | RAM/Flash超限或链接脚本容量不对 | 检查芯片型号选择,看MAP文件定位超限段 |
| HardFault出现在RTOS启动后 | 栈溢出/MPU配置/中断优先级问题 | 排查线程栈,检查NVIC优先级分组 |
| 串口输出乱码 | 晶振或波特率配置不匹配 | 检查时钟树、SystemCoreClock和串口配置 |
| 编译超慢 | 开了高优化以及全量编译 | 增量编译,关闭Browser Info |
| 变量无法实时查看 | 优化级别太高 | 把目标文件对应优化级别调低为-O0 |
| Cannot access memory | 目标板未复位或者调试器连接不稳 | 降低下载速度,手动复位后再调试 |
| 找不到core_cm7.h | MDK版本过低或Pack缺失 | 安装较新的Cortex-M7支持包 |
这张表其实就是一个排查索引,真正动手时还要结合自己的代码逻辑来看。遇到问题多利用Keil的寄存器窗口、Call Stack窗口和RT-Thread的FinSH命令,这些配合起来定位速度快很多。
6. 实操心得与后续扩展建议
6.1 我习惯的移植工作流
经过这几个来回,我总结了一套固定的移植工作流。第一步固定RT-Thread源码版本,不要频繁切换主干。第二步在env环境下用menuconfig明确关闭不需要的组件,能显著减少编译时间。第三步生成mdk5工程后,统一检查Device、FPU、Linker、调试器四项基础配置,避免后面反复折腾。第四步才是开始烧录验证。
这套流程的好处是每一步都有清晰的验证点和回滚点,出问题时能快速判断是哪个环节引起的。比如编译报错,基本锁定在工具链和头文件路径;跑起来没输出,锁定在时钟和串口配置;跑一会儿死机,锁定在内存和栈配置。
6.2 后续还能折腾什么方向
如果你也用的是GD32H759I-EVAL,建议下一步试试以太网和USB,板载接口都齐全,RT-Thread也有相应的驱动框架,把这些外设跑通后,整个板子的实用性会上一个台阶。再往后可以挂文件系统、接小屏幕跑GUI,Cortex-M7的性能带这些绰绰有余。
我个人目前的方向是继续把一些自研协议栈和低功耗功能做进去,GD32H7的算力优势很适合这种复合型负载。这次从官方库到Keil5的移植只是起步,后面还有大量设计空间可以挖。