STM32 HAL库实现串口DMA接收不定长数据

基于STM32 HAL库的串口DMA接收不定长数据的解决方案,包含空闲中断检测、环形缓冲区管理和数据处理。

一、系统架构设计

1.1 头文件定义

c 复制代码
// uart_dma.h
#ifndef __UART_DMA_H
#define __UART_DMA_H

#include "main.h"
#include "stm32f4xx_hal.h"
#include <string.h>
#include <stdbool.h>

// 定义使用的串口和DMA
#define UART_DMA_HUART         huart1      // 串口句柄
#define UART_DMA_HDMA          hdma_usart1_rx  // DMA句柄
#define UART_DMA_RX_BUFFER_SIZE 1024       // 接收缓冲区大小
#define UART_DMA_TX_BUFFER_SIZE 256        // 发送缓冲区大小
#define UART_DMA_MAX_PACKET_SIZE 512       // 最大数据包长度
#define UART_DMA_TIMEOUT_MS    1000        // 超时时间(毫秒)

// 串口状态枚举
typedef enum {
    UART_DMA_STATE_READY = 0,
    UART_DMA_STATE_BUSY,
    UART_DMA_STATE_ERROR,
    UART_DMA_STATE_TIMEOUT
} UART_DMA_StateTypeDef;

// 串口数据包结构
typedef struct {
    uint8_t data[UART_DMA_MAX_PACKET_SIZE];
    uint16_t length;
    uint32_t timestamp;
} UART_DMA_PacketTypeDef;

// 串口DMA管理结构
typedef struct {
    UART_HandleTypeDef *huart;
    DMA_HandleTypeDef *hdma;
    
    // 接收缓冲区(双缓冲机制)
    uint8_t rx_buffer[2][UART_DMA_RX_BUFFER_SIZE];
    volatile uint8_t active_buffer;  // 当前活动的接收缓冲区索引(0或1)
    
    // 环形缓冲区用于存储接收到的完整数据包
    UART_DMA_PacketTypeDef rx_packets[16];  // 最多存储16个数据包
    volatile uint16_t write_index;          // 写索引
    volatile uint16_t read_index;           // 读索引
    volatile uint16_t packet_count;         // 当前数据包数量
    
    // DMA接收相关变量
    volatile uint16_t dma_write_pos;        // DMA当前写入位置
    volatile uint16_t dma_last_pos;         // DMA上一次位置
    volatile bool dma_receiving;            // DMA接收状态
    
    // 空闲中断相关
    volatile bool idle_flag;                // 空闲中断标志
    volatile uint32_t last_rx_time;         // 最后接收时间
    
    // 状态和错误
    UART_DMA_StateTypeDef state;
    uint32_t error_flags;
    
    // 回调函数
    void (*on_receive_complete)(UART_DMA_PacketTypeDef *packet);
    void (*on_error)(uint32_t error_code);
    
    // 统计信息
    uint32_t rx_byte_count;
    uint32_t rx_packet_count;
    uint32_t error_count;
} UART_DMA_HandleTypeDef;

// 函数声明
void UART_DMA_Init(UART_DMA_HandleTypeDef *huart_dma, UART_HandleTypeDef *huart, DMA_HandleTypeDef *hdma);
void UART_DMA_StartReceive(UART_DMA_HandleTypeDef *huart_dma);
void UART_DMA_StopReceive(UART_DMA_HandleTypeDef *huart_dma);
void UART_DMA_IdleCallback(UART_DMA_HandleTypeDef *huart_dma);
void UART_DMA_ProcessData(UART_DMA_HandleTypeDef *huart_dma);
bool UART_DMA_GetPacket(UART_DMA_HandleTypeDef *huart_dma, UART_DMA_PacketTypeDef *packet);
uint16_t UART_DMA_SendData(UART_DMA_HandleTypeDef *huart_dma, uint8_t *data, uint16_t length);
uint16_t UART_DMA_SendString(UART_DMA_HandleTypeDef *huart_dma, char *str);
void UART_DMA_Reset(UART_DMA_HandleTypeDef *huart_dma);
uint32_t UART_DMA_GetRxByteCount(UART_DMA_HandleTypeDef *huart_dma);
uint32_t UART_DMA_GetRxPacketCount(UART_DMA_HandleTypeDef *huart_dma);
uint32_t UART_DMA_GetErrorCount(UART_DMA_HandleTypeDef *huart_dma);

// 中断服务函数原型
void UART_DMA_RX_IRQHandler(UART_DMA_HandleTypeDef *huart_dma);
void UART_DMA_DMA_IRQHandler(UART_DMA_HandleTypeDef *huart_dma);

#endif /* __UART_DMA_H */

二、核心实现文件

2.1 串口DMA初始化和管理

c 复制代码
// uart_dma.c
#include "uart_dma.h"

// 私有函数声明
static void UART_DMA_ResetRxBuffer(UART_DMA_HandleTypeDef *huart_dma);
static void UART_DMA_SwitchBuffer(UART_DMA_HandleTypeDef *huart_dma);
static uint16_t UART_DMA_GetDMARemainingData(UART_DMA_HandleTypeDef *huart_dma);
static uint16_t UART_DMA_GetDMAWritePosition(UART_DMA_HandleTypeDef *huart_dma);
static void UART_DMA_StorePacket(UART_DMA_HandleTypeDef *huart_dma, uint8_t *data, uint16_t length);

// 初始化串口DMA接收
void UART_DMA_Init(UART_DMA_HandleTypeDef *huart_dma, UART_HandleTypeDef *huart, DMA_HandleTypeDef *hdma)
{
    // 参数检查
    if (huart_dma == NULL || huart == NULL || hdma == NULL) {
        return;
    }
    
    // 初始化结构体
    memset(huart_dma, 0, sizeof(UART_DMA_HandleTypeDef));
    
    // 保存句柄
    huart_dma->huart = huart;
    huart_dma->hdma = hdma;
    
    // 初始化状态
    huart_dma->state = UART_DMA_STATE_READY;
    huart_dma->active_buffer = 0;
    huart_dma->dma_receiving = false;
    huart_dma->idle_flag = false;
    
    // 初始化环形缓冲区
    huart_dma->write_index = 0;
    huart_dma->read_index = 0;
    huart_dma->packet_count = 0;
    
    // 清空缓冲区
    memset(huart_dma->rx_buffer, 0, sizeof(huart_dma->rx_buffer));
    for (int i = 0; i < 16; i++) {
        huart_dma->rx_packets[i].length = 0;
    }
    
    // 初始化统计
    huart_dma->rx_byte_count = 0;
    huart_dma->rx_packet_count = 0;
    huart_dma->error_count = 0;
    
    // 配置串口
    __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);    // 禁用RXNE中断
    __HAL_UART_DISABLE_IT(huart, UART_IT_PE);      // 禁用奇偶错误中断
    __HAL_UART_DISABLE_IT(huart, UART_IT_ERR);     // 禁用错误中断
    
    // 使能空闲中断
    __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);
    
    // 配置DMA为循环模式
    hdma->Init.Mode = DMA_CIRCULAR;
    hdma->Init.PeriphInc = DMA_PINC_DISABLE;
    hdma->Init.MemInc = DMA_MINC_ENABLE;
    hdma->Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma->Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma->Init.Priority = DMA_PRIORITY_HIGH;
    
    HAL_DMA_Init(hdma);
    
    // 关联DMA到串口
    __HAL_LINKDMA(huart, hdmarx, *hdma);
    
    // 启动DMA接收
    UART_DMA_StartReceive(huart_dma);
}

// 启动DMA接收
void UART_DMA_StartReceive(UART_DMA_HandleTypeDef *huart_dma)
{
    if (huart_dma->state != UART_DMA_STATE_READY) {
        return;
    }
    
    // 重置接收缓冲区
    UART_DMA_ResetRxBuffer(huart_dma);
    
    // 启动DMA接收
    HAL_UART_Receive_DMA(huart_dma->huart, 
                         huart_dma->rx_buffer[huart_dma->active_buffer], 
                         UART_DMA_RX_BUFFER_SIZE);
    
    // 更新状态
    huart_dma->dma_receiving = true;
    huart_dma->dma_last_pos = 0;
    huart_dma->state = UART_DMA_STATE_BUSY;
    huart_dma->last_rx_time = HAL_GetTick();
}

// 停止DMA接收
void UART_DMA_StopReceive(UART_DMA_HandleTypeDef *huart_dma)
{
    if (!huart_dma->dma_receiving) {
        return;
    }
    
    // 停止DMA传输
    HAL_UART_DMAStop(huart_dma->huart);
    
    // 更新状态
    huart_dma->dma_receiving = false;
    huart_dma->state = UART_DMA_STATE_READY;
}

// 重置接收缓冲区
static void UART_DMA_ResetRxBuffer(UART_DMA_HandleTypeDef *huart_dma)
{
    memset(huart_dma->rx_buffer[0], 0, UART_DMA_RX_BUFFER_SIZE);
    memset(huart_dma->rx_buffer[1], 0, UART_DMA_RX_BUFFER_SIZE);
    huart_dma->active_buffer = 0;
    huart_dma->dma_write_pos = 0;
    huart_dma->dma_last_pos = 0;
}

// 获取DMA剩余数据量
static uint16_t UART_DMA_GetDMARemainingData(UART_DMA_HandleTypeDef *huart_dma)
{
    if (huart_dma->hdma == NULL) {
        return 0;
    }
    
    return __HAL_DMA_GET_COUNTER(huart_dma->hdma);
}

// 获取DMA当前写入位置
static uint16_t UART_DMA_GetDMAWritePosition(UART_DMA_HandleTypeDef *huart_dma)
{
    uint16_t remaining = UART_DMA_GetDMARemainingData(huart_dma);
    uint16_t write_pos = UART_DMA_RX_BUFFER_SIZE - remaining;
    
    return write_pos;
}

// 空闲中断回调函数
void UART_DMA_IdleCallback(UART_DMA_HandleTypeDef *huart_dma)
{
    if (!huart_dma->dma_receiving) {
        return;
    }
    
    // 清除空闲中断标志
    __HAL_UART_CLEAR_IDLEFLAG(huart_dma->huart);
    
    // 获取DMA当前写入位置
    huart_dma->dma_write_pos = UART_DMA_GetDMAWritePosition(huart_dma);
    
    // 计算接收到的数据长度
    uint16_t received_len = 0;
    
    if (huart_dma->dma_write_pos >= huart_dma->dma_last_pos) {
        // 正常情况,没有发生缓冲区回绕
        received_len = huart_dma->dma_write_pos - huart_dma->dma_last_pos;
    } else {
        // 发生缓冲区回绕
        received_len = (UART_DMA_RX_BUFFER_SIZE - huart_dma->dma_last_pos) + huart_dma->dma_write_pos;
        
        // 切换到另一个缓冲区
        UART_DMA_SwitchBuffer(huart_dma);
    }
    
    if (received_len > 0) {
        // 处理接收到的数据
        uint8_t *data_ptr = &huart_dma->rx_buffer[huart_dma->active_buffer][huart_dma->dma_last_pos];
        
        // 更新最后接收时间
        huart_dma->last_rx_time = HAL_GetTick();
        
        // 统计接收字节数
        huart_dma->rx_byte_count += received_len;
        
        // 存储数据包
        UART_DMA_StorePacket(huart_dma, data_ptr, received_len);
        
        // 调用接收完成回调
        if (huart_dma->on_receive_complete != NULL) {
            UART_DMA_PacketTypeDef temp_packet;
            temp_packet.length = received_len;
            temp_packet.timestamp = huart_dma->last_rx_time;
            memcpy(temp_packet.data, data_ptr, received_len);
            
            huart_dma->on_receive_complete(&temp_packet);
        }
    }
    
    // 更新DMA最后位置
    huart_dma->dma_last_pos = huart_dma->dma_write_pos;
    
    // 如果发生了缓冲区回绕,重置最后位置
    if (huart_dma->dma_last_pos >= UART_DMA_RX_BUFFER_SIZE) {
        huart_dma->dma_last_pos = 0;
    }
}

// 切换到另一个缓冲区
static void UART_DMA_SwitchBuffer(UART_DMA_HandleTypeDef *huart_dma)
{
    // 切换活动缓冲区索引
    huart_dma->active_buffer = (huart_dma->active_buffer + 1) % 2;
    
    // 重新配置DMA目标地址
    HAL_UART_DMAStop(huart_dma->huart);
    
    // 重新启动DMA,使用新的缓冲区
    HAL_UART_Receive_DMA(huart_dma->huart,
                        huart_dma->rx_buffer[huart_dma->active_buffer],
                        UART_DMA_RX_BUFFER_SIZE);
    
    // 重置写入位置
    huart_dma->dma_write_pos = 0;
    huart_dma->dma_last_pos = 0;
}

// 存储数据包到环形缓冲区
static void UART_DMA_StorePacket(UART_DMA_HandleTypeDef *huart_dma, uint8_t *data, uint16_t length)
{
    if (length == 0 || length > UART_DMA_MAX_PACKET_SIZE) {
        return;
    }
    
    // 检查环形缓冲区是否已满
    if (huart_dma->packet_count >= 16) {
        huart_dma->error_count++;
        if (huart_dma->on_error != NULL) {
            huart_dma->on_error(0x01);  // 缓冲区溢出错误
        }
        return;
    }
    
    // 获取当前写入位置
    uint16_t write_idx = huart_dma->write_index;
    
    // 存储数据包
    memcpy(huart_dma->rx_packets[write_idx].data, data, length);
    huart_dma->rx_packets[write_idx].length = length;
    huart_dma->rx_packets[write_idx].timestamp = HAL_GetTick();
    
    // 更新写索引和数据包计数
    huart_dma->write_index = (write_idx + 1) % 16;
    huart_dma->packet_count++;
    huart_dma->rx_packet_count++;
}

// 处理接收到的数据
void UART_DMA_ProcessData(UART_DMA_HandleTypeDef *huart_dma)
{
    // 检查是否有数据包需要处理
    if (huart_dma->packet_count == 0) {
        return;
    }
    
    // 获取当前读索引
    uint16_t read_idx = huart_dma->read_index;
    
    // 处理数据包
    UART_DMA_PacketTypeDef *packet = &huart_dma->rx_packets[read_idx];
    
    // 这里可以添加自定义的数据处理逻辑
    // 例如:解析协议、校验数据等
    
    // 示例:回显接收到的数据
    UART_DMA_SendData(huart_dma, packet->data, packet->length);
    
    // 更新读索引和数据包计数
    huart_dma->read_index = (read_idx + 1) % 16;
    huart_dma->packet_count--;
}

// 获取数据包
bool UART_DMA_GetPacket(UART_DMA_HandleTypeDef *huart_dma, UART_DMA_PacketTypeDef *packet)
{
    if (huart_dma->packet_count == 0 || packet == NULL) {
        return false;
    }
    
    // 获取当前读索引
    uint16_t read_idx = huart_dma->read_index;
    
    // 复制数据包
    memcpy(packet->data, huart_dma->rx_packets[read_idx].data, 
           huart_dma->rx_packets[read_idx].length);
    packet->length = huart_dma->rx_packets[read_idx].length;
    packet->timestamp = huart_dma->rx_packets[read_idx].timestamp;
    
    // 更新读索引和数据包计数
    huart_dma->read_index = (read_idx + 1) % 16;
    huart_dma->packet_count--;
    
    return true;
}

// 发送数据
uint16_t UART_DMA_SendData(UART_DMA_HandleTypeDef *huart_dma, uint8_t *data, uint16_t length)
{
    if (huart_dma->state != UART_DMA_STATE_READY || data == NULL || length == 0) {
        return 0;
    }
    
    HAL_StatusTypeDef status = HAL_UART_Transmit_DMA(huart_dma->huart, data, length);
    
    if (status != HAL_OK) {
        huart_dma->error_count++;
        if (huart_dma->on_error != NULL) {
            huart_dma->on_error(0x02);  // 发送错误
        }
        return 0;
    }
    
    return length;
}

// 发送字符串
uint16_t UART_DMA_SendString(UART_DMA_HandleTypeDef *huart_dma, char *str)
{
    if (str == NULL) {
        return 0;
    }
    
    return UART_DMA_SendData(huart_dma, (uint8_t*)str, strlen(str));
}

// 重置串口DMA
void UART_DMA_Reset(UART_DMA_HandleTypeDef *huart_dma)
{
    // 停止接收
    UART_DMA_StopReceive(huart_dma);
    
    // 重置结构体
    UART_DMA_Init(huart_dma, huart_dma->huart, huart_dma->hdma);
}

// 获取统计信息
uint32_t UART_DMA_GetRxByteCount(UART_DMA_HandleTypeDef *huart_dma)
{
    return huart_dma->rx_byte_count;
}

uint32_t UART_DMA_GetRxPacketCount(UART_DMA_HandleTypeDef *huart_dma)
{
    return huart_dma->rx_packet_count;
}

uint32_t UART_DMA_GetErrorCount(UART_DMA_HandleTypeDef *huart_dma)
{
    return huart_dma->error_count;
}

三、中断处理模块

3.1 中断服务函数

c 复制代码
// uart_dma_it.c
#include "uart_dma.h"

// 外部变量声明
extern UART_DMA_HandleTypeDef huart1_dma;

// 串口接收中断服务函数
void UART_DMA_RX_IRQHandler(UART_DMA_HandleTypeDef *huart_dma)
{
    if (huart_dma == NULL) {
        return;
    }
    
    // 检查空闲中断
    if (__HAL_UART_GET_FLAG(huart_dma->huart, UART_FLAG_IDLE) != RESET) {
        // 处理空闲中断
        UART_DMA_IdleCallback(huart_dma);
        
        // 清除空闲中断标志
        __HAL_UART_CLEAR_IDLEFLAG(huart_dma->huart);
    }
    
    // 检查其他错误中断
    if (__HAL_UART_GET_FLAG(huart_dma->huart, UART_FLAG_PE) != RESET) {
        // 奇偶校验错误
        __HAL_UART_CLEAR_PEFLAG(huart_dma->huart);
        huart_dma->error_count++;
        if (huart_dma->on_error != NULL) {
            huart_dma->on_error(0x04);
        }
    }
    
    if (__HAL_UART_GET_FLAG(huart_dma->huart, UART_FLAG_FE) != RESET) {
        // 帧错误
        __HAL_UART_CLEAR_FEFLAG(huart_dma->huart);
        huart_dma->error_count++;
        if (huart_dma->on_error != NULL) {
            huart_dma->on_error(0x08);
        }
    }
    
    if (__HAL_UART_GET_FLAG(huart_dma->huart, UART_FLAG_NE) != RESET) {
        // 噪声错误
        __HAL_UART_CLEAR_NEFLAG(huart_dma->huart);
        huart_dma->error_count++;
        if (huart_dma->on_error != NULL) {
            huart_dma->on_error(0x10);
        }
    }
    
    if (__HAL_UART_GET_FLAG(huart_dma->huart, UART_FLAG_ORE) != RESET) {
        // 溢出错误
        __HAL_UART_CLEAR_OREFLAG(huart_dma->huart);
        huart_dma->error_count++;
        if (huart_dma->on_error != NULL) {
            huart_dma->on_error(0x20);
        }
    }
}

// DMA中断服务函数
void UART_DMA_DMA_IRQHandler(UART_DMA_HandleTypeDef *huart_dma)
{
    if (huart_dma == NULL || huart_dma->hdma == NULL) {
        return;
    }
    
    // 检查DMA传输完成中断
    if (__HAL_DMA_GET_FLAG(huart_dma->hdma, __HAL_DMA_GET_TC_FLAG_INDEX(huart_dma->hdma))) {
        // 传输完成
        __HAL_DMA_CLEAR_FLAG(huart_dma->hdma, __HAL_DMA_GET_TC_FLAG_INDEX(huart_dma->hdma));
        
        // 处理DMA传输完成
        // 这里可以添加DMA传输完成的处理逻辑
    }
    
    // 检查DMA半传输完成中断
    if (__HAL_DMA_GET_FLAG(huart_dma->hdma, __HAL_DMA_GET_HT_FLAG_INDEX(huart_dma->hdma))) {
        // 半传输完成
        __HAL_DMA_CLEAR_FLAG(huart_dma->hdma, __HAL_DMA_GET_HT_FLAG_INDEX(huart_dma->hdma));
        
        // 处理DMA半传输完成
        // 可以在这里处理前半部分数据
    }
    
    // 检查DMA传输错误中断
    if (__HAL_DMA_GET_FLAG(huart_dma->hdma, __HAL_DMA_GET_TE_FLAG_INDEX(huart_dma->hdma))) {
        // 传输错误
        __HAL_DMA_CLEAR_FLAG(huart_dma->hdma, __HAL_DMA_GET_TE_FLAG_INDEX(huart_dma->hdma));
        
        huart_dma->error_count++;
        if (huart_dma->on_error != NULL) {
            huart_dma->on_error(0x40);  // DMA传输错误
        }
    }
}

// 超时检测函数(需要在主循环中定期调用)
void UART_DMA_TimeoutCheck(UART_DMA_HandleTypeDef *huart_dma)
{
    if (huart_dma == NULL || !huart_dma->dma_receiving) {
        return;
    }
    
    uint32_t current_time = HAL_GetTick();
    
    // 检查是否超时(超过1秒没有收到数据)
    if ((current_time - huart_dma->last_rx_time) > UART_DMA_TIMEOUT_MS) {
        // 超时处理
        if (huart_dma->dma_write_pos != huart_dma->dma_last_pos) {
            // 有未处理的数据,强制处理
            UART_DMA_IdleCallback(huart_dma);
        }
        
        // 重置最后接收时间
        huart_dma->last_rx_time = current_time;
    }
}

四、CubeMX配置和主程序

4.1 CubeMX配置步骤

c 复制代码
// 在CubeMX中配置以下内容:

// 1. 串口配置 (USART1为例)
//    - Mode: Asynchronous
//    - Baud Rate: 115200
//    - Word Length: 8 Bits
//    - Parity: None
//    - Stop Bits: 1
//    - Oversampling: 16
//    - Advanced Features: 启用DMA接收

// 2. DMA配置
//    - Stream: DMA2 Stream2 (USART1_RX)
//    - Direction: Peripheral To Memory
//    - Priority: High
//    - Mode: Circular
//    - Increment Address: Memory
//    - Data Width: Byte

// 3. NVIC配置
//    - 启用USART1全局中断
//    - 启用DMA2 Stream2全局中断
//    - 设置合适的优先级

4.2 主程序

c 复制代码
// main.c
#include "main.h"
#include "uart_dma.h"

// 全局变量
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
UART_DMA_HandleTypeDef huart1_dma;

// 自定义回调函数
void OnReceiveComplete(UART_DMA_PacketTypeDef *packet)
{
    // 接收到数据的处理函数
    // 可以在这里处理协议解析、数据存储等
    
    // 示例:通过串口回显接收到的数据
    UART_DMA_SendData(&huart1_dma, packet->data, packet->length);
    
    // 可以添加其他处理逻辑
    // 比如:数据校验、命令解析等
}

void OnError(uint32_t error_code)
{
    // 错误处理函数
    switch (error_code) {
        case 0x01:
            // 缓冲区溢出
            break;
        case 0x02:
            // 发送错误
            break;
        case 0x04:
            // 奇偶校验错误
            break;
        case 0x08:
            // 帧错误
            break;
        case 0x10:
            // 噪声错误
            break;
        case 0x20:
            // 溢出错误
            break;
        case 0x40:
            // DMA传输错误
            break;
        default:
            break;
    }
}

// 系统时钟配置
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
    // 配置HSE、PLL等
    // ... CubeMX生成的代码
}

// 外设初始化
static void MX_GPIO_Init(void)
{
    // GPIO初始化
    // ... CubeMX生成的代码
}

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;
    HAL_UART_Init(&huart1);
}

static void MX_DMA_Init(void)
{
    // DMA控制器时钟使能
    __HAL_RCC_DMA2_CLK_ENABLE();
    
    // 配置DMA2 Stream2
    hdma_usart1_rx.Instance = DMA2_Stream2;
    hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
    hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
    hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    HAL_DMA_Init(&hdma_usart1_rx);
    
    // 关联DMA到串口
    __HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);
}

// 初始化所有外设
void MX_Init(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_USART1_UART_Init();
}

int main(void)
{
    // 外设初始化
    MX_Init();
    
    // 初始化串口DMA接收
    UART_DMA_Init(&huart1_dma, &huart1, &hdma_usart1_rx);
    
    // 设置回调函数
    huart1_dma.on_receive_complete = OnReceiveComplete;
    huart1_dma.on_error = OnError;
    
    // 启动接收
    UART_DMA_StartReceive(&huart1_dma);
    
    // 发送欢迎信息
    UART_DMA_SendString(&huart1_dma, "UART DMA Receiver Ready!\r\n");
    
    while (1) {
        // 主循环
        
        // 1. 超时检测
        UART_DMA_TimeoutCheck(&huart1_dma);
        
        // 2. 处理接收到的数据
        UART_DMA_ProcessData(&huart1_dma);
        
        // 3. 其他任务
        // ...
        
        // 4. 延时
        HAL_Delay(1);
    }
}

// 串口1中断服务函数
void USART1_IRQHandler(void)
{
    UART_DMA_RX_IRQHandler(&huart1_dma);
}

// DMA2 Stream2中断服务函数
void DMA2_Stream2_IRQHandler(void)
{
    UART_DMA_DMA_IRQHandler(&huart1_dma);
}

五、高级功能和协议解析

5.1 协议解析模块

c 复制代码
// protocol_parser.c
#include "protocol_parser.h"
#include "uart_dma.h"

// 私有变量
static ProtocolParser_HandleTypeDef hparser;

// 初始化协议解析器
void ProtocolParser_Init(void)
{
    memset(&hparser, 0, sizeof(ProtocolParser_HandleTypeDef));
    hparser.state = PROTOCOL_STATE_IDLE;
    hparser.checksum = 0;
}

// 解析接收到的数据
ProtocolParseResult ProtocolParser_Parse(uint8_t *data, uint16_t length)
{
    ProtocolParseResult result = {0};
    
    for (uint16_t i = 0; i < length; i++) {
        uint8_t byte = data[i];
        
        switch (hparser.state) {
            case PROTOCOL_STATE_IDLE:
                if (byte == PROTOCOL_HEADER) {
                    hparser.state = PROTOCOL_STATE_HEADER;
                    hparser.buffer[hparser.index++] = byte;
                    hparser.checksum = 0;
                }
                break;
                
            case PROTOCOL_STATE_HEADER:
                if (byte == PROTOCOL_SECOND_HEADER) {
                    hparser.buffer[hparser.index++] = byte;
                    hparser.state = PROTOCOL_STATE_LENGTH;
                } else {
                    ProtocolParser_Reset();
                }
                break;
                
            case PROTOCOL_STATE_LENGTH:
                hparser.expected_length = byte;
                hparser.buffer[hparser.index++] = byte;
                
                if (hparser.expected_length > PROTOCOL_MAX_LENGTH) {
                    ProtocolParser_Reset();
                    result.status = PROTOCOL_STATUS_ERROR_LENGTH;
                } else {
                    hparser.state = PROTOCOL_STATE_DATA;
                }
                break;
                
            case PROTOCOL_STATE_DATA:
                hparser.buffer[hparser.index++] = byte;
                hparser.checksum ^= byte;  // 简单的异或校验
                
                if (hparser.index >= (hparser.expected_length + PROTOCOL_HEADER_SIZE)) {
                    hparser.state = PROTOCOL_STATE_CHECKSUM;
                }
                break;
                
            case PROTOCOL_STATE_CHECKSUM:
                if (byte == hparser.checksum) {
                    // 校验成功
                    hparser.buffer[hparser.index++] = byte;
                    
                    // 完整的协议帧
                    result.status = PROTOCOL_STATUS_OK;
                    result.length = hparser.index;
                    memcpy(result.data, hparser.buffer, hparser.index);
                    
                    // 触发回调
                    if (hparser.on_frame_received != NULL) {
                        hparser.on_frame_received(result.data, result.length);
                    }
                } else {
                    result.status = PROTOCOL_STATUS_ERROR_CHECKSUM;
                }
                
                ProtocolParser_Reset();
                break;
                
            default:
                ProtocolParser_Reset();
                break;
        }
        
        // 防止缓冲区溢出
        if (hparser.index >= PROTOCOL_BUFFER_SIZE) {
            ProtocolParser_Reset();
            result.status = PROTOCOL_STATUS_ERROR_OVERFLOW;
        }
    }
    
    return result;
}

// 构建协议帧
uint16_t ProtocolParser_BuildFrame(uint8_t *data, uint8_t length, uint8_t *frame)
{
    if (length > PROTOCOL_MAX_DATA_LENGTH) {
        return 0;
    }
    
    uint8_t index = 0;
    uint8_t checksum = 0;
    
    // 帧头
    frame[index++] = PROTOCOL_HEADER;
    frame[index++] = PROTOCOL_SECOND_HEADER;
    
    // 数据长度
    frame[index++] = length;
    
    // 数据
    for (uint8_t i = 0; i < length; i++) {
        frame[index++] = data[i];
        checksum ^= data[i];
    }
    
    // 校验和
    frame[index++] = checksum;
    
    return index;
}

// 重置解析器
void ProtocolParser_Reset(void)
{
    hparser.state = PROTOCOL_STATE_IDLE;
    hparser.index = 0;
    hparser.checksum = 0;
    memset(hparser.buffer, 0, PROTOCOL_BUFFER_SIZE);
}

// 设置接收回调
void ProtocolParser_SetCallback(void (*callback)(uint8_t *, uint16_t))
{
    hparser.on_frame_received = callback;
}

5.2 协议解析头文件

c 复制代码
// protocol_parser.h
#ifndef __PROTOCOL_PARSER_H
#define __PROTOCOL_PARSER_H

#include <stdint.h>
#include <string.h>

// 协议定义
#define PROTOCOL_HEADER          0xAA
#define PROTOCOL_SECOND_HEADER   0x55
#define PROTOCOL_BUFFER_SIZE     256
#define PROTOCOL_MAX_LENGTH      128
#define PROTOCOL_MAX_DATA_LENGTH 64
#define PROTOCOL_HEADER_SIZE     3  // 帧头1 + 帧头2 + 长度

// 协议状态
typedef enum {
    PROTOCOL_STATE_IDLE = 0,
    PROTOCOL_STATE_HEADER,
    PROTOCOL_STATE_LENGTH,
    PROTOCOL_STATE_DATA,
    PROTOCOL_STATE_CHECKSUM
} ProtocolState;

// 解析结果状态
typedef enum {
    PROTOCOL_STATUS_OK = 0,
    PROTOCOL_STATUS_ERROR_HEADER,
    PROTOCOL_STATUS_ERROR_LENGTH,
    PROTOCOL_STATUS_ERROR_CHECKSUM,
    PROTOCOL_STATUS_ERROR_OVERFLOW,
    PROTOCOL_STATUS_ERROR_TIMEOUT
} ProtocolStatus;

// 解析结果
typedef struct {
    uint8_t data[PROTOCOL_BUFFER_SIZE];
    uint16_t length;
    ProtocolStatus status;
} ProtocolParseResult;

// 协议解析器句柄
typedef struct {
    ProtocolState state;
    uint8_t buffer[PROTOCOL_BUFFER_SIZE];
    uint16_t index;
    uint8_t checksum;
    uint8_t expected_length;
    
    void (*on_frame_received)(uint8_t *data, uint16_t length);
} ProtocolParser_HandleTypeDef;

// 函数声明
void ProtocolParser_Init(void);
ProtocolParseResult ProtocolParser_Parse(uint8_t *data, uint16_t length);
uint16_t ProtocolParser_BuildFrame(uint8_t *data, uint8_t length, uint8_t *frame);
void ProtocolParser_Reset(void);
void ProtocolParser_SetCallback(void (*callback)(uint8_t *, uint16_t));

#endif /* __PROTOCOL_PARSER_H */

六、示例应用

6.1 数据转发应用

c 复制代码
// app_forward.c
#include "app_forward.h"
#include "uart_dma.h"
#include "protocol_parser.h"

// 私有变量
static ForwardApp_HandleTypeDef hforward;

// 初始化转发应用
void ForwardApp_Init(UART_DMA_HandleTypeDef *huart1, UART_DMA_HandleTypeDef *huart2)
{
    memset(&hforward, 0, sizeof(ForwardApp_HandleTypeDef));
    
    hforward.huart1 = huart1;
    hforward.huart2 = huart2;
    hforward.enabled = true;
    
    // 初始化协议解析器
    ProtocolParser_Init();
    ProtocolParser_SetCallback(ForwardApp_OnFrameReceived);
}

// 处理接收到的数据
void ForwardApp_Process(void)
{
    if (!hforward.enabled) {
        return;
    }
    
    // 从UART1获取数据包
    UART_DMA_PacketTypeDef packet;
    if (UART_DMA_GetPacket(hforward.huart1, &packet)) {
        // 解析协议
        ProtocolParseResult result = ProtocolParser_Parse(packet.data, packet.length);
        
        if (result.status == PROTOCOL_STATUS_OK) {
            // 协议解析成功,转发到UART2
            UART_DMA_SendData(hforward.huart2, result.data, result.length);
            
            // 统计
            hforward.forward_count++;
        } else {
            // 协议解析失败,直接转发原始数据
            UART_DMA_SendData(hforward.huart2, packet.data, packet.length);
            
            // 统计错误
            hforward.error_count++;
        }
    }
}

// 帧接收回调
void ForwardApp_OnFrameReceived(uint8_t *data, uint16_t length)
{
    // 可以在这里添加特殊的帧处理逻辑
    // 例如:心跳包响应、命令执行等
    
    // 检查是否是心跳包
    if (length == 4 && data[0] == 0xAA && data[1] == 0x55 && data[2] == 0x01 && data[3] == 0x01) {
        // 心跳包,发送响应
        uint8_t response[] = {0xAA, 0x55, 0x01, 0x02, 0x00};  // 校验和需要计算
        response[4] = response[2] ^ response[3];  // 计算校验和
        
        UART_DMA_SendData(hforward.huart1, response, sizeof(response));
    }
}

// 获取统计信息
ForwardApp_Stats ForwardApp_GetStats(void)
{
    ForwardApp_Stats stats;
    
    stats.forward_count = hforward.forward_count;
    stats.error_count = hforward.error_count;
    stats.rx_bytes = UART_DMA_GetRxByteCount(hforward.huart1);
    stats.tx_bytes = hforward.tx_bytes;  // 需要自己统计发送字节数
    
    return stats;
}

// 启用/禁用转发
void ForwardApp_SetEnabled(bool enabled)
{
    hforward.enabled = enabled;
}

七、错误处理和调试

7.1 调试信息输出

c 复制代码
// debug_utils.c
#include "debug_utils.h"
#include "uart_dma.h"

// 发送调试信息
void Debug_SendInfo(const char *format, ...)
{
    char buffer[256];
    va_list args;
    
    va_start(args, format);
    int len = vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);
    
    if (len > 0) {
        UART_DMA_SendData(&huart1_dma, (uint8_t*)buffer, len);
    }
}

// 十六进制转储
void Debug_HexDump(const char *label, uint8_t *data, uint16_t length)
{
    char buffer[64];
    char hex[16];
    
    Debug_SendInfo("\r\n=== %s (%d bytes) ===\r\n", label, length);
    
    for (uint16_t i = 0; i < length; i += 16) {
        // 地址
        snprintf(buffer, sizeof(buffer), "%04X: ", i);
        
        // 十六进制
        for (uint8_t j = 0; j < 16; j++) {
            if (i + j < length) {
                snprintf(hex, sizeof(hex), "%02X ", data[i + j]);
                strcat(buffer, hex);
            } else {
                strcat(buffer, "   ");
            }
            
            if (j == 7) {
                strcat(buffer, " ");
            }
        }
        
        strcat(buffer, " ");
        
        // ASCII
        for (uint8_t j = 0; j < 16; j++) {
            if (i + j < length) {
                uint8_t c = data[i + j];
                if (c >= 32 && c <= 126) {
                    snprintf(hex, sizeof(hex), "%c", c);
                } else {
                    snprintf(hex, sizeof(hex), ".");
                }
                strcat(buffer, hex);
            } else {
                strcat(buffer, " ");
            }
        }
        
        Debug_SendInfo("%s\r\n", buffer);
    }
    
    Debug_SendInfo("===================\r\n");
}

// 发送状态信息
void Debug_SendStatus(UART_DMA_HandleTypeDef *huart_dma)
{
    Debug_SendInfo("\r\n=== UART DMA Status ===\r\n");
    Debug_SendInfo("State: %s\r\n", 
                   huart_dma->state == UART_DMA_STATE_READY ? "Ready" :
                   huart_dma->state == UART_DMA_STATE_BUSY ? "Busy" :
                   huart_dma->state == UART_DMA_STATE_ERROR ? "Error" : "Unknown");
    Debug_SendInfo("Active Buffer: %d\r\n", huart_dma->active_buffer);
    Debug_SendInfo("DMA Receiving: %s\r\n", huart_dma->dma_receiving ? "Yes" : "No");
    Debug_SendInfo("Packet Count: %d\r\n", huart_dma->packet_count);
    Debug_SendInfo("RX Bytes: %lu\r\n", huart_dma->rx_byte_count);
    Debug_SendInfo("RX Packets: %lu\r\n", huart_dma->rx_packet_count);
    Debug_SendInfo("Error Count: %lu\r\n", huart_dma->error_count);
    Debug_SendInfo("=======================\r\n");
}

参考代码 串口通信+DMA接收不定长数据 www.youwenfan.com/contentcsv/71862.html

八、使用示例

8.1 完整应用示例

c 复制代码
// main_example.c
#include "main.h"
#include "uart_dma.h"
#include "protocol_parser.h"
#include "debug_utils.h"

// 全局句柄
UART_DMA_HandleTypeDef huart1_dma;
UART_DMA_HandleTypeDef huart2_dma;

// 自定义接收回调
void OnDataReceived(UART_DMA_PacketTypeDef *packet)
{
    // 发送接收到的数据到调试串口
    Debug_HexDump("Received Data", packet->data, packet->length);
    
    // 解析协议
    ProtocolParseResult result = ProtocolParser_Parse(packet->data, packet->length);
    
    if (result.status == PROTOCOL_STATUS_OK) {
        Debug_SendInfo("Valid protocol frame received (%d bytes)\r\n", result.length);
        
        // 处理协议帧
        ProcessProtocolFrame(result.data, result.length);
    } else {
        Debug_SendInfo("Invalid frame (status: %d)\r\n", result.status);
    }
}

// 处理协议帧
void ProcessProtocolFrame(uint8_t *data, uint16_t length)
{
    if (length < 4) {
        return;
    }
    
    uint8_t command = data[3];  // 假设第4个字节是命令
    
    switch (command) {
        case 0x01:  // 读取状态
            SendStatusResponse();
            break;
            
        case 0x02:  // 设置参数
            ProcessSetCommand(data, length);
            break;
            
        case 0x03:  // 读取数据
            SendDataResponse();
            break;
            
        default:
            SendErrorResponse(0x01);  // 无效命令
            break;
    }
}

// 发送状态响应
void SendStatusResponse(void)
{
    uint8_t response[32];
    uint16_t length = 0;
    
    // 构建响应帧
    response[length++] = 0xAA;  // 帧头1
    response[length++] = 0x55;  // 帧头2
    response[length++] = 0x10;  // 数据长度
    response[length++] = 0x81;  // 命令响应
    
    // 状态数据
    response[length++] = 0x01;  // 设备状态
    response[length++] = 0x00;  // 错误代码
    response[length++] = 0x00;  // 保留
    response[length++] = 0x00;  // 保留
    
    // 统计信息
    uint32_t rx_count = UART_DMA_GetRxByteCount(&huart1_dma);
    response[length++] = (rx_count >> 24) & 0xFF;
    response[length++] = (rx_count >> 16) & 0xFF;
    response[length++] = (rx_count >> 8) & 0xFF;
    response[length++] = rx_count & 0xFF;
    
    uint32_t packet_count = UART_DMA_GetRxPacketCount(&huart1_dma);
    response[length++] = (packet_count >> 24) & 0xFF;
    response[length++] = (packet_count >> 16) & 0xFF;
    response[length++] = (packet_count >> 8) & 0xFF;
    response[length++] = packet_count & 0xFF;
    
    uint32_t error_count = UART_DMA_GetErrorCount(&huart1_dma);
    response[length++] = (error_count >> 24) & 0xFF;
    response[length++] = (error_count >> 16) & 0xFF;
    response[length++] = (error_count >> 8) & 0xFF;
    response[length++] = error_count & 0xFF;
    
    // 计算校验和
    uint8_t checksum = 0;
    for (uint16_t i = 2; i < length; i++) {  // 从长度字节开始
        checksum ^= response[i];
    }
    response[length++] = checksum;
    
    // 发送响应
    UART_DMA_SendData(&huart1_dma, response, length);
}

int main(void)
{
    // 初始化
    HAL_Init();
    SystemClock_Config();
    
    // 初始化外设
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_USART1_UART_Init();
    MX_USART2_UART_Init();
    
    // 初始化串口DMA
    UART_DMA_Init(&huart1_dma, &huart1, &hdma_usart1_rx);
    UART_DMA_Init(&huart2_dma, &huart2, &hdma_usart2_rx);
    
    // 设置回调
    huart1_dma.on_receive_complete = OnDataReceived;
    
    // 启动接收
    UART_DMA_StartReceive(&huart1_dma);
    UART_DMA_StartReceive(&huart2_dma);
    
    // 发送启动信息
    Debug_SendInfo("System Started\r\n");
    Debug_SendStatus(&huart1_dma);
    
    while (1) {
        // 主循环任务
        
        // 1. 超时检测
        UART_DMA_TimeoutCheck(&huart1_dma);
        UART_DMA_TimeoutCheck(&huart2_dma);
        
        // 2. 处理接收到的数据
        UART_DMA_ProcessData(&huart1_dma);
        UART_DMA_ProcessData(&huart2_dma);
        
        // 3. 定期发送状态
        static uint32_t last_status_time = 0;
        if (HAL_GetTick() - last_status_time > 5000) {  // 每5秒
            SendStatusResponse();
            last_status_time = HAL_GetTick();
        }
        
        // 4. 其他任务
        // ...
        
        HAL_Delay(1);
    }
}

九、注意事项

  1. DMA缓冲区大小:根据实际数据量和系统内存调整缓冲区大小
  2. 中断优先级:合理配置串口中断和DMA中断的优先级
  3. 数据竞争:在多任务环境下注意数据访问的互斥保护
  4. 错误处理:完善各种错误情况的处理机制
  5. 性能优化:根据实际需求调整DMA传输模式

这个实现方案提供了完整的串口DMA接收不定长数据的功能,包括:

  • 双缓冲机制防止数据丢失
  • 空闲中断检测帧结束
  • 环形缓冲区管理
  • 协议解析支持
  • 错误处理和调试功能
  • 统计信息记录
相关推荐
yuan199976 小时前
STM32直流无刷电机六拍方波控制器程序
stm32·单片机·嵌入式硬件
番茄灭世神7 小时前
PN学堂GD32教程第21篇——WiFiIOT
c语言·stm32·单片机·嵌入式·gd32
不怕犯错,就怕不做8 小时前
ARM设备异常断电容易造成数据损坏,硬件如何设计
linux·驱动开发·嵌入式硬件
jghhh019 小时前
基于DSP28335的RS485串口通信与AD采样开发方案
单片机·嵌入式硬件
say_fall9 小时前
微处理器及其体系结构:从8088到现代多核处理器
单片机·硬件架构·硬件工程
2301_775602389 小时前
晶振相关知识
单片机
2zcode10 小时前
基于STM32的直流电机串级PID伺服控制系统设计与实现
stm32·单片机·嵌入式硬件·直流电机
都在酒里10 小时前
STM32低功耗休眠详解——睡眠、停止与待机模式实战,综合应用(三)
stm32·单片机·嵌入式硬件
嵌入式小站10 小时前
STM32 零基础可移植教程 06:外部中断按键,不用一直在 while 里盯着它
stm32·单片机·嵌入式硬件