天天看点

[STM32系列]一、HAL库的串口中断接收

[STM32系列]一、HAL库的串口中断任意长度接收

    • 1、前言
    • 2、回调函数
    • 3、HAL库中断接收函数使用

1、前言

HAL即硬件抽象层(英语:Hardware Abstraction Layer),实现了不同硬件的统一接口操作。这就极大的简化了程序员的移植工作,搭配STM32CubeMX,使用起来非常方便。

2、回调函数

HAL库使用了很多的回调机制,这样写能够更好的实现程序的分层处理,不影响程序的主体框架,方便后期修改移植。

3、HAL库中断接收函数使用

使用HAL_UART_Receive_IT函数前,需要使能串口的接收中断,并配置中断优先级。

/* Peripheral clock enable */
    __HAL_RCC_USART2_CLK_ENABLE();
  
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART2 GPIO Configuration    
    PA2     ------> USART2_TX
    PA3     ------> USART2_RX 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_USART2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART2 interrupt Init */
    HAL_NVIC_SetPriority(USART2_IRQn, 1, 1);
    HAL_NVIC_EnableIRQ(USART2_IRQn);
           

在对应的串口中断函数中需要对应的中断处理

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */

  /* USER CODE END USART2_IRQn 1 */
}
           

这时就可以使用

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

函数接收了,该函数会打开接收中断接收数据,函数的第一个参数指定接收串口的结构体指针,第二个参数为接收数据指针,第三个参数为接收数据长度。该函数没有超时机制,必须要指定接收的数据长度,在数据没有达到接收长度时该函数会一直阻塞,这就要求接收的数据长度必须为已知,在接收不定长度数据时会不适用。

在使用串口接收Modbus-RTU数据时,需要接收不定长的数据帧,并且要求非阻塞接收。像在标准库中接收数据一样,这里可以通过HAL_UART_Receive_IT接收单个字节的数据,在接收回调函数中加入超时处理即可完成一帧数据的接收。接收流程如下:

保证数据帧完整的接收,就需要在接收完一个字节数据的时候将计时清零,继续下一次接收,直到超时退出,然后再处理这一帧数据。具体实现内容如下:

//数据接收结构体
struct M_Rev{
	uint8_t revcnt;			//接收计数
	uint8_t revact;			//开始标志
	uint8_t oldcnt;			//上次计数
	uint32_t revtick;		//接收计时
	uint8_t revbuff[MODBUS_MAX_LEN];		//接收缓存
}Modbus_Rev;
           
//接收回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART2)		//判断串口
	{
		if(Modbus_Rev.revcnt == 0)		//帧第一个数据
		{
			if(Modbus_Rev.revact == 1)	//首次接收
			{
				Modbus_Rev.revact = 2;		
			}
			else	//非首次接收
			{
				Modbus_Rev.revbuff[0] = Modbus_Rev.revbuff[Modbus_Rev.oldcnt];	//上一次接收数据位置
			}	
			HAL_TIM_Base_Start_IT(&htim2);	//开启定时器
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET);	//开启指示灯
		}		
		Modbus_Rev.revcnt += 1;	//接收计数
		if(Modbus_Rev.revcnt >= MODBUS_MAX_LEN)	Modbus_Rev.revcnt = MODBUS_MAX_LEN - 1;	
		
		Modbus_Rev.revtick = 0;	//计时清0
		HAL_UART_Receive_IT(huart, &(Modbus_Rev.revbuff[Modbus_Rev.revcnt]), 1);	//接收下一次数据
	}
}
           
//定时器回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	Modbus_Rev.revtick += 1;				//计时增加
	if(Modbus_Rev.revtick > MAX_TIM_CNT)	//定时器溢出
	{
		Modbus_Rev.revtick =0;				//计时清0
	}
	if(Modbus_Rev.revtick >= MODBUS_T35)	//接收超时
	{
		if(Modbus_Rev.revact == 2)			//首次接收
		{
			Modbus_Rev.revact = 0;			//关闭首次接收
		}	
		Modbus_ReciveData(Modbus_Rev.revbuff,Modbus_Rev.revcnt);		//完成一帧接收,处理
		Modbus_Rev.oldcnt = Modbus_Rev.revcnt;		//保存接受位置
		Modbus_Rev.revcnt = 0;				//接收计数清0
		HAL_TIM_Base_Stop_IT(&htim2);	//关闭定时器
		
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET);	//关闭指示灯
	}
}
           

其中要注意的一点是,在第二帧接收时,由于接收的地址是上一帧的接收位置 + 1所以需要保存其位置,将其数值赋值到这一帧的第一个字节即可。

继续阅读