HAL UART DMA 数据收发
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处理空闲中断,但是没用找到,因此代码稍显的强侵入了.如果哪位朋友有更好的办法,不妨留言交流一下.
作者:DW039
出处:http://www.cnblogs.com/dw039
本文由DW039原创并发布于博客园,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。