stm32f103 串列埠 DMA收發
1、stm32串列埠傳送佔用的時間
做嵌入式以來,一直自認為在兩個MCU之間的串列埠通訊很佔用時間,讓我感覺很是不爽。為了遠端資料傳輸,波特率較低,假設bps = 9600,傳輸20個位元組大概需要20ms。
如果使用HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)等待硬體資料傳送完成
MCU將一直等待發送 完成標誌 while(__HAL_UART_GET_FLAG(huart, Flag) == RESET),所以效率較低
串列埠傳送資料:
傳送資料在軟體層面來看是按照位元組來發送的。USARTx->DR = (Data & (uint16_t)0x01FF);CPU只需要把一個位元組的資料填充到DR暫存器中就可以了,然後具體的傳送過程是由硬體來完成,單位元組的傳送過程中不消耗CPU。但是為什麼我們使用串列埠來發送連續的資料時為什麼還是感覺到這麼慢,而且連續傳送的過程中CPU不能去做其他任務呢?究其原因是因為我們為保證資料傳送的完整性在傳送過程中加入了while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET){} 這條語句來等待發送結束。讓CPU白白浪費在while的死迴圈中,等待的時間為 1(位元組)x10(位) / 使用的波特率bps = 死迴圈時間。如果使用115200bps波特率的話,等待時間為86us。。由此來看其實發送資料的時間幾乎全部都花費在了等待上(兩次傳送的這段時間間隔是必須要有的,但是這段時間的CPU可以去做其他任務)。時間間隔必須要有是因為在硬體傳送的過程中是需要這段時間來按位傳送電平資訊的。如下圖所示:傳送完一個位元組(10位,開始位+8bit資料+結束位)產生一個TXE標誌位,然後接著傳送下一位元組資料
串列埠接收資料:
串列埠接收資料在軟體層面來看也是按照位元組接收,return (uint16_t)(USARTx->DR & (uint16_t)0x01FF);返回的就是DR暫存器的資料,接收中斷是在接收完1位元組後來產生中斷,接收在軟體層面來看只是從DR暫存器取出數值這個動作而已,幾乎也不佔用CPU時間,具體的接收過程是由硬體來完成,在硬體層面是按照位來接收的,硬體接收過程也是需要時間的,中間傳輸過程佔用時間與傳送佔用時間是一樣的。
2. 解決方法
1. 串列埠中斷髮送 中斷接收
HAL_StatusTypeDef UART_Transmit_IT(UART_HandleTypeDef *huart)
HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
優點:硬體實現收發,主程式無需等待
缺點:頻繁呼叫中斷,bps較高時,或者使用三路串列埠或以上時,中斷消耗時間過大(未曾測試,bps較高時,宕機,資料不穩定)。
2. DMA方式傳送
使用DMA方式傳送資料,HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
3. DMA 加 空閒方式接收
void HAL_UART_IDLEHandler(UART_HandleTypeDef * huartx){
uint32_t tmp_flag = 0;
uint32_t temp;
tmp_flag = __HAL_UART_GET_FLAG(huartx,UART_FLAG_IDLE);
if((tmp_flag != RESET))
{
__HAL_UART_CLEAR_IDLEFLAG(huartx);
temp = huartx->Instance->SR; // 清除IDLE標誌位
temp = huartx->Instance->DR;
HAL_UART_DMAStop(huartx);
if(huartx == &huart3)
{
temp = hdma_usart3_rx.Instance->CNDTR;
Rec_flg3.rx_len = BUFFER_SIZE - temp;
Rec_flg3.recv_end_flag = 1;
inQueueBuff(&u3_rx,(int8_t *)(huartx->pRxBuffPtr),Rec_flg3.rx_len); //將接收到的資料壓如棧中
HAL_UART_Receive_DMA(huartx,aRxBuffer3,BUFFER_SIZE);
}
else if(huartx == &huart2)
{
temp = hdma_usart2_rx.Instance->CNDTR; // 剩餘的接收資料
Rec_flg2.rx_len = BUFFER_SIZE - temp;
Rec_flg2.recv_end_flag = 1;
inQueueBuff(&u2_rx,(int8_t *)(huartx->pRxBuffPtr),Rec_flg2.rx_len); //將接收到的資料壓如棧中
HAL_UART_Receive_DMA(huartx,aRxBuffer2,BUFFER_SIZE);
}
else if(huartx == &huart1)
{
temp = hdma_usart1_rx.Instance->CNDTR; // 剩餘的接收資料
Rec_flg1.rx_len = BUFFER_SIZE - temp;
Rec_flg1.recv_end_flag = 1;
inQueueBuff(&u1_rx,(int8_t *)(huartx->pRxBuffPtr),Rec_flg1.rx_len); //將接收到的資料壓如棧中
HAL_UART_Receive_DMA(huartx,aRxBuffer1,BUFFER_SIZE);
}
}
}
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(huart->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* Peripheral clock enable */
__USART1_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_LOW;
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);
/* Peripheral DMA 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_CIRCULAR;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM;
HAL_DMA_Init(&hdma_usart1_rx);
__HAL_LINKDMA(huart,hdmarx,hdma_usart1_rx);
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_MEDIUM;
HAL_DMA_Init(&hdma_usart1_tx);
__HAL_LINKDMA(huart,hdmatx,hdma_usart1_tx);
/* Peripheral interrupt init*/
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
__HAL_UART_ENABLE_IT(huart, UART_IT_IDLE);
initQueue(&u1_rx);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
}