那些我们一起踩过的STM32HAL库的串口坑

写在前面

之前面试听一位面试官问我有没有使用过HAL库和 STM32CubeMX,那时候只是再原子的例程上看到过。但是他跟我力荐,于是不由自主的对STM32CubeMX产生了一定的好感。自己再这一年里断断续续使用过STM32CubeMX以及HAL库,一直没有再项目中使用过,于是我终于等到一个机会。

项目简介

项目不方便细说,大概就是使用华为9094G模块连接服务器通讯,并且处理服务器的数据,来操纵IO口。实现项目功能。一开始也是犯了邪。我使用标准库,初始化的串口2给4G模块发数据,4G模块死活收不到我发的东西。而发送完成标记位,寄存器都自己清楚了。万般无奈自己使用了CubeMX建立了一个工程居然可以。然后就开始了挖坑。

HAL库使用的一些问题

首先CubeMX建立的工程初始化串口,并且配置中断,其实它并没有帮你设置好是什么中断,是RXNE中断还是IT_ERR中断。所以再初始化串口之后需要自己加上
__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE); 或者
HAL_UART_Receive_IT(&huart2,aRxBuffer, 1); 这句
当然两句有不同的区别
HAL_UART_Receive_IT(&huart2,aRxBuffer, 1); 这句进入函数以后
注意!!!!这一句一定要在串口初始化函数结束之后再调用,如果这个函数的返回值是HAL_BUSY,恭喜你,你的串口是进不去中断的,只有这个函数初始化返回值是HAL_OK,才会进入串口接收中断

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  uint32_t tmp_state = 0;
  
  tmp_state = huart->State;
  if((tmp_state == HAL_UART_STATE_READY) || (tmp_state == HAL_UART_STATE_BUSY_TX))
  {
    if((pData == NULL ) || (Size == 0))
    {
      return HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);

    huart->pRxBuffPtr = pData;
    huart->RxXferSize = Size;
    huart->RxXferCount = Size;

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    /* Check if a transmit process is ongoing or not */
    if(huart->State == HAL_UART_STATE_BUSY_TX)
    {
      huart->State = HAL_UART_STATE_BUSY_TX_RX;
    }
    else
    {
      huart->State = HAL_UART_STATE_BUSY_RX;
    }

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    /* Enable the UART Parity Error Interrupt */
    __HAL_UART_ENABLE_IT(huart, UART_IT_PE);

    /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
    __HAL_UART_ENABLE_IT(huart, UART_IT_ERR);

    /* Enable the UART Data Register not empty Interrupt */
    __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

是会通过查询串口是否准备就绪,上锁HAL库。以及将传入参数丢给串口HAL结构体
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount = Size;
也就是接收数据缓存地址,字节大小,长度,传给串口结构体,
然后解锁HAL库使能三个中断,其中就有RXNE中断

然后串口接收到数据之后,进入串口中断,串口中断我是这样写的

void USART2_IRQHandler(void)
{
	
  /* USER CODE BEGIN USART2_IRQn 0 */
//使用串口中断回调函数HAL_UART_RxCpltCallback来处理
#if RXNE_CALL
	HAL_UART_IRQHandler(&huart2);	
	
	uint32_t timeout = 0;	
	timeout=0;
    while (HAL_UART_GetState(&huart2) != HAL_UART_STATE_READY)//等待就绪
	{
	 timeout++;超时处理
     if(timeout>0x1FFFF) break;		
	}

	timeout=0;
	while(HAL_UART_Receive_IT(&huart2,aRxBuffer, 1) != HAL_OK)//一次处理完成之后,重新开启中断并设置RxXferCount为1
	{
	 timeout++; //超时处理
	 if(timeout>0x1FFFF) break;	
	}
	}

再串口中断中首先进入串口中断处理函数 HAL_UART_IRQHandler(&huart2);
也就是这个样子

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
  uint32_t tmp_flag = 0, tmp_it_source = 0;

  tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_PE);
  tmp_it_source = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_PE);  
  /* UART parity error interrupt occurred ------------------------------------*/
  if((tmp_flag != RESET) && (tmp_it_source != RESET))
  { 
    huart->ErrorCode |= HAL_UART_ERROR_PE;
  }
  
  tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_FE);
  tmp_it_source = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_ERR);
  /* UART frame error interrupt occurred -------------------------------------*/
  if((tmp_flag != RESET) && (tmp_it_source != RESET))
  { 
    huart->ErrorCode |= HAL_UART_ERROR_FE;
  }
  
  tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_NE);
  /* UART noise error interrupt occurred -------------------------------------*/
  if((tmp_flag != RESET) && (tmp_it_source != RESET))
  { 
    huart->ErrorCode |= HAL_UART_ERROR_NE;
  }
  
  tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_ORE);
  /* UART Over-Run interrupt occurred ----------------------------------------*/
  if((tmp_flag != RESET) && (tmp_it_source != RESET))
  { 
    huart->ErrorCode |= HAL_UART_ERROR_ORE;
  }
  
  tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE);
  tmp_it_source = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_RXNE);
  /* UART in mode Receiver ---------------------------------------------------*/
  if((tmp_flag != RESET) && (tmp_it_source != RESET))
  { 
    UART_Receive_IT(huart);
  }
  
  tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_TXE);
  tmp_it_source = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_TXE);
  /* UART in mode Transmitter ------------------------------------------------*/
  if((tmp_flag != RESET) && (tmp_it_source != RESET))
  {
    UART_Transmit_IT(huart);
  }

  tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_TC);
  tmp_it_source = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_TC);
  /* UART in mode Transmitter end --------------------------------------------*/
  if((tmp_flag != RESET) && (tmp_it_source != RESET))
  {
    UART_EndTransmit_IT(huart);
  }  

  if(huart->ErrorCode != HAL_UART_ERROR_NONE)
  {
    /* Clear all the error flag at once */
    __HAL_UART_CLEAR_PEFLAG(huart);
    
    /* Set the UART state ready to be able to start again the process */
    huart->State = HAL_UART_STATE_READY;
    
    HAL_UART_ErrorCallback(huart);
  }  
}	

然后根据中断类型进行相对应的处理,因为是收到数据,所以会进入要此处处理

tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE);
  tmp_it_source = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_RXNE);
  /* UART in mode Receiver ---------------------------------------------------*/
  if((tmp_flag != RESET) && (tmp_it_source != RESET))
  { 
    UART_Receive_IT(huart);
  }

跳入 UART_Receive_IT(huart);函数

static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
uint16_t* tmp;
uint32_t tmp_state = 0;

tmp_state = huart->State; 
if((tmp_state == HAL_UART_STATE_BUSY_RX) || (tmp_state == HAL_UART_STATE_BUSY_TX_RX))
{
  if(huart->Init.WordLength == UART_WORDLENGTH_9B)
  {
    tmp = (uint16_t*) huart->pRxBuffPtr;
    if(huart->Init.Parity == UART_PARITY_NONE)
    {
      *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
      huart->pRxBuffPtr += 2;
    }
    else
    {
      *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
      huart->pRxBuffPtr += 1;
    }
  }
  else
  {
    if(huart->Init.Parity == UART_PARITY_NONE)
    {
      *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
    }
    else
    {
      *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
    }
  }

  if(--huart->RxXferCount == 0)
  {
    __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);

    /* Check if a transmit process is ongoing or not */
    if(huart->State == HAL_UART_STATE_BUSY_TX_RX) 
    {
      huart->State = HAL_UART_STATE_BUSY_TX;
    }
    else
    {
      /* Disable the UART Parity Error Interrupt */
      __HAL_UART_DISABLE_IT(huart, UART_IT_PE);

      /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
      __HAL_UART_DISABLE_IT(huart, UART_IT_ERR);

      huart->State = HAL_UART_STATE_READY;
    }
    HAL_UART_RxCpltCallback(huart);

    return HAL_OK;
  }
  return HAL_OK;
}
else
{
  return HAL_BUSY; 
}
}

它也是看串口状态,然后根据串口配置的是8位数据位还是9位数据位,把串口接收寄存器的值读到串口的HAL结构体的接收数据地址(*huart->pRxBuffPtr)里面去,也就是我们先前传入
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
这个函数的pData地址。
到了这个时候一个字节的数据已经接收完成,但是这个中断处理函数把我们的RXNE中断给关了。
这个也就是主循环之前为什么调用了一遍
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
函数,再串口中断里面又调用一边这个函数。
再然后串口中断又调用了 HAL_UART_RxCpltCallback(huart);函数
这个也就是所说的串口中断回调函数,这个时候我们可以定义一个这个函数,再这个函数里面对接收的数据进行处理。

当然我们可以做简单点的处理 ,我试过也可以。
直接主循环之前调用
__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);
来打开RXNE中断
然后再串口中断函数里面直接处理数据也就是把串口中断写成这个样子

void USART2_IRQHandler(void)
{
	uint8_t Res;
	if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_RXNE)!=RESET)) 
	{
        HAL_UART_Receive(&huart2,&Res,1,1000); 

		if(Hua_Wei_Module.Rx_Cnt_W >= 1024) Hua_Wei_Module.Rx_Cnt_W = 0;
		Hua_Wei_Module.Rx_Data_temp[Hua_Wei_Module.Rx_Cnt_W++]=Res ;// 储存到缓冲区,在定时器里处理缓冲区的内容	  		 
	}
	
//-------下面这句中断处理最终会调用HAL_UART_RxCpltCallback(huart);这个回调函数,
//-------并且__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);禁用RX非空中断,
//-------所以需要在这个函数处理完成之后打开RXNE中断。
  /* USER CODE END USART2_IRQn 0 */
	HAL_UART_IRQHandler(&huart2);

  /* USER CODE BEGIN USART2_IRQn 1 */
	__HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);
#endif	
}

这个处理更简单粗暴,HAL库的中断处理确实复杂繁琐,初学者使用非常不方便。

下面第二个坑

第二个坑也就是HAL库的串口发送,最开始我用4G模块联网通讯一直很正常,应用层的协议我们是用公司自己制定的。然后联网发送心跳什么的都很正常数据字节也在50-100之间。这个时候使用HAL库的串口发送函数就没什么问题,一直很正常。也就是这一句

  /**
  * @brief  向909模块发送字符串函数
  * @param  data:需要发送的字符串
  * @retval 
  */
//void SendStr_MU909(uint8_t *data)
//{
//	HAL_UART_Transmit(&huart2,data,strlen((const char*)data),0xffff);
//}

等到服务器发送需要执行的任务数据,已经MCU执行完需要返回执行结果的时候,数据大概再100个字节,串口发送函数就开始出毛病,MCU卡死再串口发送这一块,定时器没有死,回不去主循环,确实我调用过的是0xFFFF,应该是一直等待发送完成的意思,所以才会一直回不去主循环,导致MCU卡住。
然后我直接使用寄存器发送

void SendStr_MU909(uint8_t *data)
{
	uint32_t t= 0;
	uint32_t len = strlen((const char*)data);
			for(t=0;t<len;t++)
			{
				USART2->DR=data[t];
				while((USART2->SR&0X40)==0);//等待发送结束
			}
}


测试很久一点问题也没有·······

  • 19
    点赞
  • 115
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
引用:特别注意!DMA初始化必须在串口初始化之前,否则就不会工作,CubeMX有一个缺点就是如果你一开始只开了串口,之后添加DMA再生成代码,DMA的初始化会在串口初始化之后。 引用:前期准备:STM32CubeMX、Proteus 8、IDE Keil(MDK-ARM)、Configure Virtual Serial Port Driver(虚拟串口) 引用:STM32 HAL库串口DMA空闲中断(IDLE)实现不定长数据接收,可以用来参考学习使用,简单易懂。 STM32 HAL库串口是指在STM32HAL库中提供的串口功能。在使用STM32 HAL库进行串口通信时,我们需要先初始化串口,并且特别注意DMA初始化必须在串口初始化之前。如果需要进行不定长数据接收,可以参考使用空闲中断(IDLE)实现。同时,为了进行虚拟串口的调试,需要进行前期准备,包括使用STM32CubeMX进行配置、Proteus 8进行仿真、IDE Keil进行开发,并配置虚拟串口驱动程序。以上是关于STM32 HAL库串口的一些基本信息和注意事项。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【串口STM32串口使用(HAL库)](https://blog.csdn.net/lrqblack/article/details/126090379)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [【STM32HAL库——串口通信(一)](https://blog.csdn.net/Qxiaofei_/article/details/116565060)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [STM32 HAL库串口+DMA空闲中断接收不定长数据](https://download.csdn.net/download/chenyongfeng123/13087603)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值