關於STM32的I2C硬件DMA實現


 

關於STM32的I2C硬件DMA實現

 

 

網上看到很多說STM32的I2C很難用,但我覺得還是理解上的問題,STM32的I2C確實很復雜,但只要基礎牢靠,並沒有想象中的那么困難。

那么就先從基礎說起,只說關鍵點,不涉及代碼。

首先說I2C這個協議:協議包括START、ACK、NACK、STOP。盡管協議中規定START必須,其他幾個非必須,但實際上其他三個仍舊非常重要。

主發從收:主 START -> 主發地址 -> 從 ACK -> (主發數據 -> 從 ACK (循環)) -> 主 STOP 或 主 START 啟動下一次傳輸

這一過程中,主控SCL線,從只在ACK時控SDA線,其他時刻主控SDA線。

主收從發:主 START -> 從發地址 -> 主 ACK -> (從發數據 -> 主ACK (循環)) -> 接受至最后一個字節時,主 NACK -> 主 STOP 或 主 START 啟動下一次傳輸

在這一過程中,主 START、主 ACK 時,主控總線;從發ACK時,主控SCL線,從控SDA線;在主接受數據時,雖然由主設備產生時鍾,但從設備在數據未准備好時,拉低SCL線,這樣主設備可知從設備未發送數據,從設備在數據准備好,可以發送的時候,停止拉低SCL線,這時候才開始真正的數據傳輸,換句話說,雖然時鍾是由住設備產生,但在總線上未必就有時鍾存在,這期間可以看做是從設備在控總線;當發送到最后一字節的時候,主設備發送NACK,從設備接受后,放棄對總線的控制。

STOP在單主環境下非必要,但在多主環境就非常必要,主控總線的設備發送STOP后,通知總線其他設備總線已經閑置。

以前的老器件很容易導致總線死鎖,但現在的產品很多都帶有超時機制,所以總線被鎖的情況基本不怎么存在了。

下面要說的是STM32的寄存器,狀態寄存器有兩個,事件、錯誤狀態一堆,看起來確實都算是有用,但實際使用的時候未必都要用到,還是要看情況,那么狀態寄存器的清除就是個問題,有兩個方法,一個是PE位禁止,不過除非在通訊結束,否則會擾亂總線上的電平,后果未知,爭取的方法是:對於普通事件,先讀SR1,再讀SR2,如果是錯誤,那么就要再增加一個將SR1寫 0 。想簡單的話,那就用一個32位無符號整形,先讀SR1,然后或上SR2左移16,再將SR1寫 0,最后用這個變量和ST公司提供那個庫中的狀態比對就行了。

STM32的I2C和其他模塊有些不同,其他模塊完全可以交給DMA控制器,但I2C不行,必須結合中斷或者IO方式,不建議IO方式,得等,萬一出點岔子,被狗咬就麻煩了,所以最佳方式是結合中斷。

主發時:PE位使能,PE位必須先使能,否則你操作不了其他位,然后使能ACK位,ITEVTEN位,DMA位,使能START位(這幾個位可以同時置),然后進入事件中斷,判斷 I2C_EVENT_MASTER_MODE_SELECT ,將從地址寫入 DR 寄存器,這里需要注意一點,就是從設備應答后,如果主設備不讀狀態寄存器,那么主設備就不會繼續發送時鍾來傳輸數據!這時候就體現出使用中斷方式的另外一個好處,每次進中斷的時候狀態寄存器都要被讀一下,不符合處理條件的你可以不管,但模塊操作可以正確進行下去。數據開始傳輸時,控制就基本完全交給DMA控制器了,這時候一般也不會有什么狀態中斷產生,當然也不是絕對沒有,有可能會有錯誤中斷,也可能會由於MCU過忙產生事件中斷,但這個事件一般影響不大,出錯的時候你可能要處理一下。當數據傳輸完成后,會產生一個 I2C_EVENT_MASTER_BYTE_TRANSMITTED,注意這個不是只在數據傳輸完成才有!如果MCU過忙,DMA在I2C傳輸完上一個數據時,沒能將下一個數據送到I2C,也會產生,這個事件只代表I2C位移寄存器內的數據被傳完,而DR寄存器又沒有被寫入新的數據!所以,在這個狀態產生的時候,要判斷一下DMA的CNDTR寄存器,這是個遞減的,如果是 0 ,那么就代表完成,可以去掉I2C的ACK位,使能STOP;或者是START進入下一輪數據傳輸。當然你不管也行,單主控下這不是必須的。

主收時:前面和主發時一樣,但有一點要特別注意,那就是主控寄存器的LAST位,這個我在ST的庫中沒找到設置的函數,也可能是我沒看仔細,反正我都是直接寄存器操作,不用庫,除非是庫中一些現成的狀態可以用一下。這個位很重要,如果你只是一輪DMA傳輸,那么這個必須被置位,因為傳輸到最后一個字節的時候,主控需要發出NACK而不是ACK來通知從設備釋放對總線的控制!LAST位就是做這個用的。主收的時候,傳輸完成就不是依靠I2C的事件中斷來判斷了,這個要通過DMA的IT_TC來完成,DMA中斷產生后,做一下結束處理工作,最后別忘了清DMA的中斷標志,不然會死循環在里面。

從發和從收這次就先不寫了,相對簡單一些,而且我感覺用的一般也不多吧,等有時間下次再寫,另外再說一下,采用這種DMA+中斷的方式,可以不去處理錯誤,操作開始的時候置一個標志,結束的時候清標注,在主程序中判斷,如果超過一定時間標志還在那,那么就要考慮重置I2C了,一方面是錯誤狀態太多……我真的判斷不過來,也可能我比較懶吧,都給統一處理了。還有一個建議就是盡量采用STM32的硬件位域操作,因為一方面你有些操作要在主程序里,一些操作要在中斷里,通常的讀再寫可能會導致錯亂,位域操作就不會,即使不錯亂,如果總線上產生錯誤,那么在操作某些位的時候會卡死在那,位域操作也不會卡死。

附一個位域操作宏

#define BITBAND_ADDRESS(x) (((x) & 0xF0000000) + 0x02000000 + (((x) & 0xFFFFF) << 5))
#define BITBAND(x,bit) (*(volatile uint32_t*)(BITBAND_ADDRESS((uint32_t)&(x)) + ((bit) << 2)))

使用 BITBAND(x,bit),x 代表 寄存器,bit 代表是操作哪個位。單獨的一個位會被展開成一個32位整形,當然,你只能寫 1 或 0


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM