開發(fā)環(huán)境:
MDK:Keil 5.30
開發(fā)板:GD32F207I-EVAL
MCU:GD32F207IK
1 I2C工作原理
1.1 I2C串行總線概述
I2C總線是PHLIPS公司推出的一種雙線式半雙工串行總線,是具備多主機系統(tǒng)所需的總線裁決和高低速器件同步功能的高性能串行總線。用于連接微控器及外圍設(shè)備。I2C總線只有兩根雙向信號線。一根是數(shù)據(jù)線SDA,另一根是時鐘線SCL。
- 物理層
1)它只使用兩條總線線路 :一條雙向串行數(shù)據(jù)線(SDA),一條串行時鐘線(SCL)。見圖 1。
2)每個連接到總線的設(shè)備都有一個獨立的地址,主機可以利用這個地址進行不同設(shè)備之間的訪問。
3)多主機同時使用總線時,為了防止數(shù)據(jù)沖突,會利用仲裁方式?jīng)Q定由哪個設(shè)備占用總線。
4)具有三種傳輸模式 :標準模式的傳輸速率為 100 Kbit/s ,快速模式為 400 Kbit/s ,高速模式下可達 3.4 Mbit/s,但目前大多12C設(shè)備尚不支持高速模式。
5)片上的濾波器可以濾去總線數(shù)據(jù)線上的毛刺波以保證數(shù)據(jù)完整。
6)連接到相同總線的 IC 數(shù)量受到總線的最大電容 400 pF 限制
I2C總線通過上拉電阻接正電源。當總線空閑時,兩根線均為高電平。連到總線上的任一器件輸出的低電平,都將使總線的信號變低,即各器件的SDA及SCL都是線“與”關(guān)系。
每個接到I2C總線上的器件都有唯一的地址。主機與其它器件間的數(shù)據(jù)傳送可以是由主機發(fā)送數(shù)據(jù)到其它器件,這時主機即為發(fā)送器。由總線上接收數(shù)據(jù)的器件則為接收器。
在多主機系統(tǒng)中,可能同時有幾個主機企圖啟動總線傳送數(shù)據(jù)。為了避免混亂, I2C總線要通過總線仲裁,以決定由哪一臺主機控制總線。
- 協(xié)議層
I2C的協(xié)議包括起始和停止條件、數(shù)據(jù)有效性、響應(yīng)、仲裁、時鐘同步和地址廣播等環(huán)節(jié),由于我們使用的是 STM32 集成的硬件I2C接口,并不需要用軟件去模擬 SDA 和SCL 線的時序。
這兩幅圖表示的是主機和從機通信時 SDA 線的數(shù)據(jù)包序列。
其中 S 表示由主機的I2C接口產(chǎn)生的傳輸起始信號(S),這時連接到I2C總線上的所有從機都會接收到這個信號。
起始信號產(chǎn)生后,所有從機就開始等待主機緊接下來廣播的從機地址信號(SLAVE_ADDRESS),在I2C總線上,每個設(shè)備的地址都是唯一的。當主機廣播的地址與某個設(shè)備地址相同時,這個設(shè)備就被選中了,沒被選中的設(shè)備將會忽略之后的數(shù)據(jù)信號。根據(jù)I2C協(xié)議,這個從機地址可以是 7 位或 10 位。
在地址位之后,是傳輸方向的選擇位,該位為 0 時,表示后面的數(shù)據(jù)傳輸方向是由主機傳輸至從機。該位為 1 時,則相反。
從機接收到匹配的地址后,主機或從機會返回一個應(yīng)答(A)或非應(yīng)答信號,只有接收到應(yīng)答信號后,主機才能繼續(xù)發(fā)送或接收數(shù)據(jù)。
若配置的方向傳輸位為寫數(shù)據(jù),廣播完地址,接收到應(yīng)答信號后,主機開始正式向從機傳輸數(shù)據(jù)(DATA),數(shù)據(jù)包的大小為 8 位。主機每發(fā)送完一個數(shù)據(jù),都要等待從機的應(yīng)答信號(A),重復(fù)這個過程,可以向從機傳輸 N 個數(shù)據(jù),這個 N 沒有大小限制。當數(shù)據(jù)傳輸結(jié)束時,主機向從機發(fā)送一個停止傳輸信號(P),表示不再傳輸數(shù)據(jù)。
若配置的方向傳輸位為讀數(shù)據(jù),廣播完地址,接收到應(yīng)答信號后,從機開始向主機返回數(shù)據(jù)(DATA),數(shù)據(jù)包大小也為 8 位。從機每發(fā)送完一個數(shù)據(jù),都會等待主機的應(yīng)答信號(A),重復(fù)這個過程,可以返回 N 個數(shù)據(jù),這個 N 也沒有大小限制。當主機希望停止接收數(shù)據(jù)時,就向從機返回一個非應(yīng)答信號,則從機自動停止數(shù)據(jù)傳輸。
- I2C接口特性
1)STM32 的中等容量和大容量型號的芯片均有多達兩個的I2C總線接口。
2)能夠工作于多主模式或從模式,分別為主接收器、主發(fā)送器、從接收器及從發(fā)送器。
3)支持標準模式 100 Kbit/s 和快速模式 400 Kbit/s,不支持高速模式。
4)支持 7 位或 10 位尋址。
5)內(nèi)置了硬件 CRC 發(fā)生器 / 校驗器。
6)I2 C 的接收和發(fā)送都可以使用 DMA 操作。
7)支持系統(tǒng)管理總線(SMBus)2.0 版。
- I 2 C 架構(gòu)
I2C的所有硬件架構(gòu)就是根據(jù) SCL 線和 SDA 線展開的(其中 SMBALERT 線用于 SMBus)。SCL 線的時序即為I2C 協(xié)議中的時鐘信號,它由I2C 接口根據(jù)時鐘控制寄存器(CCR)控制,控制的參數(shù)主要為時鐘頻率。而 SDA 線的信號則通過一系列數(shù)據(jù)控制架構(gòu),在將要發(fā)送的數(shù)據(jù)的基礎(chǔ)上,根據(jù)協(xié)議添加各種起始信號、應(yīng)答信號、地址信號,實現(xiàn)以 I 2 C 協(xié)議的方式發(fā)送出去。讀取數(shù)據(jù)時則從 SDA 線上的信號中取出接收到的數(shù)據(jù)值。發(fā)送和接收的數(shù)據(jù)都被保存在數(shù)據(jù)寄存器(DR)上。
1.2 I2C總線的數(shù)據(jù)傳送
- 數(shù)據(jù)位的有效性規(guī)定
I2C總線進行數(shù)據(jù)傳送時,時鐘信號為高電平期間,數(shù)據(jù)線上的數(shù)據(jù)必須保持穩(wěn)定,只有在時鐘線上的信號為低電平期間,數(shù)據(jù)線上的高電平或低電平狀態(tài)才允許變化。
- 起始和終止信號
SCL線為高電平期間, SDA線由高電平向低電平的變化表示起始信號 ;SCL線為高電平期間, SDA線由低電平向高電平的變化表示終止信號 。
起始和終止信號都是由主機發(fā)出的 ,在起始信號產(chǎn)生后,總線就處于被占用的狀態(tài);在終止信號產(chǎn)生后,總線就處于空閑狀態(tài)。連接到I2C總線上的器件,若具有I2C總線的硬件接口,則很容易檢測到起始和終止信號。每當發(fā)送器件傳輸完一個字節(jié)的數(shù)據(jù)后,后面必須緊跟一個校驗位,這個校驗位是接收端通過控制SDA(數(shù)據(jù)線)來實現(xiàn)的,以提醒發(fā)送端數(shù)據(jù)我這邊已經(jīng)接收完成,數(shù)據(jù)傳送可以繼續(xù)進行。
- 數(shù)據(jù)傳送格式
- 字節(jié)傳送與應(yīng)答
每一個字節(jié)必須保證是8位長度。數(shù)據(jù)傳送時,先傳送最高位(MSB),每一個被傳送的字節(jié)后面都必須跟隨一位應(yīng)答位(即一幀共有9位)。
由于某種原因從機不對主機尋址信號應(yīng)答時(如從機正在進行實時性的處理工作而無法接收總線上的數(shù)據(jù)),它必須將數(shù)據(jù)線置于高電平,而由主機產(chǎn)生一個終止信號以結(jié)束總線的數(shù)據(jù)傳送。
如果從機對主機進行了應(yīng)答,但在數(shù)據(jù)傳送一段時間后無法繼續(xù)接收更多的數(shù)據(jù)時,從機可以通過對無法接收的第一個數(shù)據(jù)字節(jié)的“非應(yīng)答”通知主機,主機則應(yīng)發(fā)出終止信號以結(jié)束數(shù)據(jù)的繼續(xù)傳送。
當主機接收數(shù)據(jù)時,它收到最后一個數(shù)據(jù)字節(jié)后,必須向從機發(fā)出一個結(jié)束傳送的信號。這個信號是由對從機的“非應(yīng)答”來實現(xiàn)的。然后,從機釋放SDA線,以允許主機產(chǎn)生終止信號。
- 總線的尋址
I2C總線協(xié)議有明確的規(guī)定:采用7位的尋址字節(jié)(尋址字節(jié)是起始信號后的第一個字節(jié))。
- 尋址字節(jié)的位定義
D7~D1位組成從機的地址。D0位是數(shù)據(jù)傳送方向位,為“0”時表示主機向從機寫數(shù)據(jù),為“1”時表示主機由從機讀數(shù)據(jù)。
主機發(fā)送地址時,總線上的每個從機都將這7位地址碼與自己的地址進行比較,如果相同,則認為自己正被主機尋址,根據(jù)R/T位將自己確定為發(fā)送器或接收器。
從機的地址由固定部分和可編程部分組成。在一個系統(tǒng)中可能希望接入多個相同的從機,從機地址中可編程部分決定了可接入總線該類器件的最大數(shù)目。如一個從機的7位尋址位有4位是固定位,3位是可編程位,這時僅能尋址8個同樣的器件,即可以有8個同樣的器件接入到該I2C總線系統(tǒng)中。
- 數(shù)據(jù)幀格式
I2C總線上傳送的數(shù)據(jù)信號是廣義的,既包括地址信號,又包括真正的數(shù)據(jù)信號。
在起始信號后必須傳送一個從機的地址(7位),第8位是數(shù)據(jù)的傳送方向位(R/T),用“0”表示主機發(fā)送數(shù)據(jù)(T),“1”表示主機接收數(shù)據(jù)(R)。每次數(shù)據(jù)傳送總是由主機產(chǎn)生的終止信號結(jié)束。但是,若主機希望繼續(xù)占用總線進行新的數(shù)據(jù)傳送,則可以不產(chǎn)生終止信號,馬上再次發(fā)出起始信號對另一從機進行尋址。
在總線的一次數(shù)據(jù)傳送過程中,可以有以下幾種組合方式:
A)主機向從機發(fā)送數(shù)據(jù),數(shù)據(jù)傳送方向在整個過程中不變;
注:有陰影部分表示數(shù)據(jù)由主機向從機傳送,無陰影部分則表示數(shù)據(jù)由從機向主機傳送。A表示應(yīng)答, A表示非應(yīng)答(高電平)。S表示起始信號,P表示終止信號。
B)主機在第一個字節(jié)后,立即從從機讀數(shù)據(jù)。
C)在傳送過程中,當需要改變傳送方向時,起始信號和從機地址都被重復(fù)產(chǎn)生一次,但兩次讀/寫方向位正好反相。
要想了解對I2C的主從模式詳細了解,參看GD32xxx參考手冊的I2C接口章節(jié)。
2 AT24Cxx存儲器原理
2.1 AT24Cxx概述
AT24C01/02/04/08/16是一個1K/2K/4K/8K/16K位串行CMOS,EEPROM內(nèi)部含有128/256/512/1024/2048個8位字節(jié)CATALYST公司的先進CMOS技術(shù)實質(zhì)上減少了器件的功耗,AT24C01/02有一個8字節(jié)頁寫緩沖器AT24C04/08/16有一個16字節(jié)頁寫緩沖器,該器件通過I2C總線接口進行操作有一個專門的寫保護功能。 AT24C01/02每頁有8個字節(jié),分別為16/32頁;AT24C04/08/16每頁有16個字節(jié),分別為32/64/128頁 。
工作特點
- 與400KHz I2C總線兼容
- 1.8到6.0伏工作電壓范圍
- 低功耗CMOS技術(shù)
- 寫保護功能當WP為高電平時進入寫保護狀態(tài)
- 頁寫緩沖器
- 自定時擦寫周期
- 100萬次編程/擦除周期
- 可保存數(shù)據(jù)100年
- 8腳DIP SOIC或TSSOP封裝
- 溫度范圍商業(yè)級和工業(yè)級
AT24Cxx的引腳定義如下:
Note: For use of 5-lead SOT23, the software A2, A1, and A0 bits in the device address word must be set to zero toproperly communicate.
2.2總線時序
I2C總線時序如下:
其讀寫周期的電壓范圍如下:
寫周期時間是指從一個寫時序的有效停止信號到內(nèi)部編程/擦除周期結(jié)束的這一段時間。在寫周期期間,總線接口電路禁能,SDA保持為高電平,器件不響應(yīng)外部操作。
2.3 器件尋址
主器件通過發(fā)送一個起始信號啟動發(fā)送過程,然后發(fā)送它所要尋址的從器件的地址。8位從器件地址的高4位固定為(1010)。接下來的3位(A2、A1、A0)為器件的地址位,用來定義哪個器件以及器件的哪個部分被主器件訪問,上述8個AT24C01/02,4個AT24C04,2個AT24C08,1個AT24C16可單獨被系統(tǒng)尋址。從器件8位地址的最低位,作為讀寫控制位。“1”表示對從器件進行讀操作,“0”表示對從器件進行寫操作。在主器件發(fā)送起始信號和從器件地址字節(jié)后,AT24C01/02/04/08/16監(jiān)視總線并當其地址與發(fā)送的從地址相符時響應(yīng)一個應(yīng)答信號(通過SDA線)。AT24C01/02/04/08/16再根據(jù)讀寫控制位(R/W)的狀態(tài)進行讀或?qū)懖僮鳌?/p>
- 字節(jié)寫
在字節(jié)寫模式下,主器件發(fā)送起始命令和從器件地址信息(R/W)位置發(fā)給從器件,在從器件產(chǎn)生應(yīng)答信號后,主器件發(fā)送AT24Cxx的字節(jié)地址,主器件在收到從器件的另一個應(yīng)答信號后,再發(fā)送數(shù)據(jù)到被尋址的存儲單元。AT24Cxx再次應(yīng)答,并在主器件產(chǎn)生停止信號后開始內(nèi)部數(shù)據(jù)的擦寫,在內(nèi)部擦寫過程中,AT24Cxx不再應(yīng)答主器件的任何請求。
- 頁寫
用頁寫,AT24C01/02可一次寫入8 個字節(jié)數(shù)據(jù),AT24C04/08/16可以一次寫入16個字節(jié)的數(shù)據(jù)。__頁寫操作的啟動和字節(jié)寫一樣,不同在于傳送了一字節(jié)數(shù)據(jù)后并不產(chǎn)生停止信號。__主器件被允許發(fā)送P(AT24C01:P=7;AT24C02/04/08/16:P=15)個額外的字節(jié)。每發(fā)送一個字節(jié)數(shù)據(jù)后AT24Cxx產(chǎn)生一個應(yīng)答位并將字節(jié)地址低位加1,高位保持不變。
如果在發(fā)送停止信號之前主器件發(fā)送超過P+1個字節(jié),地址計數(shù)器將自動翻轉(zhuǎn),先前寫入的數(shù)據(jù)被覆蓋。
接收到P+1字節(jié)數(shù)據(jù)和主器件發(fā)送的停止信號后,AT24Cxx啟動內(nèi)部寫周期將數(shù)據(jù)寫到數(shù)據(jù)區(qū)。所有接收的數(shù)據(jù)在一個寫周期內(nèi)寫入AT24Cxx。
- 讀字節(jié)
讀操作允許主器件對寄存器的任意字節(jié)進行讀操作,主器件首先通過發(fā)送起始信號、從器件地址和它想讀取的字節(jié)數(shù)據(jù)的地址執(zhí)行一個寫操作。在AT24Cxx應(yīng)答之后,主器件重新發(fā)送起始信號和從器件地址,此時R/W位置1,AT24Cxx響應(yīng)并發(fā)送應(yīng)答信號,然后輸出所要求的一個8位字節(jié)數(shù)據(jù),主器件不發(fā)送應(yīng)答信號但產(chǎn)生一個停止信號。
- 順序讀
在AT24Cxx發(fā)送完一個8位字節(jié)數(shù)據(jù)后,主器件產(chǎn)生一個應(yīng)答信號來響應(yīng),告知AT24Cxx主器件要求更多的數(shù)據(jù),對應(yīng)每個主機產(chǎn)生的應(yīng)答信號AT24Cxx將發(fā)送一個8位數(shù)據(jù)字節(jié)。當主器件不發(fā)送應(yīng)答信號而發(fā)送停止位時結(jié)束此操作。
從AT24Cxx輸出的數(shù)據(jù)按順序由N到N+1輸出。讀操作時地址計數(shù)器在AT24Cxx整個地址內(nèi)增加,這樣整個寄存器區(qū)域可在一個讀操作內(nèi)全部讀出,當讀取的字節(jié)超過E(對于24WC01,E=127;對24C02,E=255;對24C04,E=511;對24C08,E=1023;對24C16,E=2047)計數(shù)器將翻轉(zhuǎn)到零并繼續(xù)輸出數(shù)據(jù)字節(jié)。
- 典型應(yīng)用
ATC02的典型電路如下:
根據(jù)AT24C02的芯片資料,我們會發(fā)現(xiàn)AT24C02有三個地址A0,A1,A2。同時,我們會在資料的Device Address介紹發(fā)現(xiàn)I2C器件一共有七位地址碼,還有一位是讀/寫(R/W)操作位,而在AT24C02的前四位已經(jīng)固定為1010。R/W為1則為 讀操作,為0則為寫操作。R/W位我們要設(shè)置為0(寫操作)。
規(guī)則為:1010(A0)(A1)(A2)(R/W)
例子1:
那么對應(yīng)的A0,A1,A2都是接的VCC,所以為A0=1,A1=1,A2=1;可以知道AT24C02的從設(shè)備寫地址為10101110(0xae),讀設(shè)備地址為10101111(0xaf)。
例子2:
那么對應(yīng)的A0,A1,A2都是接的GND,所以為A0=0,A1=0,A2=0;可以知道AT24C02的從設(shè)備寫地址為10100000(0xa0),讀設(shè)備地址為10100001(0xa1)。
3 I2C寄存器描述
I2C有6類寄存器,詳細的介紹請參考GD32F2XXX參考手冊的I2C寄存器描述部分。在這里筆者只講最重要的2個寄存器。
- 數(shù)據(jù)寄存器
數(shù)據(jù)寄存器的詳細描述如下所示。
- 時鐘寄存器
時鐘寄存器是I2C中比較重要的一個寄存器,時鐘信號的信號的穩(wěn)定是I2C正常工作的前提。
4 硬件設(shè)計及連接
本文是使用I2C協(xié)議對EEPROM進行讀寫操作,具體的硬件連接如下。
從硬件鏈接可以得到AT24C02的地址是0xA0,I2C的接口是I2C0。
5 硬件I2C
5.1 具體代碼實現(xiàn)
首先看看I2C的初始化。這有兩部分。
一部分是I2C的GPIO初始化。
/*
brief configure the GPIO ports
param[in] i2c_typedef_enum i2c_id
param[out] none
retval none
*/
void i2c_gpio_config(i2c_typedef_enum i2c_id)
{
/* enable GPIO clock */
rcu_periph_clock_enable(I2C_BUS_SCL_GPIO_CLK[i2c_id]);
rcu_periph_clock_enable(I2C_BUS_SDA_GPIO_CLK[i2c_id]);
/* Config I2C_SCL */
gpio_init(I2C_BUS_SCL_GPIO_PORT[i2c_id], GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, I2C_BUS_SCL_PIN[i2c_id]);
/* Config I2C_SDA */
gpio_init(I2C_BUS_SDA_GPIO_PORT[i2c_id], GPIO_MODE_AF_OD, GPIO_OSPEED_50MHZ, I2C_BUS_SDA_PIN[i2c_id]);
}
/*
brief configure the I2C0 interfaces
param[in] i2c_typedef_enum i2c_id
param[out] none
retval none
*/
void i2c_mode_config(i2c_typedef_enum i2c_id)
{
/* enable I2C clock */
rcu_periph_clock_enable(I2C_BUS_CLK[i2c_id]);
/* configure I2C clock */
i2c_clock_config(I2C_BUS[i2c_id], I2C_SPEED, I2C_DTCY_2);
/* configure I2C address */
i2c_mode_addr_config(I2C_BUS[i2c_id], I2C_I2CMODE_ENABLE, I2C_ADDFORMAT_7BITS, I2C_OWN_ADDRESS7);
/* enable I2C0 */
i2c_enable(I2C_BUS[i2c_id]);
/* enable acknowledge */
i2c_ack_config(I2C_BUS[i2c_id], I2C_ACK_ENABLE);
}
主要配置I2C模式、低電平占空比、I2C尋址模式以及通信速率,最后使能I2C設(shè)備。
初始化完成后就是對AT24C02的讀寫操作,嚴格按照相應(yīng)的時序操作就行。
- 字節(jié)寫
在字節(jié)寫模式下,向AT24C02中寫數(shù)據(jù)時序如下:
操作時序如下:
1.MCU先發(fā)送一個開始信號(START)啟動總線
2.接著跟上首字節(jié),發(fā)送器件寫操作地址(DEVICE ADDRESS)+寫數(shù)據(jù)(0xA0)
3.等待應(yīng)答信號(ACK)
4.發(fā)送數(shù)據(jù)的存儲地址。24C02一共有256個字節(jié)的存儲空間,地址從0x00~0xFF,想把數(shù)據(jù)存儲在哪個位置,此刻寫的就是哪個地址。
5.發(fā)送要存儲的數(shù)據(jù),在寫數(shù)據(jù)的過程中,AT24C02會回應(yīng)一個“應(yīng)答位0”,則表明寫AT24C02數(shù)據(jù)成功,如果沒有回應(yīng)答位,說明寫入不成功。
6.發(fā)送結(jié)束信號(STOP)停止總線。
代碼很簡單,跟著時序來就行。
/*
brief write one byte to the I2C EEPROM
param[in] i2c_typedef_enum i2c_id
p_buffer: pointer to the buffer containing the data to be written to the EEPROM
param[in] write_address: EEPROM's internal address to write to
param[out] none
retval none
*/
void eeprom_byte_write(i2c_typedef_enum i2c_id, uint8_t *p_buffer, uint8_t write_address)
{
/* wait until I2C bus is idle */
while(i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_I2CBSY));
/* send a start condition to I2C bus */
i2c_start_on_bus(I2C_BUS[i2c_id]);
/* wait until SBSEND bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_SBSEND));
/* send slave address to I2C bus */
i2c_master_addressing(I2C_BUS[i2c_id], eeprom_address, I2C_TRANSMITTER);
/* wait until ADDSEND bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_ADDSEND));
/* clear the ADDSEND bit */
i2c_flag_clear(I2C_BUS[i2c_id], I2C_FLAG_ADDSEND);
/* wait until the transmit data buffer is empty */
while(SET != i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_TBE));
/* send the EEPROM's internal address to write to : only one byte address */
i2c_data_transmit(I2C_BUS[i2c_id], write_address);
/* wait until BTC bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_BTC));
/* send the byte to be written */
i2c_data_transmit(I2C_BUS[i2c_id], *p_buffer);
/* wait until BTC bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_BTC));
/* send a stop condition to I2C bus */
i2c_stop_on_bus(I2C_BUS[i2c_id]);
/* wait until the stop condition is finished */
while(I2C_CTL0(I2C_BUS[i2c_id]) & 0x0200);
}
- 頁寫
用頁寫,AT24C01可一次寫入8 個字節(jié)數(shù)據(jù),AT24C02/04/08/16可以一次寫入16個字節(jié)的數(shù)據(jù)。__頁寫操作的啟動和字節(jié)寫一樣,不同在于傳送了一字節(jié)數(shù)據(jù)后并不產(chǎn)生停止信號。__每發(fā)送一個字節(jié)數(shù)據(jù)后AT24Cxx產(chǎn)生一個應(yīng)答位并將字節(jié)地址低位加1,高位保持不變。
如果在發(fā)送停止信號之前主器件發(fā)送超過P+1個字節(jié),地址計數(shù)器將自動翻轉(zhuǎn),先前寫入的數(shù)據(jù)被覆蓋。
接收到P+1字節(jié)數(shù)據(jù)和主器件發(fā)送的停止信號后,AT24Cxx啟動內(nèi)部寫周期將數(shù)據(jù)寫到數(shù)據(jù)區(qū)。所有接收的數(shù)據(jù)在一個寫周期內(nèi)寫入AT24Cxx。
代碼很簡單,和字節(jié)寫不同的是,數(shù)據(jù)會一直發(fā),直到主機發(fā)送停止信號。
/*
brief write more than one byte to the EEPROM with a single write cycle
param[in] i2c_typedef_enum i2c_id
p_buffer: pointer to the buffer containing the data to be written to the EEPROM
param[in] write_address: EEPROM's internal address to write to
param[in] number_of_byte: number of bytes to write to the EEPROM
param[out] none
retval none
*/
void eeprom_page_write(i2c_typedef_enum i2c_id, uint8_t *p_buffer, uint8_t write_address, uint8_t number_of_byte)
{
/* wait until I2C bus is idle */
while(i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_I2CBSY));
/* send a start condition to I2C bus */
i2c_start_on_bus(I2C_BUS[i2c_id]);
/* wait until SBSEND bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_SBSEND));
/* send slave address to I2C bus */
i2c_master_addressing(I2C_BUS[i2c_id], eeprom_address, I2C_TRANSMITTER);
/* wait until ADDSEND bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_ADDSEND));
/* clear the ADDSEND bit */
i2c_flag_clear(I2C_BUS[i2c_id], I2C_FLAG_ADDSEND);
/* wait until the transmit data buffer is empty */
while(SET != i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_TBE));
/* send the EEPROM's internal address to write to : only one byte address */
i2c_data_transmit(I2C_BUS[i2c_id], write_address);
/* wait until BTC bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_BTC));
/* while there is data to be written */
while(number_of_byte--)
{
i2c_data_transmit(I2C_BUS[i2c_id], *p_buffer);
/* point to the next byte to be written */
p_buffer++;
/* wait until BTC bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_BTC));
}
/* send a stop condition to I2C bus */
i2c_stop_on_bus(I2C_BUS[i2c_id]);
/* wait until the stop condition is finished */
while(I2C_CTL0(I2C_BUS[i2c_id]) & 0x0200);
}
- 任意寫
在實際過程中,我們經(jīng)常需要任意寫數(shù)據(jù),這里就調(diào)用頁寫的操作,來實現(xiàn)任意字節(jié)的寫操作。
/*
brief write buffer of data to the I2C EEPROM
param[in] i2c_typedef_enum i2c_id
p_buffer: pointer to the buffer containing the data to be written to the EEPROM
param[in] write_address: EEPROM's internal address to write to
param[in] number_of_byte: number of bytes to write to the EEPROM
param[out] none
retval none
*/
void eeprom_buffer_write(i2c_typedef_enum i2c_id, uint8_t *p_buffer, uint8_t write_address, uint16_t number_of_byte)
{
uint8_t number_of_page = 0, number_of_single = 0, address = 0, count = 0;
address = write_address % I2C_PAGE_SIZE;
count = I2C_PAGE_SIZE - address;
number_of_page = number_of_byte / I2C_PAGE_SIZE;
number_of_single = number_of_byte % I2C_PAGE_SIZE;
/* if write_address is I2C_PAGE_SIZE aligned */
if(0 == address)
{
while(number_of_page--)
{
eeprom_page_write(i2c_id, p_buffer, write_address, I2C_PAGE_SIZE);
eeprom_wait_standby_state(i2c_id);
write_address += I2C_PAGE_SIZE;
p_buffer += I2C_PAGE_SIZE;
}
if(0 != number_of_single)
{
eeprom_page_write(i2c_id, p_buffer, write_address, number_of_single);
eeprom_wait_standby_state(i2c_id);
}
}
else
{
/* if write_address is not I2C_PAGE_SIZE aligned */
if(number_of_byte < count)
{
eeprom_page_write(i2c_id, p_buffer, write_address, number_of_byte);
eeprom_wait_standby_state(i2c_id);
}
else
{
number_of_byte -= count;
number_of_page = number_of_byte / I2C_PAGE_SIZE;
number_of_single = number_of_byte % I2C_PAGE_SIZE;
if(0 != count)
{
eeprom_page_write(i2c_id, p_buffer, write_address, count);
eeprom_wait_standby_state(i2c_id);
write_address += count;
p_buffer += count;
}
/* write page */
while(number_of_page--)
{
eeprom_page_write(i2c_id, p_buffer, write_address, I2C_PAGE_SIZE);
eeprom_wait_standby_state(i2c_id);
write_address += I2C_PAGE_SIZE;
p_buffer += I2C_PAGE_SIZE;
}
/* write single */
if(0 != number_of_single)
{
eeprom_page_write(i2c_id, p_buffer, write_address, number_of_single);
eeprom_wait_standby_state(i2c_id);
}
}
}
}
主要分為兩種情況,寫的地址正好是一頁的開始,另外一種是在一頁的中間。不管如何,始終遵循的原則就是最大智能寫一頁,可以從一頁的中間開始。
- 讀字節(jié)
讀操作允許主器件對寄存器的任意字節(jié)進行讀操作,主器件首先通過發(fā)送起始信號、從器件地址和它想讀取的字節(jié)數(shù)據(jù)的地址執(zhí)行一個寫操作。在AT24Cxx應(yīng)答之后,主器件重新發(fā)送起始信號和從器件地址,此時R/W位置1,AT24Cxx響應(yīng)并發(fā)送應(yīng)答信號,然后輸出所要求的一個8位字節(jié)數(shù)據(jù),主器件不發(fā)送應(yīng)答信號但產(chǎn)生一個停止信號。
讀取字節(jié)的時序如下:
1.MCU先發(fā)送一個開始信號(START)啟動總線
2.接著跟上首字節(jié),發(fā)送器件寫操作地址(DEVICE ADDRESS)+寫數(shù)據(jù)(0xA0)
注意:這里寫操作是為了要把所要讀的數(shù)據(jù)的存儲地址先寫進去,告訴AT24Cxx要讀取哪個地址的數(shù)據(jù)。
3.發(fā)送要讀取內(nèi)存的地址(WORD ADDRESS),通知AT24Cxx讀取要哪個地址的信息。
4.重新發(fā)送開始信號(START)。
5.發(fā)送設(shè)備讀操作地址(DEVICE ADDRESS)對AT24Cxx進行讀操作 (0xA1)。
6.AT24Cxx會自動向主機發(fā)送數(shù)據(jù),主機讀取從器件發(fā)回的數(shù)據(jù),在讀一個字節(jié)后,MCU會回應(yīng)一個應(yīng)答信號(ACK)。
7.發(fā)送一個“非應(yīng)答位NAK(1)”。發(fā)送結(jié)束信號(STOP)停止總線。
- 順序讀
在AT24Cxx發(fā)送完一個8位字節(jié)數(shù)據(jù)后,主器件產(chǎn)生一個應(yīng)答信號來響應(yīng),告知AT24Cxx主器件要求更多的數(shù)據(jù),對應(yīng)每個主機產(chǎn)生的應(yīng)答信號AT24Cxx將發(fā)送一個8位數(shù)據(jù)字節(jié)。當主器件不發(fā)送應(yīng)答信號而發(fā)送停止位時結(jié)束此操作。
從AT24Cxx輸出的數(shù)據(jù)按順序由N到N+1輸出。讀操作時地址計數(shù)器在AT24Cxx整個地址內(nèi)增加,這樣整個寄存器區(qū)域可在一個讀操作內(nèi)全部讀出,當讀取的字節(jié)超過E(對于24WC01,E=127;對24C02,E=255;對24C04,E=511;對24C08,E=1023;對24C16,E=2047)計數(shù)器將翻轉(zhuǎn)到零并繼續(xù)輸出數(shù)據(jù)字節(jié)。
我們常用的方式就是連續(xù)讀取,代碼很簡單。
/*
brief read data from the EEPROM
param[in] i2c_typedef_enum i2c_id
p_buffer: pointer to the buffer that receives the data read from the EEPROM
param[in] read_address: EEPROM's internal address to start reading from
param[in] number_of_byte: number of bytes to reads from the EEPROM
param[out] none
retval none
*/
void eeprom_buffer_read(i2c_typedef_enum i2c_id, uint8_t *p_buffer, uint8_t read_address, uint16_t number_of_byte)
{
/* wait until I2C bus is idle */
while(i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_I2CBSY));
if(2 == number_of_byte)
{
i2c_ackpos_config(I2C_BUS[i2c_id], I2C_ACKPOS_NEXT);
}
/* send a start condition to I2C bus */
i2c_start_on_bus(I2C_BUS[i2c_id]);
/* wait until SBSEND bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_SBSEND));
/* send slave address to I2C bus */
i2c_master_addressing(I2C_BUS[i2c_id], eeprom_address, I2C_TRANSMITTER);
/* wait until ADDSEND bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_ADDSEND));
/* clear the ADDSEND bit */
i2c_flag_clear(I2C_BUS[i2c_id], I2C_FLAG_ADDSEND);
/* wait until the transmit data buffer is empty */
while(SET != i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_TBE));
/* enable I2C*/
i2c_enable(I2C_BUS[i2c_id]);
/* send the EEPROM's internal address to write to */
i2c_data_transmit(I2C_BUS[i2c_id], read_address);
/* wait until BTC bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_BTC));
/* send a start condition to I2C bus */
i2c_start_on_bus(I2C_BUS[i2c_id]);
/* wait until SBSEND bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_SBSEND));
/* send slave address to I2C bus */
i2c_master_addressing(I2C_BUS[i2c_id], eeprom_address, I2C_RECEIVER);
if(number_of_byte < 3)
{
/* disable acknowledge */
i2c_ack_config(I2C_BUS[i2c_id], I2C_ACK_DISABLE);
}
/* wait until ADDSEND bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_ADDSEND));
/* clear the ADDSEND bit */
i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
if(1 == number_of_byte)
{
/* send a stop condition to I2C bus */
i2c_stop_on_bus(I2C_BUS[i2c_id]);
}
/* while there is data to be read */
while(number_of_byte)
{
if(3 == number_of_byte)
{
/* wait until BTC bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_BTC));
/* disable acknowledge */
i2c_ack_config(I2C_BUS[i2c_id], I2C_ACK_DISABLE);
}
if(2 == number_of_byte)
{
/* wait until BTC bit is set */
while(!i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_BTC));
/* send a stop condition to I2C bus */
i2c_stop_on_bus(I2C_BUS[i2c_id]);
}
/* wait until the RBNE bit is set and clear it */
if(i2c_flag_get(I2C_BUS[i2c_id], I2C_FLAG_RBNE))
{
/* read a byte from the EEPROM */
*p_buffer = i2c_data_receive(I2C_BUS[i2c_id]);
/* point to the next location where the byte read will be saved */
p_buffer++;
/* decrement the read bytes counter */
number_of_byte--;
}
}
/* wait until the stop condition is finished */
while(I2C_CTL0(I2C_BUS[i2c_id]) & 0x0200);
/* enable acknowledge */
i2c_ack_config(I2C_BUS[i2c_id], I2C_ACK_ENABLE);
i2c_ackpos_config(I2C_BUS[i2c_id], I2C_ACKPOS_CURRENT);
}
最后看下主函數(shù)吧。
/*
brief main function
param[in] none
param[out] none
retval none
*/
int main(void)
{
//systick init
sysTick_init();
// led init
led_init(LED1);
//usart init 115200 8-N-1
com_init(COM1, 115200, 0, 1);
/* initialize EEPROM */
I2C_EE_Init(IIC0);
i2c_24c02_test(IIC0);
while(1)
{
led_toggle(LED1);
delay_ms(1000);
}
}
很簡單,往AT24C02中寫入數(shù)據(jù),然后再讀取數(shù)據(jù),讀寫測試的函數(shù)如下:
/*
brief I2C read and write functions
param[in] i2c_typedef_enum i2c_id
param[out] none
retval I2C_OK or I2C_FAIL
*/
uint8_t i2c_24c02_test(i2c_typedef_enum i2c_id)
{
uint16_t i;
uint8_t i2c_buffer_write[BUFFER_SIZE];
uint8_t i2c_buffer_read[BUFFER_SIZE];
printf("\\r\\nAT24C02 writing...\\r\\n");
/* initialize i2c_buffer_write */
for(i = 0; i < BUFFER_SIZE; i++)
{
i2c_buffer_write[i] = i;
printf("0x%02X ", i2c_buffer_write[i]);
if(15 == i % 16)
{
printf("\\r\\n");
}
}
/* EEPROM data write */
eeprom_buffer_write(i2c_id, i2c_buffer_write, EEP_FIRST_PAGE, BUFFER_SIZE);
printf("AT24C02 reading...\\r\\n");
/* EEPROM data read */
eeprom_buffer_read(i2c_id, i2c_buffer_read, EEP_FIRST_PAGE, BUFFER_SIZE);
/* compare the read buffer and write buffer */
for(i = 0; i < BUFFER_SIZE; i++)
{
if(i2c_buffer_read[i] != i2c_buffer_write[i])
{
printf("0x%02X ", i2c_buffer_read[i]);
printf("Err:data read and write aren't matching.\\n\\r");
return I2C_FAIL;
}
printf("0x%02X ", i2c_buffer_read[i]);
if(15 == i % 16)
{
printf("\\r\\n");
}
}
printf("I2C-AT24C02 test passed!\\n\\r");
return I2C_OK;
}
當然在讀寫測試之前應(yīng)該對AT24C02進行初始化操作。
/**
* @brief I2C 外設(shè)(EEPROM)初始化
* @param i2c_typedef_enum i2c_id
* @retval 無
*/
void I2C_EE_Init(i2c_typedef_enum i2c_id)
{
/* 選擇EEPROM要寫入的地址 */
#ifdef EEPROM_BLOCK0_ADDRESS
/* 選擇 EEPROM Block0 來寫入 */
eeprom_address = EEPROM_BLOCK0_ADDRESS;
#endif
#ifdef EEPROM_BLOCK1_ADDRESS
/* 選擇 EEPROM Block1 來寫入 */
eeprom_address = EEPROM_BLOCK2_ADDRESS;
#endif
#ifdef EEPROM_BLOCK2_ADDRESS
/* 選擇 EEPROM Block2 來寫入 */
eeprom_address = EEPROM_BLOCK2_ADDRESS;
#endif
#ifdef EEPROM_BLOCK3_ADDRESS
/* 選擇 EEPROM Block3 來寫入 */
eeprom_address = EEPROM_BLOCK3_ADDRESS;
#endif
i2c_gpio_config(i2c_id);
i2c_mode_config(i2c_id);
}
5.2 實驗現(xiàn)象
下載好程序后,打開串口助手,可以看到如下信息。
最后,我們使用邏輯分析來查看數(shù)據(jù)。
使用的400kHz的速率,可以看到數(shù)據(jù)的寫操作和前面分析的時序是一樣的,完全吻合。
6 軟件I2C
6.1 具體代碼實現(xiàn)
首先實現(xiàn)I2C的協(xié)議。
/**
* @brief 延時nus
@param nus為要延時的us數(shù). nus:0~190887435(最大值即2^32/fac_us@fac_us=22.5)
* @retval None
*/
void I2C_Delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD=nus*fac_us; //時間加載
SysTick->VAL=0x00; //清空計數(shù)器
SysTick->CTRL=0x01 ; //開始倒數(shù)
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待時間到達
SysTick->CTRL=0x00; //關(guān)閉計數(shù)器
SysTick->VAL =0X00; //清空計數(shù)器
}
/**
* @brief 延時nms
@param nms:要延時的ms數(shù)
* @retval None
*/
void I2C_Delay_ms(uint16_t nms)
{
uint32_t i;
for(i=0;i
注釋很清楚,對照I2C的協(xié)議看就行。
接著就是實現(xiàn)AT2C02的讀寫操作。
/**
* @brief EEPROM_CheckOk, 判斷串行EERPOM是否正常
* @param None
* @retval 1 表示正常, 0 表示不正常
*/
uint8_t EEPROM_CheckOk(void)
{
if (I2C_CheckDevice(EEPROM_DEV_ADDR) == 0)
{
return 1;
}
else
{
/* 失敗后,切記發(fā)送I2C總線停止信號 */
I2C_Stop();
return 0;
}
}
/**
* @brief EEPROM_ReadBytes, 從串行EEPROM指定地址處開始讀取若干數(shù)據(jù)
* @param _usAddress : 起始地址
* _usSize : 數(shù)據(jù)長度,單位為字節(jié)
* _pReadBuf : 存放讀到的數(shù)據(jù)的緩沖區(qū)指針
* @retval 0 表示失敗,1表示成功
*/
uint8_t EEPROM_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i;
/* 采用串行EEPROM隨即讀取指令序列,連續(xù)讀取若干字節(jié) */
/* 第1步:發(fā)起I2C總線啟動信號 */
I2C_Start();
/* 第2步:發(fā)起控制字節(jié),高7bit是地址,bit0是讀寫控制位,0表示寫,1表示讀 */
I2C_SendByte(EEPROM_DEV_ADDR | I2C_WR); /* 此處是寫指令 */
/* 第3步:發(fā)送ACK */
if (I2C_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件無應(yīng)答 */
}
/* 第4步:發(fā)送字節(jié)地址,24C02只有256字節(jié),因此1個字節(jié)就夠了,如果是24C04以上,那么此處需要連發(fā)多個地址 */
I2C_SendByte((uint8_t)_usAddress);
/* 第5步:發(fā)送ACK */
if (I2C_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件無應(yīng)答 */
}
/* 第6步:重新啟動I2C總線。前面的代碼的目的向EEPROM傳送地址,下面開始讀取數(shù)據(jù) */
I2C_Start();
/* 第7步:發(fā)起控制字節(jié),高7bit是地址,bit0是讀寫控制位,0表示寫,1表示讀 */
I2C_SendByte(EEPROM_DEV_ADDR | I2C_RD); /* 此處是讀指令 */
/* 第8步:發(fā)送ACK */
if (I2C_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件無應(yīng)答 */
}
/* 第9步:循環(huán)讀取數(shù)據(jù) */
for (i = 0; i < _usSize; i++)
{
_pReadBuf[i] = I2C_ReadByte(); /* 讀1個字節(jié) */
/* 每讀完1個字節(jié)后,需要發(fā)送Ack, 最后一個字節(jié)不需要Ack,發(fā)Nack */
if (i != _usSize - 1)
{
I2C_Ack(); /* 中間字節(jié)讀完后,CPU產(chǎn)生ACK信號(驅(qū)動SDA = 0) */
}
else
{
I2C_NAck(); /* 最后1個字節(jié)讀完后,CPU產(chǎn)生NACK信號(驅(qū)動SDA = 1) */
}
}
/* 發(fā)送I2C總線停止信號 */
I2C_Stop();
return 1; /* 執(zhí)行成功 */
cmd_fail: /* 命令執(zhí)行失敗后,切記發(fā)送停止信號,避免影響I2C總線上其他設(shè)備 */
/* 發(fā)送I2C總線停止信號 */
I2C_Stop();
return 0;
}
/**
* @brief EEPROM_WriteBytes, 向串行EEPROM指定地址寫入若干數(shù)據(jù),采用頁寫操作提高寫入效率
* @param _usAddress : 起始地址
* _usSize : 數(shù)據(jù)長度,單位為字節(jié)
* _pWriteBuf : 存放讀到的數(shù)據(jù)的緩沖區(qū)指針
* @retval 0 表示失敗,1表示成功
*/
uint8_t EEPROM_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
uint16_t i,m;
uint16_t usAddr;
/*
寫串行EEPROM不像讀操作可以連續(xù)讀取很多字節(jié),每次寫操作只能在同一個page。
對于24xx02,page size = 8
簡單的處理方法為:按字節(jié)寫操作模式,沒寫1個字節(jié),都發(fā)送地址
為了提高連續(xù)寫的效率: 本函數(shù)采用page wirte操作。
*/
usAddr = _usAddress;
for (i = 0; i < _usSize; i++)
{
/* 當發(fā)送第1個字節(jié)或是頁面首地址時,需要重新發(fā)起啟動信號和地址 */
if ((i == 0) || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0)
{
/*第0步:發(fā)停止信號,啟動內(nèi)部寫操作*/
I2C_Stop();
/* 通過檢查器件應(yīng)答的方式,判斷內(nèi)部寫操作是否完成, 一般小于 10ms
CLK頻率為200KHz時,查詢次數(shù)為30次左右
*/
for (m = 0; m < 100; m++)
{
/* 第1步:發(fā)起I2C總線啟動信號 */
I2C_Start();
/* 第2步:發(fā)起控制字節(jié),高7bit是地址,bit0是讀寫控制位,0表示寫,1表示讀 */
I2C_SendByte(EEPROM_DEV_ADDR | I2C_WR); /* 此處是寫指令 */
/* 第3步:發(fā)送一個時鐘,判斷器件是否正確應(yīng)答 */
if (I2C_WaitAck() == 0)
{
break;
}
}
if (m == 1000)
{
goto cmd_fail; /* EEPROM器件寫超時 */
}
/* 第4步:發(fā)送字節(jié)地址,24C02只有256字節(jié),因此1個字節(jié)就夠了,如果是24C04以上,那么此處需要連發(fā)多個地址 */
I2C_SendByte((uint8_t)usAddr);
/* 第5步:發(fā)送ACK */
if (I2C_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件無應(yīng)答 */
}
}
/* 第6步:開始寫入數(shù)據(jù) */
I2C_SendByte(_pWriteBuf[i]);
/* 第7步:發(fā)送ACK */
if (I2C_WaitAck() != 0)
{
goto cmd_fail; /* EEPROM器件無應(yīng)答 */
}
usAddr++; /* 地址增1 */
}
/* 命令執(zhí)行成功,發(fā)送I2C總線停止信號 */
I2C_Stop();
return 1;
cmd_fail: /* 命令執(zhí)行失敗后,切記發(fā)送停止信號,避免影響I2C總線上其他設(shè)備 */
/* 發(fā)送I2C總線停止信號 */
I2C_Stop();
return 0;
}
/**
* @brief EEPROM_Erase
* @param None
* @retval None
*/
void EEPROM_Erase(void)
{
uint16_t i;
uint8_t buf[EEPROM_SIZE];
/* 填充緩沖區(qū) */
for (i = 0; i < EEPROM_SIZE; i++)
{
buf[i] = 0xFF;
}
/* 寫EEPROM, 起始地址 = 0,數(shù)據(jù)長度為 256 */
if (EEPROM_WriteBytes(buf, 0, EEPROM_SIZE) == 0)
{
printf("擦除eeprom出錯!\\r\\n");
return;
}
else
{
printf("擦除eeprom成功!\\r\\n");
}
}
/**
* @brief EE_Delay
* @param nCount
* @retval None
*/
static void EEPROM_Delay(__IO uint32_t nCount) //簡單的延時函數(shù)
{
for(; nCount != 0; nCount--);
}
/**
* @brief AT24C02 初始化
* @param None
* @retval None
*/
void EEPROM_Init(void)
{
/*-----------------------------------------------------------------------------------*/
if (EEPROM_CheckOk() == 0)
{
/* 沒有檢測到EEPROM */
printf("沒有檢測到串行EEPROM!\\r\\n");
}
}
/**
* @brief AT24C02 讀寫測試
* @param None
* @retval None
*/
void EEPROM_Test(void)
{
uint16_t i;
uint8_t write_buf[EEPROM_SIZE];
uint8_t read_buf[EEPROM_SIZE];
/*------------------------------------------------------------------------------------*/
/* 填充測試緩沖區(qū) */
for (i = 0; i < EEPROM_SIZE; i++)
{
write_buf[i] = i;
}
/*------------------------------------------------------------------------------------*/
if (EEPROM_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)
{
printf("寫eeprom出錯!\\r\\n");
return;
}
else
{
printf("寫eeprom成功!\\r\\n");
}
/*寫完之后需要適當?shù)难訒r再去讀,不然會出錯*/
EEPROM_Delay(0x0FFFFF);
/*-----------------------------------------------------------------------------------*/
if (EEPROM_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)
{
printf("讀eeprom出錯!\\r\\n");
return;
}
else
{
printf("讀eeprom成功,數(shù)據(jù)如下:\\r\\n");
}
/*-----------------------------------------------------------------------------------*/
for (i = 0; i < EEPROM_SIZE; i++)
{
if(read_buf[i] != write_buf[i])
{
printf("0x%02X ", read_buf[i]);
printf("錯誤:EEPROM讀出與寫入的數(shù)據(jù)不一致");
return;
}
printf(" %02X", read_buf[i]);
if ((i & 15) == 15)
{
printf("\\r\\n");
}
}
printf("eeprom讀寫測試成功\\r\\n");
}
代碼很簡單,和使用硬件I2C的邏輯是一樣的。
最后看下主函數(shù)吧。
/*
brief main function
param[in] none
param[out] none
retval none
*/
int main(void)
{
//systick init
sysTick_init();
// led init
led_init(LED1);
//usart init 115200 8-N-1
com_init(COM1, 115200, 0, 1);
printf("eeprom 軟件模擬i2c測試例程 \\r\\n");
EEPROM_Init();
EEPROM_Test();
while(1)
{
led_toggle(LED1);
delay_ms(1000);
}
}
主函數(shù)中重點關(guān)注EEPROM_Test()函數(shù),這就是對AT24C02的讀寫操作。
6.2 實驗現(xiàn)象
下載程序,連接串口打印信息如下。
最后,我們使用邏輯分析來查看數(shù)據(jù)
-
mcu
+關(guān)注
關(guān)注
146文章
16984瀏覽量
350294 -
I2C
+關(guān)注
關(guān)注
28文章
1481瀏覽量
123280 -
開發(fā)板
+關(guān)注
關(guān)注
25文章
4943瀏覽量
97188 -
Cortex-M
+關(guān)注
關(guān)注
2文章
227瀏覽量
29726 -
GD32
+關(guān)注
關(guān)注
7文章
403瀏覽量
24219
發(fā)布評論請先 登錄
相關(guān)推薦
評論