[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

¡Esta es una revisión vieja del documento!


Traducción en progreso. rramp

Conectando dispositivos I2C a tu sistema

Circuito Inter-Integrado (I²C o más a menudo también escrito como I2C) es un bus multimaestro en serie de un solo extremo inventado por la división de semiconductores de Philips (consulte el artículo de wikipedia para obtener más información I2C) y de uso común en muchos dispositivos electrónicos modernos, incluyendo PC. I²C usa solo dos lineas bidireccionales open-drain, Serial Data Line (SDA) y Serial Clock Line(SCL), llevados al nivel lógico 1 por resistencias de pullup. Típicamente son empleadas tensiones de +5 Volt o +3,3 Volt aunque sistemas con otras tensiones son permitidas. Esto tiene una implicación interesante: el nivel lógico 1 es logrado sin hacer nada, mientras que el nivel lógico 0 debe ser llevado a tierra. Aunque existen desplazadores de nivel de tensión para I2C bidireccionales (PCA9306) puede ser factible experimentar soluciones más simples si en el bus I2C no va a tener muchos dispositivos conectados. Para más información, consulte la sección “Desplazamiento del nivel de tensión”.

I²C uses only two bidirectional open-drain lines, Serial Data Line (SDA) and Serial Clock Line (SCL), pulled to logic level 1 by pullup resistors. Typical voltages used are +5 V or +3.3 V although systems with other voltages are permitted and are often encountered. This has an interesting implication: logic level 1 is achieved by doing nothing whilst logic level 0 needs to be pulled down to ground. Although there are bidirectional I2C voltage level shifters (like the PCA9306) it may be feasible to experiment simpler solutions if on the I2C bus you're not going to have many devices connected. See more on this in the “Voltage Level Shifting” chapter.

Prefacio

La mayoría de las computadoras personales (PC) modernas tienen componentes internos que comunican información vital, por ejemplo temperaturas criticas de componentes, sobre un bus I2C. Este tipo de cosas están conectadas de fábrica en su PC y pueden ser tratadas con lm_sensors… lo que queremos hacer aquí es usar un bus I2C en su computadora para conectar algún sensor I2C externo como un acelerómetros. Estoy etiquetando esto en la sección de hardware de ARM porque creo que, excluyendo el tema de los sensores lm, la mayoría de la gente estará haciendo este tipo de cosas en sistemas ARM embebidos… pero los conceptos son aplicables a cualquier sistema que admita Linux con un bus I2C.

Preparando el sistema principal

Antes de empezar, es posible que desee asegurarse de que el sistema operativo tiene todo lo necesario para gestionar el bus I2C que va a utilizar. Lo primero que hay que hacer es asegurarse de que tiene el controlador de kernel correcto para cualquier implementación de capa física en su sistema. Tendrás que investigar en las hojas de datos del hardware de tu sistema… mi Pi tiene bcm2708 así que en mi caso se trataba de cargar el módulo bcm2708_i2c. También puede ser necesario cargar el módulo i2c-dev dependiendo de su configuración.

Una vez que tenga los controladores correctos, es posible que desee disponer de una herramienta de espacio de usuario que le ayude a detectar buses, presentar dispositivos y comunicarse con los dispositivos I2C de cada bus. Utilizo i2ctools para esto pero no está empaquetado entre los paquetes de Slackware y no pude encontrar un tercero que ofreciera un paquete de ARM Slackware, así que descargué las fuentes y las compilé para mí mismo. Las fuentes se pueden obtener desde aquí: I2CTools.

Conectando un dispositivo sobre el bus I2C

Siempre que haya resuelto los problemas de nivel de tensión (consulte el capítulo “Desplazamiento del nivel de tensión”), añadir un nuevo dispositivo en el bus es muy sencillo. El bus es multimaster, lo que significa que puedes tener muchos dispositivos (hasta 101) en el mismo bus, así que todo lo que tienes que hacer es hacer 4 conexiones: Potencia, tierra, SDA y SCL. Si es el primer dispositivo que conecta en el bus, puede ser necesario instalar los pullups entre Power-SDA y Power-SCL. Es tan simple como eso y si el sistema está listo con los controladores apropiados y las utilidades del país del usuario, usted está listo para acceder al dispositivo recién conectado.

Detectando dispositivos conectados

Hay probablemente muchas formas para determinar que está conectado a un bus I2C. Elegí usar cosas del proyecto i2ctools. No pude encontrar un paquete Slackware ARM para i2ctools, así que lo compilé e instalé en mi sistema.

Lo primero que debe saber es qué buses I2C están presentes en su sistema, ya que puede haber más de uno y mirar en el bus equivocado puede ser frustrante:

root@pi:~# i2cdetect  -l
i2c-0   i2c             bcm2708_i2c.0                           I2C adapter
i2c-1   i2c             bcm2708_i2c.1                           I2C adapter
root@pi:~#

Si no estás seguro de en qué bus conectaste tus cosas, es posible que quieras hacer esto en todos los buses:

root@pi:~# i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 1e -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- 69 -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- 77                         
root@pi:~#

Comunicación con un dispositivo I2C

Communication with I2C devices id done by reading and writing to it's registers. Each device has it's own register list and is something you need to look for in the device's datasheet. Some devices even need some preliminary calibration to be done before you can read any sensible data out of them so before you start using i2cget to read out some registers you should at least have an idea of what registers you are interested in and fave figured out if your device needs calibration prior to reading any sensible data. Registers can be set by using i2cset but do this only if you have read the datasheet.

Even once you know if your device needs calibration and the registers involved, the content of the registers may not be in a convenient format for ready use. There are generally various scripts in perl or python that address calibrating and managing data from specific I2C devices so that human readable information can be produced.

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 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     0      WHO_AM_I      R/W    
 15    21     SMPLRT_DIV     R/W   
 16    22       DLPF_FS      R/W   
 17    23       INT_CFG      R/W  
 1A    26     INT_STATUS     R     
 1B    27     TEMP_OUT_H     R                            TEMP_OUT_H
 1C    28     TEMP_OUT_L     R                             TEMP_OUT_L
 1D    29    GYRO_XOUT_H     R                           GYRO_XOUT_H
 1E    30    GYRO_XOUT_L     R                           GYRO_XOUT_L
 1F    31    GYRO_YOUT_H     R                           GYRO_YOUT_H
 20    32    GYRO_YOUT_L     R                           GYRO_YOUT_L
 21    33    GYRO_ZOUT_H     R                           GYRO_ZOUT_H
 22    34    GYRO_ZOUT_L     R                            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
     0  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);
}

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 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.

Fuentes

  • Escrito originalmente por louigi600.
  • Traducido por — rramp 2019/07/16 22:51 (UTC).

 es:howtos:hardware:arm:interfacing_i2c_devices ()
Esta traducción es más antigua que la página original y podría estar obsoleta. Ver lo que ha cambiado.