[STM32系列]一、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接收单个字节的数据,在接收回调函数中加入超时处理即可完成一帧数据的接收。接收流程如下:
Created with Raphaël 2.2.0
开始
接收一个字节
超时?
帧处理
结束
yes
no
保证数据帧完整的接收,就需要在接收完一个字节数据的时候将计时清零,继续下一次接收,直到超时退出,然后再处理这一帧数据。具体实现内容如下:
//数据接收结构体
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所以需要保存其位置,将其数值赋值到这一帧的第一个字节即可。