1. GD32单片机串口通信基础解析
第一次接触国产GD32系列单片机时,我被它出色的性价比和与STM32的高度兼容性所吸引。作为国内首款Cortex-M3内核的32位通用MCU,GD32在工业控制、消费电子等领域已经逐步替代部分进口芯片。今天要分享的是最基础的串口通信实验——通过串口0和串口1实现数据转发,这个实验看似简单,却包含了单片机通信中最核心的半双工与全双工概念。
我选择GD32F103C8T6这款入门级芯片作为实验平台,它具备2个USART接口,正好对应项目需求。与STM32F103相比,GD32在时钟树设计上做了优化,最高主频可达108MHz,这意味着在相同价格下能获得更好的性能表现。开发环境使用Keil MDK,配合GD32官方提供的固件库,可以快速搭建项目框架。
注意:GD32虽然与STM32引脚兼容,但部分寄存器的默认值存在差异,直接移植STM32代码可能导致意外行为。建议首次使用时仔细核对数据手册中的寄存器描述。
2. 硬件设计与环境搭建
2.1 最小系统搭建
GD32的最小系统需要以下基本元件:
- GD32F103C8T6核心板(或自制PCB)
- 8MHz晶振及22pF负载电容
- 复位电路(10kΩ电阻+104电容)
- 3.3V稳压电路(如AMS1117)
- 串口转换芯片(CH340G或CP2102)
特别要注意的是GD32的BOOT0引脚处理方式。与STM32不同,GD32的BOOT0在内部已有下拉电阻,常规使用中直接接地即可,不需要额外配置上拉/下拉电阻。这个细节在官方数据手册中容易被忽略,但实际应用中会影响芯片的启动模式。
2.2 串口硬件连接
本实验需要连接两个串口:
- USART0(PA9-TX, PA10-RX)连接至PC端的USB转串口工具
- USART1(PA2-TX, PA3-RX)连接另一个GD32开发板或USB转串口工具
在面包板搭建时,建议为每个串口添加LED指示灯(通过三极管驱动),这样可以直观观察数据收发状态。USART0采用半双工模式时,需要特别注意收发切换的时序控制,硬件上可以增加一个方向控制引脚(如PC13)来控制收发器的方向。
3. 软件架构设计与实现
3.1 工程配置要点
使用Keil创建GD32工程时,关键配置步骤如下:
- 选择Device为GD32F10x系列
- 在C/C++选项卡中添加宏定义:GD32F10X_MD
- 设置正确的Flash下载算法(GD32F10x Med-density)
固件库的初始化顺序很重要,推荐按照以下流程:
c复制// 系统时钟初始化
rcu_config();
// 外设时钟使能
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_USART0);
rcu_periph_clock_enable(RCU_USART1);
// GPIO配置
gpio_init();
// USART配置
usart_config();
// NVIC配置
nvic_config();
3.2 串口参数配置详解
USART0(半双工)配置关键代码:
c复制usart_deinit(USART0);
usart_baudrate_set(USART0, 115200U);
usart_word_length_set(USART0, USART_WL_8BIT);
usart_stop_bit_set(USART0, USART_STB_1BIT);
usart_parity_config(USART0, USART_PM_NONE);
usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
usart_receive_config(USART0, USART_RECEIVE_DISABLE); // 初始化为只发模式
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
usart_enable(USART0);
USART1(全双工)配置差异点:
c复制usart_receive_config(USART1, USART_RECEIVE_ENABLE); // 同时启用收发
usart_transmit_config(USART1, USART_TRANSMIT_ENABLE);
实测发现:GD32的USART在115200波特率下工作稳定,但当波特率超过1Mbps时,建议降低系统时钟分频系数或使用硬件流控。
4. 半双工与全双工实现策略
4.1 半双工状态机设计
USART0的半双工操作需要严格的状态控制,我设计了一个简单的状态机:
c复制typedef enum {
UART0_IDLE,
UART0_TX_MODE,
UART0_RX_MODE,
UART0_SWITCHING
} uart0_state_t;
volatile uart0_state_t uart0_state = UART0_IDLE;
void uart0_switch_to_rx(void) {
if(uart0_state == UART0_TX_MODE) {
uart0_state = UART0_SWITCHING;
while(USART_STAT0(USART0) & USART_STAT0_TC); // 等待发送完成
usart_transmit_config(USART0, USART_TRANSMIT_DISABLE);
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
uart0_state = UART0_RX_MODE;
}
}
void uart0_switch_to_tx(void) {
if(uart0_state == UART0_RX_MODE) {
uart0_state = UART0_SWITCHING;
delay_ms(1); // 确保最后一个字节接收完成
usart_receive_config(USART0, USART_RECEIVE_DISABLE);
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
uart0_state = UART0_TX_MODE;
}
}
4.2 数据转发逻辑实现
主循环中的数据转发核心代码:
c复制uint8_t rx_buf[256];
uint16_t rx_len = 0;
void usart1_rx_irq_handler(void) {
if(usart_interrupt_flag_get(USART1, USART_INT_FLAG_RBNE)) {
uint8_t data = usart_data_receive(USART1);
rx_buf[rx_len++] = data;
if(rx_len >= sizeof(rx_buf)) rx_len = 0;
}
}
void process_forward_data(void) {
if(rx_len > 0) {
uart0_switch_to_tx();
for(uint16_t i=0; i<rx_len; i++) {
usart_data_transmit(USART0, rx_buf[i]);
while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
}
rx_len = 0;
uart0_switch_to_rx();
}
}
5. 调试技巧与性能优化
5.1 常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 只能发送不能接收 | 1. 串口初始化错误 2. NVIC未使能中断 3. 接收缓冲区溢出 |
1. 检查USART_CR1的RE位 2. 确认NVIC配置 3. 增加接收超时处理 |
| 数据丢失 | 1. 波特率不匹配 2. 中断优先级过低 3. 半双工切换延时不足 |
1. 用示波器测量实际波特率 2. 提高USART中断优先级 3. 增加切换后的稳定延时 |
| 通信不稳定 | 1. 地线未接好 2. 信号线过长 3. 电源噪声 |
1. 确保共地 2. 缩短连线或使用屏蔽线 3. 增加电源滤波电容 |
5.2 性能优化实践
通过实测发现几个优化点:
- 将USART中断优先级设置为最高可减少数据丢失概率:
c复制nvic_irq_enable(USART0_IRQn, 0, 0);
nvic_irq_enable(USART1_IRQn, 1, 0);
- 使用DMA传输可以大幅提升吞吐量,特别适合高速通信场景:
c复制dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;
dma_init_struct.memory_addr = (uint32_t)rx_buf;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_init_struct.number = sizeof(rx_buf);
dma_init_struct.periph_addr = (uint32_t)&USART_DATA(USART1);
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
dma_init_struct.priority = DMA_PRIORITY_HIGH;
dma_init(DMA0, DMA_CH4, &dma_init_struct);
- 在切换半双工方向时,实测需要至少10us的稳定时间,建议在切换函数中加入微小延时:
c复制void uart0_switch_to_tx(void) {
// ...原有代码...
delay_us(15); // 增加方向切换稳定时间
}
6. 进阶应用与扩展思路
掌握了基础串口通信后,可以尝试以下扩展应用:
- 自定义协议设计:在数据帧中添加帧头、长度、校验和等字段,提高通信可靠性
- Modbus RTU从机实现:利用GD32的定时器实现3.5字符间隔检测
- 无线透传模块集成:通过串口连接HC-12等无线模块,实现无线数据转发
- 多机通信网络:利用USART的硬件地址检测功能,构建主从式通信网络
一个实用的技巧是使用GD32的硬件CRC模块来校验通信数据,相比软件CRC可以大幅降低CPU负载:
c复制rcu_periph_clock_enable(RCU_CRC);
CRC_CTL = CRC_CTL_RST;
for(int i=0; i<len; i++) {
CRC_DATA = data[i];
}
uint32_t crc_value = CRC_DATA;
在实际项目中,我发现GD32的USART有个隐藏特性:当使能硬件流控时,即使不连接RTS/CTS信号线,USART的FIFO缓冲区行为也会发生变化。这个特性在高速通信时可以改善数据吞吐性能,但需要仔细测试确认稳定性。
