[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.
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionLast revisionBoth sides next revision | ||
interfacing_i2c_devices [2014/03/18 20:00 (UTC)] – [Communicating With An I2C Device] louigi600 | howtos:hardware:arm:interfacing_i2c_devices [2023/05/28 14:36 (UTC)] – [Hacking I2C in PC DIMM modules] louigi600 | ||
---|---|---|---|
Line 5: | Line 5: | ||
====== Preface ====== | ====== Preface ====== | ||
- | Most modern PC have several internal components that communicate vital information, | + | Most modern PC have several internal components that communicate vital information, |
+ | |||
+ | 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 " | ||
====== 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:// | + | There are probably many ways to determine what's connected to an I2C bus, I chose to use stuff out of the [[http:// |
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 69: | Line 72: | ||
I chose the ITG3200 because it has a temperature sensor inside and I'm hoping I can read that without having to do any calibration, | I chose the ITG3200 because it has a temperature sensor inside and I'm hoping I can read that without having to do any calibration, | ||
- | root@pi:~# i2cdump | + | root@pi:~# i2cdump |
- | WARNING! This program can confuse your I2C bus, cause data loss and worse! | + | |
- | I will probe file /dev/i2c-1, address 0x69, mode byte | + | |
- | Probe range limited to 0x1b-0x1c. | + | |
- | Continue? [Y/n] y | + | |
| | ||
10: c0 90 ?? | 10: c0 90 ?? | ||
root@pi:~# | root@pi:~# | ||
- | So we got c090 as our temperature reading. | + | The above example dumps registers 1b and 1c from the ITG3200 the same result can be achieved with i2cget: |
+ | |||
+ | root@pi:~# i2cget -y 1 0x69 0x1b b | ||
+ | 0xc0 | ||
+ | root@pi:~# i2cget -y 1 0x69 0x1c b | ||
+ | 0x90 | ||
+ | root@pi: | ||
+ | |||
+ | So we got c090 as our temperature reading. | ||
+ | 16528 - 32768 = -16240 | ||
+ | I was unable to find in the datasheet what units this reading is in but they did mention that there was an average offset of 13200. I did a little google search and found this formula: | ||
+ | |||
+ | 35 + ((raw value + 13200) / 280)) | ||
+ | 35 + ((13200 - 16240)/280) = 24.14 | ||
+ | |||
+ | Considering that my current ambient temperature is about 20 Celcius I guess that for uncalibrated data that's OK. | ||
+ | |||
+ | If you want a script that does the maths for you and just reads out the ITG3200 sensor data in a human readable format here's an example: | ||
+ | |||
+ | # | ||
+ | BUS=1 | ||
+ | ID=0x69 | ||
+ | ATH=0x1b | ||
+ | ATL=0x1c | ||
+ | ARXH=0x1d | ||
+ | ARXL=0x1e | ||
+ | ARYH=0x1f | ||
+ | ARYL=0x20 | ||
+ | ARZH=0x21 | ||
+ | ARZL=0x22 | ||
+ | |||
+ | #need upper case hex stripped of prefix " | ||
+ | for VAR in TH TL RXH RXL RYH RYL RZH RZL | ||
+ | do | ||
+ | CMD=" | ||
+ | eval $CMD | ||
+ | eval "echo $VAR = \$$VAR" | ||
+ | done | ||
+ | |||
+ | echo "Temp register: $(echo " | ||
+ | |||
+ | echo -n "Temp in Celcius: " | ||
+ | #this takes hex input and evaluates the followin formula in hexadecimal | ||
+ | #temp= 35 + ((raw + 13200) / 280))" | ||
+ | #where raw is the input reading un 2's compliment | ||
+ | #(to uncompliment the input I take away 0x10000 if input is larger then 0x8000) | ||
+ | echo " | ||
+ | |||
+ | echo "X Axis Rotation Register: $(echo " | ||
+ | echo -n "X Axis Angula velocity degree/sec: " | ||
+ | echo " | ||
+ | |||
+ | echo "Y Axis Rotation Register: $(echo " | ||
+ | echo -n "Y Axis Angula velocity degree/sec: " | ||
+ | echo " | ||
+ | |||
+ | echo "Z Axis Rotation Register: $(echo " | ||
+ | echo -n "Z Axis Angula velocity degree/sec: " | ||
+ | echo " | ||
+ | |||
+ | 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, | ||
+ | |||
+ | 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 < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | #define I2C_DEVICE "/ | ||
+ | |||
+ | / | ||
+ | #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, | ||
+ | { __s32 res; | ||
+ | int i,j,k; | ||
+ | |||
+ | for(i=0; | ||
+ | { k=0; | ||
+ | for (j=0; | ||
+ | { | ||
+ | if( (res = i2c_smbus_read_byte_data(file, | ||
+ | { printf(" | ||
+ | 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_YRH, | ||
+ | int ITG3200_RAW_DATA[4]; | ||
+ | float ITG3200_DATA[4]; | ||
+ | |||
+ | if ((file = open(I2C_DEVICE, | ||
+ | { perror(" | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | if (ioctl(file, | ||
+ | { printf(" | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | /*Take an avarage over 10 consecuitve readings on the ITG3200*/ | ||
+ | for (i=0; | ||
+ | { ITG3200_read(file,& | ||
+ | |||
+ | 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; | ||
+ | |||
+ | printf(" | ||
+ | printf(" | ||
+ | printf(" | ||
+ | printf(" | ||
+ | |||
+ | 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:// | 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:// | ||
- | This is how I connected my 5v IMU pcb to a 3.3v I2C bus on my RaspberryPI: | + | This is how I connected my 5v IMU pcb to a 3.3v I2C bus on my RaspberryPI: |
+ | |||
+ | |||
+ | ====== 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' | ||
+ | |||
+ | 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: / | ||
+ | |||
+ | The last part 0-0050 identifies bus 0 address 50. | ||
+ | |||
====== Sources ====== | ====== Sources ====== |