文章目录
前言
本文介绍使用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的工作效率。