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所示。
/*********************************************************************************************************
** e2prom设备文件操作集
*********************************************************************************************************/
struct file_operations GfileOperate = {
.fo_open = __e2promOpen,
.fo_close = __e2promClose,
.fo_read = __e2promRead,
.fo_write = __e2promWrite,
.fo_ioctl = __e2promIoctl
};
通过调用标准I/O函数,可最终调用到file_operations结构体中的对应的成员函数。
/*********************************************************************************************************
** 函数名称: __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所示。
/*********************************************************************************************************
** 函数名称: __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函数实现接收与发送操作。
/*********************************************************************************************************
** 函数名称: __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所示。
/*********************************************************************************************************
** 函数名称: __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函数实现接收与发送操作。
/*********************************************************************************************************
** 函数名称: __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内部寄存器中。
/*********************************************************************************************************
** 函数名称: 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所示。
/*********************************************************************************************************
** 函数名称: module_exit
** 功能描述: 驱动卸载模块
** 输 入 : NONE
** 输 出 : NONE
*********************************************************************************************************/
void module_exit (void)
{
printk("hello_module exit!\n");
API_IosDevDelete(&GdevhdrHdr); /* 删除设备 */
API_I2cDeviceDelete(Gi2cDev); /* 删除指定的 i2c 设备 */
return ;
}