天天看點

WINCE下IIC接口FLASH驅動

轉自: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 信号。

WINCE下IIC接口FLASH驅動

                                      圖 3 啟動、停止條件

當主裝置發送完一個位元組的資料,從裝置會在下一個時鐘周期回複一個 ACK ,即一個低電平信号,如圖 4 所示。

WINCE下IIC接口FLASH驅動

圖 4 輸出 ACK 信号

在 AT24LC08 的寫操作過程中(以頁寫模式為例),其操作流程如圖 5 所示。首先主裝置發出 START 信号,緊接着發送 8 位器件位址和 8 位字位址。器件位址最末位為 WRITE ( 0 )。從裝置驗證完位址并發回 ACK 信号後,主機就開始發送資料。從裝置每收到一個位元組的資料就傳回一個 ACK 。發送結束後,主機發出 STOP 信号,至此,一個完整的寫操作結束。

需要注意的是:頁寫模式最多支援 16 個位元組的寫操作。按字位址低四位逐次累加,當累加到 1111 時,下一個時鐘周期位址翻轉到 0000 ,新寫入的資料将覆寫之前的值。

WINCE下IIC接口FLASH驅動
WINCE下IIC接口FLASH驅動

圖 5 頁寫資料順序

l         讀操作

以頁讀模式為例分析讀操作的過程:首先主機發出 START 信号,依次發送器件位址和字位址,此時的讀寫控制位為 WRITE 。從機收到位址傳回 ACK 。然後,主機再發送 START 信号,再傳器件位址,此時的讀寫控制位為 READ 。主機等到從機傳回的 ACK ,就開始讀資料。讀到最後一個位元組時,從機傳回一個 NACK 信号,主機發出 STOP ,标志讀過程結束。

同樣,頁讀模式也支援最多 16 個位元組的操作。按字位址的低四位累加,如果溢出就翻轉到 0000 位址開始讀。

WINCE下IIC接口FLASH驅動
WINCE下IIC接口FLASH驅動

圖 6 頁讀資料順序

1.4.2 驅動程式架構和通路流程

l         體系結構和通路流程

在流式接口驅動程式中,驅動程式負責把外設抽象成一個檔案,而應用程式則使用作業系統提供的檔案 API 對外設進行通路。檔案 API 被作業系統轉發到 FileSys.exe 程序中;然後 FileSys.exe 發現是對裝置的操作,就會把執行交給 Device.exe 處理;接着 Device.exe 根據具體的請求,調用流驅動接口函數;最終,驅動程式負責與硬體互動。

WINCE下IIC接口FLASH驅動

圖 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

繼續閱讀