Linux 下使用IIC总线 读写 EEPROM by 韩大卫 @吉林师范大学
handawei@jusontech.com
转载请务必表明出处
******************* **********************************************
2012.7.16
1,本文给出了linux 下使用IIC总线读写EEPROM 的实现程序。
2, 本文给出了在编程中遇到的几种非常隐蔽的错误的解决方法。
3,本文的读写程序非常通用:
i2c -d /dev/i2c-1 -s 0x51 0x05 18 -----Write 18 to the register: 0x05 of the i2c-slave address: 0x51
i2c -d /dev/i2c-10 0x57 0x05 ------Read the register: 0x05 of the i2c-slave address: 0x57
i2c 0x40 0x0f ----- 在默认路径下读 i2c 从设备地址为0x40的 0x0f的地址(或寄存器地址)
我们嵌入式系统中的E2PROM 是 24C02.先简单了解一下这款芯片:
AT24C02的存储容量为2Kb,内容分成32页,每页8B,共256B,操作时有两种寻址方式:芯片寻址和片内子地址寻址。
(1)芯片寻址:AT24C02的芯片地址为1010,其地址控制字格式为 1010A2A1A0R/W。其中A2,A1,A0可编程地址选择位。
A2,A1,A0引脚接高、低电平后得到确定的三位编码,与1010形成7位编码, 即为该器件的地址码。R/W为芯片读写控制位,
该位为0,表示芯片进行写操作。
(2)片内子地址寻址:芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。
我们采用的是第2种 寻址方式。
另外,有一个问题需要了解一下,就是EEPROM 与flash , 什么时候使用EEPROM,什么时候用FLASH合适。
********************
From Www.baidu.com :
Flash存储器又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还可以快速读取数据(NVRAM的优势),使数据不会因为断电而丢失。U盘和MP3里用的就是这种存储器。用作存储Bootloader以及操作系统或者程序代码,或者直接当硬盘使用(U盘)。
一, EEPROM以单字节读写,FLASH部分芯片只能以块方式擦除(整片擦除),部分芯片可以单字节写入(编程),一般需要采用块写入方式;
二,FLASH比EEPROM读写速度更快,可靠性更高。
三,价格方面比较,FLASH比EEPROM贵。
So,我们的版卡参数信息,等一些固定的,小量的,不需要经常修改资料信息放在EEPROM中。而flash作为存储程序的存储器,存放操作系统代码等需要快速读写的,经常访问的数据。
************** ******************************************************
**** *******************************************************
先介绍下遇到的一些问题:
问题一: Bad address 在使用ioctl 在用户层将包装好的data 发送给内核,但是运行结果显示:
error = Bad address
我原来以为是不是给我访问地址不对, 可是地址是正确的。
后来看到了报错的位置:
在内核代码 driver/i2c/i2c-dev.c ,函数i2cdev_ioctl_rdrw()中
if (copy_from_user(&rdwr_arg,
(struct i2c_rdwr_ioctl_data __user *)arg,
sizeof(rdwr_arg)))
return -EFAULT;
这条语句返回了错误提示bad address 。
经过查资料,出错的非常原因隐蔽又非常简单:
copy_to_user的定义是:
copy_to_user ( void __user * to , const void * from , unsigned long n );
可是这个unsigned long 在32bit的处理器上是等于 unsigned int 的,都是4个字节,32bit。
所以我最初在eeprom_io.h中有这样的定义:typedef unsigned long u32;
在eeprom_io.c中:ioctl(fd, I2C_RDWR, (u32)iocs);
我们的版卡的处理器是64bit mips系列处理器,unsigned long 就是8个字节,64bit。交叉编译器不会报错的。
但运行后就会由于字节的问题一直有 Bad address的错误反馈。这误导我单纯了认为是地址不对。
后来我将typedef unsigned long u32 改为 typedef unsigned long u64;
ioctl(fd, I2C_RDWR, (u64)iocs) 。才将这个问题解决。
************** ***************************************************************
问题二,程序执行一次write 后再执行write的时候出现:Input/output error 同样的,执行一次write 后再执行 read 也有这样的错误。
我将addr改为 0x90,0x10 。运行结果都是报错: Input output error
找到报错的位置:
是在 XXX_i2c_xfer () 中下的一的执行函数: octeon_i2c_simple_write()
cmd = __raw_readq(i2c->twsi_base + SW_TWSI);
printk(KERN_DEBUG "%s:after readq cmd = %llx\n",__func__,cmd);
if ((cmd & SW_TWSI_R) == 0) {
if (octeon_i2c_lost_arb(cmd))
goto retry;
ret = -EIO;
这个EIO 在用户层上
printf("%s:error = %s\n",__FUNCTION__,strerror(errno));
会显示input/output error
观察dmesg:
[ 2656.285759] octeon_i2c_xfer: num = 1
[ 2656.285769] octeon_i2c_xfer:msgs[0].addr = 57, msgs[0].flags = 0, msgs[0].len = 2
[ 2656.285780] octeon_i2c_xfer:msgs[0].buf[0] = 0
[ 2656.285789] octeon_i2c_simple_write:
[ 2656.285797] octeon_i2c_simple_write:cmd = 8090570000000000
[ 2656.285808] octeon_i2c_simple_write:msgs[0].buf[1] = 10,cmd = 8090570000000010
[ 2656.285820] octeon_i2c_simple_write:msgs[0].buf[0] = 0,cmd = 8090570000000010
[ 2656.285948] octeon_i2c_simple_write:after readq cmd = 090570000000020
[ 2656.285955]
我将正确执行的程序dmesg 打印出来作为对比:
[ 4312.259857] octeon_i2c_xfer: num = 1
[ 4312.259866] octeon_i2c_xfer:msgs[0].addr = 51, msgs[0].flags = 0, msgs[0].len = 2
[ 4312.259878] octeon_i2c_xfer:msgs[0].buf[0] = 2
[ 4312.259887] octeon_i2c_simple_write:
[ 4312.259895] octeon_i2c_simple_write:cmd = 8090510000000000
[ 4312.259906] octeon_i2c_simple_write:msgs[0].buf[1] = 30,cmd = 8090510000000030
[ 4312.259918] octeon_i2c_simple_write:msgs[0].buf[0] = 02,cmd = 8090510000000230
[ 4312.260227] octeon_i2c_simple_write:after readq cmd = 01905100ffffffff
第一次write执行成功,这说明代码没有问题,那么第二次执行失败,应该是有别的原因,上网查了一下24C02 的资料,
原来是这样:
“数据写完之后,给一个停止信号后一定要延时10MS,24C02需要这么久载入数据” 这是24C02的电气特性。
在write函数使一次用usleep(10000) 。
补充一下:我们的CPU 为6内核500M主频,计算处理能力极强。如果在用户层写一般的延时
程序根本不起作用,一般使用2个for,一个for执行10000次,在PC上就有会明显的延时。 但
是在我们的嵌入式系统中,使用8个for语句,每个for执行10000次,丝毫没有影响,起不到
一丝的延时作用。另外,单纯的使用for作用延时,会将CPU处于满负荷阻塞状态,
影响其他功能,所以,建议大家使用usleep()函数,usleep(10000), 正好是10Ms,这样
最佳的使用了CPU时间。
********* *****************************************************
************** ************************************************************
问题三,关于
fd = i_open("/dev/i2c-2",TIMEOUT,RETRY);
这个语句本身没有问题,在我的系统上也测试通过了,但是其他系统有使用出错的情况,
我观察他的dmesg:
[ 4515.609931] octeon_i2c_xfer: num = 1
[ 4515.609941] octeon_i2c_xfer:msgs[0].addr = 57, msgs[0].flags = 0, msgs[0].len = 2
[ 4515.609952] octeon_i2c_xfer:msgs[0].buf[0] = 2
[ 4515.609961] octeon_i2c_simple_write:
[ 4515.609969] octeon_i2c_simple_write:cmd = 8090570000000000
[ 4515.609980] octeon_i2c_simple_write:msgs[0].buf[1] = 01,cmd = 8090570000000001
[ 4515.609992] octeon_i2c_simple_write:msgs[0].buf[0] = 02,cmd = 8090570000000201
[ 4515.610117] octeon_i2c_simple_write:after readq cmd = 0090570000000020
我执行了一次正确的write, 打印出结果作为对比:
[ 4312.259857] octeon_i2c_xfer: num = 1
[ 4312.259866] octeon_i2c_xfer:msgs[0].addr = 51, msgs[0].flags = 0, msgs[0].len = 2
[ 4312.259878] octeon_i2c_xfer:msgs[0].buf[0] = 2
[ 4312.259887] octeon_i2c_simple_write:
[ 4312.259895] octeon_i2c_simple_write:cmd = 8090510000000000
[ 4312.259906] octeon_i2c_simple_write:msgs[0].buf[1] = 30,cmd = 8090510000000030
[ 4312.259918] octeon_i2c_simple_write:msgs[0].buf[0] = 02,cmd = 8090510000000230
[ 4312.260227] octeon_i2c_simple_write:after readq cmd = 01905100ffffffff
后来经过研究,是访问的i2c bus 出了问题。
原因如下:
在我的版卡的datasheet 上: 24C02 是挂在I2C0 上的,/dev/i2c-0, /dev/i2c2--/dev/i2c-9
都是属于I2c0的,这样访问这些bus都可以。但是 /dev/i2c-1,/devi2c-10---/dev/i2c-33 这些的都
是属于I2C1的 ,所以单纯的 fd = i_open("/dev/i2c-2",TIMEOUT,RET
RY)这样的程序不能得到通用,唯一的办法是在中使用argv ,
将路径作为参数传进来, 进而能进入正确的/dev/i2c-* 号bus。 同时为了做到简化,
将/dev/i2c-0作为默认路径。 使用时候可以
i2c -d /dev/i2c-1 0x57 0x10 使用 /dev/i2c-1 作为路径
i2c 0x57 0x10 使用默认路径/dev/i2c-0.
**************************************** *****************************************************
下面是 实现代码 . Main.c :
***********************************************
#include "i2c.h"
#define TIMEOUT 3
#define RETRY 3
static int fd;
static u16 addr;
static u8 offset;
int i_open(unsigned char* dev, unsigned int timeout, unsigned int retry){
return i2c_open(dev,timeout,retry);
}
int read_data(u16 addr, u8 offset, u8 *val){
int ret;
ret = i2c_read_data(addr,offset,val);
if(ret < 0){
printf("%s error!\n",__FUNCTION__);
exit(-1);
}
printf("read success, val = %02x\n",*val);
return 0;
}
int write_data(u16 addr, u8 offset, char* argv){
int ret;
u8 val = (unsigned char)strtoul(argv,0,16);
ret = i2c_write_data(addr,offset,val);
if(ret < 0){
printf("%s error!\n",__FUNCTION__);
exit(-1);
}
printf("write success , val = %02x\n",val);
usleep(10000); // 延时程序
return 0;
}
int help_info(void){
printf("\nUsage: i2c [-d PATH] ADDR OFFSET\n");
printf("\nOr: i2c [-d PATH] -s ADDR OFFSET DATA \n");
printf("\nRead or Write the register of i2c slave\n");
printf("\nFor example\n");
printf("\ti2c 0x51 0x05\t\t\t\t\t\tRead the register: 0x05 of the address: 0x51\n");
printf("\ti2c -d /dev/i2c-10 0x51 0x05\t\t\t\tRead the register: 0x05 of the address: 0x51\n");
printf("\ti2c -d /dev/i2c-1 -s 0x51 0x05 18\t\t\tWrite 18 to the register: 0x05 of the address: 0x51\n\n");
return 0;
}
void i2c_path(char* argv){
fd = i_open(argv,TIMEOUT,RETRY);
if( fd < 0 ){
printf("i2c_open error!\n");
exit(-1);
}
}
void i2c_addr(char * argv){
char *s = argv;
addr = bin2bcd(atoi(s+2));
}
void i2c_offs(char *argv){
char *s = argv;
offset = bin2bcd(atoi(s+2));
}
int main(int argc,char* argv[]){
u8 val;
switch(argc){
case 2 :{
if(!strcmp(argv[1],"-h") || !strcmp(argv[1],"--help")){
help_info();
}else{
printf("cmd error!\n");
exit(-1);
}
}break;
case 3 :{
i2c_path("/dev/i2c-1");
i2c_addr(argv[1]);
i2c_offs(argv[2]);
read_data(addr,offset,&val);
}break;
case 5 :{
if(!strcmp(argv[1],"-d")){
i2c_path(argv[2]);
i2c_addr(argv[3]);
i2c_offs(argv[4]);
read_data(addr,offset,&val);
}else if(!strcmp(argv[1],"-s")){
i2c_path("/dev/i2c-1");
i2c_addr(argv[2]);
i2c_offs(argv[3]);
write_data(addr,offset,argv[4]);
}else {
printf("cmd error!\n");
exit(-1);
}
}break;
case 7 :{
if(!strcmp(argv[1],"-d")){
i2c_path(argv[2]);
if(!strcmp(argv[3],"-s")){
i2c_addr(argv[4]);
i2c_offs(argv[5]);
write_data(addr,offset,argv[6]);
}
}else {
printf("cmd error!\n");
exit(-1);
}
}break;
default:
printf("Please input --help or -h for help information\n");
}
close(fd);
return 0;
}
******************************* **************************************
i2c.c :
************** ************************************
#include "i2c.h"
static int fd;
int
i2c_read_data(u16 addr, u8 offset, u8 *val)
{
int i,ret = 0;
struct i2c_rdwr_ioctl_data *data;
if ((data = (struct i2c_rdwr_ioctl_data *)malloc(sizeof(struct i2c_rdwr_ioctl_data))) ==
NULL)
return -1;
data->nmsgs = 2;
if ((data->msgs = (struct i2c_msg *)malloc(data->nmsgs * sizeof(struct i2c_msg))) ==
NULL) {
ret = -1;
goto errexit3;
}
if ((data->msgs[0].buf = (unsigned char *)malloc(sizeof(unsigned char))) == NULL) {
ret = -1;
goto errexit2;
}
if ((data->msgs[1].buf = (unsigned char *)malloc(sizeof(unsigned char))) == NULL) {
ret = -1;
goto errexit1;
}
data->msgs[0].addr = addr;
data->msgs[0].flags = 0;
data->msgs[0].len = 1;
data->msgs[0].buf[0] = offset;
data->msgs[1].addr = addr;
data->msgs[1].flags = I2C_M_RD;
data->msgs[1].len = 13; //original data is 1
data->msgs[1].buf[0] = 0;
if ((ret = __i2c_send(fd, data)) < 0)
goto errexit0;
for(i = 0 ;i < data->msgs[1].len; i++)
val[i] = data->msgs[1].buf[i];
errexit0:
free(data->msgs[1].buf);
errexit1:
free(data->msgs[0].buf);
errexit2:
free(data->msgs);
errexit3:
free(data);
return ret;
}
int
i2c_write_data(u16 addr, u8 offset, u8 val)
{
int ret = 0;
struct i2c_rdwr_ioctl_data *data;
if ((data = (struct i2c_rdwr_ioctl_data *)malloc(sizeof(struct i2c_rdwr_ioctl_data))) ==
NULL)
return -1;
data->nmsgs = 1;
if ((data->msgs = (struct i2c_msg *)malloc(data->nmsgs * sizeof(struct i2c_msg))) == NULL)
{
ret = -1;
goto errexit2;
}
if ((data->msgs[0].buf = (unsigned char *)malloc(2 * sizeof(unsigned char))) == NULL) {
ret = -1;
goto errexit1;
}
data->msgs[0].addr = addr;
data->msgs[0].flags = 0;
data->msgs[0].len = 2;
data->msgs[0].buf[0] = offset;
data->msgs[0].buf[1] = val;
if ((ret = __i2c_send(fd, data)) < 0)
goto errexit0;
errexit0:
free(data->msgs[0].buf);
errexit1:
free(data->msgs);
errexit2:
free(data);
return ret;
}
int
i2c_open(unsigned char* dev, unsigned int timeout, unsigned int retry)
{
if ((fd = open(dev, O_RDWR)) < 0)
return fd;
__i2c_set(fd, timeout, retry);
return fd;
}
static int
__i2c_send(int fd, struct i2c_rdwr_ioctl_data *data)
{
if (fd < 0)
return -1;
if (data == NULL)
return -1;
if (data->msgs == NULL || data->nmsgs == 0)
return -1;
return ioctl(fd, I2C_RDWR, (unsigned long)data) ;
}
static int
__i2c_set(int fd, unsigned int timeout, unsigned int retry)
{
if (fd == 0 )
return -1;
ioctl(fd, I2C_TIMEOUT, timeout ? timeout : I2C_DEFAULT_TIMEOUT);
ioctl(fd, I2C_RETRIES, retry ? retry : I2C_DEFAULT_RETRY);
return 0;
}
void
i2c_close(int fd)
{
if (fd < 0)
return;
close(fd);
}
unsigned bcd2bin(unsigned char val)
{
return (val & 0x0f) + (val >> 4) * 10;
}
unsigned char bin2bcd(unsigned val)
{
return ((val / 10) << 4) + val % 10;
}
i2c.h :
************* ********************************************
#ifndef I2C_H
#define I2C_H
#include <linux/types.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <linux/rtc.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#define I2C_DEFAULT_TIMEOUT 1
#define I2C_DEFAULT_RETRY 3
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef signed char s8;
typedef short s16;
typedef int s32;
typedef long long s64;
unsigned bcd2bin(unsigned char val);
unsigned bcd2bin(unsigned char val);
static int
__i2c_send(int fd, struct i2c_rdwr_ioctl_data *data);
static int
__i2c_set(int fd, unsigned int timeout, unsigned int retry);
int
i2c_read_data(u16 addr, u8 offset, u8 *val);
int
i2c_write_data(u16 addr, u8 offset, u8 val);
int
i2c_open(unsigned char* dev, unsigned int timeout, unsigned int retry);
#endif
************* ************************************************
by 韩大卫 @吉林师范大学
转载于:https://my.oschina.net/handawei/blog/68526