SylixOS中EEPROM设备驱动实现
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 设备地址
其中Bit[3:1]由硬件引脚电平决定,在没有设置写保护的情况下,对于标准EEPROM可进行读写操作,而扩展部分仅支持读操作。SAMA5D2开发板EEPROM的电路图如图 2-3所示。
图 2-3 EEPROM电路图连线
结合图 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设备文件操作集*********************************************************************************************************/structfile_operationsGfileOperate={.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所示。
程序清单 3-2 __e2promRead实现
/***********************************************************************************************************函数名称:__e2promRead**功能描述:读取eeprom设备**输 入:pvArg版本类型选择参数**pcBuffer缓冲区**stMaxByte缓冲区大小**输 出:ERROR*********************************************************************************************************/staticssize_t__e2promRead(PVOIDpvArg,PCHARpcBuffer,size_tstMaxByte){UINT32uiRet;if(!pcBuffer){returnPX_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寄存器读函数**输 入:pI2cDevi2c设备**RegAddress寄存器地址**buf数据接收缓冲区**len需要读取的数据长度**输 出:返回0表示函数执行成功*********************************************************************************************************/staticint__at24xxRead(PLW_I2C_DEVICEpI2cDev,UINT8ucRegAddress,UINT8*ucBuf,UINTuiLen){INTiError;LW_I2C_MESSAGEi2cMsgs[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*********************************************************************************************************/staticssize_t__e2promWrite(PVOIDpvArg,PCHARpcBuffer,size_tstMaxByte){UINT32uiRet;if(!pcBuffer){returnPX_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寄存器写函数**输 入:pI2cDevi2c设备**RegAddress寄存器地址**buf需要写入寄存器的数据**len写入数据长度**输 出:返回0表示函数执行成功*********************************************************************************************************/staticint__at24xxWrite(PLW_I2C_DEVICEpI2cDev,UINT8ucRegAddress,UINT8*ucBuf,UINTuiLen){INTiError;if(!pI2cDev){returnPX_ERROR;}/**发送缓存大小:至少为(数据+地址)字节数*/UINT8*pui2cBuf=(UINT8*)malloc(uiLen+1);LW_I2C_MESSAGEi2cMsgs[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():failedtoi2ctransfer!\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*********************************************************************************************************/staticINT__e2promIoctl(PLW_DEV_HDRpdevhdrHdr,INTiCmd,LONGlArg){INTiError;structstat*pstat;switch(iCmd){caseFIOSEEK:/*获取e2prom内部地址偏移*/Goffset=*(off_t*)lArg;break;caseFIOFSTATGET:/*获得文件属性*/pstat=(structstat*)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);/*默认使用rootfs基准时间*/pstat->st_mtime=API_RootFsTime(LW_NULL);pstat->st_ctime=API_RootFsTime(LW_NULL);break;default:errno=ENOSYS;iError=PX_ERROR;break;}returnERROR_NONE;}
通过在应用层调用lseek,可以调用到底层的__e2promIoctl函数,在__e2promIoctl函数中通过给全局变量Goffset赋值,在调用read/write函数时,底层相应的__e2promRead/ __e2promWrite便可获得Goffset的偏移值,进而读取/写入到EEPROM内部寄存器中。
3.4 驱动模块初始化及卸载
驱动模块初始化实现如程序清单 3-7所示。
程序清单 3-7 模块初始化
/***********************************************************************************************************函数名称:module_init**功能描述:驱动加载模块**输 入:NONE**输 出:ERROR_CODE*********************************************************************************************************/intmodule_init(void){printk("hello_moduleinit!\n");INTiDrvNum=API_IosDrvInstallEx(&GfileOperate);/*安装驱动程序*/API_IosDevAdd(&GdevhdrHdr,"/dev/eeprom",iDrvNum);/*安装设备*/Gi2cDev=API_I2cDeviceCreate("/bus/i2c/1","/dev/eeprom",DEVICE_ADDR,0);returnERROR_NONE;}
模块卸载实现如程序清单 3-8所示。
程序清单 3-8 模块卸载
/***********************************************************************************************************函数名称:module_exit**功能描述:驱动卸载模块**输 入:NONE**输 出:NONE*********************************************************************************************************/voidmodule_exit(void){printk("hello_moduleexit!\n");API_IosDevDelete(&GdevhdrHdr);/*删除设备*/API_I2cDeviceDelete(Gi2cDev);/*删除指定的i2c设备*/return;}
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。