STM32单片机串口数据接收处理框架

STM32单片机串口数据接收处理框架整理

在使用STM32单片机串口接收不固定长度数据包时,采用了每个字节中断,然后接收存储的方式,前面一直会有错误,尤其是波特率比较高(可能相对于该款单片机的主频,其他中断任务过多),甚至到后面没法接收到数据的情况。所以,修改整理了串口处理流程如下,以备后面需要时套用框架:

1 初始化

...
	MX_USART4_UART_Init();
	if (huart4.gState == HAL_UART_STATE_READY)HAL_UART_Receive_IT(&huart4, &rec_ch_uart4,1);
	_cdcCmd.rawVal=0;
...
static void MX_USART4_UART_Init(void)
{
  huart4.Instance = USART4;
  huart4.Init.BaudRate = 57600; 
  huart4.Init.WordLength = UART_WORDLENGTH_8B;
  huart4.Init.StopBits = UART_STOPBITS_1;
  huart4.Init.Parity = UART_PARITY_NONE;
  huart4.Init.Mode = UART_MODE_TX_RX;
  huart4.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart4.Init.OverSampling = UART_OVERSAMPLING_16;
  huart4.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart4.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  huart4.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart4) != HAL_OK)
  {
    Error_Handler();
  }

}

MX_USART4_UART_Init(void)是设置后参数后,CubeMX自动生成的

2 错误处理中断回调函数重载

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{

    if (huart->Instance == USART4) {  
    	if (huart->ErrorCode & HAL_UART_ERROR_ORE) {
    	        // 处理溢出错误
    	        __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_OREF);
    	    }
    	    if (huart->ErrorCode & HAL_UART_ERROR_FE) {
    	        // 处理帧错误
    	        __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_FEF);
    	    }
    	HAL_UART_AbortReceive(&huart4); // 重启接收
        HAL_UART_Receive_IT(&huart4, &rec_ch_uart4,1);
    }
}

3 接收中断回调重载

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
	if(huart->Instance == USART4){
		if(rec_counter_uart4<MAX_LEN_USART4){
			Uart4_RxBuffer[rec_counter_uart4]=rec_ch_uart4;
			rec_counter_uart4++;
			usartIdleCounter=UsartIdleMax; 
		}
		if (huart4.gState == HAL_UART_STATE_READY) HAL_UART_Receive_IT(&huart4, &rec_ch_uart4,1);
	}
}

保存收到的字节到缓存区,重启接收(1字节中断)
usartIdleCounter计数变成10(或20)ms

4 ms中断处理(systick)

void _embed_ms_loop()
{
	msCounter++;
	if(msCounter>=1000)
	{
		if(TTL)
		{
			_cdcCmd.bits.ReportStateCmd=1;
			TTL--;
		}else{
			TTL=20;  //Only for debug
		}
		msCounter=0;
	}
	if(usartIdleCounter)
	{
		usartIdleCounter--;
	}else{
		_cdcCmd.bits.PackageReady=1;
	}
}

如果usartIdleCounter减到0,说明有段时间没有收到字节了,可以处理缓存区里的包

5 主循环

void baidu_interface_embed_main_loop()
{
	if(_cdcCmd.bits.PackageReady)
	{
		parseCDCPackage();
		_cdcCmd.bits.PackageReady=0;
	}
	if(_cdcCmd.bits.SetExtrasCmd)
	{
		SetSurcharge();
		_cdcCmd.bits.SetExtrasCmd=0;
	}
	if(_cdcCmd.bits.ReportStateCmd)
	{
		toCDC_reportState();
		_cdcCmd.bits.ReportStateCmd=0;
	}
	if(_cdcCmd.bits.SimpleRspCmd)
	{
		Respond2Cmd(RspCmd,RspErrCode);
		_cdcCmd.bits.SimpleRspCmd=0;
	}
	if(_cdcCmd.bits.SetRtcCmd)
	{
		rtc_set();
		_cdcCmd.bits.SetRtcCmd=0;
	}

}

所有任务主体都在主循环里处理,中断里面只是做些命令置位、清零、缓存、计数等动作以避免长时间在中断内执行。

解析包子函数

void parseCDCPackage()  
{
	uint8_t packageLen;
	if(	rec_counter_uart4>6)  //7 is the least length of a package
	{
		if((Uart4_RxBuffer[0]==0xFF) && (Uart4_RxBuffer[1]==0xCD)) //Correct Header and device
		{
			packageLen=Uart4_RxBuffer[3]+7;
			if(rec_counter_uart4>=packageLen)// enough length.
			{
					//copy package
					memcpy(packageBuff,(unsigned char*)(&Uart4_RxBuffer[2]),Uart4_RxBuffer[3]+2);
					//check CRC and tail byte
					uint8_t ucReadCRC_L=Uart4_RxBuffer[Uart4_RxBuffer[3]+4];
					uint8_t ucReadCRC_H=Uart4_RxBuffer[Uart4_RxBuffer[3]+5];
					uint16_t ReadCRC = ((uint16_t)ucReadCRC_H<< 8)+ ucReadCRC_L;
					uint16_t calc_CRC=Get_CRC(packageBuff,Uart4_RxBuffer[3]+2);
					if(calc_CRC==ReadCRC && Uart4_RxBuffer[Uart4_RxBuffer[3]+6]==0xEF)
					{
						UseCOMData(packageBuff,Uart4_RxBuffer[3]+2);//apply the command;
					}
			}
		}
		rec_counter_uart4=0; //clear buffer
	}
	else
	{
		HAL_UART_AbortReceive(&huart4); // 重启接收
		HAL_UART_DeInit(&huart4);
		HAL_UART_Init(&huart4);
		rec_counter_uart4=0; //clear buffer
		HAL_UART_Receive_IT(&huart4, &rec_ch_uart4,1);
	}
	usartIdleCounter=250;
}

解析完一个包后,把usartIdleCounter设置为大些(根据需要),那么长时间接收不到包,也会重启串口模块。

DMA改进

📍方法 1:DMA + 串口空闲中断 (IDLE Line Interrupt) - 最常用

原理:

  • 使能 USART/UART 的空闲中断 (IDLE Line Detection)。当 RX 线持续空闲(高电平)超过 1 个完整字符传输时间(包括停止位)后,硬件会触发空闲中断。
  • 配置 DMA 在循环模式 (Circular Mode) 下接收数据到一个足够大的缓冲区。
  • DMA 会在后台持续接收数据到缓冲区,覆盖旧数据(循环缓冲)。
  • 当一帧数据接收完毕,RX 线进入空闲状态,触发空闲中断。
  • 在空闲中断服务程序 (ISR) 中:
  • 计算本次接收到的数据长度:Length = BufferSize -DMA_GetRemainingDataCount(DMA_Stream);
  • 处理接收到的完整一帧数据(复制到其他缓冲区、设置标志位通知主循环等)。
  • 清除空闲中断标志位。

优点:

  • 高效: CPU 参与度极低,仅在帧结束时处理一次中断。
  • 实时性好: 帧结束检测及时。
  • 硬件支持: 利用 STM32 内置硬件特性,实现简单可靠。
  • 非常适合不定长帧: 不依赖特定帧头帧尾或超时。

缺点:

  • 需要芯片的 USART/UART 支持空闲中断检测(绝大多数 STM32 都支持)。
  • 需要足够大的 DMA 缓冲区以防止在高波特率或密集数据时被覆盖。
  • 连续快速发送帧时,如果处理不及时,缓冲区可能会被覆盖(需合理设计缓冲区大小和处理速度)。

实现步骤 (以 HAL 库为例):

// 1. 定义足够大的循环缓冲区
#define RX_BUFFER_SIZE 256
uint8_t RxBuffer[RX_BUFFER_SIZE];

// 2. 初始化 UART 和 DMA (循环模式)
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;

// ... (配置 UART 参数: 波特率, 数据位, 停止位, 校验位等)
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(); }

// ... (配置 DMA 通道和流/通道,关联到 UART RX)
__HAL_RCC_DMA1_CLK_ENABLE(); // 根据实际 DMA 和 UART 使能时钟
hdma_usart1_rx.Instance = DMA1_Stream5; // 根据芯片手册选择正确的 Stream/Channel
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_MEDIUM;
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK) { Error_Handler(); }
__HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx); // 关联 DMA 到 UART

// 3. 使能 UART 的空闲中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使用 HAL 库的宏

// 4. 启动 DMA 接收
HAL_UART_Receive_DMA(&huart1, RxBuffer, RX_BUFFER_SIZE);

// 5. 编写空闲中断服务函数 (通常在 stm32..._it.c 中)
void USART1_IRQHandler(void) {
    HAL_UART_IRQHandler(&huart1); // 调用 HAL 的通用处理函数
    // 或者直接判断中断标志:
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET) {
        __HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除空闲中断标志!非常重要!

        // 计算接收到的数据长度
        uint16_t receivedLength = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);

        // 处理接收到的数据 (receivedLength 字节, 起始位置需要计算)
        // 注意:在循环缓冲区中,数据可能不是从 RxBuffer[0] 开始的!
        uint16_t dma_current_index = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
        uint16_t start_index = (dma_current_index - receivedLength) % RX_BUFFER_SIZE; // 计算起始索引
        // 处理从 RxBuffer[start_index] 开始的 receivedLength 字节数据...
        // 例如:复制到应用层缓冲区 (memcpy),设置标志通知主循环等。

        // 注意: DMA 会继续在循环缓冲区中接收,无需重启 DMA
    }
}

关键点:

  • 清除空闲中断标志 (__HAL_UART_CLEAR_IDLEFLAG()) 是必须的。
  • 计算 receivedLength 和确定数据在循环缓冲区中的起始位置 (start_index) 是核心。
  • 使用 % RX_BUFFER_SIZE 处理循环缓冲区的环绕。

📍 方法 2:DMA + 接收超时中断 (Receiver Timeout Interrupt - RTO) - (较新系列如 G0, G4, L4+, H5, H7)

原理:

  • 使能 USART 的接收超时中断 (RTO)。RTO 在连续两个字符的起始位之间的时间间隔超过可编程阈值时触发。
  • 配置 DMA 在普通模式 (Normal Mode) 或循环模式下接收。
  • 当帧内字符间隔超过设定时间,触发 RTO 中断。
  • 在 RTO ISR 中计算接收到的数据长度并处理帧(类似 IDLE 中断)。
  • 如果是普通模式 DMA,在 RTO ISR 中需要重新启动 DMA 接收。

优点:

  • 比 IDLE 更灵活: 超时时间可编程,适应不同协议。
  • 避免噪声误触发: IDLE 要求总线严格高电平,RTO 只关心字符间隔。
  • 同样高效。

缺点:

  • 仅支持较新的 STM32 系列 (G0, G4, L4+, H5, H7 等)。
  • 需要配置超时时间 (RTO 寄存器)。
  • 普通模式 DMA 需要重启。

实现要点:

// 启用 RTO 中断
SET_BIT(huart->Instance->CR2, USART_CR2_RTOEN); // 使能 RTO
SET_BIT(huart->Instance->CR1, USART_CR1_RTOIE); // 使能 RTO 中断
// 设置 RTO 值 (例如 3.5 个字符时间: 3.5 * (1 + 8 + 1) / BaudRate)
uint32_t rto_value = ...; // 根据波特率和所需超时计算
MODIFY_REG(huart->Instance->RTOR, USART_RTOR_RTO, rto_value);

// RTO 中断服务程序 (USARTx_IRQHandler) 中:
if (__HAL_UART_GET_FLAG(huart, UART_FLAG_RTOF) != RESET) {
    __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_RTOF);
    // 计算接收长度 (如果是普通模式,长度就是设定的DMA大小减去剩余计数;循环模式计算同IDLE)
    uint16_t receivedLength = ...;
    // 处理数据...
    if (huart->hdmarx->Init.Mode == DMA_NORMAL) {
        HAL_UART_Receive_DMA(huart, RxBuffer, RX_BUFFER_SIZE); // 重启 DMA
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code .

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值