UART使用DMA進行數據收發,實現功能,串口2發送指令到上位機,上位機返回數據給串口2,串口2收到數據后由串口1進行轉發,該功能為實驗功能
1、UART與DMA通道進行綁定
1 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) 2 { 3 4 GPIO_InitTypeDef GPIO_InitStruct = {0}; 5 if(uartHandle->Instance==USART1) 6 { 7 /* USER CODE BEGIN USART1_MspInit 0 */ 8 9 /* USER CODE END USART1_MspInit 0 */ 10 /* USART1 clock enable */ 11 __HAL_RCC_USART1_CLK_ENABLE(); 12 13 __HAL_RCC_GPIOA_CLK_ENABLE(); 14 /**USART1 GPIO Configuration 15 PA9 ------> USART1_TX 16 PA10 ------> USART1_RX 17 */ 18 GPIO_InitStruct.Pin = GPIO_PIN_9; 19 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; 20 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; 21 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 22 23 GPIO_InitStruct.Pin = GPIO_PIN_10; 24 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; 25 GPIO_InitStruct.Pull = GPIO_NOPULL; 26 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 27 28 /* USART1 DMA Init */ 29 /* USART1_TX Init */ 30 hdma_usart1_tx.Instance = DMA1_Channel4; 31 hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; 32 hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE; 33 hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE; 34 hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; 35 hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; 36 hdma_usart1_tx.Init.Mode = DMA_NORMAL; 37 hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW; 38 if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK) 39 { 40 Error_Handler(); 41 } 42 43 __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx); 44 45 /* USART1 interrupt Init */ 46 HAL_NVIC_SetPriority(USART1_IRQn, 0, 0); 47 HAL_NVIC_EnableIRQ(USART1_IRQn); 48 /* USER CODE BEGIN USART1_MspInit 1 */ 49 50 /* USER CODE END USART1_MspInit 1 */ 51 } 52 else if(uartHandle->Instance==USART2) 53 { 54 /* USER CODE BEGIN USART2_MspInit 0 */ 55 56 /* USER CODE END USART2_MspInit 0 */ 57 /* USART2 clock enable */ 58 __HAL_RCC_USART2_CLK_ENABLE(); 59 60 __HAL_RCC_GPIOA_CLK_ENABLE(); 61 /**USART2 GPIO Configuration 62 PA2 ------> USART2_TX 63 PA3 ------> USART2_RX 64 */ 65 GPIO_InitStruct.Pin = GPIO_PIN_2; 66 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; 67 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; 68 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 69 70 GPIO_InitStruct.Pin = GPIO_PIN_3; 71 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; 72 GPIO_InitStruct.Pull = GPIO_NOPULL; 73 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); 74 75 /* USART2 DMA Init */ 76 /* USART2_RX Init */ 77 hdma_usart2_rx.Instance = DMA1_Channel6; 78 hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; 79 hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; 80 hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE; 81 hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; 82 hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; 83 hdma_usart2_rx.Init.Mode = DMA_NORMAL; 84 hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW; 85 if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK) 86 { 87 Error_Handler(); 88 } 89 90 __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx); 91 92 /* USART2_TX Init */ 93 hdma_usart2_tx.Instance = DMA1_Channel7; 94 hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; 95 hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE; 96 hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE; 97 hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; 98 hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; 99 hdma_usart2_tx.Init.Mode = DMA_NORMAL; 100 hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW; 101 if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK) 102 { 103 Error_Handler(); 104 } 105 106 __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart2_tx); 107 108 /* USART2 interrupt Init */ 109 HAL_NVIC_SetPriority(USART2_IRQn, 0, 0); 110 HAL_NVIC_EnableIRQ(USART2_IRQn); 111 /* USER CODE BEGIN USART2_MspInit 1 */ 112 113 /* USER CODE END USART2_MspInit 1 */ 114 } 115 }
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usartx_xx)為UART與DMA綁定核心代碼
2、數據發送與接收
2.1 DMA中斷進行數據收發
2.1.1 DMA中斷
在HAL使用DMA方式進行串口數據傳輸時,DMA全局中斷模式是必需打開的,因此在DMA方式進行數據傳輸時(收,發),在數據傳輸過半,完成均會觸發DMA中斷
void DMA1_Channel6_IRQHandler(void) { /* USER CODE BEGIN DMA1_Channel6_IRQn 0 */ /* USER CODE END DMA1_Channel6_IRQn 0 */ HAL_DMA_IRQHandler(&hdma_usart2_rx); /* USER CODE BEGIN DMA1_Channel6_IRQn 1 */ /* USER CODE END DMA1_Channel6_IRQn 1 */ } /** * @brief This function handles DMA1 channel7 global interrupt. */ void DMA1_Channel7_IRQHandler(void) { /* USER CODE BEGIN DMA1_Channel7_IRQn 0 */ /* USER CODE END DMA1_Channel7_IRQn 0 */ HAL_DMA_IRQHandler(&hdma_usart2_tx); /* USER CODE BEGIN DMA1_Channel7_IRQn 1 */ /* USER CODE END DMA1_Channel7_IRQn 1 */ }
HAL_DMA_IRQHandler(&hdma_usart2_tx)會根據中斷標識,調用傳輸過半/完成/錯誤,回調函數,如下:
void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma) { uint32_t flag_it = hdma->DmaBaseAddress->ISR; uint32_t source_it = hdma->Instance->CCR; /* Half Transfer Complete Interrupt management ******************************/ if (((flag_it & (DMA_FLAG_HT1 << hdma->ChannelIndex)) != RESET) && ((source_it & DMA_IT_HT) != RESET)) { /* Disable the half transfer interrupt if the DMA mode is not CIRCULAR */ if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U) { /* Disable the half transfer interrupt */ __HAL_DMA_DISABLE_IT(hdma, DMA_IT_HT); } /* Clear the half transfer complete flag */ __HAL_DMA_CLEAR_FLAG(hdma, __HAL_DMA_GET_HT_FLAG_INDEX(hdma)); /* DMA peripheral state is not updated in Half Transfer */ /* but in Transfer Complete case */ if(hdma->XferHalfCpltCallback != NULL) { /* Half transfer callback */ hdma->XferHalfCpltCallback(hdma);//DMA傳輸過半回調 } } /* Transfer Complete Interrupt management ***********************************/ else if (((flag_it & (DMA_FLAG_TC1 << hdma->ChannelIndex)) != RESET) && ((source_it & DMA_IT_TC) != RESET)) { if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U) { /* Disable the transfer complete and error interrupt */ __HAL_DMA_DISABLE_IT(hdma, DMA_IT_TE | DMA_IT_TC); /* Change the DMA state */ hdma->State = HAL_DMA_STATE_READY; } /* Clear the transfer complete flag */ __HAL_DMA_CLEAR_FLAG(hdma, __HAL_DMA_GET_TC_FLAG_INDEX(hdma)); /* Process Unlocked */ __HAL_UNLOCK(hdma); if(hdma->XferCpltCallback != NULL) { /* Transfer complete callback */ hdma->XferCpltCallback(hdma);//DMA傳輸完成回調 } } /* Transfer Error Interrupt management **************************************/ else if (( RESET != (flag_it & (DMA_FLAG_TE1 << hdma->ChannelIndex))) && (RESET != (source_it & DMA_IT_TE))) { /* When a DMA transfer error occurs */ /* A hardware clear of its EN bits is performed */ /* Disable ALL DMA IT */ __HAL_DMA_DISABLE_IT(hdma, (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE)); /* Clear all flags */ hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << hdma->ChannelIndex); /* Update error code */ hdma->ErrorCode = HAL_DMA_ERROR_TE; /* Change the DMA state */ hdma->State = HAL_DMA_STATE_READY; /* Process Unlocked */ __HAL_UNLOCK(hdma); if (hdma->XferErrorCallback != NULL) { /* Transfer error callback */ hdma->XferErrorCallback(hdma);//傳輸錯誤回調 } } return; }
DMA傳輸中斷的回調函數是在哪被設置的呢?被這個問題困擾了幾個小時,原來是在DMA發送/接收數據函數中默認設置的,如下:
HAL_UART_Transmit_DMA(&huart2,cmd1,8);
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { uint32_t *tmp; /* Check that a Tx process is not already ongoing */ if (huart->gState == HAL_UART_STATE_READY) { if ((pData == NULL) || (Size == 0U)) { return HAL_ERROR; } /* Process Locked */ __HAL_LOCK(huart); huart->pTxBuffPtr = pData; huart->TxXferSize = Size; huart->TxXferCount = Size; huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_BUSY_TX; /* Set the UART DMA transfer complete callback */ huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt; /* Set the UART DMA Half transfer complete callback */ huart->hdmatx->XferHalfCpltCallback = UART_DMATxHalfCplt; /* Set the DMA error callback */ huart->hdmatx->XferErrorCallback = UART_DMAError; /* Set the DMA abort callback */ huart->hdmatx->XferAbortCallback = NULL; /* Enable the UART transmit DMA channel */ tmp = (uint32_t *)&pData; HAL_DMA_Start_IT(huart->hdmatx, *(uint32_t *)tmp, (uint32_t)&huart->Instance->DR, Size); /* Clear the TC flag in the SR register by writing 0 to it */ __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_TC); /* Process Unlocked */ __HAL_UNLOCK(huart); /* Enable the DMA transfer for transmit request by setting the DMAT bit in the UART CR3 register */ SET_BIT(huart->Instance->CR3, USART_CR3_DMAT); return HAL_OK; } else { return HAL_BUSY; } }
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { uint32_t *tmp; /* Check that a Rx process is not already ongoing */ if (huart->RxState == HAL_UART_STATE_READY) { if ((pData == NULL) || (Size == 0U)) { return HAL_ERROR; } /* Process Locked */ __HAL_LOCK(huart); huart->pRxBuffPtr = pData; huart->RxXferSize = Size; huart->ErrorCode = HAL_UART_ERROR_NONE; huart->RxState = HAL_UART_STATE_BUSY_RX; /* Set the UART DMA transfer complete callback */ huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt; /* Set the UART DMA Half transfer complete callback */ huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt; /* Set the DMA error callback */ huart->hdmarx->XferErrorCallback = UART_DMAError; /* Set the DMA abort callback */ huart->hdmarx->XferAbortCallback = NULL; /* Enable the DMA channel */ tmp = (uint32_t *)&pData; HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size); /* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */ __HAL_UART_CLEAR_OREFLAG(huart); /* Process Unlocked */ __HAL_UNLOCK(huart); /* Enable the UART Parity Error Interrupt */ SET_BIT(huart->Instance->CR1, USART_CR1_PEIE); /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */ SET_BIT(huart->Instance->CR3, USART_CR3_EIE); /* Enable the DMA transfer for the receiver request by setting the DMAR bit in the UART CR3 register */ SET_BIT(huart->Instance->CR3, USART_CR3_DMAR); return HAL_OK; } else { return HAL_BUSY; } }
就是說:只要你使用DMA發送/接收數據,就會自動設置DMA中斷回調函數,👍
轉到 UART_DMATransmitCplt等三個函數的定義看一下
static void UART_DMATransmitCplt(DMA_HandleTypeDef *hdma) { UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent; /* DMA Normal mode*/ if ((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U) { huart->TxXferCount = 0x00U; /* Disable the DMA transfer for transmit request by setting the DMAT bit in the UART CR3 register */ CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT); /* Enable the UART Transmit Complete Interrupt */ SET_BIT(huart->Instance->CR1, USART_CR1_TCIE); } /* DMA Circular mode */ else { #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) /*Call registered Tx complete callback*/ huart->TxCpltCallback(huart); #else /*Call legacy weak Tx complete callback*/ HAL_UART_TxCpltCallback(huart); #endif /* USE_HAL_UART_REGISTER_CALLBACKS */ } }
可以看的對應傳輸完成中斷,根據DMA數據傳輸模式的不同(正常/循環),會執行不同的代碼操作.
正常模式(一次傳輸),在DMA傳輸完成后會觸發UART的 USART_CR1_TCIE 中斷
循環模式,會調用huart的或用戶重寫的 TxCpltCallback 函數
在我的程序中DMA都被配置為正常模式,因此要處理DMA數據發送和接收完成的事件(中斷),應該在串口中斷處理程序中進行.循環模式應該可以通過指定
huart->TxCpltCallback函數或重寫HAL_UART_TxCpltCallback(huart)函數來處理發送/接收完成事件.
2.1.2 UART中斷中處理數據接收/發送完成事件
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_UART_IRQHandler(&huart2) 串口2中斷處理函數,內容如下:
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) { uint32_t isrflags = READ_REG(huart->Instance->SR); uint32_t cr1its = READ_REG(huart->Instance->CR1); uint32_t cr3its = READ_REG(huart->Instance->CR3); uint32_t errorflags = 0x00U; uint32_t dmarequest = 0x00U; /* If no error occurs */ errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE)); if (errorflags == RESET) { /* UART in mode Receiver -------------------------------------------------*/ if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)) { UART_Receive_IT(huart); return; } } /* If some errors occur */ if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET))) { /* UART parity error interrupt occurred ----------------------------------*/ if (((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET)) { huart->ErrorCode |= HAL_UART_ERROR_PE; } /* UART noise error interrupt occurred -----------------------------------*/ if (((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET)) { huart->ErrorCode |= HAL_UART_ERROR_NE; } /* UART frame error interrupt occurred -----------------------------------*/ if (((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET)) { huart->ErrorCode |= HAL_UART_ERROR_FE; } /* UART Over-Run interrupt occurred --------------------------------------*/ if (((isrflags & USART_SR_ORE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET)) { huart->ErrorCode |= HAL_UART_ERROR_ORE; } /* Call UART Error Call back function if need be --------------------------*/ if (huart->ErrorCode != HAL_UART_ERROR_NONE) { /* UART in mode Receiver -----------------------------------------------*/ if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)) { UART_Receive_IT(huart); } /* If Overrun error occurs, or if any error occurs in DMA mode reception, consider error as blocking */ dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR); if (((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || dmarequest) { /* Blocking error : transfer is aborted Set the UART state ready to be able to start again the process, Disable Rx Interrupts, and disable Rx DMA request, if ongoing */ UART_EndRxTransfer(huart); /* Disable the UART DMA Rx request if enabled */ if (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) { /* Set the UART DMA Abort callback : will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */ huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError; if (HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK) { /* Call Directly XferAbortCallback function in case of error */ huart->hdmarx->XferAbortCallback(huart->hdmarx); } } else { /* Call user error callback */ #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) /*Call registered error callback*/ huart->ErrorCallback(huart); #else /*Call legacy weak error callback*/ HAL_UART_ErrorCallback(huart); #endif /* USE_HAL_UART_REGISTER_CALLBACKS */ } } else { /* Call user error callback */ #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) /*Call registered error callback*/ huart->ErrorCallback(huart); #else /*Call legacy weak error callback*/ HAL_UART_ErrorCallback(huart); #endif /* USE_HAL_UART_REGISTER_CALLBACKS */ } } else { /* Non Blocking error : transfer could go on. Error is notified to user through user error callback */ #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) /*Call registered error callback*/ huart->ErrorCallback(huart); #else /*Call legacy weak error callback*/ HAL_UART_ErrorCallback(huart); #endif /* USE_HAL_UART_REGISTER_CALLBACKS */ huart->ErrorCode = HAL_UART_ERROR_NONE; } } return; } /* End if some error occurs */ /* UART in mode Transmitter ------------------------------------------------*/ if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET)) { UART_Transmit_IT(huart); return; } /* UART in mode Transmitter end --------------------------------------------*/ if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET)) { UART_EndTransmit_IT(huart); return; } }
代碼很多,實際僅是根據UART是接收模式還是發送模式來進行中斷處理函數調用.
UART是如何切換接收/發送模式的,開始我也沒用搞明白,后來想了下在調用DMA進行數據發送時,串口就應該時被設置為發送模式,相反調用DMA接收時,UART就切換為接收模式.
HAL_UART_Transmit_DMA(&huart2,cmd1,8);//DMA方式通過UART2發送數據,這樣UART2就被切換到發送模式
HAL_UART_Receive_DMA(huart,Usart2_Rx_Buffer,USART2_RX_BUFFER_SIZE);//開啟UART2的DMA數據接收,UART2切換為接收模式
2.1.3 UART接收/發送模式下,執行的處理程序
接收:UART_Receive_IT(huart);
發送:UART_EndTransmit_IT(huart);
UART_EndTransmit_IT 函數,如下:
static HAL_StatusTypeDef UART_EndTransmit_IT(UART_HandleTypeDef *huart) { /* Disable the UART Transmit Complete Interrupt */ __HAL_UART_DISABLE_IT(huart, UART_IT_TC); /* Tx process is ended, restore huart->gState to Ready */ huart->gState = HAL_UART_STATE_READY; #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) /*Call registered Tx complete callback*/ huart->TxCpltCallback(huart); #else /*Call legacy weak Tx complete callback*/ HAL_UART_TxCpltCallback(huart); #endif /* USE_HAL_UART_REGISTER_CALLBACKS */ return HAL_OK; }
核心邏輯就是調用傳輸完成回調函數
因此對於DMA數據發送,中斷發生及處理的過程如下:
2.1.4 完整的DMA UART數據發送處理程序如下
main.c

int main(void) { /* USER CODE BEGIN 1 */ extern Worker_TypeDef Worker; /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ uint8_t cmd1[] = {0x01,0x04,0x00,0x00,0x00,0x05,0x30,0x09}; /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_TIM2_Init(); MX_USART1_UART_Init(); MX_USART2_UART_Init(); /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ HAL_UART_Transmit_DMA(&huart2,cmd1,8); HAL_Delay(500); /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
uart.c
/* USER CODE BEGIN 1 */ /******************************************************************** 串口發送完成中斷回調 ********************************************************************/ void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart == &huart2) //判斷是否是串口1 { //切換485芯片為接收模式 HAL_GPIO_TogglePin(UART2_CTL_GPIO_Port, UART2_CTL_Pin); //切換uart2到接收模式 HAL_UART_Receive_DMA(huart,Usart2_Rx_Buffer,USART2_RX_BUFFER_SIZE); } } /* USER CODE END 1 */
2.2 DMA數據接收及處理
這一部分功能的目的是在DMA模式下,UART2接收完數據后,馬上將數據從UART1發出去
2.2.1 uart.c中定義DMA RX緩沖區
/* USER CODE BEGIN 0 */ #define USART2_RX_BUFFER_SIZE 40 uint8_t Usart2_Rx_Buffer[USART2_RX_BUFFER_SIZE] = {0x00}; /* USER CODE END 0 */
2.2.2 開啟UART接收模式
在DMA數據發送完成后,會立即切換UART為接收模式
2.2.3 DMA UART 數據接收
DMA數據發送/接收過半,完成均會產生DMA終端,RX正在DMA通道6上引發中斷,TX在通道7上引發中斷,雖然通道不同,但中斷邏輯與DMA中斷處理程序完全一樣.
只是發送/接收數據設置的中斷回調函數不同.HAL_UART_Receive_DMA 接收函數中設置的中斷回調函數為:
/* Set the UART DMA transfer complete callback */ huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt; /* Set the UART DMA Half transfer complete callback */ huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt; /* Set the DMA error callback */ huart->hdmarx->XferErrorCallback = UART_DMAError; /* Set the DMA abort callback */ huart->hdmarx->XferAbortCallback = NULL;
UART_DMAReceiveCplt回調函數內容如下:
static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma) { UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent; /* DMA Normal mode*/ if ((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U) { huart->RxXferCount = 0U; /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */ CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE); CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE); /* Disable the DMA transfer for the receiver request by setting the DMAR bit in the UART CR3 register */ CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR); /* At end of Rx process, restore huart->RxState to Ready */ huart->RxState = HAL_UART_STATE_READY; } #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) /*Call registered Rx complete callback*/ huart->RxCpltCallback(huart); #else /*Call legacy weak Rx complete callback*/ HAL_UART_RxCpltCallback(huart); #endif /* USE_HAL_UART_REGISTER_CALLBACKS */ }
這里可以看到,接收模式下不區分是否為循環狀態,也不會再次觸發UART中斷,而是直接調用 RxCpltCallback 接收完成回調函數
因此DMA數據接收及處理過程如下:
2.2.4 完整的DMA數據接入代碼如下:
uart.c
/* USER CODE BEGIN 1 */ /******************************************************************** 串口發送完成中斷回調 ********************************************************************/ void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { if(huart == &huart2) //判斷是否是串口1 { //切換485芯片為接收模式 HAL_GPIO_TogglePin(UART2_CTL_GPIO_Port, UART2_CTL_Pin); //切換uart2到接收模式 HAL_UART_Receive_DMA(huart,Usart2_Rx_Buffer,USART2_RX_BUFFER_SIZE); } } /******************************************************************** 串口接收完成中斷回調 ********************************************************************/ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart == &huart2)//判斷是否是串口1 { uint8_t temp = __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);// 獲取DMA中未使用的字節個數 uint8_t length = USART2_RX_BUFFER_SIZE - temp; HAL_UART_Transmit_DMA(&huart1,Usart2_Rx_Buffer,length); } } /* USER CODE END 1 */
僅添加紅色部分代就可處理數據傳輸完成中斷事件👍
3、DMA中斷數據接收的問題
在進行DMA中斷方式數據接收時,當數據接收完成時會調用 HAL_UART_RxCpltCallback 回調函數,但這里所謂的數據接收完成是 "Rx buffer接收緩沖區滿" .
因此上面的接收的例子中,只有 Usart2_Rx_Buffer裝滿40個字節才會調用 HAL_UART_RxCpltCallback.很多時候,我們很難一次將緩沖區填滿,除非進行固定長度的數據收發.對於任意長度的數據接收,上面的程序就不能滿足需要了.
要實現任意長度的數據接收(不超出Rx緩沖區大小),需要對以上程序進行如下改造:
3.1 屏蔽串口接收完成回調函數 HAL_UART_RxCpltCallback,因為需要在數據未填滿緩沖區的情況下進行數據處理
3.2開啟 UART 空閑中斷,實現數據接收完成時觸發空閑中斷
main.c
int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_TIM2_Init(); MX_USART1_UART_Init(); MX_USART2_UART_Init(); /* USER CODE BEGIN 2 */ __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE); /* USER CODE END 2 */
3.3 編寫自定義串口空閑中斷處理程序,並在UART中斷除程序中調用
stm32f1xx_it.c
void My_UART_IRQHandler(UART_HandleTypeDef *huart) { if(huart == &huart2) //判斷是否是串口1 { if(RESET != __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE)) //判斷是否是空閑中斷 { __HAL_UART_CLEAR_IDLEFLAG(huart); //清楚空閑中斷標志(否則會一直不斷進入中斷) HAL_UART_DMAStop(huart); //HAL_UART_DMAResume uint8_t temp = __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);// 獲取DMA中未傳輸的數據個數 uint8_t length = USART2_RX_BUFFER_SIZE - temp; HAL_UART_Transmit_DMA(&huart1,Usart2_Rx_Buffer,length); } } }
/** * @brief This function handles USART2 global interrupt. */ void USART2_IRQHandler(void) { /* USER CODE BEGIN USART2_IRQn 0 */ My_UART_IRQHandler(&huart2); /* USER CODE END USART2_IRQn 0 */ HAL_UART_IRQHandler(&huart2); /* USER CODE BEGIN USART2_IRQn 1 */ /* USER CODE END USART2_IRQn 1 */ }
3.4 總結
這樣一來數據接收完成后,觸發UART串口空閑中斷,在 USART2_IRQHandler 中對空閑中斷進行處理(獲取數據轉發出去).
這樣雖然實現了任意長度的數據接收,但是本人覺得美中不足之處在於,UART空閑中斷的處理方式上.HAL庫在DMA中斷的處理上采用了回調callback的方式,本來也想是否HAL_UART_IRQHandler(&huart2) 也支持callback處理空閑中斷,但是沒用找到,因此代碼稍顯的強侵入了.如果哪位朋友有更好的辦法,不妨留言交流一下.