[2024-feb-29] Sad news: Eric Layton aka Nocturnal Slacker aka vtel57 passed away on Feb 26th, shortly after hospitalization. He was one of our Wiki's most prominent admins. He will be missed.

Welcome to the Slackware Documentation Project

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
interfacing_i2c_devices [2014/03/22 04:01 (UTC)] – [Communicating With An I2C Device] louigi600howtos:hardware:arm:interfacing_i2c_devices [2023/05/28 14:46 (UTC)] (current) – [Hacking I2C in PC DIMM modules] louigi600
Line 5: Line 5:
  
 ====== Preface ====== ====== Preface ======
-Most modern PC have several internal components that communicate vital information, like internal temperatures of critical components, over I2C bus. This type of stuff is factory wired into your PC and can be dealt with lm_senosts ... what we want to do here is use an I2C bus on your computer to connect some external I2C sensor like an accelerometer. I'm tagging this in the ARM hardware section because I think that, excluding the lm-sensors stuff, most people will be doing this sort of thing on embedded ARM systems ... but the concepts are applicable to any linux capable system with an I2C bus.+Most modern PC have several internal components that communicate vital information, like internal temperatures of critical components, over I2C bus. This type of stuff is factory wired into your PC and is normally dealt with lm_senosrs ... what we want to do here is use an I2C bus on your computer to connect some external I2C sensor like an accelerometer. I'm tagging this in the ARM hardware section because I think that, excluding the lm-sensors stuff, most people will be doing this sort of thing on embedded ARM systems ... but the concepts are applicable to any linux capable system with an I2C bus. 
 + 
 +Should you want to hack one of the many I2C busses on your PC the easiest one to access is the one in the DIMM modules. Modern DIMM modules have an I2C eprom in them that the bios reads to find out the characteristics of the DIMM module. Since the DIMM module can be removed from PC and hacked separately without risking to damage permanently your PC it is probably your safest option. See chapter "Hacking I2C in PC DIMM modules" for more details. 
  
 ====== Preparing Your Host System ====== ====== Preparing Your Host System ======
Line 18: Line 21:
  
 ===== Detecting Connected Devices ===== ===== Detecting Connected Devices =====
-There are probably many ways to determine what's connected to an I2C bus, I chose to use stuff out of the [[http://www.lm-sensors.org/wiki/I2CTools | i2ctools]] project. I was unable to find a Slackware ARM package for i2ctools so I compiled and installed it on my system.+There are probably many ways to determine what's connected to an I2C bus, I chose to use stuff out of the [[http://www.lm-sensors.org/wiki/I2CTools | i2ctools]] project. I was unable to find a Slackware ARM package for i2ctools so I compiled and installed it on my system. On some distribution the package may be called t2c-tools.
  
 First thing you want to know is what I2C busses are present on your system as there may be more then one and looking in the wrong bus may be frustrating: First thing you want to know is what I2C busses are present on your system as there may be more then one and looking in the wrong bus may be frustrating:
Line 135: Line 138:
  
 The above script just does one simple dump of the register data and converts the values into human readable format, it does nothing with regards to calibration and averaging out vibrations. More consistent gyroscopic readings would be achieved if an average over 10 consecutive data samples was made thus averaging out most of the ambient vibrations.  The above script just does one simple dump of the register data and converts the values into human readable format, it does nothing with regards to calibration and averaging out vibrations. More consistent gyroscopic readings would be achieved if an average over 10 consecutive data samples was made thus averaging out most of the ambient vibrations. 
 +
 +A bash script is really not the most suitable way to read data from I2C devices, a faster means of managing the data from the devices is really mandatory in order to do calibration, data averaging and what more to make the information consistent and useful for further calculations. I found the Linux kernel i2c documentation a usefull reference (<kernel source tree>/Documentation/i2c/dev-interface); it's not the only way that data can be read but it's a good starting point.
 +
 +I hate showing my poor C programming capabilities but here's some code that uses i2c-dev to read stuff from the ITG3200 and takes an average over 10 readings:
 +
 +  #include <sys/ioctl.h>
 +  #include <errno.h>
 +  #include <string.h>
 +  #include <stdio.h>
 +  #include <stdlib.h>
 +  #include <unistd.h>
 +  #include <linux/i2c-dev.h>
 +  #include <fcntl.h>
 +  #include <errno.h>
 +  
 +  #define I2C_DEVICE "/dev/i2c-1"
 +  
 +  /*ITG3200*/
 +  #define ITG3200_ADDR 0x69
 +  #define ITG3200_SELF 0x0
 +  #define ITG3200_INT 0x1a
 +  #define ITG3200_TH 0x1b /*2 bytes Hight byte and Low byte*/
 +  #define ITG3200_TL 0x1c
 +  #define ITG3200_XRH 0x1d /*2 byte Hight byte and Low byte*/
 +  #define ITG3200_XRL 0x1e
 +  #define ITG3200_YRH 0x1f /*2 byte Hight byte and Low byte*/
 +  #define ITG3200_YRL 0x20
 +  #define ITG3200_ZRH 0x21 /*2 byte Hight byte and Low byte*/
 +  #define ITG3200_ZRL 0x22 /*2 byte Hight byte and Low byte*/
 +  #define ITG3200_TEMP_RAW_OFFSET 13200
 +  #define ITG3200_TEMP_RAW_SENSITIVITY 280
 +  #define ITG3200_TEMP_OFFSET 35
 +  #define ITG3200_ROT_RAW_SENSITIVITY 14.375
 +  
 +  int twosc2int(int twoscomplimentdata)
 +  { int retval;
 +    if( twoscomplimentdata > 32768 ) retval = twoscomplimentdata - 65536;
 +    else retval = twoscomplimentdata;
 +    return retval;
 +  }
 +  
 +  float ITG3200_rot_conv(int rawdata)
 +  { float retval;
 +    int raw;
 +  
 +    raw=twosc2int(rawdata);
 +    retval = (float)raw / (float)ITG3200_ROT_RAW_SENSITIVITY;
 +    return retval;
 +  }
 +  
 +  float ITG3200_temp_conv(int rawdata)
 +  { float retval;
 +    int raw;
 +  
 +    raw=twosc2int(rawdata);
 +    retval = (float)ITG3200_TEMP_OFFSET + (((float)raw + ITG3200_TEMP_RAW_OFFSET) / ITG3200_TEMP_RAW_SENSITIVITY);
 +    return retval;
 +  }
 +  
 +  void ITG3200_read (int file, int *raw, int *reg_array,int size)
 +  { __s32 res;
 +    int i,j,k;
 +  
 +    for(i=0;i<size;i++)
 +    { k=0;
 +      for (j=0;j<2;j++)
 +      {
 +        if( (res = i2c_smbus_read_byte_data(file,*(reg_array + i + j)) )< 0 )
 +        { printf("Failed to read from the i2c bus.\n");
 +          exit(1);
 +        }
 +        if (j == 0) k=(int)res << 8;
 +        else
 +        { k += (int)res;
 +          *(raw + (i/2))=k;
 +        }
 +      }
 +      i++;
 +    }
 +  }
 +  
 +  main ()
 +  { int file;
 +    int i,j,k;
 +    float data[4]={0};
 +  
 +    int ITG3200_REGS[8]={ITG3200_TH,ITG3200_TL,ITG3200_XRH,ITG3200_XRL,
 +      ITG3200_YRH, ITG3200_YRL,ITG3200_ZRH,ITG3200_ZRL};
 +    int ITG3200_RAW_DATA[4];
 +    float ITG3200_DATA[4];
 +  
 +    if ((file = open(I2C_DEVICE, O_RDWR)) < 0)
 +    { perror("Failed to open the i2c bus");
 +      exit(1);
 +    }
 +  
 +    if (ioctl(file, I2C_SLAVE, ITG3200_ADDR) < 0)
 +    { printf("Failed to acquire bus access and/or talk to slave.\n");
 +      exit(1);
 +    }
 +  
 +  /*Take an avarage over 10 consecuitve readings on the ITG3200*/
 +    for (i=0;i<10;i++)
 +    { ITG3200_read(file,&ITG3200_RAW_DATA[0],&ITG3200_REGS[0],sizeof(ITG3200_REGS)/sizeof(ITG3200_REGS[0]));
 +  
 +      data[0] += ITG3200_temp_conv(ITG3200_RAW_DATA[0]);
 +      data[1] += ITG3200_rot_conv(ITG3200_RAW_DATA[1]);
 +      data[2] += ITG3200_rot_conv(ITG3200_RAW_DATA[2]);
 +      data[3] += ITG3200_rot_conv(ITG3200_RAW_DATA[3]);
 +    }
 +    for(i=0;i<4;i++) data[i] /= 10; 
 +  
 +    printf("Temp. : %2.2f \n",data[0]);
 +    printf("Rot. X : %2.2f \n",data[1]);
 +    printf("Rot. Y : %2.2f \n",data[2]);
 +    printf("Rot. Z : %2.2f \n",data[3]);
 +  
 +    close(file);
 +  }
 +
 +
 ====== Voltage Level Shifting ====== ====== Voltage Level Shifting ======
 You may end up with heterogeneous voltage level devices and if you have many devices the correct way to work around this problem is by using bidirectional I2C voltage level shifters like the [[ http://www.ti.com/lit/ds/symlink/pca9306.pdf |PCA9306]], but if you only have a few devices all grouped up in a neat PCB like the 10DOF IMU unit you might want to give a simpler system a try. You may end up with heterogeneous voltage level devices and if you have many devices the correct way to work around this problem is by using bidirectional I2C voltage level shifters like the [[ http://www.ti.com/lit/ds/symlink/pca9306.pdf |PCA9306]], but if you only have a few devices all grouped up in a neat PCB like the 10DOF IMU unit you might want to give a simpler system a try.
-This is how I connected my 5v IMU pcb to a 3.3v I2C bus on my RaspberryPI: I took as educated guess that 4.4v (5v with a 4148 diode in series) would still be a tolerable power supply voltage for the whole IMU pcb, this would most likely allow all the I2C devices on the IMU pcb to recognize a minimum of 3.08v (4.4 * 0.7) as the lowest reliable logic level 1 tension allowing it to inter-operate with the PI's 3.3v logig levels. I was not able to find if the PI has internal pullups on the I2C bus or if they have to be externally placed so in doubt I put in 10k pullups between the 4.4v power line and the 2 data lines. I was the able to correctly detect the sensors on the IMU pcb.   +This is how I connected my 5v IMU pcb to a 3.3v I2C bus on my RaspberryPI: I took as educated guess that 4.4v (5v with a 4148 diode in series) would still be a tolerable power supply voltage for the whole IMU pcb, this would most likely allow all the I2C devices on the IMU pcb to recognize a minimum of 3.08v (4.4 * 0.7) as the lowest reliable logic level 1 tension allowing it to inter-operate with the PI's 3.3v logic levels. I was not able to find if the PI has internal pullups on the I2C bus or if they have to be externally placed so in doubt I put in 10k pullups between the 4.4v power line and the 2 data lines. I was the able to correctly detect the sensors on the IMU pcb.    
 + 
 + 
 +====== Hacking I2C in PC DIMM modules ====== 
 +As mentioned in the Preface it is possible to hack, in fact, any one of the I2C busses on your PC it's just that the one in DIMM modules it the easiest one. 
 +You will need to identify these 4 connections on your DIMM module's eprom that respectively go to these pins on the DIMM module itself: 
 + 
 +200 pin SO-DIMM 1/2: 
 +^ Pin ^ Function  ^  
 +| 197 | Vcc (3v)  | 
 +| 193 | SDA       | 
 +| 195 | SCL       | 
 +| 185 | GND       | 
 + 
 +204 pin SO-DIMM 3:  
 +^ Pin   ^ Function  ^  
 +| 199     | Vcc (3v)| 
 +| 200     | SDA     | 
 +| 202     | SCL     | 
 +| 203/204 | GND     | 
 + 
 +260 pin SO-DIMM 4: 
 +^ Pin     ^ Function  ^  
 +| 255     | Vcc (2.5v)| 
 +| 254     | SDA       | 
 +| 253     | SCL       | 
 +| 251/252 | GND       | 
 + 
 +Once you have identified them you can stack up another I2C device on the bus provided it will not conflict with the addresses in use on this bus. 
 + 
 +I have an old laptop with a 4Gb SO-DIMM 3 that is perfect for experimenting. 
 +The DIMM has onboard a ST M34: a I2C bus Serial EEPROM, SPD for DRAM. I downloaded the datasheet and found that on the eprom itself 
 +^ Pin ^ Function ^  
 +| 8   | Vcc (3v) | 
 +| 5   | SDA      | 
 +| 6   | SCL      | 
 +| 4   | GND      | 
 + 
 +and I double-checked that these pins are actually connected to the respective pins on the SO-DIMM module. 
 +I was particularly lucky and this module has unused solder pads for a second eprom unit, making it super easy to solder some wires on the unused pats to hack another I2C device into the bus. 
 + 
 +Next you will need to identify which bus is the one reading the eprom on the DIMM modules, there is another tool that is part of the i2ctool that comes in handy for this: decode-dimms. 
 +At the beginning it will sit out a line like this 
 + 
 +Decoding EEPROM: /sys/bus/i2c/drivers/ee1004/0-0050 
 + 
 +The last part 0-0050 identifies bus 0 address 50.  
 + 
 +Looking at what devices are present on bus 0 
 + 
 +  # i2cdetect -y 0 
 +        1  2  3  4  5  6  7  8  9  a  b  c  d  e  f 
 +  00:                         08 -- -- -- -- -- -- --  
 +  10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --  
 +  20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --  
 +  30: 30 -- -- -- -- 35 UU UU -- -- -- -- -- -- -- --  
 +  40: -- -- -- -- 44 -- -- -- -- -- -- -- -- -- -- --  
 +  50: UU -- 52 -- -- -- -- -- -- -- -- -- -- -- -- --  
 +  60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --  
 +  70: -- -- -- -- -- -- -- -- 
 + 
 +So as long as whatever device I hack into this bus does not use address 8,30,35,36,37,44,50 or 52 it should work fine, provided the voltages are compatible. 
 + 
  
 ====== Sources ====== ====== Sources ======
 howtos:hardware:arm:interfacing_i2c_devices ()