
1. 初识GD32VF103一块值得把玩的RISC-V入门板去年九月份RT-Thread社区的Andy Chen牵头定制了一批GD32V开发板我有幸搭车入手了一块。板子到手第一感觉是“精致”设计非常紧凑一个Type-C口就集成了供电、程序烧录和调试串口打印三大功能对于喜欢桌面整洁、讨厌线缆缠绕的开发者来说这简直是福音。但最吸引我的还是板载的主控芯片——GD32VF103。这是一颗采用RISC-V内核设计的微控制器MCU对于一直想亲手摸摸RISC-V硬件但又苦于找不到合适低成本入门平台的我来说这块板子来得正是时候。GD32VF103系列是兆易创新GigaDevice与芯来科技Nuclei合作的产物瞄准的是物联网IoT市场。它的核心是芯来科技设计的“Bumblebee” RISC-V处理器内核主频最高能达到108MHz。根据型号不同片内Flash从16KB到128KBSRAM从8KB到32KB不等。我们这批定制板选用了顶配的VF103VBT6Flash和SRAM都是最大容量分别是128KB和32KB在学习和开发初期完全不用担心资源紧张可以更专注于RISC-V架构和RTOS本身的学习。更难得的是兆易创新开放了完整的用户手册足足500多页硬件外设的寄存器描述、操作流程写得非常详细。而芯来科技也提供了Bumblebee内核的指令架构手册详细阐述了其实现的RISC-V指令集、特权模式、中断异常机制等。这两份文档一份讲“身体”外设一份讲“大脑”内核结合起来为深入理解这颗芯片提供了绝佳的资料。所以无论你是想学习RISC-V指令集还是想基于一个真实的RISC-V MCU进行RT-Thread或嵌入式应用开发GD32VF103这块板子都是一个非常合适的起点。2. 开发环境搭建从零开始配置RISC-V编译链拿到开发板配套的SDK里已经有一个基于RT-Thread Nano的工程可以直接编译烧录。但如果你和我一样喜欢折腾“主线”mainline代码想用上最新的特性和修复那么就需要自己搭建一套编译环境。这个过程也是熟悉RISC-V开发工具链的好机会。2.1 获取RT-Thread ENV工具与源码RT-Thread推荐使用其官方的ENV工具来管理项目和进行编译它集成了scons构建系统、menuconfig配置工具和包管理器能极大提升开发效率。首先我们需要去RT-Thread官网下载ENV工具包。解压到任意英文路径下切记路径不要有中文运行里面的env.exe即可启动一个配置好的命令行终端。我习惯把它固定到任务栏方便随时打开。接下来获取最新的RT-Thread源码。RT-Thread的代码托管在GitHub上使用git克隆下来即可git clone https://github.com/RT-Thread/rt-thread.git克隆完成后我们进入bsp/gd32vf103v-eval目录。注意RT-Thread主线代码默认支持的是兆易官方的GD32VF103V-EVAL评估板。我们手上的定制板虽然主控相同但在一些细节上比如时钟源有差异这需要我们稍后调整。2.2 安装与配置RISC-V GCC工具链这是最关键的一步。ENV工具默认只携带了ARM GCC工具链我们需要手动添加RISC-V的工具链。RISC-V官方的GCC工具链可以在其GitHub仓库找到。我选择的是xpack项目预编译好的版本对于Windows用户来说开箱即用省去了自己编译的麻烦。下载完成后将其解压到ENV工具目录下的tools/gnu_gcc/risc-v/文件夹中。解压后你应该能看到bin目录里面包含riscv-none-embed-gcc.exe等可执行文件。为了让ENV命令行能识别到新的工具链需要修改环境变量。具体方法是编辑ENV安装目录下tools/ConEmu/ConEmu/CmdInit.cmd这个文件。找到设置RTT_EXEC_PATH环境变量的地方将其值从原来的ARM GCC路径改为你刚刚解压的RISC-V GCC工具链的bin目录绝对路径。注意这个方法是我通过分析ENV启动脚本摸索出来的可能不是“官方推荐”的修改方式但它确实有效。RT-Thread的Env用户手册中关于添加新工具链的说明并不显眼社区里也有不少开发者遇到类似问题。如果你有更优雅的配置方法比如通过ENV自带的包管理或设置脚本欢迎交流。不过这种“直接修改”的方式虽然粗暴但对于快速验证和开始开发来说是最直接的。完成修改后重新启动ENV命令行输入riscv-none-embed-gcc -v如果能看到版本信息说明工具链配置成功。3. 代码适配让RT-Thread主线跑在我们的板子上环境搭好了直接编译bsp/gd32vf103v-eval下的代码可能会失败或者烧录后无法运行。这是因为我们的定制板和官方的EVAL板存在硬件差异最主要的一点就是时钟源。3.1 时钟配置的调整官方EVAL板焊接了外部高速晶振HXTAL而我们的定制板为了成本和尺寸通常只使用芯片内部的8MHz RC振荡器IRC8M。RC振荡器的精度通常±1%远不如外部晶振但对于不依赖高精度时钟的外设如UART、SPI、PWM以及不使用以太网功能的场景完全足够。我们需要修改板级支持包BSP中的时钟初始化代码。具体位置在bsp/gd32vf103v-eval/board/board.c文件中找到系统时钟配置相关的部分。通常代码里会有类似#define __SYSTEM_CLOCK_108M_PLL_HXTAL和#define __SYSTEM_CLOCK_108M_PLL_IRC8M的宏定义。我们需要注释掉或确保未定义使用外部晶振的宏并启用使用内部RC振荡器的宏。// 使用内部8M RC振荡器通过PLL倍频到108MHz #define __SYSTEM_CLOCK_108M_PLL_IRC8M (uint32_t)(108000000) // 注释掉外部晶振的配置 // #define __SYSTEM_CLOCK_108M_PLL_HXTAL (uint32_t)(108000000)然后在system_gd32vf103.c文件中的system_clock_108m_irc8m()函数会被调用完成从8MHz倍频到108MHz的配置。这一步至关重要如果时钟源配置错误整个系统的心跳就不准轻则串口乱码重则根本无法启动。3.2 添加一个简单的LED流水灯应用验证系统是否跑起来最直观的就是点个灯。我们的板子上有三个LED分别连接在GPIOE的PIN3、PIN4和PIN5上。我们可以在应用程序main.c中编写一个简单的流水灯程序。首先需要初始化对应GPIO的时钟和引脚模式。GD32的库函数设计得很清晰#include “gd32vf103.h” #include rtthread.h int main(void) { // 打印启动信息包含编译时间便于确认烧录的是新程序 rt_kprintf(“Hello GD32VF103VBT6! build %s %sn”, __DATE__, __TIME__); // 1. 使能GPIOE端口时钟 rcu_periph_clock_enable(RCU_GPIOE); // 2. 初始化PE3, PE4, PE5为推挽输出模式低速2MHz即可 gpio_init(GPIOE, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, GPIO_PIN_3); gpio_init(GPIOE, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, GPIO_PIN_4); gpio_init(GPIOE, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, GPIO_PIN_5); // 3. 流水灯逻辑 while (1) { // 依次点亮LED假设低电平点亮 gpio_bit_reset(GPIOE, GPIO_PIN_3); // PE3输出低电平LED亮 rt_thread_mdelay(300); // 延时300毫秒 gpio_bit_reset(GPIOE, GPIO_PIN_4); rt_thread_mdelay(300); gpio_bit_reset(GPIOE, GPIO_PIN_5); rt_thread_mdelay(300); // 依次熄灭LED gpio_bit_set(GPIOE, GPIO_PIN_3); // PE3输出高电平LED灭 rt_thread_mdelay(30); gpio_bit_set(GPIOE, GPIO_PIN_4); rt_thread_mdelay(30); gpio_bit_set(GPIOE, GPIO_PIN_5); rt_thread_mdelay(30); } }这段代码先点亮每个LED并保持300ms然后快速30ms依次熄灭形成一种“追逐”的效果。rt_thread_mdelay()是RT-Thread提供的毫秒级延时函数它会使当前线程睡眠从而让出CPU给其他任务在单线程的main函数里虽然效果不明显但这是一个好的编程习惯。实操心得在修改BSP代码时尤其是时钟、内存初始化等底层代码务必谨慎。建议先备份原文件。每次只做一处修改编译测试通过后再进行下一处。这样一旦出现问题可以快速定位。对于GD32这类有完整库函数的MCU多查阅其标准外设库SPL的例程能帮你快速理解外设的使用方法。4. 编译、烧录与上电调试代码适配完成后就可以进行编译和烧录了。4.1 使用SCons进行编译在ENV命令行中切换到bsp/gd32vf103v-eval目录下直接输入scons命令即可开始编译。SCons会自动调用我们配置好的RISC-V GCC工具链。编译成功后会在当前目录下生成rtthread.bin、rtthread.elf等文件。其中.bin文件是纯二进制镜像用于烧录。如果编译出错常见原因有工具链路径错误重新检查CmdInit.cmd中的RTT_EXEC_PATH设置确保指向正确的bin目录。头文件或库文件缺失检查rtconfig.py和SConscript文件中的编译选项和链接路径是否正确包含了GD32VF1xx的标准外设库。代码语法错误仔细阅读编译器的报错信息通常能准确定位到行号和问题。4.2 通过串口ISP烧录固件GD32VF103支持通过串口进行ISP在系统编程烧录这也是我们板子Type-C口的另一个重要功能。需要使用兆易创新提供的GigaDevice MCU ISP Programmer软件。烧录步骤如下连接开发板的Type-C口到电脑。打开设备管理器确认开发板对应的串口号例如COM5。重要关闭任何可能占用该串口的终端软件如Putty、Xshell等。按住开发板上的BOOT0按键不放再短暂按下RESET按键然后松开RESET最后松开BOOT0。此时芯片会从系统存储器启动进入串口烧录模式。打开ISP编程软件选择正确的串口号将波特率设置为256000。这个波特率是GD32 ISP协议固定的不能设错。点击“连接”如果成功软件会显示芯片信息和Flash容量。选择编译生成的rtthread.bin文件设置好烧录地址通常是0x08000000点击“编程”即可。烧录完成后再次按下RESET键芯片就会从用户Flash启动运行我们刚烧进去的RT-Thread系统。4.3 串口调试与信息查看系统启动后RT-Thread会通过同一个串口打印内核启动信息。这时需要打开一个串口终端软件如Putty、SecureCRT或MobaXterm。关键细节烧录波特率256000和运行时的调试串口波特率115200是不同的这是最容易混淆的地方。烧录时是芯片内置的Bootloader在通讯它固定使用256000波特率。烧录完成后运行的是我们编写的应用程序我们在代码中通常是rt_hw_board_init函数里将串口初始化为115200波特率。所以在终端软件中需要将波特率设置为115200数据位8停止位1无校验位。连接成功后复位板子你应该能在终端里看到类似以下的输出\ | / - RT - Thread Operating System / | \ 4.0.2 build Jun 12 2023 2006 - 2021 Copyright by rt-thread team Hello GD32VF103VBT6! build Jun 12 2023 14:30:00 msh /同时板载的三个LED开始循环闪烁。看到“msh /”提示符说明RT-Thread的FinSH控制台也成功启动了你可以在这里输入list_device查看设备free查看内存使用情况等命令。5. 深入探索RT-Thread在RISC-V上的特性与优化让系统跑起来只是第一步。基于RISC-V架构运行RT-Thread有一些独特的地方值得深入探究。5.1 RISC-V的中断处理与上下文切换与ARM Cortex-M系列使用NVIC嵌套向量中断控制器不同GD32VF103的Bumblebee内核遵循RISC-V的特权架构标准其中断控制器ECLIC和异常处理机制有其特点。RT-Thread为了适配它在libcpu/risc-v/nuclei目录下实现了专门的移植层。中断入口在context_gcc.S等汇编文件中定义了trap_entry等异常向量入口。当中断发生时硬件会自动跳转到此处保存当前线程的上下文寄存器到栈中然后调用C语言写的通用中断处理函数。上下文切换这是RTOS多任务的核心。在context_gcc.S中rt_hw_context_switch_to和rt_hw_context_switch函数负责实现线程的切换。它们会保存当前线程的栈指针sp和寄存器到线程控制块TCB中然后从下一个线程的TCB中恢复上下文。RISC-V的通用寄存器较多32个保存和恢复的过程比ARM Cortex-M稍显复杂但原理相通。ECLIC配置GD32的标准外设库提供了ECLIC的驱动函数如eclic_global_interrupt_enable()、eclic_priority_group_set()等。在RT-Thread的BSP中需要在board.c的rt_hw_board_init()函数中初始化ECLIC并设置中断优先级分组。理解这部分代码对于调试复杂的中断问题、优化任务切换性能甚至为其他RISC-V芯片移植RT-Thread都至关重要。5.2 内存管理与堆栈设置在串口启动信息中你可能会看到一个关于堆栈的warning。这通常是因为某个线程的堆栈大小设置得不够合理。在RT-Thread中每个线程都有自己的栈空间用于存储局部变量、函数调用上下文等。修改堆栈大小线程栈大小在创建线程时指定。对于主线程main函数其栈大小在rtconfig.h中由RT_MAIN_THREAD_STACK_SIZE宏定义。如果发现程序运行不稳定或出现硬件错误可以尝试适当增大这个值例如从1024增加到2048。Heap管理RT-Thread默认使用小内存管理算法SLAB。系统启动时会从SRAM中划出一块区域作为堆heap。堆的大小在board.c的rt_system_heap_init()函数中指定参数是起始地址和结束地址。对于GD32VF103VBT6我们有32KB SRAM需要合理划分给栈全局变量、线程栈和堆。通常将未使用的内存尾部区域全部作为堆是一个简单有效的策略。排查技巧如果程序运行出现非预期的复位或HardFault在RISC-V中可能是非法指令异常或访问错误异常首先检查堆栈是否溢出。可以使用RT-Thread的ps命令查看各线程的栈使用率或者通过free命令观察堆内存的剩余情况。在调试阶段可以故意将线程栈设置得大一些排除因栈空间不足导致的问题。5.3 外设驱动与PIN设备框架RT-Thread提供了丰富的设备驱动框架如PINGPIO、UART、I2C、SPI等。对于GD32VF103RT-Thread主线已经提供了PIN设备和UART设备的驱动框架支持。使用PIN设备操作LED之前我们直接调用GD32的库函数操作GPIO这是一种“裸机”方式。更RT-Thread的方式是使用PIN设备框架。首先需要在rtconfig.h中打开RT_USING_PIN宏定义。然后在board.c的rt_hw_board_init()函数中调用rt_hw_pin_init()。之后就可以在应用层使用rt_pin_mode()、rt_pin_write()等标准API来控制LED了。这样做的好处是代码与硬件解耦将来更换MCU或板子时应用层代码几乎不用修改。串口控制台RT-Thread的FinSH控制台默认绑定到一个串口设备。在GD32VF103的BSP中通常使用USART0。驱动已经实现我们只需要确保在rtconfig.h中正确配置了RT_CONSOLE_DEVICE_NAME为对应的串口设备名如”uart0”。6. 进阶玩法使用RT-Thread Studio进行图形化开发对于习惯集成开发环境IDE的开发者使用ENV命令行可能稍显繁琐。RT-Thread Studio是基于Eclipse的官方IDE它集成了芯片支持包BSP、图形化配置、代码编辑、构建和调试功能对新手更为友好。创建项目在RT-Thread Studio中选择“基于开发板创建项目”然后搜索或选择“GD32VF103V-EVAL”。Studio会自动下载对应的BSP和工具链。图形化配置通过RT-Thread Settings视图可以直观地开启或关闭内核组件、软件包、硬件驱动等。例如勾选“PIN设备驱动”、“UART设备驱动”配置FinSH控制台使用的串口号等。所有配置会自动同步到rtconfig.h和SConscript文件。编写代码与编译在applications目录下编写你的main.c。点击编译按钮Studio会自动调用底层的scons和工具链完成构建。调试这是Studio的一大优势。配合J-Link等调试器注意我们的定制板需要修改才能连接JTAG可以进行单步调试、查看变量、设置断点这对于深入理解RT-Thread内核运行机制和排查复杂Bug非常有帮助。个人体会ENV和Studio各有优劣。ENV更轻量、灵活适合深度定制和脚本化构建也便于理解RT-Thread的构建体系。Studio则提供了“一站式”体验特别是其图形化配置和调试功能能显著降低入门门槛提升开发效率。建议初学者可以从Studio入手快速搭建项目并运行起来当需要更精细的控制或为新芯片移植时再深入研究ENV和底层构建脚本。7. 常见问题与故障排查实录在实际操作中你可能会遇到以下问题。这里记录了我踩过的坑和解决方法。问题现象可能原因排查步骤与解决方案编译失败提示找不到riscv-none-embed-gcc1. 工具链路径未正确设置。2. ENV环境变量未更新。1. 检查CmdInit.cmd中RTT_EXEC_PATH的路径确保指向工具链的bin目录。2. 关闭所有ENV命令行窗口重新启动ENV。烧录时ISP软件无法连接1. 串口被其他软件占用。2. BOOT0进入ISP模式的操作不对。3. 波特率设置错误。4. 驱动问题。1.务必关闭串口终端软件。2. 严格按照“先按住BOOT0再按/放RESET最后放BOOT0”的顺序操作。3. 确认ISP软件波特率为256000。4. 检查设备管理器中串口驱动是否正常如CH340、CP2102等。串口终端无输出或乱码1. 终端波特率设置错误。2. 系统时钟配置错误如误用外部晶振。3. 串口引脚复用错误。1. 确认终端波特率为115200。2.重点检查board.c中的时钟配置宏确保启用的是IRC8M。3. 核对原理图确认调试串口使用的引脚通常是PA9/PA10并在代码中检查其初始化。程序运行不稳定偶尔死机或复位1. 堆栈溢出。2. 中断冲突或优先级设置不当。3. 电源不稳定。1. 增大RT_MAIN_THREAD_STACK_SIZE或使用ps命令检查线程栈使用率。2. 检查是否有中断服务程序ISR执行时间过长或未清除中断标志。合理配置ECLIC中断优先级。3. 确保Type-C线缆连接可靠供电充足。开发板在电流较大时USB供电可能不足可尝试外接电源。LED不闪烁1. GPIO初始化代码错误或未执行。2. LED硬件连接是共阳还是共阴搞反。3. main函数未进入或卡死。1. 检查rcu_periph_clock_enable(RCU_GPIOE)是否被调用。2. 查看原理图确认LED是低电平点亮还是高电平点亮调整gpio_bit_reset/set。3. 在while(1)循环前加一句rt_kprintf确认程序执行到了这里。最后关于那块需要“小修改”才能使用JTAG的瑕疵原理图上通常标注了需要焊接或断开某个电阻。如果你需要进行源码级单步调试这是必要的步骤。不过对于大部分应用开发串口打印日志结合ISP烧录已经足够高效。整个折腾下来从环境搭建到代码运行你收获的不仅仅是一个闪烁的LED更是一套在RISC-V架构上开展嵌入式RTOS开发的完整方法论。这颗GD32VF103就像一把钥匙帮你打开了RISC-V世界的大门。