UART 编程详解
一、UART 基础概念
对于刚入门电子行业的工程师来说,UART 编程是一个绕不开的重要知识点。那么,UART 到底是什么呢?UART 全称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),是一种通用的数据通信协议。它的独特之处在于无需时钟信号,仅通过发送线(TX)和接收线(RX)就能实现设备间的数据传输。在通信时,数据会以帧的形式发送,每帧数据包含起始位、数据位、校验位和停止位,这些部分共同保障了数据传输的准确性。
UART 协议是一种异步串行通信协议,它在数据传输过程中,发送方和接收方不需要共享同一个时钟信号,而是通过约定好的波特率来保证数据的正确传输。这一特点使得 UART 在电子设备通信中得到了广泛的应用,因为它可以简化硬件连接,降低系统成本。
(一)UART 的发展历程
UART 的发展可以追溯到早期的计算机通信时代。在计算机发展的初期,设备之间的数据传输需要一种简单、可靠的方式,UART 应运而生。随着电子技术的不断发展,UART 的性能也在不断提升,从最初的低波特率、简单功能,逐渐发展到支持更高的波特率、更复杂的校验方式等,以满足不同场景下的数据传输需求。
在早期的计算机系统中,UART 主要用于连接调制解调器(Modem),实现计算机与外部网络的通信。随着嵌入式系统的兴起,UART 又成为了单片机与其他外设通信的重要手段,如单片机与传感器、显示屏、上位机等设备的通信都大量采用了 UART 协议。
(二)UART 的特点
-
异步通信:无需时钟信号,发送方和接收方通过约定的波特率进行数据同步。
-
串行传输:数据以 bit 为单位,按照顺序依次传输,相比并行传输,减少了信号线的数量,降低了硬件成本和布线难度。
-
帧结构固定:每帧数据包含起始位、数据位、校验位和停止位,结构固定,便于硬件和软件的实现。
-
灵活性高:可以通过设置不同的参数,如波特率、数据位、校验位和停止位等,适应不同的通信需求。
二、UART 工作原理
UART 的工作原理主要基于数据帧的传输和接收。当发送数据时,UART 控制器会将并行数据转换为串行数据,并按照一定的帧格式添加起始位、校验位和停止位,然后通过发送线(TX)逐位发送出去。接收方的 UART 控制器则通过接收线(RX)逐位接收串行数据,去除起始位、校验位和停止位后,将串行数据转换为并行数据,供接收设备处理。
(一)数据帧格式
数据帧是 UART 通信中数据传输的基本单位,其格式如下:
-
起始位:通常为 1 位,用于表示数据传输的开始。起始位的电平为低电平,与空闲状态的高电平形成鲜明对比,便于接收方检测到数据的开始。
-
数据位:可以是 5、6、7 或 8 位,用于传输实际的有效数据。在大多数情况下,采用 8 位数据位,因为它可以表示 0-255 的数值范围,满足大多数数据传输的需求。
-
校验位:可选位,用于检验数据传输的正确性。常见的校验方式有奇校验、偶校验和无校验。奇校验要求数据位和校验位中 1 的个数为奇数;偶校验要求数据位和校验位中 1 的个数为偶数;无校验则不进行校验,适用于对数据传输准确性要求不高的场景。
-
停止位:通常为 1、1.5 或 2 位,用于表示数据传输的结束。停止位的电平为高电平,接收方检测到停止位后,就知道一帧数据已经传输完毕。
(二)波特率
波特率是 UART 通信中的一个重要参数,它表示单位时间内传输的二进制位数,单位为波特(Baud)。例如,波特率为 9600 表示每秒可以传输 9600 个二进制位。
在 UART 通信中,发送方和接收方必须设置相同的波特率,否则会导致数据传输错误。因为接收方是根据波特率来确定每个位的持续时间的,如果波特率不匹配,接收方就无法正确识别数据位。
常见的波特率值有 300、600、1200、2400、4800、9600、19200、38400、57600、115200 等。在实际应用中,应根据通信距离、数据传输速率要求等因素选择合适的波特率。一般来说,通信距离越远,应选择较低的波特率,以减少信号衰减和干扰对数据传输的影响;数据传输速率要求越高,则应选择较高的波特率。
(三)数据传输过程
-
发送过程:
发送设备的 CPU 将需要发送的数据写入 UART 的发送缓冲区,UART 控制器从发送缓冲区中读取数据,并将其转换为串行数据。然后,按照设定的帧格式,在数据的前面添加起始位,后面添加校验位(如果有)和停止位,形成一帧完整的数据。最后,UART 控制器通过发送线(TX)将这帧数据逐位发送出去。
-
接收过程:
接收设备的 UART 控制器通过接收线(RX)不断监测线路上的电平变化。当检测到起始位(低电平)时,便开始接收数据。接收方根据设定的波特率,确定每个位的采样时间,逐位接收数据位,并将其暂存起来。当接收到停止位(高电平)时,表明一帧数据接收完毕。UART 控制器会对接收的数据进行校验(如果设置了校验位),如果校验通过,则将接收的数据转换为并行数据,存入接收缓冲区,同时发出中断信号通知 CPU 读取数据;如果校验失败,则会产生错误标志,提示数据传输出错。
三、UART 硬件结构
UART 的硬件结构主要包括 UART 控制器、收发器、引脚等部分,下面将详细介绍各部分的功能和作用。
(一)UART 控制器
UART 控制器是 UART 硬件的核心部分,它负责数据的收发控制、帧格式的生成和解析、波特率的产生等功能。UART 控制器通常集成在微处理器(MCU)、微控制器(MPU)或专用的通信芯片中。
UART 控制器内部包含发送缓冲区、接收缓冲区、波特率发生器、控制寄存器、状态寄存器等模块。发送缓冲区用于暂存 CPU 需要发送的数据;接收缓冲区用于暂存接收到的数据;波特率发生器用于产生与设定波特率对应的时钟信号,为数据的收发提供时间基准;控制寄存器用于设置 UART 的工作参数,如波特率、数据位、校验位、停止位等;状态寄存器用于反映 UART 的工作状态,如发送缓冲区空、接收缓冲区满、数据传输错误等。
(二)收发器
收发器主要负责将 UART 控制器输出的 TTL 电平信号转换为符合 RS - 232、RS - 485 等标准的信号,或者将外部输入的符合相关标准的信号转换为 TTL 电平信号,以便 UART 控制器进行处理。
-
RS - 232 收发器:RS - 232 是一种常用的串行通信标准,它规定了信号的电气特性、引脚定义等。RS - 232 信号的电平范围较大,通常为 - 15V 到 + 15V,其中逻辑 1 为 - 3V 到 - 15V,逻辑 0 为 + 3V 到 + 15V。RS - 232 收发器可以实现 TTL 电平(0V 和 + 5V)与 RS - 232 电平之间的转换,使得 UART 设备可以与符合 RS - 232 标准的设备进行通信,如计算机的串口、调制解调器等。
-
RS - 485 收发器:RS - 485 是一种差分串行通信标准,它具有抗干扰能力强、传输距离远等优点,适用于工业控制、楼宇自动化等领域。RS - 485 采用差分信号传输,即通过两根信号线(A 和 B)之间的电压差来表示逻辑状态,当 A 线电压比 B 线电压高 200mV 以上时,表示逻辑 1;当 B 线电压比 A 线电压高 200mV 以上时,表示逻辑 0。RS - 485 收发器可以将 UART 控制器输出的 TTL 电平转换为 RS - 485 差分信号,也可以将外部输入的 RS - 485 差分信号转换为 TTL 电平。
(三)引脚定义
UART 通信通常需要以下几个关键引脚:
-
TX(Transmit):发送引脚,用于输出串行数据。
-
RX(Receive):接收引脚,用于输入串行数据。
-
GND(Ground):接地引脚,为整个电路提供参考地电位,保证信号的稳定传输。
在实际应用中,不同的设备可能会有额外的引脚,如 RTS(Request To Send)、CTS(Clear To Send)等,这些引脚用于实现硬件流控制,防止数据溢出。当发送方的发送缓冲区即将满时,会通过 RTS 引脚通知接收方暂停发送数据;当接收方的接收缓冲区有足够空间时,会通过 CTS 引脚通知发送方继续发送数据。
(四)UART 硬件结构示意图
上图展示了 UART 的硬件结构示意图,CPU 与 UART 控制器相连,UART 控制器内部包含多个功能模块,收发器连接 UART 控制器和外部引脚,实现信号的转换和传输。
四、UART 编程步骤
UART 编程主要包括初始化 UART、数据发送和数据接收三个主要步骤,下面将详细介绍每个步骤的具体实现方法。
(一)初始化 UART
初始化 UART 是 UART 编程的第一步,其目的是设置 UART 的工作参数,如波特率、数据位、校验位、停止位等,以确保 UART 能够按照预期的方式进行通信。
-
选择 UART 端口:如果微处理器或微控制器有多个 UART 端口,需要首先选择要使用的 UART 端口。
-
使能 UART 时钟:UART 控制器需要时钟信号才能工作,因此需要使能相应的时钟源。不同的芯片可能有不同的时钟使能方式,一般可以通过配置相应的寄存器来实现。
-
设置波特率:根据通信需求,计算出波特率对应的分频值,并将其写入波特率寄存器。波特率的计算公式如下:
波特率 = 时钟频率 /(分频值 × 16)
其中,时钟频率是 UART 控制器的工作时钟频率,分频值是一个整数,用于对时钟频率进行分频,以得到所需的波特率。
例如,假设 UART 控制器的工作时钟频率为 16MHz,要设置波特率为 9600,则分频值可以通过以下计算得到:
分频值 = 时钟频率 /(波特率 × 16)= 16000000 /(9600 × 16)≈ 104.17
由于分频值必须为整数,因此可以取 104,此时实际波特率为 16000000 /(104 × 16)≈ 9615,与目标波特率 9600 非常接近,误差在可接受范围内。
-
设置数据位:通过配置控制寄存器,选择数据位的位数,如 5、6、7 或 8 位。
-
设置校验位:根据需要,选择校验方式,如奇校验、偶校验或无校验。
-
设置停止位:选择停止位的位数,如 1、1.5 或 2 位。
以下是一个基于 STM32 单片机的 UART 初始化代码示例(使用 HAL 库):
\#include "stm32f1xx\_hal.h"
UART\_HandleTypeDef huart2;
void MX\_USART2\_UART\_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 9600;
huart2.Init.WordLength = UART\_WORDLENGTH\_8B;
huart2.Init.StopBits = UART\_STOPBITS\_1;
huart2.Init.Parity = UART\_PARITY\_NONE;
huart2.Init.Mode = UART\_MODE\_TX\_RX;
huart2.Init.HwFlowCtl = UART\_HWCONTROL\_NONE;
huart2.Init.OverSampling = UART\_OVERSAMPLING\_16;
if (HAL\_UART\_Init(\&huart2) != HAL\_OK)
{
Error\_Handler();
}
}
void HAL\_UART\_MspInit(UART\_HandleTypeDef\* uartHandle)
{
GPIO\_InitTypeDef GPIO\_InitStruct = {0};
if(uartHandle->Instance==USART2)
{
\_\_HAL\_RCC\_USART2\_CLK\_ENABLE();
\_\_HAL\_RCC\_GPIOA\_CLK\_ENABLE();
GPIO\_InitStruct.Pin = GPIO\_PIN\_2;
GPIO\_InitStruct.Mode = GPIO\_MODE\_AF\_PP;
GPIO\_InitStruct.Speed = GPIO\_SPEED\_FREQ\_HIGH;
HAL\_GPIO\_Init(GPIOA, \&GPIO\_InitStruct);
GPIO\_InitStruct.Pin = GPIO\_PIN\_3;
GPIO\_InitStruct.Mode = GPIO\_MODE\_INPUT;
GPIO\_InitStruct.Pull = GPIO\_NOPULL;
HAL\_GPIO\_Init(GPIOA, \&GPIO\_InitStruct);
}
}
在上述代码中,首先定义了一个 UART_HandleTypeDef 类型的结构体 huart2,用于存储 UART2 的配置信息。然后,在 MX_USART2_UART_Init 函数中,设置了 UART2 的波特率为 9600、数据位为 8 位、停止位为 1 位、无校验、收发模式、无硬件流控制和 16 倍过采样。最后,通过 HAL_UART_Init 函数初始化 UART2。HAL_UART_MspInit 函数用于初始化 UART2 的相关 GPIO 引脚,将 PA2 配置为复用推挽输出(TX),PA3 配置为输入(RX)。
(二)数据发送
数据发送是 UART 编程的重要环节,其目的是将数据通过 UART 发送出去。UART 数据发送可以采用查询方式、中断方式或 DMA 方式。
- 查询方式:查询方式是一种简单的发送方式,通过不断查询发送缓冲区的状态来判断是否可以发送数据。当发送缓冲区为空时,将数据写入发送缓冲区,然后等待数据发送完成。
以下是一个使用查询方式发送数据的代码示例:
void UART\_SendData(UART\_HandleTypeDef \*huart, uint8\_t \*pData, uint16\_t Size)
{
for (uint16\_t i = 0; i < Size; i++)
{
while (!(huart->Instance->SR & USART\_SR\_TXE)); // 等待发送缓冲区为空
huart->Instance->DR = pData\[i]; // 将数据写入发送数据寄存器
}
while (!(huart->Instance->SR & USART\_SR\_TC)); // 等待发送完成
}
在上述代码中,通过循环将数据逐个写入发送数据寄存器(DR),在写入每个数据之前,先查询发送缓冲区为空(TXE)标志位,确保可以写入数据。全部数据发送完成后,等待发送完成(TC)标志位,以确保数据已经全部发送出去。
- 中断方式:中断方式是一种高效的发送方式,当发送缓冲区为空时,UART 控制器会产生一个发送中断,CPU 响应中断后,将数据写入发送缓冲区。这种方式可以避免 CPU 的空等,提高 CPU 的利用率。
以下是一个使用中断方式发送数据的代码示例(基于 STM32 HAL 库):
uint8\_t tx\_buffer\[] = "Hello, UART!";
uint16\_t tx\_len = sizeof(tx\_buffer) - 1;
void UART\_SendData\_IT(UART\_HandleTypeDef \*huart, uint8\_t \*pData, uint16\_t Size)
{
HAL\_UART\_Transmit\_IT(huart, pData, Size);
}
void HAL\_UART\_TxCpltCallback(UART\_HandleTypeDef \*huart)
{
// 发送完成回调函数,可以在这里进行后续处理
}
在上述代码中,通过 HAL_UART_Transmit_IT 函数启动中断方式发送数据,当数据发送完成后,会调用 HAL_UART_TxCpltCallback 回调函数。
- DMA 方式:DMA(直接存储器访问)方式可以实现数据在存储器和 UART 之间的直接传输,而不需要 CPU 的干预,进一步提高了数据传输的效率,适用于大量数据的发送。
以下是一个使用 DMA 方式发送数据的代码示例(基于 STM32 HAL 库):
uint8\_t tx\_buffer\[] = "Hello, UART DMA!";
uint16\_t tx\_len = sizeof(tx\_buffer) - 1;
void UART\_SendData\_DMA(UART\_HandleTypeDef \*huart, uint8\_t \*pData, uint16\_t Size)
{
HAL\_UART\_Transmit\_DMA(huart, pData, Size);
}
void HAL\_UART\_TxCpltCallback(UART\_HandleTypeDef \*huart)
{
// 发送完成回调函数,可以在这里进行后续处理
HAL\_DMA\_Abort(huart->hdmatx); // 关闭DMA通道
}
在上述代码中,通过 HAL_UART_Transmit_DMA 函数启动 DMA 方式发送数据,当数据发送完成后,会调用 HAL_UART_TxCpltCallback 回调函数,在回调函数中可以关闭 DMA 通道。
(三)数据接收
数据接收与数据发送类似,也可以采用查询方式、中断方式或 DMA 方式。
- 查询方式:查询方式通过不断查询接收缓冲区的状态来判断是否有数据接收。当接收缓冲区有数据时,读取接收缓冲区的数据。
以下是一个使用查询方式接收数据的代码示例:
void UART\_ReceiveData(UART\_HandleTypeDef \*huart, uint8\_t \*pData, uint16\_t Size)
{
for (uint16\_t i = 0; i < Size; i++)
{
while (!(huart->Instance->SR & USART\_SR\_RXNE)); // 等待接收缓冲区有数据
pData\[i] = huart->Instance->DR; // 读取接收数据寄存器
}
}
在上述代码中,通过循环读取接收数据寄存器(DR)中的数据,在读取每个数据之前,先查询接收缓冲区非空(RXNE)标志位,确保有数据可以读取。
- 中断方式:当接收缓冲区有数据时,UART 控制器会产生一个接收中断,CPU 响应中断后,读取接收缓冲区的数据。
以下是一个使用中断方式接收数据的代码示例(基于 STM32 HAL 库):
uint8\_t rx\_buffer\[100];
uint16\_t rx\_len = 0;
void UART\_ReceiveData\_IT(UART\_HandleTypeDef \*huart, uint8\_t \*pData, uint16\_t Size)
{
HAL\_UART\_Receive\_IT(huart, pData, Size);
}
void HAL\_UART\_RxCpltCallback(UART\_HandleTypeDef \*huart)
{
// 接收完成回调函数,可以在这里处理接收到的数据
rx\_len = 1; // 假设接收1个字节
// 进行数据处理
UART\_ReceiveData\_IT(huart, rx\_buffer, 1); // 继续接收数据
}
在上述代码中,通过 HAL_UART_Receive_IT 函数启动中断方式接收数据,当接收到指定数量的数据后,会调用 HAL_UART_RxCpltCallback 回调函数。在回调函数中,可以处理接收到的数据,并继续启动接收。
- DMA 方式:DMA 方式也可以用于数据接收,实现数据从 UART 到存储器的直接传输。
以下是一个使用 DMA 方式接收数据的代码示例(基于 STM32 HAL 库):
uint8\_t rx\_buffer\[100];
uint16\_t rx\_len = 100;
void UART\_ReceiveData\_DMA(UART\_HandleTypeDef \*huart, uint8\_t \*pData, uint16\_t Size)
{
HAL\_UART\_Receive\_DMA(huart, pData, Size);
}
void HAL\_UART\_RxCpltCallback(UART\_HandleTypeDef \*huart)
{
// 接收完成回调函数,可以在这里处理接收到的数据
HAL\_DMA\_Abort(huart->hdmarx); // 关闭DMA通道
// 进行数据处理
UART\_ReceiveData\_DMA(huart, rx\_buffer, rx\_len); // 继续接收数据
}
在上述代码中,通过 HAL_UART_Receive_DMA 函数启动 DMA 方式接收数据,当接收到指定数量的数据后,会调用 HAL_UART_RxCpltCallback 回调函数。在回调函数中,可以处理接收到的数据,并继续启动接收。
五、UART 应用场景
UART 协议由于其简单、可靠、成本低等优点,在电子设备中得到了广泛的应用,以下是一些常见的应用场景。
(一)嵌入式系统中单片机与上位机通信
在嵌入式系统开发过程中,单片机通常需要与上位机(如计算机)进行通信,以实现程序下载、数据调试等功能。UART 是实现这种通信的常用方式,通过 USB - to - UART 转换模块,可以将单片机的 UART 接口与计算机的 USB 接口相连,上位机通过串口调试助手等软件与单片机进行数据交互。
例如,在单片机开发中,开发者可以通过 UART 向上位机发送调试信息,如变量的值、程序运行状态等,以便及时了解程序的运行情况,进行故障排查。同时,上位机也可以通过 UART 向单片机发送控制命令,控制单片机的工作状态。
(二)传感器与微控制器通信
许多传感器都支持 UART 通信接口,如温湿度传感器、气体传感器、加速度传感器等。微控制器可以通过 UART 与这些传感器进行通信,读取传感器采集的数据。
以温湿度传感器 SHT30 为例,它支持 I2C 和 UART 两种通信方式。当采用 UART 通信方式时,微控制器可以通过发送指令的方式读取 SHT30 采集的温湿度数据,然后对数据进行处理和显示。
(三)显示屏与控制器通信
一些字符型显示屏、图形点阵显示屏等也支持 UART 通信接口,控制器可以通过 UART 向显示屏发送显示指令和数据,控制显示屏显示相应的内容。
例如,1602 字符型显示屏通过 UART 接口与单片机相连,单片机可以向显示屏发送指令,设置显示位置、显示内容等,实现信息的显示。
(四)工业控制领域
在工业控制领域,UART 常用于设备之间的短距离通信,如 PLC(可编程逻辑控制器)与传感器、执行器之间的通信。由于工业环境中存在较强的电磁干扰,采用 RS - 485 标准的 UART 通信可以提高抗干扰能力,保证数据传输的可靠性。
例如,在一个自动化生产线上,PLC 通过 RS - 485 总线与多个传感器和执行器相连,传感器将采集到的生产数据通过 UART 发送给 PLC,PLC 根据数据进行逻辑判断,然后通过 UART 向执行器发送控制命令,实现生产过程的自动化控制。
(五)智能家居领域
在智能家居领域,UART 可以用于智能家居设备之间的通信,如智能网关与智能开关、智能灯具、智能窗帘等设备之间的通信。通过 UART,智能网关可以接收各设备的状态信息,并向设备发送控制指令,实现智能家居的集中控制。
例如,智能网关通过 UART 与智能开关相连,当用户通过手机 APP 发出开灯指令时,手机 APP 将指令发送给智能网关,智能网关再通过 UART 将指令发送给智能开关,智能开关接收到指令后执行开灯操作,并将操作结果反馈给智能网关。
六、UART 编程常见问题及解决方法
在 UART 编程过程中,可能会遇到各种问题,如数据传输错误、通信中断等,以下是一些常见问题及解决方法。
(一)数据传输错误
-
波特率不匹配:发送方和接收方的波特率设置不一致,会导致接收方无法正确识别数据位,从而产生数据传输错误。解决方法是确保发送方和接收方设置相同的波特率。在实际应用中,可以使用示波器测量 UART 信号的波特率,以验证波特率是否正确。
-
干扰:在数据传输过程中,外部电磁干扰可能会导致信号失真,从而产生数据传输错误。解决方法包括:
-
采用屏蔽线传输 UART 信号,减少外部干扰的影响。
-
增加接地措施,降低接地电阻,提高抗干扰能力。
-
在 UART 信号线上添加滤波电容,滤除高频干扰信号。
- 校验位错误:如果设置了校验位,当数据传输过程中出现错误时,校验位会检测到错误。解决方法是检查数据传输过程中的干扰情况,确保数据传输的稳定性;如果干扰较强,可以考虑采用无校验位,并通过软件方式进行数据校验,如添加校验和、CRC 校验等。
(二)通信中断
-
硬件连接问题:UART 引脚连接错误、接触不良等硬件问题可能会导致通信中断。解决方法是检查 UART 引脚的连接是否正确,确保 TX 与 RX 交叉连接(发送方的 TX 连接接收方的 RX,发送方的 RX 连接接收方的 TX),GND 连接正确;检查连接器是否接触良好,必要时更换连接器。
-
电源问题:UART 设备的电源电压不稳定或供电不足,可能会导致设备工作异常,从而引起通信中断。解决方法是确保 UART 设备的电源电压稳定在规定范围内,检查电源线路是否有压降过大的情况,必要时更换电源。
-
软件问题:软件程序中的错误,如发送或接收缓冲区溢出、中断处理不当等,也可能导致通信中断。解决方法是:
-
合理设置发送和接收缓冲区的大小,避免缓冲区溢出。
-
正确处理中断,确保中断服务程序能够及时响应和处理中断事件,避免中断嵌套或中断丢失。
-
在程序中添加错误检测和恢复机制,当检测到通信中断时,能够自动重新初始化 UART,恢复通信。
(三)数据丢失
- 接收缓冲区溢出:当接收数据的速度快于 CPU 处理数据的速度时,接收缓冲区可能会溢出,导致数据丢失。解决方法是:
-
增大接收缓冲区的大小,提高缓冲区的存储能力。
-
采用中断方式或 DMA 方式接收数据,提高数据接收的效率,减少 CPU 的负担。
-
在程序中及时处理接收缓冲区的数据,避免数据积压。
- 发送缓冲区溢出:当发送数据的速度快于 UART 发送数据的速度时,发送缓冲区可能会溢出,导致数据丢失。解决方法是:
-
合理控制发送数据的速度,避免发送过快。
-
采用中断方式或 DMA 方式发送数据,提高数据发送的效率。
-
在程序中检查发送缓冲区的状态,当缓冲区满时,暂停发送数据,等待缓冲区有空间后再继续发送。
七、UART 调试工具
在 UART 编程和调试过程中,使用合适的调试工具可以提高调试效率,快速定位问题。以下是一些常用的 UART 调试工具。
(一)串口调试助手
串口调试助手是一种常用的软件工具,它可以通过计算机的串口与 UART 设备进行通信,实现数据的发送和接收。串口调试助手通常具有以下功能:
-
选择串口:可以选择计算机上的多个串口进行通信。
-
设置波特率、数据位、校验位、停止位等参数。
-
发送数据:可以手动输入数据发送,也可以发送文件中的数据。
-
接收数据:可以实时显示接收到的数据,并以十六进制或 ASCII 码形式显示。
-
数据存储:可以将接收到的数据存储到文件中,便于后续分析。
常见的串口调试助手有 SSCOM、XCOM、Tera Term 等。
(二)示波器
示波器是一种电子测量仪器,它可以显示电信号的波形。在 UART 调试中,示波器可以用于测量 UART 信号的波特率、脉冲宽度、电平变化等,帮助判断数据传输是否正常。
例如,通过示波器观察 UART 发送的信号波形,可以判断起始位、数据位、校验位和停止位的格式是否正确,波特率是否符合设定值,信号是否存在干扰等。
(三)逻辑分析仪
逻辑分析仪是一种用于分析数字逻辑信号的仪器,它可以同时采集多个数字信号,并以时序图的形式显示出来。在 UART 调试中,逻辑分析仪可以同时采集 TX 和 RX 信号,分析数据传输的时序关系,帮助定位数据传输错误的原因。
与示波器相比,逻辑分析仪具有更高的采样率和更多的通道数,可以同时分析多个信号,适用于复杂的数字系统调试。
(四)USB - to - UART 转换模块
USB - to - UART 转换模块可以将计算机的 USB 接口转换为 UART 接口,便于计算机与 UART 设备进行通信。常见的 USB - to - UART 转换芯片有 CH340、PL2303 等,这些芯片支持多种操作系统,使用方便。
在调试过程中,将 USB - to - UART 转换模块的 TX 引脚连接到 UART 设备的 RX 引脚,RX 引脚连接到 UART 设备的 TX 引脚,GND 引脚连接到 UART 设备的 GND 引脚,然后将模块的 USB 接口连接到计算机,就可以通过串口调试助手与 UART 设备进行通信了。
八、UART 与其他通信协议的比较
在电子设备通信中,除了 UART 协议外,还有 I2C、SPI 等常用的通信协议,它们各有特点,适用于不同的应用场景。
(一)UART 与 I2C 的比较
-
通信线路:UART 需要两根通信线(TX 和 RX),加上 GND 共三根线;I2C 需要两根通信线(SDA 和 SCL),加上 GND 共三根线。
-
通信方式:UART 是异步通信,无需时钟信号;I2C 是同步通信,需要时钟信号(SCL)来同步数据传输。
-
设备数量:UART 通常用于两个设备之间的通信;I2C 总线上可以连接多个主设备和多个从设备,通过地址来区分不同的设备。
-
传输速率:UART 的传输速率相对较低,一般在几十 kbps 到几 Mbps 之间;I2C 的传输速率较高,标准模式下为 100kbps,快速模式下为 400kbps,高速模式下为 3.4Mbps。
-
抗干扰能力:UART 采用单端信号传输,抗干扰能力相对较弱;I2C 采用差分信号传输(SDA 和 SCL 相对于 GND),抗干扰能力比 UART 强。
-
应用场景:UART 适用于两个设备之间的短距离、低速率通信,如单片机与上位机、传感器之间的通信;I2C 适用于多个设备之间的短距离、中高速率通信,如单片机与 EEPROM、LCD 显示屏等外设之间的通信。
(二)UART 与 SPI 的比较
-
通信线路:UART 需要两根通信线(TX 和 RX),加上 GND 共三根线;SPI 需要四根通信线(MOSI、MISO、SCK、CS),其中 CS 为片选信号,每个从设备需要一根 CS 线,加上 GND 共五根线(对于一个主设备和一个从设备)。
-
通信方式:UART 是异步通信;SPI 是同步通信,需要时钟信号(SCK)来同步数据传输。
-
设备数量:UART 通常用于两个设备之间的通信;SPI 总线上可以连接一个主设备和多个从设备,通过片选信号(CS)来选择要通信的从设备。
-
传输速率:UART 的传输速率相对较低;SPI 的传输速率较高,可以达到几十 Mbps 甚至更高。
-
抗干扰能力:UART 抗干扰能力较弱;SPI 采用差分信号传输(MOSI 和 MISO 相对于 GND),抗干扰能力比 UART 强,但比 I2C 弱。
-
应用场景:UART 适用于两个设备之间的短距离、低速率通信;SPI 适用于主设备与多个从设备之间的短距离、高速率通信,如单片机与 Flash 存储器、AD 转换器等外设之间的通信。
九、UART 的发展趋势
随着电子技术的不断发展,UART 协议也在不断演进和完善,以下是一些 UART 的发展趋势。
(一)更高的传输速率
随着数据传输需求的不断增加,对 UART 的传输速率提出了更高的要求。目前,一些新型的 UART 控制器已经支持更高的波特率,如 1Mbps、5Mbps 甚至更高,以满足高速数据传输的需求。
(二)更好的抗干扰性能
在复杂的电磁环境中,提高 UART 的抗干扰性能至关重要。未来的 UART 技术可能会采用更先进的信号处理技术,如差分信号传输、纠错编码等,以提高抗干扰能力,保证数据传输的可靠性。
(三)集成更多功能
为了简化系统设计,提高系统的集成度,未来的 UART 控制器可能会集成更多的功能,如硬件流控制、自动波特率检测、数据加密解密等,以满足不同应用场景的需求。
(四)与其他协议的融合
随着物联网、工业互联网等技术的发展,设备之间的通信需要更加灵活和多样化。未来的 UART 技术可能会与其他通信协议(如蓝牙、Wi - Fi 等)进行融合,实现更广泛的设备互联。
十、总结
UART 作为一种通用的异步串行通信协议,具有简单、可靠、成本低等优点,在电子设备通信中得到了广泛的应用。本文详细介绍了 UART 的基础概念、工作原理、硬件结构、编程步骤、应用场景、常见问题及解决方法、调试工具、与其他通信协议的比较以及发展趋势等内容。
对于刚入门电子行业的工程师来说,掌握 UART 编程是非常重要的。通过本文的学习,希望能够帮助读者理解 UART 的基本原理和编程方法,在实际应用中能够熟练使用 UART 进行设备通信,并能够解决 UART 编程过程中遇到的各种问题。
随着电子技术的不断发展,UART 技术也在不断进步,相信 UART 在未来的电子设备通信中仍将发挥重要的作用。
(注:文档部分内容可能由 AI 生成)
UART 编程程序流程图
一、UART 初始化流程
是 否 开始 选择UART端口 使能UART时钟 设置波特率 设置数据位 设置校验位 设置停止位 初始化GPIO引脚 使能UART外设 初始化成功? 结束 调用错误处理函数
流程说明:
-
初始化流程从选择 UART 端口开始,根据硬件资源确定使用的 UART 外设
-
必须先使能对应 UART 的时钟源,否则外设无法工作
-
按顺序配置波特率、数据位、校验位和停止位等核心参数
-
完成 GPIO 引脚复用配置(TX 为推挽输出,RX 为输入)
-
使能 UART 外设后进行状态判断,失败则进入错误处理
二、UART 数据发送流程(查询方式)
流程说明:
-
发送前先检查数据长度,避免无效操作
-
通过循环逐个发送字节,每次发送前必须等待发送缓冲区为空
-
所有字节发送完成后,需等待发送完成标志(TC)置位,确保数据完全发送
三、UART 数据发送流程(中断方式)
是 否 开始 检查发送缓冲区状态 加载数据到发送缓冲区 启动发送中断 等待中断触发 进入发送中断服务程序 发送当前字节 是否发送完成? 清除中断标志 调用发送完成回调函数 结束 继续发送下一字节
流程说明:
-
中断方式通过启动发送中断(HAL_UART_Transmit_IT)触发数据发送
-
中断服务程序负责实际的数据发送操作
-
全部数据发送完成后会触发 TxCpltCallback 回调函数
-
适合需要 CPU 并行处理其他任务的场景
四、UART 数据发送流程(DMA 方式)
是 否 开始 配置DMA通道 设置DMA传输方向 设置数据长度 启动DMA传输 等待DMA传输完成 传输成功? 关闭DMA通道 调用发送完成回调函数 结束 调用错误处理函数
流程说明:
-
DMA 方式需先配置对应的 DMA 通道参数
-
传输过程由 DMA 控制器独立完成,不占用 CPU 资源
-
适合大数据量连续传输场景
-
传输结束后通过回调函数通知 CPU 处理后续任务
五、UART 数据接收流程(查询方式)
流程说明:
-
接收前需初始化缓冲区用于存储接收数据
-
通过循环查询接收缓冲区状态,有数据时读取
-
按预设长度接收指定数量的字节
-
简单直观但会占用 CPU 资源,适合数据量小的场景
六、UART 数据接收流程(中断方式)
是 否 开始 初始化接收缓冲区 使能接收中断 等待接收中断触发 进入接收中断服务程序 读取接收数据到缓冲区 是否接收完成? 清除中断标志 调用接收完成回调函数 重新启动接收中断 结束 继续等待数据
流程说明:
-
启动时需使能 RXNE 中断(接收缓冲区非空中断)
-
每收到一个字节就触发一次中断
-
接收完成后通过 RxCpltCallback 回调函数处理数据
-
需在回调函数中重新启动接收中断以实现连续接收
七、UART 数据接收流程(DMA 方式)
是 否 开始 配置DMA接收通道 设置接收缓冲区地址 设置接收数据长度 启动DMA接收 等待DMA接收完成 接收成功? 关闭DMA通道 调用接收完成回调函数 处理接收数据 重新启动DMA接收 结束 调用错误处理函数
流程说明:
-
DMA 接收需指定接收缓冲区的起始地址和长度
-
数据接收过程由 DMA 控制器自动完成
-
适合接收大数据块,不占用 CPU 资源
-
接收完成后通过回调函数通知 CPU 进行数据处理
-
实际应用中通常需要重新启动 DMA 以实现循环接收
UART 编程 C 语言具体示例
一、基于 STM32 HAL 库的 UART 编程示例
(一)UART 初始化完整代码
\#include "stm32f1xx\_hal.h"
// 定义UART句柄结构体
UART\_HandleTypeDef huart2;
// USART2初始化函数
void MX\_USART2\_UART\_Init(void)
{
// 配置UART基本参数
huart2.Instance = USART2; // 使用USART2外设
huart2.Init.BaudRate = 115200; // 波特率115200
huart2.Init.WordLength = UART\_WORDLENGTH\_8B; // 8位数据位
huart2.Init.StopBits = UART\_STOPBITS\_1; // 1位停止位
huart2.Init.Parity = UART\_PARITY\_NONE; // 无校验位
huart2.Init.Mode = UART\_MODE\_TX\_RX; // 同时使能收发模式
huart2.Init.HwFlowCtl = UART\_HWCONTROL\_NONE; // 无硬件流控制
huart2.Init.OverSampling = UART\_OVERSAMPLING\_16; // 16倍过采样
if (HAL\_UART\_Init(\&huart2) != HAL\_OK) // 初始化UART
{
Error\_Handler(); // 初始化失败则调用错误处理
}
}
// 底层硬件初始化(被HAL\_UART\_Init调用)
void HAL\_UART\_MspInit(UART\_HandleTypeDef\* uartHandle)
{
GPIO\_InitTypeDef GPIO\_InitStruct = {0};
if(uartHandle->Instance==USART2)
{
// 1. 使能USART2时钟
\_\_HAL\_RCC\_USART2\_CLK\_ENABLE();
// 2. 使能GPIOA时钟(USART2默认挂载在GPIOA)
\_\_HAL\_RCC\_GPIOA\_CLK\_ENABLE();
// 3. 配置TX引脚(PA2)
GPIO\_InitStruct.Pin = GPIO\_PIN\_2;
GPIO\_InitStruct.Mode = GPIO\_MODE\_AF\_PP; // 复用推挽输出
GPIO\_InitStruct.Speed = GPIO\_SPEED\_FREQ\_HIGH; // 高速
HAL\_GPIO\_Init(GPIOA, \&GPIO\_InitStruct);
// 4. 配置RX引脚(PA3)
GPIO\_InitStruct.Pin = GPIO\_PIN\_3;
GPIO\_InitStruct.Mode = GPIO\_MODE\_INPUT; // 输入模式
GPIO\_InitStruct.Pull = GPIO\_NOPULL; // 无上下拉
HAL\_GPIO\_Init(GPIOA, \&GPIO\_InitStruct);
// 5. 配置UART中断(如果使用中断方式)
HAL\_NVIC\_SetPriority(USART2\_IRQn, 0, 0); // 中断优先级
HAL\_NVIC\_EnableIRQ(USART2\_IRQn); // 使能中断
}
}
// 错误处理函数
void Error\_Handler(void)
{
// 可以添加LED闪烁等错误指示
while(1)
{
// 死循环表示初始化失败
}
}
(二)数据发送示例
1. 查询方式发送
/\*\*
\* @brief 使用查询方式发送数据
\* @param pData: 待发送数据缓冲区指针
\* @param Size: 发送数据长度
\* @retval 0:成功 1:失败
\*/
uint8\_t UART\_SendData\_Query(UART\_HandleTypeDef \*huart, uint8\_t \*pData, uint16\_t Size)
{
uint32\_t timeout = 0;
// 参数校验
if(pData == NULL || Size == 0)
return 1;
// 逐个字节发送
for(uint16\_t i=0; i\<Size; i++)
{
timeout = 0;
// 等待发送缓冲区为空(TXE标志置位)
while(!(huart->Instance->SR & USART\_SR\_TXE))
{
timeout++;
if(timeout > 0xFFFF) // 超时保护
return 1;
}
// 发送一个字节
huart->Instance->DR = pData\[i];
}
// 等待所有数据发送完成(TC标志置位)
timeout = 0;
while(!(huart->Instance->SR & USART\_SR\_TC))
{
timeout++;
if(timeout > 0xFFFF)
return 1;
}
return 0; // 发送成功
}
// 使用示例
uint8\_t send\_buf\[] = "Hello UART Query Mode!\r\n";
UART\_SendData\_Query(\&huart2, send\_buf, sizeof(send\_buf)-1);
2. 中断方式发送
// 发送缓冲区定义
uint8\_t tx\_buffer\[128];
uint16\_t tx\_len = 0;
uint16\_t tx\_ptr = 0;
/\*\*
\* @brief 启动中断方式发送
\* @param pData: 数据缓冲区
\* @param Size: 数据长度
\*/
void UART\_SendData\_IT(UART\_HandleTypeDef \*huart, uint8\_t \*pData, uint16\_t Size)
{
if(Size > sizeof(tx\_buffer))
Size = sizeof(tx\_buffer); // 防止缓冲区溢出
// 复制数据到发送缓冲区
memcpy(tx\_buffer, pData, Size);
tx\_len = Size;
tx\_ptr = 0;
// 使能发送缓冲区空中断
\_\_HAL\_UART\_ENABLE\_IT(huart, UART\_IT\_TXE);
}
// UART中断服务函数
void USART2\_IRQHandler(void)
{
// 检查发送缓冲区空标志
if(\_\_HAL\_UART\_GET\_FLAG(\&huart2, UART\_FLAG\_TXE) &&
\_\_HAL\_UART\_GET\_IT\_SOURCE(\&huart2, UART\_IT\_TXE))
{
if(tx\_ptr < tx\_len)
{
// 发送一个字节
huart2.Instance->DR = tx\_buffer\[tx\_ptr++];
}
else
{
// 发送完成,关闭TXE中断
\_\_HAL\_UART\_DISABLE\_IT(\&huart2, UART\_IT\_TXE);
// 使能传输完成中断
\_\_HAL\_UART\_ENABLE\_IT(\&huart2, UART\_IT\_TC);
}
}
// 检查传输完成标志
if(\_\_HAL\_UART\_GET\_FLAG(\&huart2, UART\_FLAG\_TC) &&
\_\_HAL\_UART\_GET\_IT\_SOURCE(\&huart2, UART\_IT\_TC))
{
// 清除TC标志
\_\_HAL\_UART\_CLEAR\_FLAG(\&huart2, UART\_FLAG\_TC);
// 关闭TC中断
\_\_HAL\_UART\_DISABLE\_IT(\&huart2, UART\_IT\_TC);
// 可以在这里添加发送完成回调处理
}
// 其他中断处理(如接收中断)
}
// 使用示例
uint8\_t send\_data\[] = "Hello UART Interrupt Mode!\r\n";
UART\_SendData\_IT(\&huart2, send\_data, sizeof(send\_data)-1);
3. DMA 方式发送
/\*\*
\* @brief 使用DMA方式发送数据
\* @param pData: 数据缓冲区
\* @param Size: 数据长度
\*/
void UART\_SendData\_DMA(UART\_HandleTypeDef \*huart, uint8\_t \*pData, uint16\_t Size)
{
// 确保DMA通道未被占用
if(huart->hdmatx->State != HAL\_DMA\_STATE\_READY)
HAL\_DMA\_Abort(huart->hdmatx);
// 启动DMA发送
HAL\_UART\_Transmit\_DMA(huart, pData, Size);
}
// DMA发送完成回调函数
void HAL\_UART\_TxCpltCallback(UART\_HandleTypeDef \*huart)
{
if(huart->Instance == USART2)
{
// 发送完成处理
// 可以关闭DMA通道或准备下一次发送
HAL\_DMA\_Abort(huart->hdmatx);
}
}
// 使用示例
uint8\_t dma\_send\_buf\[] = "Hello UART DMA Mode!\r\n";
UART\_SendData\_DMA(\&huart2, dma\_send\_buf, sizeof(dma\_send\_buf)-1);
(三)数据接收示例
1. 查询方式接收
/\*\*
\* @brief 查询方式接收数据
\* @param pData: 接收缓冲区
\* @param Size: 要接收的长度
\* @param timeout: 超时时间(单位:ms)
\* @retval 实际接收长度
\*/
uint16\_t UART\_ReceiveData\_Query(UART\_HandleTypeDef \*huart, uint8\_t \*pData,
uint16\_t Size, uint32\_t timeout)
{
uint16\_t recv\_len = 0;
uint32\_t tick\_start = HAL\_GetTick(); // 获取当前系统时间
while(recv\_len < Size)
{
// 等待接收缓冲区非空(RXNE标志置位)
while(!(huart->Instance->SR & USART\_SR\_RXNE))
{
// 超时判断
if((HAL\_GetTick() - tick\_start) > timeout)
return recv\_len; // 返回已接收长度
}
// 读取接收数据
pData\[recv\_len++] = huart->Instance->DR;
}
return recv\_len;
}
// 使用示例
uint8\_t recv\_buf\[50];
uint16\_t len = UART\_ReceiveData\_Query(\&huart2, recv\_buf, 10, 1000); // 超时1秒
2. 中断方式接收
// 接收缓冲区定义
uint8\_t rx\_buffer\[128];
uint16\_t rx\_len = 0;
uint8\_t rx\_complete\_flag = 0;
/\*\*
\* @brief 启动中断方式接收
\*/
void UART\_StartReceive\_IT(UART\_HandleTypeDef \*huart)
{
rx\_len = 0;
rx\_complete\_flag = 0;
// 使能接收中断
\_\_HAL\_UART\_ENABLE\_IT(huart, UART\_IT\_RXNE);
}
// UART中断服务函数(补充接收部分)
void USART2\_IRQHandler(void)
{
// 检查接收中断标志
if(\_\_HAL\_UART\_GET\_FLAG(\&huart2, UART\_FLAG\_RXNE) &&
\_\_HAL\_UART\_GET\_IT\_SOURCE(\&huart2, UART\_IT\_RXNE))
{
// 读取接收数据
uint8\_t data = huart2.Instance->DR;
// 存储到接收缓冲区
if(rx\_len < sizeof(rx\_buffer))
{
rx\_buffer\[rx\_len++] = data;
// 示例:遇到换行符认为接收完成
if(data == '\n')
{
rx\_complete\_flag = 1;
// 可以关闭接收中断(按需)
// \_\_HAL\_UART\_DISABLE\_IT(\&huart2, UART\_IT\_RXNE);
}
}
}
// 其他中断处理...
}
// 主循环中处理接收数据
void ProcessReceivedData(void)
{
if(rx\_complete\_flag)
{
// 处理接收的数据
// ...
// 重置标志,准备下次接收
rx\_complete\_flag = 0;
rx\_len = 0;
}
}
3. DMA 方式接收
// DMA接收缓冲区
uint8\_t dma\_rx\_buffer\[100];
uint8\_t dma\_rx\_complete = 0;
/\*\*
\* @brief 启动DMA方式接收
\* @param Size: 要接收的字节数
\*/
void UART\_StartReceive\_DMA(UART\_HandleTypeDef \*huart, uint16\_t Size)
{
// 确保缓冲区大小足够
if(Size > sizeof(dma\_rx\_buffer))
Size = sizeof(dma\_rx\_buffer);
// 中止当前DMA传输(如果正在进行)
if(huart->hdmarx->State != HAL\_DMA\_STATE\_READY)
HAL\_DMA\_Abort(huart->hdmarx);
// 启动DMA接收
HAL\_UART\_Receive\_DMA(huart, dma\_rx\_buffer, Size);
}
// DMA接收完成回调函数
void HAL\_UART\_RxCpltCallback(UART\_HandleTypeDef \*huart)
{
if(huart->Instance == USART2)
{
dma\_rx\_complete = 1;
// 关闭DMA通道
HAL\_DMA\_Abort(huart->hdmarx);
}
}
// 处理DMA接收数据
void ProcessDMAReceivedData(void)
{
if(dma\_rx\_complete)
{
// 处理接收的数据
// ...
// 重置标志
dma\_rx\_complete = 0;
// 重新启动DMA接收(连续接收模式)
UART\_StartReceive\_DMA(\&huart2, sizeof(dma\_rx\_buffer));
}
}
二、基于 51 单片机的 UART 编程示例
以 STC89C52 为例,使用标准库函数:
(一)UART 初始化
\#include \<reg52.h>
\#define FOSC 11059200UL // 晶振频率
\#define BAUD 9600 // 波特率
// 初始化UART
void UART\_Init(void)
{
SCON = 0x50; // 8位数据位,1位停止位,允许接收(0x50 = 01010000)
TMOD &= 0x0F; // 清除定时器1配置
TMOD |= 0x20; // 定时器1工作在模式2(8位自动重装载)
PCON |= 0x80; // 波特率加倍(如果需要)
// 计算定时器初值(波特率9600,晶振11.0592MHz)
// 公式:TH1 = TL1 = 256 - FOSC/(12\*32\*BAUD)
TH1 = 0xFD; // 9600波特率对应初值
TL1 = 0xFD;
ET1 = 0; // 禁止定时器1中断
TR1 = 1; // 启动定时器1
ES = 1; // 允许UART中断
EA = 1; // 允许总中断
}
(二)51 单片机发送函数
// 发送一个字节
void UART\_SendByte(uint8\_t dat)
{
SBUF = dat; // 加载数据到发送缓冲区
while(!TI); // 等待发送完成
TI = 0; // 清除发送完成标志
}
// 发送字符串
void UART\_SendString(uint8\_t \*str)
{
while(\*str != '\0')
{
UART\_SendByte(\*str++);
}
}
// 使用示例
void main(void)
{
UART\_Init();
UART\_SendString("Hello 51 UART!\r\n");
while(1)
{
// 主循环
}
}
(三)51 单片机接收函数(中断方式)
uint8\_t rx\_buffer\[50];
uint8\_t rx\_index = 0;
bit rx\_complete = 0; // 接收完成标志位
// UART中断服务函数
void UART\_ISR(void) interrupt 4
{
if(RI) // 接收中断标志
{
RI = 0; // 清除接收标志
rx\_buffer\[rx\_index++] = SBUF; // 读取接收数据
// 遇到回车换行认为接收完成
if(rx\_buffer\[rx\_index-1] == '\n' || rx\_index >= sizeof(rx\_buffer))
{
rx\_complete = 1;
rx\_buffer\[rx\_index] = '\0'; // 添加字符串结束符
rx\_index = 0;
}
}
}
// 主函数中处理接收数据
void main(void)
{
UART\_Init();
while(1)
{
if(rx\_complete)
{
// 回显接收的数据
UART\_SendString("Received: ");
UART\_SendString(rx\_buffer);
rx\_complete = 0; // 重置标志
}
}
}
三、代码使用说明
- STM32 代码说明:
-
需配合 STM32CubeMX 生成的工程框架使用
-
不同型号 STM32 的 UART 引脚和时钟配置可能不同,需根据数据手册调整
-
DMA 方式需要在 CubeMX 中提前配置好 DMA 通道
- 51 单片机代码说明:
-
波特率计算与晶振频率密切相关,不同晶振需重新计算 TH1 和 TL1 值
-
硬件资源有限,不建议使用过大的缓冲区
- 通用注意事项:
-
发送和接收双方必须保持波特率、数据位、校验位、停止位完全一致
-
中断方式和 DMA 方式需正确配置中断优先级
-
长时间运行需考虑缓冲区溢出问题,建议添加保护机制
以上代码涵盖了 UART 编程的主要场景,可根据实际硬件平台和需求进行修改和扩展。对于入门工程师,建议先从查询方式开始练习,理解基本原理后再逐步掌握中断和 DMA 方式。