文章目录
-
- 一、前言
-
- [1.1 技术背景](#1.1 技术背景)
- [1.2 应用场景](#1.2 应用场景)
- [1.3 本文目标](#1.3 本文目标)
- 二、环境准备
-
- [2.1 硬件准备](#2.1 硬件准备)
- [2.2 STM32CubeMX配置](#2.2 STM32CubeMX配置)
- 三、核心实现
-
- [3.1 UART+DMA基础驱动](#3.1 UART+DMA基础驱动)
- [3.2 中断服务程序修改](#3.2 中断服务程序修改)
- [3.3 主程序实现](#3.3 主程序实现)
- [3.4 高级功能:不定长数据接收](#3.4 高级功能:不定长数据接收)
- 四、测试验证
-
- [4.1 测试环境](#4.1 测试环境)
- [4.2 功能测试](#4.2 功能测试)
- [4.3 性能测试](#4.3 性能测试)
- 五、故障排查与问题解决
-
- [5.1 通信问题](#5.1 通信问题)
- [5.2 DMA问题](#5.2 DMA问题)
- [5.3 IDLE中断问题](#5.3 IDLE中断问题)
- 六、进阶应用
-
- [6.1 Modbus RTU通信](#6.1 Modbus RTU通信)
- [6.2 数据帧协议设计](#6.2 数据帧协议设计)
- 七、总结
-
- [7.1 核心知识点回顾](#7.1 核心知识点回顾)
- [7.2 性能优化建议](#7.2 性能优化建议)
- [7.3 扩展学习方向](#7.3 扩展学习方向)
- [7.4 学习资源](#7.4 学习资源)
一、前言
1.1 技术背景
串口通信(UART)是嵌入式开发中最基础、最常用的通信方式之一。在STM32开发中,传统的查询方式和中断方式虽然简单,但在大数据量传输时会占用大量CPU资源,影响系统实时性。
DMA(Direct Memory Access,直接存储器访问)技术可以在不占用CPU的情况下,实现外设与内存之间的高速数据传输。将UART与DMA结合使用,可以:
- 大幅降低CPU占用率
- 提高数据传输效率
- 实现后台数据收发
- 支持大数据量连续传输
1.2 应用场景
- 高速数据采集与传输
- 无线模块(WiFi/蓝牙/4G)通信
- 调试信息输出
- 多传感器数据汇总
- 固件升级(IAP)
1.3 本文目标
通过本文,你将学会:
- STM32 UART外设的工作原理
- DMA传输机制和中断处理
- 使用STM32CubeMX配置UART+DMA
- 实现高效的数据收发
- 环形缓冲区设计
- 常见问题排查
技术栈:
- 开发板:STM32F103C8T6
- 开发环境:STM32CubeIDE
- 固件库:STM32 HAL库
- 调试工具:USB转TTL模块
二、环境准备
2.1 硬件准备
| 器件 | 数量 | 说明 |
|---|---|---|
| STM32F103C8T6最小系统板 | 1 | 主控芯片 |
| USB转TTL模块 | 1 | 串口调试 |
| 杜邦线 | 若干 | 连接线 |
UART引脚连接:
| STM32 | USB转TTL | 说明 |
|---|---|---|
| PA9 (USART1_TX) | RXD | 发送 |
| PA10 (USART1_RX) | TXD | 接收 |
| GND | GND | 共地 |
⚠️ 注意:不要连接VCC,避免电压冲突!
2.2 STM32CubeMX配置
1. 时钟配置:
System Clock: 72MHz
APB2 Clock (USART1): 72MHz
APB1 Clock (USART2/3): 36MHz
2. USART1配置:
Mode: Asynchronous
Baud Rate: 115200
Word Length: 8 Bits
Stop Bits: 1
Parity: None
3. DMA配置:
USART1_TX: DMA1 Channel4
USART1_RX: DMA1 Channel5
Mode: Normal (单次传输) 或 Circular (循环传输)
Priority: Medium
4. NVIC配置:
USART1 global interrupt: Enabled
DMA1 Channel4 global interrupt: Enabled
DMA1 Channel5 global interrupt: Enabled
三、核心实现
3.1 UART+DMA基础驱动
📄 创建文件:
Inc/uart_dma.h
c
#ifndef __UART_DMA_H
#define __UART_DMA_H
#include "stm32f1xx_hal.h"
#include <string.h>
#include <stdio.h>
// 缓冲区大小定义
#define UART_TX_BUF_SIZE 256
#define UART_RX_BUF_SIZE 256
#define UART_RX_RING_SIZE 512
// UART句柄声明
extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_tx;
extern DMA_HandleTypeDef hdma_usart1_rx;
// 环形缓冲区结构体
typedef struct
{
uint8_t buffer[UART_RX_RING_SIZE];
volatile uint16_t head;
volatile uint16_t tail;
volatile uint16_t count;
} RingBuffer_TypeDef;
// 函数声明
void UART_DMA_Init(void);
void UART_DMA_DeInit(void);
// 发送函数
HAL_StatusTypeDef UART_DMA_Send(uint8_t *data, uint16_t len);
HAL_StatusTypeDef UART_DMA_SendString(char *str);
HAL_StatusTypeDef UART_DMA_Printf(const char *fmt, ...);
// 接收函数
uint16_t UART_DMA_GetRxData(uint8_t *buf, uint16_t len);
uint16_t UART_DMA_GetRxCount(void);
void UART_DMA_FlushRx(void);
// 环形缓冲区操作
uint16_t RingBuffer_Read(RingBuffer_TypeDef *rb, uint8_t *data, uint16_t len);
uint16_t RingBuffer_Write(RingBuffer_TypeDef *rb, uint8_t *data, uint16_t len);
uint16_t RingBuffer_GetCount(RingBuffer_TypeDef *rb);
void RingBuffer_Flush(RingBuffer_TypeDef *rb);
// 中断回调
void UART_DMA_RxCpltCallback(void);
void UART_DMA_IdleCallback(void);
#endif
📄 创建文件:
Src/uart_dma.c
c
#include "uart_dma.h"
#include <stdarg.h>
// 发送缓冲区
static uint8_t tx_buffer[UART_TX_BUF_SIZE];
static volatile uint8_t tx_busy = 0;
// 接收缓冲区
static uint8_t rx_buffer[UART_RX_BUF_SIZE];
static RingBuffer_TypeDef rx_ring_buffer;
// 空闲接收缓冲区
static uint8_t idle_rx_buffer[UART_RX_BUF_SIZE];
/**
* @brief UART DMA初始化
*/
void UART_DMA_Init(void)
{
// 初始化环形缓冲区
rx_ring_buffer.head = 0;
rx_ring_buffer.tail = 0;
rx_ring_buffer.count = 0;
// 启动DMA空闲接收
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart1, idle_rx_buffer, UART_RX_BUF_SIZE);
}
/**
* @brief UART DMA发送数据
* @param data: 数据指针
* @param len: 数据长度
* @retval HAL状态
*/
HAL_StatusTypeDef UART_DMA_Send(uint8_t *data, uint16_t len)
{
if(len == 0 || len > UART_TX_BUF_SIZE)
return HAL_ERROR;
// 等待上次发送完成
while(tx_busy);
tx_busy = 1;
// 复制数据到发送缓冲区
memcpy(tx_buffer, data, len);
// 启动DMA发送
return HAL_UART_Transmit_DMA(&huart1, tx_buffer, len);
}
/**
* @brief UART DMA发送字符串
* @param str: 字符串指针
* @retval HAL状态
*/
HAL_StatusTypeDef UART_DMA_SendString(char *str)
{
return UART_DMA_Send((uint8_t*)str, strlen(str));
}
/**
* @brief UART DMA格式化输出
* @param fmt: 格式化字符串
* @param ...: 可变参数
* @retval HAL状态
*/
HAL_StatusTypeDef UART_DMA_Printf(const char *fmt, ...)
{
va_list ap;
uint16_t len;
va_start(ap, fmt);
len = vsnprintf((char*)tx_buffer, UART_TX_BUF_SIZE, fmt, ap);
va_end(ap);
if(len > UART_TX_BUF_SIZE - 1)
len = UART_TX_BUF_SIZE - 1;
return UART_DMA_Send(tx_buffer, len);
}
/**
* @brief 获取接收数据
* @param buf: 目标缓冲区
* @param len: 最大读取长度
* @retval 实际读取字节数
*/
uint16_t UART_DMA_GetRxData(uint8_t *buf, uint16_t len)
{
return RingBuffer_Read(&rx_ring_buffer, buf, len);
}
/**
* @brief 获取接收缓冲区数据量
* @retval 数据字节数
*/
uint16_t UART_DMA_GetRxCount(void)
{
return RingBuffer_GetCount(&rx_ring_buffer);
}
/**
* @brief 清空接收缓冲区
*/
void UART_DMA_FlushRx(void)
{
RingBuffer_Flush(&rx_ring_buffer);
}
// ==================== 环形缓冲区实现 ====================
/**
* @brief 向环形缓冲区写入数据
*/
uint16_t RingBuffer_Write(RingBuffer_TypeDef *rb, uint8_t *data, uint16_t len)
{
uint16_t i;
uint16_t free_space = UART_RX_RING_SIZE - rb->count;
if(len > free_space)
len = free_space;
for(i = 0; i < len; i++)
{
rb->buffer[rb->head] = data[i];
rb->head = (rb->head + 1) % UART_RX_RING_SIZE;
}
// 更新计数(关中断保护)
__disable_irq();
rb->count += len;
__enable_irq();
return len;
}
/**
* @brief 从环形缓冲区读取数据
*/
uint16_t RingBuffer_Read(RingBuffer_TypeDef *rb, uint8_t *data, uint16_t len)
{
uint16_t i;
if(len > rb->count)
len = rb->count;
for(i = 0; i < len; i++)
{
data[i] = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) % UART_RX_RING_SIZE;
}
// 更新计数(关中断保护)
__disable_irq();
rb->count -= len;
__enable_irq();
return len;
}
/**
* @brief 获取环形缓冲区数据量
*/
uint16_t RingBuffer_GetCount(RingBuffer_TypeDef *rb)
{
return rb->count;
}
/**
* @brief 清空环形缓冲区
*/
void RingBuffer_Flush(RingBuffer_TypeDef *rb)
{
__disable_irq();
rb->head = 0;
rb->tail = 0;
rb->count = 0;
__enable_irq();
}
// ==================== 中断回调函数 ====================
/**
* @brief UART发送完成回调
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
tx_busy = 0;
}
}
/**
* @brief UART空闲中断处理(IDLE Line Detection)
* @note 在一帧数据接收完成后触发
*/
void UART_DMA_IdleCallback(void)
{
// 清除IDLE标志
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
// 停止DMA接收
HAL_UART_DMAStop(&huart1);
// 计算接收到的数据长度
uint16_t recv_len = UART_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
// 将数据写入环形缓冲区
if(recv_len > 0)
{
RingBuffer_Write(&rx_ring_buffer, idle_rx_buffer, recv_len);
}
// 重新启动DMA接收
HAL_UART_Receive_DMA(&huart1, idle_rx_buffer, UART_RX_BUF_SIZE);
}
/**
* @brief UART错误回调
*/
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
// 错误处理:重新初始化UART
HAL_UART_DeInit(&huart1);
MX_USART1_UART_Init();
UART_DMA_Init();
}
}
3.2 中断服务程序修改
📝 修改文件:
Src/stm32f1xx_it.c
在USART1_IRQHandler函数中添加空闲中断处理:
c
void USART1_IRQHandler(void)
{
// 检查IDLE标志
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
{
UART_DMA_IdleCallback();
}
// 调用HAL中断处理
HAL_UART_IRQHandler(&huart1);
}
3.3 主程序实现
📄 创建文件:
Src/main.c
c
#include "main.h"
#include "uart_dma.h"
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_tx;
DMA_HandleTypeDef hdma_usart1_rx;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
int main(void)
{
uint8_t rx_data[128];
uint16_t rx_len;
// HAL初始化
HAL_Init();
SystemClock_Config();
// 初始化外设
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
// 初始化UART DMA
UART_DMA_Init();
// 发送启动信息
UART_DMA_Printf("STM32 UART DMA Demo\r\n");
UART_DMA_Printf("Baud Rate: 115200\r\n");
UART_DMA_Printf("Ready to receive data...\r\n\r\n");
uint32_t last_tick = HAL_GetTick();
while(1)
{
// 检查接收数据
rx_len = UART_DMA_GetRxData(rx_data, sizeof(rx_data) - 1);
if(rx_len > 0)
{
rx_data[rx_len] = '\0';
UART_DMA_Printf("Received [%d bytes]: %s\r\n", rx_len, rx_data);
// 回显数据
UART_DMA_SendString("Echo: ");
UART_DMA_Send(rx_data, rx_len);
UART_DMA_SendString("\r\n");
}
// 每秒发送心跳
if(HAL_GetTick() - last_tick >= 1000)
{
last_tick = HAL_GetTick();
UART_DMA_Printf("[Heartbeat] Uptime: %lus, RxCount: %d\r\n",
last_tick / 1000, UART_DMA_GetRxCount());
}
// 其他任务...
HAL_Delay(10);
}
}
/**
* @brief USART1初始化
*/
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief DMA初始化
*/
static void MX_DMA_Init(void)
{
// DMA控制器时钟使能
__HAL_RCC_DMA1_CLK_ENABLE();
// DMA中断配置
HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
}
/**
* @brief GPIO初始化
*/
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
}
void Error_Handler(void)
{
__disable_irq();
while(1) {}
}
3.4 高级功能:不定长数据接收
📄 创建文件:
Src/uart_dma_advanced.c
c
#include "uart_dma.h"
// 高级接收结构体
typedef struct
{
uint8_t buffer[2][UART_RX_BUF_SIZE]; // 双缓冲
uint8_t current_buf;
volatile uint8_t frame_ready;
uint16_t frame_len;
} AdvancedRx_TypeDef;
static AdvancedRx_TypeDef adv_rx;
/**
* @brief 初始化高级DMA接收(双缓冲模式)
*/
void UART_DMA_AdvancedInit(void)
{
adv_rx.current_buf = 0;
adv_rx.frame_ready = 0;
adv_rx.frame_len = 0;
// 使能IDLE中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
// 启动DMA接收(使用第一个缓冲区)
HAL_UART_Receive_DMA(&huart1, adv_rx.buffer[0], UART_RX_BUF_SIZE);
}
/**
* @brief 获取一帧数据
* @param buf: 输出缓冲区
* @param max_len: 最大长度
* @retval 实际长度,0表示无数据
*/
uint16_t UART_DMA_GetFrame(uint8_t *buf, uint16_t max_len)
{
if(!adv_rx.frame_ready)
return 0;
uint16_t len = adv_rx.frame_len;
if(len > max_len)
len = max_len;
// 复制数据
uint8_t buf_idx = adv_rx.current_buf ^ 1; // 上一个缓冲区
memcpy(buf, adv_rx.buffer[buf_idx], len);
adv_rx.frame_ready = 0;
return len;
}
/**
* @brief 高级空闲中断处理
*/
void UART_DMA_AdvancedIdleCallback(void)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
// 停止DMA
HAL_UART_DMAStop(&huart1);
// 计算接收长度
uint16_t recv_len = UART_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
if(recv_len > 0)
{
adv_rx.frame_len = recv_len;
adv_rx.frame_ready = 1;
// 切换缓冲区
adv_rx.current_buf ^= 1;
}
// 启动下一次接收
HAL_UART_Receive_DMA(&huart1, adv_rx.buffer[adv_rx.current_buf], UART_RX_BUF_SIZE);
}
四、测试验证
4.1 测试环境
串口助手配置:
- 波特率:115200
- 数据位:8
- 停止位:1
- 校验:无
- 流控:无
4.2 功能测试
测试1:数据回显
发送:Hello STM32
接收:Received [12 bytes]: Hello STM32
Echo: Hello STM32
测试2:大数据量传输
发送:连续发送1KB数据
验证:数据完整性,无丢包
测试3:心跳检测
接收:[Heartbeat] Uptime: 1s, RxCount: 0
接收:[Heartbeat] Uptime: 2s, RxCount: 0
...
4.3 性能测试
CPU占用率对比:
| 方式 | 发送1KB | 接收1KB |
|---|---|---|
| 查询方式 | 约85% | 约90% |
| 中断方式 | 约30% | 约40% |
| DMA方式 | 约5% | 约8% |
测试代码:
c
// 测量DMA传输时间
uint32_t start = HAL_GetTick();
UART_DMA_Send(large_buffer, 1024);
while(tx_busy); // 等待完成
uint32_t elapsed = HAL_GetTick() - start;
printf("Transfer 1KB in %lu ms\r\n", elapsed);
// 理论时间:1024*10/115200 ≈ 89ms
五、故障排查与问题解决
5.1 通信问题
问题1:无法接收到数据
现象:
串口助手发送数据,STM32无响应。
原因分析:
- 硬件连接错误
- 波特率不匹配
- DMA未正确启动
- IDLE中断未使能
解决方案:
方案1:检查硬件连接
STM32 PA9(TX) -> USB-TTL RX
STM32 PA10(RX) -> USB-TTL TX
STM32 GND -> USB-TTL GND
注意:TX接RX,RX接TX,不要接反!
方案2:验证波特率
c
// 输出实际波特率
uint32_t apb2_clock = HAL_RCC_GetPCLK2Freq();
uint32_t baud = huart1.Init.BaudRate;
printf("APB2 Clock: %lu Hz\r\n", apb2_clock);
printf("Baud Rate: %lu bps\r\n", baud);
// 检查USARTDIV计算
float usartdiv = (float)apb2_clock / (16 * baud);
printf("USARTDIV: %.2f\r\n", usartdiv);
方案3:检查DMA状态
c
// 检查DMA是否运行
if(hdma_usart1_rx.State != HAL_DMA_STATE_BUSY)
{
printf("DMA not running! Restarting...\r\n");
HAL_UART_Receive_DMA(&huart1, idle_rx_buffer, UART_RX_BUF_SIZE);
}
问题2:数据丢失或乱码
现象:
接收到的数据不完整或有乱码。
原因分析:
- 缓冲区溢出
- 处理速度跟不上接收速度
- 环形缓冲区操作冲突
解决方案:
方案1:增大缓冲区
c
// 根据数据量调整缓冲区大小
#define UART_RX_BUF_SIZE 512 // 原256
#define UART_RX_RING_SIZE 1024 // 原512
方案2:优化处理速度
c
// 在主循环中及时处理数据
while(1)
{
// 高优先级处理接收数据
rx_len = UART_DMA_GetRxData(rx_data, sizeof(rx_data));
if(rx_len > 0)
{
ProcessData(rx_data, rx_len); // 快速处理
}
// 低优先级任务
HAL_Delay(1);
}
方案3:添加溢出检测
c
// 在RingBuffer_Write中添加
if(len > free_space)
{
printf("Ring buffer overflow! Dropping %d bytes\r\n", len - free_space);
len = free_space;
}
5.2 DMA问题
问题3:DMA传输不启动
现象:
调用HAL_UART_Transmit_DMA返回错误。
原因分析:
- DMA未初始化
- DMA通道被占用
- 数据指针或长度错误
解决方案:
方案1:检查DMA初始化
c
// 确保在UART初始化之前初始化DMA
MX_DMA_Init(); // 先初始化DMA
MX_USART1_UART_Init(); // 再初始化UART
UART_DMA_Init(); // 最后初始化UART DMA
方案2:检查DMA状态
c
HAL_StatusTypeDef status = HAL_UART_Transmit_DMA(&huart1, data, len);
if(status != HAL_OK)
{
printf("DMA Error: %d\r\n", status);
printf("DMA State: %d\r\n", hdma_usart1_tx.State);
printf("UART State: %d\r\n", huart1.gState);
}
问题4:DMA传输死锁
现象:
DMA传输后程序卡死。
原因分析:
- 中断优先级配置错误
- DMA中断未使能
- 回调函数问题
解决方案:
方案1:检查中断优先级
c
// 确保DMA中断优先级正确
HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 1, 0); // 发送
HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 1, 0); // 接收
HAL_NVIC_SetPriority(USART1_IRQn, 2, 0); // UART
// 注意:DMA优先级应高于或等于UART
方案2:检查中断使能
c
// 在MX_DMA_Init中确认
HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
5.3 IDLE中断问题
问题5:IDLE中断不触发
现象:
数据接收后IDLE中断不触发,无法处理数据。
原因分析:
- IDLE中断未使能
- 中断标志清除顺序错误
- DMA配置问题
解决方案:
方案1:检查IDLE使能
c
// 确保在UART初始化后使能IDLE中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
// 验证IDLE是否使能
if(__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_IDLE))
{
printf("IDLE interrupt enabled\r\n");
}
方案2:检查中断处理
c
void USART1_IRQHandler(void)
{
// 必须在调用HAL_UART_IRQHandler之前处理IDLE
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
{
UART_DMA_IdleCallback();
}
HAL_UART_IRQHandler(&huart1);
}
六、进阶应用
6.1 Modbus RTU通信
c
// Modbus帧结构:地址(1) + 功能码(1) + 数据(N) + CRC(2)
#define MODBUS_FRAME_MAX 256
typedef struct
{
uint8_t address;
uint8_t function;
uint8_t data[MODBUS_FRAME_MAX];
uint16_t data_len;
uint16_t crc;
} ModbusFrame_TypeDef;
/**
* @brief 解析Modbus帧
*/
int Modbus_ParseFrame(uint8_t *raw_data, uint16_t len, ModbusFrame_TypeDef *frame)
{
if(len < 4) return -1; // 最小帧长度
frame->address = raw_data[0];
frame->function = raw_data[1];
frame->data_len = len - 4; // 减去地址、功能码和CRC
memcpy(frame->data, &raw_data[2], frame->data_len);
frame->crc = (raw_data[len-1] << 8) | raw_data[len-2];
// 验证CRC
uint16_t calc_crc = Modbus_CalculateCRC(raw_data, len - 2);
if(calc_crc != frame->crc)
return -2; // CRC错误
return 0;
}
/**
* @brief 发送Modbus响应
*/
void Modbus_SendResponse(ModbusFrame_TypeDef *frame)
{
uint8_t tx_buf[MODBUS_FRAME_MAX];
uint16_t len = 0;
tx_buf[len++] = frame->address;
tx_buf[len++] = frame->function;
memcpy(&tx_buf[len], frame->data, frame->data_len);
len += frame->data_len;
uint16_t crc = Modbus_CalculateCRC(tx_buf, len);
tx_buf[len++] = crc & 0xFF;
tx_buf[len++] = (crc >> 8) & 0xFF;
UART_DMA_Send(tx_buf, len);
}
6.2 数据帧协议设计
c
// 自定义数据帧格式:
// [帧头(2)] + [长度(2)] + [命令(1)] + [数据(N)] + [校验(1)] + [帧尾(1)]
// 0xAA 0x55 LEN_H LEN_L CMD DATA SUM 0x0D
#define FRAME_HEADER_H 0xAA
#define FRAME_HEADER_L 0x55
#define FRAME_TAIL 0x0D
typedef enum
{
FRAME_STATE_WAIT_HEADER_H = 0,
FRAME_STATE_WAIT_HEADER_L,
FRAME_STATE_WAIT_LEN_H,
FRAME_STATE_WAIT_LEN_L,
FRAME_STATE_WAIT_CMD,
FRAME_STATE_WAIT_DATA,
FRAME_STATE_WAIT_SUM,
FRAME_STATE_WAIT_TAIL
} FrameParse_State;
/**
* @brief 状态机解析数据帧
*/
void Frame_ParseByte(uint8_t byte)
{
static FrameParse_State state = FRAME_STATE_WAIT_HEADER_H;
static uint8_t frame_buf[256];
static uint16_t frame_len;
static uint16_t data_cnt;
switch(state)
{
case FRAME_STATE_WAIT_HEADER_H:
if(byte == FRAME_HEADER_H)
state = FRAME_STATE_WAIT_HEADER_L;
break;
case FRAME_STATE_WAIT_HEADER_L:
if(byte == FRAME_HEADER_L)
state = FRAME_STATE_WAIT_LEN_H;
else
state = FRAME_STATE_WAIT_HEADER_H;
break;
case FRAME_STATE_WAIT_LEN_H:
frame_len = byte << 8;
state = FRAME_STATE_WAIT_LEN_L;
break;
case FRAME_STATE_WAIT_LEN_L:
frame_len |= byte;
if(frame_len > 0 && frame_len < 256)
{
data_cnt = 0;
state = FRAME_STATE_WAIT_CMD;
}
else
{
state = FRAME_STATE_WAIT_HEADER_H;
}
break;
case FRAME_STATE_WAIT_CMD:
frame_buf[0] = byte; // CMD
state = FRAME_STATE_WAIT_DATA;
break;
case FRAME_STATE_WAIT_DATA:
frame_buf[1 + data_cnt++] = byte;
if(data_cnt >= frame_len - 4) // 减去CMD、SUM、TAIL
state = FRAME_STATE_WAIT_SUM;
break;
case FRAME_STATE_WAIT_SUM:
// 验证校验和
state = FRAME_STATE_WAIT_TAIL;
break;
case FRAME_STATE_WAIT_TAIL:
if(byte == FRAME_TAIL)
{
// 帧接收完成,处理数据
ProcessFrame(frame_buf, frame_len);
}
state = FRAME_STATE_WAIT_HEADER_H;
break;
}
}
七、总结
7.1 核心知识点回顾
- UART通信:异步串行通信,配置波特率、数据位、停止位、校验位
- DMA传输:不占用CPU的数据传输方式,大幅提高传输效率
- IDLE中断:检测数据帧结束,实现不定长数据接收
- 环形缓冲区:解决生产-消费速率不匹配问题
7.2 性能优化建议
- 双缓冲机制:使用双缓冲区实现零拷贝数据接收
- DMA循环模式:对于连续数据流,使用DMA循环模式
- 中断优先级:合理配置中断优先级,确保实时性
- 缓冲区大小:根据数据量合理设置缓冲区大小
7.3 扩展学习方向
- RS485通信:学习半双工RS485的多机通信
- USB虚拟串口:使用USB CDC实现高速数据传输
- RTOS集成:在FreeRTOS中使用UART DMA
7.4 学习资源
官方文档: