当前位置: 首页 > news >正文

USART 串口通信全解析:原理、结构与代码实战

文章目录

    • USART
      • USART简介
      • USART框图
      • USART基本结构
      • 数据帧
      • 起始位侦测
      • 数据采样
      • 波特率发生器
      • 串口发送数据 主要代码
      • 串口接收数据与发送数据主要代码

USART

USART简介

一、USART 的全称与基本定义

  1. 英文全称
    • USARTUniversal Synchronous Asynchronous Receiver Transmitter,即 通用同步异步收发器
    • 核心功能:实现串行通信,支持 同步通信(需时钟信号)和 异步通信(无需时钟信号,依赖波特率同步)。
  2. 与 UART 的本质区别
    • UARTUniversal Asynchronous Receiver Transmitter,即 通用异步收发器仅支持异步通信
    • 关键差异
      • USART 比 UART 多一个 同步时钟输出功能(对应引脚 CLK),可在通信中提供时钟信号。
      • 实际应用中,由于串口通信极少使用同步模式,USART 与 UART 在异步模式下可视为等价,硬件驱动和配置流程基本一致。

二、USART 的核心功能特性

  1. 通信模式灵活切换
    • 异步模式(常用):
      • 无需时钟信号,通信双方通过 波特率 约定数据传输速率。
      • 数据帧包含 起始位、数据位、校验位(可选)、停止位,通过 TX/RX 引脚完成收发。
    • 同步模式(少用):
      • 通过 CLK 引脚输出时钟信号,用于同步发送方和接收方的时序。
      • 时钟频率与波特率一致,可兼容 SPI 等需要时钟的协议,但 仅支持时钟输出,不支持输入,因此 无法实现两个 USART 设备间的同步通信
  2. 硬件自动化处理
    • 数据帧生成与解析
      • 发送时,自动将数据寄存器(TDR)中的字节转换为符合协议的波形(如添加起始位、停止位),通过 TX 引脚输出。
      • 接收时,自动从 RX 引脚读取波形,按协议解析为字节数据存入接收寄存器(RDR)。
    • 双缓存机制
      • 发送端:TDR(写缓冲)与发送移位寄存器配合,允许连续写入数据,提升传输效率。
      • 接收端:RDR(读缓冲)与接收移位寄存器配合,避免数据丢失。
  3. 可配置参数
    • 波特率:最高支持 4.5 Mbps,通过内部波特率发生器(分频器)配置。
    • 数据位长度:8 位(无校验)或 9 位(含 1 位奇偶校验位)。
    • 停止位长度:0.5/1/1.5/2 位,常用 1 位。
    • 校验方式:无校验、奇校验、偶校验。

在这里插入图片描述

USART框图

  1. 数据传输路径
    • 发送路径:CPU 或 DMA 将数据写入发送数据寄存器(TDR) ,TDR 的数据转移至发送移位寄存器,然后在发送器控制的作用下(这里是向右移位的与串口输出的低位先行是一致的),通过 TX 引脚逐位发送出去。在此过程中,发送数据寄存器(TDR)起到缓冲作用,允许 CPU 或 DMA 在发送移位寄存器工作时写入下一个数据。当数据转移至移位寄存器中会置标志位 TXE 发送寄存器空,就可以将数据写入寄存器。
    • 接收路径:RX 引脚接收到的数据进入接收移位寄存器,将数据先放在最高位然后向右移位(也是符合串口通信低位先行的原则),当接收移位寄存器接收到完整的数据字节后,数据被转移到接收数据寄存器(RDR),在接收数据过程中,也是会置标志位,接收寄存器非空RXNE,当检测到该标志位后, CPU 或 DMA 可以进行读取。
  2. 寄存器功能
    • 数据寄存器(DR):实际包含发送数据寄存器(TDR)和接收数据寄存器(RDR),对 DR 进行写操作时数据存入 TDR,进行读操作时从 RDR 读取数据,也就是两个寄存器占据一块空间。
    • 状态寄存器(SR) :包含多个标志位,如 TXE(发送寄存器空)、TC(发送完成)、RXNE(接收寄存器非空)、IDLE(总线空闲) 、ORE(溢出错误)、NE(噪声错误)、FE(帧错误)、PE(奇偶校验错误)等,用于反映 USART 的工作状态,方便软件判断何时进行数据发送、接收以及检测通信错误 。
    • 波特率寄存器(BRR) :用于设置波特率,其值决定了发送器和接收器的波特率时钟。计算公式为 USARTDIV = DIV_Mantissa + (DIV_Fraction / 16) ,通过设置 DIV_Mantissa(整数部分)和 DIV_Fraction(小数部分)来得到所需的波特率分频值 。
    • 控制寄存器(CR1 - CR3) :CR1 用于控制 USART 的基本功能,如使能发送(TE)、接收(RE) ,奇偶校验(PCE、PS、PE),唤醒功能(WAKE)等;CR2 用于控制停止位(STOP [1:0])、时钟使能(CKEN)等;CR3 用于控制硬件流控(CTSE、RTSE)、智能卡模式(SCEN)等功能 。
  3. 控制单元
    • 发送器控制:根据控制寄存器(如 CR1 中的 TE 位)的配置来控制发送移位寄存器的工作,决定何时将 TDR 的数据转移到发送移位寄存器并进行发送,同时参与波特率控制相关操作 。
    • 接收器控制:负责管理接收移位寄存器的工作,包括起始位侦测、数据采样等操作。通过对 RX 引脚的信号以波特率 16 倍频率采样来侦测起始位,然后逐位接收数据并转移到 RDR 。同时,还具备噪声过滤等功能,通过三次采样投票机制(2:1 规则)判断有效数据,若采样不一致则置 NE 标志位 (说明有噪声干扰)。
    • 硬件数据流控:如果发送数据太快,接收设备来不及处理就会出现数据丢失或覆盖的现象,可以通过 nRTS(请求发送 Request to send)和 nCTS(清除发送 clear to send)引脚实现硬件流控。当接收方接收缓冲区快满时,通过 nRTS 引脚通知发送方暂停发送,发送方根据 nCTS 引脚状态判断是否可以继续发送,以此避免数据丢失 。接收方的nRTS 与 发送方的 nCTS相连接,也就是这两个引脚相互连接。置高电平说明会产生信号,nRTS就是停止发送。
    • 中断控制:根据状态寄存器(SR)中的标志位状态来触发中断,如 TXE、RXNE、NE 等标志位置 1 时,可触发相应中断,使 CPU 能够及时响应并处理 USART 相关事件,如读取接收数据、写入新的发送数据或处理通信错误等 。
  4. 时钟相关
    • 时钟来源:USART1 时钟来自 APB2 总线时钟(PCLK2),USART2 和 USART3 时钟来自 APB1 总线时钟(PCLK1) 。
    • 波特率时钟生成:时钟经过波特率发生器(由 BRR 寄存器配置)进行分频,得到发送器和接收器的波特率时钟。发送器波特率控制和接收器波特率控制分别根据 BRR 寄存器的值生成相应的时钟信号,确保发送和接收数据的速率符合设定的波特率 。
    • 同步时钟(SCLK) :在同步模式下,SCLK 引脚输出同步时钟信号,其频率与波特率一致(发送寄存器每移位一次,同步时钟就跳变一个周期)。同步时钟信号由 SCLK 控制模块根据相关配置(如 CR2 中的时钟控制位)生成,用于为同步通信提供时钟基准,如在与 SPI 等协议兼容通信时发挥作用 和 做自适应波特率。
  5. 其他功能模块
    • IrDA 编码解码模块:用于红外数据通信(IrDA 模式),对发送数据进行编码后通过 IrDA_OUT 引脚发送,对 IrDA_IN 引脚接收到的信号进行解码后送入接收路径 。
    • 唤醒单元:配合多设备通信场景,可根据配置的 USART 地址,在接收到特定地址的信号时唤醒 USART 设备,使其进入工作状态,实现类似 I2C 的多主机通信机制 。

在这里插入图片描述

USART基本结构

USART 基本结构

  • 波特率发生器:位于最左边,用于产生约定的通信数据。
  • 时钟单元:由 PCLK2 或经过分频后产生,为发送和接收控制器提供时钟,控制发送和接收移位。
  • 发送部分:发送数据寄存器和发送移位寄存器配合,将数据一位一位右移(低位先行),通过 GPIO 口复用输出到 TX 引脚,产生规定波形,数据转移时置 TXE 标志位。
  • 接收部分:RX 引脚波形通过 GPIO 口输入,在接收控制器控制下,数据右移(低位先行)进入接收移位寄存器,移完一帧后转运到接收数据寄存器,转移时置 RXNE 标志位,该标志位可用于判断是否收到数据及申请中断。
  • 寄存器:右边实际有 4 个寄存器,软件层面只有 Dr 寄存器可读写,写入 Dr 走发送路径,读取 Dr 走接收路径。
  • 开关控制:配置完成后用 CMD 开启外设。

在这里插入图片描述

数据帧

一、字长与校验位选择

  • 9 位字长
    • 结构为 “1 位起始位 + 8 位数据位 + 1 位校验位 + 停止位(自定义长度)”。
    • 校验位可配置为奇校验或偶校验,用于检测数据传输中的错误。
  • 8 位字长
    • 结构为 “1 位起始位 + 8 位数据位 + 停止位(自定义长度)”。
    • 无校验位,每帧有效载荷为完整的 8 位(1 字节),适用于一般数据传输场景,可提升传输效率,避免因校验位导致的额外开销。

二、同步时钟作用

  • 功能:在同步模式下,USART 通过 CLK 引脚输出同步时钟信号,该信号在每个数据位的中间位置产生上升沿。
  • 意义:接收端可利用此时钟信号精准采样数据位,确保数据接收的准确性,尤其适用于与外部设备(如模拟 SPI 从设备)的同步通信。
  • 配置:时钟极性(CPIO,空闲时电平状态)和相位(CPHA,采样时刻)可通过寄存器配置,以适配不同协议的时序要求。

三、特殊数据帧

  • 空闲帧(Idle Frame)
    • 定义:整个数据帧的波形从头到尾均为高电平(逻辑 1)。
    • 用途:用于局域网(LIN)等协议中,表示总线空闲状态,通知从设备准备接收后续数据。
    • 串口应用:在普通串口通信中极少使用。
  • 断开帧(Break Frame)
    • 定义:整个数据帧的波形从头到尾均为低电平(逻辑 0)。
    • 用途:用于局域网协议中,通常作为一种特殊的控制信号,打断当前通信流程或唤醒从设备。
    • 串口应用:在普通串口通信中极少使用。

四、停止位波形变化

  • 配置选项:STM32 的 USART 可配置停止位长度为 0.5 位、1 位、1.5 位或 2 位。
  • 波形时长
    • 停止位的波形时长与波特率相关,例如波特率为 9600 bps 时,1 位停止位的时长为 96001≈104.17μs,0.5 位则约为 52.08μs
  • 常用选择:实际应用中,1 位停止位最为常用,因其兼容性好,适用于大多数串口设备(如 PC 端串口助手、传感器模块等)。

五、数据帧整体作用

  • 数据帧是 USART 通信的基本单元,通过定义起始位、数据位、校验位(可选)、停止位的组合,确保收发双方在异步通信中实现时序同步,避免数据错位或丢失。
  • 不同的帧格式(如 8 位无校验、9 位带校验)和停止位配置,为开发者提供了灵活的选择,可根据具体应用场景(如工业控制的高可靠性需求、普通调试信息传输的高效性需求)优化通信参数。

起始位侦测

  • 频采样机制:接收端以波特率 16 倍的频率对 RX 引脚进行采样,这种高频采样有助于精确捕捉信号变化,为起始位检测提供基础。
  • 起始位下降沿侦测:当检测到 RX 引脚出现下降沿(从高电平跳变到低电平)时,初步判断可能是起始位的开始。
  • 多次采样确认:在侦测到下降沿后,通过连续多批次采样进一步判断。若后续采样持续检测到低电平,则确认该下降沿为真正的起始位;若采样结果不一致,则借助噪声处理机制判断。
  • 噪声处理:采用三次采样投票机制(2:1 规则),即连续三次采样中,若两次结果相同则判定为有效电平。若采样过程中出现噪声导致不一致,会置位噪声标志位(NE),但不影响正常数据接收,仅作为提示。

在这里插入图片描述

数据采样

采样位置对齐:起始位侦测通过后,接收状态机发生改变,调整后续采样的时间点,使采样位置精确对齐数据位的中心,确保后续数据位采样的准确性。

在这里插入图片描述

波特率发生器

一、定义与核心功能

波特率发生器是 USART(通用同步异步收发器)的关键模块,用于生成数据发送与接收的时钟信号,决定数据位的传输速率(波特率),确保收发双方在串行通信中时序同步,避免数据错位或乱码。

二、输入时钟来源

  • USART1:挂载于 APB2 总线,输入时钟为 PCLK2(通常为 72 MHz)。
  • USART2/3:挂载于 APB1 总线,输入时钟为 PCLK1(通常为 36 MHz)。

三、波特率计算原理
在这里插入图片描述

四、寄存器配置

  • BRR 寄存器:存储分频系数 DIV,分为整数部分(BRR[15:4])和小数部分(BRR[3:0])。通过配置该寄存器,可灵活调整波特率。

五、对通信的影响

  • 若波特率设置错误,收发双方时序不匹配,会导致数据接收错误(如乱码)。
  • 在同步模式下,波特率发生器还控制同步时钟(SCLK)的频率,使其与波特率一致,确保数据与时钟信号同步输出。

串口发送数据 主要代码

// Serial.c USART模块主要代码#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>
void Serial_Init(void)
{//1.开启时钟 //开启GPIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//初始化gpioinit参数中的结构体GPIO_InitTypeDef GPIO_InitStructure;//将gpio口设置为推挽输出,因为非工作模式默认为高电平,所以配置为上拉GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//初始化发送引脚 9号引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);USART_InitTypeDef USART_InitStruct;//要配置的波特率USART_InitStruct.USART_BaudRate = 9600;//有没有启动硬件数据流控USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//模式输出模式USART_InitStruct.USART_Mode = USART_Mode_Tx;//奇偶校验位USART_InitStruct.USART_Parity = USART_Parity_No;//停止位USART_InitStruct.USART_StopBits = USART_StopBits_1;USART_InitStruct.USART_WordLength = USART_WordLength_8b;USART_Init(USART1, &USART_InitStruct);//开始USART1的总开关USART_Cmd(USART1, ENABLE);
}//封装发送数据的函数
void Serial_SendBtye(uint8_t byte)
{USART_SendData(USART1, byte);//等待数据发送完成,读取标志位while( USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//标志位置一之后不需要手动清零,进行写操作会自动清零}
void Serial_SendArray(uint8_t *Array, uint8_t length)
{uint8_t i;for(i = 0; i < length; i++){Serial_SendBtye(Array[i]);}
}void Serial_SendString(char *string)
{for(uint8_t i = 0; string[i] != '\0'; i++){Serial_SendBtye(string[i]);}
}
uint32_t Pow(uint8_t x, uint8_t y)
{uint32_t result = 1;while(y--){result *= x;}return result;
}
void Serial_SendNum(uint32_t Number, uint8_t length)
{for(uint8_t i = 0; i < length; i++){Serial_SendBtye(Number / Pow(10, length - i - 1) % 10 + '0');}
}
/*** 函    数:使用printf需要重定向的底层函数* 参    数:保持原始格式即可,无需变动* 返 回 值:保持原始格式即可,无需变动*/
int fputc(int ch, FILE *f)
{Serial_SendBtye(ch);			//将printf的底层重定向到自己的发送字节函数return ch;
}
/*** 函    数:自己封装的prinf函数* 参    数:format 格式化字符串* 参    数:... 可变的参数列表* 返 回 值:无*/
void Serial_Printf(char *format, ...)
{char String[100];				//定义字符数组va_list arg;					//定义可变参数列表数据类型的变量argva_start(arg, format);			//从format开始,接收参数列表到arg变量vsprintf(String, format, arg);	//使用vsprintf打印格式化字符串和参数列表到字符数组中va_end(arg);					//结束变量argSerial_SendString(String);		//串口发送字符数组(字符串)
}

串口接收数据与发送数据主要代码

//Serial.c USART模块主要代码#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>int Serial_Data;
int Serial_flag;
void Serial_Init(void)
{//1.开启时钟 //开启GPIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//初始化gpioinit参数中的结构体GPIO_InitTypeDef GPIO_InitStructure;//将gpio口设置为推挽输出,因为非工作模式默认为高电平,所以配置为上拉GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//初始化发送引脚 9号引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);//接收数据初始化 10号引脚 GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;//初始化发送引脚 9号引脚GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;USART_InitTypeDef USART_InitStruct;//要配置的波特率USART_InitStruct.USART_BaudRate = 9600;//有没有启动硬件数据流控USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//模式输出模式USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//奇偶校验位USART_InitStruct.USART_Parity = USART_Parity_No;//停止位USART_InitStruct.USART_StopBits = USART_StopBits_1;USART_InitStruct.USART_WordLength = USART_WordLength_8b;USART_Init(USART1, &USART_InitStruct);//如果想要使用中断,就开启中断,然后配置NVICUSART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//先设置中断分组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStruct);//开始USART1的总开关USART_Cmd(USART1, ENABLE);
}//封装发送数据的函数
void Serial_SendBtye(uint8_t byte)
{USART_SendData(USART1, byte);//等待数据发送完成,读取标志位while( USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);//标志位置一之后不需要手动清零,进行写操作会自动清零}
void Serial_SendArray(uint8_t *Array, uint8_t length)
{uint8_t i;for(i = 0; i < length; i++){Serial_SendBtye(Array[i]);}
}void Serial_SendString(char *string)
{for(uint8_t i = 0; string[i] != '\0'; i++){Serial_SendBtye(string[i]);}
}
uint32_t Pow(uint8_t x, uint8_t y)
{uint32_t result = 1;while(y--){result *= x;}return result;
}
void Serial_SendNum(uint32_t Number, uint8_t length)
{for(uint8_t i = 0; i < length; i++){Serial_SendBtye(Number / Pow(10, length - i - 1) % 10 + '0');}
}
/*** 函    数:使用printf需要重定向的底层函数* 参    数:保持原始格式即可,无需变动* 返 回 值:保持原始格式即可,无需变动*/
int fputc(int ch, FILE *f)
{Serial_SendBtye(ch);			//将printf的底层重定向到自己的发送字节函数return ch;
}
/*** 函    数:自己封装的prinf函数* 参    数:format 格式化字符串* 参    数:... 可变的参数列表* 返 回 值:无*/
void Serial_Printf(char *format, ...)
{char String[100];				//定义字符数组va_list arg;					//定义可变参数列表数据类型的变量argva_start(arg, format);			//从format开始,接收参数列表到arg变量vsprintf(String, format, arg);	//使用vsprintf打印格式化字符串和参数列表到字符数组中va_end(arg);					//结束变量argSerial_SendString(String);		//串口发送字符数组(字符串)
}
int Serial_GetFlag(void)
{//实现读取标志位后自动清除的功能if(Serial_flag == 1){Serial_flag = 0;return 1;}return 0;
}
int Serial_GetData(void)
{return Serial_Data;
}void USART1_IRQHandler(void)
{if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET){Serial_Data = USART_ReceiveData(USART1);Serial_flag = 1;//不用手动清除标志位,读取数据会自动清除}
}

http://www.xdnf.cn/news/756325.html

相关文章:

  • YOLOv11改进 | Conv/卷积篇 | 全维度动态卷积ODConv与二次创新C3k2助力YOLOv11有效涨点
  • GIS数据类型综合解析
  • 【笔记】在 MSYS2(MINGW64)中安装 Python 工具链的记录
  • 【计网】第六章(网络层)习题测试集
  • 【科研绘图系列】R语言绘制论文组合图形(multiple plots)
  • 某数字藏品qm加密算法技术解析:多层混合加密体系的深度剖析
  • RV1126-OPENCV 图像叠加
  • 【PhysUnits】15.8 引入P1后的减法运算(sub.rs)
  • 图文详解Java集合面试题
  • TDengine 基于 TDgpt 的 AI 应用实战
  • 【论文阅读 | PR 2024 |ICAFusion:迭代交叉注意力引导的多光谱目标检测特征融合】
  • vue3中的ref和reactive
  • pc端小卡片功能-原生JavaScript金融信息与节日日历
  • 2024 CKA模拟系统制作 | Step-By-Step | 16、题目搭建-sidecar 代理容器日志
  • 工作流引擎-06-流程引擎(Process Engine)对比 Flowable、Activiti 与 Camunda 全维度对比分析
  • 一位汽车行业从业人员对Simulink热度变化的观察与讨论 (2024)
  • 中国风展示工作总结商务通用PPT模版
  • M-OFDM模糊函数原理及仿真
  • 过滤攻击-聚合数据
  • [Windows]在Win上安装bash和zsh - 一个脚本搞定
  • Maven(黑马)
  • YOLOv7 辅助检测头与重参数化解析2025.6.1
  • 鸿蒙HarmonyOS —(cordova)研发方案详解
  • 数论——质数和合数及求质数
  • 工程的焊接技术
  • 哈尔滨工业大学提出ADSUNet—红外暗弱小目标邻帧检测新框架
  • Altium Disigner(16.1)学习-原理图绘制以及必要操作
  • 批量导出CAD属性块信息生成到excel——CAD C#二次开发(插件实现)
  • Leetcode 3568. Minimum Moves to Clean the Classroom
  • DAY 35 超大力王爱学Python