[--------------点击下面,进入总目录索引--------------]
STM32系列精品Demo集 - 总目录
一、UART接收方案汇总
补充说明:
上述接收方案中有协议帧,本协议帧指的是有帧头帧尾这种协议,不包含类似AT指令这种协议方案
二、UART丢包问题分析
2.1 、裸跑机制
裸跑时上述UART接收方案中的方法一 、二分析
- 主任务能够及时轮询处理,fifo空间写入与读取能够满足,都不会丢包。
- 两个接口在主任务不能及时轮询处理,fifo被中断写满,都会导致丢包:
2.1. 上述UART接收方案中的情形一 :存在单字节数据丢失,导致ffifo中后续的数据已经不是完整帧,全部错乱。(!!所以不建议采用这种方式!!)
2.2. 上述UART接收方案中的情形二 :存在丢整帧数据,但是如果丢也是整帧数据,不会扰乱fifo中其它的完整帧。
裸机情况下,如果主任务其它工作耗时特别大,等轮询到读取fifo并处理时,fifo早都被uart中断填满,丢掉了好多数据,所以裸机跑应该注意一下几点:
- 尽可能代码优化,减少耗时,这样轮询时间就快一点读取到fifo,不至于fifo满掉。
- 可以把fifo空间调大,以匹配该项目的时间轮序机制。
裸机情况下如何尽可能保证不丢包?
说明:为何说尽可能,因为fifo不是无限大的,定义大了,很有可能栈错误。所以fifo大小也是有限的,而如果主任务不能及时读取fifo,数据又不断的发过来填充到fifo,必然导致丢数据。
-
多个fifo实现乒乓操作或者把一个fifo缓冲区调节的足够大**,
其目的是让fifo缓冲尽可能大
**:
1.1. 一个fifo缓冲区调节的足够大,大到满足项目需求。
1.2. 多个fifo实现乒乓操作,以满足项目需求(有弊端,主任务不能把控先写入fifo的先处理)。 -
DMA+空闲中断+fifo的环形缓冲。 (如果系统资源够,那就把dma用上无妨)
目的:让cpu尽可能高效率的执行,减少时间开销能尽快的轮询到uart读取fifo,这样就可以让fifo内存重复利用不至于填满。
2.2 、跑OS分析
- os下,单个线程中读取fifo处理,问题不大,数据能及时的从fifo读出,使得fifo内存一直有可用空间,不至于fifo填满,导致数据丢包。
三、UART DEMO示例
3.1 、定长数据接收(无所谓有无协议)
采用方案:UART接收方案->数据定长->情形一->方法一
定长数据接收:无所谓有无协议都可以用下述示例去接收,如果有协议当然亦可以按照协议去解析完整包。
/***********************************************************************
* 函数名称: CreatRingBuffer
* 功能描述: 创建环形缓冲FIFO
* 输入参数:
* 输出参数:
* 返 回 值:
* 其 它:
***********************************************************************/
int CreatRingBuffer()
{
int iRet = 0;
//初始化RingBuffer操作句柄,绑定缓冲区数组;
iRet = Ring_Buffer_Init(&g_stRingBuff, g_aucUartRepBuff, RING_BUFFER_SIZE);
if (iRet <= 0)
{
return -1;
}
return 0;
}
unsigned char g_ucTmpCnt = 0;
unsigned char g_aucTmpValue[RECV_FIX_DATA_LEN];
unsigned int g_uiLostPackNum = 0;
/***********************************************************************
* 函数名称: USART3_IRQRecvFixDataLenWithOutProtocolF1
* 功能描述: 每接收到一包完整的数据帧,写环形缓冲FIFO,并统计丢包数
* 输入参数:
* 输出参数:
* 返 回 值:
* 其 它:
***********************************************************************/
void USART3_IRQRecvFixDataLenWithOutProtocolF1(void)
{
unsigned char RepData = 0;
int iRet = 0;
RepData = USART_ReceiveData(USART3);
g_aucTmpValue[g_ucTmpCnt] = RepData;
g_ucTmpCnt++;
if(g_ucTmpCnt==RECV_FIX_DATA_LEN)
{
g_ucTmpCnt = 0;
iRet = Ring_Buffer_Write_String(&g_stRingBuff, g_aucTmpValue,RECV_FIX_DATA_LEN);
if(iRet == RING_BUFFER_ERROR)
{
g_uiLostPackNum++;
}
}
}
/***********************************************************************
* 函数名称: Uart3_IRQHandler
* 功能描述: Uart3中断服务函数
* 输入参数:
* 输出参数:
* 返 回 值:
* 其 它:
***********************************************************************/
void USART3_IRQHandler (void)
{
unsigned char RepData = 0;
if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
{
//USART3_IRQRecvFixDataLenWithOutProtocolF0();
USART3_IRQRecvFixDataLenWithOutProtocolF1();
}
}
unsigned int g_uiProcessPackageNum = 0;
/***********************************************************************
* 函数名称: PrintfUartRecvPackage
* 功能描述:打印接收的数据包与丢包数
* 输入参数:
* 输出参数:
* 返 回 值:
* 其 它:
***********************************************************************/
void PrintfUartRecvPackage()
{
int ix;
int iRet;
unsigned int uiBuffLen = 0;
unsigned char pucBuf[RECV_FIX_DATA_LEN];
static unsigned int uiTmpRecord = 0;
uiBuffLen = Ring_Buffer_Get_Lenght(&g_stRingBuff);
//环形缓冲数据已经有完整包
if (uiBuffLen >= RECV_FIX_DATA_LEN)
{
Ring_Buffer_Read_String(&g_stRingBuff, pucBuf, RECV_FIX_DATA_LEN);
for(ix = 0;ix < RECV_FIX_DATA_LEN; ix++)
{
printf(" 0x%x",pucBuf[ix]);
}
g_uiProcessPackageNum++;
//printf("\r\nLost Package Num = %d.\r\n",g_uiLostDataLen);
printf("\r\nLost Package Num = %d.\r\n",g_uiLostPackNum);
printf("\r\nProcess PackageNum = %d.\r\n",g_uiProcessPackageNum);
}
}
int main(void)
{
int iRet = 0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
ControlUartInit(UART0_CHANNEL, 115200);
ControlUartInit(UART3_CHANNEL, 115200);
iRet = CreatRingBuffer();
if(iRet < 0)
{
printf("Ring Buffer Init fail.\r\n");
return 0;
}
printf("system is runing.\r\n");
while (1)
{
PrintfUartRecvPackage();
delay_ms(10); //用Delay方式来模拟主机在执行其它功能任务
}
}
100ms间隔测试无丢包
7ms间隔定时测试就已经开始丢包:
丢包原因就是主任务没有及时轮询到读fifo,导致fifo满了,数包丢失
3.2 、非定长有协议帧数据接收
数据通信中协议帧的包头包尾一般有两种体现形式
帧格式1
:包头包尾各一个字节:如0xc0 xxx xxx xxx xxx… 0xc0
帧格式2:
包头包尾各两个字节:如0xeb 0x90 … 0xed 0x03
- 帧格式1分析:
包头包尾有仅一个字节,那么很有可中间的数据段也是0xc0与包头包尾一致,此时就需要就行转译与反转译
- 帧格式2分析:
包头包尾各两个字节,中间连续数据段的某两个字节与包头或包尾一致,此概率还是较小的,所以一般通信协议的制定建议包头包尾各两个字节
。
- 重点重点
对于非定长 、非阻塞式(需要做数据缓存)、有协议帧数据类型的接收方案,主机任务处理端建议一次性把fifo读空,这样不管是本次读到的数据还是后续fifo填充后的整体数据都能保证是完整的帧
。如果是按某一长度去读,由于数据非定长,会导致读到的数据以及fifo余下的数据不是完整帧
。
/***********************************************************************
* 函数名称: FrameDataUnpack
* 功能描述: 解析单字节数据流,输出一个完整的数据帧
* 输入参数:
data:单字节流数据
frame:此处作为输出参数
* 输出参数:
* 返 回 值:
1:数据帧解析成功
0:数据帧解析失败
* 其 它:
包头包尾单字节,带转义功能
0xc0 xx xx 0xc0
***********************************************************************/
int FrameDataUnpack(ST_FRAME *frame, unsigned char data)
{
int ret = FRAME_PARSER_ERROR;
switch (frame->recvFlag)
{
case RecvNothing:
if (0xC0 == data)
{
frame->recvFlag = RecvHeader;
//如果不想把包头数据整进去,此处可以去掉
frame->buffer[frame->len++] = 0xC0;
}
break;
case RecvHeader:
if (0xC0 == data)
{
if (frame->len < 2)
{
frame->recvFlag = RecvHeader;
}
else
{
/* [add by jiangfeng.zhang, 2021-07-08] :整帧数据接收完成,直接返回*/
//如果不想把包尾数据整进去,此处可以去掉
frame->buffer[frame->len++] = 0xC0;
return FRAME_PARSER_SUCCESS;
}
}
else
{
if (frame->len >= frame->bufSize)
{
//溢出
ResetFrame(frame);
ret = FRAME_PARSER_ERROR;
break;
}
//在线即时转义
if (frame->escape)
{
if (0xDC == data)
{
frame->buffer[frame->len++] = 0xC0;
}
else if (0xDD == data)
{
frame->buffer[frame->len++] = 0xDB;
}
else
{
//非法转义字符,忽略转义
frame->buffer[frame->len++] = data;
}
frame->escape = EscapeDisEnable;
}
else
{
if (0xDB == data)
{
frame->escape = EscapeEnable;
}
else
{
frame->buffer[frame->len++] = data;
}
}
}
break;
default:
ResetFrame(frame);
break;
}
return ret;
}
/***********************************************************************
* 函数名称: FrameDataUnpackDoubleWithoutEsc
* 功能描述: 解析单字节数据流,输出一个完整的数据帧
* 输入参数:
data:单字节流数据
frame:此处作为输出参数
* 输出参数:
* 返 回 值:
1:数据帧解析成功
0:数据帧解析失败
* 其 它:
包头包尾双字节+不带转译功能,不考虑中间数据字段出现连续0xeb 0x90
0xeb 0x90 ... 0xed 0x03
***********************************************************************/
int FrameDataUnpackDoubleWithoutEsc(ST_FRAME *frame, unsigned char data)
{
int ret = FRAME_PARSER_ERROR;
switch (frame->recvFlag)
{
case RecvNothing:
if (0xeb == data)
{
frame->buffer[frame->len++] = data;
frame->recvFlag = RecvFirstHeader;
}
break;
case RecvFirstHeader:
//检测到0xeb前提下,紧接着检测到0x90,则认为检测到了包头。
if(0x90==data)
{
frame->buffer[frame->len++] = data;
frame->recvFlag = RecvHeader;
}
//已经检测到0xeb,但是下一个数据检测到的不是0x90,认为数据有问题,重新找包头包尾。
else
{
ResetFrame(frame);
}
break;
case RecvHeader:
//0xed有可能只是数据段,有可能是包尾
if (0xed == data)
{
frame->recvFlag = RecvSubTail;
frame->buffer[frame->len++] = data;
}
else
{
if (frame->len >= frame->bufSize)
{
//溢出
ResetFrame(frame);
ret = FRAME_PARSER_ERROR;
break;
}
else
{
frame->buffer[frame->len++] = data;
}
}
break;
case RecvSubTail:
//检测到0xed的前提下,仅接着看这次数据是否是帧尾的最后一个字节0x03
if (0x03 == data)
{
/* [add by jiangfeng.zhang, 2021-07-08] :整帧数据接收完成,直接返回*/
frame->buffer[frame->len++] = data;
return FRAME_PARSER_SUCCESS;
}
if(0xed !=data)
{
frame->recvFlag = RecvHeader;
frame->buffer[frame->len++] = data;
}
//else分支主要考虑此种情况:eb 90 11 22 33 44 55 66 77 ed ed 03
else
{
frame->buffer[frame->len++] = data;
}
break;
default:
ResetFrame(frame);
break;
}
return ret;
}
/***********************************************************************
* 函数名称: Uart3_IRQHandler
* 功能描述: Uart3中断服务函数
* 输入参数:
* 输出参数:
* 返 回 值:
* 其 它:
***********************************************************************/
void USART3_IRQHandler (void)
{
unsigned char RepData = 0;
int iRet = 0;
if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
{
RepData = USART_ReceiveData(USART3);
// iRet = FrameDataUnpack(&g_stFrame,RepData);
iRet = FrameDataUnpackDoubleWithoutEsc(&g_stFrame,RepData);
//此处认为解析到一组完整的数据帧
if(iRet == FRAME_PARSER_SUCCESS)
{
iRet = Ring_Buffer_Write_String(&g_stRingBuff, g_stFrame.buffer,g_stFrame.len);
if(iRet == RING_BUFFER_ERROR)
{
g_uiLostPackNum++;
}
//数据处理完成后,所有的转态进行复位
ResetFrame(&g_stFrame);
}
}
USART_ClearITPendingBit(USART3, USART_IT_RXNE);
}
unsigned int g_uiProcessPackageNum = 0;
/***********************************************************************
* 函数名称: PrintfUartRecvAll
* 功能描述: 将fifo中所有的数据一次读空进行处理
* 输入参数:
* 输出参数:
* 返 回 值:
* 其 它:
***********************************************************************/
void PrintfUartRecvAll()
{
int ix;
int iRet;
unsigned int uiBuffLen = 0;
unsigned char pucBuf[RING_BUFFER_SIZE];
static unsigned int uiTmpRecord = 0;
uiBuffLen = Ring_Buffer_Get_Lenght(&g_stRingBuff);
if(uiBuffLen>0)
{
Ring_Buffer_Read_String(&g_stRingBuff, pucBuf, uiBuffLen);
for(ix = 0;ix < uiBuffLen; ix++)
{
printf(" 0x%x",pucBuf[ix]);
}
g_uiProcessPackageNum++;
//printf("\r\nLost Package Num = %d.\r\n",g_uiLostDataLen);
printf("\r\nLost Package Num = %d.\r\n",g_uiLostPackNum);
printf("\r\nProcess PackageNum = %d.\r\n",g_uiProcessPackageNum);
}
}
/***********************************************************************
* 函数名称: main
* 功能描述:
* 输入参数:
* 输出参数:
* 返 回 值:
* 其 它:
***********************************************************************/
int main(void)
{
int iRet = 0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
ControlUartInit(UART0_CHANNEL, 115200);
ControlUartInit(UART3_CHANNEL, 115200);
iRet = CreatRingBuffer();
if(iRet < 0)
{
printf("Ring Buffer Init fail.\r\n");
return 0;
}
iRet = CreateFrameBufferFun1(&g_stFrame,20);
if(iRet <= 0)
{
printf("FrameBuffer Init fail.\r\n");
return 0;
}
printf("system is runing.\r\n");
while (1)
{
PrintfUartRecvAll();
delay_ms(10); //用Delay方式来模拟主机在执行其它功能任务
}
}
1000包协议帧测试无丢包