STM32L4 的 DMA 簡介
DMA Mapping
DMA 相關配置及使用
以下根據 STM32L43xxx 系列進行 USART2 + DMA 的開發。
串口配置
sg_USART2_HandleStruct.Instance = USART2;
sg_USART2_HandleStruct.Init.BaudRate = bound;
sg_USART2_HandleStruct.Init.WordLength = UART_WORDLENGTH_8B; /* 字長爲8位數據格式 */
sg_USART2_HandleStruct.Init.StopBits = UART_STOPBITS_1; /* 一個停止位 */
sg_USART2_HandleStruct.Init.Parity = UART_PARITY_NONE; /* 無奇偶校驗位 */
sg_USART2_HandleStruct.Init.Mode = UART_MODE_TX_RX; /* 收發模式 */
sg_USART2_HandleStruct.Init.HwFlowCtl = UART_UART_HWCONTROL_NONE_RTS_CTS;
sg_USART2_HandleStruct.Init.OverSampling = UART_OVERSAMPLING_16;
sg_USART2_HandleStruct.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
sg_USART2_HandleStruct.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
HAL_UART_Init(&sg_USART2_HandleStruct) ;
I/O 配置
/* 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_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
配置並使用 TX + DMA
DMA 配置
根據 DMA MAP 表可知,USART2 TX 可使用 DMA1 通道 7 (1-7),通道請求爲 2 (0-7),方向爲存儲器到外設,並且設置字節長度。
/* Configure DMA Tx parameters */
sg_USART2_TxDMAHandleStruct.Instance = DMA1_Channel7;
sg_USART2_TxDMAHandleStruct.Init.Request = DMA_REQUEST_2;
sg_USART2_TxDMAHandleStruct.Init.Direction = DMA_MEMORY_TO_PERIPH;
sg_USART2_TxDMAHandleStruct.Init.PeriphInc = DMA_PINC_DISABLE;
sg_USART2_TxDMAHandleStruct.Init.MemInc = DMA_MINC_ENABLE;
sg_USART2_TxDMAHandleStruct.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
sg_USART2_TxDMAHandleStruct.Init.MemDataAlignment = DMA_PDATAALIGN_BYTE;
sg_USART2_TxDMAHandleStruct.Init.Priority = DMA_PRIORITY_HIGH;
sg_USART2_TxDMAHandleStruct.Init.Mode = DMA_NORMAL;
/* Associate the DMA handle */
__HAL_LINKDMA(uartHandle, hdmatx, sg_USART2_TxDMAHandleStruct);
/* Stop any ongoing transfer and reset the state*/
HAL_DMA_DeInit(&sg_USART2_TxDMAHandleStruct);
/* Configure the DMA Channel */
HAL_DMA_Init(&sg_USART2_TxDMAHandleStruct);
DMA TX 使用(禁止中斷)
定義一個發送函數(該函數沒有使用發送完成中斷處理,在下次進入該函數時檢測 DMA 相關標誌並清除,因此,須確保每次調用該函數的間隔時間能完成上次的數據傳輸),傳輸完成必須關閉串口 DMA ,否則不能啓動下一次 DMA 傳輸。
void USART2_UART_Transmit(uint8_t *pData, uint16_t len)
{
if (__HAL_DMA_GET_FLAG(&sg_USART2_HandleStruct, DMA_FLAG_TC7))
{
__HAL_DMA_CLEAR_FLAG(&sg_USART2_HandleStruct, DMA_FLAG_TC7); /* 清除DMA1_Steam7傳輸完成標誌 */
HAL_UART_DMAStop(&sg_USART2_HandleStruct); /* 傳輸完成以後關閉串口DMA */
}
HAL_UART_Transmit_DMA(&sg_USART2_HandleStruct, pData, len);
}
DMA TX 使用(使能中斷)
暫無
配置並使用 RX + DMA
DMA 配置
根據 DMA MAP 表可知,USART2 RX 可使用 DMA1 通道 6 (1-7),通道請求爲 2 (0-7),方向爲外設到存儲器,並且設置字節長度。
/* Configure DMA Rx parameters */
sg_USART2_RxDMAHandleStruct.Instance = DMA1_Channel6;
sg_USART2_RxDMAHandleStruct.Init.Request = DMA_REQUEST_2;
sg_USART2_RxDMAHandleStruct.Init.Direction = DMA_PERIPH_TO_MEMORY;
sg_USART2_RxDMAHandleStruct.Init.PeriphInc = DMA_PINC_DISABLE;
sg_USART2_RxDMAHandleStruct.Init.MemInc = DMA_MINC_ENABLE;
sg_USART2_RxDMAHandleStruct.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
sg_USART2_RxDMAHandleStruct.Init.MemDataAlignment = DMA_PDATAALIGN_BYTE;
sg_USART2_RxDMAHandleStruct.Init.Priority = DMA_PRIORITY_HIGH;
sg_USART2_RxDMAHandleStruct.Init.Mode = DMA_NORMAL;
/* Associate the DMA handle */
__HAL_LINKDMA(uartHandle, hdmarx, sg_USART2_RxDMAHandleStruct);
/* Stop any ongoing transfer and reset the state*/
HAL_DMA_DeInit(&sg_USART2_RxDMAHandleStruct);
/* Configure the DMA Channel */
HAL_DMA_Init(&sg_USART2_RxDMAHandleStruct);
接收中斷配置
使用 RX DMA,通常採用 DMA + RX + UART_IT_IDLE(空閒中斷)用來接收不定長的數據內容。
DMA RX 使用
定義接收中斷函數,做相關處理。
void USART2_IRQHandler(void)
{
uint32_t tmp;
if ((__HAL_UART_GET_IT(&sg_USART2_HandleStruct, UART_IT_IDLE) != RESET))
{
/* 清除相關標誌 */
__HAL_UART_CLEAR_IDLEFLAG(&sg_USART2_HandleStruct);
tmp = sg_USART2_HandleStruct.Instance->ISR;/* 通過讀取該寄存器來清除 */
tmp = sg_USART2_HandleStruct.Instance->RDR;/* 通過讀取該寄存器來清除 */
HAL_UART_DMAStop(&sg_USART2_HandleStruct);
/* 獲取實際接收長度 */
tmp = __HAL_DMA_GET_COUNTER(&sg_USART2_RxDMAHandleStruct);
sg_USART2_RxLen = USART_MAX_RX_BUF_SIZE - tmp;
g_Usart2RecvFinish = 1;
/* 重新啓動接收 */
HAL_UART_Receive_DMA(&sg_USART2_HandleStruct, sg_USART2_RxBuffer, USART_MAX_RX_BUF_SIZE);
}
HAL_UART_IRQHandler(&sg_USART2_HandleStruct);
}
注意事項
1、串口接收中斷中若通過函數 HAL_UART_Receive 讀取串口數據,會出現沒有正常讀取數據,導致不停地進入接收中斷,造成程序無法正常運行。
void USART2_IRQHandler(void)
{
uint32_t tmp;
if ((__HAL_UART_GET_IT(&sg_USART2_HandleStruct, UART_IT_RXNE) != RESET))
{
將函數
HAL_UART_Receive(&sg_USART2_HandleStruct, sg_USART2_RxBuffer[sg_USART2_RxLen++], 1, 10);
修改爲
sg_USART2_RxBuffer[sg_USART2_RxLen++] = USART2->RDR;
}
HAL_UART_IRQHandler(&sg_USART2_HandleStruct);
}
2、在同時使用 DMA RX 和 DAM TX 的時候,在其中一個完成後會關閉串口 DMA,導致另一個受其影響,導致發送或接收異常。
原因:
由於使用了函數 HAL_UART_DMAStop(&sg_USART2_HandleStruct) 用來關閉 UASRT DMA 將TX 和 RX 均給關閉了,導致影響了另一 DMA 正常傳輸。
解決方案:
基於函數 HAL_UART_DMAStop(UART_HandleTypeDef *huart) 爲原型,定義一個函數 MY_HAL_UART_DMAStop(UART_HandleTypeDef *huart, uint8_t obj),
用來可單獨關閉其中一個 DMA 的傳輸,函數如下,其中函數 My_UART_EndTxTransfer 和 My_UART_EndRxTransfer 也是分別基於函數 HAL_UART_DMAStop(UART_HandleTypeDef *huart) 內部中調用的 UART_EndTxTransfer 和 UART_EndRxTransfer 爲原型,除了函數名稱,沒有其他任何改動(在不改動 HAL 庫的情況下可這樣自定義三個函數,其中 USART_TX_DMA 是自定義的一個宏)。
/**
* @brief End ongoing Tx transfer on UART peripheral (following error detection or Transmit completion).
* @note 函數原型: UART_EndTxTransfer(UART_HandleTypeDef *huart), 函數功能完全一樣
* @param huart UART handle.
* @retval None
*/
static void My_UART_EndTxTransfer(UART_HandleTypeDef *huart)
{
#if defined(USART_CR1_FIFOEN)
/* Disable TXEIE, TCIE, TXFT interrupts */
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_TXEIE_TXFNFIE | USART_CR1_TCIE));
CLEAR_BIT(huart->Instance->CR3, (USART_CR3_TXFTIE));
#else
/* Disable TXEIE and TCIE interrupts */
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_TXEIE | USART_CR1_TCIE));
#endif /* USART_CR1_FIFOEN */
/* At end of Tx process, restore huart->gState to Ready */
huart->gState = HAL_UART_STATE_READY;
}
/**
* @brief End ongoing Rx transfer on UART peripheral (following error detection or Reception completion).
* @note 函數原型: UART_EndRxTransfer(UART_HandleTypeDef *huart), 函數功能完全一樣
* @param huart UART handle.
* @retval None
*/
static void My_UART_EndRxTransfer(UART_HandleTypeDef *huart)
{
/* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
#if defined(USART_CR1_FIFOEN)
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE_RXFNEIE | USART_CR1_PEIE));
CLEAR_BIT(huart->Instance->CR3, (USART_CR3_EIE | USART_CR3_RXFTIE));
#else
CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));
CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);
#endif /* USART_CR1_FIFOEN */
/* At end of Rx process, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
/* Reset RxIsr function pointer */
huart->RxISR = NULL;
}
/**
* @brief Stop the DMA Transfer.
* @note 函數原型: HAL_UART_DMAStop(UART_HandleTypeDef *huart), 可單獨關閉 TX/RX 其中一個DMA
* @param huart UART handle.
* @param obj USART_TX_DMA or USART_RX_DMA.
* @retval HAL status
*/
HAL_StatusTypeDef MY_HAL_UART_DMAStop(UART_HandleTypeDef *huart, uint8_t obj)
{
const HAL_UART_StateTypeDef gstate = huart->gState;
const HAL_UART_StateTypeDef rxstate = huart->RxState;
if (obj == USART_TX_DMA)
{
/* Stop UART DMA Tx request if ongoing */
if ((HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAT)) &&
(gstate == HAL_UART_STATE_BUSY_TX))
{
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);
/* Abort the UART DMA Tx channel */
if (huart->hdmatx != NULL)
{
if (HAL_DMA_Abort(huart->hdmatx) != HAL_OK)
{
if (HAL_DMA_GetError(huart->hdmatx) == HAL_DMA_ERROR_TIMEOUT)
{
/* Set error code to DMA */
huart->ErrorCode = HAL_UART_ERROR_DMA;
return HAL_TIMEOUT;
}
}
}
My_UART_EndTxTransfer(huart);
}
}
else
{
/* Stop UART DMA Rx request if ongoing */
if ((HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR)) &&
(rxstate == HAL_UART_STATE_BUSY_RX))
{
CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);
/* Abort the UART DMA Rx channel */
if (huart->hdmarx != NULL)
{
if (HAL_DMA_Abort(huart->hdmarx) != HAL_OK)
{
if (HAL_DMA_GetError(huart->hdmarx) == HAL_DMA_ERROR_TIMEOUT)
{
/* Set error code to DMA */
huart->ErrorCode = HAL_UART_ERROR_DMA;
return HAL_TIMEOUT;
}
}
}
My_UART_EndRxTransfer(huart);
}
}
return HAL_OK;
}