Beaglebone: Inputs: Adding i2c sensors to CASSY: Accelerometer, Magnometer, Gyroscope

7/6/2017
Andrew B. Wright, Ph. D., SM ’88

Previous Post: Beaglebone: All you want to do is talk-talk, talk-talk

Next Post: Beaglebone: Inputs: Adding a Flora light sensor

I was going to use the analog inputs to connect the yaw rate sensor to the beaglebone.  However, there are so many really cool i2c sensors around.  So, I’m going to try out an adafruit 9-dof sensor for the navigation (adafruit 1714).  If the only sensor needed is yaw rate, so be it.  But, the extra functionality of all those extra inputs could be useful for expansion.  It doesn’t hurt that all the extras cost less than one yaw rate sensor.

The adafruit 9 dof accelerometer package (1714)  uses the L320gdh and the LSM303 sensors.

The mounting hole pattern uses 4 holes (0.06″[?] diameter) on a 0.7″ x 1.3″ grid), and the board fits inside 1.9″ x 0.9″ rectangle.  The addresses for the 3 different sensor packages are 0x19 (accelerometer?), 0x1e (magnetometer?), and 0x6b (gyroscope).


i2c from the command line

The adafruit 1714 shifts the voltage level up to 5v on the output pin if you apply 5v to Vin.  Beaglebone wants 3.3v, so connect 3.3v to either Vin or 3.3Vo, and SDA/SCL will be referenced to 3.3V.

There are four signals: ground, power (3.3 v on the beaglebone), SCL, SDA. I don’t know whether I’ll need to provide signal conditioning between beaglebone and accelerometer, but I think it’s handled at the accelerometer.

The i2c bus allows daisy-chaining of sensors.  In that case, SCL is connected to SCL and SCA is connected to SDA.  There is no cross-over.  I’m not sure how many sensors can be connected in series.  I’m not sure if a terminator is required (ala SCSI).  I think this is an electrical engineering thing, and your mileage may vary.

The i2c signals on the beaglebone’s expansion header occupy the following pins (3.3 v through pull-up resistor):

i2c-1: P9-18 (sda)/P9-26 (sda) and P9-17 (scl)/P9-24 (scl)

i2c-2 P9-20 (sda) and P9-19 (scl)

In addition: P9.21, P9.22 are i2c pins.

Of these pins, only P9-17 (sda) and P9-18 (scl) appear to be totally free (NOTE: now, I’m experiencing difficulty this morning.  Could it be because I had the i2c connected at boot? Nope.  Looks like some random weirdness.).  So, that’s the i2c to start with. (NOTE: lets of the internet examples use a different pin configuration [P9-19, P9-20].  I found that config-pin would not let me set those pins [although, this morning, they’re preconfigured to sda, scl].  This uses i2c2.) [NOTE: this may be because I ran i2cdetect before running the config-pin commands.

Remember to

config-pin P9.17 i2c

config-pin P9.18 i2c

Actually, i2c1 is not available at all.  I seem to remember this is something discussed on the internet.

The command

i2cdetect -r <0,1,2>

allows you to poll for addresses on /dev/i2c-<0,1,2>.

I connected

Vin (pin 1 on the 1714 header) to pin P9.3 (3.3v)

Gnd (pin 3 on the 1714 header) to pin P9.1 (gnd)

SCL (pin 4 on the 1714 header) to pin P9.17 (i2c1 scl) P9.19 (i2c2 scl)

SDA (pin 5 on the 1714 header) to pin P9.18 (i2c1 sda) P9.20 (i2c2 sda)

SCL and SDA are bi-directional.  So, you can connect this backwards (SDA-SCL, SCL-SDA), and it won’t work.  However, Beaglebone will talk on both pins.  So, a ‘scope test on the pins will give data on both lines.

When I ran

i2cdetect -y 1

I received 0x19, 0x1e, and 0x6b, which are the addresses of the sensor.

The command

i2cget -y 2 0x19

gives the response 0x00.

The command

i2cget -y 2 0x1e

gives the response 0x10.

The command

i2cget -y 2 0x6b

gives the response 0x0a (register 0x0).

To get a specific register, run the command

i2cget -y 2 0x6b 0xf b

which will show you one byte (b) of register, 0xf,  on i2c2 (-y 2) for device 0x6b.

The commands

i2cdump -y 2 0x19 b

i2cdump -y 2 0x1e b

i2cdump -y 2 0x6b b

allows you to dump the registers on the device.  This dumps byte-by-byte (b) rather than word-by-word (w).

In order to make sense of the data, you need to look at the data sheet for your device to determine what all those bytes mean.


On the c-coding.

Here’s a page talking about some of the issues related to reading and writing to an i2c device.

Here’s a page with some Raspberry Pi i2c c-code.  These functions can be directly translated to the beaglebone.

The first steps of opening and closing the device are easy.  Figuring out how to read and write and what you’re reading and writing is another story.

Luckily, I found some good examples and looked through the header files (linux/i2c-dev.h, linux/i2c.h).  Here is a hacked up program that let’s you read something from the i2c.  This example reads from device 0x6b register 0xf (WHO_AM_I).  Return value is 0xd7, which matches the spec.

#include <stdio.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>

main(){
 int fd,r;
 char res,buf[10];
 long x;
 
 if((fd = open("/dev/i2c-2",O_RDWR)) < 0) {
 printf("Failed to open bus\n");
 return(0);
 }
 printf("success 1\n");

 if(ioctl(fd,I2C_SLAVE,0x6b) < 0){
 printf("error\n");
 close(fd);
 return(0);
 }
 printf("success 2\n");

 if(ioctl(fd,I2C_FUNCS,&x) < 0){
 printf("error\n");
 close(fd);
 return(0);
 }
 printf("funcs = %x\n",x);
 if((r=read(fd,buf,2)) != 2){
 printf("read %d\n",r);
 close(fd);
 return(0);
 }
 printf("r = %d, %x, %x\n",r,buf[0],buf[1]);


 union i2c_smbus_data data;
 int err;
 struct i2c_smbus_ioctl_data args;

 args.read_write = I2C_SMBUS_READ;
 args.command = 0x0f;
 args.size = I2C_SMBUS_BYTE_DATA;
 args.data = &data;

 err = ioctl(fd, I2C_SMBUS, &args);
 printf("data = %x\n", data.byte);
 
 
 close(fd);
 return(1);
}

The main features of this are the data definitions and their interactions with the ioctl function.

Put this code in a file called i2c.c and compile it using

gcc -o i2c i2c.c

Run it from the command line using

./i2c


A more extensive example that reads words from the z axis of the sensor, polls to determine when data is ready, and converts the data to degrees per second (dps) is here:

#include <stdio.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>

#define DEVICE_ID 0x6b
#define WHO_AM_I 0xf
#define CTRL1 0x20
#define ZL 0x2c
#define ZH 0x2d
#define STATUS 0x27
#define ZMASK 0x04

int i2c_init(){
 int fd;
if((fd = open("/dev/i2c-2",O_RDWR)) < 0) {
 printf("Failed to open bus\n");
 return(-1);
 }
 printf("success 1\n");

 if(ioctl(fd,I2C_SLAVE,DEVICE_ID) < 0){
 printf("error\n");
 close(fd);
 return(-1);
 }
 printf("success 2\n");
 return(fd);
}

int i2c_read(int fd,union i2c_smbus_data *data, unsigned char reg){
 struct i2c_smbus_ioctl_data args;

 args.read_write = I2C_SMBUS_READ;
 args.command = reg;
 args.size = I2C_SMBUS_BYTE_DATA;
 args.data = data;

 return(ioctl(fd,I2C_SMBUS,&args));
}

int i2c_write(int fd,union i2c_smbus_data *data,unsigned char reg){
 struct i2c_smbus_ioctl_data args;
 args.read_write = I2C_SMBUS_WRITE;
 args.command = reg;
 args.size = I2C_SMBUS_BYTE_DATA;
 args.data = data;
 return(ioctl(fd,I2C_SMBUS,&args));
}

main(){
 int fd,k,flag;
 unsigned short x;
 short y;
 union i2c_smbus_data data;
 
 if((fd = i2c_init()) < 0) return(0); 

 if(ioctl(fd,I2C_FUNCS,&x) < 0){
 printf("no funks\n");
 }else{
 printf("funcs = %x\n",x);
 }


 i2c_read(fd,&data,WHO_AM_I);
 printf("who am I = %x\n",data.byte);

 i2c_read(fd,&data,CTRL1);
 printf("ctrl 1 = %x\n",data.byte);
 data.byte |= 0x8; //turn on data acq
 i2c_write(fd,&data,CTRL1);
 for(k=0;k<5000;k++){
 while(flag){
 i2c_read(fd,&data,STATUS);
 if(data.byte & ZMASK) flag = 1;
 }
 flag = 0;
 i2c_read(fd,&data,ZL);
 x = data.byte;
 i2c_read(fd,&data,ZH);
 x |= ((unsigned)data.byte)<<8;
 y = (int)x;
 printf("z = %x, zi = %d,zf = %f\n",x,y,(((float)y)/0x7fff)*245.0);
 }
 data.byte = 0x7; //turn on data acq
 i2c_write(fd,&data,CTRL1);
 
 
 close(fd);
 return(1);
}

 

For this example, the defaults are taken for the sample rate (100 Hz) and the sensitivity (245 dps).  These values are determined from the CTRL registers.

Low Output Data Rate (ODR) is set by register 0x39.  The default is as 0 for “low speed ODR disabled.”

Table 21 of the data sheet indicates that CTRL1 sets the default ODR with “low speed ODR disabled” to 100 Hz with a low pass filter cut-off of 12.5 Hz.

The sensitivity is set by CTRL4 as 245 degrees per second for full scale. (Other values are 500 dps and 2000 dps.)  Data is 16-bit, 2’s complement.  So, full scale is 0x7fff. The high bit 0x8000, determines the sign.

CTRL4 also sets the endianness.  Picking the right endianness allows your program to correctly process ‘word’ reads instead of ‘byte’ reads and doing the byte shifting.

This example polls the status bit for the z-axis to determine when data is ready before making a read.


Doing it from the PRU:

In order to have this work from the PRU, the open, close, and ioctl functions must be replaced or replicated by the PRU.

I presume at this point, this only requires setting up of the i2c registers (see Chapter 21 of the TRM).  By default, i2c0 and i2c2 are enabled for the ARM.  It might be best, in using the PRU, to use i2c1 (assuming it’s unoccupied).

For my application, the yaw rate sensor is not the highest priority sensor to read from the PRU, so I’m likely to deprioritize this for the near term so as to complete other work. (7/6/2017)


NOTES:

The ioctl call with I2C_FUNCS allows you to see what functions are implemented on your device.  It is probably wonderful for a generic driver.  For a specific device, you can call it in the testing phase to see if smbus is enabled. If not, the coding will be harder.  The two sensors I picked returned the following function value, 0xefe000d.  This indicates that smbus is implemented.

The header file, <linux/i2c.h>, defines the structure for transport, i2c_smbus_data.  The other header file, <linux/i2c-dev.h>, defines the ioctl structure, i2c_smbus_ioctl_data.  Put the right bits into these structures and call ioctl(<pointer to device from open>,I2C_SMBUS,<pointer to i2c_smbus_ioctl_data structure>), and … presto-magico … you can write to registers and read from registers (including the sensor data).


Add a 4-pin header to the cape.  Pull in ground and power from the rail. Add a level adjuster chip if necessary.

Beaglebone i2c is multi-master with slow data xfer (100k bits/s) and fast (400k bits/s).

The command

i2cset tbd tbd

allows you to set a register.

eLinux’s i2c

quadrotor that uses the above stuff

adafruit flora (1356): TCS34725 with code library [put this in the next blog]

Google summer of code beagleboard version

Q&A about clock module for PRU to enable.

Posted in: Robotics

Comments are closed.