资讯动态

GD32E10x官方例程包实战指南:从STM32迁移到外设调试避坑

发布时间:2026/10/14 18:34:47 来源:尧图企业网站定制
简介GD32E10x系列官方例程是兆易创新面向嵌入式开发者提供的示例代码集基于ARM Cortex-M3内核主频可达72MHz适用于工业控制、消费电子、通信设备等场景适合已具备单片机基础、希望快速上手国产MCU的工程师与高校学生。压缩包共935个文件约11.16MB以288个h头文件与261个c源文件为核心配套37套uvproj/uvopt工程、36套IAR工程文件以及若干启动汇编、链接脚本、Makefile与说明文档覆盖GPIO、SPI、I2C、UART、ADC、DAC、定时器、CAN等外设驱动与中断处理示例。已有873人学习下载。通过阅读这些初始化代码、驱动与应用程序读者可掌握系统时钟配置、外设初始化、中断服务及LED闪烁、串口通信、ADC采样等典型功能的实现思路并借助Keil MDK或IAR环境直接编译调试缩短项目开发周期提升嵌入式系统开发能力。1. 从一份 GD32E10x 官方例程包说起它到底能帮你省下多少事如果你手头正捏着一块 GD32E10x 系列的板子却对着空白的工程模板发愁那这份官方例程包大概率就是你最该先翻的东西。GD32E10x 是兆易创新基于 ARM Cortex-M3 内核做的通用 MCU主频能跑到 120MHz外设覆盖 GPIO、USART、SPI、I2C、ADC、DAC、定时器、CAN、USB 这一整套。官方例程包本质上就是把这些外设的驱动代码、初始化流程、中断处理、时钟配置全部拆成一个个独立可编译的工程每个工程只干一件事比如“用定时器输出 PWM”“用 USART 收发数据”“用 ADC 采电压”。对刚从 STM32 转过来的嵌入式硬件工程师来说这套东西最大的价值不是代码写得多漂亮而是它把 GD32 和 STM32 之间那些“看着一样、跑起来不一样”的坑提前用可运行的代码给你标出来了。适合谁适合手里有 GD32E10x 开发板、想快速验证硬件设计、或者需要从 STM32 迁移到 GD32 但不想重写底层驱动的从业者。新手能照着工程一步步跑通熟手能直接扒里面的时钟树配置和中断向量表来改自己的项目。2. 例程包的结构拆解从目录到最小可编译单元2.1 拿到包先看什么目录层级与文件职责一份典型的 GD32E10x 官方例程包解压后通常长这样根目录下会有Firmware、Template、Examples这几个文件夹。Firmware里放的是 CMSIS 内核文件、GD32E10x 标准外设库的.c和.h还有启动文件startup_gd32e10x.s和链接脚本gd32e10x_flash.ld。Template是一个空壳工程只配好了时钟和中断适合拿来当自己项目的起点。Examples下面按外设分文件夹比如GPIO、USART、TIMER、ADC、SPI、I2C每个外设文件夹里再按功能分子目录像GPIO/Running_led、USART/Printf、TIMER/PWM_output。每个子目录里至少有一个main.c、一个gd32e10x_it.c中断服务函数、一个工程文件可能是 Keil 的.uvprojx或者 IAR 的.eww以及一个readme.txt简单说明这个例程演示什么。我一般拿到包先不急着编译而是先看Firmware/GD32E10x_standard_peripheral/Include下面有哪些头文件这决定了标准库支持哪些外设。然后翻Examples里跟手头硬件最接近的那个例程比如板子上有个 LED 接在 PC13那就直接找GPIO/Running_led打开main.c看它怎么配时钟、怎么初始化 GPIO、怎么翻转电平。这一步能帮你快速判断这份例程包的版本和你的芯片型号是否对得上。2.2 最小可编译单元一个 GPIO 例程的完整构成拿GPIO/Running_led举例一个能跑起来的最小工程包含这些文件startup_gd32e10x.s启动文件负责设置堆栈指针、初始化中断向量表、调用SystemInit和main。system_gd32e10x.c系统时钟初始化把芯片从内部 8MHz RC 时钟切换到外部晶振并倍频到 120MHz。gd32e10x.h寄存器定义和标准库函数声明。main.c用户代码配置 GPIO 为推挽输出然后在死循环里翻转电平。gd32e10x_it.c中断服务函数空工程里通常是空的但必须存在因为启动文件里引用了中断向量。gd32e10x_libopt.h标准库的裁剪开关通过宏定义决定编译哪些外设驱动能减小代码体积。编译这个工程你至少需要把Firmware/CMSIS/GD/GD32E10x/Source和Firmware/GD32E10x_standard_peripheral/Source下的.c文件全部加入工程头文件路径也要指到对应的Include目录。很多人第一次编译报一堆undefined reference八成就是库文件没加全或者路径没配对。2.3 从 STM32 迁移过来的人第一件事该看哪里STM32 和 GD32 虽然都是 Cortex-M3外设寄存器地址和位定义却不一样。比如 STM32F103 的 GPIO 输出速度配置寄存器是GPIOx_CRL和GPIOx_CRHGD32E10x 虽然也有类似寄存器但复位后的默认值和时钟使能顺序有差别。官方例程里system_gd32e10x.c的SystemInit函数就是第一个要看的地方它决定了芯片跑多快、外部晶振多少兆、PLL 怎么倍频。第二个要看的是gd32e10x_it.c里的中断向量名STM32 叫USART1_IRQHandlerGD32 可能叫USART0_IRQHandler名字对不上编译直接报错。第三个是标准库函数的命名习惯STM32 标准库是GPIO_InitGD32 标准库是gpio_init参数结构体成员名也有细微差别。把这些对清楚迁移就完成了一半。3. 把例程跑起来环境搭建与第一个工程编译3.1 工具链选择Keil、IAR 还是 GCC官方例程包通常同时提供 Keil MDK 和 IAR EWARM 的工程文件有些版本还会给 Makefile 支持 GCC。如果你习惯 Keil直接双击Examples/GPIO/Running_led/Project/Running_led.uvprojx就能打开。但要注意Keil 需要装 GD32E10x 的器件支持包否则打开工程会提示找不到器件。这个支持包一般从官网下载安装后在 Keil 的Pack Installer里能看到GigaDevice::GD32E10x系列。IAR 用户同理需要对应的器件描述文件。GCC 用户则要自己写 Makefile 或者用官方的Makefile模板把arm-none-eabi-gcc的路径配好。我一般推荐新手先用 Keil因为例程包里的 Keil 工程配置最全下载算法、调试器设置、宏定义都配好了省得自己折腾。熟手如果要做 CI 或者跨平台开发可以拿 GCC 版本改但要注意链接脚本里的内存地址和芯片实际 Flash/RAM 大小必须一致。3.2 编译前必须检查的三个配置项打开工程后先别急着点编译检查这三个地方第一Options for Target里的Device是否选对了具体型号比如GD32E103C8还是GD32E103RBFlash 和 RAM 大小不一样选错了下载会失败。第二C/C选项卡里的Define框通常会有GD32E10X、USE_STDPERIPH_DRIVER这类宏如果缺了USE_STDPERIPH_DRIVER标准库的gd32e10x_libopt.h就不会被包含编译会报找不到函数声明。第三Debug选项卡里的调试器选对比如 ST-Link 还是 J-Link然后点Settings确认能识别到芯片 ID。如果识别不到检查接线SWDIO、SWCLK、GND、VCC 四根线必须接对有些板子还要把 BOOT0 拉低。/* main.c 里典型的 GPIO 初始化代码以 Running_led 为例 */ #include gd32e10x.h #include systick.h void gpio_config(void) { /* 使能 GPIOC 时钟GD32 的标准库函数是 rcu_periph_clock_enable */ rcu_periph_clock_enable(RCU_GPIOC); /* 配置 PC13 为推挽输出速度 50MHz */ gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13); } int main(void) { /* 配置系统滴答定时器1ms 中断一次 */ systick_config(); gpio_config(); while(1) { /* 翻转 PC13 电平 */ gpio_bit_write(GPIOC, GPIO_PIN_13, (bit_status)(1 - gpio_output_bit_get(GPIOC, GPIO_PIN_13))); /* 延时 500ms */ delay_1ms(500); } }这段代码里rcu_periph_clock_enable对应 STM32 的RCC_APB2PeriphClockCmdgpio_init对应GPIO_Init参数顺序和枚举名都变了。GPIO_MODE_OUT_PP是推挽输出GPIO_OSPEED_50MHZ是输出速度。gpio_bit_write和gpio_output_bit_get是 GD32 标准库提供的位操作函数比直接读改写寄存器安全。systick_config和delay_1ms通常在systick.c里实现例程包里会带。3.3 下载与调试识别不到芯片怎么办编译通过后点下载如果 Keil 报No target connected或者Flash Download failed按这个顺序排查先确认调试器驱动装了没有设备管理器里有没有识别到 ST-Link 或 J-Link再确认工程Debug设置里的调试器型号选对然后检查板子供电有些 GD32 板子 USB 供电和外部供电只能二选一同时接可能烧坏最后看复位电路GD32 的 NRST 引脚如果被电容拉得太狠调试器可能无法复位芯片可以试着把复位方式改成Connect under Reset。还有一个玄学问题有些 Keil 版本对 GD32 的 Flash 算法支持不好需要手动添加GD32E10x_Flash.FLM到Flash Download的编程算法列表里这个文件通常在器件支持包的Flash目录下。4. 外设例程逐个拆USART、定时器、ADC 的实操要点4.1 USART 收发与 printf 重定向Examples/USART/Printf这个例程演示了怎么把printf重定向到串口方便调试。核心是重写fputc函数/* 重定向 printf 到 USART0 */ int fputc(int ch, FILE *f) { /* 等待发送数据寄存器空 */ while(usart_flag_get(USART0, USART_FLAG_TBE) RESET); /* 把字符写入数据寄存器 */ usart_data_transmit(USART0, (uint8_t)ch); return ch; }usart_flag_get查的是USART_FLAG_TBE发送缓冲区空标志usart_data_transmit把数据塞进USART_DATA寄存器。串口初始化部分要配波特率、字长、停止位、校验位GD32 的usart_baudrate_set函数会根据当前时钟自动算分频值但前提是rcu_periph_clock_enable(RCU_USART0)已经调用而且 USART0 挂的 APB 时钟频率跟system_gd32e10x.c里配的一致。常见坑是波特率算出来偏差太大导致乱码这时候要检查外部晶振频率是不是跟system_gd32e10x.c里的HXTAL_VALUE宏对得上比如板子焊的是 8MHz 晶振宏定义写的是 12MHz那串口肯定乱。4.2 定时器 PWM 输出频率与占空比怎么算Examples/TIMER/PWM_output演示了用 TIMER2 的通道 0 输出 PWM。关键参数是预分频器prescaler和自动重装载值period。PWM 频率 定时器时钟 / ((prescaler 1) * (period 1))。假设定时器时钟是 120MHz想要 1kHz PWM那(prescaler 1) * (period 1)应该等于 120000。可以设prescaler 119period 999。占空比由timer_channel_output_pulse_value_config函数设置比如设成 500占空比就是 500/1000 50%。/* 配置 TIMER2 通道 0 输出 1kHz PWM占空比 50% */ timer_parameter_struct timer_initpara; timer_oc_parameter_struct timer_ocintpara; rcu_periph_clock_enable(RCU_TIMER2); timer_deinit(TIMER2); timer_initpara.prescaler 119; timer_initpara.alignedmode TIMER_COUNTER_EDGE; timer_initpara.counterdirection TIMER_COUNTER_UP; timer_initpara.period 999; timer_initpara.clockdivision TIMER_CKDIV_DIV1; timer_init(TIMER2, timer_initpara); timer_ocintpara.ocpolarity TIMER_OC_POLARITY_HIGH; timer_ocintpara.outputstate TIMER_CCX_ENABLE; timer_channel_output_config(TIMER2, TIMER_CH_0, timer_ocintpara); timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_0, 500); timer_channel_output_mode_config(TIMER2, TIMER_CH_0, TIMER_OC_MODE_PWM0); timer_channel_output_shadow_config(TIMER2, TIMER_CH_0, TIMER_OC_SHADOW_DISABLE); timer_auto_reload_shadow_enable(TIMER2); timer_enable(TIMER2);注意timer_channel_output_shadow_config如果设成TIMER_OC_SHADOW_DISABLE改占空比会立即生效设成ENABLE则要等下一个更新事件才生效。调试电机驱动时这个区别很关键立即生效可能导致波形抖动。4.3 ADC 规则通道采样与 DMA 搬运Examples/ADC/ADC_DMA演示了 ADC0 规则通道配合 DMA 自动搬运数据。配置步骤先使能 ADC0 和 DMA0 时钟配 DMA 源地址为ADC_RDATA(ADC0)目标地址为用户数组传输长度设成通道数然后配 ADC 为规则通道、扫描模式、连续转换最后使能 ADC 的 DMA 请求。常见坑是 ADC 时钟不能超过 14MHzGD32E10x 的 ADC 时钟来自 APB2 分频如果系统跑 120MHzAPB2 也是 120MHz那 ADC 预分频至少设成 8 分频才能降到 15MHz 以下。另一个坑是 DMA 传输完成中断里要清标志否则会反复进中断。5. 避坑与排查GD32E10x 例程里最容易翻车的五个地方5.1 现象编译报错undefined reference to SystemInit原因启动文件startup_gd32e10x.s里调用了SystemInit但system_gd32e10x.c没加入工程或者头文件路径没包含Firmware/CMSIS/GD/GD32E10x/Include。解决把system_gd32e10x.c加到工程源文件列表并在C/C的Include Paths里加上 CMSIS 和标准库的Include目录。5.2 现象串口打印乱码波特率明明设对了原因外部晶振频率和system_gd32e10x.c里的HXTAL_VALUE宏不一致。比如板子焊的是 8MHz 晶振宏定义却是 12MHz系统时钟实际跑不到 120MHz串口分频值算错。解决打开system_gd32e10x.c找到#define HXTAL_VALUE那一行改成板子实际晶振频率重新编译。5.3 现象PWM 输出频率不对示波器测出来差很多原因定时器时钟源搞错了。GD32E10x 的 TIMER0 挂 APB2TIMER1/2/3 挂 APB1如果 APB1 分频系数不是 1定时器时钟会是 APB1 时钟的 2 倍。比如系统 120MHzAPB1 分频 2 得 60MHz但定时器实际时钟是 120MHz。解决查rcu_clock_freq_get(CK_APB1)和rcu_clock_freq_get(CK_TIMER1)的实际值按真实时钟算 prescaler 和 period。5.4 现象ADC 采样值跳动很大或者一直是 0原因ADC 时钟超频或者 GPIO 没配成模拟输入模式。GD32E10x 的 ADC 时钟上限是 14MHz超过后采样值不可信。另外 ADC 通道对应的 GPIO 必须配成GPIO_MODE_AIN如果配成浮空输入采样值会飘。解决在rcu_adc_clock_config里把 ADC 预分频设大一点确保 ADC 时钟低于 14MHz检查 GPIO 初始化代码把对应引脚设成模拟输入。5.5 现象中断进不去或者进了中断就死循环原因中断向量名写错或者中断标志没清。GD32 的中断向量名和 STM32 不完全一样比如 USART0 的中断函数是USART0_IRQHandler不是USART1_IRQHandler。另外在中断服务函数里必须调用对应的xxx_interrupt_flag_clear清标志否则中断会反复触发。解决对照startup_gd32e10x.s里的向量表确认函数名在中断处理末尾加清标志语句。6. 进阶用法把例程改造成自己的项目模板6.1 从 Template 工程开始裁剪官方例程包里的Template工程是最干净的起点只配了系统时钟和 SysTick没有多余外设。我一般把它复制一份重命名为项目名然后按需往里加标准库源文件。比如项目要用 USART 和定时器就只把gd32e10x_usart.c和gd32e10x_timer.c加入工程其他外设的.c文件不加这样编译出来的固件体积小也减少变量冲突。gd32e10x_libopt.h里对应的宏也要打开比如#define GD32E10X_USART和#define GD32E10X_TIMER否则头文件里函数声明会被条件编译掉。6.2 用宏定义做硬件版本管理实际项目里板子可能改版LED 引脚从 PC13 换到 PA1串口从 USART0 换到 USART1。我习惯在main.h里用宏定义把硬件相关参数集中管理/* main.h 硬件配置宏 */ #define LED_GPIO_PORT GPIOC #define LED_GPIO_PIN GPIO_PIN_13 #define LED_RCU_CLOCK RCU_GPIOC #define DEBUG_USART USART0 #define DEBUG_USART_RCU RCU_USART0 #define DEBUG_USART_GPIO GPIOA #define DEBUG_USART_TX_PIN GPIO_PIN_9 #define DEBUG_USART_RX_PIN GPIO_PIN_10这样换板子时只改main.h不用满工程搜GPIOC和USART0。标准库函数调用时把宏传进去就行比如gpio_init(LED_GPIO_PORT, ...)。这个习惯是从一次血泪经验里养成的当时项目从 GD32E103 换到 GD32E103 的另一个封装LED 引脚变了结果忘了改一处gpio_bit_write里的端口参数调试了半天才发现。6.3 验证方法用示波器和逻辑分析仪确认时序例程跑起来只是第一步要确认时序对不对还得上仪器。PWM 输出用示波器看频率和占空比串口用逻辑分析仪抓波形解码SPI 和 I2C 用逻辑分析仪看时钟极性和相位。我一般会在关键代码前后翻转一个空闲 GPIO然后用示波器测这个 GPIO 的高电平持续时间就能算出函数执行时间。比如在 ADC 采样前后翻转 PA0测到高电平 2us说明采样加处理花了 2us。这个方法比用调试器单步更接近真实运行状态也更容易发现中断延迟问题。从那以后我每次拿到新的 MCU 例程包都强制走一遍“编译→下载→示波器测波形→改宏定义换引脚→再测”的流程确认整条链路都通了才往里面加业务代码。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑