有人在使用STM32的UART收發并開啟空閑中斷時,有時會發現空閑中斷相比預期多進一次的情況。比方,本來以為只會進3次空閑中斷的結果進了4次;或者說根本沒開啟接收,一使能空閑中斷就立即進一次中斷服務程序;有時即使在使能空閑中斷之前還特意做了空閑事件標志的清零也會發生類似情況。
下面我找了塊STM32開發板,選擇USART1做自發自收的測試。也的確可以重現問題。
下面是我的測試代碼的main程序:
#define?Length?(25) uint8_t Data_RX[Length]={0}; uint32_t??UART_Rx_Len;?//the?Number?of?received data by DMA uint32_t?UART_Rx_Count_IDLE;//Counting IDLE interrupt times 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_USART1_UART_Init(); ??/*?USER?CODE?BEGIN?2?*/ ?? ???//HAL_Delay(20); ??__HAL_UART_CLEAR_FLAG(&huart1,?UART_CLEAR_IDLEF);???? __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); HAL_UART_Receive_DMA(&huart1, Data_RX, Length); HAL_UART_Transmit(&huart1,(uint8_t *)"ABC",3,0xffff);//1st TX HAL_Delay(20); HAL_UART_Transmit(&huart1,(uint8_t *)"BDEF",4,0xffff);//2nd TX HAL_Delay(20); HAL_UART_Transmit(&huart1,(uint8_t *)"CGHIJ",5,0xffff);//3rd TX HAL_Delay(20); HAL_UART_Transmit(&huart1,(uint8_t *)"DKLMNP",6,0xffff);//4th TX HAL_Delay(20); /* USER CODE END 2 */ while(1) { } }
從代碼里不難看出,這里做了4幀數據的發送,幀間加了20ms的延時。每發送一幀數據之后應會產生一個空閑幀。
下面是IDLE中斷處理代碼
void USART1_IRQHandler(void) { ??/*?USER?CODE?BEGIN?USART1_IRQn?0?*/ if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!=0) ??{ ????__HAL_UART_CLEAR_IDLEFLAG(&huart1); ????UART_Rx_Count_IDLE++;//counting?idle?interrupt?times ????UART_Rx_Len=Length-?huart1.hdmarx->Instance->CNDTR; ???HAL_UART_DMAStop(&huart1); ???HAL_UART_Receive_DMA(&huart1,?Data_RX,?Length);?//Receive?again? ??}?? /* USER CODE END USART1_IRQn 0 */ /* HAL_UART_IRQHandler(&huart1);*/ ??/*?USER?CODE?BEGIN?USART1_IRQn?1?*/ /* USER CODE END USART1_IRQn 1 */ }
中斷處理代碼很簡單。這里沒有開啟 UART其它相關中斷,僅僅針對IDLE事件做處理,其它UART事件的中斷就不用理睬。檢測到空閑事件后,清除空閑中斷請求標志,統計收到的數據個數和進入空閑中斷的次數,然后重新開啟新的UART的DMA方式接收。
變量UART_Rx_Count_IDLE表示CPU進入空閑中斷的次數。
變量UART_Rx_Len表示UART通過DMA接收到內存的數據個數。
我們基于上面代碼進行驗證測試。
我們先發送第1幀“ABC”3個字母出去,看看UART接收和IDLE事件響應的情況。
從發送和接收的情況來看,收發是正常的、IDLE中斷里統計到數據個數也正確,但統計到IDLE中斷次數UART_Rx_Count_IDLE明顯不對,似乎多計了1次。因為現在才發送1幀數據出去,應該只會有1次IDLE事件,怎么進了2次IDLE中斷呢?【注:我將上圖中右下角放大后截圖放在圖中間便于查看。】
我們不妨繼續發送第2幀"BDEF"4個字母出去,看看UART接收和IDLE事件的情況。
同樣,收發結果一致,統計到接收數據個數也正確,就是進IDLE中斷的次數多了1次。【注:我依然將上圖中右下角放大后截圖放在圖中間便于查看。】
基于上面代碼,當我把4幀數據都發送完畢的話,按理只應該進4次空閑中斷,可是卻進了5次空閑中斷。
不論發到第幾幀數據,收發的結果正常,就是進空閑中斷的次數比預想的多了1次。這是怎么回事呢?
這里多出來的1次中斷有時可能會導致些麻煩,尤其在不知情的情況下。因為我們常常根據空閑中斷來做些判斷及處理,如果像這種不清不楚地多1次中斷可能會給我們的應用帶來些隱患或困惑。
。。。。。。
查看STM32手冊UART章節相關內容。
空閑幀是一個特殊的通信幀,全幀是包含起始位、停止位在內的全“1”幀。
在UART每次接收到數據后,緊接著若通信線上出現不短于1個字符傳輸時間的高電平時則被硬件判為空閑通信幀并可以觸發空閑中斷。【對于UART傳輸,每個傳輸字的起始位是低電平,停止位是高電平】
另外,我們還可以從STM32手冊中看到,對于STM32片內的UART,在使能其發送功能時,具體操作就是在對USART_CR1寄存器的TE位置位時,硬件會自動發送1個空閑幀出去。【下圖是來自STM32手冊相關描述】
現在是基于USART自發自收,難道前面多出來的那次空閑中斷是因為在做UART初始化時對TE位置1操作所導致的?
細想起來,這種可能性的確存在。如果代碼里在使能UART的發送功能,即對USART_CR1寄存器的TE位置位時產生空閑幀,UART接收端也感受到了,這樣的話,若使能IDLE中斷時若先不做空閑事件標志清零的話,是會立即進入中斷一次。顯然,此時還并沒有真正的數據發送或接收。
但是,我們從前面main()函數代碼里看到了在使能IDLE中斷之前已經先做對IDLE事件標志的清零,莫非這個清零操作太早?此時,IDLE事件或許還沒真正生成呢!以下面示意圖為例,黃色區域表示空閑幀持續時間,清除空閑事件標志的操作顯然不能太著急,清得太早也沒意義。
那么,我們不妨先研究下代碼,看看是哪個地方對TE@USART_CR1實現置位的。
我們不難追查到對TE置位是發生在? MX_USART1_UART_Init()函數;在這個初始化代碼里,最終在?UART_SetConfig()這個函數里完成。
既然這樣,如果我們在MX_USART1_UART_Init();執行之后稍作延時后再對IDLE事件標志清零,然后使能IDLE事件中斷,按理應該就可以避免上面提到的多進一次空閑中斷的情況了。
我們把前面的main()代碼稍作調整,修改成下面樣子,實際上就是在做IDLE事件標志清零之前加了個延時。至于IDLE中斷響應代碼保持不變。【下面延時所加的20ms延時可能有點夸張,這里只為演示和驗證結果。】
#define Length (25) uint8_t Data_RX[Length]={0}; uint32_t UART_Rx_Len; //the Number of received data by DMA uint32_t??UART_Rx_Count_IDLE;//Counting?IDLE?interrupt?times 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_USART1_UART_Init(); /* USER CODE BEGIN 2 */ ??HAL_Delay(20);?//Newly?added ?? ?__HAL_UART_CLEAR_FLAG(&huart1,?UART_CLEAR_IDLEF);?? __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); HAL_UART_Receive_DMA(&huart1, Data_RX, Length); HAL_UART_Transmit(&huart1,(uint8_t *)"ABC",3,0xffff);//1st TX HAL_Delay(20); HAL_UART_Transmit(&huart1,(uint8_t *)"BDEF",4,0xffff);//2nd TX HAL_Delay(20); HAL_UART_Transmit(&huart1,(uint8_t *)"CGHIJ",5,0xffff);//3rd TX HAL_Delay(20); HAL_UART_Transmit(&huart1,(uint8_t *)"DKLMNP",6,0xffff);//4th TX HAL_Delay(20); /* USER CODE END 2 */ while(1) { } }
基于上面修改后的代碼,驗證結果都是正常的,不再有多進一次中斷的情況了。下圖是UART發送2次后的接收及IDLE中斷響應的情況。
下圖是UART發送4次后的接收及IDLE中斷響應的情況。
基于修改后的代碼經過反復驗證,最終可以實現發送幾次就對應幾次IDLE中斷【每次發送后保留了足夠延時,以保證數據發送后的空閑幀產生】的目的,這也說明了上面的分析和判斷是正確的。
前面剛開始測試時遇到多出1次IDLE中斷的情形,是因為使能UART發送功能時發送了一個空閑幀并觸發了中斷。解決辦法就是等待該空閑幀發送完畢后直接清除IDLE事件標志,然后才使能IDLE中斷,這樣就避免了UART硬件使能發送功能時的空閑幀觸發中斷的問題,進而可以避免個別應用時的麻煩或困惑。
不過,在具體應用中是否會產生多次1次空閑中斷的問題還要具體問題具體分析。比方當完成UART初始化并使能發送功能后,并不立刻使能IDLE中斷,而是優哉游哉地做了其它諸多事情后才來使能IDLE中斷【注:假定此時空閑幀早已發送完畢也被接收到】,并在使能IDLE中斷前做了IDLE事件標志的清零,這時也不會產生多進1次IDLE中斷的問題。
個人覺得這里的重點是我們要知道有這么回事,在具體應用時我們可以靈活處理。比方,即使在開啟UART接收空閑幀中斷前不做任何延時也可以,我們可以在IDLE中斷里檢查數據的接收情況,因為使能UART發送功能時發送的空閑幀之前是沒有數據接收的。
我們還是以前面的測試代碼為例,在UART初始化之后不做任何延時就開啟UART的接收并使能IDLE中斷。我們只需將IDLE中斷響應代碼稍微調整也可以規避啟動UART發送功能時發出的空閑幀對我們程序判斷的影響。
下面是調整后的IDLE中斷響應代碼之截圖。
前面的測試是將4幀不同長度的數據分4批發送,不同發送幀間保持了足夠的延時以產生空閑事件,如果將這4幀數據發送間的延時取消掉,即將上面代碼中幾個UART發送函數間的Delay(20)屏蔽掉,并給UART采用DMA方式的接收安排足夠長度的接收緩沖【這里只設置為25,具體應用時視情況而定】,看看結果怎么樣。
測試代碼是下面的樣子,就是在前面修改過的main()代碼基礎上,屏蔽掉4次發送操作間的Delay(20)延時。中斷處理還是最初的代碼。
#define Length (25) uint8_t Data_RX[Length]={0}; uint32_t UART_Rx_Len; //the Number of received data by DMA uint32_t UART_Rx_Count_IDLE;//Counting IDLE interrupt times 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_USART1_UART_Init(); /* USER CODE BEGIN 2 */ HAL_Delay(20); //Newly added __HAL_UART_CLEAR_FLAG(&huart1, UART_CLEAR_IDLEF); __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE); HAL_UART_Receive_DMA(&huart1, Data_RX, Length); HAL_UART_Transmit(&huart1,(uint8_t *)"ABC",3,0xffff);//1st TX ??//?HAL_Delay(20); HAL_UART_Transmit(&huart1,(uint8_t *)"BDEF",4,0xffff);//2nd TX // HAL_Delay(20); HAL_UART_Transmit(&huart1,(uint8_t *)"CGHIJ",5,0xffff);//3rd TX // HAL_Delay(20); HAL_UART_Transmit(&huart1,(uint8_t *)"DKLMNP",6,0xffff);//4th TX ???HAL_Delay(20); /* USER CODE END 2 */ while(1) { } }
我們來看看運行結果。
從結果不難看出4幀數據都被完整接收到一批緩存了,即作為一次性DMA接收的結果。盡管數據分4幀發送,由于發送間隔較短不足以觸發空閑事件,也就不會重新開啟新的DMA接收,都盡收在1批內存區了,共18個字符,全部接收完畢后進了一次空閑中斷,并做好了下次接收的準備。這也是基于空閑事件接收不定長數據的常見處理方式。
關于UART空閑事件中斷多進一次的話題就聊到這里,供君參考。知道怎么回事了在具體應用時靈活處理即可。
編輯:黃飛
?
評論
查看更多