RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

GD32開(kāi)發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)篇) 第21章 I2C

嵌入式大雜燴 ? 來(lái)源:嵌入式大雜燴 ? 作者:嵌入式大雜燴 ? 2023-05-22 09:06 ? 次閱讀

開(kāi)發(fā)環(huán)境:

MDK:Keil 5.30

開(kāi)發(fā)板:GD32F207I-EVAL

MCU:GD32F207IK

1 I2C工作原理

1.1 I2C串行總線概述

I2C總線是PHLIPS公司推出的一種雙線式半雙工串行總線,是具備多主機(jī)系統(tǒng)所需的總線裁決和高低速器件同步功能的高性能串行總線。用于連接微控器及外圍設(shè)備。I2C總線只有兩根雙向信號(hào)線。一根是數(shù)據(jù)線SDA,另一根是時(shí)鐘線SCL。

  • 物理層

1)它只使用兩條總線線路 :一條雙向串行數(shù)據(jù)線(SDA),一條串行時(shí)鐘線(SCL)。見(jiàn)圖 1。

2)每個(gè)連接到總線的設(shè)備都有一個(gè)獨(dú)立的地址,主機(jī)可以利用這個(gè)地址進(jìn)行不同設(shè)備之間的訪問(wèn)。

3)多主機(jī)同時(shí)使用總線時(shí),為了防止數(shù)據(jù)沖突,會(huì)利用仲裁方式?jīng)Q定由哪個(gè)設(shè)備占用總線。

4)具有三種傳輸模式 :標(biāo)準(zhǔn)模式的傳輸速率為 100 Kbit/s ,快速模式為 400 Kbit/s ,高速模式下可達(dá) 3.4 Mbit/s,但目前大多12C設(shè)備尚不支持高速模式。

5)片上的濾波器可以濾去總線數(shù)據(jù)線上的毛刺波以保證數(shù)據(jù)完整。

6)連接到相同總線的 IC 數(shù)量受到總線的最大電容 400 pF 限制

1684633670533lhn352peak

I2C總線通過(guò)上拉電阻接正電源。當(dāng)總線空閑時(shí),兩根線均為高電平。連到總線上的任一器件輸出的低電平,都將使總線的信號(hào)變低,即各器件的SDA及SCL都是線“與”關(guān)系。

16846336708962h44cnnhxm

每個(gè)接到I2C總線上的器件都有唯一的地址。主機(jī)與其它器件間的數(shù)據(jù)傳送可以是由主機(jī)發(fā)送數(shù)據(jù)到其它器件,這時(shí)主機(jī)即為發(fā)送器。由總線上接收數(shù)據(jù)的器件則為接收器。

在多主機(jī)系統(tǒng)中,可能同時(shí)有幾個(gè)主機(jī)企圖啟動(dòng)總線傳送數(shù)據(jù)。為了避免混亂, I2C總線要通過(guò)總線仲裁,以決定由哪一臺(tái)主機(jī)控制總線。

  • 協(xié)議層

I2C的協(xié)議包括起始和停止條件、數(shù)據(jù)有效性、響應(yīng)、仲裁、時(shí)鐘同步和地址廣播等環(huán)節(jié),由于我們使用的是 STM32 集成的硬件I2C接口,并不需要用軟件去模擬 SDA 和SCL 線的時(shí)序。

1684633671187u7bg1qs1jg

16846336714300q1gyex2f0

這兩幅圖表示的是主機(jī)和從機(jī)通信時(shí) SDA 線的數(shù)據(jù)包序列。

其中 S 表示由主機(jī)的I2C接口產(chǎn)生的傳輸起始信號(hào)(S),這時(shí)連接到I2C總線上的所有從機(jī)都會(huì)接收到這個(gè)信號(hào)。

起始信號(hào)產(chǎn)生后,所有從機(jī)就開(kāi)始等待主機(jī)緊接下來(lái)廣播的從機(jī)地址信號(hào)(SLAVE_ADDRESS),在I2C總線上,每個(gè)設(shè)備的地址都是唯一的。當(dāng)主機(jī)廣播的地址與某個(gè)設(shè)備地址相同時(shí),這個(gè)設(shè)備就被選中了,沒(méi)被選中的設(shè)備將會(huì)忽略之后的數(shù)據(jù)信號(hào)。根據(jù)I2C協(xié)議,這個(gè)從機(jī)地址可以是 7 位或 10 位。

在地址位之后,是傳輸方向的選擇位,該位為 0 時(shí),表示后面的數(shù)據(jù)傳輸方向是由主機(jī)傳輸至從機(jī)。該位為 1 時(shí),則相反。

從機(jī)接收到匹配的地址后,主機(jī)或從機(jī)會(huì)返回一個(gè)應(yīng)答(A)或非應(yīng)答信號(hào),只有接收到應(yīng)答信號(hào)后,主機(jī)才能繼續(xù)發(fā)送或接收數(shù)據(jù)。

若配置的方向傳輸位為寫(xiě)數(shù)據(jù),廣播完地址,接收到應(yīng)答信號(hào)后,主機(jī)開(kāi)始正式向從機(jī)傳輸數(shù)據(jù)(DATA),數(shù)據(jù)包的大小為 8 位。主機(jī)每發(fā)送完一個(gè)數(shù)據(jù),都要等待從機(jī)的應(yīng)答信號(hào)(A),重復(fù)這個(gè)過(guò)程,可以向從機(jī)傳輸 N 個(gè)數(shù)據(jù),這個(gè) N 沒(méi)有大小限制。當(dāng)數(shù)據(jù)傳輸結(jié)束時(shí),主機(jī)向從機(jī)發(fā)送一個(gè)停止傳輸信號(hào)(P),表示不再傳輸數(shù)據(jù)。

若配置的方向傳輸位為讀數(shù)據(jù),廣播完地址,接收到應(yīng)答信號(hào)后,從機(jī)開(kāi)始向主機(jī)返回?cái)?shù)據(jù)(DATA),數(shù)據(jù)包大小也為 8 位。從機(jī)每發(fā)送完一個(gè)數(shù)據(jù),都會(huì)等待主機(jī)的應(yīng)答信號(hào)(A),重復(fù)這個(gè)過(guò)程,可以返回 N 個(gè)數(shù)據(jù),這個(gè) N 也沒(méi)有大小限制。當(dāng)主機(jī)希望停止接收數(shù)據(jù)時(shí),就向從機(jī)返回一個(gè)非應(yīng)答信號(hào),則從機(jī)自動(dòng)停止數(shù)據(jù)傳輸。

  • I2C接口特性

1)STM32 的中等容量和大容量型號(hào)的芯片均有多達(dá)兩個(gè)的I2C總線接口。

2)能夠工作于多主模式或從模式,分別為主接收器、主發(fā)送器、從接收器及從發(fā)送器。

3)支持標(biāo)準(zhǔn)模式 100 Kbit/s 和快速模式 400 Kbit/s,不支持高速模式。

4)支持 7 位或 10 位尋址。

5)內(nèi)置了硬件 CRC 發(fā)生器 / 校驗(yàn)器。

6)I2 C 的接收和發(fā)送都可以使用 DMA 操作。

7)支持系統(tǒng)管理總線(SMBus)2.0 版。

  • I 2 C 架構(gòu)

I2C的所有硬件架構(gòu)就是根據(jù) SCL 線和 SDA 線展開(kāi)的(其中 SMBALERT 線用于 SMBus)。SCL 線的時(shí)序即為I2C 協(xié)議中的時(shí)鐘信號(hào),它由I2C 接口根據(jù)時(shí)鐘控制寄存器(CCR)控制,控制的參數(shù)主要為時(shí)鐘頻率。而 SDA 線的信號(hào)則通過(guò)一系列數(shù)據(jù)控制架構(gòu),在將要發(fā)送的數(shù)據(jù)的基礎(chǔ)上,根據(jù)協(xié)議添加各種起始信號(hào)、應(yīng)答信號(hào)、地址信號(hào),實(shí)現(xiàn)以 I 2 C 協(xié)議的方式發(fā)送出去。讀取數(shù)據(jù)時(shí)則從 SDA 線上的信號(hào)中取出接收到的數(shù)據(jù)值。發(fā)送和接收的數(shù)據(jù)都被保存在數(shù)據(jù)寄存器(DR)上。

1684633672553yebaw59s20

1.2 I2C總線的數(shù)據(jù)傳送

  • 數(shù)據(jù)位的有效性規(guī)定

I2C總線進(jìn)行數(shù)據(jù)傳送時(shí),時(shí)鐘信號(hào)為高電平期間,數(shù)據(jù)線上的數(shù)據(jù)必須保持穩(wěn)定,只有在時(shí)鐘線上的信號(hào)為低電平期間,數(shù)據(jù)線上的高電平或低電平狀態(tài)才允許變化。

1684633672849j1ygrcot7v

  • 起始和終止信號(hào)

SCL線為高電平期間, SDA線由高電平向低電平的變化表示起始信號(hào) ;SCL線為高電平期間, SDA線由低電平向高電平的變化表示終止信號(hào) 。

1684633673077fjpfzjb8r3

起始和終止信號(hào)都是由主機(jī)發(fā)出的 ,在起始信號(hào)產(chǎn)生后,總線就處于被占用的狀態(tài);在終止信號(hào)產(chǎn)生后,總線就處于空閑狀態(tài)。連接到I2C總線上的器件,若具有I2C總線的硬件接口,則很容易檢測(cè)到起始和終止信號(hào)。每當(dāng)發(fā)送器件傳輸完一個(gè)字節(jié)的數(shù)據(jù)后,后面必須緊跟一個(gè)校驗(yàn)位,這個(gè)校驗(yàn)位是接收端通過(guò)控制SDA(數(shù)據(jù)線)來(lái)實(shí)現(xiàn)的,以提醒發(fā)送端數(shù)據(jù)我這邊已經(jīng)接收完成,數(shù)據(jù)傳送可以繼續(xù)進(jìn)行。

  • 數(shù)據(jù)傳送格式
  • 字節(jié)傳送與應(yīng)答

每一個(gè)字節(jié)必須保證是8位長(zhǎng)度。數(shù)據(jù)傳送時(shí),先傳送最高位(MSB),每一個(gè)被傳送的字節(jié)后面都必須跟隨一位應(yīng)答位(即一幀共有9位)。

1684633673303tz8x0cse92

由于某種原因從機(jī)不對(duì)主機(jī)尋址信號(hào)應(yīng)答時(shí)(如從機(jī)正在進(jìn)行實(shí)時(shí)性的處理工作而無(wú)法接收總線上的數(shù)據(jù)),它必須將數(shù)據(jù)線置于高電平,而由主機(jī)產(chǎn)生一個(gè)終止信號(hào)以結(jié)束總線的數(shù)據(jù)傳送。

如果從機(jī)對(duì)主機(jī)進(jìn)行了應(yīng)答,但在數(shù)據(jù)傳送一段時(shí)間后無(wú)法繼續(xù)接收更多的數(shù)據(jù)時(shí),從機(jī)可以通過(guò)對(duì)無(wú)法接收的第一個(gè)數(shù)據(jù)字節(jié)的“非應(yīng)答”通知主機(jī),主機(jī)則應(yīng)發(fā)出終止信號(hào)以結(jié)束數(shù)據(jù)的繼續(xù)傳送。

當(dāng)主機(jī)接收數(shù)據(jù)時(shí),它收到最后一個(gè)數(shù)據(jù)字節(jié)后,必須向從機(jī)發(fā)出一個(gè)結(jié)束傳送的信號(hào)。這個(gè)信號(hào)是由對(duì)從機(jī)的“非應(yīng)答”來(lái)實(shí)現(xiàn)的。然后,從機(jī)釋放SDA線,以允許主機(jī)產(chǎn)生終止信號(hào)。

  • 總線的尋址

I2C總線協(xié)議有明確的規(guī)定:采用7位的尋址字節(jié)(尋址字節(jié)是起始信號(hào)后的第一個(gè)字節(jié))。

1684633673518xvtc75e3ay

  • 尋址字節(jié)的位定義

D7~D1位組成從機(jī)的地址。D0位是數(shù)據(jù)傳送方向位,為“0”時(shí)表示主機(jī)向從機(jī)寫(xiě)數(shù)據(jù),為“1”時(shí)表示主機(jī)由從機(jī)讀數(shù)據(jù)。

主機(jī)發(fā)送地址時(shí),總線上的每個(gè)從機(jī)都將這7位地址碼與自己的地址進(jìn)行比較,如果相同,則認(rèn)為自己正被主機(jī)尋址,根據(jù)R/T位將自己確定為發(fā)送器或接收器。

從機(jī)的地址由固定部分和可編程部分組成。在一個(gè)系統(tǒng)中可能希望接入多個(gè)相同的從機(jī),從機(jī)地址中可編程部分決定了可接入總線該類(lèi)器件的最大數(shù)目。如一個(gè)從機(jī)的7位尋址位有4位是固定位,3位是可編程位,這時(shí)僅能尋址8個(gè)同樣的器件,即可以有8個(gè)同樣的器件接入到該I2C總線系統(tǒng)中。

  • 數(shù)據(jù)幀格式

I2C總線上傳送的數(shù)據(jù)信號(hào)是廣義的,既包括地址信號(hào),又包括真正的數(shù)據(jù)信號(hào)。

在起始信號(hào)后必須傳送一個(gè)從機(jī)的地址(7位),第8位是數(shù)據(jù)的傳送方向位(R/T),用“0”表示主機(jī)發(fā)送數(shù)據(jù)(T),“1”表示主機(jī)接收數(shù)據(jù)(R)。每次數(shù)據(jù)傳送總是由主機(jī)產(chǎn)生的終止信號(hào)結(jié)束。但是,若主機(jī)希望繼續(xù)占用總線進(jìn)行新的數(shù)據(jù)傳送,則可以不產(chǎn)生終止信號(hào),馬上再次發(fā)出起始信號(hào)對(duì)另一從機(jī)進(jìn)行尋址。

在總線的一次數(shù)據(jù)傳送過(guò)程中,可以有以下幾種組合方式:

A)主機(jī)向從機(jī)發(fā)送數(shù)據(jù),數(shù)據(jù)傳送方向在整個(gè)過(guò)程中不變;

1684633673722kyay9yo8kv

注:有陰影部分表示數(shù)據(jù)由主機(jī)向從機(jī)傳送,無(wú)陰影部分則表示數(shù)據(jù)由從機(jī)向主機(jī)傳送。A表示應(yīng)答, A表示非應(yīng)答(高電平)。S表示起始信號(hào),P表示終止信號(hào)。

B)主機(jī)在第一個(gè)字節(jié)后,立即從從機(jī)讀數(shù)據(jù)。

1684633673932n8tfisfwgv

C)在傳送過(guò)程中,當(dāng)需要改變傳送方向時(shí),起始信號(hào)和從機(jī)地址都被重復(fù)產(chǎn)生一次,但兩次讀/寫(xiě)方向位正好反相。

1684633674132xvanmmcugx

1684633674344rt78493cst

要想了解對(duì)I2C的主從模式詳細(xì)了解,參看GD32xxx參考手冊(cè)的I2C接口章節(jié)。

2 AT24Cxx存儲(chǔ)器原理

2.1 AT24Cxx概述

AT24C01/02/04/08/16是一個(gè)1K/2K/4K/8K/16K位串行CMOS,EEPROM內(nèi)部含有128/256/512/1024/2048個(gè)8位字節(jié)CATALYST公司的先進(jìn)CMOS技術(shù)實(shí)質(zhì)上減少了器件的功耗,AT24C01/02有一個(gè)8字節(jié)頁(yè)寫(xiě)緩沖器AT24C04/08/16有一個(gè)16字節(jié)頁(yè)寫(xiě)緩沖器,該器件通過(guò)I2C總線接口進(jìn)行操作有一個(gè)專(zhuān)門(mén)的寫(xiě)保護(hù)功能。 AT24C01/02每頁(yè)有8個(gè)字節(jié),分別為16/32頁(yè);AT24C04/08/16每頁(yè)有16個(gè)字節(jié),分別為32/64/128頁(yè) 。

工作特點(diǎn)

  • 與400KHz I2C總線兼容
  • 1.8到6.0伏工作電壓范圍
  • 低功耗CMOS技術(shù)
  • 寫(xiě)保護(hù)功能當(dāng)WP為高電平時(shí)進(jìn)入寫(xiě)保護(hù)狀態(tài)
  • 頁(yè)寫(xiě)緩沖器
  • 自定時(shí)擦寫(xiě)周期
  • 100萬(wàn)次編程/擦除周期
  • 可保存數(shù)據(jù)100年
  • 8腳DIP SOIC或TSSOP封裝
  • 溫度范圍商業(yè)級(jí)和工業(yè)級(jí)

AT24Cxx的引腳定義如下:

1684633674574z2lxs9v7ws

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.

1684633674909vdvue4k0gz

2.2總線時(shí)序

I2C總線時(shí)序如下:

16846336753392d3l1vj27n

其讀寫(xiě)周期的電壓范圍如下:

1684633675715dtey3ehaa1

寫(xiě)周期時(shí)間是指從一個(gè)寫(xiě)時(shí)序的有效停止信號(hào)到內(nèi)部編程/擦除周期結(jié)束的這一段時(shí)間。在寫(xiě)周期期間,總線接口電路禁能,SDA保持為高電平,器件不響應(yīng)外部操作。

2.3 器件尋址

主器件通過(guò)發(fā)送一個(gè)起始信號(hào)啟動(dòng)發(fā)送過(guò)程,然后發(fā)送它所要尋址的從器件的地址。8位從器件地址的高4位固定為(1010)。接下來(lái)的3位(A2、A1、A0)為器件的地址位,用來(lái)定義哪個(gè)器件以及器件的哪個(gè)部分被主器件訪問(wèn),上述8個(gè)AT24C01/02,4個(gè)AT24C04,2個(gè)AT24C08,1個(gè)AT24C16可單獨(dú)被系統(tǒng)尋址。從器件8位地址的最低位,作為讀寫(xiě)控制位?!?”表示對(duì)從器件進(jìn)行讀操作,“0”表示對(duì)從器件進(jìn)行寫(xiě)操作。在主器件發(fā)送起始信號(hào)和從器件地址字節(jié)后,AT24C01/02/04/08/16監(jiān)視總線并當(dāng)其地址與發(fā)送的從地址相符時(shí)響應(yīng)一個(gè)應(yīng)答信號(hào)(通過(guò)SDA線)。AT24C01/02/04/08/16再根據(jù)讀寫(xiě)控制位(R/W)的狀態(tài)進(jìn)行讀或?qū)懖僮鳌?/p>

1684633676175pxeug081dy

  • 字節(jié)寫(xiě)

在字節(jié)寫(xiě)模式下,主器件發(fā)送起始命令和從器件地址信息(R/W)位置發(fā)給從器件,在從器件產(chǎn)生應(yīng)答信號(hào)后,主器件發(fā)送AT24Cxx的字節(jié)地址,主器件在收到從器件的另一個(gè)應(yīng)答信號(hào)后,再發(fā)送數(shù)據(jù)到被尋址的存儲(chǔ)單元。AT24Cxx再次應(yīng)答,并在主器件產(chǎn)生停止信號(hào)后開(kāi)始內(nèi)部數(shù)據(jù)的擦寫(xiě),在內(nèi)部擦寫(xiě)過(guò)程中,AT24Cxx不再應(yīng)答主器件的任何請(qǐng)求。

1684633676484wukfnf0sjo

  • 頁(yè)寫(xiě)

用頁(yè)寫(xiě),AT24C01/02可一次寫(xiě)入8 個(gè)字節(jié)數(shù)據(jù),AT24C04/08/16可以一次寫(xiě)入16個(gè)字節(jié)的數(shù)據(jù)。__頁(yè)寫(xiě)操作的啟動(dòng)和字節(jié)寫(xiě)一樣,不同在于傳送了一字節(jié)數(shù)據(jù)后并不產(chǎn)生停止信號(hào)。__主器件被允許發(fā)送P(AT24C01:P=7;AT24C02/04/08/16:P=15)個(gè)額外的字節(jié)。每發(fā)送一個(gè)字節(jié)數(shù)據(jù)后AT24Cxx產(chǎn)生一個(gè)應(yīng)答位并將字節(jié)地址低位加1,高位保持不變。

如果在發(fā)送停止信號(hào)之前主器件發(fā)送超過(guò)P+1個(gè)字節(jié),地址計(jì)數(shù)器將自動(dòng)翻轉(zhuǎn),先前寫(xiě)入的數(shù)據(jù)被覆蓋。

接收到P+1字節(jié)數(shù)據(jù)和主器件發(fā)送的停止信號(hào)后,AT24Cxx啟動(dòng)內(nèi)部寫(xiě)周期將數(shù)據(jù)寫(xiě)到數(shù)據(jù)區(qū)。所有接收的數(shù)據(jù)在一個(gè)寫(xiě)周期內(nèi)寫(xiě)入AT24Cxx。

1684633676702e0m63f0p6i

  • 讀字節(jié)

讀操作允許主器件對(duì)寄存器的任意字節(jié)進(jìn)行讀操作,主器件首先通過(guò)發(fā)送起始信號(hào)、從器件地址和它想讀取的字節(jié)數(shù)據(jù)的地址執(zhí)行一個(gè)寫(xiě)操作。在AT24Cxx應(yīng)答之后,主器件重新發(fā)送起始信號(hào)和從器件地址,此時(shí)R/W位置1,AT24Cxx響應(yīng)并發(fā)送應(yīng)答信號(hào),然后輸出所要求的一個(gè)8位字節(jié)數(shù)據(jù),主器件不發(fā)送應(yīng)答信號(hào)但產(chǎn)生一個(gè)停止信號(hào)。

16846336769686k3c29fu7j

  • 順序讀

在AT24Cxx發(fā)送完一個(gè)8位字節(jié)數(shù)據(jù)后,主器件產(chǎn)生一個(gè)應(yīng)答信號(hào)來(lái)響應(yīng),告知AT24Cxx主器件要求更多的數(shù)據(jù),對(duì)應(yīng)每個(gè)主機(jī)產(chǎn)生的應(yīng)答信號(hào)AT24Cxx將發(fā)送一個(gè)8位數(shù)據(jù)字節(jié)。當(dāng)主器件不發(fā)送應(yīng)答信號(hào)而發(fā)送停止位時(shí)結(jié)束此操作。

從AT24Cxx輸出的數(shù)據(jù)按順序由N到N+1輸出。讀操作時(shí)地址計(jì)數(shù)器在AT24Cxx整個(gè)地址內(nèi)增加,這樣整個(gè)寄存器區(qū)域可在一個(gè)讀操作內(nèi)全部讀出,當(dāng)讀取的字節(jié)超過(guò)E(對(duì)于24WC01,E=127;對(duì)24C02,E=255;對(duì)24C04,E=511;對(duì)24C08,E=1023;對(duì)24C16,E=2047)計(jì)數(shù)器將翻轉(zhuǎn)到零并繼續(xù)輸出數(shù)據(jù)字節(jié)。

168463367719777znsxbglf

  • 典型應(yīng)用

ATC02的典型電路如下:

1684633677472dlwqz7xw5l

根據(jù)AT24C02的芯片資料,我們會(huì)發(fā)現(xiàn)AT24C02有三個(gè)地址A0,A1,A2。同時(shí),我們會(huì)在資料的Device Address介紹發(fā)現(xiàn)I2C器件一共有七位地址碼,還有一位是讀/寫(xiě)(R/W)操作位,而在AT24C02的前四位已經(jīng)固定為1010。R/W為1則為 讀操作,為0則為寫(xiě)操作。R/W位我們要設(shè)置為0(寫(xiě)操作)。

規(guī)則為:1010(A0)(A1)(A2)(R/W)

例子1:

那么對(duì)應(yīng)的A0,A1,A2都是接的VCC,所以為A0=1,A1=1,A2=1;可以知道AT24C02的從設(shè)備寫(xiě)地址為10101110(0xae),讀設(shè)備地址為10101111(0xaf)。

例子2:

那么對(duì)應(yīng)的A0,A1,A2都是接的GND,所以為A0=0,A1=0,A2=0;可以知道AT24C02的從設(shè)備寫(xiě)地址為10100000(0xa0),讀設(shè)備地址為10100001(0xa1)。

3 I2C寄存器描述

I2C有6類(lèi)寄存器,詳細(xì)的介紹請(qǐng)參考GD32F2XXX參考手冊(cè)的I2C寄存器描述部分。在這里筆者只講最重要的2個(gè)寄存器。

  • 數(shù)據(jù)寄存器

數(shù)據(jù)寄存器的詳細(xì)描述如下所示。

1684633677753jqwyrcyw35

  • 時(shí)鐘寄存器

時(shí)鐘寄存器是I2C中比較重要的一個(gè)寄存器,時(shí)鐘信號(hào)的信號(hào)的穩(wěn)定是I2C正常工作的前提。

16846336780924amw1tzup9

4 硬件設(shè)計(jì)及連接

本文是使用I2C協(xié)議對(duì)EEPROM進(jìn)行讀寫(xiě)操作,具體的硬件連接如下。

1684633678467powz2w66xn

從硬件鏈接可以得到AT24C02的地址是0xA0,I2C的接口是I2C0。

5 硬件I2C

5.1 具體代碼實(shí)現(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è)備。

初始化完成后就是對(duì)AT24C02的讀寫(xiě)操作,嚴(yán)格按照相應(yīng)的時(shí)序操作就行。

  • 字節(jié)寫(xiě)

在字節(jié)寫(xiě)模式下,向AT24C02中寫(xiě)數(shù)據(jù)時(shí)序如下:

16846336788315v4o0dthkg

操作時(shí)序如下:

1.MCU先發(fā)送一個(gè)開(kāi)始信號(hào)(START)啟動(dòng)總線

2.接著跟上首字節(jié),發(fā)送器件寫(xiě)操作地址(DEVICE ADDRESS)+寫(xiě)數(shù)據(jù)(0xA0)

3.等待應(yīng)答信號(hào)(ACK)

4.發(fā)送數(shù)據(jù)的存儲(chǔ)地址。24C02一共有256個(gè)字節(jié)的存儲(chǔ)空間,地址從0x00~0xFF,想把數(shù)據(jù)存儲(chǔ)在哪個(gè)位置,此刻寫(xiě)的就是哪個(gè)地址。

5.發(fā)送要存儲(chǔ)的數(shù)據(jù),在寫(xiě)數(shù)據(jù)的過(guò)程中,AT24C02會(huì)回應(yīng)一個(gè)“應(yīng)答位0”,則表明寫(xiě)AT24C02數(shù)據(jù)成功,如果沒(méi)有回應(yīng)答位,說(shuō)明寫(xiě)入不成功。

6.發(fā)送結(jié)束信號(hào)(STOP)停止總線。

代碼很簡(jiǎn)單,跟著時(shí)序來(lái)就行。

/*
    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);
}
  • 頁(yè)寫(xiě)

用頁(yè)寫(xiě),AT24C01可一次寫(xiě)入8 個(gè)字節(jié)數(shù)據(jù),AT24C02/04/08/16可以一次寫(xiě)入16個(gè)字節(jié)的數(shù)據(jù)。__頁(yè)寫(xiě)操作的啟動(dòng)和字節(jié)寫(xiě)一樣,不同在于傳送了一字節(jié)數(shù)據(jù)后并不產(chǎn)生停止信號(hào)。__每發(fā)送一個(gè)字節(jié)數(shù)據(jù)后AT24Cxx產(chǎn)生一個(gè)應(yīng)答位并將字節(jié)地址低位加1,高位保持不變。

如果在發(fā)送停止信號(hào)之前主器件發(fā)送超過(guò)P+1個(gè)字節(jié),地址計(jì)數(shù)器將自動(dòng)翻轉(zhuǎn),先前寫(xiě)入的數(shù)據(jù)被覆蓋。

接收到P+1字節(jié)數(shù)據(jù)和主器件發(fā)送的停止信號(hào)后,AT24Cxx啟動(dòng)內(nèi)部寫(xiě)周期將數(shù)據(jù)寫(xiě)到數(shù)據(jù)區(qū)。所有接收的數(shù)據(jù)在一個(gè)寫(xiě)周期內(nèi)寫(xiě)入AT24Cxx。

1684633679042lo49pl88od

代碼很簡(jiǎn)單,和字節(jié)寫(xiě)不同的是,數(shù)據(jù)會(huì)一直發(fā),直到主機(jī)發(fā)送停止信號(hào)。

/*
    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);
}
  • 任意寫(xiě)

在實(shí)際過(guò)程中,我們經(jīng)常需要任意寫(xiě)數(shù)據(jù),這里就調(diào)用頁(yè)寫(xiě)的操作,來(lái)實(shí)現(xiàn)任意字節(jié)的寫(xiě)操作。

/*
    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);
            }
        }
    }
}

主要分為兩種情況,寫(xiě)的地址正好是一頁(yè)的開(kāi)始,另外一種是在一頁(yè)的中間。不管如何,始終遵循的原則就是最大智能寫(xiě)一頁(yè),可以從一頁(yè)的中間開(kāi)始。

  • 讀字節(jié)

讀操作允許主器件對(duì)寄存器的任意字節(jié)進(jìn)行讀操作,主器件首先通過(guò)發(fā)送起始信號(hào)、從器件地址和它想讀取的字節(jié)數(shù)據(jù)的地址執(zhí)行一個(gè)寫(xiě)操作。在AT24Cxx應(yīng)答之后,主器件重新發(fā)送起始信號(hào)和從器件地址,此時(shí)R/W位置1,AT24Cxx響應(yīng)并發(fā)送應(yīng)答信號(hào),然后輸出所要求的一個(gè)8位字節(jié)數(shù)據(jù),主器件不發(fā)送應(yīng)答信號(hào)但產(chǎn)生一個(gè)停止信號(hào)。

1684633679321gu6dvtteof

讀取字節(jié)的時(shí)序如下:

1.MCU先發(fā)送一個(gè)開(kāi)始信號(hào)(START)啟動(dòng)總線

2.接著跟上首字節(jié),發(fā)送器件寫(xiě)操作地址(DEVICE ADDRESS)+寫(xiě)數(shù)據(jù)(0xA0)

注意:這里寫(xiě)操作是為了要把所要讀的數(shù)據(jù)的存儲(chǔ)地址先寫(xiě)進(jìn)去,告訴AT24Cxx要讀取哪個(gè)地址的數(shù)據(jù)。

3.發(fā)送要讀取內(nèi)存的地址(WORD ADDRESS),通知AT24Cxx讀取要哪個(gè)地址的信息。

4.重新發(fā)送開(kāi)始信號(hào)(START)。

5.發(fā)送設(shè)備讀操作地址(DEVICE ADDRESS)對(duì)AT24Cxx進(jìn)行讀操作 (0xA1)。

6.AT24Cxx會(huì)自動(dòng)向主機(jī)發(fā)送數(shù)據(jù),主機(jī)讀取從器件發(fā)回的數(shù)據(jù),在讀一個(gè)字節(jié)后,MCU會(huì)回應(yīng)一個(gè)應(yīng)答信號(hào)(ACK)。

7.發(fā)送一個(gè)“非應(yīng)答位NAK(1)”。發(fā)送結(jié)束信號(hào)(STOP)停止總線。

  • 順序讀

在AT24Cxx發(fā)送完一個(gè)8位字節(jié)數(shù)據(jù)后,主器件產(chǎn)生一個(gè)應(yīng)答信號(hào)來(lái)響應(yīng),告知AT24Cxx主器件要求更多的數(shù)據(jù),對(duì)應(yīng)每個(gè)主機(jī)產(chǎn)生的應(yīng)答信號(hào)AT24Cxx將發(fā)送一個(gè)8位數(shù)據(jù)字節(jié)。當(dāng)主器件不發(fā)送應(yīng)答信號(hào)而發(fā)送停止位時(shí)結(jié)束此操作。

從AT24Cxx輸出的數(shù)據(jù)按順序由N到N+1輸出。讀操作時(shí)地址計(jì)數(shù)器在AT24Cxx整個(gè)地址內(nèi)增加,這樣整個(gè)寄存器區(qū)域可在一個(gè)讀操作內(nèi)全部讀出,當(dāng)讀取的字節(jié)超過(guò)E(對(duì)于24WC01,E=127;對(duì)24C02,E=255;對(duì)24C04,E=511;對(duì)24C08,E=1023;對(duì)24C16,E=2047)計(jì)數(shù)器將翻轉(zhuǎn)到零并繼續(xù)輸出數(shù)據(jù)字節(jié)。

1684633679539js6io7oyik

我們常用的方式就是連續(xù)讀取,代碼很簡(jiǎn)單。

/*
    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);
    }
}

很簡(jiǎn)單,往AT24C02中寫(xiě)入數(shù)據(jù),然后再讀取數(shù)據(jù),讀寫(xiě)測(cè)試的函數(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;
}

當(dāng)然在讀寫(xiě)測(cè)試之前應(yīng)該對(duì)AT24C02進(jìn)行初始化操作。

/**
  * @brief  I2C 外設(shè)(EEPROM)初始化
  * @param  i2c_typedef_enum i2c_id
  * @retval 無(wú)
  */
void I2C_EE_Init(i2c_typedef_enum i2c_id)
{
    /* 選擇EEPROM要寫(xiě)入的地址 */
#ifdef EEPROM_BLOCK0_ADDRESS
    /* 選擇 EEPROM Block0 來(lái)寫(xiě)入 */
    eeprom_address = EEPROM_BLOCK0_ADDRESS;
#endif

#ifdef EEPROM_BLOCK1_ADDRESS  
    /* 選擇 EEPROM Block1 來(lái)寫(xiě)入 */
    eeprom_address = EEPROM_BLOCK2_ADDRESS;
#endif

#ifdef EEPROM_BLOCK2_ADDRESS  
    /* 選擇 EEPROM Block2 來(lái)寫(xiě)入 */
    eeprom_address = EEPROM_BLOCK2_ADDRESS;
#endif

#ifdef EEPROM_BLOCK3_ADDRESS  
    /* 選擇 EEPROM Block3 來(lái)寫(xiě)入 */
    eeprom_address = EEPROM_BLOCK3_ADDRESS;
#endif
    i2c_gpio_config(i2c_id); 
 
    i2c_mode_config(i2c_id);
}

5.2 實(shí)驗(yàn)現(xiàn)象

下載好程序后,打開(kāi)串口助手,可以看到如下信息。

1684633679809tka9v1xdzp

最后,我們使用邏輯分析來(lái)查看數(shù)據(jù)。

168463368049216b3sl9j91

使用的400kHz的速率,可以看到數(shù)據(jù)的寫(xiě)操作和前面分析的時(shí)序是一樣的,完全吻合。

6 軟件I2C

6.1 具體代碼實(shí)現(xiàn)

首先實(shí)現(xiàn)I2C的協(xié)議。

/**
  * @brief  延時(shí)nus
    @param  nus為要延時(shí)的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;      //時(shí)間加載
    SysTick->VAL=0x00;          //清空計(jì)數(shù)器
    SysTick->CTRL=0x01 ;        //開(kāi)始倒數(shù)
    do
    {
        temp=SysTick->CTRL;
    }while((temp&0x01)&&!(temp&(1<<16)));    //等待時(shí)間到達(dá)
    SysTick->CTRL=0x00;      //關(guān)閉計(jì)數(shù)器
    SysTick->VAL =0X00;    //清空計(jì)數(shù)器 
}

/**
  * @brief  延時(shí)nms
    @param  nms:要延時(shí)的ms數(shù)
  * @retval None
  */
void I2C_Delay_ms(uint16_t nms)
{
    uint32_t i;
    for(i=0;i

注釋很清楚,對(duì)照I2C的協(xié)議看就行。

接著就是實(shí)現(xiàn)AT2C02的讀寫(xiě)操作。

/**
  * @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總線停止信號(hào) */
        I2C_Stop();        
        return 0;
    }
}

/**
  * @brief  EEPROM_ReadBytes, 從串行EEPROM指定地址處開(kāi)始讀取若干數(shù)據(jù)
  * @param  _usAddress : 起始地址
  *            _usSize : 數(shù)據(jù)長(zhǎng)度,單位為字節(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總線啟動(dòng)信號(hào) */
    I2C_Start();
    
    /* 第2步:發(fā)起控制字節(jié),高7bit是地址,bit0是讀寫(xiě)控制位,0表示寫(xiě),1表示讀 */
    I2C_SendByte(EEPROM_DEV_ADDR | I2C_WR);    /* 此處是寫(xiě)指令 */
    
    /* 第3步:發(fā)送ACK */
    if (I2C_WaitAck() != 0)
    {
        goto cmd_fail;    /* EEPROM器件無(wú)應(yīng)答 */
    }

    /* 第4步:發(fā)送字節(jié)地址,24C02只有256字節(jié),因此1個(gè)字節(jié)就夠了,如果是24C04以上,那么此處需要連發(fā)多個(gè)地址 */
    I2C_SendByte((uint8_t)_usAddress);
    
    /* 第5步:發(fā)送ACK */
    if (I2C_WaitAck() != 0)
    {
        goto cmd_fail;    /* EEPROM器件無(wú)應(yīng)答 */
    }
    
    /* 第6步:重新啟動(dòng)I2C總線。前面的代碼的目的向EEPROM傳送地址,下面開(kāi)始讀取數(shù)據(jù) */
    I2C_Start();
    
    /* 第7步:發(fā)起控制字節(jié),高7bit是地址,bit0是讀寫(xiě)控制位,0表示寫(xiě),1表示讀 */
    I2C_SendByte(EEPROM_DEV_ADDR | I2C_RD);    /* 此處是讀指令 */
    
    /* 第8步:發(fā)送ACK */
    if (I2C_WaitAck() != 0)
    {
        goto cmd_fail;    /* EEPROM器件無(wú)應(yīng)答 */
    }    
    
    /* 第9步:循環(huán)讀取數(shù)據(jù) */
    for (i = 0; i < _usSize; i++)
    {
        _pReadBuf[i] = I2C_ReadByte();    /* 讀1個(gè)字節(jié) */
        
        /* 每讀完1個(gè)字節(jié)后,需要發(fā)送Ack, 最后一個(gè)字節(jié)不需要Ack,發(fā)Nack */
        if (i != _usSize - 1)
        {
            I2C_Ack();    /* 中間字節(jié)讀完后,CPU產(chǎn)生ACK信號(hào)(驅(qū)動(dòng)SDA = 0) */
        }
        else
        {
            I2C_NAck();    /* 最后1個(gè)字節(jié)讀完后,CPU產(chǎn)生NACK信號(hào)(驅(qū)動(dòng)SDA = 1) */
        }
    }
    /* 發(fā)送I2C總線停止信號(hào) */
    I2C_Stop();
    return 1;    /* 執(zhí)行成功 */

cmd_fail: /* 命令執(zhí)行失敗后,切記發(fā)送停止信號(hào),避免影響I2C總線上其他設(shè)備 */
    /* 發(fā)送I2C總線停止信號(hào) */
    I2C_Stop();
    return 0;
}

/**
  * @brief  EEPROM_WriteBytes, 向串行EEPROM指定地址寫(xiě)入若干數(shù)據(jù),采用頁(yè)寫(xiě)操作提高寫(xiě)入效率
  * @param  _usAddress : 起始地址
  *            _usSize : 數(shù)據(jù)長(zhǎng)度,單位為字節(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;
    
    /* 
        寫(xiě)串行EEPROM不像讀操作可以連續(xù)讀取很多字節(jié),每次寫(xiě)操作只能在同一個(gè)page。
        對(duì)于24xx02,page size = 8
        簡(jiǎn)單的處理方法為:按字節(jié)寫(xiě)操作模式,沒(méi)寫(xiě)1個(gè)字節(jié),都發(fā)送地址
        為了提高連續(xù)寫(xiě)的效率: 本函數(shù)采用page wirte操作。
    */

    usAddr = _usAddress;    
    for (i = 0; i < _usSize; i++)
    {
        /* 當(dāng)發(fā)送第1個(gè)字節(jié)或是頁(yè)面首地址時(shí),需要重新發(fā)起啟動(dòng)信號(hào)和地址 */
        if ((i == 0) || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0)
        {
            /*第0步:發(fā)停止信號(hào),啟動(dòng)內(nèi)部寫(xiě)操作*/
            I2C_Stop();
            
            /* 通過(guò)檢查器件應(yīng)答的方式,判斷內(nèi)部寫(xiě)操作是否完成, 一般小于 10ms             
                CLK頻率為200KHz時(shí),查詢(xún)次數(shù)為30次左右
            */
            for (m = 0; m < 100; m++)
            {                
                /* 第1步:發(fā)起I2C總線啟動(dòng)信號(hào) */
                I2C_Start();
                
                /* 第2步:發(fā)起控制字節(jié),高7bit是地址,bit0是讀寫(xiě)控制位,0表示寫(xiě),1表示讀 */
                I2C_SendByte(EEPROM_DEV_ADDR | I2C_WR);    /* 此處是寫(xiě)指令 */
                
                /* 第3步:發(fā)送一個(gè)時(shí)鐘,判斷器件是否正確應(yīng)答 */
                if (I2C_WaitAck() == 0)
                {
                    break;
                }
            }
            if (m  == 1000)
            {
                goto cmd_fail;    /* EEPROM器件寫(xiě)超時(shí) */
            }
        
            /* 第4步:發(fā)送字節(jié)地址,24C02只有256字節(jié),因此1個(gè)字節(jié)就夠了,如果是24C04以上,那么此處需要連發(fā)多個(gè)地址 */
            I2C_SendByte((uint8_t)usAddr);
            
            /* 第5步:發(fā)送ACK */
            if (I2C_WaitAck() != 0)
            {
                goto cmd_fail;    /* EEPROM器件無(wú)應(yīng)答 */
            }
        }
    
        /* 第6步:開(kāi)始寫(xiě)入數(shù)據(jù) */
        I2C_SendByte(_pWriteBuf[i]);
    
        /* 第7步:發(fā)送ACK */
        if (I2C_WaitAck() != 0)
        {
            goto cmd_fail;    /* EEPROM器件無(wú)應(yīng)答 */
        }

        usAddr++;    /* 地址增1 */        
    }
    
    /* 命令執(zhí)行成功,發(fā)送I2C總線停止信號(hào) */
    I2C_Stop();
    return 1;

cmd_fail: /* 命令執(zhí)行失敗后,切記發(fā)送停止信號(hào),避免影響I2C總線上其他設(shè)備 */
    /* 發(fā)送I2C總線停止信號(hào) */
    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;
    }
    
    /* 寫(xiě)EEPROM, 起始地址 = 0,數(shù)據(jù)長(zhǎng)度為 256 */
    if (EEPROM_WriteBytes(buf, 0, EEPROM_SIZE) == 0)
    {
        printf("擦除eeprom出錯(cuò)!\\r\\n");
        return;
    }
    else
    {
        printf("擦除eeprom成功!\\r\\n");
    }
}

/**
  * @brief  EE_Delay
  * @param  nCount
  * @retval None
  */
static void EEPROM_Delay(__IO uint32_t nCount)     //簡(jiǎn)單的延時(shí)函數(shù)
{
    for(; nCount != 0; nCount--);
}
/**
  * @brief  AT24C02 初始化
  * @param  None
  * @retval None
  */
void EEPROM_Init(void)
{
    /*-----------------------------------------------------------------------------------*/  
    if (EEPROM_CheckOk() == 0)
    {
        /* 沒(méi)有檢測(cè)到EEPROM */
        printf("沒(méi)有檢測(cè)到串行EEPROM!\\r\\n");
    }
}

/**
  * @brief  AT24C02 讀寫(xiě)測(cè)試
  * @param  None
  * @retval None
  */
void EEPROM_Test(void)
{
    uint16_t i;
    uint8_t write_buf[EEPROM_SIZE];
    uint8_t read_buf[EEPROM_SIZE];

    /*------------------------------------------------------------------------------------*/  
    /* 填充測(cè)試緩沖區(qū) */
    for (i = 0; i < EEPROM_SIZE; i++)
    {        
        write_buf[i] = i;
    }
    /*------------------------------------------------------------------------------------*/  
    if (EEPROM_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)
    {
        printf("寫(xiě)eeprom出錯(cuò)!\\r\\n");
        return;
    }
    else
    {        
        printf("寫(xiě)eeprom成功!\\r\\n");
    }

    /*寫(xiě)完之后需要適當(dāng)?shù)难訒r(shí)再去讀,不然會(huì)出錯(cuò)*/
    EEPROM_Delay(0x0FFFFF);
    /*-----------------------------------------------------------------------------------*/
    if (EEPROM_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)
    {
        printf("讀eeprom出錯(cuò)!\\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("錯(cuò)誤:EEPROM讀出與寫(xiě)入的數(shù)據(jù)不一致");
            return;
        }
        printf(" %02X", read_buf[i]);

        if ((i & 15) == 15)
        {
            printf("\\r\\n");
        }
    }
    printf("eeprom讀寫(xiě)測(cè)試成功\\r\\n");
}

代碼很簡(jiǎ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測(cè)試?yán)?\\r\\n");
    EEPROM_Init();
    EEPROM_Test();
    while(1)
    {
        led_toggle(LED1);
        delay_ms(1000);
    }
}

主函數(shù)中重點(diǎn)關(guān)注EEPROM_Test()函數(shù),這就是對(duì)AT24C02的讀寫(xiě)操作。

6.2 實(shí)驗(yàn)現(xiàn)象

下載程序,連接串口打印信息如下。

1684633680837vg47h2kfgd

最后,我們使用邏輯分析來(lái)查看數(shù)據(jù)

16846336812501oudsoau2i

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • mcu
    mcu
    +關(guān)注

    關(guān)注

    146

    文章

    17123

    瀏覽量

    350973
  • I2C
    I2C
    +關(guān)注

    關(guān)注

    28

    文章

    1484

    瀏覽量

    123617
  • 開(kāi)發(fā)板
    +關(guān)注

    關(guān)注

    25

    文章

    5032

    瀏覽量

    97371
  • Cortex-M
    +關(guān)注

    關(guān)注

    2

    文章

    229

    瀏覽量

    29752
  • GD32
    +關(guān)注

    關(guān)注

    7

    文章

    403

    瀏覽量

    24326
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    GD32開(kāi)發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)) 1 開(kāi)發(fā)環(huán)境搭建

    設(shè)計(jì)的GD32F207I-EVAL開(kāi)發(fā)板。 GD32F207I-EVAL開(kāi)發(fā)板使用 GD32F207IK作為主控制器,主頻120MHz、集成
    的頭像 發(fā)表于 05-07 23:35 ?1.1w次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開(kāi)發(fā)</b><b class='flag-5'>實(shí)戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>1<b class='flag-5'>章</b> <b class='flag-5'>開(kāi)發(fā)</b>環(huán)境搭建

    GD32開(kāi)發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)) 8 定時(shí)器

    開(kāi)發(fā)環(huán)境: MDK:Keil 5.30 開(kāi)發(fā)板:GD32F207I-EVAL MCU:GD32F207IK 1 PWM輸出的工作原理 脈沖寬度調(diào)制(PWM) ,是英文“Pulse Wi
    的頭像 發(fā)表于 05-12 22:14 ?7924次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開(kāi)發(fā)</b><b class='flag-5'>實(shí)戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>8<b class='flag-5'>章</b> 定時(shí)器

    GD32開(kāi)發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)) 12 ADC

    GD32F2系列有 3 個(gè)逐次逼近型的ADC,精度為 12 位,有18個(gè)多路復(fù)用通道,可以轉(zhuǎn)換來(lái)自16個(gè)外部通道和2個(gè)內(nèi)部通道的模擬信號(hào)。其中ADC0 和 ADC1都有 16 個(gè)外部通道, ADC2
    的頭像 發(fā)表于 05-16 09:03 ?1.1w次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開(kāi)發(fā)</b><b class='flag-5'>實(shí)戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>12<b class='flag-5'>章</b> ADC

    GD32開(kāi)發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)) 14 內(nèi)部溫度傳感器

    GD32 有一個(gè)內(nèi)部的溫度傳感器,可以用來(lái)測(cè)量 CPU 及周?chē)臏囟?TA)。該溫度傳感器在內(nèi)部和 ADCx_IN16 輸入通道相連接,此通道把傳感器輸出的電壓轉(zhuǎn)換成數(shù)字值。溫度傳感器模擬輸入
    的頭像 發(fā)表于 05-17 08:58 ?5334次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開(kāi)發(fā)</b><b class='flag-5'>實(shí)戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>14<b class='flag-5'>章</b> 內(nèi)部溫度傳感器

    GD32開(kāi)發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)) 16 RTC

    開(kāi)發(fā)環(huán)境: MDK:Keil 5.30 開(kāi)發(fā)板:GD32F207I-EVAL MCU:GD32F207IK 1 RTC工作原理 1.1 RTC簡(jiǎn)介
    的頭像 發(fā)表于 05-18 22:14 ?7151次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開(kāi)發(fā)</b><b class='flag-5'>實(shí)戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>16<b class='flag-5'>章</b> RTC

    GD32開(kāi)發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)) 17 看門(mén)狗

    開(kāi)發(fā)環(huán)境: MDK:Keil 5.30 開(kāi)發(fā)板:GD32F207I-EVAL MCU:GD32F207IK GD32 有兩個(gè)看門(mén)狗, 一個(gè)是
    的頭像 發(fā)表于 06-03 16:00 ?1.1w次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開(kāi)發(fā)</b><b class='flag-5'>實(shí)戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>17<b class='flag-5'>章</b> 看門(mén)狗

    【圖書(shū)分享】《STM32庫(kù)開(kāi)發(fā)實(shí)戰(zhàn)指南

    庫(kù)函數(shù)開(kāi)發(fā)小結(jié) 10 DMA——為CPU減負(fù) 11 ADC實(shí)驗(yàn)(DMA方式) 12
    發(fā)表于 03-13 17:01

    GD32 MCU原理及固件庫(kù)開(kāi)發(fā)指南》+讀后感

    ,包括ADC和DAC。 7介紹GD32 MCU的基礎(chǔ)通信外設(shè),包括USART、I2C和SPI。 8
    發(fā)表于 06-06 21:52

    I2C Guid I2C指南

    I2C Guid  I2C指南 The I2C bus is used in a wide rangeof applications because it is simpl
    發(fā)表于 04-23 13:55 ?36次下載

    基于I2C 邏輯選型指南

    基于I2C 邏輯選型指南
    發(fā)表于 09-22 13:30 ?5次下載
    基于<b class='flag-5'>I2C</b> 邏輯選型<b class='flag-5'>指南</b>

    《Linux設(shè)備驅(qū)動(dòng)開(kāi)發(fā)詳解》15、Linux的I2C核心、總線與設(shè)備驅(qū)動(dòng)

    《Linux設(shè)備驅(qū)動(dòng)開(kāi)發(fā)詳解》15、Linux的I2C核心、總線與設(shè)備驅(qū)動(dòng)
    發(fā)表于 10-27 11:19 ?8次下載
    《Linux設(shè)備驅(qū)動(dòng)<b class='flag-5'>開(kāi)發(fā)</b>詳解》<b class='flag-5'>第</b>15<b class='flag-5'>章</b>、Linux的<b class='flag-5'>I2C</b>核心、總線與設(shè)備驅(qū)動(dòng)

    I2C快速指南

    I2C快速指南
    發(fā)表于 04-22 16:51 ?32次下載
    <b class='flag-5'>I2C</b>快速<b class='flag-5'>指南</b>

    GD32開(kāi)發(fā)實(shí)戰(zhàn)指南(基礎(chǔ)) 19 程序加密

    GD32通過(guò)讀取芯片唯一ID號(hào)來(lái)實(shí)現(xiàn)程序的保護(hù),防止被抄襲。96位的產(chǎn)品唯一身份標(biāo)識(shí)所提供的參考號(hào)碼對(duì)任意一個(gè)GD32微控制器
    的頭像 發(fā)表于 05-20 09:10 ?4138次閱讀
    <b class='flag-5'>GD32</b><b class='flag-5'>開(kāi)發(fā)</b><b class='flag-5'>實(shí)戰(zhàn)</b><b class='flag-5'>指南</b>(基礎(chǔ)<b class='flag-5'>篇</b>) <b class='flag-5'>第</b>19<b class='flag-5'>章</b> 程序加密

    Rockchip I2C開(kāi)發(fā)指南

    電子發(fā)燒友網(wǎng)站提供《Rockchip I2C開(kāi)發(fā)指南.pdf》資料免費(fèi)下載
    發(fā)表于 09-15 15:33 ?0次下載
    Rockchip <b class='flag-5'>I2C</b><b class='flag-5'>開(kāi)發(fā)指南</b>

    I2C基本指南

    電子發(fā)燒友網(wǎng)站提供《I2C基本指南.pdf》資料免費(fèi)下載
    發(fā)表于 09-10 09:40 ?0次下載
    <b class='flag-5'>I2C</b>基本<b class='flag-5'>指南</b>
    RM新时代网站-首页