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
}

241

被折叠的 条评论
为什么被折叠?



