Linux下使用I2C總線讀寫 EEPROM(讀寫i2c從設備通用程序)

 Linux下使用IIC總線 讀寫i2c從設備寄存器

by 韓大衛 @吉林師範大學

[email protected]

                  轉載請務必表明出處
******************* **********************************************

2012.7.16

1,本文給出了linux 下使用IIC總線讀寫i2c從設備的實現程序。
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的地址(或寄存器地址)

4,本程序以EEPROM爲例,最後出了如何讀寫i2c下的從設備的通用程序,而不是針對於EEPROM這一種芯片。另篇文章《linux下使用i2c總線與E2PROM通信》中有針對

EEPROM的一些操作。但原理是與本文相同的。

 我們嵌入式系統中的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   韓大衛 @吉林師範大學
 
發佈了29 篇原創文章 · 獲贊 28 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章