一、USART简介

USART = Universal Synchronous Asynchronous Receiver Transmitter,通用同步异步收发器,是STM32最常用的串行通信外设,基于UART(异步收发器)升级而来,支持同步、异步两种通信模式,核心用于STM32与外部设备(串口调试助手、蓝牙、WiFi、GPS、单片机)之间的数据传输。
UART:通用异步收发传输器(Universal Asynchronous Receiver/Transmitter)。
USART:通用同步/异步收发器,兼容 UART 异步模式,STM32 外设命名为 USART,日常开发全部当作 UART 异步串口使用。

1、USART通信示意图

在这里插入图片描述

2、核心功能

支持异步通信(最常用):无需时钟线,仅需TX(发送)、RX(接收)、GND(共地)3根线即可实现双向通信。
支持同步通信:需额外提供SCLK(时钟线),适合高速、同步传输场景(较少用)。
支持全双工通信:可同时进行发送和接收操作,互不影响。
支持中断、DMA两种高效传输模式,可解放CPU,避免CPU被数据传输占用。
支持奇偶校验、帧错误检测、溢出错误检测,提升通信可靠性。

3、核心特点(STM32F103 专属)

STM32F103 包含3个USART:USART1(APB2总线,最高72MHz)、USART2/3(APB1总线,最高36MHz)。
常用波特率:9600、115200(最常用)、38400、19200,波特率误差需控制在1%以内,否则会出现乱码。
数据格式可配置:数据位(8位/9位,默认8位)、停止位(1位/2位,默认1位)、校验位(无校验/奇校验/偶校验,默认无校验)。
支持硬件流控(RTS/CTS),可防止高速传输时数据溢出(工业场景常用)。
可配合DMA实现高速、大量数据的自动收发,是工程中首选的通信方式。

4、USART 与 UART 的区别

对比项 USART UART
通信模式 同步、异步均可 仅异步
时钟线 支持SCLK时钟线(同步模式) 无需时钟线
STM32支持 USART1/2/3(F103) 无单独UART,USART可兼容UART模式
实际应用 主流选择,兼容UART,功能更全 简化版,仅异步,部分低端芯片使用

5、物理层

两个通讯设备的“DB9接口”之间通过串口信号线建立起连接,串口信号线中使用“RS-232标准”传输数据信号。
由于RS-232电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个“电平转换芯片”转换成控制器能识别的“TTL标准”的电平信号,才能实现通讯。
在这里插入图片描述

6、 电平标准

因为控制器一般使用TTL电平标准,所以常常会使用MA3232芯片对TTL及RS-232电平的信号进行互相转换。

通讯标准 电平标准(发送端)
5V TTL 逻辑1:2.4V-5V
逻辑0:0~0.5V
RS-232 逻辑1:-15V~-3V
逻辑0:+3V~+15V

7、RS-232信号线

在这里插入图片描述

序号 名称 符号 数据方向 说明
1 载波检测 DCD DTEDCE Data Carrier Detect,数据载波检测,用于DTE告知对方,本机是否收到对方的载波信号
2 接收数据 RXD DTEDCE Receive Data,数据接收信号,即输入 。
3 发送数据 TXD DTEDCE Transmit Data,数据发送信号,即输出。两个设备之间的TXD与RXD应交叉相连
4 数据终端 (DTE) 就绪 DTR DTEDCE Data Terminal Ready,数据终端就绪,用于DTE向对方告知本机是否已准备好
5 信号地 GND 地线,两个通讯设备之间的地电位可能不一样,所以两个设备之间必须要使用地线连接,即共地。
6 数据设备(DCE)就绪 DSR DTEDCE Data Set Ready,数据发送就绪,用于DCE告知对方本机是否处于待命状态
7 请求发送 RTS DTEDCE Request To Send,请求发送, DTE 请求 DCE 本设备向DCE端发送数据
8 允许发送 CTS DTEDCE Clear To Send,允许发送,DCE回应对方的RTS发送请求,告知对方是否可以发送数据
9 响铃指示 RI DTEDCE Ring Indicator,响铃指示,表示DCE端与线路已接通

二、USART模块详解

1、USART 内部结构框图

1.功能引脚

TX:发送数据输出引脚。
RX:接收数据输入引脚。
SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。
nRTS:请求以发送(Request To Send),n表示低电平有效。如果使能RTS流控制,当USART接收器准备好接收新数据时就会将nRTS变成低电平;当接收寄存器已满时,nRTS将被设置为高电平。该引脚只适用于硬件流控制。
nCTS:清除以发送(Clear To Send),n表示低电平有效。如果使能CTS流控制,发送器在发送下一帧数据之前会检测nCTS引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。
SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。

2.数据寄存器

USART数据寄存器(USART_DR)只有低9位有效,并且第9位数据是否有效要取决于USART控制寄存器1(USART_CR1)的M位设置,当M位为0时表示8位数据字长,当M位为1表示9位数据字长,我们一般使用8位数据字长。
USART_DR包含了已发送的数据或者接收到的数据。USART_DR实际是包含了两个寄存器,一个专门用于发送的可写TDR,一个专门用于接收的可读RDR。当进行发送操作时,往USART_DR写入数据会自动存储在TDR内;当进行读取操作时,向USART_DR读取数据会自动提取RDR数据。
TDR和RDR都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的,发送时把TDR内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到RDR。

3.控制器

USART有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。
使用USART之前需要向USART_CR1寄存器的UE位置1使能USART,UE位用来开启供给给串口的时钟。
发送或者接收数据字长可选8位或9位,由USART_CR1的M位控制。

4.小数波特率生成

波特率指数据信号对载波的调制速率,它用单位时间内载波调制状态改变次数来表示,单位为波特。
比特率指单位时间内传输的比特数,单位bit/s(bps)。
对于USART波特率与比特率相等,以后不区分这两个概念。
波特率越大,传输速率越快。
计算公式如下:

  在这里插入图片描述
其中,fPLCK为USART时钟, USARTDIV是一个存放在波特率寄存器(USART_BRR)的一个无符号定点数。
其中DIV_Mantissa[11:0]位定义USARTDIV的整数部分,DIV_Fraction[3:0]位定义USARTDIV的小数部分。

2、核心概念

1.异步通信核心:

无需时钟线,双方约定相同的波特率、数据格式(数据位、停止位、校验位),通过起始位(低电平)、数据位、停止位(高电平)实现数据同步,每帧数据格式如下:
  起始位(1位,低电平)→ 数据位(8/9位)→ 校验位(0/1位)→ 停止位(1/2位,高电平)

2.波特率:

每秒传输的二进制位数(bit/s),是通信的“速度”,双方必须一致,否则会出现乱码;
STM32F103 USART1(72MHz)最大波特率可达到4.5Mbps,常用115200bps(兼顾速度和稳定性)。

3.数据缓冲器:

TDR(发送缓冲器)和RDR(接收缓冲器),各1个字节,用于暂存数据,避免因CPU处理不及时导致数据丢失;
  例如,接收数据时,串行数据先存入RDR,CPU再从RDR读取数据。

4.三种工作模式(工程核心,按效率排序):

轮询模式(阻塞模式):CPU不断查询USART状态,等待数据收发完成,简单易实现,但占用CPU资源,适合少量、低速数据传输。
中断模式:数据收发完成后触发中断,CPU正常执行其他逻辑,中断到来时再处理数据,不占用CPU资源,适合中少量、实时性要求高的数据传输。
DMA模式(推荐):通过DMA硬件自动完成数据搬运,CPU完全不参与,适合大量、高速数据传输(如串口接收不定长数据、批量发送数据)。

5.空闲中断(IDLE):

当USART接收完一帧数据后,若一段时间内没有新的数据到来,触发空闲中断,是实现“不定长数据接收”的核心(工业标配方案)。

6.硬件流控(RTS/CTS):

RTS(请求发送)、CTS(清除发送),用于高速传输时防止数据溢出;
例如,STM32发送数据前,先检查CTS引脚电平,若为低电平,说明接收方准备就绪,再发送数据。

7.数据帧格式

标准一帧数据结构:起始位(1) + 数据位(8) + 校验位(0) + 停止位(1)
  - 波特率:常用 9600、115200
  - 数据位:8bit(默认)
  - 停止位:1bit
  - 校验位:无校验(工程默认)

3、STM32F103 USART 引脚分配

默认引脚(可通过AFIO重映射修改,CubeMX可直接配置):
  注意:PA9/PA10(USART1)默认是JTAG调试引脚,若使用USART1,需在CubeMX中关闭JTAG功能(设置为GPIO),否则串口通信异常。
在这里插入图片描述

三、CudeMx配置USART

  配置USART1为调试输出。

1、系统时钟配置

STM32F103默认72MHz,确保APB1(36MHz)、APB2(72MHz)时钟配置正确(USART1挂在APB2,USART2/3挂在APB1)。
预先占用调试引脚:左侧「SYS」→ 「Debug」选择「Serial Wire」。
在这里插入图片描述

2、选择 USART1

Pinout & Configuration → Connectivity找到 USART1。
  Mode 选择 Asynchronous(异步)。
  参数设置(默认即可)
    Baud Rate:115200
    Word Length:8 Bits
    Parity:None
    Stop Bits:1
    Hardware Flow Control:Disable
在这里插入图片描述

3、fputc重映射函数

#include "stdio.h"

#define        huart_debug                huart1

/*
* @name   fputc
* @brief  fputc映射物理串口
* @param  ch -> 待发送字符
* @retval ch -> 已发送字符      
*/
int fputc(int ch,FILE* f)
{
//通过查询的方式循环发送
HAL_UART_Transmit(&huart_debug, (uint8_t *)&ch, 1, 0x000A);

return ch;
}

4、__weak弱函数

相当于C++的虚函数,可以被重写,重映射,重映射后的函数优先执行。

5、勾选Target中的use MicroLIB

在这里插入图片描述

1.选上”Use MicroLIB”这是KEIL自带的一个简易的库,例如你用printf函数的时候,就会从串口1 输出字符串,直接默认定向到串口1。
2.microlib 是缺省 C 库的备选库。 它用于必须在极少量内存环境下运行的深层嵌入式应用程序。

6、主函数直接使用

printf("UART Test OK!\r\n");  

四、USART编程模块

1、数据结构

    串口初始化参数结构体  
			typedef struct
			{
			  uint32_t BaudRate;            // 波特率
			  uint32_t WordLength;          // 数据位
			  uint32_t StopBits;            // 停止位
			  uint32_t Parity;              // 校验位
			  uint32_t Mode;                // 收发模式
			  uint32_t HwFlowCtl;           // 硬件流控
			  uint32_t OverSampling;        // 过采样
			  } UART_InitTypeDef;
    `HAL串口外设的核心数据结构`  
			typedef struct __UART_HandleTypeDef
			{
			  USART_TypeDef                 *Instance;        /* 串口寄存器基地址 */
			  UART_InitTypeDef              Init;             /* 串口初始化参数 */
			  uint8_t                       *pTxBuffPtr;      /* 发送数据缓冲区指针 */
			  uint16_t                      TxXferSize;       /* 发送数据总长度 */
			  __IO uint16_t                 TxXferCount;      /* 发送剩余长度 */
			  uint8_t                       *pRxBuffPtr;      /* 接收数据缓冲区指针 */
			  uint16_t                      RxXferSize;       /* 接收数据总长度 */
			  __IO uint16_t                 RxXferCount;      /* 接收剩余长度 */
			  DMA_HandleTypeDef             *hdmatx;          /* TX DMA句柄 */
			  DMA_HandleTypeDef             *hdmarx;          /* RX DMA句柄 */
			  HAL_LockTypeDef               Lock;             /* 资源锁 */
			  __IO HAL_UART_StateTypeDef    State;            /* 串口工作状态 */
			  __IO uint32_t                 ErrorCode;        /* 错误码 */
			  } UART_HandleTypeDef;

2、句柄

串口句柄

	UART_HandleTypeDef huart1;  // USART1 句柄

DMA句柄

	DMA_HandleTypeDef *hdmatx;
	DMA_HandleTypeDef *hdmarx;

3、API

初始化和配置

            // 串口初始化  
            HAL_UART_Init(UART_HandleTypeDef *huart);  
              
            // 串口反初始化(关闭时钟、复位)  
            HAL_UART_DeInit(UART_HandleTypeDef *huart);  
              
            // 底层初始化(GPIO、时钟、中断),由HAL自动调用  
            void HAL_UART_MspInit(UART_HandleTypeDef *huart);  

阻塞(轮询)收发 API

          // 阻塞发送  
           HAL_UART_Transmit(  
             UART_HandleTypeDef *huart,  
             uint8_t *pData,  
             uint16_t Size,  
             uint32_t Timeout  
           );  
             
           // 阻塞接收  
           HAL_UART_Receive(  
             UART_HandleTypeDef *huart,  
             uint8_t *pData,  
             uint16_t Size,  
             uint32_t Timeout  
           );  

中断收发 API

            // 中断发送  
            HAL_UART_Transmit_IT(huart, pData, Size);  
            // 中断接收  
            HAL_UART_Receive_IT(huart, pData, Size);  
            // 发送完成回调  
            void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);  
            // 接收完成回调  
            void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);  

DMA 收发 API

           // DMA发送  
           HAL_UART_Transmit_DMA(huart, pData, Size);  
           // DMA接收  
           HAL_UART_Receive_DMA(huart, pData, Size);  
           // 停止DMA接收  
           HAL_UART_DMAStop(huart);  
             
           void HAL_UART_TxHalfCpltCallback(huart);  // 发送半完成  
           void HAL_UART_TxCpltCallback(huart);      // 发送完成  
           void HAL_UART_RxHalfCpltCallback(huart);  // 接收半完成  
           void HAL_UART_RxCpltCallback(huart);      // 接收完成  

中止 / 停止传输 API

           // 中止所有收发  
           HAL_UART_Abort(UART_HandleTypeDef *huart);  
           // 只中止发送  
           HAL_UART_AbortTransmit(huart);  
           // 只中止接收  
           HAL_UART_AbortReceive(huart);  
           // 中断模式中止  
           HAL_UART_Abort_IT(huart);  

状态与错误 API

          // 获取串口状态  
          HAL_UART_StateTypeDef HAL_UART_GetState(huart);  
          // 获取错误码  
          uint32_t HAL_UART_GetError(huart);  
          // 错误回调  
          void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);  

串口高级控制

		// 暂停串口
		HAL_UART_DMAPause(huart);
		// 恢复串口
		HAL_UART_DMAResume(huart);
		// 停止串口
		HAL_UART_DMAStop(huart);

4、状态/错误

		HAL_UART_STATE_RESET:未初始化
		HAL_UART_STATE_READY:就绪空闲
		HAL_UART_STATE_BUSY_TX:发送中
		HAL_UART_STATE_BUSY_RX:接收中
		HAL_UART_STATE_ERROR:异常错误 

五、USART模块应用实例

1、轮询方式发送 + 接收

功能:

串口循环发送字符串,同时阻塞接收数据。

特点:

最简单,不用中断、不用 DMA。
阻塞式,CPU 一直等待。
适合调试、打印日志。

代码:

// 发送缓冲区
uint8_t send_buf[] = "Hello USART Polling!\r\n";
// 接收缓冲区
uint8_t recv_buf[10] = {0};

int main(void)
{
    // HAL 初始化
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();  // 串口初始化

    while(1)
    {
        // 1. 轮询发送
        HAL_UART_Transmit(&huart1, send_buf, sizeof(send_buf)-1, 100);
        
        // 2. 轮询接收:等待收到1个字节
        HAL_UART_Receive(&huart1, recv_buf, 1, 1000);
        
        // 3. 把收到的数据发回去
        HAL_UART_Transmit(&huart1, recv_buf, 1, 100);
        
        HAL_Delay(500);
    }
}

2、中断方式接收

功能:

串口收到数据后触发中断,在回调函数处理,不影响主循环。

特点:

中断接收一次有效,必须在回调里重新开启。
不占用主循环,实时性好。
工程最常用。

代码:

CubeMX在配置时打开中断。

在这里插入图片描述

			// 每次接收1字节
			uint8_t recv_data = 0;
			
			int main(void)
			{
			    HAL_Init();
			    SystemClock_Config();
			    MX_GPIO_Init();
			    MX_USART1_UART_Init();
			
			    // 开启第一次中断接收
			    HAL_UART_Receive_IT(&huart1, &recv_data, 1);
			
			    while(1)
			    {
			        // 主循环可以做别的事情,不被串口阻塞
			    }
			}
			
			// 接收完成中断回调函数
			void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
			{
			    if(huart == &huart1)
			    {
			        // 把收到的数据回发
			        HAL_UART_Transmit_IT(&huart1, &recv_data, 1);
			        
			        // 重新开启中断接收(必须写!)
			        HAL_UART_Receive_IT(&huart1, &recv_data, 1);
			    }
}

3、DMA 循环接收

功能:DMA 自动接收数据,CPU 完全不参与,适合蓝牙、WiFi、串口屏。
特点:
CPU 零负担。
适合高速、大量、连续数据。
串口透传、GPS、WiFi 模块必用。
代码:

CubeMX配置

在这里插入图片描述

			// DMA接收缓冲区
			#define TX_BUF_SIZE    32    // 发送缓冲区大小
			#define RX_BUF_SIZE    32    // 接收缓冲区大小
			
			uint8_t DMA_TX_Buf[TX_BUF_SIZE] = "DMA Circular TX Test!!!\r\n";
			uint8_t DMA_RX_Buf[RX_BUF_SIZE] = {0};
			
			int main(void)
			{
			    HAL_Init();
			    SystemClock_Config();
			    MX_GPIO_Init();
			    MX_USART1_UART_Init();
			    MX_DMA_Init();  // DMA必须初始化
			
			    // 启动DMA循环接收
			    HAL_UART_Receive_DMA(&huart1, DMA_RX_Buf, RX_BUF_SIZE);
			    // 启动DMA循环发送
			    HAL_UART_Transmit_DMA(&huart1, DMA_TX_Buf, TX_BUF_SIZE);
			
			
			    while(1)
			    {
			        // 主循环处理数据
			        // 例如:解析 dma_recv_buf 里的数据
			        HAL_Delay(100);
			    }
			}


4、重定向 printf(调试神器)

直接用 printf 打印调试信息。
勾选 Use MicroLIB ✅
(不勾,printf 会卡死/不打印)

#include <stdio.h>

//初始化USART1
……
// 重定向fputc
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 100);
    return ch;
}
//使用
printf("Test\n");

六、总结

UART是嵌入式通信基础,掌握CubeMX配置、三种收发模式、Printf调试、DMA不定长接收。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐