文章目录
-
- 一、前言
-
- [1.1 技术背景](#1.1 技术背景)
- [1.2 本文目标](#1.2 本文目标)
- [1.3 技术栈](#1.3 技术栈)
- 二、环境准备
-
- [2.1 硬件连接](#2.1 硬件连接)
- [2.2 STM32CubeMX配置](#2.2 STM32CubeMX配置)
-
- [2.2.1 创建工程](#2.2.1 创建工程)
- [2.2.2 配置时钟](#2.2.2 配置时钟)
- [2.2.3 配置USART1](#2.2.3 配置USART1)
- [2.2.4 配置NVIC中断](#2.2.4 配置NVIC中断)
- [2.2.5 生成代码](#2.2.5 生成代码)
- 三、核心实现
-
- [3.1 项目文件结构](#3.1 项目文件结构)
- [3.2 串口处理模块实现](#3.2 串口处理模块实现)
- [3.3 修改中断处理文件](#3.3 修改中断处理文件)
- [3.4 修改主函数](#3.4 修改主函数)
- 四、系统架构与流程
-
- [4.1 DMA传输流程图](#4.1 DMA传输流程图)
- [4.2 空闲中断工作原理](#4.2 空闲中断工作原理)
- 五、测试验证
-
- [5.1 编译下载](#5.1 编译下载)
- [5.2 串口调试](#5.2 串口调试)
- [5.3 预期输出](#5.3 预期输出)
- 六、故障排查与问题解决
-
- [6.1 常见问题](#6.1 常见问题)
- [6.2 性能优化建议](#6.2 性能优化建议)
- 七、总结
-
- [7.1 核心知识点](#7.1 核心知识点)
- [7.2 扩展方向](#7.2 扩展方向)
- [7.3 学习资源](#7.3 学习资源)
一、前言
1.1 技术背景
STM32系列微控制器是意法半导体(STMicroelectronics)推出的基于ARM Cortex-M内核的32位微控制器,广泛应用于工业控制、消费电子、汽车电子等领域。在嵌入式系统开发中,串口通信(UART/USART)是最基础也是最常用的通信方式之一。
传统的串口通信采用轮询或中断方式处理数据传输,但在大数据量、高频率通信场景下,CPU占用率过高成为性能瓶颈。**DMA(Direct Memory Access,直接存储器访问)**技术的引入,使得外设可以直接与内存进行数据交换,无需CPU干预,从而大幅提升系统效率。
1.2 本文目标
通过本文,你将学习到:
- STM32 HAL库串口通信的完整配置流程
- DMA传输的工作原理和配置方法
- 串口DMA发送与接收的实现
- 空闲中断(IDLE)配合DMA实现不定长数据接收
- 性能优化技巧和常见问题解决方案
完成本文学习后,你将能够构建一个高效、稳定的串口DMA通信系统,适用于工业通信、数据采集、物联网网关等应用场景。
1.3 技术栈
硬件平台:
- 开发板:STM32F407VGT6(或STM32F103C8T6等)
- 调试器:ST-Link V2
- USB转串口模块(CH340/CP2102等)
软件环境:
- IDE:STM32CubeIDE 1.13.0+
- 配置工具:STM32CubeMX 6.9.0+
- 固件库:STM32CubeF4 HAL库(或对应系列HAL库)
- 串口调试助手:SSCOM、XCOM等
二、环境准备
2.1 硬件连接
STM32与USB转串口模块连接:
| STM32引脚 | USB转串口模块 | 说明 |
|---|---|---|
| PA9 (USART1_TX) | RXD | 发送数据 |
| PA10 (USART1_RX) | TXD | 接收数据 |
| GND | GND | 共地 |
💡 提示: 注意TX接RX,RX接TX,不要接反。
2.2 STM32CubeMX配置
2.2.1 创建工程
- 打开STM32CubeMX,点击 File → New Project
- 在MCU/MPU Selector中搜索并选择你的芯片型号(如STM32F407VG)
- 点击 Start Project
2.2.2 配置时钟
- 进入 Clock Configuration 标签页
- 配置系统时钟为168MHz(F4系列)或72MHz(F1系列)
- 确保APB2总线时钟(USART1所在总线)配置正确
2.2.3 配置USART1
- 在 Pinout & Configuration 标签页,找到PA9和PA10
- 将PA9设置为 USART1_TX ,PA10设置为 USART1_RX
- 在左侧 Connectivity 菜单中选择 USART1
- 配置参数:
- Mode:Asynchronous(异步模式)
- Baud Rate:115200 Bits/s
- Word Length:8 Bits
- Stop Bits:1
- Parity:None
- DMA Settings :
- 点击 Add 添加USART1_TX和USART1_RX的DMA请求
- Direction:Memory to Peripheral(发送)/ Peripheral to Memory(接收)
- Mode:Normal(单次传输)或Circular(循环传输)
2.2.4 配置NVIC中断
- 进入 NVIC 配置
- 启用以下中断:
- USART1 global interrupt:启用
- DMA2 stream7 global interrupt(USART1_TX DMA):启用
- DMA2 stream2 global interrupt(USART1_RX DMA):启用
2.2.5 生成代码
- 点击 Project → Generate Code
- 设置工程名称和路径
- 选择IDE为 STM32CubeIDE
- 点击 Generate,等待代码生成完成
三、核心实现
3.1 项目文件结构
本教程将创建以下代码文件:
Core/
├── Inc/
│ ├── main.h
│ ├── usart.h # 串口初始化头文件(CubeMX生成)
│ ├── dma.h # DMA初始化头文件(CubeMX生成)
│ └── uart_handler.h # 串口处理模块头文件
└── Src/
├── main.c
├── usart.c # 串口初始化实现(CubeMX生成)
├── dma.c # DMA初始化实现(CubeMX生成)
├── uart_handler.c # 串口处理模块实现
└── stm32f4xx_it.c # 中断处理(CubeMX生成)
3.2 串口处理模块实现
📄 创建文件:
Core/Inc/uart_handler.h
c
/**
* @file uart_handler.h
* @brief 串口DMA通信处理模块头文件
*
* 功能:
* - 串口DMA发送与接收
* - 空闲中断实现不定长数据接收
* - 环形缓冲区管理
*
* @author STM32 Tutorial
* @version 1.0
*/
#ifndef __UART_HANDLER_H
#define __UART_HANDLER_H
#include "main.h"
#include "usart.h"
#include <string.h>
#include <stdbool.h>
/* ===================== 宏定义 ===================== */
#define UART_RX_BUFFER_SIZE 256 // 接收缓冲区大小
#define UART_TX_BUFFER_SIZE 256 // 发送缓冲区大小
#define UART_MAX_RX_LEN 128 // 单次最大接收长度
/* ===================== 数据结构 ===================== */
/**
* @brief 串口接收状态结构体
*/
typedef struct {
uint8_t rx_buffer[UART_RX_BUFFER_SIZE]; // DMA接收缓冲区
uint8_t tx_buffer[UART_TX_BUFFER_SIZE]; // DMA发送缓冲区
volatile uint16_t rx_len; // 当前接收数据长度
volatile uint16_t rx_head; // 缓冲区读指针
volatile uint16_t rx_tail; // 缓冲区写指针
volatile bool rx_complete; // 接收完成标志
volatile bool tx_busy; // 发送忙标志
} UART_HandleTypeDef_Custom;
/* ===================== 函数声明 ===================== */
/**
* @brief 初始化串口DMA通信
* @param huart 串口句柄指针
* @retval None
*/
void UART_DMA_Init(UART_HandleTypeDef *huart);
/**
* @brief 启动DMA接收(空闲中断模式)
* @param huart 串口句柄指针
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef UART_DMA_StartReceive(UART_HandleTypeDef *huart);
/**
* @brief DMA发送数据
* @param huart 串口句柄指针
* @param data 待发送数据指针
* @param len 数据长度
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef UART_DMA_Transmit(UART_HandleTypeDef *huart,
uint8_t *data, uint16_t len);
/**
* @brief 处理接收到的数据(在空闲中断回调中调用)
* @param huart 串口句柄指针
* @retval None
*/
void UART_DMA_ProcessRxData(UART_HandleTypeDef *huart);
/**
* @brief 获取接收缓冲区中的数据
* @param huart 串口句柄指针
* @param buffer 输出缓冲区
* @param max_len 最大读取长度
* @retval uint16_t 实际读取的字节数
*/
uint16_t UART_DMA_ReadRxData(UART_HandleTypeDef *huart,
uint8_t *buffer, uint16_t max_len);
/**
* @brief 串口空闲中断回调函数
* @param huart 串口句柄指针
* @retval None
*/
void UART_DMA_IdleCallback(UART_HandleTypeDef *huart);
/**
* @brief 串口发送完成回调函数
* @param huart 串口句柄指针
* @retval None
*/
void UART_DMA_TxCompleteCallback(UART_HandleTypeDef *huart);
#endif /* __UART_HANDLER_H */
📄 创建文件:
Core/Src/uart_handler.c
c
/**
* @file uart_handler.c
* @brief 串口DMA通信处理模块实现
*
* 实现功能:
* - DMA方式发送数据
* - 空闲中断+DMA方式接收不定长数据
* - 环形缓冲区管理
*
* @author STM32 Tutorial
* @version 1.0
*/
#include "uart_handler.h"
/* ===================== 私有变量 ===================== */
// 串口1自定义句柄(可根据需要扩展为数组支持多串口)
static UART_HandleTypeDef_Custom uart1_custom = {0};
/* ===================== 私有函数 ===================== */
/**
* @brief 获取串口对应的自定义句柄
* @param huart 串口句柄指针
* @retval UART_HandleTypeDef_Custom* 自定义句柄指针
*/
static UART_HandleTypeDef_Custom* GetUartCustomHandle(UART_HandleTypeDef *huart)
{
// 根据串口实例返回对应的自定义句柄
// 可扩展支持USART2、USART3等
if (huart->Instance == USART1) {
return &uart1_custom;
}
return NULL;
}
/**
* @brief 清除串口空闲中断标志
* @param huart 串口句柄指针
* @retval None
*
* @note 必须先读取SR寄存器,再读取DR寄存器才能清除IDLE标志
*/
static void UART_ClearIdleFlag(UART_HandleTypeDef *huart)
{
// 清除空闲中断标志:先读SR,再读DR
__IO uint32_t tmp;
tmp = huart->Instance->SR;
tmp = huart->Instance->DR;
(void)tmp; // 避免编译器警告
}
/* ===================== 公有函数实现 ===================== */
/**
* @brief 初始化串口DMA通信
*
* 初始化流程:
* 1. 清空缓冲区
* 2. 清除中断标志
* 3. 启动DMA接收
* 4. 使能空闲中断
*/
void UART_DMA_Init(UART_HandleTypeDef *huart)
{
UART_HandleTypeDef_Custom *custom = GetUartCustomHandle(huart);
if (custom == NULL) {
return;
}
// 1. 初始化缓冲区
memset(custom->rx_buffer, 0, UART_RX_BUFFER_SIZE);
memset(custom->tx_buffer, 0, UART_TX_BUFFER_SIZE);
custom->rx_len = 0;
custom->rx_head = 0;
custom->rx_tail = 0;
custom->rx_complete = false;
custom->tx_busy = false;
// 2. 清除空闲中断标志
UART_ClearIdleFlag(huart);
// 3. 启动DMA接收(循环模式)
HAL_UART_Receive_DMA(huart, custom->rx_buffer, UART_RX_BUFFER_SIZE);
// 4. 使能空闲中断
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);
printf("[UART] DMA初始化完成\r\n");
}
/**
* @brief 启动DMA接收
*
* 使用HAL库的Receive_DMA函数启动DMA接收,
* 配合空闲中断实现不定长数据接收。
*/
HAL_StatusTypeDef UART_DMA_StartReceive(UART_HandleTypeDef *huart)
{
UART_HandleTypeDef_Custom *custom = GetUartCustomHandle(huart);
if (custom == NULL) {
return HAL_ERROR;
}
// 重置接收缓冲区指针
custom->rx_head = 0;
custom->rx_tail = 0;
custom->rx_complete = false;
// 启动DMA接收(循环模式,持续接收)
return HAL_UART_Receive_DMA(huart, custom->rx_buffer, UART_RX_BUFFER_SIZE);
}
/**
* @brief DMA发送数据
*
* 发送流程:
* 1. 检查发送状态,避免冲突
* 2. 复制数据到发送缓冲区
* 3. 启动DMA发送
* 4. 设置忙标志
*/
HAL_StatusTypeDef UART_DMA_Transmit(UART_HandleTypeDef *huart,
uint8_t *data, uint16_t len)
{
UART_HandleTypeDef_Custom *custom = GetUartCustomHandle(huart);
if (custom == NULL || data == NULL || len == 0) {
return HAL_ERROR;
}
// 检查发送是否忙
if (custom->tx_busy) {
printf("[UART] 发送忙,请稍后重试\r\n");
return HAL_BUSY;
}
// 检查数据长度
if (len > UART_TX_BUFFER_SIZE) {
printf("[UART] 数据长度超过缓冲区大小\r\n");
return HAL_ERROR;
}
// 复制数据到发送缓冲区
memcpy(custom->tx_buffer, data, len);
custom->tx_busy = true;
// 启动DMA发送
HAL_StatusTypeDef status = HAL_UART_Transmit_DMA(huart, custom->tx_buffer, len);
if (status != HAL_OK) {
custom->tx_busy = false;
printf("[UART] DMA发送启动失败: %d\r\n", status);
} else {
printf("[UART] DMA发送启动,长度: %d\r\n", len);
}
return status;
}
/**
* @brief 处理接收到的数据
*
* 在空闲中断中调用,计算接收数据长度并更新缓冲区指针。
*/
void UART_DMA_ProcessRxData(UART_HandleTypeDef *huart)
{
UART_HandleTypeDef_Custom *custom = GetUartCustomHandle(huart);
if (custom == NULL) {
return;
}
// 获取当前DMA传输剩余数据量
uint16_t remaining = __HAL_DMA_GET_COUNTER(huart->hdmarx);
// 计算本次接收的数据长度
// 原理:总缓冲区大小 - 剩余未传输的数据量 = 已接收的数据量
uint16_t received_len = UART_RX_BUFFER_SIZE - remaining;
// 更新接收长度(考虑环形缓冲区)
custom->rx_len = received_len;
custom->rx_complete = true;
printf("[UART] 接收到数据,长度: %d\r\n", received_len);
// 处理接收到的数据(可在此添加协议解析逻辑)
// 示例:简单的回显
// UART_DMA_Transmit(huart, custom->rx_buffer, received_len);
}
/**
* @brief 获取接收缓冲区中的数据
*
* 从环形缓冲区中读取数据,支持多次读取。
*/
uint16_t UART_DMA_ReadRxData(UART_HandleTypeDef *huart,
uint8_t *buffer, uint16_t max_len)
{
UART_HandleTypeDef_Custom *custom = GetUartCustomHandle(huart);
if (custom == NULL || buffer == NULL || max_len == 0) {
return 0;
}
// 计算可读数据量
uint16_t available = custom->rx_len;
uint16_t to_read = (available < max_len) ? available : max_len;
if (to_read == 0) {
return 0;
}
// 复制数据到输出缓冲区
memcpy(buffer, custom->rx_buffer, to_read);
// 更新缓冲区状态
custom->rx_len = 0;
custom->rx_complete = false;
// 重新启动DMA接收(如果需要)
// HAL_UART_Receive_DMA(huart, custom->rx_buffer, UART_RX_BUFFER_SIZE);
return to_read;
}
/**
* @brief 串口空闲中断回调
*
* 当串口处于空闲状态(接收到数据后超过1字节时间无新数据)时触发。
* 这是实现不定长数据接收的关键。
*/
void UART_DMA_IdleCallback(UART_HandleTypeDef *huart)
{
// 检查是否是空闲中断
if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE)) {
// 清除空闲中断标志
UART_ClearIdleFlag(huart);
// 处理接收数据
UART_DMA_ProcessRxData(huart);
}
}
/**
* @brief 串口发送完成回调
*
* DMA发送完成后调用,清除忙标志。
*/
void UART_DMA_TxCompleteCallback(UART_HandleTypeDef *huart)
{
UART_HandleTypeDef_Custom *custom = GetUartCustomHandle(huart);
if (custom != NULL) {
custom->tx_busy = false;
printf("[UART] DMA发送完成\r\n");
}
}
3.3 修改中断处理文件
📝 修改文件:
Core/Src/stm32f4xx_it.c
在文件中找到USART1_IRQHandler函数,添加空闲中断处理:
c
/**
* @brief USART1全局中断处理
*
* 处理USART1的所有中断:
* - 空闲中断(IDLE):用于DMA接收完成检测
* - 其他中断由HAL库处理
*/
void USART1_IRQHandler(void)
{
// 检查空闲中断标志
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) &&
__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_IDLE)) {
// 调用自定义空闲中断处理
UART_DMA_IdleCallback(&huart1);
}
// 调用HAL库中断处理
HAL_UART_IRQHandler(&huart1);
}
3.4 修改主函数
📝 修改文件:
Core/Src/main.c
c
/* Private includes ----------------------------------------------------------*/
#include "uart_handler.h"
#include <stdio.h>
/* Private variables ---------------------------------------------------------*/
// 重定向printf到串口
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
/**
* @brief 主函数
*/
int main(void)
{
// HAL初始化
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 初始化外设
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
// 初始化串口DMA通信
UART_DMA_Init(&huart1);
printf("\r\n========================================\r\n");
printf(" STM32 UART DMA 通信示例\r\n");
printf("========================================\r\n");
printf("等待接收数据...\r\n");
uint8_t rx_data[128];
uint32_t last_tick = HAL_GetTick();
while (1)
{
// 检查是否有接收到的数据
uint16_t rx_len = UART_DMA_ReadRxData(&huart1, rx_data, sizeof(rx_data));
if (rx_len > 0) {
printf("[Main] 收到数据 (%d bytes): ", rx_len);
// 打印接收到的数据(十六进制)
for (uint16_t i = 0; i < rx_len; i++) {
printf("%02X ", rx_data[i]);
}
printf("\r\n");
// 打印ASCII字符
printf("[Main] ASCII: ");
for (uint16_t i = 0; i < rx_len; i++) {
if (rx_data[i] >= 32 && rx_data[i] <= 126) {
printf("%c", rx_data[i]);
} else {
printf(".");
}
}
printf("\r\n");
// 回显数据
UART_DMA_Transmit(&huart1, rx_data, rx_len);
// 重新启动DMA接收
UART_DMA_StartReceive(&huart1);
}
// 每5秒发送一次心跳
if (HAL_GetTick() - last_tick >= 5000) {
last_tick = HAL_GetTick();
uint8_t heartbeat[] = "[Heartbeat] System running...\r\n";
UART_DMA_Transmit(&huart1, heartbeat, strlen((char*)heartbeat));
}
HAL_Delay(10); // 10ms延时,降低CPU占用
}
}
四、系统架构与流程
4.1 DMA传输流程图
发送
接收
否
是
开始
发送/接收?
准备发送数据
启动DMA接收
复制数据到发送缓冲区
启动DMA发送
DMA控制器传输数据
发送完成中断
清除忙标志
结束
DMA接收数据到缓冲区
检测到空闲?
空闲中断触发
计算接收数据长度
处理接收数据
重新启动DMA接收
4.2 空闲中断工作原理
空闲中断(IDLE)是USART的一个特殊中断,当接收线路处于空闲状态超过1帧时间时触发。配合DMA使用可以实现不定长数据接收:
- 启动DMA接收:配置DMA将接收到的数据连续写入缓冲区
- 数据到达:每接收一个字节,DMA自动将数据存入缓冲区
- 数据停止:当发送方停止发送,线路进入空闲状态
- 空闲中断触发:USART检测到空闲状态,触发IDLE中断
- 计算数据长度:通过DMA剩余计数器计算实际接收长度
- 处理数据:读取缓冲区中的有效数据
- 重新启动:清空标志,准备接收下一帧数据
五、测试验证
5.1 编译下载
- 在STM32CubeIDE中点击 Project → Build All 编译工程
- 连接ST-Link调试器到开发板
- 点击 Run → Debug 下载并运行程序
5.2 串口调试
- 打开串口调试助手(如SSCOM)
- 选择正确的COM口,波特率115200
- 打开串口连接
测试步骤:
- 发送测试:在串口助手发送框输入"Hello STM32",点击发送
- 观察回显:应收到回显的"Hello STM32"
- 十六进制测试 :发送十六进制数据
31 32 33 0D 0A(即"123\r\n") - 大数据测试:发送超过100字节的数据,验证DMA传输稳定性
- 心跳检测:观察每5秒自动发送的心跳信息
5.3 预期输出
========================================
STM32 UART DMA 通信示例
========================================
等待接收数据...
[UART] DMA初始化完成
[UART] DMA发送启动,长度: 31
[Main] 收到数据 (11 bytes): 48 65 6C 6C 6F 20 53 54 4D 33 32
[Main] ASCII: Hello STM32
[UART] DMA发送启动,长度: 11
[UART] DMA发送完成
[Heartbeat] System running...
六、故障排查与问题解决
6.1 常见问题
问题1:无法接收到数据
现象: 串口助手发送数据,STM32无响应
排查步骤:
-
检查硬件连接
c// 确认TX/RX未接反,共地正常 // 使用万用表测量电平:TX引脚应为3.3V高电平 -
检查波特率配置
c// 确认双方波特率一致 // 在main.c中添加调试输出 printf("USART1 BaudRate: %lu\r\n", huart1.Init.BaudRate); -
检查DMA配置
c// 确认DMA流和通道配置正确 // USART1_RX使用DMA2 Stream2 Channel4 // USART1_TX使用DMA2 Stream7 Channel4 -
检查中断使能
c// 确认NVIC中启用了相应中断 HAL_NVIC_EnableIRQ(USART1_IRQn); HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn); HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);
问题2:数据接收不完整
现象: 接收到的数据被截断或丢失
原因分析:
- DMA缓冲区溢出
- 数据处理速度跟不上接收速度
- 空闲中断未正确触发
解决方案:
-
增大缓冲区
c// uart_handler.h中修改 #define UART_RX_BUFFER_SIZE 512 // 从256改为512 -
使用循环模式
c// 在CubeMX中配置DMA为Circular模式 // 或在代码中修改 HAL_UART_Receive_DMA(&huart1, rx_buffer, UART_RX_BUFFER_SIZE); // 改为循环模式后无需重新启动接收 -
优化数据处理
c// 减少主循环中的延时 // 使用中断方式处理数据,而非轮询
问题3:发送数据乱码
现象: 接收方看到乱码字符
排查步骤:
-
检查波特率匹配
c// 双方波特率必须完全一致 // 检查时钟配置是否正确 -
检查数据格式
c// 确认数据位、停止位、校验位配置一致 huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; -
检查时钟源
c// 确认USART时钟源配置正确 // 在CubeMX Clock Configuration中检查APB2时钟
问题4:DMA发送冲突
现象: 连续发送数据时出现错误或丢失
原因: 上一次DMA发送未完成就启动新的发送
解决方案:
c
// 在发送前检查忙标志
HAL_StatusTypeDef UART_DMA_Transmit_Safe(UART_HandleTypeDef *huart,
uint8_t *data, uint16_t len)
{
// 等待上一次发送完成
uint32_t timeout = HAL_GetTick() + 1000; // 1秒超时
while (custom->tx_busy) {
if (HAL_GetTick() > timeout) {
printf("[UART] 发送超时\r\n");
custom->tx_busy = false;
break;
}
HAL_Delay(1);
}
return UART_DMA_Transmit(huart, data, len);
}
6.2 性能优化建议
-
使用双缓冲机制
c// 使用两个缓冲区交替接收 uint8_t rx_buffer1[256]; uint8_t rx_buffer2[256]; // 当一个缓冲区满时,切换到另一个 -
优化中断处理
c// 在中断中只做标记,数据处理放到主循环 void UART_DMA_IdleCallback(UART_HandleTypeDef *huart) { // 仅设置标志 rx_ready_flag = true; // 清除中断标志 UART_ClearIdleFlag(huart); } -
使用DMA FIFO
c// 启用DMA FIFO减少总线访问次数 // 在CubeMX DMA配置中启用FIFO
七、总结
7.1 核心知识点
- DMA传输原理:外设直接与内存交换数据,无需CPU干预
- 空闲中断机制:检测数据帧结束,实现不定长数据接收
- HAL库使用:掌握HAL_UART_Transmit_DMA和HAL_UART_Receive_DMA
- 中断处理:正确配置和清除中断标志
- 缓冲区管理:环形缓冲区实现高效数据流转
7.2 扩展方向
- 多串口支持:扩展代码支持USART2、USART3
- 协议封装:实现Modbus、自定义协议帧解析
- RTOS集成:在FreeRTOS中使用DMA串口
- 流控支持:添加硬件流控(RTS/CTS)
- DMA双缓冲:实现无间断连续接收
7.3 学习资源
官方文档:
官方GitHub: