關於STM32的I2C接口死鎖在BUSY狀態沒法恢復的現象,網上已有不少討論,看早幾年比較老的貼子,有人提到復位MCU也沒法恢復、只有斷電才行的情況,那但是至關嚴重的問題。相似復位也沒法恢復的狀況是存在的,技術支持矢口否定問題存在,並非正確面對問題的態度。好比我用這款F439芯片的SDRAM控制器,在錯誤操做後進入HardFault狀態,復位沒法恢復,JTAG也沒法聯機,只能斷電重來,官方的Erratasheet裏也提到了。html
若是I2C接口沒法可靠工做,那麼所作的設計將存在嚴重隱患,不可能要求用戶用斷電的方法恢復系統。若是像某些網友提到棄用硬件I2C,轉爲GPIO模擬I2C時序,那麼首先I2C時鐘頻率不易肯定,由於STM32的時鐘頻率能夠動態調節;此外不用硬件I2C,沒法用中斷、DMA等高級模式,會嚴重下降ARM內核效率。因此務須確認和解決這個問題。函數
一.問題存在工具
我用STM32F439IGT,爲了肯定問題存在,讓I2C控制器做Master,先人爲產生I2C總線故障。產生I2C總線故障的方法簡單而粗暴:在I2C總線工做過程當中,用鑷子把SCL和SDA兩個信號短路一下,很容易進入BUSY死鎖狀態。長時間短路也可能產生超時。HAL_I2C_Init()、HAL_I2C_Master_Transmit()、HAL_I2C_Master_Receive()等函數返回值分別爲HAL_BUSY(0x02)、HAL_TIMEOUT(0x03)。測試
試着用MCU復位,是能夠恢復的,說明硬件沒死穴。又測試不用MCU復位,而是在程序中依次調用STM32Cube_FW_F4_V1.5.0固件庫提供的以下兩個初始化函數:HAL_I2C_DeInit(&hi2c1)、HAL_I2C_Init(&hi2c1),並不能保證必定恢復正常。ui
BUSY死鎖時,用萬用表測試I2C信號電壓,SCL、SDA均爲低電平。若是調用函數:HAL_I2C_DeInit(&hi2c1),會函數釋放IO口回到GPIO的默認狀態(Input),此時再測SCL、SDA電壓,均爲高電平。這說明總線是被MCU這邊的Master拉低的,而不是被Slave拉低的。固然也存在Slave恰好輸出低電平拉低SDA的可能。this
二.出錯代碼位置跟蹤url
單步運行,能夠看到進入stm32f4xx_hal_i2c.c程序中I2C讀寫函數不遠處(如圖陰影所在行),讀BUSY位,總會獲得SET的結果,沒法繼續執行後續程序而返回。spa
三.參考文獻設計
讀了網上不少解決方案,其中比較有啓發意義的有這幾篇:htm
1. 百度文庫,這個好像是ST官方客服提供的,關於死鎖的可能機理和解決方案作了說明:
http://wenku.baidu.com/link?url=KB9p-TYrQcmVu1azHG66BXAcG6Pe6Bm2kWF_9ERSU35EOA8obiTVTDrZ6fZ3IOjfVAb71RCvJIiAODo4p4Sr0fUPDy0kQyyqWWJgxjfYHzO
2. STM社區,這個提到了初始化I2C引腳前應該先置爲OUT及高電平。這在上電初始化時無虞,由於MCU復位後IO口爲輸入,並由外部上拉電阻拉爲高電平。但在作故障恢復時很重要,由於此時IO口可能正被Master或Slave拉成低電平。http://www.stmcu.org/module/forum/thread-518463-1-1.html
3. 這個解決方案和上面思想兩個相仿,可是寫了太多代碼,又有放置位置的要求,看起來頭大。僅做參考:http://bbs.ednchina.com/BLOG_ARTICLE_2154168.HTM
4. 最重要的說明,在ST官方提供的STM32F4xx用戶指南:RM0090 Reference manualRev9,第845頁,關於I2C_CR1,SWRST位的Note,提到解決BUSY死鎖問題:
意思是說SWRST位能夠在出錯或死鎖時,用於復位I2C控制器,例如衆所周知的BUSY位問題。我沒有看其它老STM型號的手冊,至少STM32F4xx有SWRST位,STM32L0xx用戶指南提到能夠用PE位復位。
四.問題的解決方案
按照ST手冊的提示,通過各類嘗試,本着儘可能少改動代碼、儘可能不改動固件庫裏只讀文件的原則,個人解決方案以下所述。假設主程序裏有以下的代碼,返回值ret不等於0表示出錯,按stm32f4xx_hal_def.h頭文件中的錯誤代碼定義,返回值爲0x02是HAL_BUSY,0x03是HAL_TIMEOUT,這兩個返回值均可能獲得。下面程序裏紅色的兩行是錯誤處理必須的:
4.1 主程序改動,加錯誤處理代碼2行:
unsigned char ret = Sensor_ReadData(uint8* buf); // I2C讀寫函數
if (ret != 0) { //I2C故障處理
HAL_I2C_DeInit(&hi2c1); //釋放IO口爲GPIO,復位句柄狀態標誌
HAL_I2C_Init(&hi2c1); //這句從新初始化I2C控制器
}
else {
// 。。。。I2C無錯誤時的正常程序
}
4.2 子程序的改動,加7行代碼:
上面HAL_I2C_Init(&hi2c1)函數會調用HAL_I2C_MspInit(hi2c)函數,這個函數在stm32f4xx_hal_msp.c文件中實現,主要是初始化IO口以及外設,由STM32CubeMX工具生成或用戶自行編寫,非只讀文件。如下節選該函數第一段,其中I2C端口用哪一個pin,是由用戶本身設定的,我這裏用的PB六、PB7。紅、綠底色的幾行是爲了處理BUSY死鎖問題專門插入的。
void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(hi2c->Instance==I2C1)
{
__I2C1_CLK_ENABLE();
// PB6 ----> I2C1_SCL
// PB7 ----> I2C1_SDA
// strong pull-uphigh to recover from locking in BUSY state
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; //此行原有
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //GPIO配置爲輸出
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; //強上拉
HAL_GPIO_Init(GPIOB,&GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, 6, GPIO_PIN_SET); //拉高SCL
HAL_GPIO_WritePin(GPIOB, 7, GPIO_PIN_SET); //拉高SDA
hi2c->Instance->CR1= I2C_CR1_SWRST; //復位I2C控制器
hi2c->Instance->CR1= 0; //解除復位(不會自動清除)
// 如下是原有代碼
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//。。。
}
上面程序中,把I2C端口配置成GPIO-OUTPUT,並強制拉高,是必需的。注意到手冊裏關於SWRST位說明的第一句:「When set, the I2C isunder reset state. Before resetting this bit,make sure the I2C lines are released and the bus is free.」 意思就是置位SWRST,會使I2C控制器保持在復位狀態。解除復位前,確保I2C總線已經釋放到空閒狀態,即SCL、SDA均爲高電平,再恢復I2C控制器。因此解除復位是用戶來作的,硬件不會自動清除該位。
五.結論
我用這款STM32F439IGT單片機,I2C部分沒有出現斷電才能解除BUSY死鎖的嚴重問題,看來STM已經意識到這個硬BUG,並在後期產品裏逐步進行了改進。
在沒有硬件死穴的狀況下,我這裏僅增長10行程序,就能夠用軟件恢復故障。屢次嘗試,觸發I2C故障時,一次就能夠恢復,無需加延時等語句,也未改動現有固件庫代碼。
Circuitlife
2015年6月3日