0. 背景 STM32H743/750中串口具有DMA传输功能,配合超时中断可实现灵活接收数据,网上流传的方法一般是把DMA设置为常规模式,其缺点是一旦一次性要接收的数据超过DMA缓冲区大小,就无法正确处理。 我们可以将DMA设置为循环模式,再结合超时中断,可以解决接收数据超过DMA缓冲区大小的问题。 1. 基本设置 file:///C:/Users/ThinkPad/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg 2. 修改代码 (1) 新建暂停DMA接收函数 static HAL_StatusTypeDef HAL_UART_DMAStopRx(UART_HandleTypeDef *huart) { /* Stop UART DMA Rx request if ongoing */ if ((huart->RxState == HAL_UART_STATE_BUSY_RX) && (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))) { CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR); /* Abort the UART DMA Rx channel */ if (huart->hdmarx != NULL) { HAL_DMA_Abort(huart->hdmarx); } //UART_EndRxTransfer(huart); /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */ CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE)); CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE); /* At end of Rx process, restore huart->RxState to Ready */ huart->RxState = HAL_UART_STATE_READY; } return HAL_OK; } (2) 开始接收函数,在其中配置超时处理: void User_UART_Start(uint8_t index) { if (index == Huart1_Index) { HAL_UART_ReceiverTimeout_Config(&huart1, 220); HAL_UART_EnableReceiverTimeout(&huart1); __HAL_UART_DISABLE_IT(uartHandle, UART_IT_ERR); __HAL_UART_DISABLE_IT(uartHandle, UART_IT_PE); /*Enable the TIMEOUT Interrupt*/ __HAL_UART_ENABLE_IT(uartHandle, UART_IT_RTO); HAL_UART_Receive_DMA (&huart1, User_Usart_Buffer[Huart1_Index].RxBuf, UART_RX_BUF_SIZE); } } HAL_UART_ReceiverTimeout_Config函数中第2个参数是超时判断阈值,其时间单位为 (1/串口波特率)秒; (3)中断处理流程 1)修改DMA传输半完成、全部完成的回调函数: void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { SCB_InvalidateDCache_by_Addr((uint32_t *)(User_Usart_Buffer[Huart1_Index].RxBuf), sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 8); // 接收到数据处理函数 User_UART_RxCpltCallback_1(User_Usart_Buffer[Huart1_Index].RxBuf, sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 2); Uart_Rcv_HalfRcv_1 = 1; } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { SCB_InvalidateDCache_by_Addr((uint32_t *)(&(User_Usart_Buffer[Huart1_Index].RxBuf[sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 2])), sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 8); // 接收到数据处理函数 User_UART_RxCpltCallback_1(&(User_Usart_Buffer[Huart1_Index].RxBuf[sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 2]), sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 2); Uart_Rcv_HalfRcv_1 = 0; } } 注: l Uart_Rcv_HalfRcv_1为传输半完成、全部完成的标记; l User_UART_RxCpltCallback_1为用户处理函数,传递参数是本次收到数据首地址和数据字节长度; l 这两个回调函数的目的是将DMA接收缓冲区分为上下两个区间,每个区间接收满了可独立处理,又不影响接着接收另一个区间; l SCB_InvalidateDCache_by_Addr这个函数在打开了数据Dcache时要使用,否则DMA可以报告收到数据的字节长度,但读取的数据都是0x00; 2)增加一个超时中断处理函数,用于处理区间未存满的情况 void UsartReceive_TIMEOUT(UART_HandleTypeDef *huart) { uint16_t i = 0, len; if ((__HAL_UART_GET_FLAG(huart, UART_FLAG_RTOF) != RESET)) { if (huart->Instance == USART1) { __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_RTOF); i = huart->Instance->ISR; i = huart->Instance->RDR; i = __HAL_DMA_GET_COUNTER(huart1.hdmarx); // 剩余 HAL_UART_DMAStopRx(huart); // 接收到的数据长度 len = UART_RX_BUF_SIZE - i; if (i != UART_RX_BUF_SIZE) // 有数据 { if (Uart_Rcv_HalfRcv_1 > 0) { SCB_InvalidateDCache_by_Addr((uint32_t *)(&(User_Usart_Buffer[Huart1_Index].RxBuf[sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 2])), sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 8); // 接收到数据处理函数 User_UART_RxCpltCallback_1(&(User_Usart_Buffer[Huart1_Index].RxBuf[UART_RX_BUF_SIZE / 2]), len - UART_RX_BUF_SIZE / 2); } else { SCB_InvalidateDCache_by_Addr((uint32_t *)(&(User_Usart_Buffer[Huart1_Index].RxBuf[0])), sizeof(User_Usart_Buffer[Huart1_Index].RxBuf) / 8); User_UART_RxCpltCallback_1(User_Usart_Buffer[Huart1_Index].RxBuf, len); } } else { __HAL_UART_CLEAR_OREFLAG(huart); } // 开始接收 User_UART_Start(Huart1_Index); Uart_Rcv_HalfRcv_1 = 0; } } } 注: l Uart_Rcv_HalfRcv_1 为传输半完成、全部完成的标记,由此可知本次超时中断发生时,DMA接收到的数据的位置,是位于DMA接收区的前半区和后半区; l __HAL_DMA_GET_COUNTER函数读取的是DMA剩余空间长度; l 在此中断处理函数中,会重新开始一次DMA接收; 3) 修改串口中断服务函数 void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ UsartReceive_TIMEOUT(&huart1); /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ //UsartReceive_IDLE(&huart1); /* USER CODE END USART1_IRQn 1 */ } 将超时处理函数放置于默认处理函数HAL_UART_IRQHandler之前,是因为在HAL_UART_IRQHandler函数中仅将超时中断做ERROR标记,而不处理;
|