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