UART串口通信编程自学笔记30000字,嵌入式编程,STM32,C语言

UART 编程详解

一、UART 基础概念

对于刚入门电子行业的工程师来说,UART 编程是一个绕不开的重要知识点。那么,UART 到底是什么呢?UART 全称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),是一种通用的数据通信协议。它的独特之处在于无需时钟信号,仅通过发送线(TX)和接收线(RX)就能实现设备间的数据传输。在通信时,数据会以帧的形式发送,每帧数据包含起始位、数据位、校验位和停止位,这些部分共同保障了数据传输的准确性。

UART 协议是一种异步串行通信协议,它在数据传输过程中,发送方和接收方不需要共享同一个时钟信号,而是通过约定好的波特率来保证数据的正确传输。这一特点使得 UART 在电子设备通信中得到了广泛的应用,因为它可以简化硬件连接,降低系统成本。

(一)UART 的发展历程

UART 的发展可以追溯到早期的计算机通信时代。在计算机发展的初期,设备之间的数据传输需要一种简单、可靠的方式,UART 应运而生。随着电子技术的不断发展,UART 的性能也在不断提升,从最初的低波特率、简单功能,逐渐发展到支持更高的波特率、更复杂的校验方式等,以满足不同场景下的数据传输需求。

在早期的计算机系统中,UART 主要用于连接调制解调器(Modem),实现计算机与外部网络的通信。随着嵌入式系统的兴起,UART 又成为了单片机与其他外设通信的重要手段,如单片机与传感器、显示屏、上位机等设备的通信都大量采用了 UART 协议。

(二)UART 的特点

  1. 异步通信:无需时钟信号,发送方和接收方通过约定的波特率进行数据同步。

  2. 串行传输:数据以 bit 为单位,按照顺序依次传输,相比并行传输,减少了信号线的数量,降低了硬件成本和布线难度。

  3. 帧结构固定:每帧数据包含起始位、数据位、校验位和停止位,结构固定,便于硬件和软件的实现。

  4. 灵活性高:可以通过设置不同的参数,如波特率、数据位、校验位和停止位等,适应不同的通信需求。

二、UART 工作原理

UART 的工作原理主要基于数据帧的传输和接收。当发送数据时,UART 控制器会将并行数据转换为串行数据,并按照一定的帧格式添加起始位、校验位和停止位,然后通过发送线(TX)逐位发送出去。接收方的 UART 控制器则通过接收线(RX)逐位接收串行数据,去除起始位、校验位和停止位后,将串行数据转换为并行数据,供接收设备处理。

(一)数据帧格式

数据帧是 UART 通信中数据传输的基本单位,其格式如下:

  1. 起始位:通常为 1 位,用于表示数据传输的开始。起始位的电平为低电平,与空闲状态的高电平形成鲜明对比,便于接收方检测到数据的开始。

  2. 数据位:可以是 5、6、7 或 8 位,用于传输实际的有效数据。在大多数情况下,采用 8 位数据位,因为它可以表示 0-255 的数值范围,满足大多数数据传输的需求。

  3. 校验位:可选位,用于检验数据传输的正确性。常见的校验方式有奇校验、偶校验和无校验。奇校验要求数据位和校验位中 1 的个数为奇数;偶校验要求数据位和校验位中 1 的个数为偶数;无校验则不进行校验,适用于对数据传输准确性要求不高的场景。

  4. 停止位:通常为 1、1.5 或 2 位,用于表示数据传输的结束。停止位的电平为高电平,接收方检测到停止位后,就知道一帧数据已经传输完毕。

(二)波特率

波特率是 UART 通信中的一个重要参数,它表示单位时间内传输的二进制位数,单位为波特(Baud)。例如,波特率为 9600 表示每秒可以传输 9600 个二进制位。

在 UART 通信中,发送方和接收方必须设置相同的波特率,否则会导致数据传输错误。因为接收方是根据波特率来确定每个位的持续时间的,如果波特率不匹配,接收方就无法正确识别数据位。

常见的波特率值有 300、600、1200、2400、4800、9600、19200、38400、57600、115200 等。在实际应用中,应根据通信距离、数据传输速率要求等因素选择合适的波特率。一般来说,通信距离越远,应选择较低的波特率,以减少信号衰减和干扰对数据传输的影响;数据传输速率要求越高,则应选择较高的波特率。

(三)数据传输过程

  1. 发送过程:

    发送设备的 CPU 将需要发送的数据写入 UART 的发送缓冲区,UART 控制器从发送缓冲区中读取数据,并将其转换为串行数据。然后,按照设定的帧格式,在数据的前面添加起始位,后面添加校验位(如果有)和停止位,形成一帧完整的数据。最后,UART 控制器通过发送线(TX)将这帧数据逐位发送出去。

  2. 接收过程:

    接收设备的 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 控制器进行处理。

  1. RS - 232 收发器:RS - 232 是一种常用的串行通信标准,它规定了信号的电气特性、引脚定义等。RS - 232 信号的电平范围较大,通常为 - 15V 到 + 15V,其中逻辑 1 为 - 3V 到 - 15V,逻辑 0 为 + 3V 到 + 15V。RS - 232 收发器可以实现 TTL 电平(0V 和 + 5V)与 RS - 232 电平之间的转换,使得 UART 设备可以与符合 RS - 232 标准的设备进行通信,如计算机的串口、调制解调器等。

  2. 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 通信通常需要以下几个关键引脚:

  1. TX(Transmit):发送引脚,用于输出串行数据。

  2. RX(Receive):接收引脚,用于输入串行数据。

  3. GND(Ground):接地引脚,为整个电路提供参考地电位,保证信号的稳定传输。

在实际应用中,不同的设备可能会有额外的引脚,如 RTS(Request To Send)、CTS(Clear To Send)等,这些引脚用于实现硬件流控制,防止数据溢出。当发送方的发送缓冲区即将满时,会通过 RTS 引脚通知接收方暂停发送数据;当接收方的接收缓冲区有足够空间时,会通过 CTS 引脚通知发送方继续发送数据。

(四)UART 硬件结构示意图

graph TD A[CPU] --> B[UART控制器] B --> C[发送缓冲区] B --> D[接收缓冲区] B --> E[波特率发生器] B --> F[控制寄存器] B --> G[状态寄存器] C --> H[收发器] D --> H H --> I[TX引脚] H --> J[RX引脚] H --> K[GND引脚] F --> L[设置波特率、数据位等参数] G --> M[反映工作状态]

上图展示了 UART 的硬件结构示意图,CPU 与 UART 控制器相连,UART 控制器内部包含多个功能模块,收发器连接 UART 控制器和外部引脚,实现信号的转换和传输。

四、UART 编程步骤

UART 编程主要包括初始化 UART、数据发送和数据接收三个主要步骤,下面将详细介绍每个步骤的具体实现方法。

(一)初始化 UART

初始化 UART 是 UART 编程的第一步,其目的是设置 UART 的工作参数,如波特率、数据位、校验位、停止位等,以确保 UART 能够按照预期的方式进行通信。

  1. 选择 UART 端口:如果微处理器或微控制器有多个 UART 端口,需要首先选择要使用的 UART 端口。

  2. 使能 UART 时钟:UART 控制器需要时钟信号才能工作,因此需要使能相应的时钟源。不同的芯片可能有不同的时钟使能方式,一般可以通过配置相应的寄存器来实现。

  3. 设置波特率:根据通信需求,计算出波特率对应的分频值,并将其写入波特率寄存器。波特率的计算公式如下:

波特率 = 时钟频率 /(分频值 × 16)

其中,时钟频率是 UART 控制器的工作时钟频率,分频值是一个整数,用于对时钟频率进行分频,以得到所需的波特率。

例如,假设 UART 控制器的工作时钟频率为 16MHz,要设置波特率为 9600,则分频值可以通过以下计算得到:

分频值 = 时钟频率 /(波特率 × 16)= 16000000 /(9600 × 16)≈ 104.17

由于分频值必须为整数,因此可以取 104,此时实际波特率为 16000000 /(104 × 16)≈ 9615,与目标波特率 9600 非常接近,误差在可接受范围内。

  1. 设置数据位:通过配置控制寄存器,选择数据位的位数,如 5、6、7 或 8 位。

  2. 设置校验位:根据需要,选择校验方式,如奇校验、偶校验或无校验。

  3. 设置停止位:选择停止位的位数,如 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 方式。

  1. 查询方式:查询方式是一种简单的发送方式,通过不断查询发送缓冲区的状态来判断是否可以发送数据。当发送缓冲区为空时,将数据写入发送缓冲区,然后等待数据发送完成。

以下是一个使用查询方式发送数据的代码示例:

复制代码
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)标志位,以确保数据已经全部发送出去。

  1. 中断方式:中断方式是一种高效的发送方式,当发送缓冲区为空时,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 回调函数。

  1. 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 方式。

  1. 查询方式:查询方式通过不断查询接收缓冲区的状态来判断是否有数据接收。当接收缓冲区有数据时,读取接收缓冲区的数据。

以下是一个使用查询方式接收数据的代码示例:

复制代码
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)标志位,确保有数据可以读取。

  1. 中断方式:当接收缓冲区有数据时,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 回调函数。在回调函数中,可以处理接收到的数据,并继续启动接收。

  1. 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 编程过程中,可能会遇到各种问题,如数据传输错误、通信中断等,以下是一些常见问题及解决方法。

(一)数据传输错误

  1. 波特率不匹配:发送方和接收方的波特率设置不一致,会导致接收方无法正确识别数据位,从而产生数据传输错误。解决方法是确保发送方和接收方设置相同的波特率。在实际应用中,可以使用示波器测量 UART 信号的波特率,以验证波特率是否正确。

  2. 干扰:在数据传输过程中,外部电磁干扰可能会导致信号失真,从而产生数据传输错误。解决方法包括:

  • 采用屏蔽线传输 UART 信号,减少外部干扰的影响。

  • 增加接地措施,降低接地电阻,提高抗干扰能力。

  • 在 UART 信号线上添加滤波电容,滤除高频干扰信号。

  1. 校验位错误:如果设置了校验位,当数据传输过程中出现错误时,校验位会检测到错误。解决方法是检查数据传输过程中的干扰情况,确保数据传输的稳定性;如果干扰较强,可以考虑采用无校验位,并通过软件方式进行数据校验,如添加校验和、CRC 校验等。

(二)通信中断

  1. 硬件连接问题:UART 引脚连接错误、接触不良等硬件问题可能会导致通信中断。解决方法是检查 UART 引脚的连接是否正确,确保 TX 与 RX 交叉连接(发送方的 TX 连接接收方的 RX,发送方的 RX 连接接收方的 TX),GND 连接正确;检查连接器是否接触良好,必要时更换连接器。

  2. 电源问题:UART 设备的电源电压不稳定或供电不足,可能会导致设备工作异常,从而引起通信中断。解决方法是确保 UART 设备的电源电压稳定在规定范围内,检查电源线路是否有压降过大的情况,必要时更换电源。

  3. 软件问题:软件程序中的错误,如发送或接收缓冲区溢出、中断处理不当等,也可能导致通信中断。解决方法是:

  • 合理设置发送和接收缓冲区的大小,避免缓冲区溢出。

  • 正确处理中断,确保中断服务程序能够及时响应和处理中断事件,避免中断嵌套或中断丢失。

  • 在程序中添加错误检测和恢复机制,当检测到通信中断时,能够自动重新初始化 UART,恢复通信。

(三)数据丢失

  1. 接收缓冲区溢出:当接收数据的速度快于 CPU 处理数据的速度时,接收缓冲区可能会溢出,导致数据丢失。解决方法是:
  • 增大接收缓冲区的大小,提高缓冲区的存储能力。

  • 采用中断方式或 DMA 方式接收数据,提高数据接收的效率,减少 CPU 的负担。

  • 在程序中及时处理接收缓冲区的数据,避免数据积压。

  1. 发送缓冲区溢出:当发送数据的速度快于 UART 发送数据的速度时,发送缓冲区可能会溢出,导致数据丢失。解决方法是:
  • 合理控制发送数据的速度,避免发送过快。

  • 采用中断方式或 DMA 方式发送数据,提高数据发送的效率。

  • 在程序中检查发送缓冲区的状态,当缓冲区满时,暂停发送数据,等待缓冲区有空间后再继续发送。

七、UART 调试工具

在 UART 编程和调试过程中,使用合适的调试工具可以提高调试效率,快速定位问题。以下是一些常用的 UART 调试工具。

(一)串口调试助手

串口调试助手是一种常用的软件工具,它可以通过计算机的串口与 UART 设备进行通信,实现数据的发送和接收。串口调试助手通常具有以下功能:

  1. 选择串口:可以选择计算机上的多个串口进行通信。

  2. 设置波特率、数据位、校验位、停止位等参数。

  3. 发送数据:可以手动输入数据发送,也可以发送文件中的数据。

  4. 接收数据:可以实时显示接收到的数据,并以十六进制或 ASCII 码形式显示。

  5. 数据存储:可以将接收到的数据存储到文件中,便于后续分析。

常见的串口调试助手有 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 的比较

  1. 通信线路:UART 需要两根通信线(TX 和 RX),加上 GND 共三根线;I2C 需要两根通信线(SDA 和 SCL),加上 GND 共三根线。

  2. 通信方式:UART 是异步通信,无需时钟信号;I2C 是同步通信,需要时钟信号(SCL)来同步数据传输。

  3. 设备数量:UART 通常用于两个设备之间的通信;I2C 总线上可以连接多个主设备和多个从设备,通过地址来区分不同的设备。

  4. 传输速率:UART 的传输速率相对较低,一般在几十 kbps 到几 Mbps 之间;I2C 的传输速率较高,标准模式下为 100kbps,快速模式下为 400kbps,高速模式下为 3.4Mbps。

  5. 抗干扰能力:UART 采用单端信号传输,抗干扰能力相对较弱;I2C 采用差分信号传输(SDA 和 SCL 相对于 GND),抗干扰能力比 UART 强。

  6. 应用场景:UART 适用于两个设备之间的短距离、低速率通信,如单片机与上位机、传感器之间的通信;I2C 适用于多个设备之间的短距离、中高速率通信,如单片机与 EEPROM、LCD 显示屏等外设之间的通信。

(二)UART 与 SPI 的比较

  1. 通信线路:UART 需要两根通信线(TX 和 RX),加上 GND 共三根线;SPI 需要四根通信线(MOSI、MISO、SCK、CS),其中 CS 为片选信号,每个从设备需要一根 CS 线,加上 GND 共五根线(对于一个主设备和一个从设备)。

  2. 通信方式:UART 是异步通信;SPI 是同步通信,需要时钟信号(SCK)来同步数据传输。

  3. 设备数量:UART 通常用于两个设备之间的通信;SPI 总线上可以连接一个主设备和多个从设备,通过片选信号(CS)来选择要通信的从设备。

  4. 传输速率:UART 的传输速率相对较低;SPI 的传输速率较高,可以达到几十 Mbps 甚至更高。

  5. 抗干扰能力:UART 抗干扰能力较弱;SPI 采用差分信号传输(MOSI 和 MISO 相对于 GND),抗干扰能力比 UART 强,但比 I2C 弱。

  6. 应用场景: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外设 初始化成功? 结束 调用错误处理函数

流程说明:

  1. 初始化流程从选择 UART 端口开始,根据硬件资源确定使用的 UART 外设

  2. 必须先使能对应 UART 的时钟源,否则外设无法工作

  3. 按顺序配置波特率、数据位、校验位和停止位等核心参数

  4. 完成 GPIO 引脚复用配置(TX 为推挽输出,RX 为输入)

  5. 使能 UART 外设后进行状态判断,失败则进入错误处理

二、UART 数据发送流程(查询方式)

graph TD A[开始] --> B[判断发送数据长度是否为0] B -->|是| C[结束] B -->|否| D[初始化索引i=0] D --> E[等待发送缓冲区为空(TXE=1)] E --> F[将pData[i]写入DR寄存器] F --> G[i自增1] G --> H{i是否等于数据长度?} H -->|否| E H -->|是| I[等待发送完成(TC=1)] I --> C

流程说明:

  1. 发送前先检查数据长度,避免无效操作

  2. 通过循环逐个发送字节,每次发送前必须等待发送缓冲区为空

  3. 所有字节发送完成后,需等待发送完成标志(TC)置位,确保数据完全发送

三、UART 数据发送流程(中断方式)

是 否 开始 检查发送缓冲区状态 加载数据到发送缓冲区 启动发送中断 等待中断触发 进入发送中断服务程序 发送当前字节 是否发送完成? 清除中断标志 调用发送完成回调函数 结束 继续发送下一字节

流程说明:

  1. 中断方式通过启动发送中断(HAL_UART_Transmit_IT)触发数据发送

  2. 中断服务程序负责实际的数据发送操作

  3. 全部数据发送完成后会触发 TxCpltCallback 回调函数

  4. 适合需要 CPU 并行处理其他任务的场景

四、UART 数据发送流程(DMA 方式)

是 否 开始 配置DMA通道 设置DMA传输方向 设置数据长度 启动DMA传输 等待DMA传输完成 传输成功? 关闭DMA通道 调用发送完成回调函数 结束 调用错误处理函数

流程说明:

  1. DMA 方式需先配置对应的 DMA 通道参数

  2. 传输过程由 DMA 控制器独立完成,不占用 CPU 资源

  3. 适合大数据量连续传输场景

  4. 传输结束后通过回调函数通知 CPU 处理后续任务

五、UART 数据接收流程(查询方式)

graph TD A[开始] --> B[初始化接收缓冲区] B --> C[初始化索引i=0] C --> D{接收数据长度是否为0?} D -->|是| E[结束] D -->|否| F[等待接收缓冲区非空(RXNE=1)] F --> G[从DR寄存器读取数据到缓冲区] G --> H[i自增1] H --> I{i是否等于接收长度?} I -->|否| F I -->|是| E

流程说明:

  1. 接收前需初始化缓冲区用于存储接收数据

  2. 通过循环查询接收缓冲区状态,有数据时读取

  3. 按预设长度接收指定数量的字节

  4. 简单直观但会占用 CPU 资源,适合数据量小的场景

六、UART 数据接收流程(中断方式)

是 否 开始 初始化接收缓冲区 使能接收中断 等待接收中断触发 进入接收中断服务程序 读取接收数据到缓冲区 是否接收完成? 清除中断标志 调用接收完成回调函数 重新启动接收中断 结束 继续等待数据

流程说明:

  1. 启动时需使能 RXNE 中断(接收缓冲区非空中断)

  2. 每收到一个字节就触发一次中断

  3. 接收完成后通过 RxCpltCallback 回调函数处理数据

  4. 需在回调函数中重新启动接收中断以实现连续接收

七、UART 数据接收流程(DMA 方式)

是 否 开始 配置DMA接收通道 设置接收缓冲区地址 设置接收数据长度 启动DMA接收 等待DMA接收完成 接收成功? 关闭DMA通道 调用接收完成回调函数 处理接收数据 重新启动DMA接收 结束 调用错误处理函数

流程说明:

  1. DMA 接收需指定接收缓冲区的起始地址和长度

  2. 数据接收过程由 DMA 控制器自动完成

  3. 适合接收大数据块,不占用 CPU 资源

  4. 接收完成后通过回调函数通知 CPU 进行数据处理

  5. 实际应用中通常需要重新启动 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;  // 重置标志

   }

 }

}

三、代码使用说明

  1. STM32 代码说明
  • 需配合 STM32CubeMX 生成的工程框架使用

  • 不同型号 STM32 的 UART 引脚和时钟配置可能不同,需根据数据手册调整

  • DMA 方式需要在 CubeMX 中提前配置好 DMA 通道

  1. 51 单片机代码说明
  • 波特率计算与晶振频率密切相关,不同晶振需重新计算 TH1 和 TL1 值

  • 硬件资源有限,不建议使用过大的缓冲区

  1. 通用注意事项
  • 发送和接收双方必须保持波特率、数据位、校验位、停止位完全一致

  • 中断方式和 DMA 方式需正确配置中断优先级

  • 长时间运行需考虑缓冲区溢出问题,建议添加保护机制

以上代码涵盖了 UART 编程的主要场景,可根据实际硬件平台和需求进行修改和扩展。对于入门工程师,建议先从查询方式开始练习,理解基本原理后再逐步掌握中断和 DMA 方式。