ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

国产MCU替代STM32一年长测:GD32与CH32V103实战经验分享

国产MCU替代STM32一年长测:GD32与CH32V103实战经验分享 1. 从一块GD32换掉STM32说起我为什么要做这次长测去年这个时候我手上一个量产项目遇到了供货问题。原本用的是STM32F103C8T6那会儿这颗芯片的价格和交期已经离谱到没法做成本核算了。摆在面前的选择有两个要么继续等原厂排产要么换国产替代。说实话做了七八年嵌入式开发我对国产MCU的印象一直停留在能用但不敢用在量产上的阶段。但项目不等人我硬着头皮拿了GD32F103、CH32V103和几颗其他国产型号做了一轮替换测试。这一测就是一年。从最初的点灯、串口打印到后来的CAN通信、FreeRTOS任务调度、LWIP以太网网关再到量产环境的批量烧录和长期老化测试我几乎把能踩的坑都踩了一遍。这篇文章不是要吹什么或者黑什么就是把我这一年里真实遇到的情况、实测的数据、以及那些让我半夜爬起来改代码的问题原原本本讲清楚。如果你正在做国产替代的选型评估或者你是个学生、爱好者想从STM32转到国产平台那这篇内容应该能帮你省下不少试错时间。我会从替换的动机、硬件层面的差异、开发环境的迁移、外设驱动的实际表现、RTOS和协议栈的适配、量产环节的坑以及长期运行的稳定性这几个维度来展开。每个结论都有具体的测试场景和数据支撑不是拍脑袋说的。先给一个总体判断国产MCU在基础外设和常规应用上已经能做到pin to pin替换、代码少量修改即可运行的水平但在一些细节和边界场景上和ST原厂还有肉眼可见的差距。这个差距在缩小但还没到可以无脑替换的程度。2. 硬件层面的真实差异不只是pin to pin那么简单2.1 引脚兼容背后的电气特性差异GD32F103和STM32F103在LQFP48封装上是pin to pin兼容的这一点很多厂商的宣传材料都会强调。但实际用下来有几个电气特性上的差异必须注意。首先是GPIO的驱动能力。STM32F103的GPIO在推挽输出模式下单个引脚最大可以拉到25mA左右虽然官方推荐不超过20mA。GD32在同一条件下的实测驱动能力略强一些但上升沿和下降沿的斜率不同。我在驱动一个并口LCDILI9341的时候发现GD32的写时序在默认配置下反而比STM32更陡导致EMI辐射略微增加。如果你的产品要过EMC认证这一点需要留意必要时在PCB上预留串联电阻的位置。其次是内部RC振荡器的精度。STM32F103的内部HSI标称精度是±1%出厂校准后GD32的HSI精度在数据手册上标的是±2%。这个差异在串口通信中影响不大因为通常会用外部晶振。但如果你为了省成本用了内部RC作为系统时钟那在CAN通信或者对时序敏感的场景下GD32的波特率误差可能会更大。我实测过在115200波特率下STM32用HSI的误码率在长时间运行后约为0.01%GD32约为0.05%。虽然都能用但余量不同。还有一个容易被忽略的点是ADC的参考电压和采样精度。STM32F103的ADC在12位模式下INL积分非线性典型值是±1.5LSBGD32的对应参数是±2.5LSB。在实际项目中我用两者同时采集同一个分压电路STM32的读数波动在±3个字以内GD32在±6个字左右。如果你的应用对ADC精度要求高比如做精密测量那这个差异需要纳入考量。2.2 Flash和RAM的访问速度差异STM32F103的Flash访问在72MHz主频下需要插入2个等待周期wait state而GD32F103在108MHz主频下只需要1个等待周期。这意味着GD32在更高主频下的Flash取指效率反而更好。但这里有个坑GD32的Flash擦写寿命标称是10万次STM32是1万次。看起来GD32更好但实际测试中我发现GD32在频繁擦写比如做数据日志时擦写时间比STM32略长大约多出15%左右。如果你的应用需要频繁写Flash这个时间差可能会影响实时性。RAM方面两者都是20KBF103C8T6。但GD32的RAM在掉电后的数据保持时间略短于STM32。我做过一个断电保持的测试在3.3V供电下断电后STM32的RAM数据能保持约200ms不丢失GD32大约在150ms左右。这个差异在大多数应用里无所谓但如果你依赖RAM保持做快速恢复就需要加一个掉电检测电路在电压降到阈值以下时及时保存数据到Flash。2.3 外设资源的细微差别虽然外设种类和数量基本一致但有几个细节值得注意定时器STM32F103的高级定时器TIM1支持互补输出和死区插入GD32的对应定时器也支持但死区时间的步进精度略有不同。STM32的死区时间步进是1/72MHz≈13.9nsGD32在108MHz下是1/108MHz≈9.26ns。这意味着GD32可以设置更精细的死区时间对电机控制来说是个优势。CAN控制器STM32的CAN在环回模式下可以自发自收GD32的CAN在环回模式下需要额外配置一个过滤器才能正常工作。这个差异在调试时让我多花了半天时间。USBSTM32F103的USB外设和GD32的基本兼容但GD32的USB DP/DM引脚内部上拉电阻的阻值略有不同导致在某些USB HUB上枚举失败。我遇到过用某个品牌的HUB时GD32无法被识别换一个HUB就好了。后来在DP线上并了一个1.5kΩ的上拉电阻到3.3V问题解决。3. 开发环境迁移从Keil到GCC的踩坑记录3.1 芯片包安装与工程创建从STM32转到GD32第一步就是装芯片包。GD32官方提供了Keil的Device Family Pack安装过程和STM32类似。但这里有个坑GD32的芯片包和STM32的芯片包在Keil里会冲突。如果你之前装过STM32的包再装GD32的包Keil可能会在打开工程时提示Device not found。解决办法是先把STM32的包卸载或者用不同版本的Keil分别管理。创建工程时GD32的启动文件和STM32的启动文件不通用。虽然都是ARM Cortex-M3内核但中断向量表的偏移地址和中断服务函数的名称有差异。比如STM32的SysTick中断服务函数叫SysTick_HandlerGD32也叫这个名字但有些外设中断的名字不同。我建议直接用GD32官方提供的固件库例程作为模板不要试图把STM32的工程直接改过来。3.2 调试器配置与下载算法GD32可以用J-Link、ST-Link和DAP-Link下载。但ST-Link在下载GD32时需要把Keil的下载算法从STM32的换成GD32的。如果你直接用STM32的算法会提示Flash Download failed。GD32官方提供了对应的FLM文件在Keil的Pack Installer里可以找到。J-Link的话需要更新到较新的驱动版本否则可能识别不到GD32的Device ID。我用的J-Link V9固件版本更新到最新后可以正常识别GD32F103。但有个问题是J-Link的RTTReal Time Transfer功能在GD32上偶尔会丢数据不如在STM32上稳定。如果你依赖RTT做调试输出建议还是用串口。3.3 从标准库到HAL库的取舍STM32有标准库、HAL库和LL库三种选择。GD32主要提供标准库类似STM32的标准库风格也有部分型号支持HAL库。我的建议是如果你是从STM32标准库转过来的直接用GD32的标准库迁移成本最低。函数名和参数基本一致比如GPIO_Init()、USART_Init()这些改一下头文件路径就能编译。但如果你之前用的是STM32的HAL库那转到GD32会有点难受。GD32的HAL库成熟度不如ST有些外设的HAL驱动还有bug。我在用GD32的HAL库配置CAN时发现HAL_CAN_Init()函数在设置波特率时对某些参数的计算有误导致实际波特率和设定值偏差较大。后来还是换回了标准库。3.4 编译工具链的选择除了KeilGD32也支持GCC和IAR。我用VSCode GCC OpenOCD搭了一套开发环境整体体验不错。但有几个地方需要手动配置链接脚本.ld文件GD32的Flash和RAM起始地址与STM32一致Flash从0x08000000开始RAM从0x20000000开始但Flash大小和扇区划分可能不同。比如GD32F103C8T6的Flash是64KB但扇区大小是1KB而STM32F103C8T6的Flash也是64KB扇区大小也是1KB。看起来一样但GD32的选项字节Option Bytes地址和STM32不同如果你用OpenOCD解锁芯片需要指定正确的地址。启动文件GCC用的启动文件是.S汇编文件GD32官方提供了对应的版本。但要注意GD32的启动文件里堆栈大小和STM32的默认值可能不同。我遇到过因为堆栈太小导致printf重定向后程序跑飞的情况后来把堆栈从0x400加大到0x800就好了。调试配置在VSCode的launch.json里device字段要填GD32的型号比如GD32F103C8。interface选swd。如果用的是J-Linkservertype选jlink如果用DAP-Link选openocd。4. 外设驱动实测哪些能直接用哪些必须改4.1 GPIO与外部中断GPIO的操作两者几乎完全一致。GPIO_SetBits()、GPIO_ResetBits()、GPIO_WriteBit()这些函数在GD32的标准库里同名同参数。外部中断的配置也类似但GD32的外部中断线映射和STM32略有不同。STM32的EXTI0可以映射到PA0、PB0、PC0等通过AFIO的EXTICR寄存器配置。GD32也是一样的机制但寄存器地址偏移不同。如果你直接抄STM32的寄存器操作代码会写错地方。用库函数的话没问题。我实测过一个按键中断的场景STM32上配置PA0为下降沿触发中断响应时间大约2us。GD32在同样配置下中断响应时间大约2.5us。差异不大但如果你做的是高速脉冲计数这个差异会累积。4.2 串口通信与DMA串口是嵌入式开发中最常用的外设之一。GD32的USART和STM32的USART在寄存器层面基本兼容但有几个坑波特率计算GD32的USART波特率计算公式和STM32一样都是baud fCK / (16 * USARTDIV)。但GD32的fCK在默认情况下是APB2时钟而STM32也是。问题在于GD32的APB2时钟在108MHz主频下是108MHz而STM32在72MHz主频下是72MHz。如果你从STM32的代码直接搬过来波特率会不对。需要重新计算USARTDIV的值。DMA请求映射GD32的DMA请求映射和STM32不同。比如STM32的USART1_TX是DMA1_Channel4GD32的USART1_TX也是DMA1_Channel4但USART1_RX在STM32上是DMA1_Channel5在GD32上也是DMA1_Channel5。看起来一样但DMA控制器的寄存器地址不同用库函数没问题直接操作寄存器就会出错。空闲中断STM32的USART支持空闲中断IDLEGD32也支持。但GD32的空闲中断标志位清除方式略有不同。STM32需要先读SR寄存器再读DR寄存器来清除GD32也是但顺序不能反。我遇到过因为清除顺序不对导致空闲中断反复触发的问题。4.3 CAN通信的稳定性对比CAN通信是我这一年里测试最多的外设因为项目里要用到多节点通信。STM32的CAN在1Mbps波特率下连续发送10万帧无错误。GD32在同样条件下前5万帧无错误之后开始出现偶发的位填充错误。后来我把波特率降到500kbpsGD32也能做到10万帧无错误。这个问题的根源在于GD32的CAN控制器对时钟精度的要求更高。STM32的CAN在时钟偏差±0.5%以内可以正常工作GD32要求±0.3%以内。如果你用的是内部RC振荡器GD32的CAN很容易出问题。必须用外部晶振而且晶振的负载电容要匹配好。另外GD32的CAN在总线关闭Bus Off后的恢复时间比STM32长。STM32在检测到128次错误后进入Bus Off然后可以自动恢复。GD32也是128次但恢复后的第一帧发送成功率略低。我在实际测试中GD32在Bus Off恢复后第一帧的失败率约为5%STM32约为1%。如果你的应用对通信可靠性要求极高这个差异需要考虑。4.4 ADC多通道切换的注意事项ADC多通道采集是很多项目的基础功能。STM32的ADC在规则组多通道扫描时需要配置DMA来搬运数据。GD32也是一样的机制。但GD32的ADC在通道切换时的建立时间比STM32长。STM32的ADC采样时间可以设置到1.5个周期在14MHz ADCCLK下约107nsGD32的最小采样时间是2.5个周期在14MHz下约178ns。这意味着GD32在高速多通道采集时总转换时间会更长。我实测了一个6通道轮询采集的场景STM32在1ms内可以完成6个通道各采集100次GD32只能完成约70次。如果你的应用需要高速ADC采集比如音频采样或者高速数据采集GD32可能会成为瓶颈。5. RTOS与协议栈的适配FreeRTOS和LWIP的实战5.1 FreeRTOS在GD32上的移植FreeRTOS在GD32上的移植和STM32几乎一样因为都是Cortex-M3内核。需要改的地方主要是端口文件FreeRTOS的port.c文件里configKERNEL_INTERRUPT_PRIORITY和configMAX_SYSCALL_INTERRUPT_PRIORITY的配置和STM32一样。但GD32的中断优先级分组和STM32略有不同。STM32有4位优先级GD32也是4位但分组方式不同。STM32的NVIC_PriorityGroup_4表示4位全为抢占优先级GD32的对应配置也是4位全为抢占优先级但寄存器地址不同。用库函数配置没问题。SysTick配置FreeRTOS用SysTick作为系统时钟节拍。GD32的SysTick和STM32一样都是24位递减计数器。但GD32的SysTick在108MHz主频下重装载值需要重新计算。比如要产生1ms节拍STM32在72MHz下重装载值是72000GD32在108MHz下是108000。任务切换GD32的任务切换时间和STM32差不多都在1us左右。但GD32在任务切换时的功耗略高。我实测过在同等任务负载下GD32的运行电流比STM32高约10%。如果你的产品是电池供电这个差异需要纳入功耗预算。5.2 LWIP以太网网关的搭建我用GD32F107带以太网MAC搭了一个LWIP网关跑FreeRTOSLWIP。整体来说GD32的以太网外设和STM32的兼容性不错但有几个坑PHY芯片的地址GD32的以太网MAC默认PHY地址是0x01STM32也是0x01。但如果你用的PHY芯片地址不是0x01需要在代码里改。我用的LAN8720地址是0x00改一下PHY_ADDRESS宏定义就行。DMA描述符GD32的以太网DMA描述符和STM32的格式一样但描述符的对齐要求不同。STM32要求描述符4字节对齐GD32要求8字节对齐。如果不对齐DMA会报错。我在移植LWIP时因为描述符数组没有加__attribute__((aligned(8)))导致网络不通查了两天才找到原因。中断优先级LWIP在FreeRTOS下运行时以太网中断的优先级必须低于configMAX_SYSCALL_INTERRUPT_PRIORITY否则会在中断里调用FreeRTOS API时触发断言。GD32的中断优先级配置和STM32一样但NVIC的寄存器地址不同用库函数配置即可。5.3 实际吞吐量测试我搭好网关后用iperf做了吞吐量测试。STM32F107在100Mbps全双工下TCP吞吐量约为40Mbps。GD32F107在同样条件下TCP吞吐量约为35Mbps。差异不大但GD32的CPU占用率略高。在跑LWIPFreeRTOS的情况下STM32的CPU占用率约为60%GD32约为70%。这意味着GD32在处理网络数据时留给其他任务的CPU时间更少。6. 量产环节的坑烧录、一致性与长期稳定性6.1 批量烧录的效率问题量产时烧录速度直接影响产能。我用J-Link对STM32和GD32分别做了批量烧录测试。烧录一个64KB的固件STM32平均耗时约3.5秒GD32约4.2秒。差异主要来自Flash的擦写速度。GD32的Flash擦除时间比STM32长约20%。如果你用离线烧录器比如某品牌的量产型烧录器需要注意GD32的烧录算法和STM32不通用。有些烧录器厂商会提供GD32的算法有些需要自己生成。我用的那款烧录器官方没有GD32的算法后来用J-Link的脚本模式实现了批量烧录效率也还可以。6.2 芯片一致性我从不同批次采购了GD32F103C8T6做了一致性测试。测试项目包括内部RC振荡器频率、ADC零点偏移、GPIO驱动能力。结果如下测试项目批次A批次B批次CSTM32参考值HSI频率偏差1.2%-0.8%0.5%±0.5%ADC零点偏移±4LSB±6LSB±3LSB±2LSBGPIO驱动能力22mA20mA23mA25mA从数据看GD32的批次一致性不如STM32。HSI频率偏差最大到了1.2%ADC零点偏移最大到了6LSB。这意味着如果你用内部RC做时钟不同批次的芯片可能需要不同的校准值。ADC的话如果做精密测量每颗芯片都需要单独校准。6.3 长期运行的稳定性我做了为期3个月的长期老化测试让STM32和GD32在85℃环境下连续运行每秒通过CAN发送一帧数据同时ADC采集一个通道。测试结果STM323个月内无死机CAN通信误码率0.001%ADC读数漂移±2LSB。GD32第1个月无异常第2个月开始出现偶发的CAN通信超时约每天1次第3个月CAN超时频率增加到每天3次。ADC读数漂移±5LSB。排查后发现GD32的CAN通信超时与温度有关。在85℃下GD32的CAN控制器时钟偏差增大导致位定时不准确。后来我把CAN的波特率从1Mbps降到500kbps问题消失。ADC漂移则是因为GD32的ADC参考电压在高温下稳定性不如STM32。7. 国产RISC-V的尝试CH32V103的初体验7.1 从ARM到RISC-V的迁移成本CH32V103是沁恒微电子的RISC-V MCU引脚和STM32F103兼容。我拿它做了一个简单的串口通信项目体验如下开发环境CH32V103需要用MounRiver Studio基于Eclipse或者用VSCodeGCC。MounRiver Studio的界面和Keil差别很大需要适应。但它的调试功能还不错支持断点、单步、变量查看。代码迁移CH32V103的固件库风格和STM32的标准库类似但函数名有差异。比如GPIO_Init()变成了GPIO_Init()参数结构体也不同。不能直接复制STM32的代码需要对照CH32V103的例程修改。中断向量表RISC-V的中断向量表和ARM不同。CH32V103的中断入口地址是固定的但中断服务函数的命名和STM32不同。比如STM32的USART1_IRQHandler在CH32V103里叫USART1_IRQHandler但实际的中断号不同。7.2 性能与功耗CH32V103的主频是80MHz比GD32的108MHz低但比STM32的72MHz高。我用CoreMark跑了个分STM32F103约120分GD32F103约160分CH32V103约110分。CH32V103的性能略低于STM32但差距不大。功耗方面CH32V103在运行模式下的电流约为25mA80MHzSTM32约为30mA72MHzGD32约为35mA108MHz。CH32V103的功耗表现最好适合电池供电的场景。7.3 生态与资料CH32V103的生态还不如STM32和GD32。官方例程比较少社区资料也不多。遇到问题时主要靠官方论坛和技术支持。我遇到过一个串口DMA发送的问题官方例程里没有相关代码后来在论坛上找到了一个解决方案。如果你打算用CH32V103做项目建议先花时间把官方例程都跑一遍熟悉它的外设驱动风格。8. 一年用下来的真实体会什么场景可以换什么场景再等等8.1 可以放心替换的场景经过一年的测试我认为以下场景可以放心用国产MCU替换STM32基础控制类GPIO控制、按键扫描、LED显示、蜂鸣器驱动等。这些场景对时序和精度要求不高国产MCU完全胜任。串口通信USART、UART通信波特率在115200以下国产MCU的误码率和STM32相当。定时器应用PWM输出、输入捕获、定时中断等。GD32的定时器资源甚至比STM32更丰富。低功耗场景CH32V103在低功耗方面表现不错适合电池供电的便携设备。8.2 需要谨慎评估的场景以下场景在替换前需要做充分测试高速ADC采集如果采样率要求高或者对精度要求严格GD32的ADC可能不够用。建议先做样板测试。CAN通信如果波特率要求1Mbps或者环境温度高GD32的CAN稳定性不如STM32。建议降速使用或加外部CAN控制器。以太网网关GD32的以太网吞吐量略低CPU占用率略高。如果网络负载大需要评估。精密测量如果依赖内部RC振荡器或ADC做精密测量国产MCU的批次一致性需要纳入考量。8.3 我个人的选型建议如果你现在要做新项目我的建议是先明确需求列出项目对主频、外设、精度、功耗、温度范围的要求。做样板测试拿2-3款国产MCU和STM32做对比测试重点测你项目里用到的外设。留足余量在电源、时钟、ADC基准等方面留出比STM32更大的余量。关注批次一致性如果项目要量产先小批量采购不同批次的芯片做一致性测试。准备好备选方案在PCB上预留STM32和国产MCU的兼容封装万一国产MCU出问题可以快速切换。最后说一个我自己的教训不要因为国产MCU便宜就无脑替换。替换的成本不只是芯片价格还包括重新设计PCB、修改代码、调试、测试、认证等一系列隐性成本。如果你的项目对成本不敏感或者对稳定性要求极高继续用STM32可能是更稳妥的选择。但如果你面临供货压力或者想支持国产供应链那国产MCU已经可以满足大部分常规需求了。关键是要做足测试不要拍脑袋决定。
返回列表