一、USART的基本概念
1.1 简介
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步接收器和发送器 ,支持全双工通信,即可以同时发送和接收数据。与UART(通用异步收发器)相比,USART不仅支持异步通信,还支持同步通信模式。
USART具有以下特点:
-
全双工/半双工:支持同时发送和接收数据。
-
异步通信:无需时钟同步,通过波特率和起始/停止位实现。
-
硬件流控制:支持RTS/CTS控制数据流。
-
DMA支持:提高数据传输效率。
1.2 USART和UART的区别
| 特性 | UART | USART |
|---|---|---|
| 通信模式 | 仅支持异步通信 | 支持同步和异步两种模式 |
| 时钟信号 | 无需外部时钟,依赖预定义的波特率 | 同步模式需外部时钟(SCLK引脚) |
| 硬件复杂度 | 较简单 | 更复杂(额外支持同步逻辑) |
| 应用场景 | 简单设备(传感器、蓝牙模块等) | 高速/时序严格的场景(智能卡、SPI兼容设备等) |
1.2.1异步模式(两者均支持)
工作原理:
通过起始位(Start Bit) 和停止位(Stop Bit) 界定数据帧。
双方需约定相同的波特率(如9600、115200bps)。
无需共享时钟,通信双方独立计时。
1.2.2 同步模式(仅USART支持)
工作原理:
通过共享时钟信号(SCLK) 同步数据传输。
发送方在时钟边沿(上升沿/下降沿)输出数据,接收方同步采样。
优势:
速率更高(时钟直接驱动,无波特率误差问题)。
时序更精确,抗干扰能力更强。
1.2.3 STM32中的实现差异
UART模式:
仅使用
TX(发送)、RX(接收)引脚。寄存器配置关闭同步相关功能(如
USART_CR2中的CLKEN)。USART同步模式:
启用
SCLK引脚输出时钟(通过USART_CR2的CLKEN位控制)。需配置时钟极性(
CPOL)和相位(CPHA),类似SPI。
二、USART硬件结构
2.1 硬件电路
USRT框图

2.1.1 发送器

TDR(发送数据寄存器)中的数据已转移到发送移位寄存器,则会设置标志位TXE = 1(发送寄存器空),表示可以向TDR(发送数据寄存器)写入新数据了。

发送移位寄存器会在发送器控制的驱动下,向右移位,将数据低位先行的方式,把数据一位一位的发送到TX引脚。
2.1.2 接收器

数据从RX引脚上来,在接收器控制下,数据一位一位的接收,向右移,移位八次之后,就是接收了一位字节。单字节完成接收之后,数据整体的会传到RDR(接受数据寄存器)。
数据转移完之后,会置一个标志位RXNE = 1(接收数据寄存器非空),此时此刻就可以吧RDR的数据读走了。
发送接收的时候内部的硬件电路会自动剔除帧头帧尾,所以我们发送接手的时候还是要带帧头帧尾的。
2.1.3 控制逻辑
当发送设备发的太快,接收设备来不及处理,会出现丢弃或覆盖数据的情况,流控就会发挥作用了。
有两个引脚(n的意思是低电平有效):
nRTS(Request To Send):请求接收,输入引脚
nCTS(Clear To Send):请求发送,输出引脚
接法也是RTS->CTS,CTS->RTS,交叉相连。
2.1.4 波特率发生器
其实就是分频器

fPCLKx(x=1,2)的意思是:USART1挂载在APB2,也就是PCLK2的时钟,一般是72M。
其他的USART都挂载在PCLK1的时钟,一般36M。

在红色区域这个时钟进行分频,也就是除于USARTDIV的分频系数。USARTDIC也就是蓝色部分,蓝色部分的左边是整数部分,右边是小数部分,有时候用72M除于整数除不尽,会有误差,小数部分的作用性就体现出来了。
最后通过发送器时钟发送到控制器部分。

如果TE = 1,则发送部分波特率有效
如果RE = 1,则接收部分波特率有效
通过以上的复习,我看到这个江协简化图就感觉一目了然了。

2.2 常用寄存器以及作用
2.2.1 USART_SR / USART_ISR (Status Register / Interrupt & Status Register) - 状态寄存器
作用: 反映USART当前的工作状态和各种事件标志。
2.2.2 USART_DR (Data Register) - 数据寄存
作用: 读写同一个物理寄存器,但具有不同的逻辑功能。
写操作 (CPU -> USART): 写入的是发送数据寄存器 (TDR)。CPU将要发送的数据写入此寄存器。
读操作 (CPU <- USART): 读取的是接收数据寄存器 (RDR)。CPU从此寄存器读取接收到的数据。
2.2.3 USART_BRR (Baud Rate Register) - 波特率寄存器
作用: 设置USART的波特率。
2.2.4 USART_CR1 (Control Register 1) - 控制寄存器1
作用: 配置USART的核心工作模式、中断使能等。
2.2.5 USART_CR2 (Control Register 2) - 控制寄存器2
作用: 主要配置停止位、时钟输出、LIN模式、智能卡模式等。
2.2.6 USART_CR3 (Control Register 3) - 控制寄存器3
作用: 配置DMA、硬件流控、错误中断、半双工模式等。
三 、USART通讯模式
3.1 轮询模式
程序通过不断检查USART的状态寄存器(如USART_SR)来判断是否有数据接收。这种方式简单但效率较低。
while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);
uint8_t data = USART_ReceiveData(USART1);
3.2 中断模式
中断模式允许程序在接收到数据时被中断处理函数唤醒。
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
3.3 DMA模式
DMA(直接内存访问)允许数据在内存和外设之间直接传输,无需CPU干预。这种方式适用于大数据量传输。
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_Channel = DMA1_Channel5;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)SendBuff;
DMA_InitStruct.DMA_DIR = DMA_DIR_PERIPH_TO_MEMORY;
DMA_InitStruct.DMA_BufferSize = sizeof(SendBuff);
DMA_InitStruct.DMA_PeripheralInc = DMA_PINC_DISABLE;
DMA_InitStruct.DMA_MemoryInc = DMA_MINC_INCREMENT;
DMA_InitStruct.DMA_Mode = DMA_NORMAL;
DMA_Init(&DMA_InitStruct);
四、USART通讯原理
USART支持两种主要通讯模式:异步模式和同步模式
4.1 数据帧结构
异步通讯(常用):
数据以帧的形式传输。一个标准数据帧包括:
-
起始位:1位逻辑0。
-
数据位:数据帧的有效载荷,低位先行,8或9位,可选奇偶校验位。
问:何为低位先行?
如果我要发0xF,即0000 1111,则从右边开始读,即11110000依次发送到引脚上。
-
校验位(可选):用于数据验证,一般分为奇校验和偶校验
问:何为奇校验和偶校验?
奇校验: 0000 1111 1 0000 0111 0(确保1的个数为奇数) 偶校验: 0000 1111 0 0000 1110 1(确保1的个数为偶数)
-
停止位:用于数据帧间隔,固定为高电平。
以下是一个无校验位的标准数据帧
一下是有校验位的数据帧
可以比对一下。
同步模式(少用):
数据传输依赖于时钟信号,通常用于高速通信。此时,数据帧结构简化,无需起始位和停止位,但需要主设备提供时钟信号。
4.2 波特率
波特率是数据传输速率,单位为bps(bit per second)。常见的波特率包括:9600、115200、57600等。波特率决定了数据传输的速度。
波特率计算
发送器和接收器的波特率由波特率寄存器BRR里的DIV确定 计算公式:波特率 = fPCLK2/1 / (16 * DIV)
问:如何配置BRR寄存器呢?
根据上面的公式,以9600为例:
波特率 = fPCLK2/1 / (16 * DIV)
9600=72M / (16*DIV)=72 000 000 / (16 x DIV)
解得DIV=468.75
二进制:111010100.11(前面补零)
4.3 时钟同步
在异步通信中,发送器和接收器的时钟是独立的,但通过波特率和采样点保持同步。
4.4 实际分析
0x55,9600,8位数据,1位停止位,无校验

0x55,9600,8位数据,1位停止位,偶校验

0x55,9600,8位数据,2位停止位,无校验

1/9600=104us,因此每个波形的时间为104us
五、USART配置
5.1 标准库配置过程
串口发送
-
使能USART时钟:通过RCC寄存器使能USART的时钟。(USART1是APB2,其他都是APB1的)
-
配置GPIO引脚:将TX和RX引脚配置为复用推挽输出或浮空或上垃输入。
-
配置USART参数:设置波特率、数据位、停止位、校验位等。
-
启用USART:通过USART_CR1寄存器使能USART。
void Serial_Init(void)
{
/*1.开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*2.GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA9引脚初始化为复用推挽输出
/*3.配置USART参数*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx; //模式,选择为发送模式
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART1, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART1
/*4.USART使能*/
USART_Cmd(USART1, ENABLE); //使能USART1,串口开始运行
}
5.2 HAL配置过程:
防止只能一次下载,我们先配置Serial Wire

接下来配置PA9和PA10,我们可以看到对应的是usart1

配置Usart1

发送代码:
/* USER CODE BEGIN Includes */
#include "string.h"
/* USER CODE END Includes */
/* USER CODE BEGIN 2 */
char message[] = "Hello World!\n";//新建字符数组
/* USER CODE END 2 */
while (1)
{
/*
&huart1:第一个参数是操作串口的指针,由于操作地址,则加“&”
(uint8_t*)message:第二个参数是发送信息的指针。由于HAL_UART_Transmit函数在这个参数要求为uint_8,因此强转。
strlen(message):第三个是发送信息的长度
100:第四个参数是超时时间,也可以用“HAL_MAX_DELAY”代表无限长发送超时时间
*/
HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), 100);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
硬件连接

RX(单片机)-TX(USB转TTTL)
TX(单片机)-RX(USB转TTL)
现象:

中断接收:
配置三个引脚

配置中断控制器

但要注意的是,这里不能将中断事件USART写在USART1_IRQHandler,因为在USART1_IRQHandler只有一个中断向量,而在“接收数据寄存器非空中断”、“发送数据寄存器空中断”和”线路空闲中断“等好几个中断都用了此处的中断处理函数,所以要做一些判断才能确定当前是否因为什么原因而触发的中断。
点击这里,跳转过去。

在2622行左右发现这个函数,复制HAL_UART_RxCpltCallback(这个函数会在接收完成前执行),把执行函数放在里面。代码如下
/* USER CODE BEGIN Includes */
#include "string.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
uint8_t receiveData[2];
/* USER CODE END PV */
/* USER CODE BEGIN 0 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Receive_IT(&huart1, receiveData, 2);
HAL_UART_Transmit_IT(&huart1, receiveData, 2);
GPIO_PinState state = GPIO_PIN_RESET;
if(receiveData[1] == '0'){
state = GPIO_PIN_SET;
}
if(receiveData[0] == 'A'){
HAL_GPIO_WritePin(A_GPIO_Port, A_Pin, state);
}else if(receiveData[0] == 'B'){
HAL_GPIO_WritePin(B_GPIO_Port, B_Pin, state);
}else if(receiveData[0] == 'C'){
HAL_GPIO_WritePin(C_GPIO_Port, C_Pin, state);
}
HAL_UART_Receive_IT(&huart1, receiveData, 2);//开启串口接收
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, receiveData, 2);//开启串口接收
/* USER CODE END 2 */
int main(void)
{
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, receiveData, 2);//开启串口接收
/* USER CODE END 2 */
while (1)
{
}
}
串口指令如下:

5.3 使用printf()所需配置
1、在stm32f1xx_hal.c包含#include<stdio.h>
#include "stm32f1xx_hal.h"
#include <stdio.h>
extern UART_HandleTypeDef huart1; //声明串口
2、stm32f1xx_hal.c 中添加fget和fput函数
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
return ch;
}
3、main.c添加
#define RX_BUFFER_SIZE 256
char RxBuffer[RX_BUFFER_SIZE];
while (1)
{
/* USER CODE END WHILE */
printf("Hello\n");
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}





1413





