clinuxsensorsi2craspberry-pi3

Access a device's register using I²C


I recently bought a GY-521 board, and I'm trying to use it with a Raspberry Pi 3 (RPi3) through the following connections:

RPi3     |     GY-521
---------------------
3.3V <-------> VCC
GND  <-------> GND
SCL  <-------> SCL
SDA  <-------> SDA

Using i2cdetect -y 1, I obtain the following

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

So the address of the device is 0x68. Reading the datasheet, I found that, for example, the acceleration on the X axis is stored in registers 3B (higher bits) and 3C (lower bits). How do I access those registers?

My idea is that if I open /dev/i2c-1 as a file descriptor, I can use the normal read and write functions. Then instead of getting data all the time, I can use poll in case of new available data.

I tried to use the read function as suggested in the documentation, but that's not working (I only get zeros) and when I use poll, it seems like there isn't anything on the other side and the timeout (100 ms) expires. I think I should say to the chip "give me the value of the 3B register", but I can't figure how to do it.

Code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <poll.h>
#include <errno.h>

const char *filename = "/dev/i2c-1";
int DEBUG = 0;
int ADDRESS = 0x68;
struct pollfd pfd;

void debug(const char* format, ...)
{
    if (DEBUG)
    {
        va_list argptr;
        va_start(argptr, format);
        fprintf(stdout, "### ");
        vfprintf(stdout, format, argptr);
        va_end(argptr);
    }
}

void error_handler(const char *msg)
{
    perror(msg);
    exit(EXIT_FAILURE);
}

void set_debug(const char *deb_lev)
{
    unsigned long num;
    char *p;
    errno = 0;

    num = strtoul(deb_lev, &p, 10);

    if (errno != 0 || *p != '\0')
        error_handler("set_debug | strtoul");

    DEBUG = (num > 0);
}

int open_file(const char *filename)
{
    int fd;

    if ((fd = open(filename, O_RDWR)) == -1)
        error_handler("open_file | open");

    debug("\"%s\" opened at %d\n", filename, fd);
    return fd;
}

int8_t read_value(int fd)
{
    debug("Reading from %d\n", fd);

    int8_t num;
    char *p = (char *)&num;
    ssize_t size = sizeof(int8_t);
    ssize_t r = 0;

    while (size > 0)
    {
        if ((r = read(fd, p, size)) == -1)
            error_handler("read_value | read");

        size -= r;
        p += r;
    }

    return num;
}

void command(uint16_t reg, int fd)
{
    debug("Writing to %d\n", fd);

    unsigned char reg_buf[2];
    ssize_t w = 0;
    ssize_t size = sizeof(unsigned char)*2;

    reg_buf[0] = (reg >> 0) & 0xFF;
    reg_buf[1] = (reg >> 8) & 0xFF;

    if ((w = write(fd, reg_buf, size)) == -1)
        error_handler("command | write");
}

void read_data_from_imu(struct pollfd *pfd)
{
    int8_t val;
    int p;

    for (;;)
    {
        command(0x3b, pfd->fd);

        switch (p = poll(pfd, 1, 100))
        {
            case -1:
                error_handler("read_data_from_imu | poll");
            case 0:
                fprintf(stderr, "Timeout expired\n");
                break;
            default:
                val = read_value(pfd->fd);
                printf("Read: %u\n", val);
                break;
        }
    }
}

int main(int argc, const char **argv)
{
    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s debug_flag\n", argv[0]);
        return EXIT_FAILURE;
    }

    set_debug(argv[1]);

    pfd.fd = open_file(filename);

    debug("Setting slave address\n");
    if (ioctl(pfd.fd, I2C_SLAVE, ADDRESS) == -1)
        error_handler("main | ioctl");

    read_data_from_imu(&pfd);

    return EXIT_SUCCESS;
}

Thanks to Kennyhyun adding a write solved the problem.

So, if you want to read the accelerometer measurement on the x axis you have to do something like this:

// ...

#define ACCEL_XOUT_H 0x3b
#define ACCEL_XOUT_L 0x3c

// ...

write_register(ACCEL_XOUT_H, pfd->fd);
x_data = read_value(pfd->fd);
write_register(ACCEL_XOUT_L, pfd->fd);
x_data = (x_data << 8) + read_value(pfd->fd);

// ...

Furthermore, you can simplify the code reading directly for two bytes after the write. You'll get something like this (error handling omitted):

write_register(ACCEL_XOUT_H, pfd->fd);
read(pfd->fd, buff, 2); // In this way, you'll read both the ACCEL_XOUT_H register and the ACCEL_XOUT_L (the next one).

Solution

  • You might need to understand the basics of I²C.

    From Understanding the I²C bus:

    3.2 Reading from a slave on the I²C bus

    Enter image description here

    To read an I²C register, you need to write the slave address, register address and again slave address and then read the data from the bus.

    But it is done by the driver.

    And the slave address is set in fd by ioctl. But you still need to write the register address.

    From your link...

    /* Using SMBus commands */
      res = i2c_smbus_read_word_data(file, reg);
      if (res < 0) {
        /* ERROR HANDLING: i2c transaction failed */
      } else {
        /* 'res' contains the read word */
      }
    

    i2c_smbus_read_word_data has reg which contains the register address. But read() is not. You need to write(reg) and then can read().

    And you need to only read one byte, unless using burst mode or something. It reads one byte, since size is 1. But with a meaningless while and p++.

    You are writing 3b before reading by command(0x3b, pfd->fd);. but it will be like writing

    68 > 3B , 68 > 00 , 68 <
    

    And trying to read. (Where > for the read bit, 0, < for 1). Maybe you need just write(pfd->fd, 0x3b, 1) instead of command.