轉自:http://blog.sina.com.cn/s/blog_61ebc5f30100fpdy.html
1.4 AT24LC08 讀寫流驅動開發
AT24LC08 是一款 I2C 接口的 EEPROM 晶片,容量為 8Kbit ,内部分 4 個 Page ,每個 Page 有 256B 。通路 AT24LC08 上的位址空間需要 10 位位址線。編寫 AT24LC08 晶片的讀寫驅動需要了解它的讀寫方式和 I2C 總線的通路時序,這裡以 Micro2440 平台為例介紹 AT24LC08 工作過程。該驅動編寫的總體流程與 1.3 類似。
1.4.1 A T24LC08 工作原理
AT24LC08 的主要管腳定義如表 1 所示。其中 A2 是和硬體上的連接配接一緻的; A1 、 A0 是用于選擇 Page 頁; SDA 用于雙向的串行資料傳輸; SCL 是串行時鐘線,時鐘上升沿時,資料從總線進入 EEPROM ,時鐘下降沿時,資料從 EEPROM 傳到總線; WP 信号接地,即采用普通的讀寫方式。
表 1 AT24LC08 主要引腳描述
引腳名稱 | 功能 |
A0 A1 A2 | 位址輸入 |
SDA | 串行資料 |
SCL | 串行時鐘輸入 |
WP | 寫保護 |
按晶片的工作流程順序,分别分析器件尋址、寫操作、讀操作三個過程。
l 器件尋址
AT24LC08 尋址的位址部分包括: 8bit 的器件位址和 8bit 的字位址。器件位址的各 bit 位分布如表 2 所示。前四位是由晶片廠商确定的,為固定值。 A2 必須和硬體的輸入管腳一緻。 P1 、 P0 為頁選位址。 R/W 是讀寫控制位, 0 表示寫操作, 1 表示讀操作。
表 2 AT24LC08 器件位址
1 | 1 | A2 | P1 | P0 | R/W |
8 位字位址用于尋址目前頁中的某個位址空間, 8 位對應 256 個位元組。
在晶片讀寫操作開始時,首先要依次往總線上發送器件位址和字位址,尋址正确才能進行後續的讀寫操作。
l 寫操作
在 Micro2440 開發闆上,我們定義 S3C2440 為主裝置, AT24LC08 晶片為從裝置。主裝置會發送 START 信号, STOP 信号;從裝置會回複 ACK 信号。
如圖 3 所示,當 SCL 為高電平時, SDA 信号被拉低,即出現 START 信号;當 SCL 為高電平時, SDA 信号由低變高,即為 STOP 信号。
圖 3 啟動、停止條件
當主裝置發送完一個位元組的資料,從裝置會在下一個時鐘周期回複一個 ACK ,即一個低電平信号,如圖 4 所示。
圖 4 輸出 ACK 信号
在 AT24LC08 的寫操作過程中(以頁寫模式為例),其操作流程如圖 5 所示。首先主裝置發出 START 信号,緊接着發送 8 位器件位址和 8 位字位址。器件位址最末位為 WRITE ( 0 )。從裝置驗證完位址并發回 ACK 信号後,主機就開始發送資料。從裝置每收到一個位元組的資料就傳回一個 ACK 。發送結束後,主機發出 STOP 信号,至此,一個完整的寫操作結束。
需要注意的是:頁寫模式最多支援 16 個位元組的寫操作。按字位址低四位逐次累加,當累加到 1111 時,下一個時鐘周期位址翻轉到 0000 ,新寫入的資料将覆寫之前的值。
圖 5 頁寫資料順序
l 讀操作
以頁讀模式為例分析讀操作的過程:首先主機發出 START 信号,依次發送器件位址和字位址,此時的讀寫控制位為 WRITE 。從機收到位址傳回 ACK 。然後,主機再發送 START 信号,再傳器件位址,此時的讀寫控制位為 READ 。主機等到從機傳回的 ACK ,就開始讀資料。讀到最後一個位元組時,從機傳回一個 NACK 信号,主機發出 STOP ,标志讀過程結束。
同樣,頁讀模式也支援最多 16 個位元組的操作。按字位址的低四位累加,如果溢出就翻轉到 0000 位址開始讀。
圖 6 頁讀資料順序
1.4.2 驅動程式架構和通路流程
l 體系結構和通路流程
在流式接口驅動程式中,驅動程式負責把外設抽象成一個檔案,而應用程式則使用作業系統提供的檔案 API 對外設進行通路。檔案 API 被作業系統轉發到 FileSys.exe 程序中;然後 FileSys.exe 發現是對裝置的操作,就會把執行交給 Device.exe 處理;接着 Device.exe 根據具體的請求,調用流驅動接口函數;最終,驅動程式負責與硬體互動。
圖 7 流接口驅動體系結構
具體過程分析如下:
(1) 應用程式須使用該裝置,首先調用 CreateFile ( TEXT (“ IIC1 ”)…)打開裝置。 CreateFile 函數是在 FileSys.exe 中實作的。但是 FileSys.exe 隻作簡單判斷:如果發現打開的是裝置驅動而不是一個檔案,那麼就重新把主動權交還給裝置管理器。
(2) 裝置管理器調用驅動程式中的 IIC_Open ()函數打開裝置。在 IIC_Open 中,驅動程式可以對硬體進行一些額外的初始化工作,使硬體進入工作狀态。
(3) IIC_Open ()函數把打開裝置的結果傳回給裝置管理器。
(4) 裝置管理器把 IIC_Open ()傳回的結果再傳給應用程式中的 CreateFile ()函數調用。
(5) 裝置已被成功打開,接下來可對裝置進行讀寫和控制操作。以控制操作為例, CreateFile 函數傳回的句柄作為 DeviceIoControl ()的第 1 個參數,向裝置發送控制請求。同樣, DeviceIoControl ()要 FileSys.exe 轉發給裝置管理器。
(6) 裝置管理器調用驅動程式中的 IIC_IOControl ()函數,與硬體完成互動,讀寫裝置的資料資訊,然後傳回給裝置管理器,再傳回給應用程式。
(7) 當應用程式不再使用該裝置時,它可調用 CloseHandle ()将裝置關閉。此時調用的是驅動程式中的 IIC_Close ()函數。
l 驅動目錄組織
在 AT24LC08 讀寫驅動中,根據通路和互動的對象不同,将驅動程式目錄組織如下:
Ø IICBus.c 用于和使用者态程式的互動,提供流驅動接口函數;
Ø IIC2440.c 和 S3C2440 的硬體互動,完成寄存器的配置;
Ø 24LC08.c 對 AT24LC08 晶片操作,按照晶片的讀寫模式配置。
1.4.3 流驅動接口函數的實作
在本驅動中,定義字首為“ IIC ”, IICBus.c 檔案中的流接口驅動函數都以該字首命名,本驅動中流接口重點實作以下 3 個函數:
BOOL IIC_Init(DWORD dwcontext)
BOOL IIC_Deinit(DWORD hDeviceContext)
BOOL IIC_IOControl(DWORD hOpenContext, DWORD dwCode,
EEPROM_INFO *pBufIn, DWORD dwLenIn,
U8 *pBufOut, DWORD dwLenOut, PDWORD pdwActualOut)
IIC_Init ()函數
該函數在裝置加載時被調用,具體代碼實作:
BOOL IIC_Init(DWORD dwcontext)
{
RETAILMSG(DBG_OUT,(TEXT("IIC_Init--- /r/n")));
Virtual_Alloc();
Setup_IIC();
return TRUE;
}
Setup_IIC ()函數完成一些端口的初始化,以及 IIC 總線寄存器的初始化配置。
Setup_IIC()
{
RETAILMSG(DBG_OUT,(TEXT("IIC_INTR_Gpio_setting --- /r/n")));
s2440IOP->rGPEUP |= 0xc000; //Pull-up disable
s2440IOP->rGPECON=(s2440IOP->rGPECON&~(3<<30))|(2 << 30); //GPE15=IICSDA.
s2440IOP->rGPECON=(s2440IOP->rGPECON&~(3 << 28))|(2 << 28); //GPE14=IICSCL.
s2440IIC->rIICCON = (1<<7) | (0<<6) | (1<<5) | (0xf); //Tx clock = 0.195MHz
//[Bit7] = Enable ACK, [Bit6] = Prescaler IICCLK=PCLK/16, [Bit5] = Enable interrupt;
//[Bit3:0] = Transmit clock value Tx clock=IICCLK/16
s2440IIC->rIICSTAT = 0x10; //IIC bus data output enable(Rx/Tx)
s2440IIC->rIICADD = 0x10; //2440 slave address = [7:1]
s2440IIC->rIICLC = (1<<2)|(3); // Filter enable, 15 clocks SDA output delay
return TRUE;
}
IIC_Deinit ()函數
該函數在裝置解除安裝時被調用,釋放加載時申請的虛拟位址空間,具體代碼實作:
BOOL IIC_Deinit(DWORD hDeviceContext)
{
RETAILMSG(DBG_OUT, (TEXT("IIC_INTR_Deinit --- /r/n")));
VirtualFree((void*)s2440IOP, sizeof(IOPreg), MEM_RELEASE);
VirtualFree((void*)s2440IIC, sizeof(PWMreg), MEM_RELEASE);
return TRUE;
}
IIC_IOControl ()函數
該函數用于處理應用程式發送過來的控制指令,控制指令 dwCode 有 IOCTL_IIC_READ 和 IOCTL_IIC_WRITE 兩種。當收到讀指令 IOCTL_IIC_READ 時,儲存讀操作的起始位址和讀取位元組數,然後調用 E2P_Read ()函數讀取 AT24LC08 上的資料,将實際讀取的位元組數儲存到 *pdwActualOut 。同樣,當收到寫指令 IOCTL_IIC_WRITE 時,儲存寫操作的起始位址和寫位元組數,然後調用 E2P_PageWrite ()函數往 AT24LC08 上寫資料,将實際寫入的位元組數儲存到 *pdwActualOut 。
BOOL IIC_IOControl(DWORD hOpenContext, DWORD dwCode,
EEPROM_INFO *pBufIn, DWORD dwLenIn,
U8 *pBufOut, DWORD dwLenOut, PDWORD pdwActualOut)
{
RETAILMSG(DBG_OUT,(TEXT("IIC: +IIC_IOControl (%d) /r/n"), dwCode));
switch(dwCode){
case IOCTL_IIC_READ:
{
U16 ReadAddress = pBufIn->start_address;
U8 ReadNum = (U8)dwLenOut; //ReadNum 為要讀取的位元組數
*pdwActualOut = E2P_Read(ReadAddress, ReadNum, pBufOut);
return TRUE;
}
case IOCTL_IIC_WRITE:
{
U16 WriteAddress = pBufIn->start_address;
U8 WriteNum = (U8)dwLenIn;
*pdwActualOut = E2P_Write(WriteAddress, WriteNum, pBufIn->dwData);
return TRUE;
}
}
return TRUE;
}
E2P_Read ()函數和 E2P_Write ()函數都是對 AT24LC08 操作的,在 24LC08.c 中實作,具體代碼如下:
U8 E2P_Read(U16 Address, U8 Num, U8 *DataRead)
{
U8 DevAddr_W, DevAddr_R;
U8 PageAddr, WordAddr;
U8 ActualNum;
int i;
if(Address > END_ADDRESS)
{
RETAILMSG(1,(TEXT("IIC: START ADDRESS ERROR!!!")));
return 0;
}
if((Address + Num) > END_ADDRESS) // 超出存儲器容量
{
ActualNum =(U8)(END_ADDRESS - Address + 1);
}
else
ActualNum = Num;
memset(&E2pInfo, 0, sizeof(IIC_INFO));
WordAddr = (U8)(Address & 0x00ff); // 擷取字位址
PageAddr = (Address >> 8) & 0x03; // 擷取 A1, A0 頁位址
DevAddr_W = 0xa0 | (PageAddr << 1); //DevAddr = 0x 1 0 1 0 A2 A1 A0 R/W
E2pInfo.iicMode = SETRDADDR;
E2pInfo.iicPt = 0;
E2pInfo.iicData[0] = WordAddr;
E2pInfo.iicDataCount = 1;
E2pInfo.DeviceAddress = DevAddr_W;
IIC_TX(&E2pInfo);
memset(&E2pInfo, 0, sizeof(IIC_INFO));
DevAddr_R = 0xa1 | (PageAddr << 1);
E2pInfo.iicMode = RDDATA;
E2pInfo.iicPt = 0;
E2pInfo.iicDataCount = ActualNum; // 讀取的位元組數
E2pInfo.DeviceAddress = DevAddr_R;
IIC_RX(&E2pInfo);
for(i = 0; i < ActualNum; i++)
*DataRead++ = *data++;
return ActualNum;
}
E2P_Read ()函數功能如下:
(1) 判斷讀入的位址參數是否有效,若無效,則退出;
(2) 判斷所讀的資料長度是否會超出存儲器的位址空間,截取實際能讀取的資料長度儲存;
(3) 将輸入的 16 位位址轉換成所需的 8 位器件位址和 8 位字位址;
(4) 把讀寫模式指令、器件位址、字位址、可讀取的實際位元組數儲存到結構體 E2pInfo 中,傳遞給 IIC_TX ()函數和 IIC_RX ()函數實作 IIC 總線上的資料收發。
(5) 傳回讀取的實際位元組數;
U8 E2P_Write(U16 Address, U8 Num, U8 *DataToWrite)
{
U8 *DataTmp, ActualNum;
U16 Address_End;
U8 FirstSector, SectorNum, EndSector;
int i;
if(Address > END_ADDRESS)
{
RETAILMSG(1,(TEXT("IIC: START ADDRESS ERROR!!!")));
return 0;
}
if((Address + Num) > END_ADDRESS) // 超出存儲器容量
{
ActualNum = (U8)(END_ADDRESS - Address + 1);// 擷取實際能寫入的字元數
}
else
ActualNum = Num;
FirstSector = MAX_BUF - (U8)(Address & 0x000f) ; // 計算第一頁能存的位元組數
if(ActualNum > FirstSector) // 判斷是否能在第一頁存完資料
{
SectorNum = (ActualNum - FirstSector) / MAX_BUF;
DataTmp = DataToWrite + FirstSector;
E2P_PageWrite(Address, FirstSector, DataToWrite); // 寫第一頁資料
for(i = 0; i < SectorNum; i++) // 按每頁 16B 寫資料
E2P_PageWrite(Address + FirstSector + i* MAX_BUF, MAX_BUF, DataTmp + i*MAX_BUF);
Address_End = Address + FirstSector + SectorNum* MAX_BUF;// 擷取末頁位址
EndSector = (ActualNum - FirstSector) % MAX_BUF;// 擷取末頁待寫位元組數
E2P_PageWrite(Address_End, EndSector, DataTmp + SectorNum*MAX_BUF);
}
else
E2P_PageWrite(Address, ActualNum, DataToWrite); // 寫第一頁資料
return ActualNum;
}
E2P_Write ()函數功能如下:
(1) 判斷寫操作起始位址參數是否有效,若無效,則退出;
(2) 判斷寫資料長度是否會超出存儲器的位址空間,截取實際能寫的資料長度儲存;
(3) 計算起始位址所在頁能寫的位元組數;
(4) 判斷實際要寫的資料數是否超出第一頁,如沒有,則直接調用 E2P_PageWrite ()函數完成頁寫操作;
(5) 若超出,則判斷需分幾頁寫,分别調用 E2P_PageWrite ()函數按頁寫資料;
(6) 傳回實際寫入的資料位元組數。
void E2P_PageWrite(U16 Address, U8 Num, U8 *SectorData)
{
U8 DevAddr_W;
U8 PageAddr, WordAddr;
int i;
memset(&E2pInfo, 0, sizeof(IIC_INFO));
WordAddr = (U8)(Address & 0x00ff); // 擷取字位址
PageAddr = (Address >> 8) & 0x03; // 擷取 A1, A0 頁位址
DevAddr_W = 0xa0 | (PageAddr << 1); //DevAddr = 0x 1 0 1 0 A2 A1 A0 R/W
E2pInfo.iicMode = WRDATA;
E2pInfo.iicPt = 0;
E2pInfo.iicData[0] = WordAddr;
E2pInfo.DeviceAddress = DevAddr_W;
for(i =0; i < Num; i++)
{
E2pInfo.iicData[i+1] = *(SectorData + i);
}
E2pInfo.iicDataCount = 1+Num;
IIC_TX(&E2pInfo);
}
E2P_PageWrite ()函數主要完成位址轉換和傳遞結構體 E2pInfo 的過程,然後調用 IIC_TX ()函數實作 IIC 總線上的資料發送。
IIC_TX ()函數和 IIC_RX ()函數在 IIC2440.c 中實作,代碼如下:
void IIC_TX(IIC_INFO *piicinfo)
{
g_iicinfo = piicinfo;
s2440IIC->rIICDS = g_iicinfo->DeviceAddress;
s2440IIC->rIICSTAT = 0xf0; //MasTx,Start
s2440IIC->rIICCON = 0xAf;
//Clearing the pending bit isn't needed because the pending bit has been cleared.
while(g_iicinfo->iicDataCount!=-1)
Run_IICPoll();
return;
}
void IIC_RX(IIC_INFO *piicinfo)
{
g_iicinfo = piicinfo;
s2440IIC->rIICDS = g_iicinfo->DeviceAddress;
s2440IIC->rIICSTAT = 0xb0; //MasRx,Start
s2440IIC->rIICCON = 0xaf; //Resumes IIC operation.
while(g_iicinfo->iicDataCount!=-1)
Run_IICPoll();
data = g_iicinfo->iicData + 1 ;
return;
}
這兩個函數都是完成和 S3C2440 的 IIC 寄存器相關的操作,按照 IIC 協定的時序依次往寄存器中器件位址和相關值,然後調用 Run_IICPoll ()函數逐個位元組傳遞資料。
void Run_IICPoll(void)
{
// When using polling mode
if(s2440IIC->rIICCON & 0x10) //Tx/Rx Interrupt Enable
IICPoll();
}
void IICPoll(void)
{
switch(g_iicinfo->iicMode)
{
case RDDATA:
if((g_iicinfo->iicDataCount--)==0)
{
g_iicinfo->iicData[g_iicinfo->iicPt++] = s2440IIC->rIICDS;
s2440IIC->rIICSTAT = 0x90; //Stop MasRx condition
s2440IIC->rIICCON = 0xAf; //Resumes IIC operation.
Delay(10); //Wait until stop condtion is in effect., Too long time...
//The pending bit will not be set after issuing stop condition.
break;
}
g_iicinfo->iicData[g_iicinfo->iicPt++] = s2440IIC->rIICDS;
//The last data has to be read with no ack.
if((g_iicinfo->iicDataCount)==0)
s2440IIC->rIICCON = 0x2f; //Resumes IIC operation with NOACK
else
s2440IIC->rIICCON = 0xAf; //Resumes IIC operation with ACK
break;
case WRDATA:
if((g_iicinfo->iicDataCount--)==0)
{
s2440IIC->rIICSTAT = 0xd0; //stop MasTx condition
s2440IIC->rIICCON = 0xAf; //resumes IIC operation.
Delay(10); // we should adjust this time.
//The pending bit will not be set after issuing stop condition.
break;
}
s2440IIC->rIICDS=g_iicinfo->iicData[g_iicinfo->iicPt++];
Delay(1); //for setup time until rising edge of IICSCL
s2440IIC->rIICCON = 0xAf; //resumes IIC operation.
break;
case SETRDADDR:
if((g_iicinfo->iicDataCount--)==0)
{
break; //IIC operation is stopped because of IICCON[4]
}
s2440IIC->rIICDS = g_iicinfo->iicData[g_iicinfo->iicPt++];
Delay(1); //for setup time until rising edge of IICSCL
s2440IIC->rIICCON = 0xAf; //resumes IIC operation.
break;
default:
break;
}
}
當 S3C2440 的 rIICCON 寄存器中斷位使能, Run_IICPoll ()函數調用 IICPoll ()函數。 IICPoll ()函數實作三種模式的資料傳輸:
( 1 ) SETRDADDR
當執行讀操作時,主機往從機寫入第一個位址時使用該模式,隻寫兩個位元組資料(器件位址和字位址),然後退出。
( 2 ) RDDATA
當執行讀操作,主機發完起始位址後,進入 RDDATA 模式,位址依次累加讀取 AT24LC08 上的資料,最完最後一個位元組時,回複 NACK ,主機收到後發 STOP 信号退出。
( 2 ) WRDATA
當執行寫操作時進入 WRDATA 模式,主機往從機發送器件位址和字位址,然後位址依次累加往 AT24LC08 寫入資料,最後主機發 STOP 信号退出。
1.4.4 添加 makefile 檔案和 Source 檔案
(一) makefile 檔案
!INCLUDE $(_MAKEENVROOT)/makefile.def
(二) Source 檔案
RELEASETYPE=PLATFORM
TARGETNAME=IICBus
TARGETTYPE=DYNLINK
DLLENTRY=DllEntry
TARGETLIBS= /
$(_COMMONSDKROOT)/lib/$(_CPUINDPATH)/coredll.lib /
MSC_WARNING_LEVEL=$(MSC_WARNING_LEVEL) /W3 /WX
INCLUDES= /
$(_TARGETPLATROOT)/inc; /
$(_COMMONOAKROOT)/inc; /
$(_PUBLICROOT)/common/oak/inc;$(_PUBLICROOT)/common/sdk/inc;$(_PUBLICROOT)/common/ddk/inc; /
../../inc /
SOURCES= /
24LC08.c /
IIC2440.c /
IICBus.c /
FILE_VIEW_INCLUDES_FOLDER= /
IIC.h /
IIC2440.h /
24LC08.h /
1.4.5 編寫 DLL 的導出函數定義檔案
.DEF 檔案定義了 DLL 的導出函數清單。在 IIC 中添加一個文本檔案,命名為 IICBus.def ,然後在該檔案中輸入如下内容:
LIBRARY IICBus
EXPORTS
IIC_Init
IIC_Deinit
IIC_Open
IIC_Close
IIC_IOControl
IIC_PowerUp
IIC_PowerDown
IIC_Read
IIC_Write
IIC_Seek
1.4.6 配置系統資料庫
在 platform.reg 中添加如下内容:
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/IICBus]
"Dll" = "IICBus.dll"
"Prefix" = "IIC"
"Index" = dword:1
"Order" = dword:0
另外,還要在 platform.bib 中添加如下内容:
IICBus.dll $(_FLATRELEASEDIR)/IICBus.dll NK SH