前言
随着嵌入式系统与物联网技术的快速发展,设备之间的数据交互越来越依赖串行通信。UART 作为最基础、最常用的异步串行通信协议,是每一位嵌入式开发者必须掌握的核心技能。
一、UART 是什么?
UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是一种异步串行通信协议,它通过两根数据线(TX 发送、RX 接收)实现设备间的全双工通信。与 SPI、I2C 不同,它没有同步时钟线,依靠双方预先约定的参数完成数据收发,广泛用于调试打印、传感器、RS485 总线等场景。
二、UART 通信原理
1. 帧格式
UART 以帧为单位传输数据,一帧数据通常包含:起始位(1 位低电平)、数据位(5-8 位)、校验位(可选)、停止位(1-2 位高电平)。通信双方必须配置相同的帧格式才能正确解析数据。
数据位的发送顺序是低位先行从最低位(LSB)到最高位(MSB)。
起始位-->数据位(5-8bit)-->校验位(可选)-->停止位
2. 校验位
校验位是附加的1bit,用来简单检测传输过程中有没有出现1位比特翻转错误,不能纠错,也不能检测偶数个bit同时出错。
偶校验:数据位加校验位,1的总数为偶数。假设数据位中1的个数为奇数那么校验位为1,如果数据位中1的个数为偶数那么校验位为0。
奇校验:数据位加校验位,1的总数为奇数。假设数据位中1的个数为奇数那么校验位为0,如果数据位中1的个数为偶数那么校验位为1。
3. 波特率
波特率表示每秒传输的码元数,常见值有 9600、115200 等。发送方和接收方必须使用相同的波特率,否则会出现乱码或丢帧。波特率由外设时钟和 USART 分频寄存器(含小数分频)共同决定。
4. UART时序图
下图是使用逻辑分析仪对115200bps、无校验、一个停止位配置的UART的发送端抓的时序图。
4. 硬件连接
UART 通信只需三根线:TX(发送)、RX(接收)、GND(共地)。注意交叉连接:设备 A 的 TX 接设备 B 的 RX,设备 A 的 RX 接设备 B 的 TX。、
三、UART 数据收发实战(以GD32F303RET6为硬件平台)
1. UART的配置
下面配置的是USART1外设,UART的中断优先级要根据实际的优先级分组和应用来分配 。
#define TX_RS485_PIN GPIO_PIN_2 #define TX_RS485_GPIO_PORT GPIOA #define RX_RS485_PIN GPIO_PIN_3 #define RX_RS485_GPIO_PORT GPIOA static void (*uart1_callback)(uint8_t byte) = NULL; /** * @brief Initialize USART1. * * @param[in] bound Baudrate of USART1. * @param[in] Parity Parity of USART1. * * @note This function is used to initialize USART1, which is used for RS485 communication. * The function initializes the GPIO ports, USART clock. */ static void usart1_init(uint32_t bound, uint32_t Parity) { /* enable GPIO clock */ rcu_periph_clock_enable(RCU_GPIOA); /* enable USART clock */ rcu_periph_clock_enable(RCU_USART1); /* connect port to USARTx_Tx */ /* connect port to USARTx_Rx */ gpio_init(TX_RS485_GPIO_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, TX_RS485_PIN); gpio_init(RX_RS485_GPIO_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, RX_RS485_PIN); /* USART configure */ /* reset USART1 */ usart_deinit(USART1); /* USART1 configure */ usart_baudrate_set(USART1, bound); if(Parity==USART_PM_EVEN || Parity==USART_PM_ODD) usart_word_length_set(USART1, USART_WL_9BIT); else usart_word_length_set(USART1, USART_WL_8BIT); usart_stop_bit_set(USART1, USART_STB_1BIT); usart_parity_config(USART1, Parity); usart_hardware_flow_rts_config(USART1, USART_RTS_DISABLE); usart_hardware_flow_cts_config(USART1, USART_CTS_DISABLE); usart_receive_config(USART1, USART_RECEIVE_ENABLE); usart_transmit_config(USART1, USART_TRANSMIT_ENABLE); /* USART1 IRQn */ nvic_irq_enable(USART1_IRQn, 2, 1); usart_interrupt_enable(USART1, USART_INT_RBNE); /* enable USART1 */ usart_enable(USART1); } void bsp_uart1_callback_set(void (*callback)(uint8_t byte)) { uart1_callback = callback; }2. 发送数据
发送数据时,将待发送的字节写入数据寄存器即可。下面给出一个简单的for循环发送函数。
备注:简单场景可用;大数据量建议改用DMA发送避免阻塞。
/** * @brief Send data through USART1. * * @param[in] data Pointer to the data to be sent. * @param[in] len Length of the data to be sent. * * @note This function is used to send data through USART1. */ void bsp_uart1_send(uint8_t *data, uint16_t len) { usart_flag_clear(USART1, USART_FLAG_TBE); for (uint16_t i = 0; i < len; i++) { usart_data_transmit(USART1, data[i]); while(RESET == usart_flag_get(USART1, USART_FLAG_TBE)); } usart_flag_clear(USART1, USART_FLAG_TBE); }3. 接收数据(中断方式)
实际项目中通常使用中断方式接收数据,避免阻塞 CPU。下面给出中断接收的配置与处理示例。
uart1_callback是一个函数指针,想要接收数据需要调用bsp_uart1_callback_set函数给指针赋值。
void USART1_IRQHandler(void) { uint8_t byte = 0; if(RESET != usart_interrupt_flag_get(USART1, USART_INT_FLAG_RBNE)) { byte = (uint8_t)usart_data_receive(USART1); if(uart1_callback != NULL) uart1_callback(byte); } if(RESET != usart_interrupt_flag_get(USART1, USART_INT_FLAG_IDLE)) { byte = USART_STAT0(USART1); byte = USART_DATA(USART1); } if(RESET != usart_interrupt_flag_get(USART1, USART_INT_FLAG_ERR_ORERR)) { byte = USART_STAT0(USART1); byte = USART_DATA(USART1); } }四、常见问题与调试技巧
在实际调试 UART 通信时,经常会遇到以下问题,这里给出对应的排查思路:
- 乱码:检查波特率是否一致,帧格式(数据位、停止位、校验位)是否匹配。
- 收不到数据:检查 TX/RX 是否交叉连接,GND 是否共地,引脚配置是否正确。
- 丢帧:接收缓冲区溢出或中断处理不及时,可增大缓冲区或使用 DMA。
- 电平不匹配:TTL 电平与 RS-232 电平不同,需使用 MAX3232 等电平转换芯片。
- 快速验证:可以使用杜邦线或者跳线帽短接RX和TX让它自发自收,这样快速验证基本的配置和硬件是否OK。
总结
本文从 UART 的基本概念、帧格式、波特率、硬件连接入手,结合 STM32 给出了初始化、发送和中断接收的完整代码示例,并总结了常见调试问题。