I2C協(xié)議詳解及裸機程序分析

版權(quán)聲明:本文為小斑馬學習總結(jié)文章,技術(shù)來源于韋東山著作马篮,轉(zhuǎn)載請注明出處沾乘!

一、I2C協(xié)議與EEPROM

I2C協(xié)議
I2C在硬件上的接法如下(圖19-1)所示浑测,主控芯片引出兩條線SCL,SDA線翅阵,在一條I2C總線上可以接很多I2C設(shè)備,我們還會放一個上拉電阻(放一個上拉電阻的原因以后我們再說)迁央。

我們怎么傳輸數(shù)據(jù)掷匠,我們需要發(fā)數(shù)據(jù)從主設(shè)備發(fā)送到從設(shè)備上去,也需要把數(shù)據(jù)從從設(shè)備傳送到主設(shè)備上去岖圈,數(shù)據(jù)涉及到雙向傳輸讹语。
舉個例子:
圖片來自百問網(wǎng)
體育老師:可以把球發(fā)給學生,也可以把球從學生中接過來蜂科。
發(fā)球:

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

接球:

  • a.老師說注意了(start)
  • b.老師說:B把球發(fā)給我(地址)
  • c.B就把球發(fā)給老師(傳輸)
  • d.老師收到球之后茸时,給B說一聲,表示收到球了(回應(yīng))鸠儿。
  • e.老師說下課(停止)

IIC的傳輸協(xié)議:

  • 老師說注意了屹蚊,表示開始信號(start)
  • 老師告訴某個學生,表示發(fā)送地址(address)
  • 老師發(fā)球/接球进每,表示數(shù)據(jù)的傳輸
  • 老師/學生收到球汹粤,回應(yīng)表示:回應(yīng)信號(ACK)
  • 老師說下課,表示IIC傳輸接受(P)

IIC傳輸數(shù)據(jù)的格式
1.寫操作:
剛開始主芯片要發(fā)出一個start信號田晚,然后發(fā)出一個設(shè)備地址(用來確定是往哪一個芯片寫數(shù)據(jù))嘱兼,方向(讀/寫,0表示寫贤徒,1表示讀)芹壕。回應(yīng)(用來確定這個設(shè)備是否存在)接奈,然后就可以傳輸數(shù)據(jù)踢涌,傳輸數(shù)據(jù)之后,要有一個回應(yīng)信號(確定數(shù)據(jù)是否接受完成)序宦,然后再傳輸下一個數(shù)據(jù)睁壁。

每傳輸一個數(shù)據(jù),接受方都會有一個回應(yīng)信號互捌,數(shù)據(jù)發(fā)送完之后潘明,主芯片就會發(fā)送一個停止信號。
白色背景:主→從
灰色背景:從→主

2.讀操作:
剛開始主芯片要發(fā)出一個start信號秕噪,然后發(fā)出一個設(shè)備地址(用來確定是從哪一個芯片讀取數(shù)據(jù))钳降,方向(讀/寫,0表示寫腌巾,1表示讀)遂填。

回應(yīng)(用來確定這個設(shè)備是否存在),然后就可以傳輸數(shù)據(jù)壤躲,傳輸數(shù)據(jù)之后城菊,要有一個回應(yīng)信號(確定數(shù)據(jù)是否接受完成),然后在傳輸下一個數(shù)據(jù)碉克。
每傳輸一個數(shù)據(jù)凌唬,接受方都會有一個回應(yīng)信號,數(shù)據(jù)發(fā)送完之后,主芯片就會發(fā)送一個停止信號客税。
白色背景:主→從
灰色背景:從→主

傳輸是以8位為單元數(shù)據(jù)傳輸?shù)目鐾剩葌鬏斪罡呶?MSB),主芯片發(fā)出start信號之后更耻,然后發(fā)出9個時鐘傳輸數(shù)據(jù)测垛。
(1)開始信號(S):SCL為高電平時,SDA山高電平向低電平跳變秧均,開始傳送數(shù)據(jù)食侮。
(2)結(jié)束信號(P):SCL為電平時,sDA由低電平向高電平跳變目胡,結(jié)束傳送數(shù)據(jù)锯七。
(3)響應(yīng)信號(ACK):接收器在接收到8位數(shù)據(jù)后,在第9個時鐘周期誉己,拉低SDA
SDA上傳輸?shù)臄?shù)據(jù)必須在SCL為高電平期間保持穩(wěn)定眉尸,SDA上的數(shù)據(jù)只能在SCL為低電平期間變化。如圖
1.問題:如何在SDA上實現(xiàn)雙向傳輸巨双?
答:主芯片通過一根SDA線既可以把數(shù)據(jù)發(fā)給從設(shè)備噪猾,也可以從SDA上讀取數(shù)據(jù),連接SDA線的引腳里面必然有兩個引腳(發(fā)送引腳/接受引腳)筑累。
2.問題:主設(shè)備(從設(shè)備)發(fā)送數(shù)據(jù)時袱蜡,從設(shè)備(主設(shè)備)的發(fā)送引腳,不影響數(shù)據(jù)的發(fā)送慢宗,怎么做到呢戒劫?
答:里面放一個三極管,使用開極(極電集開發(fā)出去作為輸出)電路婆廊,如下圖

  • 從真值表和電路圖我們可以知道,當某一個芯片不行影響SDA線時巫橄,那就不驅(qū)動這個三極管淘邻。
  • 想輸出高電平時;都不驅(qū)動(高電平就由上拉電阻決定)湘换。
  • 想輸出低電平宾舅,就驅(qū)動三極管。

從下面的例子可以看看數(shù)據(jù)是怎么傳的(實現(xiàn)雙向傳輸)彩倚,比如:主設(shè)備發(fā)送(8bit)給從設(shè)備
1.前8個clk

  • 從設(shè)備不要影響筹我,從設(shè)備不驅(qū)動三極管;
  • 主設(shè)備決定數(shù)據(jù)帆离;

2.第9個clk蔬蕊,由從設(shè)備決定數(shù)據(jù)

  • 主設(shè)備不驅(qū)動三極管;
  • 從設(shè)備決定數(shù)據(jù)哥谷;

從上面的例子岸夯,就可以知道麻献,怎樣在一條線上實現(xiàn),雙向傳輸?shù)霓k法猜扮。這就是為什么在SDA,SCL上放上拉電阻的原因勉吻。
在第9個時鐘之后,如果有某一方處于繁忙狀態(tài)旅赢,它可以一直把SCL拉低當SCL為低電平時候齿桃,大家都不應(yīng)該使用IIC總線,只有當SCL從低電平變?yōu)楦唠娖降臅r候煮盼,IIC總線才能被使用短纵。
從前圖我們也可以知道ACK信號應(yīng)該是低電平。主設(shè)備不驅(qū)動三極管孕似,如果從設(shè)備不驅(qū)動三極端的化SDA應(yīng)該是高電平踩娘,當從設(shè)備接收數(shù)據(jù)之后,發(fā)出回應(yīng)信號的時候喉祭,就會驅(qū)動三極管养渴,讓SDA變?yōu)榈碗娖健K哉f:ACK信號是低電平泛烙。

對于IIC協(xié)議它只能規(guī)定怎么傳輸數(shù)據(jù)理卑,數(shù)據(jù)什么含義它完全不能夠控制,數(shù)據(jù)的含義有從設(shè)備決定蔽氨。

二藐唠、S3C2440的I2C控制器

在嵌入式系統(tǒng)里面的主控芯片一般都會有I2C控制器,要是沒有可以根據(jù)I2C協(xié)議用GPIO管腳模擬鹉究,但是非常麻煩宇立,我們要發(fā)送數(shù)據(jù)時,可以把數(shù)據(jù)放到某個寄存器自赔,它就會自動的發(fā)出時鐘妈嘹,并且把數(shù)據(jù)發(fā)送給從設(shè)備,同時會等待從設(shè)備會返回回應(yīng)信號绍妨。

當我們想發(fā)送一個數(shù)據(jù)的時候润脸,要設(shè)置某個寄存器啟動傳輸,它也一樣會產(chǎn)生時鐘他去,然后從設(shè)備就會把數(shù)據(jù)通過SDA傳到I2C控制器里面毙驯,組裝進某個寄存器里面,最終寄存器會把接收到的8位數(shù)據(jù)返回給我們的程序灾测,從這里可以看到I2C控制器簡化了I2C的操作爆价。簡短電路連接圖,如圖:

根據(jù)上圖,我們首先設(shè)置IICCON(來設(shè)置時鐘)允坚,時鐘源是PCLK(是50MHZ)太快了我們需要設(shè)置這個分頻系數(shù)魂那,把時鐘降低,降低到我們想要的SCL,然后我們要發(fā)出start信號稠项,我們需要設(shè)置寄存器發(fā)出start信號涯雅,之后我們需要發(fā)出數(shù)據(jù)啊,我們的程序可以把數(shù)據(jù)寫入到IICDS寄存器展运,一寫入就會自動的發(fā)出時鐘活逆,并且把這8位數(shù)據(jù)從SDA發(fā)送給從設(shè)備,數(shù)據(jù)發(fā)送之后拗胜,在第九個時鐘會收到回應(yīng)信號蔗候,可以查詢IICSTAT是否有ACK(有ACK表示數(shù)據(jù)發(fā)送成功了),可以繼續(xù)發(fā)送數(shù)據(jù)埂软,等發(fā)完數(shù)據(jù)之后锈遥,再來設(shè)置IICSTAT讓它發(fā)出P信號。

在第九個CLK勘畔,就會產(chǎn)生一個中斷,在中斷處理過程中SCL被拉為低電平所灸,誰都不能再使用IIC總線,等待中斷處理完成.
怎樣處理中斷炫七?
寫操作:
若無ACK,出錯爬立,然后發(fā)出P信號結(jié)束,
:: 若有ACK信號表示上一個字節(jié)成功發(fā)送出去
:: 若仍有數(shù)據(jù)万哪,寫入IICDS寄存器侠驯,然后清中斷,一清中斷就會釋放SCL信號奕巍,繼續(xù)發(fā)出時鐘吟策,把數(shù)據(jù)再次發(fā)送出去。
:: 若沒有數(shù)據(jù)了的止,發(fā)出P信號結(jié)束踊挠。

讀操作:
讀到8位數(shù)時,應(yīng)該回應(yīng)一個ACK信號冲杀。
:: 還想讀數(shù)據(jù),清中斷睹酌,啟動傳輸权谁。等它再次發(fā)生中斷時,再來讀取IICDS寄存器,得到數(shù)據(jù)憋沿。不想讀取數(shù)據(jù)旺芽,發(fā)出P信號結(jié)束。
重點: 發(fā)生中斷時,我們的IIC控制器會把SCL拉低采章,阻止任何設(shè)備再使用IIC總線运嗜,清中斷之后才能繼續(xù)使用,這種機制就給我們中斷服務(wù)程序的執(zhí)行提供了時間悯舟。

讀-寫操作
在發(fā)送模式:

  • 1.往寄存器IICDS寄存器放入一個val值担租。
  • 2.發(fā)完,產(chǎn)生中斷抵怎,并且會把 SCL拉低奋救。
  • 3.在中斷程序里,判斷狀態(tài)反惕,然后往IICDS里面寫入下一個數(shù)據(jù)尝艘,一旦寫入下一個數(shù)據(jù)IIC繼續(xù)操作,若再次發(fā)完姿染,就會再次產(chǎn)生中斷背亥。

在接受模式:

  • 1.我的程序發(fā)起傳輸,接受數(shù)據(jù)悬赏。
  • 2.接收到數(shù)據(jù)之后狡汉,產(chǎn)生中斷,SCL被拉低舷嗡。
  • 3.中斷程序里轴猎,判斷數(shù)據(jù)是否要繼續(xù)接受等,如果還有繼續(xù)接受的話进萄,再次設(shè)置捻脖,設(shè)置好之后讀IICDS寄存器,一但讀出來IIC中鼠。
    繼續(xù)接受下一個數(shù)據(jù)可婶,收到新數(shù)據(jù)之后,又會產(chǎn)生一個中斷(就是這樣循環(huán)操作)援雇。

IICCON寄存器(Multi-masterIIC-buscontrol)
IICCON寄存器用于控制是否發(fā)出ACK信號矛渴、設(shè)置發(fā)送器的時鐘、開啟惫搏,i2c中斷具温,并標識中斷是否發(fā)生。它的各位含義如表:

使用IICCON寄存器時筐赔,有如下注意事項铣猩。

  • 1.發(fā)送模式的時鐘頻率由位[6]、位[3:0]聯(lián)合決定,另外茴丰,llCCON[6]=0,IICCON[3:0]
    不能取0或10
  • 2.12c中斷在以下3種情況下發(fā)生:當發(fā)出地址信息或接收到一個從機地址并且吻合時,當總線仲裁失敗時达皿,當發(fā)送/接收完一個字節(jié)的數(shù)據(jù)(包括響應(yīng)位)時天吓。
  • 3.基于SDA、SCL線上時間特性的考慮峦椰,要發(fā)送數(shù)據(jù)時龄寞,先將數(shù)據(jù)寫入IICDS寄存器,然后再清除中斷汤功。
  • 4.如果IICCON[5]=0物邑,IICCON14]將不能正常工作。所以冤竹,即使不使用12c中斷拂封,也要將IICCON[5]設(shè)為1。

IICSTAT寄存器(Multi-masterIIC-buscontrol/status)
IICSTAT寄存器用于選擇12c接口的工作模式鹦蠕,發(fā)出S信號冒签、P信號侥锦,使能接收/發(fā)送功能岗钩,并標識各種狀態(tài)晌梨,比如總線仲裁是否成功辖源、作為從機時是否被尋址喉恋、是否接收到0地址宁改、是否接收到ACK信號等逊躁。IICSTAT寄存器的各位如表:

IICADD寄存器(Multi-masterIlC-busaddress)
用到IICADD寄存器的位[7:11]谢翎,表示從機地址屹徘。IICADD寄存器在串行輸出使能位
IICSTAT[4]為0時走趋,才可以寫入:在任何時間都可以讀出。IICADD寄存器的各位如表:
IICDS寄存器(Multi-masterIIC-busTx/Rxdatashift)
用到IICDS寄存器的位丨7:0]噪伊,其中保存的是要發(fā)送或己經(jīng)接收的數(shù)據(jù)簿煌。IICDS寄存器在串行輸出使能位IICSTAT()1為1時,叼可以寫入鉴吹;在任何時間都可以讀出姨伟。IICDS寄存器的各位如表:
讀寫操作流程圖

主機發(fā)送器模式操作:

主機接收器模式操作:

三、程序框架

寫程序之前 考慮好程序的框架豆励,我們想寫出一個結(jié)構(gòu)比較好夺荒,比較容易擴展的程序,我們先要考慮清楚框架的設(shè)計良蒸。
IIC控制器的功能
IIC會做什么事情呢技扼?
對于IIC控制器,它負責傳輸數(shù)據(jù)嫩痰,不知道數(shù)據(jù)的含義淮摔,但是它要實現(xiàn)寫/讀操作
讀操作


寫操作
IIC設(shè)備的功能
很顯然,IIC控制器提供了傳輸數(shù)據(jù)的能力始赎,至于數(shù)據(jù)有什么含義和橙,IIC控制器并不知道,數(shù)據(jù)的含義有外接的IIC芯片決定造垛,我們需要閱讀芯片手冊魔招,才知道IIC控制器應(yīng)該發(fā)出怎樣的數(shù)據(jù),
AT24cxx的操作方法
顯然我們的程序應(yīng)該分為兩層(IIC設(shè)備層五辽,IIC控制器層)办斑,框架如下圖所示:
搜狗截圖18年11月20日1823_18.png
我們提供一個統(tǒng)一的接口i2c_transfer,不關(guān)使用哪個芯片杆逗,他最終都會調(diào)用i2c_transfer乡翅,來選擇某一款I(lǐng)2C控制器,把數(shù)據(jù)發(fā)送出去罪郊,或者從I2c設(shè)備讀到數(shù)據(jù)蠕蚜,對于每一次傳輸?shù)臄?shù)據(jù)都可以用一個i2c_msg結(jié)構(gòu)體來表示。但是悔橄,讀某個地址的數(shù)據(jù)時靶累,就要用兩個i2c_msg結(jié)構(gòu)體來描述它,因為一個i2c_msg結(jié)構(gòu)體只能描述一個傳輸方向(讀/寫)癣疟,我們讀取ac24ccxx某個地址上的數(shù)據(jù)時挣柬,要先寫出要讀取的地址,然后來讀取設(shè)備地址上的數(shù)據(jù)睛挚。
我們想設(shè)計出以一個結(jié)構(gòu)體比較容易擴展的框架邪蛔,對于I2C控制器我們要抽象出一個結(jié)構(gòu)體i2c_controller,我們構(gòu)造這個結(jié)構(gòu)體之后扎狱,把這個這個結(jié)構(gòu)體侧到,告訴上層(I2C控制器那一層),上層有個管理者i2c_contreller.c文件委乌。
我們在s3c2440_i2c_controller.c這個文件中我們構(gòu)造出一個i2c_controller結(jié)構(gòu)體床牧,把它放入上層文件中的數(shù)組里,以后就根據(jù)結(jié)構(gòu)體的名字遭贸,把這個結(jié)構(gòu)體取出來使用戈咳。
假設(shè)我們有一個TI的開發(fā)板,在ti_i2c_controller.c文件中壕吹,也要構(gòu)造出一個i2c_controller結(jié)構(gòu)體著蛙,同樣們也會把這個結(jié)構(gòu)體放入上層的結(jié)構(gòu)體數(shù)組(i2c_contreller.c文件中)中,以后根據(jù)名字先出來使用耳贬。
對于設(shè)備層中的at24cxx芯片我們寫出at24cxx.c文件在這個文件實現(xiàn)讀寫函數(shù):
1.at24cxx_write函數(shù)
2.at24cxx_read踏堡。
函數(shù)讀寫函數(shù)都會調(diào)用i2c_transfer發(fā)起IIC傳輸,所以我們寫程序的時候主要的暫時會涉及到三個文件:
at24cxx.c咒劲, s3c2440_i2c_controller.c顷蟆,i2c_contreller.c诫隅。在最上層會寫出一個i2c_test.c文件,它會提供菜單供我們選擇來測試帐偎。
下面我們寫一個程序框架逐纬,涉及到的文件有:i2c_test.c、at24cxx.c削樊、i2c_controller.c豁生、s3c2440_i2c_controller.c。
i2c_test.c文件
該文件的內(nèi)容如下:

void i2c_test(void)
{
/* 初始化: 選擇I2C控制器 */

/* 提供菜單供測試 */
}

這個菜單最終會調(diào)用到at24cxx.c里面的函數(shù)漫贞。
at24cxx.c文件
在里面會使用標準的接口i2c_transfer來啟動I2C傳輸甸箱。該文件的內(nèi)容如下:

int at24cxx_write(unsigned int addr, unsigned char *data, int len)
{
/* 構(gòu)造i2c_msg */

/* 調(diào)用i2c_transfer */
}


int at24cxx_read(unsigned int addr, unsigned char *data, int len)
{
/* 構(gòu)造i2c_msg */

/* 調(diào)用i2c_transfer */
}

i2c_controller.c文件
在里面會使用標準的接口i2c_transfer來啟動I2C傳輸。該文件的內(nèi)容如下:

int at24cxx_write(unsigned int addr, unsigned char *data, int len)
{
/* 構(gòu)造i2c_msg */

/* 調(diào)用i2c_transfer */
}


int at24cxx_read(unsigned int addr, unsigned char *data, int len)
{
/* 構(gòu)造i2c_msg */

/* 調(diào)用i2c_transfer */
}

i2c_controller.c文件
該文件的內(nèi)容如下:

/* 有一個i2c_controller數(shù)組用來存放各種不同芯片的操作結(jié)構(gòu)體 */
void register_i2c_controller()
{
}

/* 根據(jù)名字來選擇某款I(lǐng)2C控制器 */
void select_i2c_controller(char *name)
{
}

/* 實現(xiàn) i2c_transfer 接口函數(shù) */

int i2c_transfer(i2c_msg msgs, int num)
{

}

select_i2c_controller函數(shù)根據(jù)名字來選擇某款I(lǐng)2C控制器后迅脐,以后就會使用被選擇的I2C控制器來啟動傳輸芍殖。

有數(shù)組一定有注冊函數(shù)register_i2c_controller會把下面實現(xiàn)的I2C控制器結(jié)構(gòu)體i2c_controller放到i2c_controller數(shù)組里面。
s3c2440_i2c_controller.c文件
對于具體的芯片仪际,要實現(xiàn)自己的i2c_controller围小。該文件的內(nèi)容如下:

/* 實現(xiàn)i2c_controller
      .init
      .master_xfer
      .name
 */

四、I2C控制器編程_框架

我們現(xiàn)在來講I2C控制器怎么寫树碱,它是I2C程序中最核心的地方肯适,我們要先構(gòu)造幾個結(jié)構(gòu)體,這幾個結(jié)構(gòu)體放在i2c_controller.h里面成榜。

我們要發(fā)出I2c傳輸時框舔,要構(gòu)造出i2c_msg,把構(gòu)造出的i2c_msg扔給下面的i2c_controller.c赎婚,i2c_controller.c會選擇某一個i2c控制器刘绣,使用里面的master_xfer來傳輸數(shù)據(jù), 所以我們需要構(gòu)造出一個i2c_controller結(jié)構(gòu)體挣输。
i2c_controller.h文件
文件的內(nèi)容如下所示:

#ifndef _I2C_CONTROLLER_H
#define _I2C_CONTROLLER_H

typedef struct i2c_msg {
   unsigned int addr;  /* 7bits */
   int flags;  /* 0 - write, 1 - read */
   int len;
   int cnt_transferred;
   unsigned char *buf;
}i2c_msg, *p_i2c_msg;

typedef struct i2c_controller {
   int (*int)(void);
   int (*master_xfer)(i2c_msg msgs, int num);
   char *name;
}i2c_controller, *p_i2c_controller;


#endif /* _I2C_CONTROLLER_H */

解析:我們構(gòu)造這兩個結(jié)構(gòu)體纬凤,我們要把它放在i2c_controller.c把它用起來,
i2c_controller.c文件
文件的內(nèi)容如下所示:

include "i2c_controller.h"

#define I2C_CONTROLLER_NUM 10

/* 有一個i2c_controller數(shù)組用來存放各種不同芯片的操作結(jié)構(gòu)體 */
static p_i2c_controller p_i2c_controllers[I2C_CONTROLLER_NUM];
static p_i2c_controller p_i2c_con_selected;


void register_i2c_controller(p_i2c_controller *p)
{
int i;
for (i = 0; i < I2C_CONTROLLER_NUM; i++)
{
    if (!p_i2c_controllers[i])
    {
        p_i2c_controllers[i] = p;
        return;
    }
}
}

解析:register_i2c_controller函數(shù)用于把參數(shù)中的結(jié)構(gòu)體指針撩嚼,注冊到p_i2c_controllers指針數(shù)組中停士。

/* 根據(jù)名字來選擇某款I(lǐng)2C控制器 */
int select_i2c_controller(char *name)
{
int i;
for (i = 0; i < I2C_CONTROLLER_NUM; i++)
{
    if (p_i2c_controllers[i] && !strcmp(name, p_i2c_controllers[i]->name))
    {
        p_i2c_con_selected = p_i2c_controllers[i];
        return 0;
    }
}
return -1;
}

解析:select_i2c_controller函數(shù)根據(jù)參數(shù)中的名字(name) 從p_i2c_controllers指針數(shù)組中取出對應(yīng)的結(jié)構(gòu)體指針復制給p_i2c_con_selected結(jié)構(gòu)體指針(靜態(tài)全局變量)。

/* 實現(xiàn) i2c_transfer 接口函數(shù) */

int i2c_transfer(i2c_msg msgs, int num)
{
    return p_i2c_con_selected->master_xfer(msgs, num);
}

解析:i2c_transfer接口函數(shù)完丽,調(diào)用選擇的p_i2c_con_selected成員中master_xfer函數(shù)恋技。

void i2c_init(void)
{
/* 注冊下面的I2C控制器 */
s3c2440_i2c_con_add();

/* 選擇某款I(lǐng)2C控制器 */

/* 調(diào)用它的init函數(shù) */
}

解析:s3c2440_i2c_con_add()函數(shù),把定義的s3c2440_i2c_con結(jié)構(gòu)體注冊到p_i2c_controllers數(shù)組中逻族。
s3c2440_i2c_controller.c文件
中斷服務(wù)函數(shù)蜻底,當發(fā)成中斷是,就會調(diào)用中斷服務(wù)函數(shù)聘鳞,代碼如下

void i2c_interrupt_func(int irq)
{
/* 每傳輸完一個數(shù)據(jù)將產(chǎn)生一個中斷 */

/* 對于每次傳輸, 第1個中斷是"已經(jīng)發(fā)出了設(shè)備地址" */
}

s3c2440_i2c_con_init函數(shù)薄辅,用來初始化I2C,控制器代碼如下:

void s3c2440_i2c_con_init(void)
{
/* 設(shè)置時鐘 */
/* [7] : IIC-bus acknowledge enable bit, 1-enable in rx mode
 * [6] : 時鐘源, 0: IICCLK = fPCLK /16; 1: IICCLK = fPCLK /512
 * [5] : 1-enable interrupt
 * [4] : 讀出為1時表示中斷發(fā)生了, 寫入0來清除并恢復I2C操作
 * [3:0] : Tx clock = IICCLK/(IICCON[3:0]+1).
 * Tx Clock = 100khz = 50Mhz/16/(IICCON[3:0]+1)
 */
IICCON = (0<<6) | (1<<5) | (30<<0);

/* 注冊中斷處理函數(shù) */
register_irq(27, i2c_interrupt_func);
}

解析:

  • 1).IICCON = (0<<6) | (1<<5) | (30<<0); 設(shè)置IICCON控制寄存器要拂。選擇發(fā)送時鐘,使能中斷站楚。

  • 2).register_irq(27, i2c_interrupt_func):注冊中斷處理函數(shù)宇弛,當發(fā)生I2C中斷的時候就會調(diào)用i2c_interrupt_func中斷處理函數(shù)。

初始化完成后源请,就可以調(diào)用do_master_tx寫I2C從機了,這個函數(shù)僅僅啟動I2C傳輸彻况,然后等待谁尸,直到數(shù)據(jù)在中斷服務(wù)程序中傳輸完畢后再返回。函數(shù)代碼如下:

void do_master_tx(p_i2c_msg msg)
{
msg->cnt_transferred = 0;

/* 設(shè)置寄存器啟動傳輸 */
/* 1. 配置為 master tx mode */
    
/* 2. 把從設(shè)備地址寫入IICDS */
IICDS = msg->addr<<1;

/* 3. IICSTAT = 0xf0 , 數(shù)據(jù)即被發(fā)送出去, 將導致中斷產(chǎn)生 */
IICSTAT = 0xf0;


/* 后續(xù)的傳輸由中斷驅(qū)動 */

/* 循環(huán)等待中斷處理完畢 */
while (msg->cnt_transferred != msg->len);
}

解析:

  • 1).IICDS = msg->addr<<1: 把從機地址(高7位纽甘,所以需要向右移一位)寫入到IICDS寄存器中良蛮。

  • 2).IICSTAT = 0xf0:設(shè)置IICSTAT寄存器,將s3c2440設(shè)為主機發(fā)送器悍赢,并發(fā)出S信號后决瞳,緊接著就發(fā)出從機地址。后續(xù)的傳輸工作將在中斷服務(wù)程序中完成左权。

do_master_rx函數(shù)的實現(xiàn)和do_master_tx函數(shù)類似皮胡,代碼如下:

void do_master_rx(p_i2c_msg msg)
{
msg->cnt_transferred = 0;

/* 設(shè)置寄存器啟動傳輸 */
/* 1. 配置為 Master Rx mode */
    
/* 2. 把從設(shè)備地址寫入IICDS */
IICDS = (msg->addr<<1)|(1<<0);

/* 3. IICSTAT = 0xb0 , 從設(shè)備地址即被發(fā)送出去, 將導致中斷產(chǎn)生 */
IICSTAT = 0xb0;


/* 后續(xù)的傳輸由中斷驅(qū)動 */

/* 循環(huán)等待中斷處理完畢 */
while (msg->cnt_transferred != msg->len);
}

解析:
1).IICDS = (msg->addr<<1)|(1<<0):把從設(shè)備地址寫入IICDS,前7位是從機地址赏迟,第8位表示傳輸方向(0表示寫操作屡贺,1表示讀操作)。
s3c2440傳輸函數(shù)锌杀,根據(jù)標志位flags甩栈,來指明是讀/寫(1:讀 0:寫)。代碼如下:

int s3c2440_master_xfer(p_i2c_msg msgs, int num)
{
int i;
for (i = 0; i < num; i++)   
{
    if (msgs[i]->flags == 0)/* write */
        do_master_tx(msgs[i]);
    else
        do_master_rx(msgs[i]);
}
}

我們定義一個i2c_controller結(jié)構(gòu)體s3c2440_i2c_con糕再。下面的代碼對他進行初始化量没。

static i2c_controller s3c2440_i2c_con = {
   .name = "s3c2440",
   .init = s3c2440_i2c_con_init,
   .master_xfer = s3c2440_master_xfer,
};

s3c2440_i2c_con_add函數(shù)把上面定義的s3c2440_i2c_con結(jié)構(gòu)體注冊到上層的i2c_controller數(shù)組中。

void s3c2440_i2c_con_add(void)
{
   register_i2c_controller(&s3c2440_i2c_con);
}

五突想、I2C控制器編程_中斷

中斷控制器是IIC程序中的核心中的核心殴蹄。
Start信號之后,發(fā)出設(shè)備地址蒿柳,在第9個時鐘就會產(chǎn)生一個中斷饶套,我們根據(jù)i2c的流程圖來編寫中斷程序。
每傳輸完一個數(shù)據(jù)將產(chǎn)生一個中斷垒探,I2C操作的主體在中斷服務(wù)程序妓蛮,它可以分為兩部分:寫操作,讀操作圾叼。
先分析寫操作蛤克,代碼如下

void i2c_interrupt_func(int irq)
{
int index;
unsigned int iicstat = IICSTAT;

p_cur_msg->cnt_transferred++;

/* 每傳輸完一個數(shù)據(jù)將產(chǎn)生一個中斷 */

/* 對于每次傳輸, 第1個中斷是"已經(jīng)發(fā)出了設(shè)備地址" */

if (p_cur_msg->flags == 0)  /* write */
{
    /* 對于第1個中斷, 它是發(fā)送出設(shè)備地址后產(chǎn)生的
     * 需要判斷是否有ACK
     * 有ACK : 設(shè)備存在
     * 無ACK : 無設(shè)備, 出錯, 直接結(jié)束傳輸
     */
    if (p_cur_msg->cnt_transferred == 0)  /* 第1次中斷 */
    {
        if (iicstat & (1<<0))
        { /* no ack */
            /* 停止傳輸 */
            IICSTAT = 0xd0;
            IICCON &= ~(1<<4);
            p_cur_msg->err = -1;
            delay(1000);
            return;
        }
    }
  • 1).p_cur_msg->cnt_transferred初始值為-1(我們后面設(shè)置)捺癞。
  • 2).p_cur_msg->cnt_transferred == 0表示是第一次傳輸數(shù)據(jù)產(chǎn)生的中斷,即發(fā)送從設(shè)備地址產(chǎn)生的中斷构挤。
  • 3).iicstat & (1<<0)表示主機沒有接受到ACK信號(即發(fā)出的設(shè)備地址不存在)髓介,需要停止傳輸。
  • 4).IICSTAT = 0xd0置IICSTAT寄存器的[5]寫為0筋现,以便發(fā)出P信號唐础,但是由于這時IICCON[4]仍為1,P信號沒有實際發(fā)出矾飞,當執(zhí)行IICCON &= ~(1<<4);清除IICCON[4]后一膨,P信號才真正發(fā)出。
  • 5).等待一段時間洒沦,確保P信號已經(jīng)發(fā)送完畢
  if (p_cur_msg->cnt_transferred < p_cur_msg->len)
    {
        /* 對于其他中斷, 要繼續(xù)發(fā)送下一個數(shù)據(jù)
         */
        IICDS = p_cur_msg->buf[p_cur_msg->cnt_transferred];
        IICCON &= ~(1<<4);
    }
    else
    {
        /* 停止傳輸 */
        IICSTAT = 0xd0;
        IICCON &= ~(1<<4);
        p_cur_msg->err = -1;
        delay(1000);
    }
}
  • 1).假如if (p_cur_msg->cnt_transferred < p_cur_msg->len)條件成立豹绪,表示數(shù)據(jù)還沒有發(fā)送完畢,需要繼續(xù)發(fā)送數(shù)據(jù)申眼。
  • 2).執(zhí)行IICDS = p_cur_msg->buf[p_cur_msg->cnt_transferred把要發(fā)送的數(shù)據(jù)寫入到IICDS寄存器中瞒津,經(jīng)過執(zhí)行IICCON &= ~(1<<4);清除中斷標志后后,緊接著就自動把數(shù)據(jù)發(fā)送出去了括尸,這將觸發(fā)下一個中斷巷蚪。
  • 3).如果條件不成立表示數(shù)據(jù)傳輸完畢,發(fā)出P信號姻氨,停止數(shù)據(jù)的傳輸钓辆。

寫操作:I2C讀操作的處理與寫操作類似,我們就不進行分析了肴焊,代碼如下:

else /* read */
{
    /* 對于第1個中斷, 它是發(fā)送出設(shè)備地址后產(chǎn)生的
     * 需要判斷是否有ACK
     * 有ACK : 設(shè)備存在, 恢復I2C傳輸, 這樣在下一個中斷才可以得到第1個數(shù)據(jù)
     * 無ACK : 無設(shè)備, 出錯, 直接結(jié)束傳輸
     */
    if (p_cur_msg->cnt_transferred == 0)  /* 第1次中斷 */
    {
        if (iicstat & (1<<0))
        { /* no ack */
            /* 停止傳輸 */
            IICSTAT = 0x90;
            IICCON &= ~(1<<4);
            p_cur_msg->err = -1;
            delay(1000);
            return;
        }
        else  /* ack */
        {
            /* 恢復I2C傳輸 */
            IICCON &= ~(1<<4);
            return;
        }
    }

    /* 非第1個中斷, 表示得到了一個新數(shù)據(jù)
     * 從IICDS讀出前联、保存
     */
    if (p_cur_msg->cnt_transferred < p_cur_msg->len)
    {
        index = p_cur_msg->cnt_transferred - 1;
        p_cur_msg->buf[index] = IICDS;
        /* 恢復I2C傳輸 */
        IICCON &= ~(1<<4);
    }
    else
    {
        /* 發(fā)出停止信號 */
        IICSTAT = 0x90;
        IICCON &= ~(1<<4);
        delay(1000);
    }
}

我們還要在s3c2440_i2c_con_init函數(shù)中設(shè)置IICCON寄存器,設(shè)置ACK應(yīng)答使能娶眷。

IICCON = (1<<7) | (0<<6) | (1<<5) | (30<<0);

六似嗤、EEPROM編程和測試代碼

從設(shè)備程序,只涉及到兩個函數(shù)分別是:從設(shè)備的寫函數(shù)届宠,從設(shè)備的讀函數(shù)烁落。下面下分析從設(shè)備的寫函數(shù),代碼如下:

#define AT24CXX_ADDR 0x50

int at24cxx_write(unsigned int addr, unsigned char *data, int len)
{
i2c_msg msg;
int i;
int err;
unsigned char buf[2];


for (i = 0; i < len; i++)
{
    buf[0] = addr++;
    buf[1] = data[i];
    
    /* 構(gòu)造i2c_msg */
    msg.addr  = AT24CXX_ADDR;
    msg.lags = 0; /* write */
    msg.len   = 2;
    msg.buf   = buf;
    msg.err   = 0;
    msg.cnt_transferred = -1;

    /* 調(diào)用i2c_transfer */
    err = i2c_transfer(&msg, 1);
    if (err)
        return err;
}

return 0;
}
  • 1).#define AT24CXX_ADDR 0x50宏定義設(shè)備地址豌注。
  • 2).我們每次只寫一個字節(jié)伤塌,所以我們需要構(gòu)造出len個msg。
  • 3).調(diào)用i2c接口函數(shù)轧铁,傳輸構(gòu)造i2C_msg結(jié)構(gòu)體每聪,我們傳輸指針只需要傳輸四個字節(jié),我們需要把以前的參數(shù)都改成傳輸指針的格式。
    從設(shè)備讀函數(shù)和寫函數(shù)類似药薯,讀函數(shù)需要構(gòu)造兩個i2c_msg(每個i2c_msg只能表示一個傳輸方向) 绑洛,因為在讀操作之前,需要把要讀的地址告訴從設(shè)備童本。

代碼如下:

int at24cxx_read(unsigned int addr, unsigned char *data, int len)
{
i2c_msg msg[2];
int err;

/* 構(gòu)造i2c_msg */
msg[0].addr  = AT24CXX_ADDR;
msg[0].lags  = 0; /* write */
msg[0].len   = 1;
msg[0].buf   = &addr;

msg[0].err   = 0;
msg[0].cnt_transferred = -1;

msg[1].addr  = AT24CXX_ADDR;
msg[1].lags  = 1; /* read */
msg[1].len   = len;
msg[1].buf   = data;
msg[1].err   = 0;
msg[1].cnt_transferred = -1;

/* 調(diào)用i2c_transfer */
err = i2c_transfer(&msg, 2);
if (err)
    return err;
return 0;
}

I2c_test測試程序如下所示:

void i2c_test(void)
{
char c;

/* 初始化 */
i2c_init();

while (1)
{
    /* 打印菜單, 供我們選擇測試內(nèi)容 */
    printf("[w] Write at24cxx\n\r");
    printf("[r] Read at24cxx\n\r");
    printf("[q] quit\n\r");
    printf("Enter selection: ");

    c = getchar();
    printf("%c\n\r", c);

    /* 測試內(nèi)容:
     * 3. 編寫某個地址
     * 4. 讀某個地址
     */
    switch (c)       
    {
        case 'q':
        case 'Q':
            return;
            break;
            
        case 'w':
        case 'W':
            do_write_at24cxx();
            break;

        case 'r':
        case 'R':
            do_read_at24cxx();
            break;
        default:
            break;
    }
}
}

1).調(diào)用i2c_controller.c里面的 i2c_init()初始化函數(shù)真屯,在這個函數(shù)中需要添加一些功能,i2c_init()代碼如下所示:

void i2c_init(void)
{
/* 注冊下面的I2C控制器 */
s3c2440_i2c_con_add();

/* 選擇某款I(lǐng)2C控制器 */
select_i2c_controller("s3c2440");

/* 調(diào)用它的init函數(shù) */
p_i2c_con_selected->init();
}

select_i2c_controller("s3c2440")用于選擇s3c2440的i2c控制器穷娱。
p_i2c_con_selected->init()調(diào)用s3c2440的i2c控制器結(jié)構(gòu)體中init初始化函數(shù)绑蔫,初始化s3c2440的i2c控制器。

2).執(zhí)行do_write_at24cxx()函數(shù)用于往at24cxx設(shè)備中寫入數(shù)據(jù)泵额,do_write_at24cxx()函數(shù)的代碼如下所示:

{
unsigned int addr;
unsigned char str[100];
int err;

/* 獲得地址 */
printf("Enter the address of sector to write: ");
addr = get_uint();

if (addr > 256)
{
    printf("address > 256, error!\n\r");
    return;
}

printf("Enter the string to write: ");
gets(str);

printf("writing ...\n\r");
err = at24cxx_write(addr, str, strlen(str)+1);
printf("at24cxx_write ret = %d\n\r", err);
}

addr = get_uint()用于把輸入的地址賦值給addr晾匠。
gets(str)用于把輸入的字符串存在str字符數(shù)組中。
at24cxx_write(addr, str, strlen(str)+1)調(diào)用at24cxx_write函數(shù)把輸入的數(shù)據(jù)str,梯刚,放在輸入的地址addr中。
3).執(zhí)行do_read_at24cxx()函數(shù)從at24cxx中讀取數(shù)據(jù)薪寓,do_read_at24cxx()函數(shù)的代碼如下所示:

void do_read_at24cxx(void)
{
unsigned int addr;
int i, j;
unsigned char c;
unsigned char data[100];
unsigned char str[16];
int len;
int err;
int cnt = 0;

/* 獲得地址 */
printf("Enter the address to read: ");
addr = get_uint();

if (addr > 256)
{
    printf("address > 256, error!\n\r");
    return;
}

/* 獲得長度 */
printf("Enter the length to read: ");
len = get_int();

err = at24cxx_read(addr, data, len);
printf("at24cxx_read ret = %d\n\r", err);

printf("Data : \n\r");
/* 長度固定為64 */
for (i = 0; i < 4; i++)
{
    /* 每行打印16個數(shù)據(jù) */
    for (j = 0; j < 16; j++)
    {
        /* 先打印數(shù)值 */
        c = data[cnt++];
        str[j] = c;
        printf("%02x ", c);
    }

    printf("   ; ");

    for (j = 0; j < 16; j++)
    {
        /* 后打印字符 */
        if (str[j] < 0x20 || str[j] > 0x7e)  /* 不可視字符 */
            putchar('.');
        else
            putchar(str[j]);
    }
    printf("\n\r");
}

調(diào)用at24cxx_read(addr, data, len)函數(shù)亡资,從addr地址中讀取len長度的字節(jié)數(shù)據(jù),放在data字符數(shù)組中向叉,后面的代碼就是把讀取得到的數(shù)據(jù)锥腻,打印出來。

七母谎、測試

在測試中瘦黑,出現(xiàn)問題和解決辦法:

  • a 中斷沒產(chǎn)生 : 未配置GPIO用于IIC功能
    解決方法: 配置引腳用于I2C
  • b. 只產(chǎn)生了一次中斷, 并且出錯 : tx err, no ack
    解決方法: 啟動傳輸之前 IICSTAT=(1<<4)
  • c. 第1次讀OK,再次寫卡死奇唤,復位再寫仍卡死幸斥,重新上電再寫OK:
    解決方法: 讀最后一個數(shù)據(jù)時,不要回應(yīng)ACK給AT24CXX

    程序框架如下圖所示:
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咬扇,一起剝皮案震驚了整個濱河市甲葬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌懈贺,老刑警劉巖经窖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異梭灿,居然都是意外死亡画侣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門堡妒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來配乱,“玉大人,你說我怎么就攤上這事∠芮洌” “怎么了的诵?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長佑钾。 經(jīng)常有香客問我西疤,道長,這世上最難降的妖魔是什么休溶? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任代赁,我火速辦了婚禮,結(jié)果婚禮上兽掰,老公的妹妹穿的比我還像新娘芭碍。我一直安慰自己,他們只是感情好孽尽,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布窖壕。 她就那樣靜靜地躺著,像睡著了一般杉女。 火紅的嫁衣襯著肌膚如雪瞻讽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天熏挎,我揣著相機與錄音速勇,去河邊找鬼。 笑死坎拐,一個胖子當著我的面吹牛烦磁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哼勇,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼都伪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了积担?” 一聲冷哼從身側(cè)響起院溺,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎磅轻,沒想到半個月后珍逸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡聋溜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年谆膳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撮躁。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡漱病,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杨帽,我是刑警寧澤漓穿,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站注盈,受9級特大地震影響晃危,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜老客,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一僚饭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧胧砰,春花似錦鳍鸵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至撤蚊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人硅蹦。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓署咽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 做單片機開發(fā)時UART,SPI和I2C都是我們最經(jīng)常使用到的硬件接口,我收集了相關(guān)的具體材料對這三種接口進行了詳細...
    梁睿坤閱讀 68,009評論 7 31
  • 1灌旧、嵌入式系統(tǒng)的定義 (1)定義:以應(yīng)用為中心,以計算機技術(shù)為基礎(chǔ)薄榛,軟硬件可裁剪硬猫,適應(yīng)應(yīng)用系統(tǒng)對功能衬横、可靠性拇泣、成本...
    榮卓然閱讀 1,823評論 0 5
  • 什么是嵌入式 IEEE(Institute of Electrical and Electronics Engin...
    Leon_Geo閱讀 3,707評論 1 20
  • 阿黃剛剛來到我們家時债朵,它的名字還不叫阿黃子眶,叫小鹿。小鹿是個男孩芝加,一段時間以來,對于小鹿這個名字,似乎并不感興...
    春風入夢鄉(xiāng)閱讀 182評論 0 2
  • 好的習慣養(yǎng)成難同廉,破壞卻很容易。每天堅持寫點東西柑司,天天寫也就不覺得堅持很難蟆湖,一旦中間斷了一天,破壞了這個習慣讼育,再接著...
    小芳Funny閱讀 99評論 0 0