一. 問題描述
能夠點進(jìn)這篇文章的小伙伴肯定是對STM32串口DMA空閑中斷接收數(shù)據(jù)感興趣的啦,今天用這一功能實現(xiàn)串口解析航模遙控器sbus信號時,查閱了很多網(wǎng)友發(fā)布的文章(勤勞的搬運工~),包括自己之前寫過一篇博客 STM32_HAL庫_CubeMx串口DMA通信(DMA發(fā)送+DMA空閑接收不定長數(shù)據(jù))。本文進(jìn)一步梳理一下HAL庫串口空閑中斷三種不同的使用方式,其中前兩種使用DMA方式,最后一種使用HAL庫自帶的空閑中斷機(jī)制。
本文環(huán)境:
- Keil MDK5.14
- STM32CubeMX6.2.1
- 開發(fā)板/芯片:自制板/STM32F407ZGT6
實現(xiàn)功能:
- 串口DMA/非DMA空閑中斷接收不定長數(shù)據(jù)/解析航模遙控器SBUS信號
二. 方法一——使用HAL_UART_Receive_DMA
最常見的方法就是使用HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
這個庫函數(shù),其使用方法類似于HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
,初始化時需要調(diào)用一次,然后每次在中斷服務(wù)函數(shù)里面處理完數(shù)據(jù)后重新調(diào)用一次。
使用HAL_UART_Receive_IT
只需要打開串口接收中斷,即HAL_NVIC_EnableIRQ(UART5_IRQn)
;
使用HAL_UART_Receive_DMA
空閑中斷需要在初始化時打開串口空閑中斷使能,調(diào)用方式為:__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE)
。
此時DMA中斷可開可不開,開了也不用管,因為數(shù)據(jù)處理是在串口空閑中斷中進(jìn)行的。
1. 串口初始化代碼:
void MX_USART2_UART_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 100000;
huart2.Init.WordLength = UART_WORDLENGTH_9B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_EVEN;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); // 開啟串口空閑中斷,必須調(diào)用
HAL_UART_Receive_DMA(&huart2,USART2_RX_BUF,USART_REC_LEN); // 啟動DMA接收
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART2)
{
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
hdma_usart2_rx.Instance = DMA1_Stream5;
hdma_usart2_rx.Init.Channel = DMA_CHANNEL_4;
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_MEDIUM;
hdma_usart2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx);
/* USART2 interrupt Init */
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
}
}
初始化中調(diào)用__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE)
開啟空閑中斷,HAL_UART_Receive_DMA(&huart2,USART2_RX_BUF,USART_REC_LEN)
啟動DMA串口接收,USART2_RX_BUF
是接收緩沖區(qū),USART_REC_LEN
是定義的DMA接收緩沖區(qū)長度,接收的數(shù)據(jù)不能超過這個長度。
2. 中斷處理:
中斷處理有兩種方式,第一種是直接定義在void USART1_IRQHandler(void)
中,第二種是自定義一個函數(shù),然后在void USART1_IRQHandler(void)
中調(diào)用。注意網(wǎng)上很多資源自定義了中斷回調(diào)函數(shù)但代碼又沒貼全,讀者可能以為是HAL庫自帶的回調(diào)函數(shù),結(jié)果因為沒有在void USART1_IRQHandler(void)
中調(diào)用導(dǎo)致失敗。
第一種形式——直接定義:
void USART2_IRQHandler(void)
{
uint32_t tmp_flag ;
u8 len;
u8 data[25];
tmp_flag = __HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE);
if( tmp_flag != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart2);//清除標(biāo)志位
HAL_UART_DMAStop(&huart2); //停止DMA接收,防止數(shù)據(jù)出錯
len = USART_REC_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);// 獲取DMA中傳輸?shù)臄?shù)據(jù)個數(shù)
// 以下為用戶數(shù)據(jù)處理,將數(shù)據(jù)拷貝出去
if(len == 25)
{
memcpy(data,USART2_RX_BUF,len);
update_sbus(data);
}
HAL_UART_Receive_DMA(&huart2,USART2_RX_BUF,USART_REC_LEN); //打開DMA接收,數(shù)據(jù)存入rx_buffer數(shù)組中。
}
HAL_UART_IRQHandler(&huart2); //調(diào)用HAL庫中斷處理公用函數(shù)
}
第二種形式——自定義回調(diào)函數(shù):
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
uint32_t tmp_flag ;
u8 len;
u8 data[25];
if (huart->Instance == USART2)
{
tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE);
if( tmp_flag != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(huart);//清除標(biāo)志位
HAL_UART_DMAStop(huart); //停止DMA接收
len = USART_REC_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);// 獲取DMA中傳輸?shù)臄?shù)據(jù)個數(shù)
// 以下為用戶數(shù)據(jù)處理,將數(shù)據(jù)拷貝出去
if(len == 25)
{
memcpy(data,USART2_RX_BUF,len);
update_sbus(data);
}
HAL_UART_Receive_DMA(huart,USART2_RX_BUF,USART_REC_LEN); //打開DMA接收,數(shù)據(jù)存入rx_buffer數(shù)組中。
}
}
}
void USART2_IRQHandler(void)
{
HAL_UART_IdleCpltCallback(&huart2)
HAL_UART_IRQHandler(&huart2); //調(diào)用HAL庫中斷處理公用函數(shù)
}
再次提醒一下,HAL庫中并沒有類似void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
這樣的回調(diào)函數(shù),大家不要看到了以為是自帶的回調(diào)函數(shù)而不在void USART2_IRQHandler(void)中調(diào)用而導(dǎo)致失敗。
中斷處理中的函數(shù)說明:__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE)
:返回中斷標(biāo)志位,產(chǎn)生空閑中斷后會返回1;__HAL_UART_CLEAR_IDLEFLAG(&huart2)
:清除空閑中斷標(biāo)志位,必須調(diào)用;HAL_UART_DMAStop(&huart2)
:停止DMA接收,防止數(shù)據(jù)處理出錯,數(shù)據(jù)處理完成后重新打開;__HAL_DMA_GET_COUNTER(&hdma_usart2_rx)
:返回DMA中未傳輸?shù)臄?shù)據(jù)量,用緩沖區(qū)總長減去未傳輸數(shù)量就是已接收的數(shù)據(jù)長度;HAL_UART_Receive_DMA(huart,USART2_RX_BUF,USART_REC_LEN)
:重新打開DMA接收。
以上就能成功實現(xiàn)不定長數(shù)據(jù)的接受了,其實只要步驟對了還是很簡單的。
三. 方法二——使用HAL_UARTEx_ReceiveToIdle_DMA
第二種方式是使用HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
這個庫函數(shù),并重定義void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
這個中斷回調(diào)函數(shù)。
Attention:__weak void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
是正兒八經(jīng)的庫函數(shù)(弱函數(shù)),不是自定義的,我們可以重定義它。
使用這個庫函數(shù),串口初始化代碼中就不需要調(diào)用__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE)
了,因為HAL_UARTEx_ReceiveToIdle_DMA
函數(shù)內(nèi)部已經(jīng)打開了空閑中斷,當(dāng)然你加上也沒事。
1. 串口初始化代碼:
void MX_USART2_UART_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 100000;
huart2.Init.WordLength = UART_WORDLENGTH_9B;
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;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
//__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);// 開啟串口空閑中斷,可省略
HAL_UARTEx_ReceiveToIdle_DMA(&huart2,USART2_RX_BUF,USART_REC_LEN); // 使用串口DMA空閑中斷,會自動開啟空空閑中斷
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==USART2)
{
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
hdma_usart2_rx.Instance = DMA1_Stream5;
hdma_usart2_rx.Init.Channel = DMA_CHANNEL_4;
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_MEDIUM;
hdma_usart2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx);
/* USART2 interrupt Init */
HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART2_IRQn);
}
}
這里__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE)
是可省略的,HAL_UARTEx_ReceiveToIdle_DMA(&huart2,USART2_RX_BUF,USART_REC_LEN)
啟動DMA串口空閑中斷接收,USART2_RX_BUF
是接收緩沖區(qū),USART_REC_LEN
是定義的DMA接收緩沖區(qū)長度,接收的數(shù)據(jù)不能超過這個長度。
2. 中斷處理:
中斷處理定義在void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
這個中斷回調(diào)函數(shù)中。
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
u8 len;
u8 data[25];
if(huart->Instance == USART2)
{
HAL_UART_DMAStop(huart);
len = USART_REC_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);// 獲取DMA中傳輸?shù)臄?shù)據(jù)個數(shù)
if (USART2_RX_BUF[0] == 0x0F && len == 25) //接受完一幀數(shù)據(jù)
{
memcpy(data,USART2_RX_BUF,len);
update_sbus(data);
}
HAL_UARTEx_ReceiveToIdle_DMA(&huart2,USART2_RX_BUF,USART_REC_LEN); // 再次開啟DMA空閑中斷
}
}
void USART2_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart2); //調(diào)用HAL庫中斷處理公用函數(shù)
}
當(dāng)然也可以像方法一種一樣,中斷邏輯處理直接定義在void USART2_IRQHandler(void)
中。
四. 方法三——使用HAL_UARTEx_ReceiveToIdle_IT(不使用DMA)
這種方法參考這篇文章: STM32 hal庫串口空閑中斷最新用法
使用方法:
1、在主函數(shù)中調(diào)用HAL_UARTEx_ReceiveToIdle_IT()
2、在回調(diào)函數(shù)HAL_UARTEx_RxEventCallback()
中做中斷邏輯處理。
這里我換用串口4,實現(xiàn)上位機(jī)發(fā)送,單片機(jī)原封不動返回數(shù)據(jù)。
1. 串口初始化代碼:
void MX_UART4_Init(void)
{
huart4.Instance = UART4;
huart4.Init.BaudRate = 115200;
huart4.Init.WordLength = UART_WORDLENGTH_8B;
huart4.Init.StopBits = UART_STOPBITS_1;
huart4.Init.Parity = UART_PARITY_NONE;
huart4.Init.Mode = UART_MODE_TX_RX;
huart4.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart4.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart4) != HAL_OK)
{
Error_Handler();
}
// HAL_UARTEx_ReceiveToIdle_IT同時開啟空閑中斷,開啟接收
HAL_UARTEx_ReceiveToIdle_IT(&huart4,USART4_RX_BUF,USART_REC_LEN);
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
if(uartHandle->Instance==UART4)
{
__HAL_RCC_UART4_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF8_UART4;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
HAL_NVIC_SetPriority(UART4_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(UART4_IRQn);
}
}
可以看到這里是沒有使用DMA的,初始化中調(diào)用了HAL_UARTEx_ReceiveToIdle_IT(&huart4,USART4_RX_BUF,USART_REC_LEN)
來開啟串口4的空閑中斷,USART4_RX_BUF是接收緩沖區(qū),USART_REC_LEN是緩沖區(qū)的長度。這里可以看下庫中HAL_UARTEx_ReceiveToIdle_IT
的定義和提示:
框1的地方說,這個函數(shù)用于在中斷模式下接收一定量數(shù)據(jù),直到指定的數(shù)據(jù)長度被接收到或者空閑中斷產(chǎn)生。也就是說,如果USART_REC_LEN定義很小,還沒到產(chǎn)生空閑中斷,接收就會完成。
框2的地方,即是打開串口空閑中斷。所以不需要額外再去開啟空閑中斷了。
2. 中斷處理:
中斷處理定義在void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
這個中斷回調(diào)函數(shù)中。注意到,和使用DMA傳輸時是同一個回調(diào)函數(shù)。
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
u8 len=0;
u8 data[25];
u8 uart4_len=0;
u8 uart4_data[50];
if(huart->Instance == USART2)
{
HAL_UART_DMAStop(huart);
len = USART_REC_LEN - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);// 獲取DMA中傳輸?shù)臄?shù)據(jù)個數(shù)
if (USART2_RX_BUF[0] == 0x0F && len == 25) //接受完一幀數(shù)據(jù)
{
memcpy(data,USART2_RX_BUF,len);
update_sbus(data);
}
HAL_UARTEx_ReceiveToIdle_DMA(&huart2,USART2_RX_BUF,USART_REC_LEN); // 再次開啟DMA空閑中斷
}
if(huart->Instance == UART4)
{
while (USART4_RX_BUF[uart4_len] != '\0') uart4_len++;
memcpy(uart4_data,USART4_RX_BUF,uart4_len);
HAL_UART_Transmit(&huart4,uart4_data,uart4_len,0xff);
// 調(diào)用printf是使用串口1
printf("data: %s\r\n", uart4_data);
printf("data length: %d\r\n", uart4_len);
memset(USART4_RX_BUF, 0, USART_REC_LEN);
memset(uart4_data, 0, sizeof(uart4_data));
HAL_UARTEx_ReceiveToIdle_IT(&huart4,USART4_RX_BUF,USART_REC_LEN);
}
}
void USART2_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart2); //調(diào)用HAL庫中斷處理公用函數(shù)
}
void UART4_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart4); //調(diào)用HAL庫中斷處理公用函數(shù)
}
回調(diào)函數(shù)中就是讀取USART4_RX_BUF
緩沖區(qū)的數(shù)據(jù)和數(shù)據(jù)長度,然后做相應(yīng)處理,這里只是簡單的原數(shù)據(jù)發(fā)送回去,如果是SBSU信號或Modbus或其他帶協(xié)議幀的數(shù)據(jù),則可根據(jù)幀頭幀尾、數(shù)據(jù)長度、校驗位等做進(jìn)一步處理。
最終實現(xiàn)的效果就是上位機(jī)發(fā)送什么,單片機(jī)返回什么,如圖:
文章來源:http://www.zghlxwxcb.cn/news/detail-484474.html
五. 總結(jié)
以上三種方式都能實現(xiàn)串口空閑中斷接收不定長數(shù)據(jù),適合用于處理不定長數(shù)據(jù)接收、減少CPU負(fù)擔(dān),相比較而言,方法三不使用DMA,使用上更簡潔。方法一、二使用DMA,好處是可以減少CPU負(fù)擔(dān)。文章來源地址http://www.zghlxwxcb.cn/news/detail-484474.html
到了這里,關(guān)于STM32-HAL庫串口DMA空閑中斷的正確使用方式+解析SBUS信號的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!