文章目录
- 串口USART
- 1. 常见的通信方式
-
- 1.1 串行通信和并行通信
- 1.2 全双工,半双工和单工通信
- 1.3 同步通信和异步通信
- 1.4 通信速率
- 1.5 常见的通信协议
- 2. 串口基础知识
-
- 2.1 电平特性
- 2.2 串口传输协议
- 2.3 STM32F103的USART资源
- 2.4 STM32F103的UART框图
-
- 2.4.1 端口引脚
- 2.4.2 数据寄存器单元
- 2.4.3 发送接收控制单元
- 2.4.4 波特率发生器
- 2.4.5 UART框图配置步骤
- 3. 实现串口发送
-
- 3.1 发送字符和字符串
- 3.2 TXE 和 TC
- 4. printf重定向
- 5. 实现串口中断接收
- 6. 串口发送接收控制LED灯
串口USART
1. 常见的通信方式
1.1 串行通信和并行通信
串行通信:速度较并行慢,但是占用的硬件资源少,通常只需要时钟线,一两根数据线还有片选线即可。 
并行通信:速度快,但是需要很多根数据线、地址线等。 
1.2 全双工,半双工和单工通信
全双工:同一时刻双方可以互相发送数据、接收数据 
半双工:同一时刻只能有一方可以给另外一方发送数据,即此发彼收,此收彼发 
单工:只能由发送方数据发送给接收方 
1.3 同步通信和异步通信
- 同步通信和异步通信都是针对串行通信而言
- 异步通信
- 1.数据是以字符为单位组成字符帧传输的。
- 2.字符帧由发送端一帧一帧的发送,每帧数据均是低位在前,高位在后,通过传输线被接收端一帧一帧的接收。
- 3.发送端和接收端可以有各自独立的时钟来控制数据的发送和接收,这两个时钟各自独立,互不同步。
- 4.接收端依靠字符帧格式来判断发送端是何时开始和结束发送的。
- 5.字符帧也叫做数据帧,由起始位,数据位,奇偶校验位,停止位等部分组成,是异步通信的一个重要指标。
- 6.同步通信的另一个重要指标是波特率 (表示每秒钟传输的符号数量,单位是 波特)。
- 同步通信
- 1.同步是指在约定的通信速率下,发送端和接收端的时钟信号始终保持一致,保证通信双方在发送和接收数据时具有完全一致的定时关系。
- 2.同步通信把许多字符组成一个信息帧,每帧的开始用同步字符来表示。
- 3.在绝大多数场合下,发送端和接收端,采用的都是同一个时钟,所以在传送数据的同时还要发送时钟信号,以便接收端可以使用时钟信号来确定每一个信息位。
- 4.同步通信一次通信只能传送一帧信息。
1.4 通信速率
对于同步通信,通信速率由时钟信号决定,时钟信号越快,传输速度越快。 对于异步通信来说,需要收发双方提前统一通信速率,这也就是我们串口调试时,波特率不对显示乱码的原因。
1、比特率:系统在单位时间内传输的比特率个数 (二进制0或1) ,通常用Rb表示,单位是比特 (bit/s),缩写为bps。 2、波特率:系统在单位时间内传输的码元个数,通常用RB来表示,单位是波特 (Bd) 3、码元有N个状态时,比特率与波特率的关系式:Rb = RB × log2 N (比特率 = 波特率 × 每符号比特数)
1.5 常见的通信协议
在嵌入式中,有众多的通信协议,往往从性能,成本,稳定性,易用性等角度考虑选择合适的协议。 
2. 串口基础知识
2.1 电平特性

2.2 串口传输协议
参数概念:
- 波特率:一般选波特率都会有9600,19200,115200等选项,其实意思就是每秒传输的码元数。
- 起始位:先发出一个逻辑 0 的信号,表示传输数据的开始。
- 数据位:可以是5~8位逻辑 “0” 或“1”,先传输Bit0,再传输bit1,以此类推。
- 校验位:数据位加上这一位之后,使得 1 的位数应为偶数(偶校验)或者奇数(奇校验),以此来校验数据传送的正确性,校验位是可选的,可以不传输。
- 停止位:它是一个字符数据的结束标志,数据线变回逻辑“1”。
2.3 STM32F103的USART资源

2.4 STM32F103的UART框图

2.4.1 端口引脚

2.4.2 数据寄存器单元

2.4.3 发送接收控制单元

2.4.4 波特率发生器

1、假设fPCLK = 72MHZ,baudrate = 115200, 那么USARTDIV=72MHZ/16/115200 =39.0625, 则DIV_Mantissa = 39<<4 = 0x270,DIV_Fraction=0.0625/0.0625=1, 因此BRR= 0X271
2、假设fPCLK = 72MHZ, baudrate = 9600, 那么USARTDIV=72MHZ/9600 = 468.75, 则DIV_Mantissa = 468<<4=0x1D40, DIV_Fraction=0.75/0.0625=12=0xC, 因而BRR=0X1D4C
2.4.5 UART框图配置步骤
- 选择需要使用的USART/UART (根据地址映射表得到地址)
- 根据需要的波特率设置BRR寄存器
- 根据需求配置控制寄存器中的停止位和校验位
- 根据需求配置同步时钟使能位
- 使能USART的发送和接收位
- 根据需求使能发送和接收的中断位
- 使能RCC中的选中的USART/UART的时钟
- 使能USART/UART
- 写USART_DR寄存器发送数据,读USART_DR寄存器接收数据
3. 实现串口发送
3.1 发送字符和字符串
软件流程设计
- 初始化系统
- 初始化GPIO、串口外设时钟
- 初始化串口引脚 (A9 数据发送, A10 数据接收)
- 初始化串口外设
- 串口发送
// USART.h 头文件
#ifndef USART_H
#define USART_H
void My_Usart_Init(void);
void My_Usart_Send_Byte(USART_TypeDef* USARTx, uint16_t Data);
void My_Usart_Send_String(USART_TypeDef* USARTx, char *str);
#endif
// USART.c 源文件
#include "stm32f10x.h"
#include "USART.h"
// 定义 USART 串口函数
void My_Usart_Init(void){
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;// 定义 USART 结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
USART_Cmd(USART1, ENABLE);// 串口时钟使能
// GPIOA9 TX 输出数据发送
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// GPIOA10 RX 输入 数据接收
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 串口 USART1 初始化
USART_InitStruct.USART_BaudRate = 115200;// 波特率
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 串口数据的字节长度
USART_InitStruct.USART_StopBits = USART_StopBits_1;// 停止位
USART_InitStruct.USART_Parity = USART_Parity_No;// 奇偶校验位
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 发送 接收模式
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 硬件流控制
USART_Init(USART1, &USART_InitStruct);// 初始化 USART1 结构体
}
// USART 串口发送一个字节函数
void My_Usart_Send_Byte(USART_TypeDef* USARTx, uint16_t Data){
USART_SendData(USARTx, Data);
// 等待传输完成(TC标志置位), 发送移位寄存器为空(影子寄存器)
while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
}
// USART串口发送字符串函数
void My_Usart_Send_String(USART_TypeDef* USARTx, char *str){
uint16_t i = 0;
while(*(str+i) != '\\0'){
My_Usart_Send_Byte(USARTx, *(str+i));
i++;
};
// 所有字节已在 My_Usart_Send_Byte 中等待 TC, 此处无需再等
}
// main.c
#include "stm32f10x.h"
#include "main.h"
#include "USART.h"
void delay(uint16_t time)
{
uint16_t i=0;
while(time—)
{
i = 12000;
while(i—);
}
}
int main()
{
My_Usart_Init();
My_Usart_Send_Byte(USART1, 'A');
My_Usart_Send_Byte(USART1, 'B');
My_Usart_Send_Byte(USART1, 'C');
My_Usart_Send_String(USART1, "ZhangSan \\r\\n");
while(1)
{
}
}
库函数实现串口发送
串口USART结构体:
typedef struct {
uint32_tUSART_BaudRate;// 设置 USART 的波特率
uint16_tUSART_WordLength;// 设置数据帧中的数据位长度
uint16_tUSART_StopBits;// 设置停止位的数量
uint16_tUSART_Parity;// 设置奇偶校验方式
uint16_tUSART_Mode;// 设置 USART 的工作模式 (发送/接收使能)
uint16_tUSART_HardwareFlowControl;// 设置硬件流控制 (RTS/CTS 信号)
} USART_InitTypeDef;
串口USART相关库函数:
voidUSART_DeInit(USART_TypeDef*USARTx);
voidUSART_Init(USART_TypeDef*USARTx, USART_InitTypeDef*
USART_InitStruct);
voidUSART_StructInit(USART_InitTypeDef*USART_InitStruct);
voidUSART_ClockInit(USART_TypeDef*USARTx, USART_ClockInitTypeDef*
USART_ClockInitStruct);
voidUSART_ClockStructInit(USART_ClockInitTypeDef*
USART_ClockInitStruct);
voidUSART_Cmd(USART_TypeDef*USARTx, FunctionalStateNewState);
voidUSART_ITConfig(USART_TypeDef*USARTx, uint16_tUSART_IT,
FunctionalStateNewState);
voidUSART_DMACmd(USART_TypeDef*USARTx, uint16_tUSART_DMAReq,
FunctionalStateNewState);
voidUSART_SetAddress(USART_TypeDef*USARTx, uint8_tUSART_Address);
voidUSART_WakeUpConfig(USART_TypeDef*USARTx, uint16_tUSART_WakeUp);voidUSART_ReceiverWakeUpCmd(USART_TypeDef*USARTx, FunctionalState
NewState);
voidUSART_LINBreakDetectLengthConfig(USART_TypeDef*USARTx, uint16_t
USART_LINBreakDetectLength);
voidUSART_LINCmd(USART_TypeDef*USARTx, FunctionalStateNewState);
voidUSART_SendData(USART_TypeDef*USARTx, uint16_tData);
uint16_tUSART_ReceiveData(USART_TypeDef*USARTx);
voidUSART_SendBreak(USART_TypeDef*USARTx);
voidUSART_SetGuardTime(USART_TypeDef*USARTx, uint8_t
USART_GuardTime);
voidUSART_SetPrescaler(USART_TypeDef*USARTx, uint8_t
USART_Prescaler);
voidUSART_SmartCardCmd(USART_TypeDef*USARTx, FunctionalState
NewState);
voidUSART_SmartCardNACKCmd(USART_TypeDef*USARTx, FunctionalState
NewState);
voidUSART_HalfDuplexCmd(USART_TypeDef*USARTx, FunctionalState
NewState);
voidUSART_OverSampling8Cmd(USART_TypeDef*USARTx, FunctionalState
NewState);
voidUSART_OneBitMethodCmd(USART_TypeDef*USARTx, FunctionalState
NewState);
voidUSART_IrDAConfig(USART_TypeDef*USARTx, uint16_tUSART_IrDAMode);voidUSART_IrDACmd(USART_TypeDef*USARTx, FunctionalStateNewState);
FlagStatusUSART_GetFlagStatus(USART_TypeDef*USARTx, uint16_t
USART_FLAG);
voidUSART_ClearFlag(USART_TypeDef*USARTx, uint16_tUSART_FLAG);
ITStatusUSART_GetITStatus(USART_TypeDef*USARTx, uint16_tUSART_IT);
voidUSART_ClearITPendingBit(USART_TypeDef*USARTx, uint16_t
USART_IT);
3.2 TXE 和 TC
| USART_FLAG_TXE | 发送数据寄存器为空 | 数据从 DR 移入移位寄存器后立即置位(数据尚未完全发出!) |
| USART_FLAG_TC | 传输完成 | 整个帧(包括停止位)真正发送完毕 |
| 发送速度 | 更快(可提前写入下一字节) | 稍慢(需等完整帧) |
| 数据可靠性 | ❌ 可能丢尾字节 | ✅ 保证物理层发送完成 |
| 适用场合 | 高速连续发送(如 DMA 或中断缓冲) | 单字节/调试输出/可靠传输 |
4. printf重定向
C语言里面的printf函数默认输出设备是显示器,如果要实现printf函数输出在串口或LCD显示屏上,必须要重定义标准库函数里面调用的I/O输出设备相关的函数,比如printf输出串口,需要将 fputc 里面的输出指向串口。
1、配置勾选 
2、printf 重定向
// USATR.c 源文件
#include "stm32f10x.h"
#include "USART.h"
#include "stdio.h"
// 定义 USART 串口函数
void My_Usart_Init(void){
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;// 定义 USART 结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
USART_Cmd(USART1, ENABLE);// 串口时钟使能
// GPIOA9 TX 输出数据发送
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// GPIOA10 RX 输入 数据接收
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 串口 USART1 初始化
USART_InitStruct.USART_BaudRate = 115200;// 波特率
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 串口数据的字节长度
USART_InitStruct.USART_StopBits = USART_StopBits_1;// 停止位
USART_InitStruct.USART_Parity = USART_Parity_No;// 奇偶校验位
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 发送 接收模式
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 硬件流控制
USART_Init(USART1, &USART_InitStruct);// 初始化 USART1 结构体
}
// printf 重定向
int fputc(int ch, FILE *p){
USART_SendData(USART1, (u8)ch);
// 等待整个帧发送完成 (包括停止位)
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
return ch;
}
// main.c
#include "stm32f10x.h"
#include "main.h"
#include "USART.h"
#include "stdio.h"
int main()
{
My_Usart_Init();
while(1)
{
printf("Hello World \\r\\n");
}
}
5. 实现串口中断接收
软件流程设计
- 初始化系统
- 初始化GPIO、串口时钟
- 初始化GPIO、串口引脚
- 初始化串口中断接收外设、NVIC嵌套中断控制器
- 编写串口中断函数
// USATR.c 源文件
#include "stm32f10x.h"
#include "USART.h"
#include "stdio.h"
// 定义 USART 串口函数
void My_Usart_Init(void){
GPIO_InitTypeDef GPIO_InitStruct;// 定义 GPIO 结构体
USART_InitTypeDef USART_InitStruct;// 定义 USART 结构体
NVIC_InitTypeDef NVIC_InitStruct;// 定义 NVIC 结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
USART_Cmd(USART1, ENABLE);// 串口时钟使能
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 优先级分组配置
// GPIOA9 TX 输出数据发送
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// GPIOA10 RX 输入 数据接收
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 串口 USART1 初始化
USART_InitStruct.USART_BaudRate = 115200;// 波特率
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 串口数据的字节长度
USART_InitStruct.USART_StopBits = USART_StopBits_1;// 停止位
USART_InitStruct.USART_Parity = USART_Parity_No;// 奇偶校验位
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 发送 接收模式
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 硬件流控制
USART_Init(USART1, &USART_InitStruct);// 初始化 USART1 结构体
// 串行通信中断配置
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);// 接收数据寄存器不为空发生中断
// NVIC 初始化
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;// 初始化串口中断线1
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
// printf 重定向
int fputc(int ch, FILE *p){
USART_SendData(USART1, (u8)ch);
// 等待整个帧发送完成 (包括停止位)
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
return ch;
}
// 串口中断函数, 只要满足完整中断条件, 自动执行函数
// "中断配置条件" 必须同时包含 USART配置 + NVIC使能
void USART1_IRQHandler(void){
char str;
// 如果检查到串口中断线(USART1)发生了中断请求
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){
str = USART_ReceiveData(USART1);// 串口数据接收
printf("receive date: %c \\r\\n", str);
// 清除串口中断线(USART1)的中断挂起标志位, 方便下次还能运行函数
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
// main.c
#include "stm32f10x.h"
#include "main.h"
#include "USART.h"
int main()
{
My_Usart_Init();
while(1)
{
}
}
6. 串口发送接收控制LED灯
// USATR.c 源文件
#include "stm32f10x.h"
#include "USART.h"
#include "stdio.h"
// 定义 USART 串口函数
void My_Usart_Init(void){
GPIO_InitTypeDef GPIO_InitStruct;// 定义 GPIO 结构体
USART_InitTypeDef USART_InitStruct;// 定义 USART 结构体
NVIC_InitTypeDef NVIC_InitStruct;// 定义 NVIC 结构体
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
USART_Cmd(USART1, ENABLE);// 串口时钟使能
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 优先级分组配置
// GPIOA9 TX 输出数据发送
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// GPIOA10 RX 输入 数据接收
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 串口 USART1 初始化
USART_InitStruct.USART_BaudRate = 115200;// 波特率
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 串口数据的字节长度
USART_InitStruct.USART_StopBits = USART_StopBits_1;// 停止位
USART_InitStruct.USART_Parity = USART_Parity_No;// 奇偶校验位
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 发送 接收模式
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 硬件流控制
USART_Init(USART1, &USART_InitStruct);// 初始化 USART1 结构体
// 串行通信中断配置
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);// 接收数据寄存器不为空发生中断
// NVIC 初始化
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;// 初始化串口中断线1
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
// printf 重定向
int fputc(int ch, FILE *p){
USART_SendData(USART1, (u8)ch);
// 等待整个帧发送完成 (包括停止位)
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
return ch;
}
// 串口中断函数, 只要满足完整中断条件, 自动执行函数
// "中断配置条件" 必须同时包含 USART配置 + NVIC使能
void USART1_IRQHandler(void){
char str;
// 如果检查到串口中断线(USART1)发生了中断请求
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){
str = USART_ReceiveData(USART1);// 串口数据接收
printf("receive date: %c \\r\\n", str);
// 清除串口中断线(USART1)的中断挂起标志位, 方便下次还能运行函数
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
// 串口中断函数, 只要满足完整中断条件, 自动执行函数
void USART1_IRQHandler(void){
char str = 0;
// 判断是否发生中断
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){
str = USART_ReceiveData(USART1);// 串口数据接收
printf("receive date: %c \\r\\n", str);
// 串口接收到字符 0 开灯
if(str == '0'){
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
printf("LED IS ON \\r\\n");
}
// 串口接收到字符 1 关灯
if(str == '1'){
GPIO_SetBits(GPIOA, GPIO_Pin_1);
printf("LED IS OFF \\r\\n");
}
// 清除串口中断线的挂起标志位, 方便函数下次运行
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
// main.c
#include "stm32f10x.h"
#include "main.h"
#include "USART.h"
#include "LED.h"
int main()
{
My_Usart_Init();
LED_Init();
while(1)
{
}
}



![[开源]基于STM32单片机WIFI智能窗户系统 Onenet云平台设计HK-003-171主机测评](https://www.171host.com/wp-content/uploads/2026/09/20260903070616-6a991c6823886-220x150.jpg)

