轉載:關於STM32硬體I2C讀寫EEPROM代碼實現原理的理解與總結

来源:https://www.cnblogs.com/wy9264/archive/2019/11/25/11931262.html
-Advertisement-
Play Games

http://home.eeworld.com.cn/my/space-uid-716241-blogid-655190.html 一、I2C協議簡介 I2C是兩線式串列匯流排,用於連接微控制器及其外圍設備。兩根信號線分別是: 時鐘信號線SCL和數據信號線SDA。 二、I2C匯流排傳輸時序 2.1 I2 ...


http://home.eeworld.com.cn/my/space-uid-716241-blogid-655190.html 一、I2C協議簡介 I2C是兩線式串列匯流排,用於連接微控制器及其外圍設備。兩根信號線分別是: 時鐘信號線SCL和數據信號線SDA。   二、I2C匯流排傳輸時序 2.1 I2C傳輸協議的三種信號 I2C在數據傳輸過程中有三種信號類型,分別是:起始信號、結束信號和應答信號。 ①起始信號:在時鐘信號SCL為高電平時,數據線SDA由高電平跳變為低電平,開始傳輸數據; ②結束信號:在時鐘信號SCL為高電平時,數據線SDA由低電平跳變為高電平,數據傳輸結束; ③應答信號:接收數據的IC在接收8位(一個位元組)數據後,向發送數據的IC發出特定的低電平信號,表示已經收到數據。準確的說法是:發送設備在時鐘信號SCL的8個脈衝的驅動下發送了8個bit,即一個位元組後,在SCL第九個脈衝到來前將SDA線釋放(即將其電平拉高),由接收設備在SCL第9個脈衝期間返回一個應答信號,即將SDA電平拉低。要註意的是:在SCL第9個脈衝高電平期間,SDA的低電平信號必須保持穩定。(實際上I2C協議中,數據傳輸時,SCL每個脈衝的高電平期間,SDA上的信號都必須保持穩定,只有在SCL為低電平期間,SDA上的數據才能變化。)   2.2 I2C協議規定 ①什麼時候匯流排為空閑狀態? SDA和SCL同時為高電平時,規定匯流排為空閑狀態。即只要SDA和SCL中有一個為低電平,則匯流排為忙狀態。 ②關於應答信號說明 應答信號為低電平時,規定為有效應答,簡稱ACK,表示已接收到數據;應答信號為高電平時,規定為非應答,簡稱NACK,一般表示數據接收沒有成功,有時根據相關操作時序,也可能需要產生一個NACK,這個後面會有說明。 2.3 傳輸協議數據包構成圖示

三、以I2C讀寫EEPROM(AT24C02)為例進行說明

3.1 針對EEPROM的基本操作

①寫操作

②讀操作

針對EEPROM的寫操作和讀操作也可以細分,如:

寫操作:

①往EEPROM中寫入一個位元組的數據;

②往EEPROM中批量寫入數據;

讀操作:

①從EEPROM中指定地址讀取一個位元組數據;

②從EEPROM中批量讀取數據;

下麵結合上面4種讀寫操作來具體說明:

1、往EEPROM中寫入一個位元組數據

1)寫入時序

2)時序分析

①第一步:主機產生起始信號,開始傳輸;

②第二步:發送掛載在I2C匯流排下的從機設備地址(這裡由於是針對EEPROM,其地址是7位),註意:EEPROM的地址高四位已經固定,為1010,低三位由自己的硬體設計決定,一般都將EEPROM的這三根地址線直接接地,所以低三位地址就為000,這樣該EEPROM的設備地址就為1010 000。由於這裡是寫入數據,所以R/W位就為0,這一位與前面的7位地址組成一個位元組,所以寫入數據時,發送的從機地址就為0xA0;

③第三步:發送完從機地址後,主機會釋放SDA線,等待從機給一個低電平應答信號ACK,主機收到ACK後,進行下一步;

④第四步:發送將要寫入數據的地址,這裡要搞清楚,這個地址不是上面的從機地址,而是我們要寫入數據到EEPROM中具體地址;(兩者的關係可以打個比方:我們假設I2C匯流排就是一條叫香樟大道的馬路,EEPROM就是這條馬路上某個地方的一棟大樓,地址是香樟大道99號,而寫入數據的地址就是香樟大道99號這棟樓里的某個具體房間號。)

⑤發送完寫入數據地址後,釋放SDA匯流排並等待從機給一個低電平的應答信號ACK;

⑥主機收到應答信號ACK後,開始向指定的地址中寫入一個位元組數據,寫完一個位元組數據後釋放SDA匯流排;

⑦從機接受到一個位元組數據後,返回一個應答信號給主機,主機接受到應答信號後,發送一個結束信號來結束本次數據傳輸。

3)根據時序分析調用STM32庫函數編寫具體的程式 /*  * 函數名:I2C_EEPROM_ByteWrite  * 描述:向EEPROM中寫入一個位元組數據  * 輸入參數:pBuffer—指針變數,指向我們要發送的數據  *           WriteAddr—要寫入數據的地址  * 說明:程式中EEPROM_ADDRESS是一個巨集定義,是從機地址,為0xA0  */ void I2C_EEPROM_ByteWrite(uint8_t *pBuffer, uint8_t WriteAddr) {  /*①調用庫函數,產生起始信號 */  I2C_GenerateSTART(I2C1, ENABLE);  /*檢測EV5事件(即SB=1,表示起始信號已發送)*/  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));    /*②調用庫函數發送從機地址*/  I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);  /*檢測EV6事件(即ADDR=1,表示從機設備地址已經發送,並且收到從機的應答)*/  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));    /*③調用庫函數發送要寫入數據的地址*/  I2C_SendData(I2C1, WriteAddr);  /*檢測EV8事件(即TxE=1,數據寄存器空,就是地址已經發送完成)*/  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTING));    /*④調用庫函數發送要寫入的數據*/  I2C_SendData(I2C1, *pBuffer);  /*檢測EV8_2事件(即TxE=1, BTF=1,請求設置停止位)*/  while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));    /*⑤調用庫函數發送停止信號結束傳輸 */  I2C_GenerateSTOP(I2C1, ENABLE); }   4) 關於3)中向EEPROM中寫入一個位元組數據函數的詳細解析 看了上面的函數,可能有些人會有疑問,比如為什麼你這函數要這麼寫?發送完起始信號為什麼要檢測EV5?後面的操作中又為什麼要檢測EV6、EV8或者EV8_2?等等問題。下麵我結合《STM32中文參考手冊_V10》中有關I2C的相關內容來進行說明。 首先,我們來看看《STM32中文參考手冊_V10》P498中有關主發送器的相關時序說明:

圖4中我們要看的是7位主發送的序列圖,下麵結合圖4的主發送器傳送序列圖來解釋上面寫一個位元組數據到EEPROM函數:

①主機產生起始信號,起始信號發送後,會產生EV5事件,EV5即表示SB=1,SB是I2C狀態寄存器I2C_SR1的bit0,如下圖5:

看上面的圖5可以知道,如果起始條件已發送,SB會被置“1”,即產生事件EV5,所以我們要檢測該位確認起始條件已發送;

②發送從機設備地址,從機應答後,會產生EV6事件,即ADDR=1,ADDR是I2C_SR1寄存器的bit1,說明如下圖6:

通過圖6我們可以知道,當地址發送完成,主機收到從機的應答後,ADDR位被置“1”,我們通過檢測EV6事件來判斷地址已發送完成並清除相關位;

③發送要寫入數據的地址(地址也是數據),從機收到數據後應答,通過圖4傳送序列圖可以看出,在第一個數據(第一個數據是要寫入數據的地址)發送完並收到應答後,EV8事件早已經產生,EV8表示TxE=1,移位寄存器非空,數據寄存器空,寫入DR寄存器將清除該事件。數據寄存器空,移位寄存器非空說明我們要發送的數據已經由數據寄存器轉入移位寄存器,數據一旦轉入移位寄存器就會在時鐘信號的驅動下自動一位一位的將數據發送出去,所以我們在發送完地址後檢測EV6事件,確認數據已經由數據寄存器轉移到移位寄存器中發送,可以向數據寄存器中寫入新的數據;

④發送我們要寫入到EEPROM中的數據,從機應答後,檢測EV8_2,這裡為什麼是檢測EV8_2而不是EV8?因為我們這個函數只向EEPROM中寫入一個位元組數據,所以這個位元組數據就是最後一個要發送的數據,通過圖4傳送序列圖可以看出,最後一個數據傳輸完成後產生事件EV8_2,表示TxE=1,BTF=1,請求設置停止位。至於為什麼產生的是EV8_2而不是EV8,我們來看看圖7中有關BTF的說明:BTF表示位元組發送結束,在發送時,當一個新數據被髮送且數據寄存器還未被寫入新的數據(TxE=1)時,BTF會被置“1”,由於我們這個函數只向從機發送一個位元組數據(不包括地址),所以在這個位元組數據被髮送後並沒有新的數據寫入到數據寄存器,因此符合了上面的條件,BTF被置“1”。在TxE=1、BTF=1的情況下,就產生EV8_2;

⑤檢測到EV8_2後,主機就可以產生停止信號來結束本次傳輸了。

有關寫入一個位元組數據到EEPROM中函數的分析到這裡就結束了,下麵來說一些從EEPROM中讀取一個位元組數據的相關代碼以及分析。

2、從EEPROM中指定地址讀取一個位元組數據 1)讀取時序

2)時序分析

①第一步:主機產生起始信號;

②第二步:主機發送從機設備地址,寫選通(即R/W為‘0’),從機接收到地址後應答;

③第三步:主機發送要讀取數據在EEPROM中的地址,從機接收到後應答;

④第四步:主機再次產生起始信號;

⑤第五步:主機發送從機設備地址,讀選通(即R/W為‘1’),從機接收到後應答;

⑥第六步:從機發送數據,主機接收,接收到最後一個數據後非應答;

⑦第七步:主機產生停止信號結束傳輸。

3)根據時序分析調用STM32庫函數編寫具體的程式 /*  * 函數名:I2C_EEPROM_ByteRead  * 描述:從I2C EEPROM中讀取一個位元組數據  * 輸入:pBuffer—指針變數,*pBuffer用來存放讀取到的數據  *       ReadAddr:讀取數據的地址  * 輸出:無 */ void I2C_EEPROM_ByteRead(uint8_t *pBuffer, uint8_t ReadAddr) {         /*等待EEPROM準備好*/ I2C_EE_WaitEepromStandbyState(); /*①產生起始信號*/ I2C_GenerateSTART(I2C1, ENABLE); /*檢測EV5並清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); /*②發送從機地址,寫選通*/ I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); /*檢測EV6並清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); /*③發送要讀取數據的地址*/ I2C_SendData(I2C1, ReadAddr); /*檢測EV8並清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); /*④重新發送起始信號 */ I2C_GenerateSTART(I2C1, ENABLE); /*檢測EV5並清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); /*⑤發送從機設備地址,讀選通*/ I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver); /*檢測EV6並清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); /*⑥檢測EV7,然後讀取數據清除標誌*/ while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));  *pBuffer = I2C_ReceiveData(I2C1); /*⑦非應答*/ I2C_AcknowledgeConfig(I2C1, DISABLE);         /*⑧產生停止信號*/ I2C_GenerateSTOP(I2C1, ENABLE); }   4)從EEPROM中讀取一個位元組數據的函數分析 與之前的寫一個位元組函數一樣,會發現這裡除了我們根據讀取時序列出的步驟之外,還多了很多事件EVx的檢測,而且開頭還有一個等待EEPROM準備好的函數。下麵同樣根據EEPROM的讀取時序結合《STM32中文參考手冊_V10》中有關I2C相關章節的說明來解釋一下,首先我們來看看STM32的I2C主接收器傳送序列圖(這裡看7位主接收):

問題說明:

問題1:為什麼要加一句等待EEPROM準備好的函數:

EEPROM的寫入是需要時間的,只有當前面的寫入操作完成,它才能繼續響應後面的讀取操作,否則在它還沒有準備好的情況下,進行讀取肯定是不成功的。關於等待EEPROM函數準備好的函數,它的實現原理就是通過向EEPROM發送從機地址,看從機是否成功應答,如果應答了就表示EEPROM已經準備好,如果應答失敗就說明EEPROM還沒準備好,然後主機繼續發送從機地址來呼叫EEPROM等待應答,直到從機應答成功就退出該函數執行下一步,其代碼如下(這段代碼是我在野火的工程代碼上修改了一點點,大家可以直接去看野火的官方常式,一定會有所收穫的):

void I2C_EE_WaitEepromStandbyState(void)       {       vu16 SR1_Tmp = 0;       do      {            /* Send START condition */           I2C_GenerateSTART(I2C1, ENABLE);           /* Read I2C1 SR1 register */           SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1);           /* Send EEPROM address for write */            I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);       }while(!(I2C_ReadRegister(I2C1, I2C_Register_SR1) & 0x0002));          /* Clear AF flag */       I2C_ClearFlag(I2C1, I2C_FLAG_AF); } 問題2:   可能到這裡有人會有疑問,一個是STM32的I2C的主接收序列,一個是EEPROM的讀時序,那寫代碼時應該依照哪一個啊?我的理解是,我們要兩者結合,按照EEPROM的讀時序來搞清楚讀一個數據需要那幾步,按照STM32的I2C主發送和主接收序列來搞清楚每一步需要檢測什麼事件(本質就是檢測哪些寄存器的哪些位來判斷相應的步驟已經順利完成)。   下麵結合主接收和主發送的序列圖以及EEPROM的讀時序圖來分析代碼為什麼這樣寫: ①等待從機準備好函數,這個上面已經解釋過了,這裡不再贅述; ②根據圖8 EEPROM讀取一個位元組時序圖可知,首先我們要產生一個起始信號S,然後根據圖4主發送序列圖可知,在成功發送起始條件後,會產生事件EV5,因此我們通過檢測事件EV5來判斷起始條件是否發送成功並清除寄存器; ③根據圖8 EEPROM讀取一個位元組時序圖可知,發送起始信號後,我們接下來要發送從機的設備地址(寫選通,即R/W為‘0’),根據圖4主發送序列圖可知,如果成功發送從機設備地址並收到應答信號後,會產生時間EV6,因此我們通過檢測EV6來判斷從機設備地址的發送情況;  ④根據圖8 EEPROM讀取一個位元組時序圖可知,發送完從機設備地址後,我們接下來要發送讀取數據的地址,根據圖4主發送序列圖可知(這裡由於依然是主機在向從機發送數據,所以要參考圖4主發送序列圖),如果成功發送地址後,可以通過檢測事件EV8來判斷; ⑤根據圖8 EEPROM讀取一個位元組時序圖可知,接下來要重新發送起始信號(從這一步開始就要參考圖9主接收序列圖),同樣檢測事件EV5; ⑥根據圖8 EEPROM讀取一個位元組時序圖可知,接下來要發送從機設備地址,讀選通(即R/W為‘1’),根據圖9主接收序列圖可知,如果讀取地址發送成功,會產生事件EV6,因此我們通過檢測EV6來判斷和清除標誌; ⑦根據圖9主接收序列圖可知,接下來就是從機發送數據,主機接收數據,在主機接收一個數據後,會產生事件EV7,我們通過判斷EV7來查看數據接收是否完成,如果接收完成,就將數據寄存器中的數據讀取出來; ⑧根據圖9主接收序列圖可知,讀取一個數據後,主機要產生一個非應答信號,然後產生一個停止信號來結束本次傳輸(因為我們只讀取一個位元組數據,所以讀取的第一個數據也是最後一個數據,最後一個數據接收完成後產生非應答信號)。   到這裡,有關STM32的I2C寫入和讀取EEPROM操作就結束了,以上的內容都是我在學習相關知識內容時的一點總結和理解,希望能給需要的人帶來一些幫助,如果有什麼理解不對說明錯誤的地方,還請大家不吝批評指正,我會感激不盡,謝謝!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 背景: 去年以前可以按照目錄WebResourceUtility批量上傳web資源,昨天發現用不了了,拿到WebResourceUtility源碼改了一下都不是很方便,感覺官方寫的太冗餘,太長了,跟我喜歡的簡單粗暴思想不太符合,剛好無意閱覽了一個上傳資源的代碼,乾脆自己手寫一個根據目錄去上傳web資 ...
  • 在之前的泛型倉儲模式實現中,每個增刪改都調用了SaveChanges方法,導致每次更新都提交了事務。 在實際開發過程中,我們經常遇到同時操作多張表數據,那麼按照之前的寫法,對資料庫提交了多次操作,開啟了多事務,不能保證數據的一致性,結合工作單元(UnitOfWork)是為了把多次操作放到同一事務中, ...
  • 最近項目里有遇到一些併發的問題,想實現一個隊列來將併發的請求一個一個串列處理,可以理解為使用消息隊列處理併發問題,之前實現過一個簡單的 `EventBus`,於是想在 `EventBus` 的基礎上改造一下,加一個隊列,改造成類似消息隊列的處理模式。消息的處理(Consumer)直接使用 .netc... ...
  • 為什麼要使用泛型倉儲?好處是? 前兩章在autofac註入的時候,用的User類作為例子,寫了增刪改查四個介面,也就是倉儲的GRUD。 當我們再添加一個實體(比如Student)時,StudentRepository跟UserRepository代碼幾乎一樣的代碼,重覆量很大,為了減少冗餘、提高工作 ...
  • MiniProfiler 是一款性能分析的輕量級程式,可以基於action(request)記錄每個階段的耗時時長,還是可以顯示訪問資料庫時的SQL(支持EF、EF Code First)等 一、安裝程式包 通過Nuget安裝MiniProfiler : Install-Package MiniPr ...
  • Swagger 是一款自動生成線上介面文檔+功能測試功能軟體 一、安裝程式包 通過管理 NuGet 程式包安裝,搜索Swashbuckle.AspNetCore 二、配置 Swagger 將 Swagger 添加到 Startup.ConfigureServices 方法中的服務集合中: //註入S ...
  • 一、準備工作 通過程式包管理器控制台安裝AutoFac: Install-Package Autofac.Extensions.DependencyInjection 創建新類庫(.NetCore 2.2類庫),存放介面跟實現類,命名為NetCoreWebApi.Repository。 創建用戶倉儲 ...
  • 一、Linux目錄結構 二、Linux安裝JDK ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...