|
一,為了那“可惡”的目的
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)
2014-12-19 02:37 上傳
點(diǎn)擊文件名下載附件
下載積分: 黑幣 -5
|
評(píng)分
-
查看全部評(píng)分
|