1 初始化代码分析
1.1 DMA初始化代码
作用是设置对应DMA通道的中断优先级;
void MX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Channel4_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn); /* DMA1_Channel5_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn); }
1.2 DMA配置以及串口连接初始化
初始化串口IO,以及配置串口与DMA通道相连接;
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if(uartHandle->Instance==USART1) { /* USER CODE BEGIN USART1_MspInit 0 */ /* USER CODE END USART1_MspInit 0 */ /* USART1 clock enable */ __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /**USART1 GPIO Configuration PA9 ------> USART1_TX PA10 ------> USART1_RX */ GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct.Pin = GPIO_PIN_10; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /* USART1 DMA Init */ /* USART1_RX Init */ hdma_usart1_rx.Instance = DMA1_Channel5; hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE; //禁止外设地址递增模式 hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE; //使能目标地址递增模式 hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_rx.Init.Mode = DMA_NORMAL; hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW; if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx); //连接串口和DMA! /*__HAL_LINKDMA 的主要作用是传入的uartHandle结构体中的DMA对象设置为目标DMA ; */ /* USART1_TX Init */ hdma_usart1_tx.Instance = DMA1_Channel4; hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_tx.Init.Mode = DMA_NORMAL; hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW; if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK) { Error_Handler(); } __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx); /* USER CODE BEGIN USART1_MspInit 1 */ /* USER CODE END USART1_MspInit 1 */ } }
2 DMA中断处理函数
当DMA中断标志位置位时,就会进入中断,利用HAL_DMA_IRQHandler中断处理函数处理;
该函数会检测中断标志位,根据相应的中断标志位调用对应的回调函数;
void DMA1_Channel4_IRQHandler(void) { /* USER CODE BEGIN DMA1_Channel4_IRQn 0 */ /* USER CODE END DMA1_Channel4_IRQn 0 */ HAL_DMA_IRQHandler(&hdma_usart1_tx); /* USER CODE BEGIN DMA1_Channel4_IRQn 1 */ /* USER CODE END DMA1_Channel4_IRQn 1 */ } void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma) { uint32_t flag_it = hdma->DmaBaseAddress->ISR; //读取中断标志位寄存器 uint32_t source_it = hdma->Instance->CCR; //读取DMA通道配置寄存器 /* 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); } } /* 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); } } /* 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; }
3 DMA使用
3.1 DMA传输函数(HAL_UART_Transmit_DMA)
调用该函数后,由于在DMA的初始化中调用过__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);即将UART_HandleTypeDef结构体中的DMA对象变量设置为目标DMA,这样当调用HAL_UART_Transmit_DMA函数时,该函数就会将目标DMA中的中断回调函数注册好,当传输中断(传输完成或其他情况)发生时在DMA中断服务函数HAL_DMA_IRQHandler会调用相应回调函数;
此外,HAL_UART_Transmit_DMA中调用了HAL_DMA_Start_IT函数,该函数的的作用配置目标DMA参数,并启动传输,在执行完HAL_DMA_Start_IT函数后(即传输完成),会做一些寄存器的位操作;HAL_DMA_Start_IT详见后面解释;
注1:不能重复调用HAL_UART_Transmit_DMA ( ) ; 会漏掉数据,因为在传输的过程中huart->gState会被设置成HAL_UART_STATE_BUSY_TX,因此如果在DMA工作(数据搬移)期间再次调用该函数,则会漏发!最好是在DMA传输完成的回调函数void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)里再去注册传输,或者利用标志位;DMA的回调函数在DMA中断中的HAL_DMA_IRQHandler( )中断处理函数里面调用!
⭐注2:若要使用HAL_UART_Transmit_DMA ( ) ; 一定要在建立工程的时候使能串口全局中断 ! ! !
由下述代码可知,在没有使能USART串口全局中断的情况下,当第一次调用HAL_UART_Transmit_DMA ( ) ;时,由于HAL库中默认使能DMA的半传输中断,DMA完成一次传输的一半后,会触发一次DMA的传输中断,此时DMA会进入中断服务函数HAL_DMA_IRQHandler(&hdma_usart1_tx);并执行半传输中断的服务函数;
UART_DMATxHalfCplt( ) ; 这个函数执行的就是串口半传输回调函数HAL_UART_TxHalfCpltCallback(huart);
在处理完半传输中断后,中断服务函数还要再处理DMA的传输完成中断;这个时候HAL_DMA_IRQHandler()中会调用UART_DMATransmitCplt( );传输完成回调函数(该函数在DMA单次传输模式下不会调用串口发送完成回调函数,只有在循环模式下才会),单次传输模式下该函数会失能USART的DMA传输功能(RESET_BIT(huart->Instance->CR3, USART_CR3_DMAT);),这样一次DMA传输的中断处理流程就结束了;而当再次调用HAL_UART_Transmit_DMA ( ) ;如果串口状态是HAL_UART_STATE_READY则会在这个函数最后打开USART的DMA传输功能(SET_BIT(huart->Instance->CR3, USART_CR3_DMAT); ) , 但是由于第一次调用HAL_UART_Transmit_DMA ( ) ;时,将huart->gState = HAL_UART_STATE_BUSY_TX;,而后面的代码都没由解除huart->gState的繁忙状态,因此第一次调用HAL_UART_Transmit_DMA ( ) ;后在调用时该函数就会返回HAL_BUSY;这也是为什么网上很多人问HAL_UART_Transmit_DMA ( ) ;调用一次后无法再次调用启动传输;
解决方法1:能串口全局中断;
对应USART数据发送有两个中断标志,一个是TXE = 发送数据寄存器空,另一个是TC = 发送结束;当TDR中的数据传送到移位寄存器后,TXE被设置,此时移位寄存器开始向TX信号线按位传输数据,但因为TDR已经变空,程序可以把下一个要发送的字节(操作USART_DR)写入TDR中,而不必等到移位寄存器中所有位发送结束,所有位发送结束时(送出停止位后)硬件会设置TC标志。
在程序初始化完成阶段,串口的TXEIE与TCIE是默认为0的,同时DMA调用HAL_UART_Transmit_DMA ( ) ; 函数中会执行 __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_TC);清除TC位,也就是说在DMA传输完成期间,串口发送中断是不会被触发的;(注:调用串口中断接收或发送函数才会置位这些发送或接收中断使能位!)
当DMA单次传输模式下完成一次传输后,在DMA中断服务函数中调用UART_DMATransmitCplt( )传输完成回调函数,该函数中会调用SET_BIT(huart->Instance->CR1, USART_CR1_TCIE);使能串口的发送中断,这样就会触发一次串口接收中断进入串口服务函数HAL_UART_IRQHandler(&huart1);
在串口中断函数中又会执行UART_EndTransmit_IT()函数,在这个函数会内恢复串口状态huart->gState = HAL_UART_STATE_READY; 并通过执行 __HAL_UART_DISABLE_IT(huart, UART_IT_TC); 失能TCIE使能接收中断;
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(USART1_IRQn); void USART1_IRQHandler(void) { /* USER CODE BEGIN USART1_IRQn 0 */ /* USER CODE END USART1_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USART1_IRQn 1 */ /* USER CODE END USART1_IRQn 1 */ }解决方法2:由于单次串口DMA发送模式下HAL库不执行串口的传输完成回调函数,因此在半传输回调函数中HAL_UART_TxHalfCpltCallback( )函数内修改huart->gStat状态为HAL_UART_STATE_READY!
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart){ if(huart->Instance == USART1){ huart->gState = HAL_UART_STATE_READY;//huart1传输完成 变为准备 } }
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 */ //UART_DMATransmitCplt该函数对单次DMA发送的处理是置零串口的DMAT位即失能串口DMA发送 //对于循环模式下的DMA则会调用传输完成回调函数 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; } }
3.2 DMA启动传输函数(HAL_DMA_Start_IT)
该函数由HAL_UART_Transmit_DMA()函数调用;
将传入的串口地址以及传需要传输的数据源地址通过DMA_SetConfig函数配置进DMA通道中,并根据DMA对象中是否定义了半传输中断回调函数来使能对应中断,最后通过 __HAL_DMA_ENABLE( );函数使能对应的DMA通道,启动传输;
DMA外设会自动从数据源读取数据并写入串口的DR寄存器,而当每一个数据被写入串口DR寄存器后,串口的SR寄存器中的TXE: Transmit data register empty;会被自动置位,会自动触发串口中断进入串口服务函数,在HAL_UART_IRQHandler(&huart1);串口中断服务函数中通过UART_Transmit_IT() 函数与UART_EndTransmit_IT()函数来处理发送;
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength) { HAL_StatusTypeDef status = HAL_OK; /* Check the parameters */ assert_param(IS_DMA_BUFFER_SIZE(DataLength)); /* Process locked */ __HAL_LOCK(hdma); if(HAL_DMA_STATE_READY == hdma->State) { /* Change DMA peripheral state */ hdma->State = HAL_DMA_STATE_BUSY; hdma->ErrorCode = HAL_DMA_ERROR_NONE; /* Disable the peripheral */ __HAL_DMA_DISABLE(hdma); /* Configure the source, destination address and the data length & clear flags*/ DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength); /* Enable the transfer complete interrupt */ /* Enable the transfer Error interrupt */ //根据DMA对象是否定义半传输中断函数来使能相应中断 if(NULL != hdma->XferHalfCpltCallback) { /* Enable the Half transfer complete interrupt as well */ __HAL_DMA_ENABLE_IT(hdma, (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE)); } else { __HAL_DMA_DISABLE_IT(hdma, DMA_IT_HT); __HAL_DMA_ENABLE_IT(hdma, (DMA_IT_TC | DMA_IT_TE)); } /* Enable the Peripheral */ //启动DMA传输; __HAL_DMA_ENABLE(hdma); } else { /* Process Unlocked */ __HAL_UNLOCK(hdma); /* Remain BUSY */ status = HAL_BUSY; } return status; }
需要注意的是其实DMA的半传输中断发生时,DMA仍然在搬运数据!!!只不过将中断信号发生给了NVIC让CPU去处理中断罢了;
3.3 DMA接收函数(UART_Start_Receive_DMA)
该函数由HAL_UART_Receive_DMA( )函数调用;
该函数主要是先注册DMA传输完成回调函数(其实就是把串口的传输完成回调函数地址赋值给了DMA结构体对象中的回调函数);
要特别注意,不能在HAL_UART_RxCpltCallback( )串口接收完成回调函数中调用UART_Start_Receive_DMA( )函数启动DMA接收;因为在DMA初始化时调用了__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);这个函数;因此DMA的传输完成中断回调函数也就被配置成了串口传输完成回调的函数。但是注意,这个DMA的回调函数是在DMA的中断中执行的!!!
也就是说如过在回调函数HAL_UART_RxCpltCallback( )中调用UART_Start_Receive_DMA( ),那么相当于DMA还没有退出接收完成中断,就又在中断里开启了DMA传输,这必然会发生错误!!!会导致程序跑飞~~
HAL_StatusTypeDef UART_Start_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { uint32_t *tmp; 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 stream */ 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; }