BA4RDK

欢迎来到我的博客

博客园 首页 新随笔 联系 订阅 管理

USART 全称通用同步异步收发器,日常我们最常使用的是异步通信模式,也就是常说的 UART。异步通信最核心的特点,
就是通信双方不需要共用同一根时钟信号线,只依靠 TX 发送引脚、RX 接收引脚两根数据线,再搭配 GND 共地,就能
够完成数据交互,这也是串口硬件接线简单、通用性极强的根本原因。

在嵌入式开发里,串口绝对是我们和外设沟通最基础、出场率最高的通信接口。不管是调试打印日志,还是和传感器、蓝牙模块传输数据,STM32 的 USART 异步串口永远是绕不开的一环。

很多新手最开始只会固定长度收发,等到碰到每行字节长度不固定、报文随机长短的数据时,就很容易出现丢数据、串包、接收错乱的问题。

今天我们就来实战不定长字节收发

USART内部的秘密!

 STM32 内部 USART 外设,`自带独立的发送移位寄存器、接收移位寄存器`。发送的时候,CPU 把字节写入数据寄存器,硬件就会自动把数据搬运到移位寄存器,
 逐位向外输出电平,整个移位过程由硬件自主完成,CPU 不需要逐位操控引脚;接收流程正好相反,`RX 引脚不断检测电平`,一旦捕捉到起始位的下降沿,硬件
 便按照波特率节拍采样电平,`把串行的高低电平拼装成完整的一字节数据,存入接收寄存器`,之后触发接收中断,通知 CPU 取走数据。

 这里也是不定长数据收发难点的源头:`USART 硬件本身只认识单字节`。硬件只会一个字节一个字节地接收,它不会自动识别一整条报文什么时候结束、
 整条数据包一共有多少字节。硬件没有 “数据包” 的概念,只有源源不断的字节流。

一定要学会变通
想要搞定不定字节的收发,本质就是我们依靠中断、空闲帧检测、DMA 这些外设功能,在连续不断的字节流里,人为分辨出一帧完整数据包的边界。
弄懂 USART 硬件底层的收发逻辑、帧结构、硬件寄存器的工作逻辑之后,我们再去对比空闲中断、DMA 接收、定时器超时这几种主流不定长接收方案,
就很容易理解每种方式的优劣和适用场景。

常见的方案

常见方案 缺点 优势
普通接收中断 + 环形缓冲区缓存 CPU 会被频繁打断,波特率越高,中断开销越严重,极易出现来不及进中断、字节丢失 易于理解
USART 空闲帧中断 两条报文间隔太短、字节之间没有空闲电平,就会发生两帧数据粘连; 硬件自带帧结束检测,不需要软件不停检索结束符
DMA 搬运 + 空闲中断 可忽略 高性能首选方案

我们的选择
当然选择DMA 搬运 + 空闲中断啦!不仅在HAL代码简洁 并且 在CubeMX中很简单就可配置

立刻进入实战!!!

CubeMX的配置

开启串口DMA
image

开启串口中断
image

HAL库代码

重定fputc函数 方便打印数据

int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
    return ch;
}

必要头文件

#include <stdio.h>
#include <string.h>

初始化开启DMA中断

 #define RX_BUFFER_SIZE  1024 //缓冲区大小
 uint8_t rx_buffer[RX_BUFFER_SIZE];//接收缓冲区
 uint8_t rx_done = 0;     // 接收完成标志
 uint16_t rx_len = 0;     // 本次接收到的数据长度
 //DMA中断
 HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);

中断回调函数

// 空闲中断回调
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == USART1)
    {
       rx_len = Size;
       rx_done = 1;
        /*
        进入中断时,这里也可以运行其他的代码 看个人需求
        */
      //重启DMA中断
	  HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
    }
}

while中运行的代码

//测试收发(PC13上LED的开关)
if(rx_done) //判断是否有接收到数据
{
			rx_done = 0; //清除标志位
			printf("Rx Len: %d\r\n", rx_len);   // 打印实际长度
            
                         //比较函数
			if(memcmp(rx_buffer, "ON", 2)==0)
			{
				HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
				printf("OLED ON");
			}
			else if(memcmp(rx_buffer, "OFF", 3)==0)
			{
				HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
				printf("OLED OFF");
			}
			else
			{
					printf("NO AT");
			}
}

啦啦啦啦啦 到这里就可以随心所欲的收发数据啦 嘻嘻嘻!!!
有缘再会,这里是小祁,拜拜!

posted on 2026-08-08 17:44  BA4RDK  阅读(1)  评论(0)    收藏  举报