在使用stm32的串口通信时,HAL库提供的函数HAL_UART_Receive_IT函数只能够接收固定长度的数据。这个中断只有在接受数据的数量到设定的数量以后,才被触发。我们在通信单片机时候,不可能是固定长度不变,如果单纯使用这种方法,每次数据量变化的时候,需要重新设置长度,很不方便。好在G431提供了另一个中断,叫做空闲中断,意思是当全部数据被接受后,出现了空闲帧,空闲中断被触发。利用这个方法即可实现串口接收不定长数据。我们首先要了解,空闲中断官方并没有给出回调函数,需要我们自己去判断空闲中断标志。
__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)函数可以获得到关于串口的标志位信息,这里我们获取到了UART_FLAG_IDLE的状态,即为空闲中断的标志位,如果他为SET,则代表空闲中断被触发,进入到我们的回调函数。_HAL_UART_CLEAR_IDLEFLAG(&huart1);代表清楚我们空闲
uint8_t U1_Rxbuff[1024]; //接受数据缓存区
uint8_t U1_Txbuff[1024]; //发送数据缓存区
FIG uart1; //uart1的配置
typedef struct{
uint8_t *start; //开始指针
uint8_t *end; //结束指针
}POINT;
typedef struct{
uint32_t Rxcounter; //记录接受信息量
uint32_t Txcounter; //记录发送信息量
uint8_t TxState; //发送状态
uint8_t RxState; //接收状态
POINT RXLOCATION; //用来指向接收数据地址的指针结构体
POINT TXLOCATION; //用来指向发送数据地址的指针结构体
}FIG;
其中我们定义了两个数据缓冲区U1_Rxbuff,U1_Txbuff,用来存储我们接收和发送的数据。uart1是我们串口一的接收和发送信息的状态变量,Rxcounter为我们接收信息时,信息的长度,Txcounter记录发送的信息量。TxState和RxState分别为,发送和接受的状态量。RXLOCATION和TXLOCATION是指针结构体,分别用来指向接收和发送数据的开始和结尾。
void U1_PtrInit(void)
{
uart1.Rxcounter = 0; //初始化接收数据的数量
uart1.RXLOCATION.start = U1_Rxbuff; //指针指向接收缓存区首地址
uart1.Txcounter = 0; //初始化发送数据的数量
uart1.TXLOCATION.start = U1_Txbuff; //指针指向发送缓存区首地址
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); //开启空闲中断
HAL_UART_Receive_DMA(&huart1,uart1.RXLOCATION.start,U1_RX_MAX); //开启DMA传输
uart1.TxState=0; //初始化接收状态
uart1.RxState=0; //初始化发送状态
}
初始化状态量,将计数值清0,接收和发送的开始指针指向缓冲区的首地址,开启空闲中断,开启DMA传输,接收状态置0。
void HAL_UART_IdleCallBack(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1) //判断串口几
{
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE))//判断是否为空闲中断
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1); //清除空闲中断
HAL_UART_DMAStop(&huart1); //停止DMA传输
uart1.Rxcounter = U1_RX_MAX - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
uart1.RXLOCATION.end = &U1_Rxbuff[uart1.Rxcounter-1]; //指针指向数据末尾
HAL_UART_Receive_DMA(&huart1, uart1.RXLOCATION.start, U1_RX_MAX); //开启DMA传输
uart1.RxState = 1; //接收状态置1
}
}
}
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
HAL_UART_IdleCallBack(&huart1);
/* USER CODE END USART1_IRQn 1 */
}
这个函数就是我们的空闲中断回调函数,首先判断串口几发送来的数据,如何判断空闲中断是否被触发,如果被触发,清除空闲中断。随后停止DMA传输,这里一定要停止,如果不停止,再次开启DMA传输,它的计数值无法被重置,接收数量会出现异常。 __HAL_DMA_GET_COUNTER(&hdma_usart1_rx)这个函数的作用是获取当前接收数据的计数值,他在中断函数中实现过程是当我们开启DMA传输时,计数值会被置为我们给定的最大接收数据量,完成回调函数被触发也是因为计数值为0。但是空闲中断开启后计数值是不会被清零的,而是计数值 = 最大接收数量 - 当前数据接收量。所以我们要获取当前数据量 = 最大接收数量 - 计数值。如果不停止DMA传输,这个值在下次开启DMA传输时,不会被置为最大接收数量,出现计数异常。end指针指向最后一个数据量,接收状态置1,表示接收成功。
由于HAL库官方并没有空闲中断的回调函数,我们需要把他放到串口中断服务函数里面。
void U1_Txdata(uint8_t *data_location, uint32_t data_len)
{
uart1.TXLOCATION.start = U1_Txbuff; //指针指向缓冲区首地址
memcpy(uart1.TXLOCATION.start,data_location,data_len); //将指针指向的地址和接收首地址
uart1.Txcounter = data_len; //发送长度为接收数据长度
uart1.TXLOCATION.end = &U1_Txbuff[uart1.Txcounter-1]; //指向数据末端
uart1.TxState = 1; //发送状态置1
}
这个函数的作用很简单,就是将接收缓冲区的数据复制到发送缓冲区,如果嫌麻烦的话,也可以用接收缓冲区发送。data_location为复制数据的首地址,data_len为数据长度。
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
uart1.TxState=0; //发送完置1
}
}
这个函数用来重置发送状态,等待下一次的发送。
while(1)
{
if(uart1.RxState == 1)
{
U1_Txdata(uart1.RXLOCATION.start ,uart1.RXLOCATION.end - uart1.RXLOCATION.start + 1);
uart1.RxState = 0;
}
if(uart1.TxState == 1)
{
HAL_UART_Transmit_DMA(&huart1,uart1.TXLOCATION.start,uart1.TXLOCATION.end - uart1.TXLOCATION.start + 1);
uart1.TxState=0;
}
}