近日某工程師向我反饋,他遇到如下問題:


現(xiàn)象是:系統(tǒng)中存在一個 10 ms 定時器中斷,中斷服務函數(shù)中會翻轉(zhuǎn) PB5。但在 main 函數(shù)中調(diào)用了他自己實現(xiàn)的 Ht1621DataUp()函數(shù)后,PB5 不再按 10 ms 周期翻轉(zhuǎn),而是出現(xiàn)無規(guī)律的跳變。根據(jù)此現(xiàn)象,他認為該函數(shù)影響了中斷。
為此他還換過MCU芯片,看來是想驗證下是不是MCU出了問題。

這個問題其實一看就可以判斷出是軟件使用問題。拿到他的代碼后我首先進行了測試,現(xiàn)象確實如他所說:
這個是PB5正常翻轉(zhuǎn)的波形:

下面是加了函數(shù)調(diào)用后異常翻轉(zhuǎn)波形:

Ht1621DataUp()函數(shù)本身并沒有定時器TIM1相關(guān)的任何代碼,所以TIM1的10ms 定時本身不可能出現(xiàn)問題。發(fā)生此問題的原因到底是什么呢?
原因是他在Ht1621DataUp()函數(shù)里也有GPIO的相關(guān)操作,

看起來是只操作了PB2、PB3、PB4三個引腳,并沒有操作PB5,但是實際上會影響到PB5的輸出。所以上述現(xiàn)象本質(zhì)上是PB5的電平被“定時器中斷翻轉(zhuǎn)”和“HT1621刷新位操作”交替改寫了。
原因出在GPIOB->ODR |= xxxxxx;的使用上,這條語句實際上執(zhí)行的讀-改-寫操作。
以上述異常波形中的局部一段為例說明,

剛開始是正常的輸出PB5高電平,while(1)里程序里讀取的ODR PB5也是高電平,緊接著定時器中斷改寫PB5為低電平,之后while(1)里繼續(xù)往下執(zhí)行后續(xù)的改和寫操作,基于之前讀到的高電平,所以又把PB5改寫成了高電平,這就導致 PB5 的低電平被覆蓋,從而產(chǎn)生異常波形。在下一個10ms定時器中斷到了之后又會執(zhí)行PB5輸出高的操作。所以整體就看到了上述的異常波形。
但是while(1)就基于之前的高電平,把它又改為了高電平。到10ms再之后,中斷又弄成了高電平,就看到了上述波形。
Ht1621Wr_Data()并不直接操作 PB5,但由于它與 TIM1 中斷都在對同一 GPIO 端口執(zhí)行“讀?改?寫”操作,因此會間接干擾 PB5 的狀態(tài),造成競態(tài)問題。
解決該問題的方法也很簡單:把ODR 改成 BSR寄存器,這樣每次置位/復位只會影響它自己的腳,不會把其他不相關(guān)的引腳一起帶著改。
ODR(Output Data Register)寄存器:本身不是原子操作,對其的位操作需要讀-改-寫三步,可能被中斷打斷導致競態(tài)。
BSRR(Bit Set Reset Register)寄存器:是原子操作(通過一次寫操作完成,無需讀-改-寫)。
關(guān)注我們:
掃碼加入嵌入式交流群:
