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