1.開發環境
操做系統:SylixOS
編程環境:RealEvo-IDE3.1.5
硬件平臺:SAMA5D2 Xplained開發板編程
2.EEPROM簡介
EEPROM,或寫做E2PROM,全稱電子抹除式可複寫只讀存儲器 (英語:Electrically-Erasable Programmable Read-Only Memory),是一種能夠經過電子方式屢次複寫的半導體存儲設備。相比EPROM,EEPROM不須要用紫外線照射,也不需取下,就能夠用特定的電壓,來抹除芯片上的信息,以便寫入新的數據。
2.1 存儲結構及設備地址
本篇使用的EEPROM芯片型號是AT24MAC402,該芯片提供2Kbit串行電可擦除可編程的存儲單元,即256 bytes,並可經過I2C兼容的串行接口(TWI)進行讀寫操做。此外,AT24MAC402可用來存放全球惟一的MAC或EUI地址(EUI-48)。其內部存儲組織結構如圖 2-1所示。緩存
圖 2-1 AT24MAC402內部存儲結構框架
由圖 2-1可知,AT24MAC402提供了128-bit Serial Number和48-bit(9Ah-9Fh)的擴展存儲部分用來存儲序列號和全球惟一的MAC或EUI地址。做爲I2C從設備,可經過兩個不一樣的設備地址訪問EEPROM的這兩部分(標準和擴展)的內部存儲地址。AT24MAC402的芯片手冊對這兩部分的編址如圖 2-2所示。函數
圖 2-2 設備地址ui
其中Bit[3:1]由硬件引腳電平決定,在沒有設置寫保護的狀況下,對於標準EEPROM可進行讀寫操做,而擴展部分僅支持讀操做。SAMA5D2開發板EEPROM的電路圖如圖 2-3所示。操作系統
圖 2-3 EEPROM電路圖連線code
結合圖 2-2可知EEPROM標準部分的設備地址是‘1010100’,即0x54;擴展部分的設備地址是‘1011100’,即0x5C。
2.2 操做模式
2.2.1 讀操做
標準EEPROM部分和擴展部分均支持讀操做,EEPROM支持如下三種類型的讀操做:
當前地址讀:在當前地址讀操做方式時無需發送讀字節地址,每次只將當前地址所存數據讀出,片內地址始終保持自加,直到讀完整個EEPROM後又回到0地址。
隨機地址讀:主設備發送有效從設備內部地址,而且從設備發送響應信號後將會將該內部地址處的數據經過I2C發送給主設備。
順序讀:多字節連續讀操做既能夠是當前地址讀,也能夠是隨機地址讀,每次處理器接收到一字節數據都返回一個ACK,EEPROM接收到此ACK後會自動地址加1,接着輸出下一個字節數據,直處處理器返回NO ACK時,讀過程結束。
2.2.2 寫操做
標準EEPROM部分,在寫保護被禁止的狀況下提供寫操做,而且支持如下兩種寫操做:
字節寫:按字節寫時一般在向EEPROM發送設備地址並收到應答信號後,發送寫字節地址再次收到ACK後開始寫數據,最後發送中止位結束寫操做。
頁寫:寫頁時EEPROM可一次連續寫入整頁數據(一頁爲16字節)。其發地址過程與寫字節時徹底相同。不一樣的是,當寫完一個數據字節後,處理器發不發中止狀態,而是在應答信號後繼續寫入數據,每個字節接收完畢後,EEPROM都返回一個ACK,一直到寫完整頁。若是頁寫時寫入數超出該物理頁邊界,則超出數據將從新寫入頁首地址覆蓋以前所寫數據。
3.技術實現
本篇經過內核模塊的方式實現EEPROM的設備驅動。
EEPROM驅動的編寫一樣是實現設備文件操做控制塊結構體file_operations的成員函數,在EEPROM設備驅動中主要實現了__e2promOpen、__e2promClose、__e2promRead、__e2promWrite、__e2promIoctl函數功能,__e2promIoctl函數用來設置待訪問的EEPROM的內部地址。
應用程序能夠經過訪問標準文件I/O函數來讀寫EEPROM設備,在讀寫EEPROM設備前,可調用lseek函數設置要讀/寫的eeprom內部寄存器地址,而後調用標準文件I/O對該內部地址進行讀/寫操做。
EEPROM的讀寫功能,實質上是調用I2C設備發送接口的方式實現的。這裏使用字符驅動的框架來實現EEPROM的讀寫操做。因爲標準EEPROM和擴展部分的設備地址不一樣,可是對這兩部分的操做是同樣的,所以本篇僅給出標準EEPROM設備的驅動實現。
標準EEPROM設備文件操做結構體如程序清單 3-1所示。接口
程序清單 3-1 e2prom設備文件操做集開發
/********************************************************************************************************* ** e2prom設備文件操做集 *********************************************************************************************************/ struct file_operations GfileOperate = { .fo_open = __e2promOpen, .fo_close = __e2promClose, .fo_read = __e2promRead, .fo_write = __e2promWrite, .fo_ioctl = __e2promIoctl };
經過調用標準I/O函數,可最終調用到file_operations結構體中的對應的成員函數。
3.1 讀操做
__e2promRead讀取EEPROM內部數據,其實現如程序清單 3-2所示。it
程序清單 3-2 __e2promRead實現
/********************************************************************************************************* ** 函數名稱: __e2promRead ** 功能描述: 讀取eeprom設備 ** 輸 入 : pvArg 版本類型選擇參數 ** pcBuffer 緩衝區 ** stMaxByte 緩衝區大小 ** 輸 出 : ERROR *********************************************************************************************************/ static ssize_t __e2promRead(PVOID pvArg, PCHAR pcBuffer, size_t stMaxByte) { UINT32 uiRet; if(!pcBuffer) { return PX_ERROR; } uiRet = __at24xxRead(Gi2cDev, Goffset, (UINT8 *)pcBuffer, stMaxByte); Goffset = (Goffset + stMaxByte) % EEPROM_MEM_SIZE; /* 內部地址計數器保存值 */ return (uiRet == ERROR_NONE) ? stMaxByte:PX_ERROR; }
__e2promRead將會調用at24xxRead函數實現讀操做,at24xxRead實現如程序清單 3-3所示。
程序清單 3-3 at24xxRead實現
/********************************************************************************************************* ** 函數名稱: __at24xxRead ** 功能描述: AT24xx 寄存器讀函數 ** 輸 入 : pI2cDev i2c設備 ** RegAddress 寄存器地址 ** buf 數據接收緩衝區 ** len 須要讀取的數據長度 ** 輸 出 : 返回 0 表示函數執行成功 *********************************************************************************************************/ static int __at24xxRead (PLW_I2C_DEVICE pI2cDev, UINT8 ucRegAddress, UINT8 *ucBuf, UINT uiLen) { INT iError; LW_I2C_MESSAGE i2cMsgs[2] = { { .I2CMSG_usAddr = pI2cDev->I2CDEV_usAddr, .I2CMSG_usFlag = 0, /* 0表示寫操做 */ .I2CMSG_usLen = sizeof(ucRegAddress), .I2CMSG_pucBuffer = &ucRegAddress, /* 先寫要讀的寄存器地址 */ }, { .I2CMSG_usAddr = pI2cDev->I2CDEV_usAddr, .I2CMSG_usFlag = LW_I2C_M_RD, /* 表示讀操做 */ .I2CMSG_usLen = uiLen, .I2CMSG_pucBuffer = ucBuf, /* 接着讀操做 */ } }; iError = API_I2cDeviceTransfer(pI2cDev, i2cMsgs, 2); if (iError < 0) { return (PX_ERROR); } return (ERROR_NONE); }
實質上,應用層調用read函數,最終是調用的API_I2cDeviceTransfer函數實現接收與發送操做。
3.2 寫操做
__e2promWrite向EEPROM寫入數據,其實現如程序清單 3-4所示。
程序清單 3-4 e2promWrite實現
/********************************************************************************************************* ** 函數名稱: __e2promWrite ** 功能描述: 寫eeprom設備 ** 輸 入 : pvArg 版本類型選擇參數 ** pcBuffer 緩衝區 ** stMaxByte 緩衝區大小 ** 輸 出 : ERROR *********************************************************************************************************/ static ssize_t __e2promWrite(PVOID pvArg, PCHAR pcBuffer, size_t stMaxByte) { UINT32 uiRet; if(!pcBuffer) { return PX_ERROR; } uiRet = __at24xxWrite (Gi2cDev, Goffset, (UINT8 *)pcBuffer, stMaxByte); Goffset = (Goffset + stMaxByte) % EEPROM_MEM_SIZE; /* 內部地址計數器保存值 */ return (uiRet == ERROR_NONE) ? stMaxByte:PX_ERROR; }
__e2promWrite將會調用at24xxWrite函數實現EEPROM的寫操做,at24xxWrite實現如程序清單 3-5所示。
程序清單 3-5 at24xxWrite實現
/********************************************************************************************************* ** 函數名稱: __at24xxWrite ** 功能描述: AT24xx 寄存器寫函數 ** 輸 入 : pI2cDev i2c設備 ** RegAddress 寄存器地址 ** buf 須要寫入寄存器的數據 ** len 寫入數據長度 ** 輸 出 : 返回 0 表示函數執行成功 *********************************************************************************************************/ static int __at24xxWrite (PLW_I2C_DEVICE pI2cDev, UINT8 ucRegAddress, UINT8 *ucBuf, UINT uiLen) { INT iError; if(!pI2cDev) { return PX_ERROR; } /* * 發送緩存大小:至少爲(數據+地址)字節數 */ UINT8 *pui2cBuf = (UINT8 *)malloc(uiLen+1); LW_I2C_MESSAGE i2cMsgs[1] = { { .I2CMSG_usAddr = pI2cDev->I2CDEV_usAddr, .I2CMSG_usFlag = 0, /* 0表示寫操做 */ .I2CMSG_usLen = uiLen + sizeof(ucRegAddress), /* (數據+地址)字節數 */ .I2CMSG_pucBuffer = pui2cBuf, }, }; /* * 發送緩存開頭存放的是地址信息,而後纔是數據 */ pui2cBuf[0] = ucRegAddress; memcpy(&pui2cBuf[1], &ucBuf[0], uiLen); iError = API_I2cDeviceTransfer(pI2cDev, i2cMsgs, 1); if (iError < 0) { free(pui2cBuf); printk(KERN_ERR "__at24xxWrite(): failed to i2c transfer!\n"); return (PX_ERROR); } free(pui2cBuf); return (ERROR_NONE); }
實質上,應用層調用write函數,最終是調用的API_I2cDeviceTransfer函數實現接收與發送操做。
3.3 設置讀寫地址
經過實現__e2promIoctl函數,完成設置待讀/寫的EEPROM的內部寄存器地址,其實現如程序清單 3-6所示。
程序清單 3-6 __e2promIoctl實現
/********************************************************************************************************* ** 函數名稱: __e2promIoctl ** 功能描述: 控制eeprom設備 ** 輸 入 : pdevhdrHdr 設備頭 ** iCmd 命令 ** lArg 命令參數 ** 輸 出 : ERROR *********************************************************************************************************/ static INT __e2promIoctl(PLW_DEV_HDR pdevhdrHdr, INT iCmd, LONG lArg) { INT iError; struct stat *pstat; switch(iCmd) { case FIOSEEK: /* 獲取e2prom內部地址偏移 */ Goffset = *(off_t *)lArg; break; case FIOFSTATGET: /* 得到文件屬性 */ pstat = (struct stat *)lArg; pstat->st_dev = (dev_t)pdevhdrHdr; pstat->st_ino = (ino_t)0; /* 至關於惟一節點 */ pstat->st_mode = 0644 | S_IFCHR; /* 默認屬性 */ pstat->st_nlink = 1; pstat->st_uid = 0; pstat->st_gid = 0; pstat->st_rdev = 1; pstat->st_size = 0; pstat->st_blksize = 0; pstat->st_blocks = 0; pstat->st_atime = API_RootFsTime(LW_NULL); /* 默認使用 root fs 基準時間 */ pstat->st_mtime = API_RootFsTime(LW_NULL); pstat->st_ctime = API_RootFsTime(LW_NULL); break; default: errno = ENOSYS; iError = PX_ERROR; break; } return ERROR_NONE; }
經過在應用層調用lseek,能夠調用到底層的__e2promIoctl函數,在__e2promIoctl函數中經過給全局變量Goffset賦值,在調用read/write函數時,底層相應的__e2promRead/ __e2promWrite即可得到Goffset的偏移值,進而讀取/寫入到EEPROM內部寄存器中。
3.4 驅動模塊初始化及卸載
驅動模塊初始化實現如程序清單 3-7所示。
程序清單 3-7 模塊初始化
/********************************************************************************************************* ** 函數名稱: module_init ** 功能描述: 驅動加載模塊 ** 輸 入 : NONE ** 輸 出 : ERROR_CODE *********************************************************************************************************/ int module_init (void) { printk("hello_module init!\n"); INT iDrvNum = API_IosDrvInstallEx(&GfileOperate); /* 安裝驅動程序 */ API_IosDevAdd (&GdevhdrHdr, "/dev/eeprom", iDrvNum); /* 安裝設備 */ Gi2cDev = API_I2cDeviceCreate("/bus/i2c/1", "/dev/eeprom", DEVICE_ADDR, 0); return ERROR_NONE; }
模塊卸載實現如程序清單 3-8所示。
程序清單 3-8 模塊卸載
/********************************************************************************************************* ** 函數名稱: module_exit ** 功能描述: 驅動卸載模塊 ** 輸 入 : NONE ** 輸 出 : NONE *********************************************************************************************************/ void module_exit (void) { printk("hello_module exit!\n"); API_IosDevDelete(&GdevhdrHdr); /* 刪除設備 */ API_I2cDeviceDelete(Gi2cDev); /* 刪除指定的 i2c 設備 */ return ; }