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