嵌入式Linux下 24系列EEPROM/FRAM驱动

版权声明:本文为博主原创文章,转载请注明出处! https://blog.csdn.net/qq_20553613/article/details/85332759

1.写在前面

  “24系列”的EEPROM,一般地我们认为是以i2c为通信接口的一系列串行EEPROM,各大半导体厂商出产的该系列EEPROM都遵循这个规则,而且电路和控制程序上也兼容。如AT24C02、ST24C02等。

  EEPROM在嵌入式开发中使用广泛,在此之前,有总结过MCU下24系列EEPROM的驱动接口——“24系列EEPROM/FRAM通用接口”。根据该文章中的接口,作调整,使在嵌入式Linux系统下使用。

2.驱动接口

  本文EEPROM驱动程序实现并不按照Linux标准驱动程序模式,不编译成驱动模块或者编译进Linux内核;或者可以说是“伪驱动”的应用。因此,也没有使用到Linux驱动“一切皆文件”的“open、read/write、close”接口。对外,提供三个接口,分别是读、写、擦除操作,擦除操作。

extern int16_t _24cxx_read(_24cxx_dev_t *pdev,uint32_t addr, uint8_t *pbuf, uint32_t size);
extern int16_t _24cxx_write(_24cxx_dev_t *pdev,uint32_t addr,uint8_t *pbuf,uint32_t size);
extern int16_t _24cxx_erase(_24cxx_dev_t *pdev,uint32_t addr,uint8_t data, uint32_t size);`

3.驱动程序实现

3.1 基本思路

  Linux内核提供了常用的总线驱动,如UART、i2c、spi等等,使用总线驱动,也是遵循“一切皆文件原则”,即“open、read/write、close”,另外还有本文使用到也是非常强大的“ioctl”函数。对于我们驱动使用到的i2c,我们可以解决调用现在的i2c驱动。

3.2 ioctl函数

  ioctl是设备驱动程序中对设备的I/O通道进行管理的函数,如配置总线速率、传输模式、传输位宽等,也可以用来传输特定数据。函数原型如下,第一个参数是设备驱动的文件句柄,第二个参数为控制命令(命令号),省略号表示这是一个可选的参数,存在与否依赖于控制命令(第二个参数)是否涉及到与设备的数据交互。

int ioctl(int fd, int cmd, …)

  ioctl命令号是该函数中最重要的参数,它描述了ioctl将要处理的命令,Linux中使用一个32位的数据来编码ioctl命令。对于本文使用的道i2c操作命令,可以从内核文件“include/linux/i2c-dev.h”查看,而使用i2c驱动EEPROM用到的是“I2C_RETRIES”、“I2C_TIMEOUT”、“I2C_RDWR”。

/*linux ioctl cmd*/
#define I2C_RETRIES     		0x0701     	/*设置未ACK时的重试次数*/
#define I2C_TIMEOUT    		 	0x0702     	/*设置超时时间*/
#define I2C_SLAVE       		0x0703		/*设置从机地址*/
#define I2C_SLAVE_FORCE 		0x0706      /*强制设置从机地址*/
#define I2C_TENBIT      		0x0704      /*选择地址位长,0为7bit地址,非0为10bit地址*/
#define I2C_FUNCS       		0x0705      /*获取适配器支持的功能*/
#define I2C_RDWR        		0x0707      /*收发命令*/
#define I2C_PEC         		0x0708    	/*非0表示SMBus使用PEC模式*/
#define I2C_SMBUS       		0x0720      /*使用SMBus总线发送*/

  ioctl是Linux系统非常重要的函数,也是非常强大和复杂的函数,具体原理、实现过程、使用范畴等等可以额外研究,本文不重点讲解。

3.3 API

3.3.1 EEPROM设备对象

  对外函数中有个重要的参数,就是结构体“_24cxx_dev_t”,在“_24cxx_dev.h”中,每个EEPROM外设必须对应一个该结构体,即唯一地址,类似“文件句柄”。该结构体需要程序员初始化,指定相关参数或者函数指针实例化。后面的读、写、擦除函数接口的第一个参数必须都是传入该对象地址(指针),如果对象为空或者未初始化,则返回错误。

/*24cxx eeprom devcie struct*/
typedef struct 
{
	char  					*i2c_dev;	/*i2c bus device*/
	uint8_t					slave_addr;	/*eeprom i2c addr*/
	_24_model_t				model;		/*eeprom model*/
	void(*wp)(uint8_t ctrl);			/*protect of write function*/
	void(*page_write_delay)(void);		/*there is a delay in continuous writin for EEPROM,FRAM not need*/
	
}_24cxx_dev_t;

1)“i2c_dev”:Linux下i2c驱动名称,包含路径。一般Linux的i2c驱动设备为“/dev/i2c/0”、“/dev/i2c/1”;
2)“slave_addr”:为EEPROM的器件从地址,7bit地址,不包括读写位;如AT24C02从地址为 0x50(地址选择引脚都接地的情况);
3)“model”:为具体EEPROM的型号,该类型为自定义的枚举型,枚举了24c01—24c1024的型号的EEPROM/FRAM;

/*eeprom model*/
typedef enum 
{
  	_24C01_E = 1,
	_24C02_E,
	_24C04_E,
	_24C08_E,
	_24C16_E,
	_24C32_E,
	_24C64_E,
	_24C128_E,
	_24C256_E,
	_24C512_E,
	_24C1024_E,
}_24_model_t;

4)“wp”:为写保护函数,如果EEPROM的写保护引脚“WP”通过CPU控制,则需实现该函数;实现“WP”引脚IO控制函数;
5 “page_write_delay”:为页写延时函数,程序中采用了连续页写算法,提高写速率,但必须实现该函数,否则写会出错;对于FRAM则不需要,将该函数至为NULL(0),程序判断存储介质为FRAM。

3.3.2 消息帧

【1】i2c总线消息帧

struct i2c_dev_message
{
    unsigned short  addr;
    unsigned short  flags;
    unsigned short  size;
    unsigned char   *buff;
};

1)addr:待操作的存储物理地址;
2)flags:操作标识,读或写等;
3)size:数据传输大小;
4)buff:数据缓存地址(指针)。

【2】ioctl数据操作消息帧
  在MCU中,我们直接使用该消息帧进行数据收发。而在Linux下通过ioctl函数操作时,需再封装一下,带入消息帧数。

struct i2c_rdwr_ioctl_data 
{
    struct i2c_dev_message	*msgs;
    int nmsgs;
};

1)msgs:i2c消息帧;
2)nmsgs:i2c消息帧数。

3.3.3 读EEPROM

函数参数 功能描述
pdev EEPROM/FRAM结构体实例地址
addr 读起始地址
pbuf 读出数据(缓存)
size 读取数据大小(字节)
/**
 * @brief  read from the eeprom.
 * @param  pdev pointer to the eeprom device struct.
 * @param  addr the address to read from.
 * @param  pbuf where to put read data.
 * @param  size number of bytes to read.
 * @retval return 0 if 0k,anything else is considered an error.
 */
int16_t _24cxx_read(_24cxx_dev_t *pdev,uint32_t addr, uint8_t *pbuf, uint32_t size)
{  
	struct i2c_dev_message 		ee24_msg;
	struct i2c_rdwr_ioctl_data 	ee24_ioctl_data;
	uint8_t	 buf[2];
	uint8_t  slave_addr,ret_size = 0;
	int		 fd = -1;
	
	if(pdev == 0)
	  	return _24CXX_ERR_DEV_NONE;
	
	if((addr+size) > get_eeprom_chipsize(pdev->model))	 
	  	return _24CXX_ERR_CHIP_SIZE;
	
	fd = open(pdev->i2c_dev,O_RDONLY);		/*Read only*/
    if(fd < 0)
        return _24CXX_ERR_DEV_NONE;

	if(pdev->model > _24C16_E)
	{
		slave_addr 	= pdev->slave_addr;
		buf[0] 		= (addr >>8)& 0xff;
		buf[1] 		= addr & 0xff;
		ee24_msg[0].size  = 2;
	}
	else
	{
		slave_addr = pdev->slave_addr | (addr>>8);
		buf[0] = addr & 0xff;
		ee24_msg[0].size  = 1;
	}
	ee24_msg[0].buff  = buf;
	ee24_msg[0].addr  = slave_addr;
	ee24_msg[0].flags = I2C_BUS_WR;
	ee24_msg[1].addr  = slave_addr;
	ee24_msg[1].flags = I2C_BUS_RD;
	ee24_msg[1].buff  = pbuf;
	ee24_msg[1].size  = size;
	
	ee24_ioctl_data.nmsgs = 2;
    ee24_ioctl_data.msgs  = ee24_msg;
	ret_size = ioctl(fd,I2C_RDWR,(unsigned long)&ee24_ioctl_data);
	
	close(fd);
	if(ret_size < 0)
	  	return _24CXX_ERR_I2C_WR;
	else
	  	return _24CXX_OK;
}

3.3.4 写EEPROM

函数参数 功能描述
pdev EEPROM/FRAM结构体实例地址
addr 写起始地址
pbuf 待写数据
size 待写数据大小(字节)
/**
 * @brief  write one page. 
 * @param  pdev pointer to the eeprom device struct.
 * @param  addr the address of write to.
 * @param  pbuf the data to write.
 * @param  size number of bytes to write..
 * @retval return 0 if 0k,anything else is considered an error.
 */
static int16_t _24cxx_write_page(_24cxx_dev_t *pdev,uint32_t addr, uint8_t *pbuf,uint32_t size)
{
	struct i2c_dev_message 		ee24_msg;
	struct i2c_rdwr_ioctl_data 	ee24_ioctl_data;
	uint8_t		*buf = NULL;
	uint16_t 	slave_addr;
	uint16_t	ee_page_size = 0;
	int			ret_size = 0;
	
	if(pdev == 0)
	  	return _24CXX_ERR_DEV_NONE;
	
	ee_page_size = get_eeprom_pagesize(pdev->model);
	if(((addr % ee_page_size) + size) > ee_page_size) 	/*the over flow of page size*/
		return _24CXX_ERR_PAGE_SIZE;
	
	buf = (uint8_t*)malloc(ee_page_size+2);
	if(buf==NULL)
	  	return _24CXX_ERR_MEM;
	
	if(pdev->model > _24C16_E)
	{/*24c32-24c1024*/
		slave_addr = pdev->slave_addr;
		buf[0] = (addr >>8)& 0xff;
		buf[1] = addr & 0xff;
		memcpy(buf+2,pbuf,size);		/*copy data*/
		ee24_msg.size  = size + 2;
	}
	else
	{/*24c01-24c16*/
		slave_addr = pdev->slave_addr | (addr>>8);
		buf[0] = addr & 0xff;
		memcpy(buf+1,pbuf,size);		/*copy data*/
		ee24_msg.size  = size + 1;
	}
	ee24_msg.addr  = slave_addr;
	ee24_msg.flags = I2C_BUS_WR;
	ee24_msg.buff  = buf;
	
	ee24_ioctl_data.nmsgs = 1;
    ee24_ioctl_data.msgs  = &ee24_msg;
	ret_size = ioctl(fd, I2C_RDWR,(unsigned long)&ee24_msg);
	
	free(buf);

	if(ret_size < 0)
	  	return _24CXX_ERR_I2C_WR;
	else
		return _24CXX_OK;
}


/**
 * @brief  write data to eeprom.
 * @param  pdev pointer to the eeprom device struct.
 * @param  addr the address of write to.
 * @param  pbuf the data to write.
 * @param  size number of bytes to write..
 * @retval return 0 if 0k,anything else is considered an error.
 */
int16_t _24cxx_write(_24cxx_dev_t *pdev,uint32_t addr, uint8_t *pbuf, uint32_t size)
{
	uint8_t  	write_len,page_offset;
	int8_t 		error = 0;
	uint16_t	ee_page_size = 0;
	int			fd = -1;
	
	if(pdev == 0)
	  	return _24CXX_ERR_DEV_NONE;
	
	if((addr+size) > get_eeprom_chipsize(pdev->model))	/*the over flow of chip size*/
	  	return _24CXX_ERR_CHIP_SIZE;
	
	fd = open(pdev->i2c_dev,O_WRONLY);		/*Write only*/
    if(fd < 0)
        return _24CXX_ERR_DEV_NONE;
	
	if(pdev->wp)	/*release write protect*/
	  	pdev->wp(0);
	
	ee_page_size = get_eeprom_pagesize(pdev->model);
	while(size > 0)
	{
		page_offset = ee_page_size - (addr % ee_page_size);
		write_len   = size > page_offset ? page_offset : size;
		error = _24cxx_write_page(pdev,addr,pbuf,write_len);	
		if(error)
		  	break;
		size = size - write_len;
		if(size > 0)
		{
			pbuf = pbuf + write_len;
			addr = addr + write_len;
		}
		if(pdev->page_write_delay)
			pdev->page_write_delay();		/*eeprom need wait*/
	}
	if(pdev->wp)
	  	pdev->wp(1);/*write protect*/
	
	close(fd);

	return error;
}

3.3.5 擦除EEPROM

  原则上EEPROM并不存在“擦除”操作,“擦除”一般指用于flash写入前的操作。但特殊场合下,需赋予EEPROM默认值,如赋值0或者0xff,此时可以使用擦除操作。为了节约内存,该函数内部使用的是单字节擦除,效率不高,如有需擦除大片空间,建议直接调用写函数进行擦除。

函数参数 功能描述
pdev EEPROM/FRAM结构体实例地址
addr 擦除起始地址
data 擦除为默认值
size 待擦除空间大小
/**
 * @brief  erase the eeprom.
 * @param  pdev pointer to the eeprom device struct.
 * @param  addr the address to erase from.
 * @param  data padding data.
 * @param  size number of bytes to erase.
 * @retval return 0 if 0k,anything else is considered an error.
 */
int16_t _24cxx_erase(_24cxx_dev_t *pdev,uint32_t addr, uint8_t data, uint32_t size)
{
	int8_t   error = 0;
	uint16_t i;
	
	if(pdev == 0)
	  	return _24CXX_ERR_DEV_NONE;
	
	if((addr+size) > get_eeprom_chipsize(pdev->model))	 
	  	return _24CXX_ERR_CHIP_SIZE;
	
	for(i = 0;i < size;i++)
	{
		error = _24cxx_write_page(pdev,addr,&data,1);
		if(error)
		  break;
	}
				
	return error;
}

3.3.6 函数调用返回状态

  调用函数时,都会返回一个状态码,无错误返回0,如返回非0可以根据返回状态码查找执行错误的代码段。具体状态码可以查看“_24cxx_dev.h”中的定义。

/*operation state*/
enum _24CXX_ERROR
{
	_24CXX_OK 				= 0,
	_24CXX_ERR_MEM			= -1,
	_24CXX_ERR_I2C_WR 		= 1,
	_24CXX_ERR_DEV_NONE 	= 2,
	_24CXX_ERR_PAGE_WRITE	= 3,
	_24CXX_ERR_PAGE_SIZE	= 4,
	_24CXX_ERR_CHIP_SIZE	= 5,
};

4.使用例程

4.1 EEPROM系列

/**
 * @brief  delay function
 * @param  none
 * @retval none
 */
static void page_write_delay(void) 
{
#if 1
  	usleep(4000);	
#else
	uint16_t i;
	
	i = 0xFFFF;
	while(i--);
#endif
}

/**
 * @brief  device init
 */
const _24cxx_dev_t at24cxx_dev =
{
	"/dev/i2c/0",				/*i2c device name*/
	0x50,						/*eeprom address*/
	_24C16_E,					/*eeprom model,eg AT24C02*/
	0,							/*no write protect*/
	page_write_delay,			/*delay function*/
};

  定义一个EEPROM结构体实例,并初始化,由于调用过程无需改变内容,可声明为“const”。
1)"/dev/i2c/0":嵌入式Linux系统i2c0驱动路径名称;
2)“0x50”:EEPROM器件7bit从地址;
3)“_24C16_E”:器件具体型号;
4)“0”:写保护函数,这里无,赋值NULL(0);
5)“page_write_delay”:页写延时函数。

  这一步完成,即可通过上述的API接口读、写EEPROM,具体demo查看工程源码。

4.2 FRAM系列

  FRAM控制接口兼容传统EEPROM,程序上几乎完全兼容,主要是页写函数可以去除,设置为NULL(0),程序则判断为FRAM类型存储介质。以FM24CL16为例,实现如下。

/**
 * @brief  device init
 */
const _24cxx_dev_t fm24clxx_dev =
{
	"/dev/i2c/0",		/*i2c device name*/
	0x50,				/*fram address*/
	_24C16_E,			/*eeprom model,eg FM24C16*/
	0,					/*no write protect*/
	0,					/*delay fun,FRAM no need*/
};

5.源码

6.相关文章

[1] 24系列EEPROM/FRAM通用接口
https://blog.csdn.net/qq_20553613/article/details/82733817

[2] EEPROM页写算法:
https://blog.csdn.net/qq_20553613/article/details/78550427

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/85332759