STM32 USART 详解与实战应用
目录
1.引入
1.1 通信接口
通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统
通信协议:制定通信的规则,通信双方按照协议规则进行数据收发
名称 | 引脚 | 双工 | 时钟 | 电平 | 设备 |
---|---|---|---|---|---|
USART | TX、RX | 全双工 | 异步 | 单端 | 点对点 |
I2C | SCL、SDA | 半双工 | 同步 | 单端 | 多设备 |
SPI | SCLK、MOSI、MISO、CS | 全双工 | 同步 | 单端 | 多设备 |
CAN | CAN_H、CAN_L | 半双工 | 异步 | 差分 | 多设备 |
USB | DP、DM | 半双工 | 异步 | 差分 | 点对点 |
1.2 串口
具体一点的内容看嵌入式驱动开发中的UART子系统对串口的硬件简介。
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信
单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力
上图左边是USB转串口模块,中间是陀螺仪传感器模块(左边的是接串口、右边的引脚接I2C),右边是蓝牙串口模块
数据低位先行
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
串口参数及时序:
串口时序:
2.USART
对应芯片参考手册的USART部分
2.1 简介
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器
USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
自带波特率发生器,最高达4.5Mbits/s。其实就是个分频器,波特率发生器对时钟进行一个分频,就能得到想要的波特率时钟,在该时钟下进行收发,就是指定的通信波特率。
可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
可选校验位(无校验/奇校验/偶校验)
支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
STM32F103C8T6 USART资源: USART1(APB2总线上的)、 USART2(APB1总线上的)、 USART3(APB1总线上的)
2.2 框图
来自参考手册
1. 数据接口模块
2. 发送器部分
发送数据寄存器 (TDR)
发送移位寄存器
发送控制逻辑
3. 接收器部分
接收移位寄存器
接收数据寄存器 (RDR)
接收控制逻辑
4. 控制逻辑模块
中断控制
中断源 (CR1, CR2, CR3):
状态寄存器 (SR):
5. 时钟生成模块
USART_BRR寄存器

其中,
DIV_Mantissa
表示整数部分,DIV_Fraction
表示小数部分。6. 模式配置模块
CR1、CR2、CR3寄存器
7. DMA支持
8. 数据传递路径
- 发送路径:
- 接收路径:
2.3 基本机构图
注意:无论是发送还是接收,数据都是低位先行。
2.4 数据帧
字长设置:
在时钟的上升沿进行数据的读取。对于9位字长,一般第9位会选择为校验位,毕竟前面8位刚好就是1字节,uint8。同理对于8位字长的数据,一般最后一位不选择为校验位。
停止位配置:
stm32的停止位可以配置停止位长度为0.5、1、1.5、2,就是停止位是时长不同。
起始位侦测采样:
对于输入的数据,要对其进行采用,最后就时钟的上升沿采用的时候刚好对于数据位的正中间,防止因为一些噪声等导致数据位有波形,造成采洋不准。
而为了实现这部分的功能,输入的这部分电路对采样时钟进行了细分,会以波特率的16倍频率进行采样(即在一位的时间里可以对一位的数据进行16次采样)。
而上图就是对起始位进行16次的采样,出现下降沿时,采样采到0,而后面还会继续采样,防止下降沿是因为某些抖动而导致的,并不是代表起始位。当在每3次采样里至少有2个0,就代表这个位确实是起始位。
数据采样:
这部分就是前面提到的,为了保证数据的正确,尽量在数据位的中间对该为数据进行采样。
2.5 波特率发生器
发送器和接收器的波特率由波特率寄存器BRR里的DIV确定
计算公式:波特率 = fPCLK2/1 / (16 * DIV)
为什么还要除以16???上面讲解数据采样的时候有说过,一位数据的采样次数是16次,也就是16个时钟的时间。因此 fPCLK2/1 /DIV取到的波特率还得除以16,才是实际上每秒传输码元的个数。
就当成时钟频率去理解就行,1位数据需要16个时钟,DIV分频后的时钟频率还得再去除以16,做到和每一位的数据相对应。
2.6 数据包
把多个字节(数据帧)给打包一起发送
2.6.1 数据模式
HEX模式/十六进制模式/二进制模式:以原始数据的形式显示
文本模式/字符模式:以原始数据编码后的形式显示
2.6.2 HEX数据包
这种为了避免载荷数据和包头包尾出现数据重复的现象,可以使用载荷数据限制大小、载荷数据限制长度和增加包头包尾的数量来解决。
至于可变包长,更适合载荷数据不会和包头包尾数据重复的情况下使用。
2.6.3 文本数据包
2.6.4 HEX数据包接收
2.6.5 文本数据包接收
3.结构体和相关API
3.1 结构体
typedef struct
{
uint32_t USART_BaudRate; /*!< This member configures the USART communication baud rate.
The baud rate is computed using the following formula:
- IntegerDivider = ((PCLKx) / (16 * (USART_InitStruct->USART_BaudRate)))
- FractionalDivider = ((IntegerDivider - ((u32) IntegerDivider)) * 16) + 0.5 */
uint16_t USART_WordLength; /*!< Specifies the number of data bits transmitted or received in a frame.
This parameter can be a value of @ref USART_Word_Length */
uint16_t USART_StopBits; /*!< Specifies the number of stop bits transmitted.
This parameter can be a value of @ref USART_Stop_Bits */
uint16_t USART_Parity; /*!< Specifies the parity mode.
This parameter can be a value of @ref USART_Parity
@note When parity is enabled, the computed parity is inserted
at the MSB position of the transmitted data (9th bit when
the word length is set to 9 data bits; 8th bit when the
word length is set to 8 data bits). */
uint16_t USART_Mode; /*!< Specifies wether the Receive or Transmit mode is enabled or disabled.
This parameter can be a value of @ref USART_Mode */
uint16_t USART_HardwareFlowControl; /*!< Specifies wether the hardware flow control mode is enabled
or disabled.
This parameter can be a value of @ref USART_Hardware_Flow_Control */
} USART_InitTypeDef;
\1. USART_BaudRate
作用:配置USART通信的波特率(每秒比特传输速率)。
计算公式:
PCLKx
是外设时钟频率。BaudRate
是配置的波特率。USART_BRR
的整数部分由 USARTDIV
的整数值构成,小数部分由其余值通过四舍五入计算得出。取值范围:常用波特率值(如 9600
, 19200
, 38400
, 115200
等)。实际取值需确保 PCLKx
能支持目标波特率。
\2. USART_WordLength
作用:指定每帧传输或接收的数据位数(包括有效数据位和可选的校验位)。
可取值:
USART_WordLength_8b
:8位数据帧(默认)。USART_WordLength_9b
:9位数据帧(通常用于特殊协议或需要扩展数据位时)。注意:
\3. USART_StopBits
作用:设置每帧发送结束时的停止位数,用于标识帧结束。
可取值:
USART_StopBits_1
:1个停止位(默认)。USART_StopBits_0.5
:0.5个停止位。USART_StopBits_2
:2个停止位(用于慢速通信或特定协议)。USART_StopBits_1.5
:1.5个停止位(特定场景)。注意:停止位设置需与接收设备保持一致。
\4. USART_Parity
作用:配置是否启用奇偶校验以及校验模式(奇校验或偶校验)。
可取值:
USART_Parity_No
:无校验(默认)。USART_Parity_Even
:偶校验。USART_Parity_Odd
:奇校验。注意:
\5. USART_Mode
作用:指定USART的工作模式(发送、接收或两者均启用)。
可取值:
USART_Mode_Rx
:仅接收模式。USART_Mode_Tx
:仅发送模式。USART_Mode_Tx_Rx
:同时启用发送和接收模式。典型应用:
\6. USART_HardwareFlowControl
作用:设置硬件流控制模式,用于控制数据流的启停(基于RTS和CTS信号)。
可取值:
USART_HardwareFlowControl_None
:无硬件流控(默认)。USART_HardwareFlowControl_RTS
:仅启用RTS流控(发送设备请求发送时使能)。USART_HardwareFlowControl_CTS
:仅启用CTS流控(接收设备允许发送时使能)。USART_HardwareFlowControl_RTS_CTS
:同时启用RTS和CTS流控。注意:硬件流控适用于需要精确控制数据流的场景,如避免数据溢出或丢失。
3.2 API
3.2.1 初始化相关函数
void USART_DeInit(USART_TypeDef* USARTx)
USART_DeInit(USART1); // 将USART1寄存器重置为默认值
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
USART_InitTypeDef
结构配置USART的波特率、数据位、停止位、校验、工作模式和硬件流控。USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Tx_Rx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init(USART1, &USART_InitStructure);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct)
作用:将USART_InitTypeDef
结构初始化为默认值。
默认值:
9600
。示例:
USART_InitTypeDef USART_InitStructure;
USART_StructInit(&USART_InitStructure); // 初始化为默认值
USART_Init(USART1, &USART_InitStructure);
3.2.2 时钟相关函数
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct)
USART_ClockInitTypeDef USART_ClockInitStructure;
USART_ClockInitStructure.USART_Clock = USART_Clock_Enable;
USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
USART_ClockInitStructure.USART_CPHA = USART_CPHA_1Edge;
USART_ClockInitStructure.USART_LastBit = USART_LastBit_Enable;
USART_ClockInit(USART1, &USART_ClockInitStructure);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct)
USART_ClockInitTypeDef
结构初始化为默认值(同步模式时钟禁用)。USART_ClockInitTypeDef USART_ClockInitStructure;
USART_ClockStructInit(&USART_ClockInitStructure);
USART_ClockInit(USART1, &USART_ClockInitStructure);
3.2.3 控制相关函数
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)
USART_Cmd(USART1, ENABLE); // 启用USART1
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)
作用:配置指定的USART中断。
中断类型:
USART_IT_TXE
:发送数据寄存器空中断。USART_IT_RXNE
:接收数据寄存器非空中断。USART_IT_PE
:奇偶校验错误中断。示例:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 启用接收非空中断
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState)
作用:使能或禁用USART的DMA请求。
参数:
USART_DMAReq_Tx
:发送数据DMA请求。USART_DMAReq_Rx
:接收数据DMA请求。示例:
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); // 启用发送DMA
3.2.4 数据收发函数
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
USART_SendData(USART1, 'A'); // 发送字符 'A'
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送完成
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
uint16_t data = USART_ReceiveData(USART1); // 接收数据
}
3.2.5 状态和中断相关函数
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)
作用:获取指定的USART标志状态。
常见标志:
USART_FLAG_TXE
:发送数据寄存器空标志。USART_FLAG_RXNE
:接收数据寄存器非空标志。USART_FLAG_ORE
:溢出错误标志。示例:
if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
// 接收到数据
}
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG)
USART_ClearFlag(USART1, USART_FLAG_ORE); // 清除溢出错误标志
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
// 接收非空中断发生
}
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT)
USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除接收非空中断挂起标志
4.实验
CH340 USB转串口的原理图
4.1 串口发送
📎9-1 串口发送.zip
User:
Hardware:
其中对于发送数据,仍然要注意的是低位数据开始传送!!!!这里是数据帧字长为8,从低位开始传输,这个其实也不用太在意,因为调用提供的USART_SendData
函数的时候内部已经帮我们做好了。
但是当我们要传输数字的时候,需要注意将其每一位数都给拆开然后再挨个传输出去。比如你要发送21(十进制),那么就要先十位的2,在发送个位的1,就是要把每一位数都给转换成一个数据帧,在这里也就是uint8_t:
/**
* 函 数:串口发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte); //将字节数据写入数据寄存器,写入后USART自动生成时序波形
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}
/**
* 函 数:次方函数(内部使用)
* 返 回 值:返回值等于X的Y次方
*/
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1; //设置结果初值为1
while (Y --) //执行Y次
{
Result *= X; //将X累乘到结果
}
return Result;
}
/**
* 函 数:串口发送数字
* 参 数:Number 要发送的数字,范围:0~4294967295
* 参 数:Length 要发送数字的长度,范围:0~10
* 返 回 值:无
*/
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i ++) //根据数字长度遍历数字的每一位
{
Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); //依次调用Serial_SendByte发送每位数字
}
}
假设咱们调用了Serial_SendNumber(123,3);
123是要传输的十进制的数(uinit32_t),得将每一位都给拆开成一个数据帧,也就是uint8_t a = 1、uint8_t b= 2、uint8_t c= 3,将a、b、c挨个传输。 逐位拆解数字并以 ASCII 码的形式发送。
这时候就可以采用取余的方式获取到低位。参2就是传这个数字的位数,这里是3。
通过调用Serial_Pow
函数去对数字的不相关的位数进行去除,比如第一次for时,Serial_Pow
传的参2是2,最后返回是100,123/100会得到1(注意是int的形式!!),1%10就可以知道百位数就是1了。其余的位数也是同理。
调用 Serial_SendNumber(123, 3)
的具体过程:
第1次循环(i=0
):
Serial_Pow(10, 3-0-1) = Serial_Pow(10, 2) = 100
。123 / 100 = 1
,1 % 10 = 1
,对应字符 '1'
。'1'
。第2次循环(i=1
):
Serial_Pow(10, 3-1-1) = Serial_Pow(10, 1) = 10
。123 / 10 = 12
,12 % 10 = 2
,对应字符 '2'
。'2'
。第3次循环(i=2
):
Serial_Pow(10, 3-2-1) = Serial_Pow(10, 0) = 1
。123 / 1 = 123
,123 % 10 = 3
,对应字符 '3'
。'3'
。4.2 串口发送 + 接收
📎9-2 串口发送+接收.zip
User:
Hardware:
4.3 HEX数据包收发
📎9-3 串口收发HEX数据包.zip
User:
Hardware:
需要注意的是:
32中中断是可以嵌套的。如果在OLED显示数据的时候,中途又发生了中断进行接收数据,对Serial_RxPacket数组进行写入,就可能会导致输出的前一半是上次的数据,后一半是新来的数据。这个需要注意。
但是这种HEX数据包多是用于传感器的各个独立数据、陀螺仪的XYZ数据等,它们相邻的数据包都是具有连续性的,即使相邻数据混在一起了也没啥关系。如果想避免这种问题,可以在中断处理函数中添加一个标志位,在上一个数据发送完后再标志为可以接收。
4.4 文本数据包收发
📎9-4 串口收发文本数据包.zip
User:
Hardware:
当在发送区输入指定的数据包点击发送后,会调用到中断函数USART1_IRQHandler
,根据数据包进行解析,将载荷数据放置到全局数组Serial_RxPacket
中。然后在main函数中通过strcmp
函数去对该数组和对应的指令进行比较,执行相应的操作:
stm32通过Serial_SendString
函数将是LED否开启成功的状态通过串口传输到串口助手,会在接收区显示。
5.FlyMcu串口下载&STLINK Utility
5.1 FlyMcu
通过串口给stm32下载程序。
📎FlyMcu程序烧录软件.zip
需要先将设置成:系统存储器模式。
红色是跳线帽地接法,之后记得按一次复位键。
需要注意的是这个烧录软件只支持USART1下的引脚。
打开keil中的工程,编译生成一个.hex文件
之后重新编译一下:
就可以看到串口下载所需要的文件
之后打开烧录助手,进行相关配置,选择.hex文件,开始编程进行烧录即可。
之后将32的跳线帽重新接回主闪存启动模式,点击复位键,就可以看到程序的运行现象。
5.2 STLINK Utility
📎STLINK Utility.zip
作者:憧憬一下