STM32实战:基于STM32CubeMX的串口通信(UART)与DMA传输优化

文章目录

一、前言

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无响应。

原因分析:

  1. 硬件连接错误
  2. 波特率不匹配
  3. DMA未正确启动
  4. 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. 缓冲区溢出
  2. 处理速度跟不上接收速度
  3. 环形缓冲区操作冲突

解决方案:

方案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返回错误。

原因分析:

  1. DMA未初始化
  2. DMA通道被占用
  3. 数据指针或长度错误

解决方案:

方案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传输后程序卡死。

原因分析:

  1. 中断优先级配置错误
  2. DMA中断未使能
  3. 回调函数问题

解决方案:

方案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中断不触发,无法处理数据。

原因分析:

  1. IDLE中断未使能
  2. 中断标志清除顺序错误
  3. 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 性能优化建议

  1. 双缓冲机制:使用双缓冲区实现零拷贝数据接收
  2. DMA循环模式:对于连续数据流,使用DMA循环模式
  3. 中断优先级:合理配置中断优先级,确保实时性
  4. 缓冲区大小:根据数据量合理设置缓冲区大小

7.3 扩展学习方向

  • RS485通信:学习半双工RS485的多机通信
  • USB虚拟串口:使用USB CDC实现高速数据传输
  • RTOS集成:在FreeRTOS中使用UART DMA

7.4 学习资源

官方文档:

相关推荐
qq_150841992 小时前
用Simplicity Studio开发EFM8单片机(续)
单片机·嵌入式硬件
yong99903 小时前
基于STM32与TFTLCD的示波器设计
stm32·单片机·嵌入式硬件
我叫洋洋3 小时前
[ESP32-S3 点亮灯]
单片机·嵌入式硬件·esp32
搁浅小泽4 小时前
可靠性试验测试时间制定方法简介
单片机·嵌入式硬件·可靠性工程师
yoyobravery4 小时前
蓝桥杯第13届单片机(满分)
单片机·蓝桥杯
清风6666664 小时前
基于单片机的正弦波与方波峰峰值与频率测量系统设计
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
gihigo19984 小时前
基于51单片机的TB6600步进电机驱动程序
stm32·单片机·51单片机
搁浅小泽5 小时前
变频空调检修完整流程(通用版)
单片机·嵌入式硬件·可靠性工程师
笨笨饿14 小时前
29_Z变换在工程中的实际意义
c语言·开发语言·人工智能·单片机·mcu·算法·机器人