stm32串口接收不定长数据包使用DMA最好的方式

stm32串口接收不定长数据包, 使用DMA是最好的方式

摘要:
本文介绍了STM32使用DMA双缓冲接收串口不定长数据包的实现方法。通过配置两个缓冲区交替接收,配合空闲中断处理机制,有效解决了传统串口中断接收可能造成的数据丢失问题。具体实现包括DMA缓冲区切换、数据包解析(以0x48/0x54开头、0x2F结尾为标志),以及CRC校验处理。文中提供了完整的数据包结构定义和DMA回调处理流程,确保了数据接收的可靠性和高效性。相比传统中断接收方式,该方法显著提升了数据吞吐量和系统稳定性。

使用串口中断进行逐个字符的接收, 会产生一个bug, 有可能会因为处理的太慢了,导致紧跟着的串口发送过来的字符丢失. 而使用DMA加双缓冲基本上可以解决命令字符丢失的问题.

int main(void)
{ 
  HAL_Init(); 
  SystemClock_Config(); 
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART2_UART_Init();
  MX_USART1_UART_Init();
  MX_CAN_Init();
  MX_USART3_UART_Init(); 
  
       RS422_Init(); //这里进行调用启动DMA
       
       while (1)
  	  {  
         HAL_Delay(200);
      }
  }
#include <stdint.h>
#include <string.h>
#include "main.h"
#include "rs422_interface.h"
#include "JetRunInfo.h"
#include <usart.h>

// DMA双缓冲配置
#define BUFFER_SIZE 64
uint8_t rx_buffer1[BUFFER_SIZE];
uint8_t rx_buffer2[BUFFER_SIZE];
volatile uint8_t active_buffer = 1; // 当前活动缓冲区
volatile uint8_t process_flag = 0;

 // 定义数据包结构
 typedef struct {
     uint8_t data[Circular_Queue_Buffer_SIZE]; // 存储队列元素的数组
     char head_char;
     char end_char;
     int length;
 } DataPacket;

// 添加状态变量
typedef enum {
    WAIT_FOR_HEADER,
    COLLECT_DATA
} PacketState;

static PacketState packetState = WAIT_FOR_HEADER;
static uint8_t  packetBuffer[60]; // 临时存储完整数据包
static uint16_t packetLength = 0;  // 当前数据包长度
 
#include "usart.h"
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <CircularQueue.h>
#include "can_cmd.h"

#include <stdint.h>
#include <stdio.h>
 

#pragma pack(push, 1) // 禁用内存对齐
typedef struct {
    // 帧头与状态
    uint8_t header1;     // 字节0:固定0xFF
    uint8_t header2;     // 字节1:预留
    uint8_t status;      // 字节2:运行状态(0-7)
    uint8_t error;       // 字节3:错误码(0-12)

    // 设备标识
    uint32_t id;         // 字节4-7:设备ID

    // 转速控制
    uint32_t set_rpm;    // 字节8-11:设置转速(转/分)

    // 运行参数
    uint32_t actual_rpm; // 字节12-15:实际转速(转/分)
    float exhaust_temp;  // 字节16-19:排气温度(℃)
    uint32_t run_time;   // 字节20-23:运行时间(秒)
    float bat_voltage;   // 字节24-27:电池电压(V)
    float bat_current;   // 字节28-31:电池电流(A)
    float pump_voltage;  // 字节32-35:油泵电压(V)
    float atmos_pressure;// 字节36-39:大气气压(Pa)

    // 油泵与发电
    uint16_t pump_rpm;    // 字节40-41:油泵转速(转/分)
    uint16_t fuel_consump;// 字节42-43:油耗(L/分钟)
    float gen_voltage;   // 字节44-47:发电电压(V)
    float gen_power;     // 字节48-51:发电功率(W)

    // 校验
    uint8_t crc;         // 字节52:CRC8校验
} RunInfoCommModel;
#pragma pack(pop) // 恢复默认内存对齐
 
            
void process_rs422cmd(DataPacket *rs422_packet );
uint8_t GetCRC8(const uint8_t *data, uint16_t len);

RunInfoCommModel var_runinfo;

uint8_t runinfo_buffer[sizeof(RunInfoCommModel)];

CircularQueue uartQueue;
                              
DataPacket  rs422_packet;
uint8_t rs422_rxData;

#define RS422BUFFER1SIZE  60
uint8_t rs422_buffer_1[RS422BUFFER1SIZE] = {0};//1级缓存.

extern JetRunInfo jetRunInfo;
extern DMA_HandleTypeDef hdma_usart2_rx;

void RS422_Init(void){
    
  CircularQueue_Init(&uartQueue, 300);

  __HAL_LINKDMA(&huart2, hdmarx, hdma_usart2_rx);
  __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
  // 使用DMA双缓冲启动接收
  HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buffer1, BUFFER_SIZE);
  active_buffer = 1;
    
}

DataPacket rs422_packet_tmp;

// DMA接收完成回调
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
    if (huart->Instance == USART2) 
    {
        // 停止当前DMA传输
        HAL_UART_DMAStop(huart);
        
        // 验证数据长度
        if (Size == 0 || Size > BUFFER_SIZE) {
            Size = BUFFER_SIZE;
        }
        
        uint8_t* processed_data;
        HAL_StatusTypeDef dma_status;
         
        // 处理当前缓冲区数据
        processed_data = (active_buffer == 1) ? rx_buffer1 : rx_buffer2;
        
        // 启动另一个缓冲区的接收
        if (active_buffer == 1) {
            dma_status = HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buffer2, BUFFER_SIZE);
            active_buffer = 2;
        } else {
            dma_status = HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buffer1, BUFFER_SIZE);
            active_buffer = 1;
        }
        
  
        // 只处理完整数据包(0x48/0x54开头,0x2F结尾)
        for (int i = 0; i < Size; ) {
            // 查找有效数据包头
            if (processed_data[i] == 0x48 || processed_data[i] == 0x54) {
                
                rs422_packet_tmp.length = 0;
                
                // 查找数据包尾
                for (int j = i ; j < Size; j++) 
                {
                    rs422_packet_tmp.data[rs422_packet_tmp.length] = processed_data[j];
                    rs422_packet_tmp.length++;
                    
                    if (processed_data[j] == 0x2F) 
                    {
                        // 存入完整数据包到队列
//                        for (int k = i; k <= j; k++) 
//                        {
//                            //CircularQueue_Enqueue(&uartQueue, &processed_data[k]);
//                            rs422_packet_tmp.data[rs422_packet_tmp.length] = processed_data[k];
//                            rs422_packet_tmp.length++;
//                        }
                        process_rs422cmd(&rs422_packet_tmp);
                        
                        //uint8_t msg[] ="process_rs422cmd()";
                        //HAL_UART_Transmit(&huart2,msg,sizeof(msg),10);
                        i = j ; // 跳过已处理数据
                        break;
                    }
                }
            } else {
                i++; // 跳过无效数据
            }
        }
        
        // 检查DMA启动状态
        if (dma_status != HAL_OK) {
            Error_Handler();
        }
        
        // 处理队列中的完整数据包
       
    }
}
 

// 错误处理回调
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART2)
    {
        // 获取并处理错误状态
        uint32_t error_code = huart->ErrorCode;
        
        // 错误计数器
        static struct {
            uint32_t pe;   // 奇偶校验错误
            uint32_t ne;   // 噪声错误
            uint32_t fe;   // 帧错误
            uint32_t ore;  // 溢出错误
            uint32_t dma;  // DMA错误
        } error_counts = {0};
        
        // 诊断错误类型
        if(error_code & HAL_UART_ERROR_PE) {
            error_counts.pe++;
            // 奇偶校验错误
        }
        if(error_code & HAL_UART_ERROR_NE) {
            error_counts.ne++;
            // 噪声错误
        }
        if(error_code & HAL_UART_ERROR_FE) {
            error_counts.fe++;
            // 帧错误(可能是波特率不匹配)
        }
        if(error_code & HAL_UART_ERROR_ORE) {
            error_counts.ore++;
            // 溢出错误(处理速度跟不上接收速度)
        }
        if(error_code & HAL_UART_ERROR_DMA) {
            error_counts.dma++;
            // DMA传输错误
        }
        
        // 清除错误标志(兼容不同HAL版本)
        __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_PE | UART_FLAG_FE | UART_FLAG_NE | UART_FLAG_ORE);
        
        // 重新启动DMA接收
        //HAL_USART_ReceiveToIdle_DMA
        HAL_UARTEx_ReceiveToIdle_DMA(&huart2, rx_buffer1, BUFFER_SIZE);
                                   
        // 这里可以添加调试输出,例如通过串口打印错误信息
        // 或者设置断点查看error_counts的值
    }
}
void process_rs422cmd(DataPacket *rs422_packet )
{
 
     if (rs422_packet->data[0] == 0x45  )
     {
                if(rs422_packet->data[1] == 0x02 && rs422_packet->data[2] == 0x03 )
                {
                    //启动命令
                    //CAN_SendStartCMD();  // 发送ID为07D0的CAN消息 
                    printf(" CAN_SendStartCMD() ");
                }
                 
                else if ( rs422_packet->data[1] == 0x06  )
                {
                    //设置转速命令 
                        union {
                            uint8_t   b4[4] ;
                            uint32_t u32;
                            float f32;
                        } speed;
                        
                        //uint32_t speed = (rs422_packet->data[2]<<24) | (rs422_packet->data[3]<<16) | (rs422_packet->data[4]<<8) | rs422_packet->data[5];
                        speed.b4[3] =  rs422_packet->data[2];     // 0 和1 位其它用途, 后面4位才是转速数据
                        speed.b4[2] =  rs422_packet->data[3];
                        speed.b4[1] =  rs422_packet->data[4];
                        speed.b4[0] =  rs422_packet->data[5];
                     
                        CAN_SendSpeedCMD(speed.u32);  // 发送ID为07D0的CAN消息   
                }  else {
                    printf(" unknown command ");
                }
    }
     //0x54 开头的数据指令包是测试系列命令
    else  if (rs422_packet->data[0] == 0x54  )
    {
               //.......
    }
}
      
 


// RS422ReadData函数已完全移除,使用DMA双缓冲实现替代

void RS422_SendRunInfo(JetRunInfo *runInfo )
{ 
    var_runinfo.header1 = 0xFF;
    var_runinfo.header2 = 0x00;
    
    //var_runinfo.actual_rpm = 60000;
 
    var_runinfo.status = runInfo->engineStatus;      // 字节2:运行状态(0-7)
    var_runinfo.error = runInfo->engineError;       // 字节3:错误码(0-12)

//    // 设备标识
    var_runinfo.id = runInfo->aircraftId;         // 字节4-7:设备ID

//    // 转速控制
    var_runinfo.set_rpm = runInfo->setRPM;    // 字节8-11:设置转速(转/分)

//    // 运行信息
    var_runinfo.actual_rpm = runInfo->actualRPM; // 字节12-15:实际转速(转/分)
    var_runinfo.exhaust_temp = runInfo->exhaustTemp;  // 字节16-19:排气温度(℃)
    var_runinfo.run_time = runInfo->runTime;   // 字节20-23:运行时间(秒)
    var_runinfo.bat_voltage = runInfo->batteryVoltage;   // 字节24-27:电池电压(V)
    var_runinfo.bat_current = runInfo->batteryCurrent;   // 字节28-31:电池电流(A)
    var_runinfo.pump_voltage = runInfo->oilPumpVoltage;  // 字节32-35:油泵电压(V)
    var_runinfo.atmos_pressure = runInfo->atmosphericPressure;// 字节36-39:大气气压(Pa)

//    // 油泵与发电
    var_runinfo.pump_rpm = runInfo->oilPumpRPM;    // 字节40-41:油泵转速(转/分)
    var_runinfo.fuel_consump = runInfo->oilConsumption;// 字节42-43:油耗(L/分钟)
    var_runinfo.gen_voltage = runInfo->genVoltage;   // 字节44-47:发电电压(V)
    var_runinfo.gen_power = runInfo->genPower;     // 字节48-51:发电功率(W)
 
    
 
    var_runinfo.crc = GetCRC8((uint8_t*)&var_runinfo,sizeof(RunInfoCommModel)-1);;
 
    // struct JetRunInfo info; 
    // 安全复制结构体到字节数组
    memcpy(runinfo_buffer, &var_runinfo, sizeof(RunInfoCommModel));
 

    HAL_UART_Transmit(&huart2, (uint8_t*)&runinfo_buffer, sizeof(RunInfoCommModel), 1000);
}
void RS422_SendStartParam(void )
{

}
void RS422_SendRunParam(void )
{

}


uint8_t GetCRC8(const uint8_t *data, uint16_t len) {
    uint8_t crc = 0x00;  // 初始值
    for (uint16_t i = 0; i < len; i++) {
        crc ^= data[i];   // 将数据字节与当前CRC异或
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x80) {
                crc = (crc << 1) ^ 0x07;  // 多项式0x07 (x^8 + x^2 + x + 1)
            } else {
                crc <<= 1;
            }
        }
    }
    return crc ^ 0x55;  // 结果异或0x55
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值