首页 > 代码库 > SylixOS中EEPROM设备驱动实现

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设备文件操作集  *********************************************************************************************************/  
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所示。

程序清单 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 ;  
}


本文出自 “12547908” 博客,请务必保留此出处http://12557908.blog.51cto.com/12547908/1914861

SylixOS中EEPROM设备驱动实现