[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/20 03:29 (UTC)] – [Communicating With An I2C Device] louigi600interfacing_i2c_devices [2014/03/22 22:10 (UTC)] – [Communicating With An I2C Device] louigi600
Line 91: Line 91:
 Considering that my current ambient temperature is about 20 Celcius I guess that for uncalibrated data that's OK. 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 temperature here's an example:+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   #!/bin/bash
 +  BUS=1
 +  ID=0x69
 +  ATH=0x1b
 +  ATL=0x1c
 +  ARXH=0x1d
 +  ARXL=0x1e
 +  ARYH=0x1f
 +  ARYL=0x20
 +  ARZH=0x21
 +  ARZL=0x22
      
-  TH=$(i2cget -y  1 0x69  0x1b b |sed -e "s/^0x//|tr "a-z" "A-Z") +  #need upper case hex stripped of prefix "0x" or bc will not like the input 
-  TL=$(i2cget -y  1 0x69  0x1c b |sed -e "s/^0x//" |tr "a-z" "A-Z")+  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 "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: " 
      
 +  echo -n "Temp in Celcius: "
   #this takes hex input and evaluates the followin formula in hexadecimal   #this takes hex input and evaluates the followin formula in hexadecimal
   #temp= 35 + ((raw + 13200) / 280))"   #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)  +  #where raw is the input reading un 2's compliment  
-  echo "ibase=16; input=$TH$TL; if ( input >= 8000 ) { raw=input - 10000;} else {raw=input;}; 23 + ((raw + 3390)/118);" |bc -l+  #(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" 
 +  #define I2C_DEV_ADDR 0x69 
 +  #define I2C_DEV_SELF 0x0 
 +  #define I2C_DEV_INT 0x1a 
 +  #define I2C_DEV_REG_START_ADDR 0x1b 
 +  #define I2C_DEV_REG_END_ADDR 0x22 
 +   
 +  #define TEMP_RAW_OFFSET 13200 
 +  #define TEMP_RAW_SENSITIVITY 280 
 +  #define TEMP_OFFSET 35 
 +   
 +  #define ROT_RAW_SENSITIVITY 14.375 
 +   
 +  void read_registers (int file, int *raw) 
 +  { char buf[256] = {0}; 
 +    int i,j,k; 
 +   
 +  /*For some unexpected reason I'm getting responses from the other I2C devices 
 +    on the same bus so I'm ignoring data that does not match the WHO AM I reg 
 +    and also data that has not set the interrupt register (meaning that data is 
 +    not really avalible) 
 +  */ 
 +    while (buf[0] != 0x69 || buf[26] == 0) 
 +    { if (read(file,buf,sizeof(buf)) != sizeof(buf))   
 +      { /* ERROR HANDLING: i2c transaction failed */ 
 +        printf("Failed to read from the i2c bus.\n"); 
 +        exit(1); 
 +      } 
 +    } 
 +   
 +    j=0; 
 +    for(i=I2C_DEV_REG_START_ADDR;i<=I2C_DEV_REG_END_ADDR;i++) 
 +    { k= (buf[i] << 8) + buf[i+1]; 
 +      if ( k > 32768 ) *(raw+j)= k - 65536; 
 +      else *(raw+j)=k; 
 +      i++; 
 +      j++; 
 +    }  
 +  } 
 +   
 +  main () 
 +  { int file,buffer; 
 +    int i,j,k; 
 +    int raw_data[4]; 
 +    float data[4],tdata[10],rxdata[10],rydata[10],rzdata[10]; 
 +    char buf[256] = {0}; 
 +   
 +    if ((file = open(I2C_DEVICE, O_RDWR)) < 0)  
 +    { /* ERROR HANDLING: you can check errno to see what went wrong */ 
 +      perror("Failed to open the i2c bus"); 
 +      exit(1); 
 +    }  
 +   
 +    if (ioctl(file, I2C_SLAVE, I2C_DEV_ADDR) < 0)  
 +    { printf("Failed to acquire bus access and/or talk to slave.\n"); 
 +      /* ERROR HANDLING; you can check errno to see what went wrong */ 
 +      exit(1); 
 +    } 
 +   
 +    for(i=0;i<10;i++) 
 +    { read_registers(file,&raw_data[0]); 
 +      tdata[i]=TEMP_OFFSET + (((float)raw_data[0] + TEMP_RAW_OFFSET) / TEMP_RAW_SENSITIVITY); 
 +      rxdata[i]=(float)raw_data[1]/ ROT_RAW_SENSITIVITY; 
 +      rydata[i]=(float)raw_data[2]/ ROT_RAW_SENSITIVITY; 
 +      rzdata[i]=(float)raw_data[3]/ ROT_RAW_SENSITIVITY; 
 +    } 
 +    close(file); 
 +   
 +    for(i=0;i<10;i++) 
 +    { data[0]=data[0] + tdata[i]; 
 +      data[1]=data[1] + rxdata[i]; 
 +      data[2]=data[2] + rydata[i]; 
 +      data[3]=data[3] + rzdata[i]; 
 +    } 
 +    for(i=0;i<4;i++) data[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]);
 +  }
 ====== 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.
 howtos:hardware:arm:interfacing_i2c_devices ()