STM32串口DMA高效接收不丢包

文章目录


前言

本文介绍使用STM32串口的DMA通道进行数据的接收,在常见的使用中,大多数人通常使用DMA一次性接收多个字节,该种用法最为简单粗暴,但是同时存在着很大的不稳定性,因为主机和从机之间上电的时间差会导致数据交互不同步,这样导致的一个结果就是从机DMA收到的数据为某一不确定时刻截取的一帧数据,这帧数据可能为两帧数据拆分合并而成的一帧数据,因此本文介绍一种DMA串口数据接收方法,每次DMA只传输一个字节数据,通过数据转存并判断等操作,进行串口数据的接收,可大大提高数据传输的稳定性。本文使用CubeMx进行配置。


实现方法

1.cubemx配置

 配置串口的DMA接收,不使能循环接收(可在软件重新使能接收,提高灵活性),一次数据传输大小选择Byte,cubemx配置DMA时自动使能DMA传输中断,在DMA触发传输中断时,会调用串口接收中断回调,因此无需使能串口接收中断。

2.实现过程

        一般完整的一帧串口数据通常包括:帧头+功能字+数据+帧尾,帧头和帧尾通常分别由2Byte组成,功能字由1Byte组成。那么具体的实现思路如下:

1、由于DMA配置为每次只0传输1Byte,传输完成后触发中断,因此我们需要每次在中断里都将该数据进行转存

2、当DMA传输完2Byte数据后,判断帧头是否正确,若帧头错误,不再接收后面的数据,并清空接收缓存数组,若帧头正确,继续接收后面的数据,直到接收到帧尾数据后,一帧数据接收完成,并返回一个状态值,表示一帧数据接收完成。

具体实现代码如下:

/*
* 版本:1.0
* 修改日期:2023.3.30
* 作者:PFA SWS
* 说明:此文件存放串口数据收发处理函数
*
* 通信协议:
* 完整数据包: 帧头+功能字+数据+帧尾
*
* 帧头: 0x0b 0x0c 
* 帧尾: 0x0d 0x0a
*
* 功率功能字: 0x0a
* 换挡功能字: 0x0b
* 等级功能字: 0x0c
*/

#include "comm.h"
#include <string.h>

/*串口数据接收初始化*/
void USART_Data_Rx_Init(USART_RX_TypeDef* p)
{
	p->rx_temp = 0;
	p->rxCnt = 0;
	memset(p->rxBuf, 0, RXBUFLEN);
}

/*串口协议初始化*/
void USART_Agreement_Init(USART_AGREEMENT_TypeDef* p)
{
	p->head1 = 0x0b;
	p->head2 = 0x0c;
	p->end1 = 0x0d;
	p->end2 = 0x0a;
}

/*串口数据接收*/
USART_RX_FLAG_Typedef USART_Data_RxState(USART_RX_TypeDef* p, USART_AGREEMENT_TypeDef* q)
{
	p->rxBuf[p->rxCnt++] = p->rx_temp;
	if(p->rxCnt >= 2)
	{
		if(p->rxBuf[0] !=  q->head1 || p->rxBuf[1] !=  q->head2)
		{
			return RX_ERROR;
		}
		if(p->rxBuf[p->rxCnt-1] == q->end2 && p->rxBuf[p->rxCnt-2] == q->end1)
		{
			p->rxCnt = 0;
			return RX_OK;
		}
	}
	return RX_BUSY;
}

.h文件如下

#ifndef __COMM_H
#define __COMM_H

#include <stdint.h>

#define RXBUFLEN 12

typedef enum
{
	RX_OK 		= 0x00U,
	RX_ERROR	= 0x01U,
	RX_BUSY		= 0x02U
}USART_RX_FLAG_Typedef;

/*串口数据接收*/
typedef struct
{
	uint8_t rx_temp;
	uint8_t rxCnt;
	uint8_t rxBuf[RXBUFLEN];
}USART_RX_TypeDef;

/*串口协议*/
typedef struct
{
	uint8_t head1;
	uint8_t head2;
	uint8_t end1;
	uint8_t end2;
}USART_AGREEMENT_TypeDef;


void USART_Data_Rx_Init(USART_RX_TypeDef*);
void USART_Agreement_Init(USART_AGREEMENT_TypeDef*);
USART_RX_FLAG_Typedef USART_Data_RxState(USART_RX_TypeDef*, USART_AGREEMENT_TypeDef*);


#endif

通过上面代码,我们就可以实现稳定且快速的串口数据接收了,最后我们在中断回调里进行各种功能字判断,就可以得到想要的数据了

中断回调代码如下:

/*串口接收中断回调*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART3)
	{
		static USART_RX_FLAG_Typedef rx_state;
		rx_state = USART_Data_RxState(&usart_rx, &agreement);
		
		/*帧头错误,数据不更新*/
		if(rx_state == RX_ERROR)
		{
			usart_rx.rxCnt = 0;
			//主机重新发送
		}
		
		/*一帧数据接收完成*/
		else if(rx_state == RX_OK)
		{
			/*功率功能字*/
			if(usart_rx.rxBuf[2] == 0x0a)
			{
				/*功率解析*/
				power_chassis = ((uint32_t)usart_rx.rxBuf[3])  |
										 ((uint32_t)usart_rx.rxBuf[4]<<8)  |
										 ((uint32_t)usart_rx.rxBuf[5]<<16) |
										 ((uint32_t)usart_rx.rxBuf[6]<<24) ;
				power_real = (float)power_chassis/1000;
			}
			
			/*换挡功能字*/
			else if(usart_rx.rxBuf[2] == 0x0b)
			{
				gear = usart_rx.rxBuf[3];
			}
			
			/*等级功能字*/
			else if(usart_rx.rxBuf[2] == 0x0c)
			{
				switch(usart_rx.rxBuf[3])
				{
					case 0x01: power_limit = 50.0f; break;
					case 0x02: power_limit = 80.0f; break;
					case 0x03: power_limit = 100.0f; break;
				}
			}
		}
		
		/*重新打开串口DMA接收,DMA配置为不连续模式*/
		start_usart_rx(&huart3, &usart_rx.rx_temp, 1);
	}
}

总结

上述代码经笔者实测,稳定性能极高,目前为止还未出现丢包情况,特别适用于通信环境复杂的情况下,且通过严谨的数据接收处理可大大提高MCU的工作效率。

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值