找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開(kāi)始

搜索
查看: 4498|回復(fù): 12
打印 上一主題 下一主題
收起左側(cè)

小白菜的IIC學(xué)習(xí)之路

  [復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:70650 發(fā)表于 2014-12-19 02:38 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
一,為了那“可惡”的目的
2011 年初,小白菜的工作比較清閑了,于是小白菜就開(kāi)始IIC 學(xué)習(xí)之路。
之前在用 IIC 總線(口線模擬)時(shí),總感覺(jué)IIC 移植時(shí)不夠簡(jiǎn)介易懂,使用時(shí),函數(shù)不
能夠適應(yīng)所有IIC 操作,于是小白菜想改寫(xiě)一下IIC 總線操作,使之成為真正的萬(wàn)能IIC 總
線驅(qū)動(dòng)。于是小白菜找到目標(biāo)了:
一是編寫(xiě)一個(gè)移植性極其好的 IIC 總線操作,只需要簡(jiǎn)單的改動(dòng)就能完成移植。
二是編寫(xiě)兩個(gè)函數(shù),一個(gè)是向器件發(fā)送數(shù)據(jù)函數(shù),一個(gè)是從器件中讀取數(shù)據(jù)函數(shù)。并且
這兩個(gè)函數(shù)真正適應(yīng)所有不同的IIC 器伯的操作。
二,過(guò)程抽象
為了達(dá)到以上的目的,小白菜需要先對(duì) IIC 進(jìn)行一次抽象。
1 單片機(jī)向器件發(fā)送數(shù)據(jù)時(shí)的抽象過(guò)程
(這里的抽象指的是抽象出不依賴具體器件的 IIC 操作)
發(fā)送時(shí)的流程:
(1) MCU 啟動(dòng)總線
(2) 發(fā)送器件IIC 地址 接收 ACK 信號(hào)
(3) 發(fā)送寄存器地址1  接收 ACK 信號(hào) [發(fā)送寄存器地址2  接收 ACK 信號(hào)]
有些器件可能沒(méi)有寄存器地址(小白菜還沒(méi)有遇到過(guò)),所以該步可能不需要。
(4) 發(fā)送數(shù)據(jù)1  接收 ACK 信號(hào)
 發(fā)送數(shù)據(jù)2  接收 ACK 信號(hào)
 發(fā)送數(shù)據(jù)3  接收 ACK 信號(hào)
……
(5) MCU 關(guān)閉總線,發(fā)送完成。
我們來(lái)具體的分析一下,
第(1)步啟動(dòng)總線,
第(2)步發(fā)送 1B 數(shù)據(jù)(IIC 地址),接收一個(gè)ACK 信號(hào)
第(3)步發(fā)送 1B 數(shù)據(jù)(寄存器地址1),接收一個(gè)ACK 信號(hào),發(fā)送1B 數(shù)據(jù)(寄存器地址2),
接收一個(gè)ACK 信號(hào)
第(4)步發(fā)送 1B 數(shù)據(jù)(數(shù)據(jù)1),接收一個(gè)ACK 信號(hào),發(fā)送1B 數(shù)據(jù)(數(shù)據(jù)2),接收一個(gè)
ACK 信號(hào)……
第(5)步關(guān)閉總線
寫(xiě)到這里,有些人可能看出來(lái)了,其實(shí)發(fā)送時(shí)不論數(shù)據(jù)或地址,都是一個(gè)相同過(guò)程,啟
動(dòng)總線后,MCU 發(fā)送一個(gè)字節(jié),等一個(gè)ACK 信號(hào),再發(fā)送一個(gè)字節(jié),等一個(gè)ACK 信號(hào),
發(fā)送……,哎、發(fā)送完了,得了,關(guān)閉總線。
繼續(xù)進(jìn)行抽象,我們可以把第(2)、(3)、(4)步進(jìn)行合并,得到IIC 寫(xiě)操作抽象:
第(1)步啟動(dòng)總線,
第(2) (3) (4)步發(fā)送 1B 數(shù)據(jù),接收一個(gè)ACK 信號(hào),直到發(fā)送完成
第(5)步關(guān)閉總線
到這里之后,似乎是大功告成了,但是小白菜一想,這么多數(shù)據(jù),參數(shù)肯定是用指針+
數(shù)據(jù)字節(jié)數(shù)還進(jìn)行傳遞,可是像讀寫(xiě)EEPROM 這樣的器件,地址就丙三字節(jié),但一次寫(xiě)入
的數(shù)據(jù)可能有很多個(gè)字節(jié),于是小白菜把(2) (3) 合在一起,把數(shù)據(jù)發(fā)送部分(4)單獨(dú)拿出來(lái)。
小白菜得到了最終的 IIC 寫(xiě)操作的抽象
第(1)步啟動(dòng)總線,
第(2) (3)步發(fā)送 1B 數(shù)據(jù),接收一個(gè)ACK 信號(hào),直到發(fā)送完成(發(fā)送地址)
第(4)步發(fā)送 1B 數(shù)據(jù),接收一個(gè)ACK 信號(hào),直到發(fā)送完成 (發(fā)送數(shù)據(jù))
第(5)步關(guān)閉總線
小白菜據(jù)此寫(xiě)出了函數(shù)名及形參和流程圖(就是上面的抽象,所以嘛,就不寫(xiě)了)。
extern uint8 IIC_MCU_Send_Str(uint8 *PAddr, uint8 AddrNum, uint8 *PDataAddr, uint16 DataNum)
*PAddr :I 第1 批發(fā)送的數(shù)據(jù)的首地址。這部分IC 地址以及子地址。PAddr[0]中存放IIC
地址,后面的存放子地址。
AddrNum :IIC 以及子地址的字節(jié)數(shù)。不可為0.
*PDataAddr :第2 批發(fā)送的數(shù)據(jù)的首地址。這部分是發(fā)送的數(shù)據(jù)。
DataNum :第2 批要發(fā)送的字節(jié)數(shù)(最大為65536 個(gè)字節(jié))。為0 時(shí)不發(fā)送這一部分。
2 單片機(jī)從有寄存器的IIC 器件讀取數(shù)據(jù)時(shí)的抽象過(guò)程:
發(fā)送時(shí)的流程:
(1) MCU 啟動(dòng)總線
(2) 發(fā)送器件IIC 地址 接收 ACK 信號(hào)
(3) 發(fā)送寄存器地址1  接收 ACK 信號(hào) [發(fā)送寄存器地址2  接收 ACK 信號(hào)]
有些器件可能沒(méi)有寄存器地址(小白菜還沒(méi)有遇到過(guò)),所以該步可能不需要。
(4) MCU 重新啟動(dòng)總線
(5) 發(fā)送器件IIC 地址(最低位置1 以表明是讀操作)  接收 ACK 信號(hào)
(6) 接收數(shù)據(jù)1  發(fā)送 ACK 信號(hào)
接收數(shù)據(jù)2  發(fā)送 ACK 信號(hào)
接收數(shù)據(jù)3  發(fā)送 ACK 信號(hào)
……
(7) 接收最后一字節(jié)數(shù)據(jù) 發(fā)送非ACK 信號(hào)
(8) MCU 關(guān)閉總線,接收完成。
根據(jù)該流程,我們可以清楚地得到讀取時(shí)的抽象:
第(1)步 MCU 啟動(dòng)總線
第(2) (3) 步發(fā)送 1B 數(shù)據(jù),接收一個(gè)ACK 信號(hào),直到發(fā)送完成(發(fā)送地址)
第(4) 步 MCU 重新啟動(dòng)總線
第(5) 步發(fā)送器件IIC 地址(最低位置1 以表明是讀操作)  接收 ACK 信號(hào)
第(6) 步接收前面的字節(jié),發(fā)送ACK 信號(hào)
第(7) 步接收最后一字節(jié)數(shù)據(jù),發(fā)送非ACK 信號(hào)
第(8) 步 MCU 關(guān)閉總線,接收完成。
雖然這里步驟多了,但是,函數(shù)參數(shù)也用不了幾個(gè),首先要知道地址吧,還要知道數(shù)據(jù)讀出
來(lái)后存放在哪里吧,小白菜想了想,這個(gè)函數(shù)的參數(shù)和寫(xiě)操作函數(shù)的參數(shù)一樣就行了,
于是小白菜據(jù)寫(xiě)出了函數(shù)名及形參和流程圖(就是上面的抽象,所以嘛,你懂得)
extern uint8 IIC_MCU_Rcv_Str(uint8 *PAddr, uint8 AddrNum, uint8 *PDataAddr, uint16
DataNum)
*PAddr :發(fā)送的數(shù)據(jù)的首地址。這部分IC 地址以及子地址。PAddr[0]中存放IIC 地址,
后面的存放子地址。
AddrNum :IIC 以及子地址的字節(jié)數(shù)。不可為0.
*PDataAddr :存放所接收數(shù)據(jù)的首地址
DataNum :要接收的字節(jié)數(shù)。
三 奮筆疾書(shū) + 代碼移植
小白菜開(kāi)始了寫(xiě)代碼了。因?yàn)槭切“撞寺铮砸婚_(kāi)始也不知道哪些地方在移植時(shí)需要
修改,于是就開(kāi)始先寫(xiě)代碼,可能在寫(xiě)的時(shí)候就能知道了。寫(xiě)啊寫(xiě),寫(xiě)啊寫(xiě),終于讓小白菜
寫(xiě)完了。
寫(xiě)著寫(xiě)著還真讓小白菜找出了哪里需要移植了?诰需要更改吧,不同的單片機(jī),頭
文件不一樣吧,還得有延時(shí)函數(shù)需要改吧……
于是小白菜把在移植時(shí)需要更改的地方做了一個(gè)“表”,放在H 文件中的“移植修改”
部分。這樣,在修改時(shí)就可以只修改H 文件中的一個(gè)地方,就能快速的完成移植。
現(xiàn)在想想,小白菜有的時(shí)候也不是那么菜嘛(偷笑 ing)。
到現(xiàn)在為止,小白菜的目的已經(jīng)達(dá)到了。突然,后背一涼,心里一個(gè)想法冒出來(lái)了,
剛寫(xiě)完的代碼還沒(méi)測(cè)試就飄飄然了!哎,被勝利沖昏了頭腦。于是小白菜自已測(cè)試了一番,
Bug 還真是有,改改更健康~~
四使用說(shuō)明
4.1 移植修改
移植修改都在 H 文件中的移植修改部分。下面進(jìn)行具體說(shuō)明。
//----------------------------------------------------------------------------//
// 編號(hào):1
// 名稱:
// 功能:?jiǎn)纹瑱C(jī)寄存器頭文件,例如reg51.h
//----------------------------------------------------------------------------//
#include "ATT703x.H"
4.1.1 這部分是請(qǐng)您把使用的單片機(jī)的頭文件包含進(jìn)來(lái)。大蝦們用過(guò)MCU 多了,知道不同
的MCU,其寄存器定義是不一樣滴,不是所有的51 單片機(jī)都用Reg51.H 或Reg52.H 頭文
件的。
//----------------------------------------------------------------------------//
// 編號(hào):2
// 名稱:SDA, SCL
// 功能:模擬I2C 數(shù)據(jù)傳送位
//----------------------------------------------------------------------------//
#if defined(IIC_IO_ENABLE)
sbit SDA = P0^0; // 模擬I2C 數(shù)據(jù)傳送位。
sbit SCL = P2^6; // 模擬I2C 時(shí)鐘控制位。
#endif
4.1.2 SDA 和SCL 口線定義。這里就是您用的口線,如果您告訴我您不知道怎么改,好吧,
你贏了……
//----------------------------------------------------------------------------//
// 編號(hào):3
// 名稱:IIC_Delay_1US()
// 功能:精確的1 微秒延時(shí)函數(shù)。請(qǐng)根據(jù)您所用的單片機(jī)來(lái)正確設(shè)置。
// :如果您的系統(tǒng)中有精確的微妙級(jí)延時(shí)函數(shù),那么您可以直接使用。
// :例如,您的延時(shí)函數(shù)是Delay_1us(),那么您可以使用下句
// :#define IIC_Delay_1us() Delay_1us()
// :來(lái)實(shí)現(xiàn)延時(shí)。
//----------------------------------------------------------------------------//
#defineIIC_Delay_500ns() _nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
#define IIC_Delay_1US() IIC_Delay_500ns();IIC_Delay_500ns();
4.1.3 軟件延時(shí)函數(shù),這里是標(biāo)準(zhǔn)IIC,不是快速IIC。1us 的延時(shí)怎么做呢?當(dāng)然是nop 函
數(shù)了。如果您不知道一個(gè)nop 的執(zhí)行時(shí)間,那么說(shuō)明您需要好好看看手冊(cè)了。
4.1.4 好多單片機(jī)都需要設(shè)置時(shí)鐘,設(shè)置GPIO 狀態(tài),所以在使用IIC 之前,請(qǐng)一定確保MCU
先初始化完畢。
4.2 函數(shù)說(shuō)明
4.2.1 MCU 向IIC 器件發(fā)送多字節(jié)數(shù)據(jù)函數(shù)
//----------------------------------------------------------------------------//
// MCU 向IIC 器件發(fā)送多字節(jié)數(shù)據(jù)函數(shù)(對(duì)外提供服務(wù))
//函數(shù)名稱:IIC_MCU_Send_Str
//函數(shù)功能:MCU 向IIC 從器件發(fā)送多字節(jié)數(shù)據(jù)。本函數(shù)是寫(xiě)IIC 從器件的抽象函數(shù)。
//入口參數(shù):
// *PAddr: IIC 地址以及子地址。PAddr[0]中存放IIC 地址,后面的存放子地址。
// AddrNum : IIC 以及子地址的字節(jié)數(shù)。不可為0.
//
// *PDataAddr: 第 2 批發(fā)送的數(shù)據(jù)的首地址。這部分是發(fā)送的數(shù)據(jù)。
// DataNum : 第 2 批要發(fā)送的字節(jié)數(shù)(最大為65536 個(gè)字節(jié))。為0 時(shí)不發(fā)送這一部分。
//出口參數(shù):0 = 操作成功,1 = 操作出錯(cuò)。
//重要說(shuō)明:這是一個(gè)從啟動(dòng)IIC 總線到發(fā)送數(shù)據(jù)再到最后結(jié)束總線為止的完整的發(fā)送過(guò)程。
// 數(shù)據(jù)發(fā)送的順序是先發(fā)送PAddr[0],最后發(fā)送PAddr[AddrNum - 1],然后發(fā)送
// PDataAddr[0],最后發(fā)送PDataAddr[DataNum - 1]。
// 一般地,PAddr 用于發(fā)送器件IIC 地址和子地址,PDataAddr 用于發(fā)送數(shù)據(jù)。
// 本函數(shù)對(duì)有無(wú)子地址的IIC 器件都適用。
//----------------------------------------------------------------------------//
extern uint8 IIC_MCU_Send_Str(uint8 *PAddr, uint8 AddrNum, uint8 *PDataAddr, uint16
DataNum)
應(yīng)用示例:
從 0x00 字節(jié)地址開(kāi)始寫(xiě)AT24C02,寫(xiě)入10 個(gè)字節(jié)數(shù)(這里不考慮頁(yè)寫(xiě)等待,因?yàn)楹?br /> IIC 寫(xiě)無(wú)關(guān)),這10B 數(shù)據(jù)存放在unsigned char Buf[10]中,寫(xiě)入時(shí)要求Buf[0]寫(xiě)入0x00 字節(jié)
地址,Buf[1]寫(xiě)入0x01 字節(jié)地址……。
A2、A1、A0 全接地。(有人說(shuō)我沒(méi)說(shuō)明WP 的接法……我只有一個(gè)問(wèn)題,你是來(lái)砸場(chǎng)
子的么。。。
首先組織 IIC 地址,設(shè)置一數(shù)組unsigned char Addr[2],其中Addr[0] = 0xA0,Addr[1] = 0x00;
Addr [0]中存放的是(二進(jìn)制表示) 1 0 1 0 A2 A1 A0 0(LSB)
Addr [1]中存放的是(二進(jìn)制表示) a7 a6 a5 a4 a3 a2 a1 a0(LSB)
調(diào)用時(shí) IIC_MCU_Send_Str(Addr, 2, Buf, 10);
您還應(yīng)當(dāng)查看一下函數(shù)的返回值,是0 表示操作成功,否則操作失敗。
4.2.1 MCU 從有子地址的IIC 器件中接收多字節(jié)函數(shù)
//----------------------------------------------------------------------------//
// MCU 從有子地址的IIC 器件中接收多字節(jié)函數(shù)(對(duì)外接口)
//函數(shù)名稱:IICMCURcvStr
//函數(shù)功能:本函數(shù)用于有子地址的IIC 器件的讀操作。
//入口參數(shù):
// *PAddr: IIC 地址以及子地址。PAddr[0]中存放IIC 地址,后面的存放子地址。
// AddrNum : IIC 以及子地址的字節(jié)數(shù)。為0 時(shí)出錯(cuò).
// *PDataAddr:存放所接收數(shù)據(jù)的首地址
// DataNum : 要接收的字節(jié)數(shù)。合法值1-65535。為0 時(shí)出錯(cuò)。
//
//出口參數(shù):0 = 操作成功,1 = 操作出錯(cuò)。
//重要說(shuō)明:
// 讀取的第一個(gè)數(shù)據(jù)存放在PDataAddr[0]中,第一個(gè)存放在PDataAddr[1]中……
// 有子地址的IIC 器件的讀操作是:
// MCU 先啟動(dòng)總線,然后發(fā)送器件的IIC 地址和需要操作的子地址
//(這一部分就是*PAddr),之后重新啟動(dòng)總線,再次發(fā)送器件的IIC 地址且最低位置1 以表
明是讀
// 操作,等待應(yīng)答后便開(kāi)始接收數(shù)據(jù)(這一部分是*PDataAddr),最后關(guān)閉總線。
//----------------------------------------------------------------------------//
extern uint8 IIC_MCU_Rcv_Str(uint8 *PAddr, uint8 AddrNum, uint8 *PDataAddr, uint16
DataNum)
讀取的第一個(gè)數(shù)據(jù)存放在 PDataAddr[0]中,第一個(gè)存放在PDataAddr[1]中……
應(yīng)用示例:
從 0x00 字節(jié)地址開(kāi)始讀AT24C02,讀10 個(gè)字節(jié)數(shù)并且存放在unsigned char Buf[10]中,
A2、A1、A0 全接地。(有人說(shuō)我沒(méi)說(shuō)明WP 的接法……還提這個(gè)問(wèn)題。。
首先組織 IIC 地址,設(shè)置一數(shù)組unsigned char Addr[2],其中Addr[0] = 0xA0,Addr[1] = 0x00;
Addr [0]中存放的是(二進(jìn)制表示) 1 0 1 0 A2 A1 A0 0(LSB)
Addr [1]中存放的是(二進(jìn)制表示) a7 a6 a5 a4 a3 a2 a1 a0(LSB)
調(diào)用時(shí) IIC_MCU_Rcv_Str (Addr, 2, Buf, 10);
這樣,Buf[0]是讀取到的0x00 字節(jié)地址的數(shù)據(jù),Buf[1]是0x01 字節(jié)地址的數(shù)據(jù)……
您還應(yīng)當(dāng)查看一下函數(shù)的返回值,是 0 表示操作成功,否則操作失敗。
五 最后的廢話
好吧,有人會(huì)說(shuō)這個(gè)實(shí)現(xiàn)的有點(diǎn)羅嗦,不如直接以IIC 地址,寄存器地址做參數(shù)來(lái)的方
便;雖然有時(shí)候我也這么覺(jué)得。
但是、但是、但是什么呢?下期見(jiàn)!
3htech
我是一顆小白菜

程序下載: IIC.rar (94 KB, 下載次數(shù): 53)

評(píng)分

參與人數(shù) 1黑幣 +5 收起 理由
ssfc + 5 很給力!

查看全部評(píng)分

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏4 分享淘帖 頂 踩
回復(fù)

使用道具 舉報(bào)

沙發(fā)
ID:72947 發(fā)表于 2015-1-31 21:25 來(lái)自手機(jī) | 只看該作者
樓下高手,謝謝分享!
回復(fù)

使用道具 舉報(bào)

板凳
ID:29438 發(fā)表于 2015-2-7 09:02 | 只看該作者
只能說(shuō)謝謝。
回復(fù)

使用道具 舉報(bào)

地板
ID:70330 發(fā)表于 2015-2-10 11:20 | 只看該作者
只能說(shuō)謝謝了
回復(fù)

使用道具 舉報(bào)

5#
ID:70330 發(fā)表于 2015-2-10 11:20 | 只看該作者
樓主強(qiáng)大,學(xué)習(xí)了
回復(fù)

使用道具 舉報(bào)

6#
ID:73775 發(fā)表于 2015-2-21 16:47 | 只看該作者
只能說(shuō)謝謝。
回復(fù)

使用道具 舉報(bào)

7#
ID:73775 發(fā)表于 2015-2-21 16:53 | 只看該作者
學(xué)習(xí)一下,看看效果
回復(fù)

使用道具 舉報(bào)

8#
ID:72238 發(fā)表于 2015-2-23 19:50 | 只看該作者
謝謝樓主
學(xué)習(xí)一下
回復(fù)

使用道具 舉報(bào)

9#
ID:79694 發(fā)表于 2016-3-6 08:57 | 只看該作者

謝謝樓主
學(xué)習(xí)一下
回復(fù)

使用道具 舉報(bào)

10#
ID:110516 發(fā)表于 2016-9-29 13:16 | 只看該作者
好用, 學(xué)習(xí)了
回復(fù)

使用道具 舉報(bào)

11#
ID:128463 發(fā)表于 2017-4-4 12:03 | 只看該作者
謝謝分享。
回復(fù)

使用道具 舉報(bào)

12#
ID:383525 發(fā)表于 2019-7-16 10:03 | 只看該作者
感謝樓主
回復(fù)

使用道具 舉報(bào)

13#
ID:585455 發(fā)表于 2019-7-17 20:00 | 只看該作者
感謝再感謝。
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表