串口通信协议UART和USART
1. UART与USART协议详解
特性 | UART (Universal Asynchronous Receiver/Transmitter) | USART (Universal Synchronous Asynchronous Receiver/Transmitter) |
---|---|---|
全称 | 通用异步收发器 | 通用同步/异步收发器 |
同步/异步 | 异步:不共享时钟,数据通过起始位和停止位同步。 | 同步和异步 :<br/>- 异步模式 :与UART相同,不共享时钟。<br/>- 同步模式:共享时钟信号,适用于需要更高数据传输速率和更严格时序的场景,例如SPI、I2S等。 |
时钟 | 无独立时钟线,发送端和接收端各自使用内部时钟,通过波特率匹配实现通信。 | 异步模式下无独立时钟线。同步模式下有独立时钟线(CK引脚)。 |
数据传输 | 串行全双工,通过TX和RX引脚进行数据传输。 | 串行全双工,通过TX和、RX、CK(同步时钟)、NSS(从设备选择)引脚进行数据传输。 |
数据帧 | 起始位 + 数据位 (5-9位) + 奇偶校验位 (可选) + 停止位 (1或2位) | 异步模式下与UART相同。同步模式下数据帧结构更灵活,通常没有起始位和停止位,数据传输由时钟控制。 |
数据速率 | 相对较低,受波特率匹配和晶振精度限制。 | 同步模式下可实现更高的数据速率。 |
应用场景 | 简单设备通信(如与PC串口通信、蓝牙模块、GPS模块、GSM模块等) | 需要高速、高精度数据传输的场景(如与外部ADC、DAC、EEPROM、其他MCU等) |
STM32支持 | 大多数STM32微控制器都集成了多个UART接口。 | 大多数STM32微控制器都集成了多个USART接口。 |
流控制 | 硬件流控制 (CTS/RTS) 可选支持。 | 硬件流控制 (CTS/RTS) 可选支持。 |
1.1 UART与USART的工作原理
UART (异步模式)
- 数据帧结构 :
- 空闲态: 线路处于高电平。
- 起始位 (Start Bit): 由高电平跳变为低电平,持续一个位周期,标志着数据传输的开始。
- 数据位 (Data Bits): 通常为5到9位,低位在前,高位在后。
- 奇偶校验位 (Parity Bit - 可选): 用于检测数据传输过程中的错误。根据数据位中1的数量,奇校验使1的数量为奇数,偶校验使1的数量为偶数。
- 停止位 (Stop Bit): 高电平,通常为1位或2位,标志着数据帧的结束。
- 空闲态: 线路再次回到高电平。
- 同步方式: 发送端和接收端通过预设的波特率进行通信。接收端根据起始位检测到数据传输开始,然后按照波特率的节拍依次采样数据位和停止位。两者的波特率必须尽可能接近,否则可能导致数据错误。
USART (同步模式)
- 时钟同步: 在同步模式下,发送端和接收端共享一个时钟信号(CK引脚)。发送端在时钟的上升沿或下降沿输出数据,接收端在对应的时钟沿采样数据。这确保了数据传输的严格同步性,从而实现更高的传输速率和更可靠的数据传输。
- 应用: 常常用于与需要外部时钟的设备进行通信,例如SPI、I2S协议的底层实现。STM32的USART模块可以配置为同步主模式或从模式。
2. STM32中UART/USART开发流程
在STM32中开发UART/USART通信通常涉及以下几个步骤。我们将以STM32CubeMX和HAL库为例进行详细讲解。
2.1 硬件连接
确保你的STM32开发板与外部设备(如PC的USB转串口模块、其他MCU、蓝牙模块等)正确连接。
- STM32 TX 引脚连接到 外部设备 RX 引脚。
- STM32 RX 引脚连接到 外部设备 TX 引脚。
- GND 连接到 GND。
- 如果使用硬件流控制,还需要连接 RTS 和 CTS 引脚。
2.2 STM32CubeMX配置
STM32CubeMX是一款强大的图形化工具,可以帮助我们快速配置STM32微控制器,生成初始化代码。
- 新建工程: 打开STM32CubeMX,选择"New Project",选择你的STM32型号。
- 配置时钟: 在"System Core" -> "RCC"中,配置高速外部晶振(HSE)或内部高速时钟(HSI)。然后到"Clock Configuration"页面,配置系统时钟树,确保外设时钟(APB1/APB2)满足UART/USART需求。
- 配置UART/USART
- 在"Connectivity"分类下,选择需要使用的UART/USART外设(例如:USART1)。
- 在配置界面中,将"Mode"设置为"Asynchronous"(异步模式,即UART功能)或"Synchronous"(同步模式,即USART功能)。
- 异步模式(UART)配置
- Baud Rate (波特率): 设置通信速率,例如:115200 bps。
- Word Length (数据位): 8 Bits或9 Bits。
- Parity (奇偶校验): None(无)、Even(偶校验)或Odd(奇校验)。
- Stop Bits (停止位): 1 Bit或2 Bits。
- Data Direction (数据方向): Receive and Transmit。
- Hardware Flow Control (硬件流控制): None(无)、RTS/CTS。
- 同步模式(USART)配置
- 除了上述异步模式的配置外,还需要配置"Clock Polarity"和"Clock Phase",这对于同步通信至关重要,需要与外部设备的设置相匹配。
- 通常还需要配置"NSS Signal"为"Hardware NSS Output"或"Software NSS"。
- GPIO设置: CubeMX会自动为UART/USART配置相应的TX和RX引脚。
- 配置中断: 如果需要使用中断接收或发送数据,在"NVIC Settings"选项卡中,勾选对应UART/USART中断的"Enabled"复选框。
- 生成代码: 点击"Project"->"Generate Code"生成工程文件。选择你的IDE(MDK-ARM、TrueSTUDIO等)。
2.3 编写应用程序代码
生成代码后,在你的IDE中打开工程。主要的工作将在main.c
文件和其他相关文件中完成。
2.3.1 初始化 (由CubeMX生成)
CubeMX会生成UART/USART的初始化代码,通常在main.c
中的MX_USARTx_UART_Init()
或MX_USARTx_Init()
函数中。
c
// 示例:USART1初始化代码片段 (由CubeMX生成)
UART_HandleTypeDef huart1;
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
2.3.2 数据发送
HAL库提供了多种数据发送方式:
-
轮询发送 (阻塞式): 简单易用,但会阻塞CPU直到发送完成。
cchar tx_data[] = "Hello, STM32 UART!\r\n"; HAL_UART_Transmit(&huart1, (uint8_t*)tx_data, strlen(tx_data), HAL_MAX_DELAY);
&huart1
: UART句柄。(uint8_t*)tx_data
: 要发送的数据的指针。strlen(tx_data)
: 要发送的数据的长度。HAL_MAX_DELAY
: 超时时间,HAL_MAX_DELAY
表示一直等待直到发送完成。
-
中断发送 (非阻塞式): 在后台发送数据,发送完成后会触发中断。
cchar tx_data[] = "Hello, Interrupt!\r\n"; HAL_UART_Transmit_IT(&huart1, (uint8_t*)tx_data, strlen(tx_data)); // 发送完成后会调用HAL_UART_TxCpltCallback()回调函数
-
DMA发送 (非阻塞式): 效率最高,适用于大量数据传输,DMA控制器在后台完成数据传输,CPU可以执行其他任务。
cchar tx_data[] = "Hello, DMA!\r\n"; HAL_UART_Transmit_DMA(&huart1, (uint8_t*)tx_data, strlen(tx_data)); // 发送完成后会调用HAL_UART_TxCpltCallback()回调函数
2.3.3 数据接收
HAL库也提供了多种数据接收方式:
-
轮询接收 (阻塞式): 等待接收到指定长度的数据后返回。
cuint8_t rx_buffer[20]; HAL_UART_Receive(&huart1, rx_buffer, sizeof(rx_buffer), HAL_MAX_DELAY); // 接收到20字节数据后继续执行
-
中断接收 (非阻塞式): 接收到1个字节或指定长度的数据后会触发中断,可以在中断回调函数中处理数据。
cuint8_t rx_byte; // 启动一个字节的中断接收 HAL_UART_Receive_IT(&huart1, &rx_byte, 1); // 在HAL_UART_RxCpltCallback()中处理接收到的字节,并再次启动接收
在
stm32f1xx_it.c
中(或其他中断服务文件)会调用HAL库的回调函数:cvoid USART1_IRQHandler(void) { HAL_UART_IRQHandler(&huart1); }
在
main.c
(或其他自定义文件)中重写接收完成回调函数:cuint8_t rx_buffer[BUFFER_SIZE]; uint16_t rx_index = 0; // 接收数据索引 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { // 接收到1个字节,将其存储到缓冲区 rx_buffer[rx_index++] = huart->RxXferData[0]; // 获取接收到的字节 if (rx_index >= BUFFER_SIZE) { rx_index = 0; // 缓冲区满,重置索引或进行处理 } // 重新启动单字节接收,以便接收下一个字节 HAL_UART_Receive_IT(&huart1, (uint8_t*)&huart1.RxXferData[0], 1); } }
- 注意: 对于中断接收,通常的做法是每次接收一个字节,然后在回调函数中处理接收到的字节,并再次启动单字节接收,以实现连续接收。
-
DMA接收 (非阻塞式): 最推荐的方式,将接收到的数据直接传输到内存缓冲区,大大减轻CPU负担。
cuint8_t rx_buffer[BUFFER_SIZE]; HAL_UART_Receive_DMA(&huart1, rx_buffer, BUFFER_SIZE); // 接收到BUFFER_SIZE长度的数据后会调用HAL_UART_RxCpltCallback()回调函数
与中断接收类似,需要重写
HAL_UART_RxCpltCallback()
来处理接收到的数据。
2.4 编译和烧录
完成代码编写后,编译工程并将其烧录到STM32微控制器中。
2.5 调试和测试
- 使用串口调试助手: 在PC上打开串口调试助手(如XCOM、Putty、SecureCRT等),配置与STM32相同的波特率、数据位、停止位和奇偶校验。
- 发送/接收数据: 在PC端发送数据到STM32,观察STM32是否正确接收并处理。在STM32端发送数据,观察PC端是否正确显示。
- 调试工具: 使用STM32的SWD/JTAG调试器(如ST-Link),可以在IDE中进行单步调试、设置断点、查看变量值等,以便找出问题。
3. 常见问题与优化
- 波特率不匹配: 这是最常见的问题。确保发送端和接收端的波特率完全一致。
- 引脚连接错误: 检查TX和RX引脚是否交叉连接。
- 时钟配置错误: 确保UART/USART外设的时钟频率配置正确,影响波特率的计算。
- 缓冲区溢出: 在使用中断或DMA接收时,如果没有及时处理接收到的数据,可能会导致缓冲区溢出。需要合理设计缓冲区大小和数据处理逻辑。
- 噪声干扰: 在复杂的电磁环境下,通信线路可能受到噪声干扰,导致数据错误。可以考虑使用屏蔽线、差分信号传输或增加奇偶校验来提高鲁棒性。
- 中断优先级: 如果同时使用多个中断,需要合理设置中断优先级,避免中断嵌套问题。
- 空闲帧检测: UART通常会检测到接收线路上的空闲帧(长时间高电平)来判断数据传输是否结束。
- 错误处理: 在实际应用中,需要考虑接收错误(帧错误、奇偶校验错误、溢出错误等),并在代码中添加相应的错误处理机制。