有人說(shuō)在MCU的開(kāi)發(fā)應(yīng)用過(guò)程中遇到過(guò)一次中斷事件觸發(fā)兩次中斷的奇怪事情。有這樣的事嗎?應(yīng)該說(shuō)有真有假,這里以STM32為例來(lái)聊聊該話題。
所謂假的,就是指基于誤會(huì)以為一次事件觸發(fā)了兩次甚至多次中斷。比方按鍵事件沒(méi)有做好消抖處理,或者中斷請(qǐng)求標(biāo)志位沒(méi)有被及時(shí)清零等。順便說(shuō)下,對(duì)于STM32芯片而言,如果中斷請(qǐng)求標(biāo)志沒(méi)有被清零會(huì)沒(méi)完沒(méi)了的循環(huán)進(jìn)相應(yīng)中斷服務(wù)程序。
這里重點(diǎn)聊聊真的,即一次中斷事件進(jìn)入兩次中斷服務(wù)程序,的確有機(jī)會(huì)碰到。偶爾也有人反映類似問(wèn)題,比方做UART通信時(shí),一個(gè)空閑事件進(jìn)入兩次空閑中斷,感覺(jué)相關(guān)標(biāo)志沒(méi)法清除;有人通過(guò)定時(shí)器觸發(fā)SPI傳輸,一個(gè)定時(shí)器事件竟然進(jìn)入兩次中斷連續(xù)給SPI數(shù)據(jù)寄存器賦值兩次。
發(fā)生這種一次觸發(fā)事件進(jìn)入兩次中斷的情況時(shí),一般有個(gè)非常明顯的特征,那就是在中斷服務(wù)程序里對(duì)中斷請(qǐng)求標(biāo)志的清零代碼往往放在中斷服務(wù)程序的最末尾。我們不妨弄個(gè)具體的實(shí)例感受下。
下面以一個(gè)定時(shí)器更新中斷為例。我讓定時(shí)器工作在基于向上計(jì)數(shù)的單脈沖PWM模式,即啟動(dòng)計(jì)數(shù)器后,當(dāng)發(fā)生溢出產(chǎn)生更新事件時(shí)即告停止。那么每次啟動(dòng)定時(shí)器后按理有且只有一次進(jìn)入更新中斷服務(wù)程序。我在中斷服務(wù)程序里放個(gè)計(jì)數(shù)變量,統(tǒng)計(jì)進(jìn)入中斷的次數(shù)。我這里使用STM32F4的開(kāi)發(fā)板測(cè)試的。
先看看中斷服務(wù)程序里清除中斷請(qǐng)求標(biāo)志的代碼不是放在最后一行的情況。其中變量counterX用來(lái)統(tǒng)計(jì)進(jìn)入中斷服務(wù)程序次數(shù)。
這次測(cè)試結(jié)果沒(méi)問(wèn)題,一次更新事件對(duì)應(yīng)進(jìn)入一次中斷服務(wù)程序。我將上面的中斷服務(wù)程序稍微調(diào)整下代碼前后順序,讓清除中斷請(qǐng)求位的代碼放在最后,再看看下面結(jié)果。
嗯?counterX結(jié)果變?yōu)?了,一次觸發(fā)事件怎么進(jìn)了兩次中斷服務(wù)程序呢?!
這時(shí)不同的人往往會(huì)有不同的判斷或結(jié)論。比方中斷請(qǐng)求標(biāo)志一次清不掉??;同樣的寫(xiě)法別的系列或型號(hào)卻可以,認(rèn)為太莫名其妙啦!【其實(shí),到底是不是完全相同的寫(xiě)法只是感覺(jué),就像我上面的寫(xiě)法不細(xì)究的話也可以說(shuō)是一樣的寫(xiě)法】,或者說(shuō)芯片很奇葩啊云云。
怎么會(huì)這樣呢?原因就在于那行清除中斷請(qǐng)求位的代碼放在最后,在第一次退出中斷服務(wù)程序時(shí)該請(qǐng)求位尚未完成被清零的狀態(tài)。程序指令執(zhí)行速度越快,這種可能性就越高。既然該中斷請(qǐng)求位依然保持置1的有效狀態(tài),經(jīng)硬件觸發(fā)再次進(jìn)入中斷服務(wù)程序就順理成章了。
有人會(huì)問(wèn),我在退出中斷服務(wù)程序之前不是已經(jīng)做了中斷請(qǐng)求位的清零操作嗎?怎么沒(méi)有立即生效呢?再怎么“立即”也是需要時(shí)間的,程序指令的執(zhí)行完畢和指令執(zhí)行后的狀態(tài)改變并不一定同步。比方你到包子鋪去跟老板說(shuō)買(mǎi)3個(gè)饅頭,老板滿口應(yīng)諾后,你不能立即扭頭就走啊。他還需要點(diǎn)時(shí)間來(lái)處理,不然一輩子都買(mǎi)不到3個(gè)饅頭。具體結(jié)合到stm32芯片,程序執(zhí)行是基于哈佛結(jié)構(gòu)的流水線形式,前面代碼執(zhí)行時(shí)依然可以執(zhí)行后序的指令代碼。
談到這里,有人或許想到在清除中斷請(qǐng)求位的代碼后面加上一句內(nèi)存屏蔽指令,即DSB。應(yīng)該說(shuō)加這個(gè)DSB指令是有效的,即該指令前的所有內(nèi)存訪問(wèn)指令執(zhí)行完畢后才執(zhí)行后序指令代碼。不過(guò),一般來(lái)講,在這個(gè)地方用不著它,我們只須注意別將清除中斷請(qǐng)求位的代碼放在服務(wù)程序的末尾,稍微給清零操作留點(diǎn)實(shí)現(xiàn)時(shí)間。就像上面打比方買(mǎi)饅頭一樣,給老板一點(diǎn)為你取饅頭的時(shí)間就行。
也許有人會(huì)說(shuō),我中斷服務(wù)程序里就只需做中斷請(qǐng)求位清零這一件事怎么辦呢?那你就隨便在清零操作代碼后面隨便一兩行無(wú)關(guān)緊要的代碼也行,確保不發(fā)生1次事件進(jìn)兩次中斷即可。
剛才前面說(shuō)了,當(dāng)清除中斷請(qǐng)求位的代碼放在服務(wù)程序最后時(shí),程序指令執(zhí)行速度越快,一次觸發(fā)事件進(jìn)入兩次中斷服務(wù)程序的可能性就越高。我們不妨看看下面基于STM32H7系列的一段中斷服務(wù)程序代碼。是TIM3的更新中斷服務(wù)程序,截圖里的兩行代碼為中斷服務(wù)程序的最末兩行。注意,清除中斷標(biāo)志的代碼沒(méi)有在最末一行。
其基本功能就是每進(jìn)一次更新中斷,先清中斷標(biāo)志,然后給SPI數(shù)據(jù)寄存器賦值令其發(fā)送一個(gè)16位數(shù)據(jù)。顯然,結(jié)合我們前面的分析,如果代碼這樣寫(xiě)一般來(lái)講是不太可能發(fā)生一次事件觸發(fā)2次中斷的,事實(shí)上當(dāng)程序代碼在FLASH里運(yùn)行時(shí)也的確沒(méi)有任何問(wèn)題。
但當(dāng)將中斷服務(wù)程序放到RAM里,比方放到DTCM里去運(yùn)行時(shí)發(fā)生了功能異常。結(jié)果變成了每次更新事件發(fā)送的數(shù)據(jù)不是16位而是32位了。這個(gè)32位數(shù)據(jù)正是因?yàn)橐淮胃率录B續(xù)兩次進(jìn)入中斷服務(wù)程序,兩次發(fā)送SPI數(shù)據(jù)。那為什么完全相同的代碼在FLASH里運(yùn)行沒(méi)這個(gè)問(wèn)題呢,因?yàn)榇a在DTCM的運(yùn)行速率要比在FLASH里快,盡管在清中斷請(qǐng)求標(biāo)志的代碼后面已經(jīng)有了兼具延時(shí)功能的那句針對(duì)SPI數(shù)據(jù)寄存器的賦值語(yǔ)句,在退出中斷前該請(qǐng)求標(biāo)志位還是未完成清零而再進(jìn)了一次中斷。
看來(lái),這里還得稍微加多點(diǎn)延時(shí)以保證中斷請(qǐng)求標(biāo)志在退出中斷前被清零。為了避免加延時(shí)代碼的盲目性,即要么短了要么長(zhǎng)了,我們可以使用對(duì)標(biāo)志位的輪詢方式,將代碼稍加改動(dòng)變成下面的樣子。
之后,再行驗(yàn)證測(cè)試都是正常的。若有興趣的話,可以在清標(biāo)志位的代碼后面加DSB指令驗(yàn)證測(cè)試下。
責(zé)任編輯:pj
-
芯片
+關(guān)注
關(guān)注
453文章
50262瀏覽量
421176 -
mcu
+關(guān)注
關(guān)注
146文章
16925瀏覽量
350008 -
計(jì)數(shù)器
+關(guān)注
關(guān)注
32文章
2253瀏覽量
94298
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論