有人在使用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ā)自收的測試。也的確可以重現(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的延時(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 */
}
中斷處理代碼很簡單。這里沒有開啟 UART其它相關(guān)中斷,僅僅針對IDLE事件做處理,其它UART事件的中斷就不用理睬。檢測到空閑事件后,清除空閑中斷請求標(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)證測試。
我們先發(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明顯不對,似乎多計(jì)了1次。因?yàn)楝F(xiàn)在才發(fā)送1幀數(shù)據(jù)出去,應(yīng)該只會(huì)有1次IDLE事件,怎么進(jìn)了2次IDLE中斷呢?【注:我將上圖中右下角放大后截圖放在圖中間便于查看?!?/p>
我們不妨繼續(xù)發(fā)送第2幀"BDEF"4個(gè)字母出去,看看UART接收和IDLE事件的情況。

同樣,收發(fā)結(jié)果一致,統(tǒng)計(jì)到接收數(shù)據(jù)個(gè)數(shù)也正確,就是進(jìn)IDLE中斷的次數(shù)多了1次?!咀ⅲ何乙廊粚⑸蠄D中右下角放大后截圖放在圖中間便于查看。】
基于上面代碼,當(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手冊UART章節(jié)相關(guān)內(nèi)容。
空閑幀是一個(gè)特殊的通信幀,全幀是包含起始位、停止位在內(nèi)的全“1”幀。

在UART每次接收到數(shù)據(jù)后,緊接著若通信線上出現(xiàn)不短于1個(gè)字符傳輸時(shí)間的高電平時(shí)則被硬件判為空閑通信幀并可以觸發(fā)空閑中斷?!緦τ赨ART傳輸,每個(gè)傳輸字的起始位是低電平,停止位是高電平】
另外,我們還可以從STM32手冊中看到,對于STM32片內(nèi)的UART,在使能其發(fā)送功能時(shí),具體操作就是在對USART_CR1寄存器的TE位置位時(shí),硬件會(huì)自動(dòng)發(fā)送1個(gè)空閑幀出去。【下圖是來自STM32手冊相關(guān)描述】

現(xiàn)在是基于USART自發(fā)自收,難道前面多出來的那次空閑中斷是因?yàn)樵谧鯱ART初始化時(shí)對TE位置1操作所導(dǎo)致的?

細(xì)想起來,這種可能性的確存在。如果代碼里在使能UART的發(fā)送功能,即對USART_CR1寄存器的TE位置位時(shí)產(chǎn)生空閑幀,UART接收端也感受到了,這樣的話,若使能IDLE中斷時(shí)若先不做空閑事件標(biāo)志清零的話,是會(huì)立即進(jìn)入中斷一次。顯然,此時(shí)還并沒有真正的數(shù)據(jù)發(fā)送或接收。
但是,我們從前面main()函數(shù)代碼里看到了在使能IDLE中斷之前已經(jīng)先做對IDLE事件標(biāo)志的清零,莫非這個(gè)清零操作太早?此時(shí),IDLE事件或許還沒真正生成呢!以下面示意圖為例,黃色區(qū)域表示空閑幀持續(xù)時(shí)間,清除空閑事件標(biāo)志的操作顯然不能太著急,清得太早也沒意義。

那么,我們不妨先研究下代碼,看看是哪個(gè)地方對TE@USART_CR1實(shí)現(xiàn)置位的。
我們不難追查到對TE置位是發(fā)生在? MX_USART1_UART_Init()函數(shù);在這個(gè)初始化代碼里,最終在?UART_SetConfig()這個(gè)函數(shù)里完成。
既然這樣,如果我們在MX_USART1_UART_Init();執(zhí)行之后稍作延時(shí)后再對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é)果。】
#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ā)送幾次就對應(yīng)幾次IDLE中斷【每次發(fā)送后保留了足夠延時(shí),以保證數(shù)據(jù)發(fā)送后的空閑幀產(chǎn)生】的目的,這也說明了上面的分析和判斷是正確的。
前面剛開始測試時(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ù)接收的。
我們還是以前面的測試代碼為例,在UART初始化之后不做任何延時(shí)就開啟UART的接收并使能IDLE中斷。我們只需將IDLE中斷響應(yīng)代碼稍微調(diào)整也可以規(guī)避啟動(dòng)UART發(fā)送功能時(shí)發(fā)出的空閑幀對我們程序判斷的影響。
下面是調(diào)整后的IDLE中斷響應(yīng)代碼之截圖。

前面的測試是將4幀不同長度的數(shù)據(jù)分4批發(fā)送,不同發(fā)送幀間保持了足夠的延時(shí)以產(chǎn)生空閑事件,如果將這4幀數(shù)據(jù)發(fā)送間的延時(shí)取消掉,即將上面代碼中幾個(gè)UART發(fā)送函數(shù)間的Delay(20)屏蔽掉,并給UART采用DMA方式的接收安排足夠長度的接收緩沖【這里只設(shè)置為25,具體應(yīng)用時(shí)視情況而定】,看看結(jié)果怎么樣。
測試代碼是下面的樣子,就是在前面修改過的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)備。這也是基于空閑事件接收不定長數(shù)據(jù)的常見處理方式。
關(guān)于UART空閑事件中斷多進(jìn)一次的話題就聊到這里,供君參考。知道怎么回事了在具體應(yīng)用時(shí)靈活處理即可。
編輯:黃飛
?
電子發(fā)燒友App








評論