STM32L051测试 (五、串口测试 — 与Enocean模块通讯问题)_stm32l051 hal库dma

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

if(test_data != Enocean_Data){
        HAL\_Delay(7);
        // printf("Enocean\_Data ID is: 0x %d test\_data is : 0x%d \r\n",Enocean\_Data,test\_data);
        if(Enocean_Data > 10){
          HAL\_UART\_Transmit(&huart1,USART_Enocean_BUF, Enocean_Data,0xFFFF); //将串口3接收到的数据通过串口1传出 
        }      
        memset(USART_Enocean_BUF, 0, sizeof(USART_Enocean_BUF));   //清空缓存区 
        Enocean_Data=0;
        // (&hlpuart1)->pRxBuffPtr = &USART\_Enocean\_BUF[Enocean\_Data];//用下面的简洁,数组名就可以当做指针用
        (&hlpuart1)->pRxBuffPtr = USART_Enocean_BUF;//这一句很重要,没有这一句,后面接收会出错
    }
    }

在主函数初始化串口后,有这么一句,打开串口中断(这里的中断可不可以理解为打开IT中断)

 HAL\_UART\_Receive\_IT(&hlpuart1, (uint8\_t \*)&USART_Enocean_BUF[0], 1);

现在我们要开启IDLE中断

 \_\_HAL\_UART\_ENABLE\_IT(&hlpuart1,UART_IT_IDLE);
  HAL\_UART\_Receive\_IT(&hlpuart1, (uint8\_t \*)&USART_Enocean_BUF[0], 1);

在stm32l0xx_it.c中找到 LPUART1_IRQHandler函数,因为所有中断先是进入 stm32l0xx_it.c 中相应的IRQHandler函数中,调用对应函数,最后才会进入到 自己设置的 Callback函数中,我们这次测试直接在void LPUART1_IRQHandler(void)函数中系统设置的函数前写一个 实现 IDLE中断后操作的函数:

void LPUART1\_IRQHandler(void)
{
  /\* USER CODE BEGIN LPUART1\_IRQn 0 \*/
  if((\_\_HAL\_UART\_GET\_FLAG(&hlpuart1,UART_FLAG_IDLE) != RESET))
  {
    		\_\_HAL\_UART\_CLEAR\_IDLEFLAG(&hlpuart1);
        ReceiveState = 1;   
  }
  /\* USER CODE END LPUART1\_IRQn 0 \*/
  HAL\_UART\_IRQHandler(&hlpuart1);
  /\* USER CODE BEGIN LPUART1\_IRQn 1 \*/

  /\* USER CODE END LPUART1\_IRQn 1 \*/
}

用 ReceiveState 来标志是否接受到了一串数据,然后打印函数变成

 if(ReceiveState == 1){
        ReceiveState = 0;      
        HAL\_UART\_Transmit(&huart1,USART_Enocean_BUF, Enocean_Data,0xFFFF); //将串口3接收到的数据通过串口1传出 
        memset(USART_Enocean_BUF, 0, sizeof(USART_Enocean_BUF));   //清空缓存区 
        Enocean_Data=0;
        (&hlpuart1)->pRxBuffPtr = USART_Enocean_BUF;//这一句很重要,没有这一句,后面接收会出错
    }

结果, 数据异常,就是收不全,结尾会有问题,明天得继续测试优化了,先上个正常的接收,这是上面用HAL_Delay(7)测试得到的正常结果。
在这里插入图片描述
在这里插入图片描述实际测试,数据会断接收不全,考虑了一下,是否本身我的通讯模组串口给MCU的时候一包数据就是分两次发送(其实就是一包数据中间有个时间会长一点,然后MCU判定为2帧数据,每次能够收到开头的一段),
我测试的打印函数每次收到一包数据,都会把数据全部打印出来,然后清空缓存区,所以下一包数据直接被清掉了,因为在进行输出的过程,可能下一包的数据中断进来了,为了验证一下,还是加上了一个延时,代码改成如下:

if(ReceiveState == 1){
        HAL\_Delay(7); //测试,重要
        ReceiveState = 0;      
        HAL\_UART\_Transmit(&huart1,USART_Enocean_BUF, Enocean_Data,0xFFFF); //将串口3接收到的数据通过串口1传出 
        memset(USART_Enocean_BUF, 0, sizeof(USART_Enocean_BUF));   //清空缓存区 
        Enocean_Data=0;
        (&hlpuart1)->pRxBuffPtr = USART_Enocean_BUF;//这一句很重要,没有这一句,后面接收会出错
    }

发现加上延时后,数据就正常了……,其实中途测试的时候在IDLE中断处理时候,我打印过 测试语句,发现我正常的一包数据,都会打印2次测试语句,也进一步的证实了,测试模块给MCU的正常的一包数据会被MCU认为是 2帧数据。
那么我们怎么来解决这个问题呢,还是直接加一个ms延时吗? 至少IDLE中断在这里用不了,一帧数据会触发2次中断,可能和通讯模块有关,因为以前测试的时候也遇到过类似情况。

但至少IDLE中断我们可以用起来,在这里做一个小结:
1、正常初始化串口以后,打开IDLE中断:

\_\_HAL\_UART\_ENABLE\_IT(&hlpuart1,UART_IT_IDLE);

这个语句可以放在串口初始化函数MX_LPUART1_UART_Init中,也可以放在main函数后面,看个人习惯;

2、在stm32l0xx_it.c 文件中响应的中断相应函数LPUART1_IRQHandler 中加入关于IDLE中断的处理于语句:

void LPUART1\_IRQHandler(void)
{
  /\* USER CODE BEGIN LPUART1\_IRQn 0 \*/
  
  /\* USER CODE END LPUART1\_IRQn 0 \*/
  HAL\_UART\_IRQHandler(&hlpuart1);
  /\* USER CODE BEGIN LPUART1\_IRQn 1 \*/
  if((\_\_HAL\_UART\_GET\_FLAG(&hlpuart1,UART_FLAG_IDLE) != RESET))
  {
    usartreceive\_IDLE(&hlpuart1);
  }
  /\* USER CODE END LPUART1\_IRQn 1 \*/
}

/\* USER CODE BEGIN 1 \*/
void usartreceive\_IDLE(UART_HandleTypeDef \*huart)
{
  \_\_HAL\_UART\_CLEAR\_IT(&hlpuart1,UART_CLEAR_IDLEF); //清除中断
  // HAL\_UART\_AbortReceive\_IT(huart); //终止接收
  ReceiveState = 1;
}

用一个标志位表示收到了一帧数据,即便这个一帧不完全,也至少表示收到一段数据了,这里不能加
HAL_UART_AbortReceive_IT(huart); //终止接收 ,因为IT中断可以继续接收,如果终止了,数据处理不够及时,下一帧数据就收不到了。

3、最后通过判断标志位 ReceiveState 来处理数据,一般情况下直接处理,特除情况,在标志位至1 后,刻意的等待几个毫秒,等待数据接收完全再处理。

!!!最后移植又发现一个问题
代码中 #define ID_blueNum 44

	printf("Enocean\_Data is :%d Read\_pt is :%d\n\r",Enocean_Data,Read_pt);
	printf("send command !\r\n");
	COMMAND\_GetmoduleID();
	// HAL\_Delay(10); //7ms不够 
	while ((Enocean_Data - Read_pt) < ID_blueNum);//加了这句不需要加上面的延时,这句就是等待,这句很重要,能够过滤掉不相关的报文
	// while ((Enocean\_Data - Read\_pt) < ID\_blueNum){
	// HAL\_UART\_Receive\_IT(&hlpuart1, &USART\_Enocean\_BUF[Enocean\_Data], 1);
	// }//2021/8/11 不是很懂为什么这里这个while等待必须得加点东西
	printf("Enocean\_Data is :%d Read\_pt is :%d\n\r",Enocean_Data,Read_pt);

在这里插入图片描述
如果按照上面的代码,会一直卡在这里过不去,其实就是while ((Enocean_Data - Read_pt) < ID_blueNum);过不去,开始怀疑是不是通讯模块本来就是坏的,我们有多种方式测试:

测试代码1(好理解,发送命令了,等待一会,让串口把数据收完):

	printf("Enocean\_Data is :%d Read\_pt is :%d\n\r",Enocean_Data,Read_pt);
	printf("send command !\r\n");
	COMMAND\_GetmoduleID();
	HAL\_Delay(10);//7ms不够 
	while ((Enocean_Data - Read_pt) < ID_blueNum);//加了这句不需要加上面的延时,这句就是等待,这句很重要,能够过滤掉不相关的报文
	printf("Enocean\_Data is :%d Read\_pt is :%d\n\r",Enocean_Data,Read_pt);

在这里插入图片描述
测试代码2(本来是想看看等待时候打印什么东西,加个条件怕一直循环):

	printf("Enocean\_Data is :%d Read\_pt is :%d\n\r",Enocean_Data,Read_pt);
	printf("send command !\r\n");
	COMMAND\_GetmoduleID();
	while ((Enocean_Data - Read_pt) < ID_blueNum){
		if(Enocean_Data < 10)printf("测试过随便打印点东西都可以!\r\n");
	}//2021/8/11 不是很懂为什么这里这个while等待必须得加点东西
	printf("Enocean\_Data is :%d Read\_pt is :%d\n\r",Enocean_Data,Read_pt);

在这里插入图片描述
测试代码3 , 4(不加条件随便在等待中打印 , 延时 ):

printf("Enocean\_Data is :%d Read\_pt is :%d\n\r",Enocean_Data,Read_pt);
	printf("send command !\r\n");
	COMMAND\_GetmoduleID();
	while ((Enocean_Data - Read_pt) < ID_blueNum){
		printf("不要条件乱打都可以测试过随便打印点东西都可以!\r\n");
		//HAL\_Delay(1);//延时可以,1ms就可以
	}//2021/8/11 不是很懂为什么这里这个while等待必须得加点东西
	printf("Enocean\_Data is :%d Read\_pt is :%d\n\r",Enocean_Data,Read_pt);

在这里插入图片描述
经过测试发现,while中虽然是干等,但是没有语句就会有问题,不是很明白,因为以前都可以,虽然知道解决办法,但是不知道问题原因,所以也不知道哪种是更优的解决办法!
= =!

1.2 串口接收发送不定长度的数据(DMA方式)

在实际使用中,我没有在L051下面测试使用DMA利用 IDLE 中断进行DMA接收发送,实际上,通过我们上面做的实验和测试,对于目前使用的通讯模块,一包数据会触发2次IDLE 中断的情况,也不太适合。
本例程是以前STM32L1系列中使用的例程,以前也是在网上参考过其他人的设计然后自己用起来的,正好也在这里做个笔记

#define USART3\_DMA\_REC\_SIE 256 //DMAbuf空间大小
#define USART3\_REC\_SIE 512 //接收区空间大小两倍,以保持两条历史数据
typedef struct
{   
    uint8\_t UsartRecFlag;   //接收数据Flag
    uint16\_t UsartDMARecLen; //DMA接收到数据的长度
    uint16\_t UsartRecLen;    //RecBuffer中已经存储数据的长度
    uint8\_t  Usart3DMARecBuffer[USART3_DMA_REC_SIE];  //dma
    uint8\_t  Usart3RecBuffer[USART3_REC_SIE];         //RecBuffer
} Usart3DMAbufstr;
extern Usart3DMAbufstr Usart3type;  

void USART3\_IRQHandler(void)
{
  /\* USER CODE BEGIN USART3\_IRQn 0 \*/

  /\* USER CODE END USART3\_IRQn 0 \*/
  HAL\_UART\_IRQHandler(&huart3);
  /\* USER CODE BEGIN USART3\_IRQn 1 \*/
  if(\_\_HAL\_UART\_GET\_FLAG(&huart3,UART_FLAG_IDLE) == SET)  //判断空闲中断
    {		
      u16 res = 0;                        
      \_\_HAL\_UART\_CLEAR\_IDLEFLAG(&huart3);       //清楚空闲中断标志
      HAL\_UART\_DMAStop(&huart3);                //暂时关闭DMA中断
	    res = huart3.Instance->ISR;          
   	  res = huart3.Instance->RDR;              
      res = hdma_usart3_rx.Instance->NDTR;                     //读取DMA接收的那个buf还剩多少空间
	  Usart3type.UsartDMARecLen = USART3_DMA_REC_SIE - temp;   //总空间减去还剩下的空间等于存入数据的空间大小 
      HAL\_UART\_RxCpltCallback(&huart3);		                   //手动调用中断回调,因为空闲中断不会主动调用
   }
  /\* USER CODE END USART3\_IRQn 1 \*/
}

数据最初都存储在DMARecBuffer中,然后转存到RecBuffer中。DMARecBuffer中的数据每接收到新的数据都会清空。

void HAL\_UART\_RxCpltCallback(UART_HandleTypeDef \*huart)
{	
    //可行选着是否开启多条数据缓存,默认不开启,开启删除下划线
    if(huart->Instance == USART3)
    {
      // if(Usart3type.UsartRecLen>0)//判断RecLen是否清0,如果没有清零代表上一个数据没有读取 
      // {
        // memcpy(&Usart3type.Usart3RecBuffer[Usart3type.UsartRecLen],Usart3type.Usart3DMARecBuffer,Usart3type.UsartDMARecLen); //把数据顺延
       // Usart3type.UsartRecLen += Usart3type.UsartDMARecLen;//数据长度增加相应的位数 
      // }
      // else
     // {
            memcpy(Usart3type.Usart3RecBuffer,Usart3type.Usart3DMARecBuffer,Usart3type.UsartDMARecLen);                          //把输入放入buf开头
            Usart3type.UsartRecLen =  Usart3type.UsartDMARecLen;								//记录数据长度 
       // }
				
        memset(Usart3type.Usart3DMARecBuffer, 0x00, sizeof(Usart3type.Usart3DMARecBuffer));                                     //把DMA缓存中的数据清空,方便下一次接收
        Usart3type.UsartRecFlag = 1;                                                                                            //RecFlag置1,代表RecBuffer中有数据
	    HAL\_UART\_Receive\_DMA(&huart3,Usart3type.Usart3DMARecBuffer,USART3_DMA_REC_SIE);                                         //重新开启DMA中断,方便再一次接收
    }
}

在相应的程序中把上面的代码添加好,然后在主函数循环中使用,RecBuffer中的数据会一直增加,直到用户读取以后才会清空:

if(Usart3type.UsartRecFlag == 1)//如果Recbuffer中有数据
{
	HAL\_UART\_Transmit(&huart1,Usart3type.Usart3RecBuffer,256,0xffff);//把RecBuffer中的数据发送到串口1
	memset(Usart3type.Usart3RecBuffer, 0x00, sizeof(Usart3type.Usart3RecBuffer));//读取完数据后记得一定要把RecBuffer中的数据清除
	Usart3type.UsartRecFlag = 0;//标志位清0
	Usart3type.UsartRecLen = 0;//已有数据长度清0
}

1.3 环形缓冲区

因为单单靠数组方式,接收处理,总感觉不是那么聪明,有时候需要干等,所以还是得花时间研究下环形缓冲区。。。

二、串口接收卡死处理

在使用了了段时间后,测试部反馈偶尔会有串口卡死说明,最终就是接收不到串口数据,但是轮询发送是正常,后来查阅了一些资料,找到了需要处理的地方,这里特此来记录一下。

2.1 清除错误标志位

在使用 HAL 库的时候,有4个错误 flag,如下图:
在这里插入图片描述

出错的时候,HAL库会把以上flag置位如果不清除就再也接收不到数据了。

所以我们可以在需要的时候使用,下面的语句清除错误标志:

	\_\_HAL\_UART\_CLEAR\_FLAG(&hlpuart1, UART_FLAG_PE);//清标志
	\_\_HAL\_UART\_CLEAR\_FLAG(&hlpuart1, UART_FLAG_FE);
	\_\_HAL\_UART\_CLEAR\_FLAG(&hlpuart1, UART_FLAG_NE);
	\_\_HAL\_UART\_CLEAR\_FLAG(&hlpuart1, UART_FLAG_ORE);

比如,在我清除串口接收缓存的函数中,我加上了这几句代码:

在这里插入图片描述

这是实际使用的情况,我并没有详细的测试到底是哪一个错误置位了,在自己了解的简单产品上,直接一步到位也是一种方式。

2.2 HAL 库函数问题

产品使用了上面的串口清除错误标志,在压力测试下下面还是有问题:

现象就是串口发送正常,但是永远接收不到数据了。

实在是没办法,后来继续找答案。

最后确实发现网上也有小伙伴遇到过相同的问题,我使用的是 HAL_UART_Receive_IT 开启中断:

在这里插入图片描述

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

csdnimg.cn/182d863c9e794b1ca59437c44bf9c0ae.png)

[外链图片转存中…(img-y5wY2Sog-1715878995332)]
[外链图片转存中…(img-NArmapcu-1715878995332)]
[外链图片转存中…(img-SCgKUyDH-1715878995332)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值