HAL即硬件抽象层(英语:Hardware Abstraction Layer),实现了不同硬件的统一接口操作。这就极大的简化了程序员的移植工作,搭配STM32CubeMX,使用起来非常方便。
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所以需要保存其位置,将其数值赋值到这一帧的第一个字节即可。