前言:使用hal库的DMA接收与串口的空闲中断,实现接收不定长的数据长度。
一 :串口的空闲中断:
顾名思义,就是当串口在一定的时间内没有接收到数据时,触发控制状态,从而产生空闲中断;一般来说,STM32在数据交互时,传输字节之间的间隔很短,然后再一个字节的通讯时间内,没有收到数据时,意味着程序进入了空闲中断,所以在程序的初始化后,我们只需要开启空闲中断后,然后在串口的回调函数中,进行数据迁移就可以,这里为了方便验证,将数据做回传处理。
在STM32的串口通讯模式中,我个人的理解是,阻塞方式,非阻塞方式和DMA发送,共计三种不同的的通讯方式,在HAL库中相关的函数代码如下:
HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size);
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size);
我在工作实际使用中,在代码量不大,要求不高的情况下,一般使用阻塞式的发送和中断方式接收,即HAL_UART_Transmit 和HAL_UART_Receive_IT的方式,实现CPU与外设的交互。
这种通讯方式比较简单,串口每接收到一个字节单片机就会进入一次串口的中断,我们在接收的过程中,只需要判断一下接收到数据的有效性,然后放入决定是否放到接收数据组。这种接收方式是最简单的,但是换来却是单片机需要频繁的进入中断,这样代码大的情况下,根据中断优先级的不同,可能会导致项目中某些程序被打断的情况出现。
使用DMA的接收方式可以实现内存与外设之间的数据直接传输,可以为CPU分担一些工作量,从而为CPU减轻负担,增加数据的传输处理速度。
HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size);
(一)函数功能是CPU以DAM模式发送 *pData指针指向的数据中固定长度的数据,并同时设置和使能DMA中断。
我们从代码中不难发现,DMA中断函数最终使用的居然是串口的中断函数。
进入到HAL_UART_Transmit_DMA这个函数中可以看到,它将DMA传输完成、半完成、错误的回调函数分别定向到了串口DMA传输完成、半完成、错误的回调函数
我们在DMA的函数中发现 static void UART_DMATransmitCplt(DMA_HandleTypeDef *hdma),打开函数功能的内部其实就是串口的发送完成中断函数 HAL_UART_TxCpltCallback(huart);可见即使使用DMA的发送方式,最后还是需要串口的发送完成中断函数相联系。
(二)串口的DMA接收
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
(三)串口DMA的停止函数
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart);
(四)查询DMA中有效的数据个数
#define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)
在宏定义中我们不难发现,其实就是读取CNDTR寄存器内的数值。这个寄存器存储的是DMA传输的剩余传输数量。在接收数据时,用定义的最大传输数量减去这个剩余数量就是已经接收的数据个数。在发送数据时,用定义的最大传输数量减去这个剩余数量就是已经发送的数据个数。
(五)串口DMA的接收函数
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart);
二:软件代码如下所示:
2.1 串口1的初始化函数:
void MX_USART1_UART_Init(void)
{
/* USER CODE BEGIN USART1_Init 0 */
/* USER CODE END USART1_Init 0 */
/* USER CODE BEGIN USART1_Init 1 */
/* USER CODE END USART1_Init 1 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART1_Init 2 */
__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
__HAL_UART_ENABLE_IT(&huart1,UART_IT_TC);
__HAL_DMA_DISABLE(&hdma_usart1_rx);
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,(uint8_t *)&RecBuffer1, reclength1);
__HAL_DMA_ENABLE(&hdma_usart1_rx);
/* USER CODE END USART1_Init 2 */
}
2.2 串口接收回调函数:
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance == USART1)
{
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, RecBuffer1, reclength1);
HAL_UART_Transmit(&huart1, RecBuffer1, Size, 100);
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
memset(RecBuffer1, 0, 32);
}
}
2.3 接收错误回调函数:
void HAL_UART_ErrorCallback(UART_HandleTypeDef * huart)
{
if(huart->Instance == USART1)
{
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, RecBuffer1, reclength1);
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
memset(RecBuffer1, 0, reclength1);
}
}
方法二:
使用编写空闲中断函数:
void UsartReceive_IDLE(UART_HandleTypeDef *huart)
{
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE) != RESET))
{
if(huart->Instance == USART1)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);
HAL_UART_DMAStop(huart);
usart1_rx_len = 256 - (__HAL_DMA_GET_COUNTER(&hdma_usart1_rx)); //
memcpy(Usart1_DEAL_RX_Buf, RecBuffer1, usart1_rx_len);
usart2_Flag = 1;
HAL_UART_Receive_DMA(&huart1, (uint8_t *)&RecBuffer1, 256);
}
}
}
然后再串口中断中调用该函数即可。
三:测试图片如下所示:
全部评论