L3 UART / USART 通信基础
本课目标
能解释异步串口数据帧,正确交叉连接 TX/RX,并按数据长度和实时性选择阻塞、中断、DMA 或空闲中断接收。Stellaris-Nexus 的 USART1 引脚见通信接口。
串口通信概念
UART(通用异步收发器)介绍
UART(Universal Asynchronous Receiver/Transmitter),即通用异步收发器,是一种广泛用于嵌入式系统的串行通信协议,用于实现设备间的异步数据传输。
USART(通用同步 / 异步收发器)介绍
USART(Universal Synchronous/Asynchronous Receiver/Transmitter),即通用同步 / 异步收发器,它在 UART 的基础上增加了同步通信能力,是一种更灵活的串行通信接口。
同步异步概念
同步:你和对方踩着同一个节奏做事,必须等对方回应 / 配合,才能继续;
异步:你和对方各干各的,不用等对方,对方弄完了会告诉你,你接着处理就行。
看看我们的板子

Usart/Uart这类串口能干什么
USART/UART相当于单片机的语言系统,可以和其他模块进行对话。
它是单片机和外部设备之间的 沟通桥梁,电脑、显示屏、传感器,还是蓝牙、WiFi 模块它都可传递数据、下达指令;就像人说话靠嘴巴和耳朵,单片机的 “说” 和 “听”,大多由 USART/UART 来实现。
而且它的沟通方式简单直接,只需要几根信号线就能建立连接,不管是让单片机把采集到的温湿度数据传给电脑并显示,还是让我们通过串口给单片机发指令控制电机转动,甚至是多个单片机之间互相协调工作,USART/UART 都能完成这些数据交互,是单片机开发中最常用、最基础的通信接口之一。

比特率和波特率
•比特率,bit/s,bps
• 每秒钟传输的二进制码元的数量
•波特率,symbol/s
• 每秒钟传输的有效信息码元的数量
•举例
• UART通信中,比特率等于波特率,因为有效信息码元直接对应二进制的0/1
码元
• 十六进制字符编码中,如果一秒传输1000个字符,每个字符对应一种唯一的电平信号,那么波特率就是1000 symbol/s;由于一个十六进制字符要用四位二进制编码表示,那么它的比特率就是4000bit/s
1 个十六进制字符 = 1 个 字符(symbol),1 个十六进制字符,必须用 4 位二进制数(4 个 bit)来表示2⁴=16
比特率 = 每秒传输的字符数 × 每个字符;对应的bit数 4000 bit/s = 1000 symbol/s × 4 bit/个
常用的波特率值包括 300、1200、2400、4800、9600、19200、38400、57600 和 115200 bps 等,这些波特率适用于不同的通信场景
UART串口异步通讯长什么样

UART接线

从图中可以看到,两个 MCU之间通过 UART 通信时,需要采用交叉连接的方式:
接线
TXD(发送端):连接到对方的 RXD(接收端),确保发送的数据能被对方正确接收。
RXD(接收端):连接到对方的 TXD(发送端),确保发送的数据能被对方正确接收。
GND(地线):共地以保证双方的电平参考一致。
通信原理
UART异步通信协议意味着通信双方通过预先约定的波特率、数据位、校验位和停止位等参数来同步数据。
设备 A ↔ 设备 B:数据可以双向传输。设备 A 通过 TXD 发送数据,设备 B 通过 RXD 接收;反之亦然。
数据帧格式
起始位(Start Bit):
由一个逻辑0的低电平信号开始,用于通知接收方 “数据即将到来”,并同步双方的时钟。
数据位(Data Bits):
紧随起始位之后,通常为 8 位(也可以是 5、6、7 位),代表要传输的实际数据内容。
校验位(Parity Bit):
可选位,用于进行简单的错误检测(如奇校验、偶校验),确保数据传输的完整性。
停止位(Stop Bit):
由一个或多个逻辑1的高电平信号结束,标志着一帧数据的传输完成,为下一帧数据做准备。
USART 串口同步/异步通讯长什么样

从图中可以看到,两个 MCU之间通过 USART 进行同步通信时,除了 TXD(发送)、RXD(接收)和 GND(地线)之外,还额外增加了一条 CK(时钟)线。
TXD ↔ RXD:实现数据的双向传输,发送端连接到对方的接收端。
CK ↔ CK:共享同一时钟信号,用于同步双方的数据传输节奏。
GND:共地,保证电平参考一致。
通信原理
USART 支持两种通信模式:
异步模式(UART):不需要共享时钟,通过预先约定的波特率来同步数据。
同步模式(USART):由一方提供时钟信号(CK,有时候写做CLK),另一方在时钟的驱动下同步接收或发送数据。
从图中可以看出,在同步模式下:
通讯数据每一位的时间宽度是由 CK (CLK)时钟频率固定的。每一个时钟脉冲对应一位数据的传输,确保了收发双方严格同步。
数据传输过程
在同步模式下,数据传输与时钟信号相互配合。
时钟驱动:CK 信号提供统一的时序,每一个时钟边沿(如上升沿或下降沿)触发一次数据采样或发送。
逐位传输:如图所示,数据位 0 到位 7 依次在时钟的控制下通过 TXD 线传输,每一位数据都与一个时钟周期对应。
同步采样:接收方(MCU2)根据 CK 时钟的节拍,在正确的时间点对 RXD 线上的信号进行采样,从而准确还原出每一位数据。
UART/USART工作原理

数据帧格式

1. 空闲状态
在没有数据传输时,通信线路保持高电平(逻辑 1),这是 UART 的默认空闲状态,用于标识当前没有数据传输。
2. 起始位(Start Bit)
占 1 位,固定为低电平(逻辑 0)。
作用:通知接收方 “数据帧即将开始”,并触发接收方开始同步采样后续数据。
3. 数据位(Data Bits)
长度为 8~9 位(常见为 8 位),是实际要传输的有效数据内容。
传输顺序是低位在前,高位在后(例如先传位 0,再传位 1,直到最后一位)。
图中标注的 “5 (6)、6 (7)、7 (8)” 表示:当数据位为 8 位时,第 8 位也可作为校验位使用。
4. 校验位(Parity Bit,可选)
这是一个可选位,用于简单的错误检测。
奇校验:使数据位 + 校验位中 “1” 的总数为奇数。
偶校验:使数据位 + 校验位中 “1” 的总数为偶数。
接收方会校验这个位,如果不符合约定规则,就判定数据传输出错。
5. 停止位(Stop Bit)
长度可选:0.5、1、1.5 或 2 位,常见为 1 位,固定为高电平(逻辑 1)。
作用:标志一帧数据传输结束,同时为下一帧数据的起始位做准备,确保帧与帧之间有明确的间隔。
特别说明:USART 和 UART 在 异步通信模式 下的数据帧格式一致;USART 独有的同步模式会额外使用时钟信号,时序与异步模式不同。
UART/USART 的一帧数据,本质上是一个 起始位 + 数据位 + 校验位(可选) + 停止位 的组合。收发双方必须预先约定好:
波特率(传输速率)
数据位长度
是否使用校验位及校验方式
停止位长度
只有这些参数完全一致,才能正确解析对方发来的数据。
CubeMX配置










代码讲解
Uart
先声明俩变量用于存储UART的收发数据,后面的代码编写也仅需在main.c中修改

uint8_t RxData[10];
uint8_t TxData[10]= "hello";阻塞式
可实现UART简单的发送定长数据,可接收一定长度以内的数据。发一条然后收一次,每个任务不超过1000ms,未果就继续往下进行

中断式
Cube使能UART中断,实现UART随意收发定长数据。主函数只负责发送,中断回调函数负责接收,需要额外注意这里的接收函数位置,遵循先开启一轮接收然后再处理的逻辑
返回CubeMx配置代码




空闲中断式
可实现UART随意发送定长数据,可接收一定长度以内的数据。主函数只负责发送,中断回调函数负责接收


DMA中断
此模式是在中断式基础上修改的。DMA能独立完成外设(如 UART)与内存之间的数据传输,无需 CPU 逐字节干预。
DMA不生产数据,是数据的搬运工。搬运时,内存需要自增地址,否则一段话会搬运到同一个地方去造成覆盖
DMA中断通信
•直接内存访问(Direct Memory Access,DMA),不产生数据,只是数据的搬运工
•发送:MCU把指定要发送的数据的起始地址与长度告诉DMA,DMA帮忙搬运给外设,发完了告诉MCU
•接收:MCU告诉DMA去把外设发给单片机的消息搬到指定位置。当然要告诉DMA接收方式,比如说等MCU通知(代码主动调用)搬运还是一直搬运(直接配置不让DMA停下来)等等。搬运完成会触发相应的中断事件
FIFO (First In First Out 先进先出)
当接收信息太多时,DMA可能搬不过来。FIFO队列可以先把来不及搬运的内容缓存下来排队,一个个让DMA搬运,但代价是让信息的时效性降低。代价比较:信息丢失 vs 信息时效性差,根据这个来决定是否使用FIFO以及FIFO的长度。长度越长,缓存能力越强,但时效性损失越大
DMA可实现UART随意收发定长数据。主函数只负责发送,中断回调函数负责接收。
中断模式如何修改为DMA中断模式
中断发送 → DMA发送
HAL_UART_Transmit_IT(&huart1, TxData, 5); // 原中断函数
HAL_UART_Transmit_DMA(&huart1, TxData, 5); // DMA版本
中断接收 → DMA接收
HAL_UART_Receive_IT(&huart1, RxData, 5); // 原中断函数
HAL_UART_Receive_DMA(&huart1, RxData, 5); // DMA版本
在低运算量、低通信负载的代码中,很难体会到DMA中断和普通中断的差异




DMA空闲中断
实现UART随意发送定长数据,可接收一定长度以内的数据
主函数只负责发送,中断回调函数负责接收


将所有的开启一轮DMA中断接收函数HAL_UART_Receive_DMA(&huart1, RxData, 5);换成HAL_UARTEx_ReceiveToIdle_DMA(&huart1, RxData, 5);
需要额外注意这里的接收中断回调函数名称需改动,与2.3一致
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){}
各个模式总结 附代码
阻塞式
- HAL_UART_Transmit()/HAL_UART_Receive() 这类函数会卡CPU,直到数据发完 / 收完,或超时才执行下一行代码。
HAL_UART_Transmit(&huart1, TxData, 5, 1000);
HAL_UART_Receive(&huart1, RxData, 5, 1000);中断式
CPU 平时干自己的活(如控制 LED、读传感器),串口收 / 发完固定长度的数据后,触发中断,CPU 才去处理串口数据。
必须提前约定收多少字节(如 5 字节),少了 or 多了都可能丢数据,只收固定长度的数据
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){}
HAL_UART_Transmit_IT(&huart1, TxData, 5);
HAL_UART_Receive_IT(&huart1, RxData, 5);//回调函数【中断】
void HAL_UART_RxCpltCallback (UART_HandleTypeDef *huart)
{
//请选择你的中断
if(huart == &huart1)
{
//接收数据
HAL_UART_Receive_IT(&huart1,RxData,5); //DMA中断式
if(RxData[0] =='1')
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
//重置
RxData[0]=0;
}
}
}空闲中断
CPU 平时干活,串口接收数据时不打扰,只有串口线路 “没数据了(空闲)”,才触发中断,CPU 再去取所有收到的数据。
能收任意长度数据(比如传 3 字节、8 字节都可以),解决了中断式 “定长” 的问题。
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){}
HAL_UART_Transmit_IT(&huart1, TxData, 5);
HAL_UARTEx_ReceiveToIdle_IT(&huart1, RxData, 5);//回调函数【空闲中断】
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
//请选择你的中断
if(huart == &huart1)
{
//接收数据
HAL_UARTEx_ReceiveToIdle_IT(&huart1,RxData,5);//空闲中断
if(RxData[0] =='1')
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
//重置
RxData[0]=0;
}
}
}DMA
CPU 不用自己搬串口数据,交给 DMA “搬运工”:DMA 从串口把数据搬到内存,或从内存搬到串口,搬完才触发中断通知 CPU。
搬运时要让内存地址 “往后挪”(自增),不然所有数据都堆在同一个位置,覆盖成最后一个;
数据太多时,FIFO 相当于 将数据存入缓冲区 ,先存着再慢慢搬,代价是数据会晚一点到(时效性降)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){}
HAL_UART_Transmit_DMA(&huart1, TxData, 5);
HAL_UART_Receive_DMA(&huart1, RxData, 5);//回调函数【中断】
void HAL_UART_RxCpltCallback (UART_HandleTypeDef *huart)
{
//请选择你的中断
if(huart == &huart1)
{
//接收数据
HAL_UART_Receive_DMA(&huart1,RxData,5); //DMA中断式
if(RxData[0] =='1')
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
//重置
RxData[0]=0;
}
}
}DMA空闲中断
DMA 空闲中断是把DMA 的 “数据搬运” 能力和空闲中断的 “识别不定长数据” 能力结合起来的通信模式,你可以把它理解成:给 DMA搬运工加了一个 智能数据收发功能,既解放 CPU,又能灵活接收不定长数据。
空闲中断的作用:不再以固定长度作为搬运结束的信号,而是以 串口线路空闲(没有新数据)作为结束信号 哪怕你只传了 2 个字节(小于预设的 5 个),只要串口空闲,就停止搬运并通知 CPU
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){}
HAL_UART_Transmit_DMA(&huart1, TxData, 5);
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, RxData, 5);//回调函数【空闲中断】
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
//请选择你的中断
if(huart == &huart1)
{
//接收数据
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,RxData,5);//DMA空闲中断
if(RxData[0] =='1')
{
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
//重置
RxData[0]=0;
}
}
}Tuxmonkey学长给大家的拜年词
