[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
Next revisionBoth sides next revision
interfacing_i2c_devices [2014/03/18 19:14 (UTC)] – [Communicating With An I2C Device] louigi600howtos:hardware:arm:interfacing_i2c_devices [2014/12/29 13:24 (UTC)] – interfacing_i2c_devices renamed to howtos:hardware:arm:interfacing_i2c_devices (This was misplaced in the Wiki namespace root) alienbob
Line 48: Line 48:
  
 At this point it's impossible to show any further info about I1C communication without going into some detail about a specific device so I'll pick one ot the devices on my IMU pcb. While detecting previously on my PI I found 4 devices with the flowing hex addresses: 1e,40,69 and 77. At this point it's impossible to show any further info about I1C communication without going into some detail about a specific device so I'll pick one ot the devices on my IMU pcb. While detecting previously on my PI I found 4 devices with the flowing hex addresses: 1e,40,69 and 77.
-Generally each I2C device has a range of addresses than can set. We shall concentrate on the device with 0x69 address. By doing some cross reference search on the IMU pcb datasheet and on the single datasheets of each device present on my IMU it's revealed that 0x69 should be the address of the ITG3200 (gyro + temp sensor) and indeed the ITG3200 asserts that it can have either 0x68 or 0x69 address selectable by logic level on pin 9.+Generally each I2C device has a range of addresses than can set. We shall concentrate on the device with 0x69 address. By doing some cross reference search on the IMU pcb datasheet and on the single datasheets of each device present on my IMU it's revealed that 0x69 should be the address of the ITG3200 (gyro + temp sensor) and indeed the ITG3200 datasheet asserts that it can have either 0x68 or 0x69 address selectable by logic level on pin 9. No what we need is the ITG3200 register chart: 
 + 
 +  Addr  Addr     Register Name R/W    
 +  Hex  Decimal 
 +    0          WHO_AM_I      R/W     
 +   15    21     SMPLRT_DIV     R/   
 +   16    22       DLPF_FS      R/W    
 +   17    23       INT_CFG      R/W   
 +   1A    26     INT_STATUS          
 +   1B    27     TEMP_OUT_H                                TEMP_OUT_H 
 +   1C    28     TEMP_OUT_L                                 TEMP_OUT_L 
 +   1D    29    GYRO_XOUT_H                               GYRO_XOUT_H 
 +   1E    30    GYRO_XOUT_L                               GYRO_XOUT_L 
 +   1F    31    GYRO_YOUT_H                               GYRO_YOUT_H 
 +   20    32    GYRO_YOUT_L                               GYRO_YOUT_L 
 +   21    33    GYRO_ZOUT_H                               GYRO_ZOUT_H 
 +   22    34    GYRO_ZOUT_L                                GYRO_ZOUT_L 
 +   3E    62      PWR_MGM       R/W  
 + 
 +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, just for the sake of keeping the example as simple as possible. According to the chart the temperature register addresses are 1b and 1c so let's go and try and get some data out of there: 
 + 
 +  root@pi:~# i2cdump  -y -r 0x1b-0x1c 1 0x69 b 
 +        1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef 
 +  10:                                  c0 90                        ??    
 +  root@pi:~# 
 + 
 +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. According to the datasheet this value is a 2's compliment of the temperature. So let's try and figure out what that would be: c090 written in binary is 1100000010010000, the most significant bit is 1 so the result should be  
 +  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: 
 + 
 +  #!/bin/bash 
 +  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 "0x" or bc will not like the input 
 +  for VAR in TH TL RXH RXL RYH RYL RZH RZL 
 +  do 
 +    CMD="$VAR=\$(i2cget -y $BUS $ID \$A$VAR  b |sed -e "s/^0x//" |tr "a-z" "A-Z")" 
 +    eval $CMD 
 +    eval "echo $VAR = \$$VAR" 
 +  done 
 +   
 +  echo "Temp register: $(echo "ibase=16; $TH$TL" | bc -l) 0x$TH$TL ($(echo "ibase=16; obase=2; $TH$TL" | bc -l))" 
 +   
 +  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 "ibase=16; input=$TH$TL; if ( input >= 8000 ) { raw=input - 10000;} else { raw=input;}; 23 + ((raw + 3390)/118);" |bc -l 
 +   
 +  echo "X Axis Rotation Register: $(echo "ibase=16; $RXH$RXL" | bc -l) 0x$RXH$RXL ($(echo "ibase=16; obase=2; $RXH$RXL" | bc -l))" 
 +  echo -n "X Axis Angula velocity degree/sec: " 
 +  echo "ibase=16; input=$RXH$RXL; if ( input >= 8000 ) { raw= input - 10000;} else { raw=input;}; raw / E.177" |bc -l 
 +   
 +  echo "Y Axis Rotation Register: $(echo "ibase=16; $RYH$RYL" | bc -l) 0x$RYH$RYL ($(echo "ibase=16; obase=2; $RYH$RYL" | bc -l))" 
 +  echo -n "Y Axis Angula velocity degree/sec: " 
 +  echo "ibase=16; input=$RYH$RYL; if ( input >= 8000 ) { raw= input - 10000;} else { raw=input;}; raw / E.177" |bc -l 
 +   
 +  echo "Z Axis Rotation Register: $(echo "ibase=16; $RZH$RZL" | bc -l) 0x$RZH$RZL ($(echo "ibase=16; obase=2; $RZH$RZL" | bc -l))" 
 +  echo -n "Z Axis Angula velocity degree/sec: " 
 +  echo "ibase=16; input=$RZH$RZL; if ( input >= 8000 ) { raw= input - 10000;} else { raw=input;}; raw / E.177" |bc -l 
 + 
 +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); 
 +  }
  
  
 howtos:hardware:arm:interfacing_i2c_devices ()