第一次拿到GD32C103开发板时,那种既兴奋又忐忑的心情记忆犹新——作为ST阵营转过来的工程师,本以为凭借经验能快速上手,结果在USB虚拟串口和CAN-FD配置上栽了不少跟头。本文将分享那些官方手册没写清楚,但实际开发中一定会遇到的"坑",以及我是如何一个个填平它们的。
Keil MDK是大多数嵌入式工程师的首选IDE,但GD32的配置有些特殊之处。安装完Keil后,需要从GD32官网下载Device Family Pack并手动安装。这里有个容易忽略的点:必须使用Keil v5.28或更高版本,旧版本对GD32C103的支持不完善。
许可证管理是另一个痛点。当看到"License Management"界面时,注意:
bash复制# 验证安装是否成功的快捷方式
$ grep -r "GD32C103" /keil_path/ARM/PACK/
开发环境变量配置不当会导致后续各种奇怪问题。建议在系统环境变量中添加:
| 变量名 | 值 | 作用 |
|---|---|---|
| GD32_TOOLCHAIN | C:\Keil_v5\ARM\ARMCC\bin | 编译器路径 |
| GD32_LIB | C:\GD32C10x_DFP\Library | 标准库路径 |
注意:路径中不要包含中文或空格,这是许多编译错误的根源
按照USB2.0规范,字符串描述符必须使用UCS-2编码。在GD32C103上实现时,新手常犯两个错误:
正确的实现方式应该是:
c复制// 错误的做法
const uint8_t StringLangID[] = {
0x04, // bLength
0x03, // bDescriptorType
0x09, // wLANGID[0]
0x04 // wLANGID[1]
};
// 正确的做法
const uint16_t StringLangID[] = {
(0x0304), // bLength + bDescriptorType
(0x0409) // wLANGID (英语美国)
};
关键点:每个Unicode字符占2字节,且必须是小端格式。例如字母'A'应表示为0x4100而非0x0041。
GD32C103的USB专用SRAM物理地址是0x5000_0000,但在代码中访问时需要使用0x4000_6000这个映射地址。这个差异在参考手册中只有一行小字说明,却会导致USB数据传输失败。
配置DMA时尤其要注意:
c复制// USB端点缓冲区配置
#define USB_EP_BUF_ADDR 0x40006000 // 不是0x50000000!
// DMA配置
DMA_Channel_StructInit(&DMA_InitStructure);
DMA_InitStructure.direction = DMA_PERIPH_TO_MEMORY;
DMA_InitStructure.memory_addr = (uint32_t)&USB_EP_BUF_ADDR; // 关键点
DMA_InitStructure.periph_addr = (uint32_t)&USBD_EP_TX_ADDR(EP0_IN);
提示:USB时钟必须精确配置为48MHz,建议使用HXTAL作为PLL输入源,通过以下公式计算分频系数:
PLL = HXTAL * N / M / P
GD32C103的CAN控制器时钟来自APB1总线,默认频率是60MHz。但在CAN-FD模式下,需要特别注意:
推荐配置步骤:
c复制#define __SYSTEM_CLOCK_108M_PLL_HXTAL (uint32_t)(108000000)
c复制CAN_InitStructure.prescaler = 3; // APB1=60MHz -> CAN时钟=20MHz
c复制CAN_InitStructure.time_segment_1 = CAN_BS1_5TQ;
CAN_InitStructure.time_segment_2 = CAN_BS2_3TQ;
CAN_InitStructure.sample_point = 87.5; // 推荐值
PB8/PB9是默认的CAN0引脚,但通过重映射可以切换到其他引脚。这个功能在参考手册8.4.9节有说明,但寄存器配置容易出错:
c复制// 启用AFIO时钟
rcu_periph_clock_enable(RCU_AFIO);
// 完全重映射CAN0到PD0/PD1
gpio_pin_remap_config(GPIO_CAN0_FULL_REMAP, ENABLE);
// 部分重映射CAN0到PB5/PB6
gpio_pin_remap_config(GPIO_CAN0_PARTIAL_REMAP, ENABLE);
常见问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| CAN发送失败 | 终端电阻未接 | 在CANH-CANL间加120Ω电阻 |
| 只能收不能发 | 回环模式未关闭 | 检查CAN_MCR寄存器的LBKM位 |
| 高速通信时丢帧 | 采样点设置不当 | 调整BS1/BS2使采样点在75%~90% |
当USB或CAN出现异常时,SWD接口比串口打印更可靠。建议配置:
USBD_State (USB设备状态)CAN_ESR (CAN错误状态寄存器)USB_EP_BUF[0] (端点0缓冲区)python复制# 简易SWD监控脚本示例
import pyocd
with pyocd.target.Target("GD32C103") as target:
while True:
print(f"USB State: {target.read32(0x50000080):X}")
print(f"CAN ESR: {target.read32(0x40006400):X}")
time.sleep(0.1)
GD32C103的64KB SRAM在复杂应用中可能吃紧,建议:
c复制__attribute__((section(".usb_ram"))) uint8_t usb_buf[1024];
code复制MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 48K
USBRAM (rw) : ORIGIN = 0x2000C000, LENGTH = 16K
}
c复制#pragma pack(push, 1)
typedef struct {
uint32_t id;
uint8_t dlc;
uint8_t data[64];
} CANFD_Frame;
#pragma pack(pop)
最后分享一个完整项目中的配置框架。这个网关实现了:
核心初始化流程:
c复制void BSP_Init(void)
{
// 1. 时钟初始化
SystemClock_Config(); // 108MHz HXTAL
// 2. USB初始化
USBD_Init(&USB_dev, &USBD_CDC_cb, USB_CORE_ENUM_FS);
USBD_RegisterClass(&USB_dev, &USBD_CDC);
// 3. CAN初始化
CAN_StructInit(&CAN_InitStructure);
CAN_Init(CAN0, &CAN_InitStructure);
// 4. 中断配置
nvic_irq_enable(USBD_IRQn, 1, 0);
nvic_irq_enable(CAN0_IRQn, 2, 0);
// 5. 启动FreeRTOS调度器
xTaskCreate(USB_Task, "USB", 256, NULL, 3, NULL);
xTaskCreate(CAN_Task, "CAN", 256, NULL, 4, NULL);
vTaskStartScheduler();
}
在调试这个项目时,发现当USB和CAN同时高负载工作时,偶尔会出现数据丢失。最终通过以下措施解决: