當(dāng)前位置:首頁 > IT技術(shù) > 系統(tǒng)服務(wù) > 正文

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)
2021-12-13 17:55:08

@[TOC]

12 I2C編程應(yīng)用開發(fā)

? I2C(Inter-Integrated Circuit BUS)是I2C BUS簡稱,中文為集成電路總線,是目前應(yīng)用最廣泛的總線之一。和IMX6ULL有些相關(guān)的是,剛好該總線是NXP前身的PHILIPS設(shè)計(jì)。

12.1 I2C協(xié)議

12.1.1 概述

? I2C是一種串行通信總線,使用多主從架構(gòu),最初設(shè)計(jì)目的為了讓主板、嵌入式系統(tǒng)或手機(jī)用來連接低速周邊設(shè)備。多用于小數(shù)據(jù)量的場(chǎng)合,有傳輸距離短,任意時(shí)刻只能有一個(gè)主機(jī)等特性。嚴(yán)格意義上講,I2C應(yīng)該是軟硬件結(jié)合體,所以我們將分物理層和協(xié)議層來介紹該總線。

? I2C總線結(jié)構(gòu)如下圖:

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

? 傳輸數(shù)據(jù)時(shí),我們需要發(fā)數(shù)據(jù),從主設(shè)備發(fā)送到從設(shè)備上去;也需要把數(shù)據(jù)從從設(shè)備傳送到主設(shè)備上去,數(shù)據(jù)涉及到雙向傳輸。

? 對(duì)于I2C通信的過程,下面使用一個(gè)形象的生活例子進(jìn)行類比。

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

? 體育老師:可以把球發(fā)給學(xué)生,也可以把球從學(xué)生中接過來。

① 發(fā)球:

  • a. 老師說:注意了(start);
  • b. 老師對(duì)A學(xué)生說,我要球發(fā)給你(A就是地址);
  • c. 老師就把球發(fā)出去了(傳輸);
  • d. A收到球之后,應(yīng)該告訴老師一聲(回應(yīng));
  • e. 老師說下課(停止)。

② 接球:

  • a. 老師說注意了(start);

  • b. 老師說:B把球發(fā)給我(B是地址);

  • c. B就把球發(fā)給老師(傳輸);

  • d. 老師收到球之后,給B說一聲,表示收到球了(回應(yīng));

  • e. 老師說下課(停止)。

我們就使用這個(gè)簡單的例子,來解釋一下I2C的傳輸協(xié)議:

① 老師說注意了,表示開始信號(hào)(start)

② 老師告訴某個(gè)學(xué)生,表示發(fā)送地址(address)

③ 老師發(fā)球/接球,表示數(shù)據(jù)的傳輸

④ 老師/學(xué)生收到球,回應(yīng)表示:回應(yīng)信號(hào)(ACK)

⑤ 老師說下課,表示I2C傳輸接受(P)

12.2.2 物理層

1) 特性1:半雙工(非全雙工)

? I2C總線中只使用兩條線路:SDA、SCL。

① SDA(串行數(shù)據(jù)線):

? 主芯片通過一根SDA線既可以把數(shù)據(jù)發(fā)給從設(shè)備,也可以從SDA上讀取數(shù)據(jù)。在I2C設(shè)備內(nèi)部有兩個(gè)引腳(發(fā)送引腳/接受引腳),它們都連接到外部的SDA線上,具體可以參考下圖device端里面的I2Cn_SDA(output/input)。

② SCL(串行時(shí)鐘線):

? I2C主設(shè)備發(fā)出時(shí)鐘,從設(shè)備接收時(shí)鐘。

? SDA和SCL引腳的內(nèi)部電路結(jié)構(gòu)一致,引腳的輸出驅(qū)動(dòng)與輸入緩沖連在一起。其中輸出為漏極開路的場(chǎng)效應(yīng)管、輸入緩沖為一只高輸入阻抗的同相器。這樣結(jié)構(gòu)有如下特性:

a. 由于 SDA、SCL 為漏極開路結(jié)構(gòu),借助于外部的上拉電阻實(shí)現(xiàn)了信號(hào)的“線與”邏輯;

b. 引腳在輸出信號(hào)的同時(shí)還作用輸入信號(hào)供內(nèi)部進(jìn)行檢測(cè),當(dāng)輸出與輸入不一致時(shí),就表示有問題發(fā)生了。這為 “時(shí)鐘同步”和“總線仲裁”提供硬件基礎(chǔ)。

? SDA和CLK連接線上連有兩個(gè)上拉電阻,當(dāng)總線空閑時(shí),兩根線均為高電平。連到總線上的任一器件輸出的低電平,都將使總線的信號(hào)變低。

? 物理層連接如下圖所示:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-kyu89PWq-1639020220827)(http://photos.100ask.net/NewHomeSite/IIC_Image003.png)]

2) 特性2:地址和角色可配置

? 每個(gè)連接到總線的器件都可以通過唯一的地址和其它器件通信,主機(jī)/從機(jī)角色和地址可配置,主機(jī)可以作為主機(jī)發(fā)送器和主機(jī)接收器。

3) 特性3:多主機(jī)

? I2C是真正的多主機(jī)總線,I2C設(shè)備可以在通訊過程轉(zhuǎn)變成主機(jī)。如果兩個(gè)或更多的主機(jī)同時(shí)請(qǐng)求總線,可以通過沖突檢測(cè)和仲裁防止總線數(shù)據(jù)被破壞。

4) 特性4:傳輸速率

? 傳輸速率在標(biāo)準(zhǔn)模式下可以達(dá)到100kb/s,快速模式下可以達(dá)到400kb/s。

5) 特性5:負(fù)載和距離

? 節(jié)點(diǎn)的最大數(shù)量受限于地址空間以及總線電容決定,另外總電容也限制了實(shí)際通信距離只有幾米。

12.2.3 協(xié)議層

1) 數(shù)據(jù)有效性

? I2C協(xié)議的數(shù)據(jù)有效性是靠時(shí)鐘來保證的,在時(shí)鐘的高電平周期內(nèi),SDA線上的數(shù)據(jù)必須保持穩(wěn)定。數(shù)據(jù)線僅可以在時(shí)鐘SCL為低電平時(shí)改變。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-WQhqSPjW-1639020220828)(http://photos.100ask.net/NewHomeSite/IIC_Image004.png)]

2) 起始和結(jié)束條件

起始條件:當(dāng)SCL為高電平的時(shí)候,SDA線上由高到低的跳變被定義為起始條件。

結(jié)束條件:當(dāng)SCL為高電平的時(shí)候,SDA線上由低到高的跳變被定義為停止條件。

? 要注意起始和終止信號(hào)都是由主機(jī)發(fā)出的,連接到I2C總線上的器件,若具有I2C總線的硬件接口,則很容易檢測(cè)到起始和終止信號(hào)。

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

? 總線在起始條件之后,視為忙狀態(tài),在停止條件之后被視為空閑狀態(tài)。

3) 應(yīng)答

? 每當(dāng)主機(jī)向從機(jī)發(fā)送完一個(gè)字節(jié)的數(shù)據(jù),主機(jī)總是需要等待從機(jī)給出一個(gè)應(yīng)答信號(hào),以確認(rèn)從機(jī)是否成功接收到了數(shù)據(jù),從機(jī)應(yīng)答主機(jī)所需要的時(shí)鐘仍是主機(jī)提供的,應(yīng)答出現(xiàn)在每一次主機(jī)完成8個(gè)數(shù)據(jù)位傳輸后緊跟著的時(shí)鐘周期,低電平0表示應(yīng)答,1表示非應(yīng)答。

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

? SDA線上每個(gè)字節(jié)必須是8位長,在每個(gè)傳輸(transfer)中所傳輸字節(jié)數(shù)沒有限制,每個(gè)字節(jié)后面必須跟一個(gè)ACK。8位數(shù)據(jù)中,先傳輸最高有效位(MSB)傳輸。

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

12.2 在linux系統(tǒng)下操作I2C總線的外設(shè)

12.2.1 概述

? 下圖是在linux系統(tǒng)環(huán)境里操作i2c總線上的外設(shè)流程框圖。我們按照從下向上的順序研究一下該流程中各個(gè)角色的功能。

? 在硬件層中,I2C硬件總線只有兩條線路,上面可以掛載多個(gè)I2C-device,這些I2C-device有的在I2C總線里充當(dāng)主機(jī)的角色,一般情況該主機(jī)為板子上的主cpu中的I2C控制器,比如我們用的100ask_imx6UL板子,這個(gè)I2C主機(jī)就是imx6中的I2C控制器模塊;其他的I2C-device在I2C總線里充當(dāng)從機(jī)的角色,通常這些從機(jī)是板子上完成特定功能的傳感器外設(shè),只不過該外設(shè)與主控cpu的通信方式是只需要兩條線路的I2C總線,比如在我們的100ask_imx6UL板子中就有eeprom和AP3216兩個(gè)外設(shè),它們?cè)贗2C總線中充當(dāng)?shù)亩际荌2C從機(jī)的角色,它們和主控芯片imx6中的I2C控制器1都是以并聯(lián)的方式掛在這個(gè)I2C總線上。

? 在內(nèi)核中,驅(qū)動(dòng)程序?qū)ο乱瓿蒊2C總線上的I2C通信協(xié)議,收集硬件傳感器的I2C數(shù)據(jù)并封裝成標(biāo)準(zhǔn)的linux操作接口供用戶空間的應(yīng)用程序操作。對(duì)上要實(shí)現(xiàn)可以通過linux程序把數(shù)據(jù)流組織成I2C協(xié)議下發(fā)到硬件層的相應(yīng)的外設(shè)傳感器中。

? 在用戶空間的應(yīng)用程序中,應(yīng)用工程師完全可以不必理會(huì)I2C協(xié)議的詳細(xì)規(guī)定。只需要按照驅(qū)動(dòng)層提供給我們的操作I2C外設(shè)的操作接口函數(shù)就可以像操作linux中其他普通設(shè)備文件那樣輕松的操作I2C外設(shè)了。

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

12.2.2 簡述I2C的linux驅(qū)動(dòng)

? I2C在linux內(nèi)核層的驅(qū)動(dòng)框架主要由三部分組成:

1) I2C核心層:

? I2C核心提供了I2C總線驅(qū)動(dòng)和設(shè)備驅(qū)動(dòng)的注冊(cè)、注銷方法,I2C通信方法(algorithm)的上層部分,并且還提供了一系列與具體硬件平臺(tái)無關(guān)的接口函數(shù)以及探測(cè)設(shè)備,檢測(cè)設(shè)備地址的上層代碼等。它位于內(nèi)核源碼目錄下的drivers/i2c/i2c-core.c文件中,是I2C總線驅(qū)動(dòng)和設(shè)備驅(qū)動(dòng)之間依賴于I2C核心作為紐帶。

? I2C核心中的主要函數(shù)包括:

? 增加/刪除i2c_adapter

int i2c_add_adapter(struct i2c_adapter *adap);
int i2c_del_adapter(struct i2c_adapter *adap);

? 增加/刪除i2c_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
inline int i2c_add_driver(struct i2c_driver *driver);

? i2c_client依附/脫離

int i2c_attach_client(struct i2c_client *client);
int i2c_detach_client(struct i2c_client *client);

? i2c傳輸、發(fā)送和接收

int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);

? 用于進(jìn)行I2C適配器和I2C設(shè)備之間的一組消息交互。其本身不具備驅(qū)動(dòng)適配器物理硬件完成消息交互的能力,它只是尋找到i2c_adapter對(duì)應(yīng)的i2c_algorithm,并使用i2c_algorithm的master_xfer()函數(shù)真正驅(qū)動(dòng)硬件流程。

int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
int i2c_master_recv(struct i2c_client *client, char *buf ,int count);

? i2c_master_send()和i2c_master_recv()函數(shù)內(nèi)部會(huì)調(diào)用i2c_transfer()函數(shù)分別完成一條寫消息和一條讀消息。

a) I2C控制命令分派

? 下面函數(shù)有助于將發(fā)給I2C適配器設(shè)備文件ioctl的命令分派給對(duì)應(yīng)適配器的algorithm的algo_control()函數(shù)或i2c_driver的command()函數(shù):

int i2c_control(struct i2c_client *client, unsigned int cmd, unsigned long arg);
void i2c_clients_command(struct i2c_adapter *adap, unsigned int cmd, void *arg);

2) I2C總線驅(qū)動(dòng)層:

? I2C總線驅(qū)動(dòng)是對(duì)I2C硬件體系結(jié)構(gòu)中適配器端的實(shí)現(xiàn),適配器可由CPU控制,甚至可以直接集成在CPU內(nèi)部。

? 它主要完成的功能有:

a) 初始化I2C適配器所使用的硬件資源,申請(qǐng)I/O地址、中斷號(hào)等。

b) 通過i2c_add_adapter()添加i2c_adapter的數(shù)據(jù)結(jié)構(gòu),當(dāng)然這個(gè)i2c_adapter數(shù)據(jù)結(jié)構(gòu)的成員已經(jīng)被xxx適配器的相應(yīng)函數(shù)指針?biāo)跏蓟?/p>

c) 釋放I2C適配器所使用的硬件資源,釋放I/O地址、中斷號(hào)等。

d) 通過i2c_del_adapter()刪除i2c_adapter的數(shù)據(jù)結(jié)構(gòu)。

3) I2C總線驅(qū)動(dòng)層:

? I2C設(shè)備驅(qū)動(dòng)(也稱為客戶驅(qū)動(dòng))是對(duì)I2C硬件體系結(jié)構(gòu)中設(shè)備端的實(shí)現(xiàn),設(shè)備一般掛接在受CPU控制的I2C適配器上,通過I2C適配器與CPU交換數(shù)據(jù)。I2C設(shè)備驅(qū)動(dòng)模塊加載函數(shù)通用的方法是在I2C設(shè)備驅(qū)動(dòng)模塊加載函數(shù)中完成兩件事:通過register_chrdev()函數(shù)將I2C設(shè)備注冊(cè)為一個(gè)字符設(shè)備。通過I2C核心的i2c_add_driver()函數(shù)添加i2c_driver。

12.3 在linux應(yīng)用層使用I2C

? 前面我們講解了I2C的協(xié)議及在linux驅(qū)動(dòng)框架,那么當(dāng)你拿到開發(fā)板或者是從公司的硬件同事拿到一個(gè)帶有I2C外設(shè)的板子,我們應(yīng)該如何最快速的使用起來這個(gè)I2C設(shè)備呢?既然我們總是說這個(gè)I2C總線在嵌入式開發(fā)中被廣泛的使用,那么是否有現(xiàn)成的測(cè)試工具幫我們完成這個(gè)快速使用板子的I2C設(shè)備呢?答案是有的,而且這個(gè)測(cè)試工具的代碼還是開源的,它被廣泛的應(yīng)用在linux應(yīng)用層來快速驗(yàn)證I2C外設(shè)是否可用,為我們測(cè)試I2C設(shè)備提供了很好的捷徑。

12.3.1 如何使用I2C tools測(cè)試I2C外設(shè)

1) I2C tools概述:

? I2C tools包含一套用于Linux應(yīng)用層測(cè)試各種各樣I2C功能的工具。它的主要功能包括:總線探測(cè)工具、SMBus訪問幫助程序、EEPROM解碼腳本、EEPROM編程工具和用于SMBus訪問的python模塊。只要你所使用的內(nèi)核中包含I2C設(shè)備驅(qū)動(dòng),那么就可以在你的板子中正常使用這個(gè)測(cè)試工具。

2) 下載I2C tools源碼:

? 前面我們已經(jīng)說過了這個(gè)I2C tools工具是開源的,那么這個(gè)源碼在哪里可以找到呢?

? 下載方法一:直接在內(nèi)核的網(wǎng)站https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/下載I2C tools代碼的壓縮包。

? 下載方法二:利用git管理工具下載這個(gè)I2C tools的源代碼,命令為git clone git://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git強(qiáng)烈建議讀者采用第二種方法下載這個(gè)代碼,因?yàn)槟憧梢酝ㄟ^git快速地了解這個(gè)開源代碼的不同版本的功能改進(jìn)及bug修復(fù),而且使用git開發(fā)也是作為一名優(yōu)秀的開發(fā)人員必備的一項(xiàng)技能。

3) 編譯I2C tools源碼:

? 進(jìn)入剛才利用git下載好的iic-tools源碼目錄,修改編譯工具為你當(dāng)前使用的交叉編譯工具:

26  CC ?= arm-linux-gnueabihf-gcc
27  AR ?= arm-linux-gnueabihf-ar

? 編譯源碼:如果你想編譯靜態(tài)版本,你可以輸入命令:make USE_STATIC_LIB=1;如果使用動(dòng)態(tài)庫的話,可以直接輸入make進(jìn)行編譯。安裝命令為:make install,如果你想要讓最后生成的二進(jìn)制文件最小的話,可以在“make install”之前運(yùn)行“make strip”。但是,這將不能生成任何調(diào)試庫,也就不能嘗試進(jìn)一步調(diào)試。然后將tools目錄下的5個(gè)可執(zhí)行文件i2cdetect,i2cdump,i2cget,i2cset和i2ctransfer復(fù)制到板子的/usr/sbin/中;將lib目錄下的libi2c.so.0.1.1文件復(fù)制到板子的/usr/lib/libi2c.so.0。之后別忘了將上面的文件修改為可執(zhí)行的權(quán)限。

4) 介紹I2C tools各功能之—i2cdetect

? i2cdetect的主要功能就是I2C設(shè)備查詢,它用于掃描I2C總線上的設(shè)備。它輸出一個(gè)表,其中包含指定總線上檢測(cè)到的設(shè)備的列表。

? 該命令的常用格式為:i2cdetect [-y] [-a] [-q|-r] i2cbus [first last]。具體參數(shù)的含義如下:

-y 取消交互模式。默認(rèn)情況下,i2cdetect將等待用戶的確認(rèn),<br> 當(dāng)使用此標(biāo)志時(shí),它將直接執(zhí)行操作。
-a 強(qiáng)制掃描非規(guī)則地址。一般不推薦。
-q 使用SMBus“快速寫入”命令進(jìn)行探測(cè)。一般不推薦。
-r 使用SMBus“接收字節(jié)”命令進(jìn)行探測(cè)。一般不推薦。
-F 顯示適配器實(shí)現(xiàn)的功能列表并退出。
-V 顯示I2C工具的版本并推出。
-l 顯示已經(jīng)在系統(tǒng)中使用的I2C總線。
i2cbus 表示要掃描的I2C總線的編號(hào)或名稱。
first last 表示要掃描的從設(shè)備地址范圍。

? 該功能的常用方式:

? 第一,先通過i2cdetect -l查看當(dāng)前系統(tǒng)中的I2C的總線情況:

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

? 第二,若總線上掛載I2C從設(shè)備,可通過i2cdetect掃描某個(gè)I2C總線上的所有設(shè)備??赏ㄟ^控制臺(tái)輸入i2cdetect -y 1:(其中"--"表示地址被探測(cè)到了,但沒有芯片應(yīng)答; "UU"因?yàn)檫@個(gè)地址目前正在被一個(gè)驅(qū)動(dòng)程序使用,探測(cè)被省略;而16進(jìn)制的地址號(hào)60,1e和50則表示發(fā)現(xiàn)了一個(gè)外部片選從地址為0x60,0x1e(AP3216)和0x50(eeprom)的外設(shè)芯片。

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

? 第三,查詢I2C總線1 (I2C -1)的功能,命令為i2cdetect -F 1:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-pCijYzNc-1639020220832)(http://photos.100ask.net/NewHomeSite/IIC_Image011.png)]

5) 介紹I2C tools各功能之—i2cget

? i2cget的主要功能是獲取I2C外設(shè)某一寄存器的內(nèi)容。該命令的常用格式為:

? i2cget [-f] [-y] [-a] i2cbus chip-address [data-address [mode]]。具體參數(shù)的含義如下:

-f 強(qiáng)制訪問設(shè)備,即使它已經(jīng)很忙。 默認(rèn)情況下,i2cget將拒絕訪問<br> 已經(jīng)在內(nèi)核驅(qū)動(dòng)程序控制下的設(shè)備。
-y 取消交互模式。默認(rèn)情況下,i2cdetect將等待用戶的確認(rèn),當(dāng)使用此<br/> 標(biāo)志時(shí),它將直接執(zhí)行操作。
-a 允許在0x00 - 0x07和0x78 - 0x7f之間使用地址。一般不推薦。
i2cbus 表示要掃描的I2C總線的編號(hào)或名稱。這個(gè)數(shù)字應(yīng)該與i2cdetect -l列出<br/> 的總線之一相對(duì)應(yīng)。
chip-address 要操作的外設(shè)從地址。
data-address 被查看外設(shè)的寄存器地址。
mode 顯示數(shù)據(jù)的方式: b (read byte data, default) w (read word data) <br/> c (write byte/read byte)

? 下面是完成讀取0總線上從地址為0x50的外設(shè)的0x10寄存器的數(shù)據(jù),命令為:

? i2cget -y -f 0 0x50 0x10

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

6) 介紹I2C tools各功能之—i2cdump

? i2cdump的主要功能查看I2C從設(shè)備器件所有寄存器的值。 該命令的常用格式為:i2cdump [-f] [-r first-last] [-y] [-a] i2cbus address [mode [bank [bankreg]]]。具體參數(shù)的含義如下:

-f 強(qiáng)制訪問設(shè)備,即使它已經(jīng)很忙。 默認(rèn)情況下,i2cget將拒絕訪問已經(jīng)在<br/>內(nèi)核驅(qū)動(dòng)程序控制下的設(shè)備。
-r 限制正在訪問的寄存器范圍。 此選項(xiàng)僅在模式b,w,c和W中可用。對(duì)于<br/>模式W,first必須是偶數(shù),last必須是奇數(shù)。
-y 取消交互模式。默認(rèn)情況下,i2cdetect將等待用戶的確認(rèn),當(dāng)使用此標(biāo)志<br/>時(shí),它將直接執(zhí)行操作。
-V 顯示I2C工具的版本并推出。
i2cbus 表示要掃描的I2C總線的編號(hào)或名稱。這個(gè)數(shù)字應(yīng)該對(duì)應(yīng)于i2cdetect -l列<br/>出的總線之一。
first last 表示要掃描的從設(shè)備地址范圍。
mode b: 單個(gè)字節(jié) w:16位字 s:SMBus模塊 i:I2C模塊的讀取大小 c: 連續(xù)讀<br/>取所有字節(jié),對(duì)于具有地址自動(dòng)遞增功能的芯片(如EEPROM)非常有用。<br/>W與 w類似,只是讀命令只能在偶數(shù)寄存器地址上發(fā)出;這也是主要用于EEPROM的。

? 下面是完成讀取0總線上從地址為0x50的eeprom的數(shù)據(jù),命令為:

? i2cdump -f -y 0 0x50

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

7) 介紹I2C tools各功能之—i2cset

? i2cset的主要功能是通過I2C總線設(shè)置設(shè)備中某寄存器的值。該命令的常用格式為:

? i2cset [-f] [-y] [-m mask] [-r] i2cbus chip-address data-address [value] ...[mode]

具體參數(shù)的含義如下:

-f 強(qiáng)制訪問設(shè)備,即使它已經(jīng)很忙。 默認(rèn)情況下,i2cget將拒絕訪問已<br/>經(jīng)在內(nèi)核驅(qū)動(dòng)程序控制下的設(shè)備。
-r 在寫入值之后立即讀取它,并將結(jié)果與寫入的值進(jìn)行比較。
-y 取消交互模式。默認(rèn)情況下,i2cdetect將等待用戶的確認(rèn),當(dāng)使用此標(biāo)<br/>志時(shí),它將直接執(zhí)行操作。
-V 顯示I2C工具的版本并推出。
i2cbus 表示要掃描的I2C總線的編號(hào)或名稱。這個(gè)數(shù)字應(yīng)該對(duì)應(yīng)于i2cdetect -l列<br/>出的總線之一。
-m mask 如果指定mask參數(shù),那么描述哪些value位將是實(shí)際寫入data-addres的。<br/>掩碼中設(shè)置為1的位將從值中取出,而設(shè)置為0的位將從數(shù)據(jù)地址中讀取,從<br/>而由操作保存。
mode b: 單個(gè)字節(jié) w:16位字 s:SMBus模塊 i:I2C模塊的讀取大小 c: 連續(xù)讀<br/>取所有字節(jié),對(duì)于具有地址自動(dòng)遞增功能的芯片(如EEPROM)非常有用。<br/> W與 w類似,只是讀命令只能在偶數(shù)寄存器地址上發(fā)出;這也是主要用于<br/>EEPROM的。

? 下面是完成向0總線上從地址為0x50的eeprom的0x10寄存器寫入0x55,命令為:

? i2cset -y -f 0 0x50 0x10 0x55

? 然后用i2cget讀取0總線上從地址為0x50的eeprom的0x10寄存器的數(shù)據(jù),命令為:i2cget -y -f 0 0x50 0x10

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

8) 介紹I2C tools各功能之—i2ctransfer

? i2ctransfer的主要功能是在一次傳輸中發(fā)送用戶定義的I2C消息。i2ctransfer是一個(gè)創(chuàng)建I2C消息并將其合并為一個(gè)傳輸發(fā)送的程序。對(duì)于讀消息,接收緩沖區(qū)的內(nèi)容被打印到stdout,每個(gè)讀消息一行。

? 該命令的常用格式為:i2ctransfer [-f] [-y] [-v] [-a] i2cbus desc [data] [desc [data]]

? 具體參數(shù)的含義如下:

-f 強(qiáng)制訪問設(shè)備,即使它已經(jīng)很忙。 默認(rèn)情況下,i2cget將拒絕訪問已<br/>經(jīng)在內(nèi)核驅(qū)動(dòng)程序控制下的設(shè)備。
-y 取消交互模式。默認(rèn)情況下,i2cdetect將等待用戶的確認(rèn),當(dāng)使用此<br/>標(biāo)志時(shí),它將直接執(zhí)行操作。
-v 啟用詳細(xì)輸出。它將打印所有信息發(fā)送,即不僅為讀消息,也為寫消息。
-V 顯示I2C工具的版本并推出。
-a 允許在0x00 - 0x02和0x78 - 0x7f之間使用地址。一般不推薦。
i2cbus 表示要掃描的I2C總線的編號(hào)或名稱。這個(gè)數(shù)字應(yīng)該對(duì)應(yīng)于i2cdetect -l<br/>列出的總線之一。

? 下面是完成向0總線上從地址為0x50的eeprom的0x20開始的4個(gè)寄存器寫入0x01,0x02,0x03,0x04命令為:i2ctransfer -f -y 0 w5@0x50 0x20 0x01 0x02 0x03 0x04然后再通過命令i2ctransfer -f -y 0 w1@0x50 0x20 r4將0x20地址的4個(gè)寄存器數(shù)據(jù)讀出來,見下圖:

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

12.3.2 在linux應(yīng)用程序中讀寫I2C外設(shè)

? 首先通過前面的介紹,我們已經(jīng)知道站在cpu的角度來看,操作I2C外設(shè)實(shí)際上就是通過控制cpu中掛載該I2C外設(shè)的I2C控制器,而這個(gè)I2C控制器在linux系統(tǒng)中被稱為“I2C適配器”,這個(gè)已經(jīng)在驅(qū)動(dòng)簡介中介紹過了。而且眾所周知,在linux系統(tǒng)中,每一個(gè)設(shè)備都是以文件的形式存在的,所以在linux中操作I2C外設(shè)就變成了操作I2C適配器設(shè)備文件。Linux系統(tǒng)(也就是內(nèi)核)為每個(gè)I2C適配器生成了一個(gè)主設(shè)備號(hào)為89的設(shè)備節(jié)點(diǎn)(次設(shè)備號(hào)為0-255),它并沒有針對(duì)特定的I2C外設(shè)而設(shè)計(jì),只是提供了通用的read(),write(),和ioctl()等文件操作接口,在用戶空間的應(yīng)用層就可以借用這些接口訪問掛接在適配器上的I2C設(shè)備的存儲(chǔ)空間或寄存器,并控制I2C設(shè)備的工作方式。

? 操作流程:

1) 確定I2C適配器的設(shè)備文件節(jié)點(diǎn)

? i2c適配器的設(shè)備節(jié)點(diǎn)是/dev/i2c-x,其中x是數(shù)字。由于適配器編號(hào)是動(dòng)態(tài)分配的(和注冊(cè)次序有關(guān)),所以想了解哪一個(gè)適配器對(duì)應(yīng)什么編號(hào),可以查看/sys/class/i2c-dev/目錄下的文件內(nèi)容(在這里筆者強(qiáng)烈建議讀者好好利用好sys文件系統(tǒng)):

cat /sys/class/i2c-dev/i2c-0/name
cat /sys/class/i2c-dev/i2c-1/name

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

? 然后查看硬件原理圖中eeprom是掛在cpu的i2c1控制器中了,然后查看IMX6UL芯片手冊(cè)中I2C1的寄存器地址為21A_0000。

Linux應(yīng)用開發(fā)【第十二章】I2C編程應(yīng)用開發(fā)

? 比對(duì)后,我們就很容易知道eeprom外設(shè)對(duì)應(yīng)的I2C控制器的設(shè)備節(jié)點(diǎn)為:/dev/i2c-0。

2) 打開適配器對(duì)應(yīng)的設(shè)備節(jié)點(diǎn)

? 當(dāng)用戶打開適配器設(shè)備節(jié)點(diǎn)的時(shí)候,Kernel中的i2c-dev代碼為其建立一個(gè)i2c_client,但是這個(gè)i2c_client并不加到i2c_adapter的client鏈表當(dāng)中。當(dāng)用戶關(guān)閉設(shè)備節(jié)點(diǎn)時(shí),它自動(dòng)被釋放。

3) IOCTL控制

? 這個(gè)可以參考內(nèi)核源碼中的include/linux/i2c-dev.h文件。下面舉例說明主要的IOCTL命令:

I2C_SLAVE_FORCE 設(shè)置I2C從設(shè)備地址(只有在該地址空閑的情況下成功)
I2C_SLAVE_FORCE 強(qiáng)制設(shè)置I2C從設(shè)備地址(無論內(nèi)核中是否已有驅(qū)動(dòng)在使用<br/>這個(gè)地址都會(huì)成功)
I2C_TENBIT 選擇地址位長: 0 表示是7bit地址 ; 不等于0 就是10 bit的<br/>地址。只有適配器支持I2C_FUNC_10BIT_ADDR,這個(gè)請(qǐng)求才是有效的。
I2C_FUNCS 獲取適配器支持的功能,詳細(xì)的可以參考文件include/linux/i2c.h
I2C_RDWR 設(shè)置為可讀寫
I2C_RETRIES 設(shè)置收不到ACK時(shí)的重試次數(shù)
I2C_TIMEOUT 設(shè)置超時(shí)的時(shí)限

4) 使用I2C協(xié)議和設(shè)備進(jìn)行通信

? 代碼為:ioctl(file,I2C_RDWR,(struct i2c_rdwr_ioctl_data )msgset); 它可以進(jìn)行連續(xù)的讀寫,中間沒有間歇。只有當(dāng)適配器支持I2C_FUNC_I2C此命令才有效。參數(shù)msgset是一個(gè)指針,指向一個(gè)i2c_rdwr_ioctl_data類型的結(jié)構(gòu)體,該結(jié)構(gòu)體的功能就是讓應(yīng)用程序可以向內(nèi)核傳遞消息,其成員包括:struct i2c_msg __ user msgs; 和表示i2c_msgs 個(gè)數(shù)的 __u32 nmsgs,它也決定了在硬件I2C總線的硬件通信中有多少個(gè)開始信號(hào)。由于I2C適配器與外設(shè)通信是以消息為單位的,所以struct i2c_msg對(duì)我們來說是非常重要的,它可以包含多條消息,而一條消息有可能包含多個(gè)數(shù)據(jù),比如對(duì)于eeprom頁寫就包含多個(gè)數(shù)據(jù)。下面就介紹一下這個(gè)結(jié)構(gòu)體的內(nèi)容:

__u16 addr; 從設(shè)備地址
__u16 flags; 標(biāo)志(讀/寫)
I2C_M_TEN 這是一個(gè)10位芯片地址
I2C_M_RD 從設(shè)備到適配器讀數(shù)據(jù)
I2C_M_NOSTART 不發(fā)送起始位
I2C_M_REV_DIR_ADDR 翻轉(zhuǎn)讀寫標(biāo)志
I2C_M_IGNORE_NAK 忽略I2C的NACK信號(hào)
I2C_M_NO_RD_ACK 讀操作的時(shí)候不發(fā)ACK信號(hào)
I2C_M_RECV_LEN 第一次接收數(shù)據(jù)的長度
__u16 len; 寫入或者讀出數(shù)據(jù)的個(gè)數(shù)(字節(jié))
__u8 *buf; 寫入或者讀出數(shù)據(jù)的地址 buf[0]。 注意:千萬不要忘記給 2c_rdwr_ioctl_data結(jié)構(gòu)體中的最重要的結(jié)構(gòu)i2c_msg中的buf分配內(nèi)存。

5) 用read和write讀寫I2C設(shè)備

? 當(dāng)然你可以使用read()/write()來與I2C設(shè)備進(jìn)行通信,代碼如下(以eeprom為例簡要概述操作過程):

? 第一,打開I2C控制器文件節(jié)點(diǎn): fd =open(“/dev/i2c-0”, O_RDWR);

? 第二,設(shè)置eeprom的設(shè)備地址:ioctl(fd,I2C_SLAVE, 0x50);

? 第三,向eeprom寫數(shù)據(jù):

首先將要操作的eeprom的第一個(gè)寄存器地址賦給寫buf的第0個(gè)元素wr_buf[0] = 0x10;

然后把要寫入的數(shù)據(jù)寫入到后面的buf中for(i=1;i<13;i++) wr_buf[i]=i;

最后通過write函數(shù)完成向eeprom寫數(shù)據(jù)的功能:write(fd, wr_buf, 13);

? 最后延遲1秒,讓后面的操作與上面的寫操作分開。

? 第四,從eeprom讀數(shù)據(jù):
首先和寫操作一樣,將要操作的寄存器首地址0x10發(fā)給eeprom:write(fd, wr_buf, 1);
從0x10寄存器地址處讀取12個(gè)字節(jié)的數(shù)據(jù):ret=read(fd, rd_buf, 12);

? 你會(huì)發(fā)現(xiàn),用read和write一次只能進(jìn)行一個(gè)方向的傳輸:或者是讀外設(shè)操作,或者就是寫操作傳輸。

? 代碼如下:

01 #include <stdio.h>
02 #include <sys/ioctl.h>
03 #include <unistd.h>
04 #include <fcntl.h>
05 #include <linux/i2c-dev.h>
06 #include <linux/i2c.h>
07  
08 /* eeprom所對(duì)應(yīng)的I2C控制器的設(shè)備節(jié)點(diǎn) */ 
09 #define EEPROM_DEVICE        "/dev/i2c-0"    
10 
11 /* eeprom的I2C設(shè)備地址 */
12 #define EEPROM_ADDR    0x50
13 
14 
15 int main()
16 {
17  int fd,i,ret=0;
18  unsigned char w_add=0x10;
19  
20  /* 將要讀取的數(shù)據(jù)buf*/
21  unsigned char rd_buf[13] = {0x10};  
22  
23  /* 要寫的數(shù)據(jù)buf,第0個(gè)元素是要操作eeprom的寄存器地址*/
24  unsigned char wr_buf[13] = {0};     
25 
26  printf("hello,this is read_write i2c test 
");
27  
28  /* 打開eeprom對(duì)應(yīng)的I2C控制器文件 */
29  fd =open(EEPROM_DEVICE, O_RDWR);
30  if (fd< 0) 
31  {
32      printf("open"EEPROM_DEVICE"failed 
");
33  }
34 
35  /*設(shè)置eeprom的I2C設(shè)備地址*/
36  if (ioctl(fd,I2C_SLAVE_FORCE, EEPROM_ADDR) < 0) 
37  {            
38      printf("set slave address failed 
");
39  }
40  
41  /* 將要操作的寄存器首地址賦給wr_buf[0] */
42  wr_buf[0] = w_add;      
43 
44  /* 把要寫入的數(shù)據(jù)寫入到后面的buf中 */
45  for(i=1;i<13;i++)
46      wr_buf[i]=i;
47 
48  /* 通過write函數(shù)完成向eeprom寫數(shù)據(jù)的功能 */
49  write(fd, wr_buf, 13);
50 
51  /* 延遲一段時(shí)間 */
52  sleep(1);
53  
54  /*重新開始下一個(gè)操作,先寫寄存器的首地址*/
55  write(fd, wr_buf, 1);
56 
57  /* 從wr_buf[0] = w_add的寄存器地址開始讀取12個(gè)字節(jié)的數(shù)據(jù) */
58  ret=read(fd, rd_buf, 12);
59  printf("ret is %d 
",ret);
60 
61  for(i=0;i<12;i++)
62  {
63      printf("rd_buf is :%d
",rd_buf[i]);
64  }
65  
66  /* 完成操作后,關(guān)閉eeprom對(duì)應(yīng)的I2C控制器的設(shè)備文件 */
67  close(fd);
68 
69  return 0;
70 }

6) 用數(shù)據(jù)包的方式操作I2C設(shè)備

? 構(gòu)建數(shù)據(jù)包結(jié)構(gòu)體:
? 首先是struct i2c_rdwr_ioctl_data data; 應(yīng)用程序通過該結(jié)構(gòu)體來給內(nèi)核傳遞消息。該結(jié)構(gòu)體包含兩個(gè)成員struct i2c_msg user * msgs;和 u32 nmsgs;其中msgs指向表示通信方法傳輸為消息的結(jié)構(gòu)體。而nmsgs則決定了該數(shù)據(jù)包有多少個(gè)這樣的通信消息,在I2C通信協(xié)議上來看就代表了有多少個(gè)開始信號(hào)。
? 接著就是struct i2c_msg; 它可以包含多條消息,而一條消息有可能包含多個(gè)數(shù)據(jù)。其成員包括:“代表I2C設(shè)備從地址的 u16 addr; 表示本次消息的標(biāo)志位的 u16 flags; 表示數(shù)據(jù)長度的 u16 len; 表示數(shù)據(jù)緩沖區(qū)的指針 u8
buf”
? 然后把要和I2C從設(shè)備通信的數(shù)據(jù)與上面兩個(gè)結(jié)構(gòu)體建立起相應(yīng)的聯(lián)系。
? 最后調(diào)用I2C_RDWR進(jìn)入驅(qū)動(dòng)程序執(zhí)行讀寫組合的I2C數(shù)據(jù)傳輸。
? 代碼如下:

01 #include <stdio.h>
02 #include <string.h>
03 #include <sys/ioctl.h>
04 #include <unistd.h>
05 #include <fcntl.h>
06 #include <linux/i2c-dev.h>
07 #include <linux/i2c.h>
08 
09 /* eeprom所對(duì)應(yīng)的I2C控制器的設(shè)備節(jié)點(diǎn) */ 
10 #define EEPROM_DEVICE        "/dev/i2c-0"    
11 
12 /* eeprom的I2C設(shè)備地址 */
13 #define EEPROM_ADDR    0x50              
14 
15 /*函數(shù)名:eeprom_write
16 **功能:向eeprom寫數(shù)據(jù)
17 **參數(shù):fd:eeprom對(duì)應(yīng)I2C控制器設(shè)備節(jié)點(diǎn)的文件名
18 **       dev_addr:eeprom的I2C從設(shè)備地址
19 **       reg_addr:eeprom的寄存器地址
20 **       data_buf:要向eeprom寫數(shù)據(jù)的數(shù)據(jù)buf
21 **       len:要寫多少個(gè)字節(jié)。本例中當(dāng)前最大支持為8個(gè)字節(jié)
22 **返回值:負(fù)數(shù)表示操作失敗,其他為成功
23 */
24 int eeprom_write(int fd, unsigned char dev_addr, unsigned char reg_addr, unsigned char * data_buf,int len)
25 {
26  int ret;
27 
28  unsigned char msg_buf[9];
29  struct i2c_rdwr_ioctl_data data;
30 
31  struct i2c_msg messages;
32 
33 
34  /* 1. 構(gòu)建msg_buf*/
35  /* 1.1. 將要操作的寄存器首地址賦給要進(jìn)行I2C數(shù)據(jù)通信的首字節(jié)數(shù)據(jù) */
36  msg_buf[0] = reg_addr;
37  
38  /* 1.2. 將要向eeprom寫數(shù)據(jù)的數(shù)據(jù)buf賦在I2C數(shù)據(jù)通信中eeprom寄存器的后面 */
39  if (len < 9) {          /* 本demo最大支持一次向eeprom寫一頁大小的8個(gè)字節(jié)數(shù)據(jù) */
40         memcpy((void *) &msg_buf[1], data_buf, len);  //第1位之后是數(shù)據(jù)
41     } else {
42         printf("This function supports up to 8 bytes at a time !!!
");
43         return -1;
44     }
45 
46  /* 2. 構(gòu)建 struct i2c_msg messages */
47  /* 2.1. 賦值eeprom的I2C從設(shè)備地址 */
48  messages.addr = dev_addr;  
49 
50  /* 2.2. 賦值flags為本次I2C通信完成寫功能 */
51  messages.flags = 0;    
52 
53  /* 2.3. 賦值len為數(shù)據(jù)buf的長度 + eeprom寄存器地址的數(shù)據(jù)長度 */
54  messages.len = len+1;
55 
56  /* 2.4. 構(gòu)建消息包的數(shù)據(jù)buf*/
57  messages.buf = msg_buf;  
58 
59  /* 3. 構(gòu)建struct i2c_rdwr_ioctl_data data */
60  /* 3.1. 將準(zhǔn)備好的消息包賦值給i2c_rdwr_ioctl_data中的msgs消息*/
61  data.msgs = &messages;
62 
63  /* 3.2. 由于本次I2C通信只有寫動(dòng)作,所以消息數(shù)為1次 */
64  data.nmsgs = 1;
65 
66  /* 4. 調(diào)用驅(qū)動(dòng)層的讀寫組合的I2C數(shù)據(jù)傳輸 */
67  if(ioctl(fd, I2C_RDWR, &data) < 0)
68  {
69      printf("I2C_RDWR err 
");
70      return -1;
71  }
72 
73  /* 5. 等待I2C總線寫入完成 */
74  sleep(1);
75 
76  return 0;
77 }
78 
79 /*函數(shù)名:eeprom_read
80 **功能:從eeprom讀數(shù)據(jù)
81 **參數(shù):fd:eeprom對(duì)應(yīng)I2C控制器設(shè)備節(jié)點(diǎn)的文件名
82 **       dev_addr:eeprom的I2C從設(shè)備地址
83 **       reg_addr:eeprom的寄存器地址
84 **       data_buf:存放從eeprom讀數(shù)據(jù)的buf
85 **       len:要讀多少個(gè)字節(jié)。
86 **返回值:負(fù)數(shù)表示操作失敗,其他為成功
87 */
88 int eeprom_read(int fd, unsigned char dev_addr, unsigned char reg_addr, unsigned char * data_buf,int len)
89 {
90  int ret;
91 
92  unsigned char msg_buf[9];
93  struct i2c_rdwr_ioctl_data data;
94 
95  struct i2c_msg messages[2];
96 
97  /* 1. 構(gòu)建 struct i2c_msg messages */
98  /* 1.1. 構(gòu)建第一條消息 messages[0] */
99  /* 1.1.1. 賦值eeprom的I2C從設(shè)備地址 */
100     messages[0].addr = dev_addr;  
101 
102     /* 1.1.2. 賦值flags為本次I2C通信完成寫動(dòng)作 */
103     messages[0].flags = 0;    
104 
105     /* 1.1.3. 賦值len為eeprom寄存器地址的數(shù)據(jù)長度是1 */
106     messages[0].len = 1;
107 
108     /* 1.1.4. 本次寫動(dòng)作的數(shù)據(jù)是要讀取eeprom的寄存器首地址*/
109     messages[0].buf = &reg_addr;  
110     
111     /* 1.2. 構(gòu)建第二條消息 messages[1] */
112     /* 1.2.1. 賦值eeprom的I2C從設(shè)備地址 */
113     messages[1].addr = dev_addr;  
114 
115     /* 1.1.2. 賦值flags為本次I2C通信完成讀動(dòng)作 */
116     messages[1].flags = I2C_M_RD;    
117 
118     /* 1.1.3. 賦值len為要讀取eeprom寄存器數(shù)據(jù)長度len */
119     messages[1].len = len;
120 
121     /* 1.1.4. 本次讀動(dòng)作的數(shù)據(jù)要存放的buf位置*/
122     messages[1].buf = data_buf; 
123 
124     /* 2. 構(gòu)建struct i2c_rdwr_ioctl_data data */
125     /* 2.1. 將準(zhǔn)備好的消息包賦值給i2c_rdwr_ioctl_data中的msgs消息*/
126     data.msgs = messages;
127 
128     /* 2.2. 由于本次I2C通信既有寫動(dòng)作也有讀動(dòng)作,所以消息數(shù)為2次 */
129     data.nmsgs = 2;
130 
131     /* 3. 調(diào)用驅(qū)動(dòng)層的讀寫組合的I2C數(shù)據(jù)傳輸 */
132     if(ioctl(fd, I2C_RDWR, &data) < 0)
133     {
134         printf("I2C_RDWR err 
");
135         return -1;
136     }
137 
138     /* 4. 等待I2C總線讀取完成 */
139     sleep(1);
140 
141     return 0;
142 }
143  
144 int main()
145 {
146     int fd,i,ret=0;
147     unsigned char w_add=0x10;
148     
149     /* 將要讀取的數(shù)據(jù)buf*/
150     unsigned char rd_buf[8] = {0};  
151     
152     /* 要寫的數(shù)據(jù)buf*/
153     unsigned char wr_buf[8] = {0};  
154 
155     printf("hello,this is I2C_RDWR i2c test 
");
156     
157     /* 打開eeprom對(duì)應(yīng)的I2C控制器文件 */
158     fd =open(EEPROM_DEVICE, O_RDWR);
159     if (fd< 0) 
160     {
161         printf("open"EEPROM_DEVICE"failed 
");
162     }   
163 
164     /* 把要寫入的數(shù)據(jù)寫入到后面的buf中 */
165     for(i=0;i<8;i++)
166         wr_buf[i]=i;
167 
168     /* 通過I2C_RDWR完成向eeprom讀數(shù)據(jù)的功能 */
169     eeprom_write(fd,EEPROM_ADDR,w_add,wr_buf,8);
170 
171     
172     /* 通過I2C_RDWR完成向eeprom寫數(shù)據(jù)的功能 */
173     eeprom_read(fd,EEPROM_ADDR,w_add,rd_buf,8);
174 
175     for(i=0;i<8;i++)
176     {
177         printf("rd_buf is :%d
",rd_buf[i]);
178     }
179     
180     /* 完成操作后,關(guān)閉eeprom對(duì)應(yīng)的I2C控制器的設(shè)備文件 */
181     close(fd);
182 
183     return 0;
184 }
185 
186  

12.3.3 簡介I2C的調(diào)試方式

1) 概述I2C通信中完成正常通信的常見元素:

? 第一,先檢查I2C總線上的所有設(shè)備是否都經(jīng)上拉電阻到電源,并檢查供電是否穩(wěn)定。

? 第二,數(shù)據(jù)線和時(shí)鐘信號(hào)線是否有接反的情況。

? 第三,I2C的通信速率是否超過了設(shè)備所支持的最高速度。

? 第四,檢查外部I2C設(shè)備與操作的I2C控制器是否掛在了同一條I2C總線上。

? 第五,檢查操作的I2C外設(shè)地址是否正確。

? 第六,檢查I2C總線上是否有多個(gè)相同設(shè)備地址的從機(jī)設(shè)備,導(dǎo)致通信沖突。

? 第七,操作的I2C外設(shè)是否處于寫保護(hù)狀態(tài),寫保護(hù)狀態(tài)是無法寫入數(shù)據(jù)的。

? 第八,檢查I2C通信時(shí)序是否滿足I2C通信協(xié)議。

? 第九,檢查在沒有開始運(yùn)行I2C通信程序的時(shí)候,I2C總線上的電平信號(hào)是否干凈穩(wěn)定的保持高電平,是否出現(xiàn)過主機(jī)誤把SDA拉低的情況,導(dǎo)致I2C總線出現(xiàn)“忙碌”狀態(tài)。

? 第十,檢查I2C通信過程中是否出現(xiàn)SDA或者SCL被長時(shí)間一直拉低的狀態(tài)。比如I2C外設(shè)從機(jī)由于異常在發(fā)送完ACK信號(hào)后沒有釋放SDA。另一種情況是cpu在做從機(jī)的時(shí)候,沒有及時(shí)完成將讀取的主機(jī)數(shù)據(jù)進(jìn)行處理,導(dǎo)致長時(shí)間將SCL拉低,破壞了I2C通信流程,因此我們?cè)趯慖2C通信的時(shí)候最好盡快在I2C接收數(shù)據(jù)中斷服務(wù)函數(shù)中完成數(shù)據(jù)處理工作并授權(quán)I2C控制器讓其正常工作。

? 由于I2C總線的協(xié)議特性,如果總線上有任何一個(gè)I2C設(shè)備將SCL或者SDA的信號(hào)拉低,其他的I2C設(shè)備都將看到這個(gè)低電平,并且都無法拉高他們。這也就是說,如果有設(shè)備不釋放總線,一直把總線的電平拉低,那么整個(gè)I2C總線將會(huì)出現(xiàn)暫停掛死的狀態(tài),將無法按照I2C協(xié)議進(jìn)行正常通信。

? 如果負(fù)責(zé)I2C總線主機(jī)cpu的I2C控制器出現(xiàn)上述長時(shí)間拉低I2C總線的電平,理論上我們可以通過調(diào)試代碼找出I2C總線死機(jī)的原因,并修改代碼重新初始化該I2C控制器來復(fù)位它,讓其重新進(jìn)行I2C通信。如果通過調(diào)試發(fā)現(xiàn)導(dǎo)致I2C總線死機(jī)的原因是由I2C外設(shè)導(dǎo)致的,那么我們可以復(fù)位該外設(shè)芯片。但是在實(shí)際的項(xiàng)目開發(fā)中,可能復(fù)位I2C總線上的元件也無法恢復(fù)正常的I2C通信,這個(gè)時(shí)候就要設(shè)計(jì)I2C總線的主機(jī)程序?qū)2C控制器引腳設(shè)置為GPIO功能并模擬I2C協(xié)議完成一次完整的I2C通信,再將I2C控制器設(shè)置設(shè)置為I2C功能。

12.4 總結(jié)I2C在嵌入式項(xiàng)目開發(fā)的應(yīng)用優(yōu)缺點(diǎn)

? 優(yōu)點(diǎn):只使用兩根線,支持多個(gè)主控制器和多個(gè)從設(shè)備,I2C具有非常廣泛使用的協(xié)議。

? 缺點(diǎn):數(shù)據(jù)傳輸速率比SPI慢,數(shù)據(jù)幀的大小限制為8位,實(shí)現(xiàn)比SPI更復(fù)雜的硬件。而且I2C通信需要注意下面的使用問題:

1) I2C時(shí)鐘信號(hào)(SCL)的同步問題

? 在I2C總線上傳送信息時(shí)的時(shí)鐘同步信號(hào)是由掛接在SCL線上的所有器件的邏輯“與”完成的。SCL線上由高電平到低電平的跳變將影響到這些器件,一旦某個(gè)器件的時(shí)鐘信號(hào)下跳為低電平,將使SCL線一直保持低電平,使SCL線上的所有器件開始低電平期。此時(shí),低電平周期短的器件的時(shí)鐘由低至高的跳變并不能影響SCL線的狀態(tài),于是這些器件將進(jìn)入高電平等待的狀態(tài)。當(dāng)所有器件的時(shí)鐘信號(hào)都上跳為高電平時(shí),低電平期結(jié)束,SCL線被釋放返回高電平,即所有的器件都同時(shí)開始它們的高電平期。其后,第一個(gè)結(jié)束高電平期的器件又將SCL線拉成低電平。這樣就在SCL線上產(chǎn)生一個(gè)同步時(shí)鐘。可見,時(shí)鐘低電平時(shí)間由時(shí)鐘低電平期最長的器件確定,而時(shí)鐘高電平時(shí)間由時(shí)鐘高電平期最短的器件確定。

2) 總線驅(qū)動(dòng)能力

? 上拉電阻和負(fù)載電容決定了總線在某一速率下的穩(wěn)定性。當(dāng)輸出為高時(shí),電流通過上拉電阻對(duì)負(fù)載電容充電。上拉越大,電容越大,所需要的時(shí)間就越長,如果超過了通信周期的10%,那么這個(gè)上升沿就太緩了,相應(yīng)的建立時(shí)間會(huì)受到影響,I2C規(guī)范的最大負(fù)載電容是400pF,快速模式下是100pF。如果輸出為低,電流通過上拉電阻被I2C master器件吸取,(注意根據(jù)I2C規(guī)范,最小只有3毫安的吸取電流)那么這個(gè)吸取電流在上拉電阻上的壓降就決定了輸出低電平能達(dá)到的范圍,如果不能達(dá)到0.3VDD以下,就會(huì)有誤采樣。有人說加大上拉電阻是不妥當(dāng)?shù)模唧w分析吸取電流、負(fù)載電容、上拉電平和通信速率才能決定(普通模式和快速模式是不一樣的)。

? 雖然速度不是特別快,但是信號(hào)線上如果有加電容的話,切記不要加大的,一定要小,否則信號(hào)還沒到從設(shè)備呢,就被電容吃了。

本文摘自 :https://blog.51cto.com/w

開通會(huì)員,享受整站包年服務(wù)立即開通 >