STM32裸机串口接收方案

在这里插入图片描述

[--------------点击下面,进入总目录索引--------------]
STM32系列精品Demo集 - 总目录


一、UART接收方案汇总

在这里插入图片描述
在这里插入图片描述

补充说明
上述接收方案中有协议帧,本协议帧指的是有帧头帧尾这种协议,不包含类似AT指令这种协议方案

二、UART丢包问题分析

2.1 、裸跑机制

  • 裸跑时上述UART接收方案中的方法一 、二分析
  1. 主任务能够及时轮询处理,fifo空间写入与读取能够满足,都不会丢包。
  2. 两个接口在主任务不能及时轮询处理,fifo被中断写满,都会导致丢包:
    2.1. 上述UART接收方案中的情形一 :存在单字节数据丢失,导致ffifo中后续的数据已经不是完整帧,全部错乱。(!!所以不建议采用这种方式!!)
    2.2. 上述UART接收方案中的情形二 :存在丢整帧数据,但是如果丢也是整帧数据,不会扰乱fifo中其它的完整帧。

  • 裸机情况下,如果主任务其它工作耗时特别大,等轮询到读取fifo并处理时,fifo早都被uart中断填满,丢掉了好多数据,所以裸机跑应该注意一下几点:
  1. 尽可能代码优化,减少耗时,这样轮询时间就快一点读取到fifo,不至于fifo满掉。
  2. 可以把fifo空间调大,以匹配该项目的时间轮序机制。

  • 裸机情况下如何尽可能保证不丢包?

说明:为何说尽可能,因为fifo不是无限大的,定义大了,很有可能栈错误。所以fifo大小也是有限的,而如果主任务不能及时读取fifo,数据又不断的发过来填充到fifo,必然导致丢数据。

  1. 多个fifo实现乒乓操作或者把一个fifo缓冲区调节的足够大**,其目的是让fifo缓冲尽可能大**:
    1.1. 一个fifo缓冲区调节的足够大,大到满足项目需求。
    1.2. 多个fifo实现乒乓操作,以满足项目需求(有弊端,主任务不能把控先写入fifo的先处理)。

  2. 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分析:

  包头包尾各两个字节,中间连续数据段的某两个字节与包头或包尾一致,此概率还是较小的,所以一般通信协议的制定建议包头包尾各两个字节

  • 重点重点
  1. 对于非定长 、非阻塞式(需要做数据缓存)、有协议帧数据类型的接收方案,主机任务处理端建议一次性把fifo读空,这样不管是本次读到的数据还是后续fifo填充后的整体数据都能保证是完整的帧
  2. 如果是按某一长度去读,由于数据非定长,会导致读到的数据以及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包协议帧测试无丢包
在这里插入图片描述

附录:Demo源码

https://gitee.com/jiangfengzhang/stm32-f4-uart-recv-demo
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值