I2C 通訊協議(Inter-Integrated Circuit)引腳少,硬件實現簡單,可擴展性強,不需要 USART、CAN 等通訊協議的外部收發設備,現在被廣泛地使用在系統內多個集成電路(IC)間的通訊。
在計算機科學里,大部分復雜的問題都可以通過分層來簡化。如芯片被分為內核層和片上外設;STM32 標準庫則是在寄存器與用戶代碼之間的軟件層。對于通訊協議,我們也以分層的方式來理解,最基本的是把它分為物理層和協議層。
物理層規定通訊系統中具有機械、電子功能部分的特性,確保原始數據在物理媒體的傳輸。協議層主要規定通訊邏輯,統一收發雙方的數據打包、解包標準。簡單來說物理層規定我們用嘴巴還是用肢體來交流,
協議層則規定我們用中文還是英文來交流。
一、I2C物理層
它的物理層有如下特點:
(1) 它是一個支持設備的總線。“總線”指多個設備共用的信號線。在一個 I2C 通訊總線中,可連接多個 I2C 通訊設備,支持多個通訊主機及多個通訊從機。
(2) 一個 I2C 總線只使用兩條總線線路,一條雙向串行數據線(SDA) ,一條串行時鐘線(SCL)。數據線即用來表示數據,時鐘線用于數據收發同步。
(3) 每個連接到總線的設備都有一個獨立的地址,主機可以利用這個地址進行不同設備之間的訪問。
(4) 總線通過上拉電阻接到電源。當 I2C 設備空閑時,會輸出高阻態,而當所有設備都空閑,都輸出高阻態時,由上拉電阻把總線拉成高電平。
(5) 多個主機同時使用總線時,為了防止數據沖突,會利用仲裁方式決定由哪個設備占用總線。
(6) 具有三種傳輸模式:標準模式傳輸速率為 100kbit/s ,快速模式為 400kbit/s ,高速模式下可達 3.4Mbit/s,但目前大多 I2C 設備尚不支持高速模式。
(7) 連接到相同總線的 IC 數量受到總線的最大電容 400pF 限制 。
二、協議層
I2C 的協議定義了通訊的起始和停止信號、數據有效性、響應、仲裁、時鐘同步和地址廣播等環節。
1、基本讀寫過程
起始信號產生后,所有從機就開始等待主機緊接下來廣播的從機地址信號(SLAVE_ADDRESS)。在 I2C 總線上,每個設備的地址都是唯一的,當主機廣播的地址與某個設備地址相同時,這個設備就被選中了,沒被選中的設備將會忽略之后的數據信號。
根據 I2C協議,這個從機地址可以是 7位或 10位。
在地址位之后,是傳輸方向的選擇位,該位為 0 時,表示后面的數據傳輸方向是由主機傳輸至從機,即主機向從機寫數據。該位為 1時,則相反。
從機接收到匹配的地址后,主機或從機會返回一個應答(ACK)或非應答(NACK)信號,只有接收到應答信號后,主機才能繼續發送或接收數據。
寫數據
若配置的方向傳輸位為“寫數據”方向,即第一幅圖,廣播完地址,接收到應答信號后,主機開始正式向從機傳輸數據(DATA),數據包的大小為 8 位,主機每發送完一個字節數據,都要等待從機的應答信號(ACK),重復,可以向從機傳輸 N 個數據,這個 N 沒有大小限制。當數據傳輸結束時,主機向從機發送一個停止傳輸信號(P),表示不再傳輸數據。
讀數據
若配置的方向傳輸位為“讀數據”方向,即第二幅圖,廣播完地址,接收到應答信號后,從機開始向主機返回數據(DATA),數據包大小也為 8 位,從機每發送完一個數據,都會等待主機的應答信號(ACK),重復這個過程,可以返回 N 個數據,這個 N 也沒有大小限制。當主機希望停止接收數據時,就向從機返回一個非應答信號(NACK),則從機自動停止數據傳輸。
讀和寫數據
除了基本的讀寫,I2C 通訊更常用的是復合格式,即第三幅圖,該傳輸過程有兩次起始信號(S)。一般在第一次傳輸中,主機通過 SLAVE_ADDRESS 尋找到從設備后,發送一段“數據”,這段數據通常用于表示從設備內部的寄存器或存儲器地址(注意區分它與 SLAVE_ADDRESS 的區別);在第二次的傳輸中,對該地址的內容進行讀或寫。也就是說,第一次通訊是告訴從機讀寫地址,第二次則是讀寫的實際內容。
以上通訊流程中包含的各個信號分解如下:
2、通訊的起始和停止信號
起始(S)和停止(P)信號是兩種特殊的狀態。
當 SCL 線是高電平時 SDA 線從高電平向低電平切換,這個情況表示通訊的起始。當 SCL 是高電平時 SDA
線由低電平向高電平切換,表示通訊的停止。起始和停止信號一般由主機產生
3、 數據有效性
I2C使用 SDA信號線來傳輸數據,使用 SCL信號線進行數據同步。SDA數據線在 SCL的每個時鐘周期傳輸一位數據。傳輸時,SCL為高電平的時候 SDA表示的數據有效,即此時的SDA為高電平時表示數據“1”,為低電平時表示數據“0”。當SCL為低電平時,SDA的數據無效,一般在這個時候 SDA進行電平切換,為下一次表示數據做好準備。
4、 地址及數據方向
I2C 總線上的每個設備都有自己的獨立地址,主機發起通訊時,通過 SDA 信號線發送設備地址(SLAVE_ADDRESS)來查找從機。I2C 協議規定設備地址可以是 7 或 10 位,實際中 7 位的地址應用比較廣泛。緊跟設備地址的一個數據位用來表示數據傳輸方向,它是數據方向位(R/W),第 8位或第 11位。數據方向位為“1”時表示主機由從機讀數據,該位為“0”時表示主機向從機寫數據。
5、 響應
I2C 的數據和地址傳輸都帶響應。響應包括“應答(ACK)”和“非應答(NACK)”兩種信號。作為數據接收端時,當設備(無論主從機)接收到 I2C傳輸的一個字節數據或地址后,若希望對方繼續發送數據,則需要向對方發送“應答(ACK)”信號,發送方會繼續發送下一個數據;若接收端希望結束數據傳輸,則向對方發送“非應答(NACK)”信號,發送方接收到該信號后會產生一個停止信號,結束信號傳輸。
三、STM32的I2C結構與特性
如果我們直接控制 STM32的兩個GPIO引腳,分別用作 SCL及 SDA,按照上述信號的時序要求,直接像控制 LED 燈那樣控制引腳的輸出(若是接收數據時則讀取 SDA 電平),就可以實現 I2C 通訊。同樣,假如我們按照 USART 的要求去控制引腳,也能實現 USART 通訊。所以只要遵守協議,就是標準的通訊,不管您如何實現它,不管是 ST生產的控制器還是 ATMEL生產的存儲器, 都能按通訊標準交互。由于直接控制 GPIO 引腳電平產生通訊時序時,需要由 CPU 控制每個時刻的引腳狀態,所以稱之為“軟件模擬協議”方式。
相對地,還有“硬件協議”方式,STM32 的 I2C 片上外設專門負責實現 I2C 通訊協議,只要配置好該外設,它就會自動根據協議要求產生通訊信號,收發數據并緩存起來,CPU只要檢測該外設的狀態和訪問數據寄存器,就能完成數據收發。這種由硬件外設處理 I2C協議的方式減輕了 CPU 的工作。
1、外設簡介
STM32 的 I2C 外設可用作通訊的主機及從機,支持 100Kbit/s 和 400Kbit/s 的速率,支持 7 位、10 位設備地址,支持 DMA 數據傳輸,并具有數據校驗功能。它的 I2C 外設還支持 SMBus2.0 協議,SMBus 協議與 I2C 類似,主要應用于筆記本電腦的電池管理中。
2.框架解析*
1)通訊引腳
I2C的所有硬件架構都是根據圖中左側 SCL 線和 SDA 線展開的(其中的 SMBA 線用于SMBUS的警告信號,I2C通訊沒有使用)。
STM32芯片有多個 I2C外設,它們的 I2C通訊信號引出到不同的 GPIO 引腳上,使用時必須配置到這些指定的引腳。
I2C1 I2C2
SCL I2C1 :PB5 / PB8(重映射) I2C2: PB10
SDA I2C1:PB6 / PB9(重映射) I2C2:PB11
2)時鐘控制邏輯
SCL線的時鐘信號,由 I2C 接口根據時鐘控制寄存器(CCR)控制,控制的參數主要為時鐘頻率。配置 I2C的 CCR 寄存器可修改通訊速率相關的參數:
? 可選擇 I2C 通訊的“標準/快速”模式,這兩個模式分別 I2C 對應 100/400Kbit/s 的通訊速率。
? 在快速模式下可選擇 SCL 時鐘的占空比,可選 Tlow/Thigh=2 或 Tlow/Thigh=16/9模式,我們知道 I2C 協議在 SCL 高電平時對 SDA 信號采樣,SCL 低電平時 SDA準備下一個數據,修改 SCL 的高低電平比會影響數據采樣,但其實這兩個模式的比例差別并不大,若不是要求非常嚴格,隨便選就可以了。
? CCR 寄存器中還有一個 12 位的配置因子 CCR,它與 I2C 外設的輸入時鐘源共同作用,產生 SCL 時鐘,STM32 的I2C 外設都掛載在 APB1 總線上,使用 APB1 的時鐘源 PCLK1,SCL信號線的輸出時鐘公式如下:
例如,我們的 PCLK1=36MHz,想要配置 400Kbit/s 的速率,計算方式如下:
PCLK時鐘周期: TPCLK1 = 1/36000000
目標 SCL時鐘周期: TSCL = 1/400000
SCL時鐘周期內的高電平時間: THIGH = TSCL/3
SCL時鐘周期內的低電平時間: TLOW = 2*TSCL/3
計算 CCR的值: CCR = THIGH/TPCLK1 = 30
計算結果得出CCR為30,向該寄存器位寫入此值則可以控制IIC的通訊速率為400KHz,其實即使配置出來的 SCL 時鐘不完全等于標準的 400KHz,IIC 通訊的正確性也不會受到影響,因為所有數據通訊都是由 SCL協調的,只要它的時鐘頻率不遠高于標準即可。
3)數據控制邏輯
I2C 的 SDA 信號主要連到數據移位寄存器上,數據移位寄存器的數據來源及目標是數據寄存器(DR)、地址寄存器(OAR)、PEC 寄存器以及 SDA 數據線。當向外發送數據的時候,數據移位寄存器以“數據寄存器”為數據源,把數據一位一位地通過 SDA 信號線發送出去;當從外部接收數據的時候,數據移位寄存器把 SDA 信號線采樣到的數據一位位地存儲到“數據寄存器”中。若使能了數據校驗,接收到的數據會經過 PCE 計算器運算,運算結果存儲在“PEC 寄存器”中。當 STM32 的 I2C 工作在從機模式的時候,接收到設備地址信號時,數據移位寄存器會把接收到的地址與 STM32 的自身的“I2C 地址寄存器”的值作比較,以便響應主機的尋址。STM32 的自身 I2C 地址可通過修改“自身地址寄存器”修改,支持同時使用兩個 I2C設備地址,兩個地址分別存儲在 OAR1和 OAR2中。
4)整體控制邏輯
整體控制邏輯負責協調整個 I2C 外設,控制邏輯的工作模式根據我們配置的“控制寄存器(CR1/CR2)”的參數而改變。在外設工作時,控制邏輯會根據外設的工作狀態修改“狀態寄存器(SR1 和 SR2)”,我們只要讀取這些寄存器相關的寄存器位,就可以了解 I2C的工作狀態。除此之外,控制邏輯還根據要求,負責控制產生 I2C 中斷信號、DMA 請求及各種 I2C的通訊信號(起始、停止、響應信號等)。
四、通訊過程
使用 I2C 外設通訊時,在通訊的不同階段它會對“狀態寄存器(SR1及 SR2)”的不同數據位寫入參數,我們通過讀取這些寄存器標志來了解通訊狀態。
1、主發送器
主發送器發送流程及事件說明如下:
(1) 控制產生起始信號(S),當發生起始信號后,它產生事件“EV5”,并會對 SR1 寄存器的“SB”位置 1,表示起始信號已發送;
(2) 接著發送設備地址并等待應答信號,若有從機應答,則產生事件“EV6”及“EV8”,這時 SR1 寄存器的“ADDR”位及“TXE”位被置 1,ADDR 為 1表示地址已經發送,TXE 為 1表示數據寄存器為空;
(3) 以上步驟正常執行并對 ADDR 位清零后,我們往 I2C 的“數據寄存器 DR”寫入要發送的數據,這時TXE位會被重置0,表示數據寄存器非空,I2C外設通過SDA信號線一位位把數據發送出去后,又會產生“EV8”事件,即 TXE 位被置 1,重復這個過程,就可以發送多個字節數據了;
(4) 當我們發送數據完成后,控制 I2C 設備產生一個停止信號(P),這個時候會產生EV8_2 事件,SR1 的 TXE位及 BTF位都被置 1,表示通訊結束。
假如我們使能了 I2C 中斷,以上所有事件產生時,都會產生 I2C 中斷信號,進入同一個中斷服務函數,到 I2C中斷服務程序后,再通過檢查寄存器位來判斷是哪一個事件。
2、主接收器
主接收器接收流程及事件說明如下:
(1) 同主發送流程,起始信號(S)是由主機端產生的,控制發生起始信號后,它產生事件“EV5”,并會對 SR1寄存器的“SB”位置 1,表示起始信號已經發送;
(2) 緊接著發送設備地址并等待應答信號,若有從機應答,則產生事件“EV6”這時SR1 寄存的“ADDR”位被置 1,表示地址已經發送。
(3) 從機端接收到地址后,開始向主機端發送數據。當主機接收到這些數據后,會產生“EV7”事件,SR1寄存器的 RXNE被置 1,表示接收數據寄存器非空,我們讀取該寄存器后,可對數據寄存器清空,以便接收下一次數據。此時我們可以控制I2C 發送應答信號(ACK)或非應答信號(NACK),若應答,則重復以上步驟接收數據,若非應答,則停止傳輸;
(4) 發送非應答信號后,產生停止信號(P),結束傳輸。
在發送和接收過程中,有的事件不只是標志了我們上面提到的狀態位,還可能同時標志主機狀態之類的狀態位,而且讀了之后還需要清除標志位,比較復雜。我們可使用STM32標準庫函數來直接檢測這些事件的復合標志,降低編程難度。
五、I2C初始化結構體
初始化結構體及函數定義在庫文件“stm32f10x_i2c.h”及“stm32f10x_i2c.c”中。
I2C 初始化結構體
typedef struct {
uint32_t I2C_ClockSpeed; /*!< 設置 SCL 時鐘頻率,此值要低于 400000*/
uint16_t I2C_Mode; /*!< 指定工作模式,可選 I2C 模式及 SMBUS 模式 */
uint16_t I2C_DutyCycle; /*指定時鐘占空比,可選 low/high = 2:1 及 16:9 模式*/
uint16_t I2C_OwnAddress1; /*!< 指定自身的 I2C 設備地址 */
uint16_t I2C_Ack; /*!< 使能或關閉響應(一般都要使能) */
uint16_t I2C_AcknowledgedAddress; /*!< 指定地址的長度,可為 7 位及 10 位 */
} I2C_InitTypeDef;
(1) I2C_ClockSpeed
本成員設置的是 I2C 的傳輸速率,在調用初始化函數時,函數會根據我們輸入的數值經過運算后把時鐘因子寫入到 I2C 的時鐘控制寄存器 CCR。寫入的這個參數值不得高于 400KHz。實際上由于 CCR 寄存器不能寫入小數類型的時鐘因子,影響到 SCL 的實際頻率可能會低于本成員設置的參數值,這時除了通訊稍慢一點以外,不會對 I2C 的標準通訊造成其它影響。
(2) I2C_Mode
本成員是選擇I2C的使用方式,有I2C模式(I2C_Mode_I2C)和SMBus主、從模式(I2C_Mode_SMBusHost、 I2C_Mode_SMBusDevice ) 。I2C 不需要在此處區分主從模式,直接設置 I2C_Mode_I2C 即可。
(3) I2C_DutyCycle
本成員設置的是 I 2 C 的 SCL 線時鐘的占空比。該配置有兩個選擇,分別為低電平時間比高電平時間為 2:1 ( I2C_DutyCycle_2)和 16:9 (I2C_DutyCycle_16_9)。其實這兩個模式的比例差別并不大,一般要求都不會如此嚴格,這里隨便選就可以。
(4) I2C_OwnAddress1
本成員配置的是 STM32 的 I2C 設備自己的地址,每個連接到 I2C 總線上的設備都要有一 個 自 己 的 地 址 , 作 為 主 機 也 不 例 外 。 地 址 可 設 置 為 7 位 或 10 位 ( 受 下 面I2C_AcknowledgeAddress成員決定),只要該地址是 I2C 總線上唯一的即可。
STM32 的 I2C 外設可同時使用兩個地址,即同時對兩個地址作出響應,這個結構成員I2C_OwnAddress1配置的是默認的、OAR1寄存器存儲的地址,若需要設置第二個地址寄存器 OAR2,可使用I2C_OwnAddress2Config 函數來配置,OAR2 不支持 10 位地址,只有 7位。
(5) I2C_Ack_Enable
本成員是關于 I 2 C 應答設置,設置為使能則可以發送響應信號。本實驗配置為允許應答(I2C_Ack_Enable),這是絕大多數遵循 I 2 C 標準的設備的通訊要求,改為禁止應答(I2C_Ack_Disable)往往會導致通訊錯誤。
(6) I2C_AcknowledgeAddress
本成員選擇 I2C 的尋址模式是 7 位還是 10 位地址。這需要根據實際連接到 I2C 總線上設備的地址進行選擇,這個成員的配置也影響到 I2C_OwnAddress1 成員,只有這里設置成10 位模式時,I2C_OwnAddress1 才支持 10位地址。
配置完這些結構體成員值,調用庫函數 I2C_Init 即可把結構體的配置寫入到寄存器中。
六、讀寫EEPROM實驗
EEPROM 是一種掉電后數據不丟失的存儲器,常用來存儲一些配置信息,以便系統重新上電的時候加載之。EEPOM芯片最常用的通訊方式就是I 2 C協議,本小節以EEPROM的讀寫實驗為大家講解 STM32的 I 2 C使用方法。實驗中 STM32的 I2C外設采用主模式,分別用作主發送器和主接收器,通過查詢事件的方式來確保正常通訊。
(本實驗板中的 EEPROM 芯片(型號:AT24C02)的 SCL及 SDA 引腳連接到了 STM32 對應的I2C引腳中,結合上拉電阻,構成了I2C通訊總線,它們通過I2C總線交互。EEPROM芯片的設備地址一共有 7 位,其中高 4 位固定為:1010 b,低 3 位則由 A0/A1/A2 信號線的電平決定,圖中的 R/W是讀寫方向位,與地址無關。)
按照我們此處的連接,A0/A1/A2均為0,所以EEPROM的7位設備地址是:101 0000b ,即 0x50。由于 I2C 通訊時常常是地址跟讀寫方向連在一起構成一個 8 位數,且當 R/W 位為0 時,表示寫方向,所以加上 7 位地址,其值為“0xA0”,常稱該值為 I2C 設備的“寫地址”;當 R/W 位為 1 時,表示讀方向,加上 7 位地址,其值為“0xA1”,常稱該值為“讀地址”。
EEPROM 芯片中還有一個 WP 引腳,具有寫保護功能,當該引腳電平為高時,禁止寫入數據,當引腳為低電平時,可寫入數據,我們直接接地,不使用寫保護功能。
關于 EEPROM 的更多信息,可參考其數據手冊《AT24C02》來了解。若您使用的實驗板 EEPROM 的型號、設備地址或控制引腳不一樣,只需根據我們的工程修改即可,程序的控制原理相同。
1、編程要點
為了使工程更加有條理,我們把讀寫 EEPROM 相關的代碼獨立分開存儲,方便以后移植。在“工程模板”之上新建“bsp_i2c_ee.c”及“bsp_i2c_ee.h”文件,這些文件也可根據您的喜好命名,它們不屬于 STM32 標準庫的內容,是由我們自己根據應用需要編寫的。
(1) 配置通訊使用的目標引腳為開漏模式;
(2) 使能 I2C外設的時鐘;
(3) 配置 I2C外設的模式、地址、速率等參數并使能 I2C外設;
(4) 編寫基本 I2C按字節收發的函數;
(5) 編寫讀寫 EEPROM 存儲內容的函數;
(6) 編寫測試程序,對讀寫數據進行校驗。
2、I2C 硬件相關宏定義
我們把 I2C 硬件相關的配置都以宏的形式定義到 “bsp_i2c_ee.h”文件中。
1 /**************************I2C 參數定義,I2C1 或 I2C2*********************/
2 #define EEPROM_I2Cx I2C1
3 #define EEPROM_I2C_APBxClock_FUN RCC_APB1PeriphClockCmd
4 #define EEPROM_I2C_CLK RCC_APB1Periph_I2C1
5 #define EEPROM_I2C_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
6 #define EEPROM_I2C_GPIO_CLK RCC_APB2Periph_GPIOB
7 #define EEPROM_I2C_SCL_PORT GPIOB
8 #define EEPROM_I2C_SCL_PIN GPIO_Pin_6
9 #define EEPROM_I2C_SDA_PORT GPIOB
10 #define EEPROM_I2C_SDA_PIN GPIO_Pin_7
11
12 /* STM32 I2C 快速模式 */
13 #define I2C_Speed 400000 //*
14
15 /* 這個地址只要與 STM32 外掛的 I2C 器件地址不一樣即可 */
16 #define I2Cx_OWN_ADDRESS7 0X0A
17
18 /* AT24C01/02 每頁有 8 個字節 */
19 #define I2C_PageSize 8
以上代碼根據硬件連接,把與 EEPROM通訊使用的 I2C號 、引腳號都以宏封裝起來,
并且定義了自身的 I2C地址及通訊速率,以便配置模式的時候使用。
3、初始化 I2C 的 GPIO
利用上面的宏,編寫 I2C GPIO 引腳的初始化函數。
1 static void I2C_GPIO_Config(void)
2 {
3 GPIO_InitTypeDef GPIO_InitStructure;
4
5 /* 使能與 I2C 有關的時鐘 */
6 EEPROM_I2C_APBxClock_FUN ( EEPROM_I2C_CLK, ENABLE );
7 EEPROM_I2C_GPIO_APBxClock_FUN ( EEPROM_I2C_GPIO_CLK, ENABLE );
8
9 /* I2C_SCL、I2C_SDA*/
10 GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN;
11 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
12 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 開漏輸出
13 GPIO_Init(EEPROM_I2C_SCL_PORT, &GPIO_InitStructure);
14
15 GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SDA_PIN;
16 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
17 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 開漏輸出
18 GPIO_Init(EEPROM_I2C_SDA_PORT, &GPIO_InitStructure);
19 }
開啟相關的時鐘并初始化 GPIO引腳,函數執行流程如下:
(1) 使用GPIO_InitTypeDef定義 GPIO初始化結構體變量,以便下面用于存儲GPIO配置;
(2) 調用庫函數 RCC_APB1PeriphClockCmd(代碼中為宏 EEPROM_I2C_APBxClock_FUN)使 能 I2C 外 設 時 鐘 , 調 用 RCC_APB2PeriphClockCmd ( 代 碼 中 為 宏EEPROM_I2C_GPIO_APBxClock_FUN)來使能 I2C 引腳使用的 GPIO 端口時鐘,調用時我們使用“|”操作同時配置兩個引腳。
(3) 向GPIO初始化結構體賦值,把引腳初始化成復用開漏模式,要注意I2C的引腳必須使用這種模式。
(4) 使用以上初始化結構體的配置,調用 GPIO_Init 函數向寄存器寫入參數,完成 GPIO 的初始化。
4、配置 I2C 的模式
以上只是配置了 I2C 使用的引腳,還不算對 I2C模式的配置。
1 /**
2 * @brief I2C 工作模式配置
3 * @param 無
4 * @retval 無
5 */
6 static void I2C_Mode_Configu(void)
7 {
8 I2C_InitTypeDef I2C_InitStructure;
9
10 /* I2C 配置 */
11 I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
12
13 /* 高電平數據穩定,低電平數據變化 SCL 時鐘線的占空比 */
14 I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
15
16 I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7;
17 I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
18
19 /* I2C 的尋址模式 */
20 I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
21
22 /* 通信速率 */
23 I2C_InitStructure.I2C_ClockSpeed = I2C_Speed;
24
25 /* I2C 初始化 */
26 I2C_Init(EEPROM_I2Cx, &I2C_InitStructure);
27
28 /* 使能 I2C */
29 I2C_Cmd(EEPROM_I2Cx, ENABLE);
30 }
31
32
33 /**
34 * @brief I2C 外設(EEPROM)初始化
35 * @param 無
36 * @retval 無
37 */
38 void I2C_EE_Init(void)
39 {
40 I2C_GPIO_Config();
41
42 I2C_Mode_Configu();
43
44 /* 根據頭文件 i2c_ee.h 中的定義來選擇 EEPROM 要寫入的設備地址 */
45 /* 選擇 EEPROM Block0 來寫入 */
46 EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
47 }
熟悉 STM32 I2C 結構的話,這段初始化程序就十分好理解,它把 I2C 外設通訊時鐘SCL的低/高電平比設置為 2,使能響應功能,使用 7 位地址 I2C_OWN_ADDRESS7 以及速率配置為 I2C_Speed(前面在 bsp_i2c_ee.h 定義的宏)。最后調用庫函數 I2C_Init 把這些配置寫入寄存器,并調用 I2C_Cmd 函數使能外設。
為方便調用,我們把 I2C的 GPIO 及模式配置都用 I2C_EE_Init 函數封裝起來。
5、向 EEPROM 寫入一個字節的數據
初始化好 I2C 外設后,就可以使用 I2C 通訊,向 EEPROM 寫入一個字節
1
2 /***************************************************************/
3 /*通訊等待超時時間*/
4 #define I2CT_FLAG_TIMEOUT ((uint32_t)0x1000)
5 #define I2CT_LONG_TIMEOUT ((uint32_t)(10 * I2CT_FLAG_TIMEOUT))
6
7 /**
8 * @brief I2C 等待事件超時的情況下會調用這個函數來處理
9 * @param errorCode:錯誤代碼,可以用來定位是哪個環節出錯.
10 * @retval 返回 0,表示 IIC 讀取失敗.
11 */
12 static uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode)
13 {
14 /* 使用串口 printf 輸出錯誤信息,方便調試 */
15 EEPROM_ERROR("I2C 等待超時!errorCode = %d",errorCode);
16 return 0;
17 }
18 /**
19 * @brief 寫一個字節到 I2C EEPROM 中
20 * @param pBuffer:緩沖區指針
21 * @param WriteAddr:寫地址
22 * @retval 正常返回 1,異常返回 0
23 */
24 uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr)
25 {
26 /* 產生 I2C 起始信號 */
27 I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
28
29 /*設置超時等待時間*/
30 I2CTimeout = I2CT_FLAG_TIMEOUT;
31 /* 檢測 EV5 事件并清除標志*/
32 while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
33 {
34 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);
35 }
36
37 /* 發送 EEPROM 設備地址 */
38 I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS,
39 I2C_Direction_Transmitter);
40
41 I2CTimeout = I2CT_FLAG_TIMEOUT;
42 /* 檢測 EV6 事件并清除標志*/
43 while (!I2C_CheckEvent(EEPROM_I2Cx,
44 I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
45 {
46 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);
47 }
48
49 /* 發送要寫入的 EEPROM 內部地址(即 EEPROM 內部存儲器的地址) */
50 I2C_SendData(EEPROM_I2Cx, WriteAddr);
51
52 I2CTimeout = I2CT_FLAG_TIMEOUT;
53 /* 檢測 EV8 事件并清除標志*/
54 while (!I2C_CheckEvent(EEPROM_I2Cx,
55 I2C_EVENT_MASTER_BYTE_TRANSMITTED))
56 {
57 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);
58 }
59 /* 發送一字節要寫入的數據 */
60 I2C_SendData(EEPROM_I2Cx, *pBuffer);
61
62 I2CTimeout = I2CT_FLAG_TIMEOUT;
63 /* 檢測 EV8 事件并清除標志*/
64 while (!I2C_CheckEvent(EEPROM_I2Cx,
65 I2C_EVENT_MASTER_BYTE_TRANSMITTED))
66 {
67 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
68 }
69
70 /* 發送停止信號 */
71 I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
72
73 return 1;
74 }
先 來 分 析 I2C_TIMEOUT_UserCallback 函 數 , 它 的 函 數 體 里 只 調 用 了 宏EEPROM_ERROR,這個宏封裝了 printf函數,方便使用串口向上位機打印調試信息,閱讀代碼時把它當成 printf函數即可。在 I2C通訊的很多過程,都需要檢測事件,當檢測到某事件后才能繼續下一步的操作,但有時通訊錯誤或者 I2C 總線被占用,我們不能無休止地等待下去,所以我們設定每個事件檢測都有等待的時間上限,若超過這個時間,我們就調用I2C_TIMEOUT_UserCallback 函數輸出調試信息(或可以自己加其它操作),并終止 I2C 通訊。
了解了這個機制,再來分析 I2C_EE_ByteWrite 函數,這個函數實現了前面講的 I2C 主發送器通訊流程:
(1) 使用庫函數 I2C_GenerateSTART產生 I2C起始信號,其中的 EEPROM_I2C宏是前面硬件定義相關的 I2C編號;
(2) 對 I2CTimeout 變量賦值為宏 I2CT_FLAG_TIMEOUT,這個 I2CTimeout 變量在下面的 while 循環中每次循環減 1,該循環通過調用庫函數 I2C_CheckEvent 檢測事件,若檢測到事件,則進入通訊的下一階段,若未檢測到事件則停留在此處一直檢測,當檢測I2CT_FLAG_TIMEOUT次都還沒等待到事件則認為通訊失敗,調用前面的 I2C_TIMEOUT_UserCallback輸出調試信息,并退出通訊;
(3) 調用庫函數I2C_Send7bitAddress發送EEPROM的設備地址,并把數據傳輸方向設置為 I2C_Direction_Transmitter(即發送方向),這個數據傳輸方向就是通過設置 I2C通訊中緊跟地址后面的 R/W位實現的。發送地址后以同樣的方式檢測 EV6標志;
(4) 調用庫函數 I2C_SendData 向 EEPROM 發送要寫入的內部地址,該地址是I2C_EE_ByteWrite 函數的輸入參數,發送完畢后等待 EV8 事件。要注意這個內部地址跟上面的 EEPROM 地址不一樣,上面的是指 I2C 總線設備的獨立地址,而此處的內部地址是指 EEPROM 內數據組織的地址,也可理解為 EEPROM 內存的地址或 I2C設備的寄存器地址;
(5) 調 用 庫 函 數 I2C_SendData 向 EEPROM 發 送 要 寫 入 的 數 據 , 該 數 據 是I2C_EE_ByteWrite 函數的輸入參數,發送完畢后等待 EV8 事件;
(6) 一個 I2C通訊過程完畢,調用 I2C_GenerateSTOP 發送停止信號。
在這個通訊過程中,STM32實際上通過 I2C向 EEPROM發送了兩個數據,但為何第一個數據被解釋為 EEPROM 的內存地址?這是由 EEPROM 的自己定義的單字節寫入時序,
EEPROM 的單字節時序規定,向它寫入數據的時候,第一個字節為內存地址,第二個字節是要寫入的數據內容。所以我們需要理解:命令、地址的本質都是數據,對數據的解釋不同,它就有了不同的功能。
6、多字節寫入及狀態等待
單字節寫入通訊結束后,EEPROM 芯片會根據這個通訊結果擦寫該內存地址的內容,這需要一段時間,所以我們在多次寫入數據時,要先等待 EEPROM 內部擦寫完畢。
1 /**
2 * @brief 將緩沖區中的數據寫到 I2C EEPROM 中,采用單字節寫入的方式,速度比頁寫入慢
3
4 * @param pBuffer:緩沖區指針
5 * @param WriteAddr:寫地址
6 * @param NumByteToWrite:寫的字節數
7 * @retval 無
8 */
9 uint8_t I2C_EE_ByetsWrite(uint8_t* pBuffer,uint8_t WriteAddr,
10 uint16_t NumByteToWrite)
11 {
12 uint16_t i;
13 uint8_t res;
14
15 /*每寫一個字節調用一次 I2C_EE_ByteWrite 函數*/
16 for (i=0; i 17 { 18 /*等待 EEPROM 準備完畢*/ 19 I2C_EE_WaitEepromStandbyState(); 20 /*按字節寫入數據*/ 21 res = I2C_EE_ByteWrite(pBuffer++,WriteAddr++); 22 } 23 return res; 24 } 這段代碼比較簡單,直接使用 for 循環調用前面定義的 I2C_EE_ByteWrite 函數一個字節一個字節地向 EEPROM 發送要寫入的數據 。在每次數據寫入通訊前調用了I2C_EE_WaitEepromStandbyState 函數等待 EEPROM 內部擦寫完畢, 1 /** 2 * @brief 等待 EEPROM 到準備狀態 3 * @param 無 4 * @retval 無 5 */ 6 void I2C_EE_WaitEepromStandbyState(void) 7 { 8 vu16 SR1_Tmp = 0; 9 10 do { 11 /* 發送起始信號 */ 12 I2C_GenerateSTART(EEPROM_I2Cx, ENABLE); 13 14 /* 讀 I2C1 SR1 寄存器 */ 15 SR1_Tmp = I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1); 16 17 /* 發送 EEPROM 地址 + 寫方向 */ 18 I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, 19 I2C_Direction_Transmitter); 20 } 21 // SR1 位 1 ADDR:1 表示地址發送成功,0 表示地址發送沒有結束 22 // 等待地址發送成功 23 while (!(I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1) & 0x0002)); 24 25 /* 清除 AF 位 */ 26 I2C_ClearFlag(EEPROM_I2Cx, I2C_FLAG_AF); 27 /* 發送停止信號 */ 28 I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE); 29 } 這個函數主要實現是向 EEPROM 發送它設備地址,檢測 EEPROM 的響應,若EEPROM 接收到地址后返回應答信號,則表示 EEPROM 已經準備好,可以開始下一次通訊。函數中檢測響應是通過讀取 STM32 的 SR1 寄存器的 ADDR 位及 AF 位來實現的,當I2C 設備響應了地址的時候,ADDR會置 1,若應答失敗,AF位會置 1。 7.頁寫入 在以上的數據通訊中,每寫入一個數據都需要向 EEPROM 發送寫入的地址,我們希望向連續地址寫入多個數據的時候,只要告訴 EEPROM 第一個內存地址 address1,后面的數 據按次序寫入到address2、address3… 這樣可以節省通訊的時間,加快速度。為應對這種需 求,EEPROM 定義了一種頁寫入時序 根據頁寫入時序,第一個數據被解釋為要寫入的內存地址 address1,后續可連續發送 n個數據,這些數據會依次寫入到內存中。其中 AT24C02 型號的芯片頁寫入時序最多可以一次發送 8個數據(即 n = 8 ),該值也稱為頁大小,某些型號的芯片每個頁寫入時序最多可傳輸 16 個數據。 1 2 /** 3 * @brief 在 EEPROM 的一個寫循環中可以寫多個字節,但一次寫入的字節數 4 * 不能超過 EEPROM 頁的大小,AT24C02 每頁有 8 個字節 5 * @param 6 * @param pBuffer:緩沖區指針 7 * @param WriteAddr:寫地址 8 * @param NumByteToWrite:要寫的字節數要求 NumByToWrite 小于頁大小 9 * @retval 正常返回 1,異常返回 0 10 */ 11 uint8_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, 12 uint8_t NumByteToWrite) 13 { 14 I2CTimeout = I2CT_LONG_TIMEOUT; 15 16 while (I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY)) 17 { 18 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4); 19 } 20 21 /* 產生 I2C 起始信號 */ 22 I2C_GenerateSTART(EEPROM_I2Cx, ENABLE); 23 24 I2CTimeout = I2CT_FLAG_TIMEOUT; 25 26 /* 檢測 EV5 事件并清除標志 */ 27 while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) 28 { 29 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5); 30 } 31 32 /* 發送 EEPROM 設備地址 */ 33 I2C_Send7bitAddress(EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter); 34 35 I2CTimeout = I2CT_FLAG_TIMEOUT; 36 37 /* 檢測 EV6 事件并清除標志*/ 38 while (!I2C_CheckEvent(EEPROM_I2Cx, 39 I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) 40 { 41 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6); 42 } 43 /* 發送要寫入的 EEPROM 內部地址(即 EEPROM 內部存儲器的地址) */ 44 I2C_SendData(EEPROM_I2Cx, WriteAddr); 45 46 I2CTimeout = I2CT_FLAG_TIMEOUT; 47 48 /* 檢測 EV8 事件并清除標志*/ 49 while (! I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) 50 { 51 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7); 52 } 53 /* 循環發送 NumByteToWrite 個數據 */ 54 while (NumByteToWrite--) 55 { 56 /* 發送緩沖區中的數據 */ 57 I2C_SendData(EEPROM_I2Cx, *pBuffer); 58 59 /* 指向緩沖區中的下一個數據 */ 60 pBuffer++; 61 62 I2CTimeout = I2CT_FLAG_TIMEOUT; 63 64 /* 檢測 EV8 事件并清除標志*/ 65 while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) 66 { 67 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(8); 68 } 69 } 70 /* 發送停止信號 */ 71 I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE); 72 return 1; 73 } 這段頁寫入函數主體跟單字節寫入函數是一樣的,只是它在發送數據的時候,使用 for循環控制發送多個數據,發送完多個數據后才產生 I2C 停止信號,只要每次傳輸的數據小于等于 EEPROM時序規定的頁大小,就能正常傳輸。 8、快速寫入多字節 利用 EEPROM 的頁寫入方式,可以改進前面的“多字節寫入”函數,加快傳輸速度 1 // AT24C01/02 每頁有 8 個字節 2 #define I2C_PageSize 8 3 4 /** 5 * @brief 將緩沖區中的數據寫到 I2C EEPROM 中 6 * @param 7 * @arg pBuffer:緩沖區指針 8 * @arg WriteAddr:寫地址 9 * @arg NumByteToWrite:寫的字節數 10 * @retval 無 11 */ 12 void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, 13 u16 NumByteToWrite) 14 { 15 u8 NumOfPage=0,NumOfSingle=0,Addr =0,count=0,temp =0; 16 17 /*mod 運算求余,若 writeAddr 是 I2C_PageSize 整數倍, 18 運算結果 Addr 值為 0*/ 19 Addr = WriteAddr % I2C_PageSize; 20 21 /*差 count 個數據值,剛好可以對齊到頁地址*/ 22 count = I2C_PageSize - Addr; 23 24 /*計算出要寫多少整數頁*/ 25 NumOfPage = NumByteToWrite / I2C_PageSize; 26 27 /*mod 運算求余,計算出剩余不滿一頁的字節數*/ 28 NumOfSingle = NumByteToWrite % I2C_PageSize; 29 30 // Addr=0,則 WriteAddr 剛好按頁對齊 aligned 31 // 這樣就很簡單了,直接寫就可以,寫完整頁后 32 // 把剩下的不滿一頁的寫完即可 33 if (Addr == 0) { 34 /* 如果 NumByteToWrite < I2C_PageSize */ 35 if (NumOfPage == 0) { 36 I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); 37 I2C_EE_WaitEepromStandbyState(); 38 } 39 /* 如果 NumByteToWrite > I2C_PageSize */ 40 else { 41 /*先把整數頁都寫了*/ 42 while (NumOfPage--) { 43 I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize); 44 I2C_EE_WaitEepromStandbyState(); 45 WriteAddr += I2C_PageSize; 46 pBuffer += I2C_PageSize; 47 } 48 /*若有多余的不滿一頁的數據,把它寫完*/ 49 if (NumOfSingle!=0) { 50 I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); 51 I2C_EE_WaitEepromStandbyState(); 52 } 53 } 54 } 55 // 如果 WriteAddr 不是按 I2C_PageSize 對齊 56 // 那就算出對齊到頁地址還需要多少個數據,然后 57 // 先把這幾個數據寫完,剩下開始的地址就已經對齊 58 // 到頁地址了,代碼重復上面的即可 59 else { 60 /* 如果 NumByteToWrite < I2C_PageSize */ 61 if (NumOfPage== 0) { 62 /*若 NumOfSingle>count,當前面寫不完,要寫到下一頁*/ 63 if (NumOfSingle > count) { 64 // temp 的數據要寫到寫一頁 65 temp = NumOfSingle - count; 66 67 I2C_EE_PageWrite(pBuffer, WriteAddr, count); 68 I2C_EE_WaitEepromStandbyState(); 69 WriteAddr += count; 70 pBuffer += count; 71 72 I2C_EE_PageWrite(pBuffer, WriteAddr, temp); 73 I2C_EE_WaitEepromStandbyState(); 74 } else { /*若 count 比 NumOfSingle 大*/ 75 I2C_EE_PageWrite(pBuffer, WriteAddr, NumByteToWrite); 76 I2C_EE_WaitEepromStandbyState(); 77 } 78 } 79 /* 如果 NumByteToWrite > I2C_PageSize */ 80 else { 81 /*地址不對齊多出的 count 分開處理,不加入這個運算*/ 82 NumByteToWrite -= count; 83 NumOfPage = NumByteToWrite / I2C_PageSize; 84 NumOfSingle = NumByteToWrite % I2C_PageSize; 85 86 /*先把 WriteAddr 所在頁的剩余字節寫了*/ 87 if (count != 0) { 88 I2C_EE_PageWrite(pBuffer, WriteAddr, count); 89 I2C_EE_WaitEepromStandbyState(); 90 91 /*WriteAddr 加上 count 后,地址就對齊到頁了*/ 92 WriteAddr += count; 93 pBuffer += count; 94 } 95 /*把整數頁都寫了*/ 96 while (NumOfPage--) { 97 I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize); 98 I2C_EE_WaitEepromStandbyState(); 99 WriteAddr += I2C_PageSize; 100 pBuffer += I2C_PageSize; 101 } 102 /*若有多余的不滿一頁的數據,把它寫完*/ 103 if (NumOfSingle != 0) { 104 I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); 105 I2C_EE_WaitEepromStandbyState(); 106 } 107 } 108 } 109 } 它的主旨就是對輸入的數據進行分頁(本型號芯片每頁 8 個字節),通過“整除”計算要寫入的數據NumByteToWrite 能寫滿多少“完整的頁”,計算得的值存儲在 NumOfPage 中,但有時數據不是剛好能寫滿完整頁的,會多一點出來,通過“求余”計算得出“不滿一頁的數據個數”就存儲在 NumOfSingle 中。計算后通過按頁傳輸 NumOfPage 次整頁數據及最后的NumOfSing 個數據,使用頁傳輸,比之前的單個字節數據傳輸要快很多。 除了基本的分頁傳輸,還要考慮首地址的問題。若首地址不是剛好對齊到頁的首地址,會需要一個count值,用于存儲從該首地址開始寫滿該地址所在的頁,還能寫多少個數據。實際傳輸時,先把這部分count個數據先寫入,填滿該頁,然后把剩余的數據(NumByteToWrite-count),再重復上述求出 NumOPage及 NumOfSingle的過程,按頁傳輸到EEPROM。 最后,強調一下,EEPROM 支持的頁寫入只是一種加速的 I2C 的傳輸時序,實際上并不要求每次都以頁為單位進行讀寫,EEPROM 是支持隨機訪問的(直接讀寫任意一個地址),如前面的單個字節寫入。在某些存儲器,如 NAND FLASH,它是必須按照 Block 寫入的,例如每個 Block 為 512 或 4096 字節,數據寫入的最小單位是 Block,寫入前都需要擦除整個 Block;NOR FLASH 則是寫入前必須以 Sector/Block 為單位擦除,然后才可以按字節寫入。而我們的 EEPROM 數據寫入和擦除的最小單位是“字節”而不是“頁”,數據寫入前不需要擦除整頁。 9、從EEPROM讀取數據 從 EEPROM 讀取數據是一個 復合的 I2C 時序 ,它實際上包含一個寫過程和一個讀過程。 讀時序的第一個通訊過程中,使用 I2C發送設備地址尋址(寫方向),接著發送要讀取的“內存地址”;第二個通訊過程中,再次使用 I2C 發送設備地址尋址,但這個時候的數據方向是讀方向;在這個過程之后,EEPROM 會向主機返回從“內存地址”開始的數據,一個字節一個字節地傳輸,只要主機的響應為“應答信號”,它就會一直傳輸下去,主機想結束傳輸時,就發送“非應答信號”,并以“停止信號”結束通訊,作為從機的 EEPROM也會停止傳輸。 1 2 /** 3 * @brief 從 EEPROM 里面讀取一塊數據 4 * @param pBuffer:存放從 EEPROM 讀取的數據的緩沖區指針 5 * @param ReadAddr:接收數據的 EEPROM 的地址 6 * @param NumByteToRead:要從 EEPROM 讀取的字節數 7 * @retval 正常返回 1,異常返回 0 8 */ 9 uint8_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, 10 u16 NumByteToRead) 11 { 12 I2CTimeout = I2CT_LONG_TIMEOUT; 13 14 while (I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY)) 15 { 16 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9); 17 } 18 19 /* 產生 I2C 起始信號 */ 20 I2C_GenerateSTART(EEPROM_I2Cx, ENABLE); 21 22 I2CTimeout = I2CT_FLAG_TIMEOUT; 23 24 /* 檢測 EV5 事件并清除標志*/ 25 while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) 26 { 27 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10); 28 } 29 30 /* 發送 EEPROM 設備地址 */ 31 I2C_Send7bitAddress(EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter); 32 33 I2CTimeout = I2CT_FLAG_TIMEOUT; 34 35 /* 檢測 EV6 事件并清除標志*/ 36 while (!I2C_CheckEvent(EEPROM_I2Cx, 37 I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) 38 { 39 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11); 40 } 41 /*通過重新設置 PE 位清除 EV6 事件 */ 42 I2C_Cmd(EEPROM_I2Cx, ENABLE); 43 44 /* 發送要讀取的 EEPROM 內部地址(即 EEPROM 內部存儲器的地址) */ 45 I2C_SendData(EEPROM_I2Cx, ReadAddr); 46 47 I2CTimeout = I2CT_FLAG_TIMEOUT; 48 49 /* 檢測 EV8 事件并清除標志*/ 50 while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED)) 51 { 52 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(12); 53 } 54 /* 產生第二次 I2C 起始信號 */ 55 I2C_GenerateSTART(EEPROM_I2Cx, ENABLE); 56 57 I2CTimeout = I2CT_FLAG_TIMEOUT; 58 59 /* 檢測 EV5 事件并清除標志*/ 60 while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) 61 { 62 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13); 63 } 64 /* 發送 EEPROM 設備地址 */ 65 I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Receiver); 66 67 I2CTimeout = I2CT_FLAG_TIMEOUT; 68 69 /* 檢測 EV6 事件并清除標志*/ 70 while (!I2C_CheckEvent(EEPROM_I2Cx, 71 I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) 72 { 73 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(14); 74 } 75 /* 讀取 NumByteToRead 個數據*/ 76 while (NumByteToRead) 77 { 78 /*若 NumByteToRead=1,表示已經接收到最后一個數據了, 79 發送非應答信號,結束傳輸*/ 80 if (NumByteToRead == 1) 81 { 82 /* 發送非應答信號 */ 83 I2C_AcknowledgeConfig(EEPROM_I2Cx, DISABLE); 84 85 /* 發送停止信號 */ 86 I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE); 87 } 88 89 I2CTimeout = I2CT_LONG_TIMEOUT; 90 while (I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0) 91 { 92 if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3); 93 } 94 { 95 /*通過 I2C,從設備中讀取一個字節的數據 */ 96 *pBuffer = I2C_ReceiveData(EEPROM_I2Cx); 97 98 /* 存儲數據的指針指向下一個地址 */ 99 pBuffer++; 100 101 /* 接收數據自減 */ 102 NumByteToRead--; 103 } 104 } 105 106 /* 使能應答,方便下一次 I2C 傳輸 */ 107 I2C_AcknowledgeConfig(EEPROM_I2Cx, ENABLE); 108 return 1; 109 } 這段中的寫過程跟前面的寫字節函數類似,而讀過程中接收數據時,需要使用庫函數I2C_ReceiveData 來讀取。響應信號則通過庫函數 I2C_AcknowledgeConfig 來發送,DISABLE 時為非響應信號,ENABLE 為響應信號。 10、EEPROM讀寫測試函數 1 /** 2 * @brief I2C(AT24C02)讀寫測試 3 * @param 無 4 * @retval 正常返回 1 ,不正常返回 0 5 */ 6 uint8_t I2C_Test(void) 7 { 8 u16 i; 9 EEPROM_INFO("寫入的數據"); 10 11 for ( i=0; i<=255; i++ ) //填充緩沖 12 { 13 I2c_Buf_Write[i] = i; 14 15 printf("0x%02X ", I2c_Buf_Write[i]); 16 if (i%16 == 15) 17 printf("\n\r"); 18 } 19 20 //將 I2c_Buf_Write 中順序遞增的數據寫入 EERPOM 中 21 //頁寫入方式 22 // I2C_EE_BufferWrite( I2c_Buf_Write, EEP_Firstpage, 256); 23 //字節寫入方式 24 I2C_EE_ByetsWrite( I2c_Buf_Write, EEP_Firstpage, 256); 25 26 EEPROM_INFO("寫結束"); 27 28 EEPROM_INFO("讀出的數據"); 29 //將 EEPROM 讀出數據順序保持到 I2c_Buf_Read 中 30 I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256); 31 32 //將 I2c_Buf_Read 中的數據通過串口打印 33 for (i=0; i<256; i++) 34 { 35 if (I2c_Buf_Read[i] != I2c_Buf_Write[i]) 36 { 37 printf("0x%02X ", I2c_Buf_Read[i]); 38 EEPROM_ERROR("錯誤:I2C EEPROM 寫入與讀出的數據不一致"); 39 return 0; 40 } 41 printf("0x%02X ", I2c_Buf_Read[i]); 42 if (i%16 == 15) 43 printf("\n\r"); 44 45 } 46 EEPROM_INFO("I2C(AT24C02)讀寫測試成功"); 47 return 1; 48 } 代碼中先填充一個數組,數組的內容為 1,2,3 至 N,接著把這個數組的內容寫入到EEPROM 中,寫入時可以采用單字節寫入的方式或頁寫入的方式。寫入完畢后再從EEPROM 的地址中讀取數據,把讀取得到的與寫入的數據進行校驗,若一致說明讀寫正常,否則讀寫過程有問題或者 EEPROM 芯片不正常。其中代碼用到的 EEPROM_INFO 跟EEPROM_ERROR 宏類似,都是對 printf 函數的封裝,使用和閱讀代碼時把它直接當成 printf 函數就好。具體的宏定義在“bsp_i2c_ee.h 文件中”,在以后的代碼我們常常會用類似的宏來輸出調試信息。 11、main函數 編寫 main 函數,函數中初始化串口、I2C 外設,然后調用上面的 I2C_Test 函數進行讀寫測試, 1 2 /** 3 * @brief 主函數 4 * @param 無 5 * @retval 無 6 */ 7 int main(void) 8 { 9 LED_GPIO_Config(); 10 11 LED_BLUE; 12 /*初始化 USART1*/ 13 Debug_USART_Config(); 14 15 printf("\r\n 歡迎使用 STM32 F103型號\r\n"); 16 17 printf("\r\n 這是一個 I2C 外設(AT24C02)讀寫測試例程 \r\n"); 18 19 /* I2C 外設(AT24C02)初始化 */ 20 I2C_EE_Init(); 21 22 if (I2C_Test() ==1) 23 { 24 LED_GREEN; 25 } 26 else 27 { 28 LED_RED; 29 } 30 31 while (1) 32 { 33 } 34 35 } 36
上一篇:STM32系統學習——SPI(讀寫串行 FLASH)
下一篇:STM32系統學習——DMA(直接儲存器訪問)
推薦閱讀
史海拾趣
隨著業務的不斷拓展,AAC公司意識到美國市場的巨大潛力。于是,在1996年,AAC決定在美國設立分公司,專門負責美國市場的銷售。與此同時,為了滿足不斷增長的市場需求,AAC還在深圳設立了美歐電子有限公司,專門批量生產手機用訊響器。這一系列的舉措使得AAC的產品線更加豐富,產能也得到了大幅提升。
隨著業務的不斷發展和規模的持續擴大,AAC公司在2005年成功在香港交易所上市。這一里程碑式的事件為AAC帶來了更多的資本支持和市場認可。此后,AAC加快了全球擴張的步伐,不僅在歐洲、北美等地設立了分支機構,還與眾多國際知名品牌建立了長期穩定的合作關系。
Amphenol Nexus Technologies深知,在電子行業中,合作與共贏是實現持續發展的重要途徑。因此,公司積極與產業鏈上下游的合作伙伴建立緊密的合作關系,共同推動行業的發展。通過與供應商、客戶以及同行業企業的合作,Amphenol Nexus Technologies不斷提升自身的競爭力,實現了業務的快速增長和市場的持續擴張。
以上便是關于Amphenol Nexus公司在電子行業發展的五個故事。這些故事展示了公司在不同歷史階段的發展軌跡、領導層的智慧和決策、技術創新的努力以及合作共贏的理念。正是這些因素的共同作用,使得Amphenol Nexus Technologies能夠在激烈的市場競爭中脫穎而出,成為電子連接器行業的佼佼者。
2008年,Amphenol公司看中了Nexus, Inc.在連接器領域的潛力,決定對其進行收購。收購完成后,Amphenol成立了提供全球銷售支持的Amphenol Nexus Technologies,同時仍保持了對客戶支持和開發的堅定承諾。這一舉措使Amphenol Nexus Technologies得以借助Amphenol的全球性的資源和網絡,進一步拓展其業務范圍和市場影響力。
1998年,Amphenol Nexus Technologies經歷了一次重要的領導層更迭。拜爾斯先生退休,弗雷德·法拉哈尼(Fred Farahani)收購了Nexus, Inc.的股份,并與弗拉納根先生成為合伙人。在Fred的領導下,公司開始關注銷售和開發方面的新機遇,產品基礎也擴展到包括微型連接器系列。這一變革為公司帶來了新的增長點,使其在激烈的市場競爭中保持了領先地位。
在競爭激烈的電子行業中,DBM Optix深知只有不斷創新才能保持領先地位。因此,公司始終將研發作為核心競爭力之一,持續投入大量資金用于新技術、新產品的開發。通過與高校、研究機構等合作,DBM Optix不斷引入新技術、新材料,并將其應用于產品中,從而不斷提升產品的性能和品質。這些創新舉措使得DBM Optix在光學通信領域始終保持領先地位。
求助:大家好,我是個新新手,想請教SIM300C無線數傳的問題。 大家好! 我剛剛接觸SIM300C,單片機通過串口連接SIM300C,要實現數據傳輸應該怎樣設置SIM300C?哪位專家能否給我一個設置流程,萬分感謝!… 查看全部問答∨ |
一直在作音視頻驅動,總是在和硬件打交道。感覺還是硬件那邊穩定,不用再學好多亂七八糟得編程語言。 python,jsp,vbscript,C#,。。。 硬件那邊只要把電路搞得很通就行了。爽啊。… 查看全部問答∨ |
昨天才出的,英文網站已經更新,中文網站還要等幾天http://focus.ti.com/docs/toolsw/folders/print/dk-lm3s9d96.html?HQS=mcu_firestorm_110724&DCMP=mytinwsltr_07_25_2011&sp_rid_pod3=LTc5NzAyMjkzOAS2&sp_mid_pod3=495039… 查看全部問答∨ |
|
atlium 作出來的lm3s下載器,漂亮! 不多說,上傳圖片! [ 本帖最后由 paulhyde 于 2012-5-17 10:20 編輯 ]… 查看全部問答∨ |
從公司原來的深圳提供芯片那里詢了下價,比ti官方價格*匯率要高不少啊。。不知道官方代理會怎么樣。。 從ti官網看的k片參考價格。正式去代理商那里下單 k片購買有問題嗎?會要加錢嗎?沒貨怎么辦? 請教下關于ti官方代理商那里購買芯片要注意什么 ...… 查看全部問答∨ |