本文作者:Miler Shao 某日某工程師跟我交流,他在使用STM32F031的芯片開發(fā)一款電子產(chǎn)品. MCU跟外界有個(gè)UART串口通訊,外界經(jīng)常會(huì)不定期地傳送一串固定數(shù)量的數(shù)據(jù)包過來。令他郁悶的是,在從外界接受數(shù)據(jù)時(shí)偶爾會(huì)出現(xiàn)數(shù)據(jù)丟失一個(gè)兩個(gè)的,尤其波特率高的時(shí)候容易發(fā)生。 經(jīng)過跟他深入溝通,了解到STM32F031跟外界有UART數(shù)據(jù)通信并開啟了RXNE接收中斷,還有對(duì)外的AD采樣動(dòng)作,通過定時(shí)器定時(shí)觸發(fā)AD轉(zhuǎn)換,并開啟了ADC1的轉(zhuǎn)換完成中斷。AD觸發(fā)間隔為2秒。再就是些其它對(duì)外的GPIO操作的東西。他陳述當(dāng)波特率低于9600,甚至更低時(shí)就很難遇到丟包的現(xiàn)象,只要當(dāng)波特率達(dá)到115200甚至更高時(shí),就比較容易丟包,經(jīng)常丟一個(gè)兩個(gè)不等,波特率越高越容易丟。 客戶給USART1的時(shí)鐘源配置的是48M系統(tǒng)時(shí)鐘。按理說,STM32F0芯片的UART的波特率跑個(gè)200K是很輕松的事。讓他用示波器在芯片RX腳監(jiān)測(cè)外界傳輸過來的信號(hào),當(dāng)外界發(fā)送方的波特率即使在200K左右時(shí)波形還是很干凈漂亮,看來不存在信號(hào)畸變的問題。 因?yàn)樗劦介_啟了UART RX中斷和ADC的EOC中斷,我懷疑他的中斷優(yōu)先級(jí)配置可能有問題。察看其代碼后,發(fā)現(xiàn)關(guān)于UARTTX/RX中斷與ADC的EOC中斷優(yōu)先級(jí)一樣的 。 看到這里,基本算是找到原因了,只待進(jìn)一步驗(yàn)證。 外界不定期通過UART發(fā)送數(shù)據(jù)給MCU,當(dāng)它收到一個(gè)數(shù)據(jù)本該通過RX中斷請(qǐng)求去讀取數(shù)據(jù)時(shí),如果此刻碰上ADC的EOC中斷服務(wù)程序剛剛開始或正在執(zhí)行途中,由于二者優(yōu)先級(jí)一樣,那UART的RX 中斷就得至少等待ADC中斷服務(wù)程序繼續(xù)執(zhí)行到彈棧前的時(shí)間。若在這個(gè)等待期間內(nèi)UART又收到了第二個(gè)數(shù)據(jù)甚至更多,那就會(huì)發(fā)生溢出導(dǎo)致數(shù)據(jù)丟失。 那為什么會(huì)只是偶爾發(fā)生而且波特率高更容易發(fā)生呢?這也不難理解。 波特率高意味著傳送速度快,相應(yīng)的每個(gè)字符的傳送時(shí)間就短,即收到一個(gè)字符后,下一個(gè)字符來得也快。而每次的ADC的中斷程序執(zhí)行時(shí)間是相對(duì)固定的,最糟糕的情形就是產(chǎn)生UART RX中斷請(qǐng)求時(shí)碰到EOC中斷服務(wù)程序剛剛開始,這樣等待時(shí)間最長(zhǎng)。在UART接收到數(shù)據(jù)等待ADC中斷釋放CPU期間,新的數(shù)據(jù)來得越快,丟數(shù)據(jù)的幾率就越高。當(dāng)然了,不是每次都是碰到那個(gè)最糟糕的情形,最好的情形就是碰上ADC中斷服務(wù)程序剛好執(zhí)行完畢。 反過來講,如果UART傳輸波特率比較低,意味著單個(gè)字符傳輸時(shí)間相對(duì)比較長(zhǎng)。碰到ADC中斷服務(wù)程序先得到響應(yīng)情況下,或許等人家執(zhí)行完了再來取“待取走”的數(shù)據(jù)還來得及,尤其不在最糟糕的情形下。 當(dāng)該工程師將UART RX中斷優(yōu)先級(jí)配置為高于ADC的中斷優(yōu)先級(jí)后就再?zèng)]那個(gè)麻煩了。 順便說說上面那紅色語句“等待ADC中斷繼續(xù)執(zhí)行直到彈棧前”。 這句紅色的話意思是說,在上面情況下,UART中斷請(qǐng)求等待EOC中斷運(yùn)行到執(zhí)行POP之前的時(shí)刻就可得到響應(yīng)而去執(zhí)行UART RX中斷服務(wù)程序,并不急著執(zhí)行EOC中斷的POP彈棧動(dòng)作,隨之的UART中斷服務(wù)程序也無需壓棧操作,UART中斷程序執(zhí)行完畢后再回來做彈棧動(dòng)作,然后回到主循環(huán)的中斷處接著運(yùn)行。這就是平常所說的咬尾中斷。不難看出,這樣可以大大提升中斷響應(yīng)速度。具體到本案,這個(gè)咬尾操作一定程度上減少了丟碼機(jī)會(huì)。  既然提到了咬尾中斷,可能很多人聽說過晚到中斷。所謂晚到中斷,簡(jiǎn)單點(diǎn)說就是低優(yōu)先級(jí)中斷服務(wù)程序正在壓;騽倝簵M戤厱r(shí)發(fā)生更高優(yōu)先級(jí)的中斷請(qǐng)求,高優(yōu)先級(jí)中斷不再做PUSH壓棧操作,等到低優(yōu)先級(jí)中斷壓棧完畢即直接運(yùn)行高優(yōu)先級(jí)中斷服務(wù)程序,隨后再返回來接著執(zhí)行低優(yōu)先級(jí)中斷服務(wù)程序,之后再做POP彈棧操作。 前面提到的STM32F0 的兩個(gè)中斷優(yōu)先級(jí)相同情況下,都是假定中斷請(qǐng)求在時(shí)間上錯(cuò)開了的情況。如果二者同時(shí)到達(dá),那CPU先響應(yīng)哪一個(gè)呢?就看二者在中斷矢量表的序號(hào),誰的序號(hào)小就先響應(yīng)誰。
另外,玩過CORTEX M3/M4內(nèi)核MCU的人,比方STM32F1,STM32F2,STM32F3,STM32F4等芯片的人可能會(huì)發(fā)現(xiàn),CORTEX M0 內(nèi)核的MCU的中斷管理跟其它CORTEX M3/M4內(nèi)核的在中斷優(yōu)先級(jí)管理上是有差異的。 M3/M4的MCU在中斷優(yōu)先級(jí)做分組管理,分搶占優(yōu)先級(jí)和響應(yīng)優(yōu)先級(jí)。只有強(qiáng)占優(yōu)先級(jí)高的中斷請(qǐng)求才可以打斷低搶占優(yōu)先級(jí)的中斷服務(wù)程序;搶占優(yōu)先級(jí)相同的情況下,高響應(yīng)優(yōu)先級(jí)的中斷請(qǐng)求頂多可以優(yōu)先獲得響應(yīng)權(quán)。而M0內(nèi)核芯片的中斷優(yōu)先級(jí)不再做分組管理,誰的優(yōu)先級(jí)高就優(yōu)先響應(yīng)并可打斷低優(yōu)先級(jí)的中斷服務(wù)程序。 當(dāng)在系統(tǒng)里開啟多個(gè)中斷事件時(shí),要合理安排各中斷源的優(yōu)先級(jí),有些時(shí)候可能還需精心安排。對(duì)于初學(xué)者,因?yàn)橹袛鄡?yōu)先級(jí)問題處理不當(dāng)而導(dǎo)致麻煩的情況時(shí)有發(fā)生。再就是對(duì)于中斷服務(wù)程序,如果不是必需,代碼盡量精簡(jiǎn),不要累贅,能放到中斷外部處理的就盡量放到外部去。 |