1. UART通信基础与GD32应用场景
在嵌入式开发领域,UART(Universal Asynchronous Receiver/Transmitter)堪称最基础却最不可或缺的通信接口。作为GD32开发者,掌握UART的使用是打通设备与外界对话的关键通道。与SPI、I2C等同步通信不同,UART采用异步传输机制,仅需TX(发送)、RX(接收)两根信号线即可实现全双工通信,这种简洁的硬件设计使其在调试输出、传感器连接、无线模块控制等场景中占据不可替代的地位。
GD32系列MCU的UART外设具有高度可配置性,支持多种数据格式(8/9位数据位、1/2位停止位)、可编程波特率(最高可达6Mbps)以及硬件流控(CTS/RTS)。实际项目中,我常用UART实现以下功能:
- 通过printf重定向输出调试信息
- 与GPS/蓝牙/WiFi等串口模块通信
- 构建简单的设备间主从通信协议
- 接收来自上位机的配置参数
注意:GD32的USART(Universal Synchronous/Asynchronous Receiver/Transmitter)实际上是UART的超集,当仅使用异步功能时,我们通常仍习惯称之为UART。
2. GD32 UART硬件配置详解
2.1 引脚映射与时钟配置
以GD32F303系列为例,每个UART接口都有固定的引脚映射,但也支持灵活的引脚重映射功能。以UART0为例,默认使用PA9(TX)/PA10(RX),通过AFIO->PCF0寄存器可将其重映射到PB6/PB7。配置时需三步走:
- 使能GPIO和UART时钟:
c复制rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_USART0);
- 配置GPIO为复用推挽输出(TX)和浮空输入(RX):
c复制gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
- 若需重映射,需额外使能AFIO时钟并配置:
c复制rcu_periph_clock_enable(RCU_AF);
gpio_pin_remap_config(GPIO_USART0_REMAP, ENABLE);
2.2 波特率计算与误差控制
GD32的波特率计算公式为:
code复制波特率 = fCK / (16 * USARTDIV)
其中fCK是UART时钟频率,USARTDIV是一个16位无符号定点数,高12位为整数部分,低4位为小数部分。
例如在72MHz系统时钟下,要实现115200bps波特率:
code复制USARTDIV = 72000000 / (16 * 115200) ≈ 39.0625
对应寄存器值为:
c复制整数部分:39 → 0x27
小数部分:0.0625 * 16 = 1 → 0x1
最终USART_BRR = 0x271;
实测技巧:当波特率误差超过2%时可能出现通信失败。对于非常规波特率,建议用示波器测量实际波形验证。
3. UART驱动实现与优化
3.1 基础收发功能实现
配置UART参数通常需要设置以下寄存器:
c复制usart_deinit(USART0);
usart_baudrate_set(USART0, 115200);
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_ENABLE);
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
usart_enable(USART0);
实现阻塞式发送函数:
c复制void uart_send_byte(uint8_t data) {
while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
usart_data_transmit(USART0, data);
}
实现中断接收(以GD32标准库为例):
c复制void USART0_IRQHandler(void) {
if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) {
uint8_t data = usart_data_receive(USART0);
// 处理接收数据
}
}
3.2 环形缓冲区设计
为避免数据丢失,建议实现环形缓冲区:
c复制#define BUF_SIZE 256
typedef struct {
uint8_t buffer[BUF_SIZE];
volatile uint16_t head;
volatile uint16_t tail;
} ring_buffer;
ring_buffer rx_buf = {0};
void uart_rx_handler(uint8_t data) {
uint16_t next = (rx_buf.head + 1) % BUF_SIZE;
if(next != rx_buf.tail) {
rx_buf.buffer[rx_buf.head] = data;
rx_buf.head = next;
} else {
// 缓冲区溢出处理
}
}
uint8_t uart_read_byte(void) {
if(rx_buf.head == rx_buf.tail) return 0;
uint8_t data = rx_buf.buffer[rx_buf.tail];
rx_buf.tail = (rx_buf.tail + 1) % BUF_SIZE;
return data;
}
4. 高级应用与性能优化
4.1 DMA传输配置
高波特率或大数据量传输时,应启用DMA减轻CPU负担:
- 配置DMA通道(以USART0_TX为例):
c复制dma_parameter_struct dma_init_struct;
rcu_periph_clock_enable(RCU_DMA0);
dma_deinit(DMA0, DMA_CH3);
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;
dma_init_struct.memory_addr = (uint32_t)send_buf;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_init_struct.number = data_len;
dma_init_struct.periph_addr = (uint32_t)&USART_DATA(USART0);
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.periph_width = DMA_PERIPH_WIDTH_8BIT;
dma_init_struct.priority = DMA_PRIORITY_HIGH;
dma_init(DMA0, DMA_CH3, &dma_init_struct);
usart_dma_transmit_config(USART0, USART_DENT_ENABLE);
dma_channel_enable(DMA0, DMA_CH3);
- 通过中断检查传输完成:
c复制void DMA0_Channel3_IRQHandler(void) {
if(DMA_INT_FLAG_FTF == dma_interrupt_flag_get(DMA0, DMA_CH3, DMA_INT_FLAG_FTF)) {
dma_interrupt_flag_clear(DMA0, DMA_CH3, DMA_INT_FLAG_FTF);
// 处理传输完成事件
}
}
4.2 低功耗模式下的UART唤醒
对于电池供电设备,可配置UART在接收数据时唤醒MCU:
c复制usart_receiver_wakeup_config(USART0, USART_WM_IDLE);
usart_wakeup_mode_enable(USART0, USART_WUM_STARTBIT);
pwr_wakeup_pin_enable(WAKEUP_PIN1);
pmu_to_standbymode(WFI_CMD);
5. 实战问题排查手册
5.1 常见故障现象与解决方案
| 故障现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 无任何数据收发 | 1. 引脚配置错误 2. 时钟未使能 3. 硬件连接问题 |
1. 检查GPIO模式是否为AF_PP/IN_FLOATING 2. 用示波器检测TX引脚是否有波形 3. 确认波特率设置与对方设备一致 |
| 接收数据乱码 | 1. 波特率不匹配 2. 停止位/校验位设置错误 3. 信号干扰 |
1. 用示波器测量实际波特率 2. 检查双方数据格式配置 3. 缩短线缆或增加终端电阻 |
| 数据丢失 | 1. 接收缓冲区溢出 2. 中断优先级过低 3. 流控未启用 |
1. 增大环形缓冲区尺寸 2. 调整NVIC优先级 3. 启用硬件流控或软件XON/XOFF |
5.2 调试技巧与工具推荐
- 逻辑分析仪使用:配置触发条件为UART起始位,可直观观察数据波形和时序
- 串口调试助手推荐:
- Windows: SecureCRT(支持自定义脚本)
- Linux: minicom(配合screen脚本自动化)
- 跨平台: CoolTerm(支持二进制显示)
- 阻抗匹配计算:当通信距离超过1米时,需考虑传输线效应,特性阻抗通常为120Ω
我在实际项目中总结出一个UART配置检查清单:
- 确认GPIO时钟和UART时钟已使能
- 验证TX/RX引脚模式配置正确
- 检查波特率计算值是否准确
- 确保中断优先级配置合理(尤其与RTOS配合时)
- 长距离通信时添加适当的终端电阻
对于需要高可靠性的场景,建议在协议层添加以下机制:
- 帧头/帧尾校验(如0xAA 0x55)
- CRC16校验字段
- 超时重传机制
- 序号确认机制
当系统中有多个UART设备时,一个易忽略的问题是共地问题。我曾遇到因传感器与主控地电位不同导致通信异常的情况,解决方案包括:
- 使用磁耦隔离器(如ADuM1201)
- 改用差分传输(如RS422/485)
- 在两地之间并联大电阻(1MΩ)平衡电位
