話不多說,先貼上主要的外設初始化流程:
int main(void) { /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_USART2_UART_Init(); /* Infinite loop */ while (1) { if(UART2_Length && Tx2_Complete_Flag == 0) { DMA_Tx2_Data(User_UART2_Buffer, UART2_Length); UART2_Length = 0; } } }
分析一下初始化流程:
GPIO的初始化:由於沒有使用到GPIO的初始化,因此GPIO的初始化還是以時鍾使能為主;
void MX_GPIO_Init(void) { /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOF_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); }
DMA初始化:使能了DMA時鍾,配置了DMA通道中斷的中斷優先級並使能了DMA中斷函數;
void MX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Channel4_5_6_7_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Channel4_5_6_7_IRQn, 2, 0); HAL_NVIC_EnableIRQ(DMA1_Channel4_5_6_7_IRQn); }
串口UART初始化:也是整個流程的重點配置;
首先是對UART2串口的配置,一如往常的配置;
接下來在HAL_UART_MspInit是對串口引腳和DMA的配置:
使能了UART2和GPIOA的時鍾;
配置PA2和PA3為引腳復用;
配置DMA的TX和RX,這里添加了兩行代碼來設定DMA源地址和目標地址;
鏈接DMA句柄和UART2的DMA句柄;
設置串口中斷函數的中斷優先級;
開啟UART的IDLE中斷並使能串口;
清除串口的TC標志位,防止第一次接收數據產生錯誤;
分別使能DMARx/Tx的TC中斷,並使能其對應的串口DMA中斷使能位;
開啟DMARx中斷,並關閉DMATx中斷;
uint8_t UART2_Tx_Buffer[TX_BUF_LEN]; uint8_t UART2_Rx_Buffer[RX_BUF_LEN]; void MX_USART2_UART_Init(void) { huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if (HAL_UART_Init(&huart2) != HAL_OK) { Error_Handler(); } } void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(uartHandle->Instance==USART2) { /* USART2 clock enable */ __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**USART2 GPIO Configuration PA2 ------> USART2_TX PA3 ------> USART2_RX */ GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF1_USART2; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USART2 DMA Init */ /* USART2_RX Init */ hdma_usart2_rx.Instance = DMA1_Channel5; hdma_usart2_rx.Instance->CPAR = (uint32_t)(&(USART2->RDR)); hdma_usart2_rx.Instance->CMAR = (uint32_t)UART2_Rx_Buffer; hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_rx.Init.Mode = DMA_NORMAL; hdma_usart2_rx.Init.Priority = DMA_PRIORITY_HIGH; if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx); /* USART2_TX Init */ hdma_usart2_tx.Instance = DMA1_Channel4; hdma_usart2_tx.Instance->CPAR = (uint32_t)(&(USART2->TDR)); hdma_usart2_tx.Instance->CMAR = (uint32_t)UART2_Tx_Buffer; hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_tx.Init.Mode = DMA_NORMAL; hdma_usart2_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH; if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart2_tx); /* USART2 interrupt Init */ HAL_NVIC_SetPriority(USART2_IRQn, 1, 0); HAL_NVIC_EnableIRQ(USART2_IRQn); /* USER CODE BEGIN USART2_MspInit 1 */ __HAL_UART_ENABLE_IT(uartHandle, UART_IT_IDLE); __HAL_UART_ENABLE(uartHandle); __HAL_UART_CLEAR_FLAG(uartHandle, UART_CLEAR_TCF); __HAL_UART_CLEAR_IT(uartHandle, UART_CLEAR_TCF); __HAL_DMA_ENABLE_IT(&hdma_usart2_rx, DMA_IT_TC); SET_BIT(uartHandle->Instance->CR3, USART_CR3_DMAR); __HAL_DMA_DISABLE(&hdma_usart2_tx); __HAL_DMA_ENABLE_IT(&hdma_usart2_tx, DMA_IT_TC); SET_BIT(uartHandle->Instance->CR3, USART_CR3_DMAT); __HAL_DMA_ENABLE(&hdma_usart2_rx); /* USER CODE END USART2_MspInit 1 */ } }
有一段代碼很有意思:
這段代碼將uartHandle.hdmarx與hdma_usart2_rx的句柄鏈接在一起,意味着兩者可以在以后的配置和使用中是對等的,用哪個進行配置都可以;
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx);
重點:
由於HAL庫中並沒有包含對IDLE中斷的回調處理函數,因此可以自己通過修改HAL庫函數來實現IDLE回調函數;
首先在HAL_UART_IRQHandler中添加對IDLE回調函數的判斷;
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) { uint32_t isrflags = READ_REG(huart->Instance->ISR); uint32_t cr1its = READ_REG(huart->Instance->CR1); uint32_t cr3its; uint32_t errorflags; /* If no error occurs */ errorflags = (isrflags & (uint32_t)(USART_ISR_PE | USART_ISR_FE | USART_ISR_ORE | USART_ISR_NE)); if (errorflags == RESET) { /* UART in mode Receiver ---------------------------------------------------*/ if(((isrflags & USART_ISR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET)) { UART_Receive_IT(huart); return; } } /* If some errors occur */ cr3its = READ_REG(huart->Instance->CR3); if( (errorflags != RESET) && ( ((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)) ) { /* UART parity error interrupt occurred -------------------------------------*/ if(((isrflags & USART_ISR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET)) { __HAL_UART_CLEAR_IT(huart, UART_CLEAR_PEF); huart->ErrorCode |= HAL_UART_ERROR_PE; } /* UART frame error interrupt occurred --------------------------------------*/ if(((isrflags & USART_ISR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET)) { __HAL_UART_CLEAR_IT(huart, UART_CLEAR_FEF); huart->ErrorCode |= HAL_UART_ERROR_FE; } /* UART noise error interrupt occurred --------------------------------------*/ if(((isrflags & USART_ISR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET)) { __HAL_UART_CLEAR_IT(huart, UART_CLEAR_NEF); huart->ErrorCode |= HAL_UART_ERROR_NE; } /* UART Over-Run interrupt occurred -----------------------------------------*/ if(((isrflags & USART_ISR_ORE) != RESET) && (((cr1its & USART_CR1_RXNEIE) != RESET) || ((cr3its & USART_CR3_EIE) != RESET))) { __HAL_UART_CLEAR_IT(huart, UART_CLEAR_OREF); 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_ISR_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 */ if (((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))) { /* 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; /* Abort DMA RX */ if(HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK) { /* Call Directly huart->hdmarx->XferAbortCallback function in case of error */ huart->hdmarx->XferAbortCallback(huart->hdmarx); } } else { /* Call user error callback */ HAL_UART_ErrorCallback(huart); } } else { /* Call user error callback */ HAL_UART_ErrorCallback(huart); } } else { /* Non Blocking error : transfer could go on. Error is notified to user through user error callback */ HAL_UART_ErrorCallback(huart); huart->ErrorCode = HAL_UART_ERROR_NONE; } } return; } /* End if some error occurs */ #if !defined(STM32F030x6) && !defined(STM32F030x8)&& !defined(STM32F070xB)&& !defined(STM32F070x6)&& !defined(STM32F030xC) /* UART wakeup from Stop mode interrupt occurred ---------------------------*/ if(((isrflags & USART_ISR_WUF) != RESET) && ((cr3its & USART_CR3_WUFIE) != RESET)) { __HAL_UART_CLEAR_IT(huart, UART_CLEAR_WUF); /* Set the UART state ready to be able to start again the process */ huart->gState = HAL_UART_STATE_READY; huart->RxState = HAL_UART_STATE_READY; HAL_UARTEx_WakeupCallback(huart); return; } #endif /* !defined(STM32F030x6) && !defined(STM32F030x8)&& !defined(STM32F070xB)&& !defined(STM32F070x6)&& !defined(STM32F030xC) */ /* UART in mode Transmitter ------------------------------------------------*/ if(((isrflags & USART_ISR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET)) { UART_Transmit_IT(huart); return; } if(((isrflags & USART_ISR_IDLE) != RESET) && ((cr1its & USART_CR1_IDLEIE) != RESET)) { HAL_UART_IdleCpltCallback(huart); return; } /* UART in mode Transmitter (transmission end) -----------------------------*/ if(((isrflags & USART_ISR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET)) { UART_EndTransmit_IT(huart); return; } }
接下來在stm32f0xx_hal_uart.c的庫代碼中添加IDLE的回調函數HAL_UART_IdleCpltCallback()的定義;
__weak void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart) { /* Prevent unused argument(s) compilation warning */ UNUSED(huart); /* NOTE: This function Should not be modified, when the callback is needed, the HAL_UART_TxCpltCallback could be implemented in the user file */ }
最后別忘了在對應的stm32f0xx_hal_uart.h中添加對HAL_UART_IdleCpltCallback()的聲明;
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart);
接下來看如何使用這個添加的IDLE回調函數:
在串口中斷函數USART2_IRQHandler中調用HAL_UART_IRQHandler(&huart2);以實現在IDLE中斷標志位置位后調用HAL_UART_IdleCpltCallback回調函數;
然后串口傳輸完成中斷TC的判斷直接在串口中斷函數中實現,而不引用相應的TC回調函數;
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart) { uint16_t len = 0; if((__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET)) { __HAL_UART_CLEAR_IDLEFLAG(huart); __HAL_DMA_DISABLE(huart->hdmarx); len = RX_BUF_LEN - huart2.hdmarx->Instance->CNDTR; memcpy(User_UART2_Buffer, UART2_Rx_Buffer, len); huart2.hdmarx->Instance->CNDTR = RX_BUF_LEN; __HAL_DMA_ENABLE(huart->hdmarx); UART2_Length = len; }else { UART2_Length = 0; } } /** * @brief This function handles USART2 global interrupt / USART2 wake-up interrupt through EXTI line 26. */ void USART2_IRQHandler(void) { /* USER CODE BEGIN USART2_IRQn 0 */ UART_ErrorFlag_Clear(&huart2); /* USER CODE END USART2_IRQn 0 */ HAL_UART_IRQHandler(&huart2); /* USER CODE BEGIN USART2_IRQn 1 */ if(__HAL_UART_GET_IT(&huart2, UART_IT_TC) != RESET) { __HAL_UART_CLEAR_IT(&huart2, UART_IT_TC); __HAL_UART_DISABLE_IT(&huart2, UART_IT_TC); Tx2_Complete_Flag = 0; } /* USER CODE END USART2_IRQn 1 */ }
這里大致講解一下DMA傳輸的流程:
當F072接收到一串數據后,進入串口中斷函數,清除IDLE中斷標志位,關閉串口DMA接收rx,獲取接收到的數據len,將接收到的數據UART2_Rx_Buffer拷貝到緩沖User_UART2_Buffer中,重新設置串口DMA接收長度,開啟串口DMA接收,將接收到的數據長度賦值給UART2_Length,如果沒有接收到數據,就將UART2_Length賦值為0;
接下來在main中判斷,若接收到數據UART2_Length不為0,同時Tx2_Complete_Flag為0表示此時串口Tx傳輸空閑,就執行傳輸命令;
while (1) { if(UART2_Length && Tx2_Complete_Flag == 0) { DMA_Tx2_Data(User_UART2_Buffer, UART2_Length); UART2_Length = 0; } }
將接收到的數據User_UART2_Buffer和傳送給UART2_Tx_Buffer,同時置位Tx2_Complete_Flag標志表示此時開始傳輸,同時重新設置串口DMATX的傳輸數據長度,然后開啟串口DMA的傳輸;
void DMA_Tx2_Data(uint8_t *data, uint16_t size) { while(Tx2_Complete_Flag); Tx2_Complete_Flag = 1; memcpy(UART2_Tx_Buffer, data, size); huart2.hdmatx->Instance->CNDTR = TX_BUF_LEN; __HAL_DMA_ENABLE(&hdma_usart2_tx); }
當DMA傳輸完成后,觸發DMA的TC中斷標志進入DMA中斷函數,清除DMA通道的TC中斷標志位,關閉DMATX通道,置位Tx2_Complete_Flag標志表示傳輸開始,同時開啟串口TC中斷;
void DMA1_Channel4_5_6_7_IRQHandler(void) { if(__HAL_DMA_GET_FLAG(&hdma_usart2_tx, DMA_FLAG_TC4) != RESET) { __HAL_DMA_CLEAR_FLAG(&hdma_usart2_tx, DMA_FLAG_TC4); __HAL_DMA_DISABLE(&hdma_usart2_tx); Tx2_Complete_Flag = 1; __HAL_UART_ENABLE_IT(&huart2, UART_IT_TC); } }
當串口UART傳輸完成后,接下來在串口中斷函數中檢測到串口TC傳輸完成標志置位,清除TC標志,同時關閉串口TC中斷,置位Tx2_Complete_Flag標志為0,表示此時串口接收空閑;