有人在使用STM32的UART收發(fā)并開啟空閑中斷時,有時會發(fā)現(xiàn)空閑中斷相比預期多進一次的情況。比方,本來以為只會進3次空閑中斷的結(jié)果進了4次;或者說根本沒開啟接收,一使能空閑中斷就立即進一次中斷服務程序;有時即使在使能空閑中斷之前還特意做了空閑事件標志的清零也會發(fā)生類似情況。
下面我找了塊STM32開發(fā)板,選擇USART1做自發(fā)自收的測試。也的確可以重現(xiàn)問題。
下面是我的測試代碼的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幀數(shù)據(jù)的發(fā)送,幀間加了20ms的延時。每發(fā)送一幀數(shù)據(jù)之后應會產(chǎn)生一個空閑幀。
下面是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其它相關(guān)中斷,僅僅針對IDLE事件做處理,其它UART事件的中斷就不用理睬。檢測到空閑事件后,清除空閑中斷請求標志,統(tǒng)計收到的數(shù)據(jù)個數(shù)和進入空閑中斷的次數(shù),然后重新開啟新的UART的DMA方式接收。
變量UART_Rx_Count_IDLE表示CPU進入空閑中斷的次數(shù)。
變量UART_Rx_Len表示UART通過DMA接收到內(nèi)存的數(shù)據(jù)個數(shù)。
我們基于上面代碼進行驗證測試。
我們先發(fā)送第1幀“ABC”3個字母出去,看看UART接收和IDLE事件響應的情況。
從發(fā)送和接收的情況來看,收發(fā)是正常的、IDLE中斷里統(tǒng)計到數(shù)據(jù)個數(shù)也正確,但統(tǒng)計到IDLE中斷次數(shù)UART_Rx_Count_IDLE明顯不對,似乎多計了1次。因為現(xiàn)在才發(fā)送1幀數(shù)據(jù)出去,應該只會有1次IDLE事件,怎么進了2次IDLE中斷呢?【注:我將上圖中右下角放大后截圖放在圖中間便于查看?!?/p>
我們不妨繼續(xù)發(fā)送第2幀"BDEF"4個字母出去,看看UART接收和IDLE事件的情況。
同樣,收發(fā)結(jié)果一致,統(tǒng)計到接收數(shù)據(jù)個數(shù)也正確,就是進IDLE中斷的次數(shù)多了1次?!咀ⅲ何乙廊粚⑸蠄D中右下角放大后截圖放在圖中間便于查看?!?/p>
基于上面代碼,當我把4幀數(shù)據(jù)都發(fā)送完畢的話,按理只應該進4次空閑中斷,可是卻進了5次空閑中斷。
不論發(fā)到第幾幀數(shù)據(jù),收發(fā)的結(jié)果正常,就是進空閑中斷的次數(shù)比預想的多了1次。這是怎么回事呢?
這里多出來的1次中斷有時可能會導致些麻煩,尤其在不知情的情況下。因為我們常常根據(jù)空閑中斷來做些判斷及處理,如果像這種不清不楚地多1次中斷可能會給我們的應用帶來些隱患或困惑。
。。。。。。
查看STM32手冊UART章節(jié)相關(guān)內(nèi)容。
空閑幀是一個特殊的通信幀,全幀是包含起始位、停止位在內(nèi)的全“1”幀。
在UART每次接收到數(shù)據(jù)后,緊接著若通信線上出現(xiàn)不短于1個字符傳輸時間的高電平時則被硬件判為空閑通信幀并可以觸發(fā)空閑中斷?!緦τ赨ART傳輸,每個傳輸字的起始位是低電平,停止位是高電平】
另外,我們還可以從STM32手冊中看到,對于STM32片內(nèi)的UART,在使能其發(fā)送功能時,具體操作就是在對USART_CR1寄存器的TE位置位時,硬件會自動發(fā)送1個空閑幀出去。【下圖是來自STM32手冊相關(guān)描述】
現(xiàn)在是基于USART自發(fā)自收,難道前面多出來的那次空閑中斷是因為在做UART初始化時對TE位置1操作所導致的?
細想起來,這種可能性的確存在。如果代碼里在使能UART的發(fā)送功能,即對USART_CR1寄存器的TE位置位時產(chǎn)生空閑幀,UART接收端也感受到了,這樣的話,若使能IDLE中斷時若先不做空閑事件標志清零的話,是會立即進入中斷一次。顯然,此時還并沒有真正的數(shù)據(jù)發(fā)送或接收。
但是,我們從前面main()函數(shù)代碼里看到了在使能IDLE中斷之前已經(jīng)先做對IDLE事件標志的清零,莫非這個清零操作太早?此時,IDLE事件或許還沒真正生成呢!以下面示意圖為例,黃色區(qū)域表示空閑幀持續(xù)時間,清除空閑事件標志的操作顯然不能太著急,清得太早也沒意義。
那么,我們不妨先研究下代碼,看看是哪個地方對TE@USART_CR1實現(xiàn)置位的。
我們不難追查到對TE置位是發(fā)生在? MX_USART1_UART_Init()函數(shù);在這個初始化代碼里,最終在?UART_SetConfig()這個函數(shù)里完成。
既然這樣,如果我們在MX_USART1_UART_Init();執(zhí)行之后稍作延時后再對IDLE事件標志清零,然后使能IDLE事件中斷,按理應該就可以避免上面提到的多進一次空閑中斷的情況了。
我們把前面的main()代碼稍作調(diào)整,修改成下面樣子,實際上就是在做IDLE事件標志清零之前加了個延時。至于IDLE中斷響應代碼保持不變。【下面延時所加的20ms延時可能有點夸張,這里只為演示和驗證結(jié)果?!?/p>
#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) { } }
基于上面修改后的代碼,驗證結(jié)果都是正常的,不再有多進一次中斷的情況了。下圖是UART發(fā)送2次后的接收及IDLE中斷響應的情況。
下圖是UART發(fā)送4次后的接收及IDLE中斷響應的情況。
基于修改后的代碼經(jīng)過反復驗證,最終可以實現(xiàn)發(fā)送幾次就對應幾次IDLE中斷【每次發(fā)送后保留了足夠延時,以保證數(shù)據(jù)發(fā)送后的空閑幀產(chǎn)生】的目的,這也說明了上面的分析和判斷是正確的。
前面剛開始測試時遇到多出1次IDLE中斷的情形,是因為使能UART發(fā)送功能時發(fā)送了一個空閑幀并觸發(fā)了中斷。解決辦法就是等待該空閑幀發(fā)送完畢后直接清除IDLE事件標志,然后才使能IDLE中斷,這樣就避免了UART硬件使能發(fā)送功能時的空閑幀觸發(fā)中斷的問題,進而可以避免個別應用時的麻煩或困惑。
不過,在具體應用中是否會產(chǎn)生多次1次空閑中斷的問題還要具體問題具體分析。比方當完成UART初始化并使能發(fā)送功能后,并不立刻使能IDLE中斷,而是優(yōu)哉游哉地做了其它諸多事情后才來使能IDLE中斷【注:假定此時空閑幀早已發(fā)送完畢也被接收到】,并在使能IDLE中斷前做了IDLE事件標志的清零,這時也不會產(chǎn)生多進1次IDLE中斷的問題。
個人覺得這里的重點是我們要知道有這么回事,在具體應用時我們可以靈活處理。比方,即使在開啟UART接收空閑幀中斷前不做任何延時也可以,我們可以在IDLE中斷里檢查數(shù)據(jù)的接收情況,因為使能UART發(fā)送功能時發(fā)送的空閑幀之前是沒有數(shù)據(jù)接收的。
我們還是以前面的測試代碼為例,在UART初始化之后不做任何延時就開啟UART的接收并使能IDLE中斷。我們只需將IDLE中斷響應代碼稍微調(diào)整也可以規(guī)避啟動UART發(fā)送功能時發(fā)出的空閑幀對我們程序判斷的影響。
下面是調(diào)整后的IDLE中斷響應代碼之截圖。
前面的測試是將4幀不同長度的數(shù)據(jù)分4批發(fā)送,不同發(fā)送幀間保持了足夠的延時以產(chǎn)生空閑事件,如果將這4幀數(shù)據(jù)發(fā)送間的延時取消掉,即將上面代碼中幾個UART發(fā)送函數(shù)間的Delay(20)屏蔽掉,并給UART采用DMA方式的接收安排足夠長度的接收緩沖【這里只設(shè)置為25,具體應用時視情況而定】,看看結(jié)果怎么樣。
測試代碼是下面的樣子,就是在前面修改過的main()代碼基礎(chǔ)上,屏蔽掉4次發(fā)送操作間的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) { } }
我們來看看運行結(jié)果。
從結(jié)果不難看出4幀數(shù)據(jù)都被完整接收到一批緩存了,即作為一次性DMA接收的結(jié)果。盡管數(shù)據(jù)分4幀發(fā)送,由于發(fā)送間隔較短不足以觸發(fā)空閑事件,也就不會重新開啟新的DMA接收,都盡收在1批內(nèi)存區(qū)了,共18個字符,全部接收完畢后進了一次空閑中斷,并做好了下次接收的準備。這也是基于空閑事件接收不定長數(shù)據(jù)的常見處理方式。
關(guān)于UART空閑事件中斷多進一次的話題就聊到這里,供君參考。知道怎么回事了在具體應用時靈活處理即可。
編輯:黃飛
?
評論
查看更多