Search code examples
craspberry-pii2c

Read a block of data from a specific register(fifo) using c/c++ and i2c in raspberry Pi


I need to read 4 bytes of data from MAX30100 chip using c/c++ and I2C on raspberry Pi. I tried doing it with python. And it worked. But the problem was the data rate is too slow, I need to update the data at least 250 times/sec, which means the frequency of reading is >= 250Hz. So, i switched the reading from python to c/c++.

There is no problem when just read or write one bytes with wiringPi. However, I need to read 4 bytes from fifo (address of the fifo is 0x04), wiringPi doesn't provide a function to do the block data reading. There are only read byte/word functions.

Then, I tried use SMBus to do the block reading, which can be found here: https://github.com/leon-anavi/rpi-examples/blob/master/BMP180/c/smbus.c

But, as soon as I call the i2c_smbus_read_block_data(), my raspberry Pi freezes completely.

Here is the read block data I added into the wiringPiI2C.c:

void i2c_smbus_read_block_data(int fd, int command, uint8_t *values, int length)
{
    union i2c_smbus_data data;
    int i, err;

    err = i2c_smbus_access(fd, I2C_SMBUS_READ, command,
                   I2C_SMBUS_BLOCK_DATA, &data);
    if (err < 0)
        return;
    printf("test1");
    for (i = 1; i <= length; i++)
        values[i-1] = data.block[i];
}

The wiringPiI2C.c can be found here: https://github.com/WiringPi/WiringPi/blob/master/wiringPi/wiringPiI2C.c

Anyone knows what's going on there? Or has a better solution?


Solution

  • For people who are still interested. Here is a working solution for me, which I only tested briefly. (C++)

    #include <unistd.h>        //Needed for I2C port
    #include <fcntl.h>          //Needed for I2C port
    #include <sys/ioctl.h>      //Needed for I2C port
    #include <linux/i2c-dev.h>  //Needed for I2C port
    #include <linux/i2c.h>      //Needed for I2C port
    
    #include <iostream>
    #include <iomanip>
    #include <string>
    #include <cerrno>
    #include <cstdint>
    #include <cstring>
    
    const std::string i2c_filename = "/dev/i2c-1";
    const int i2c_addr = 0x5b;          //<<<<<The I2C address of the slave
    
    static inline int i2c_rdwr_block(int fd, uint8_t reg, uint8_t read_write, uint8_t length, unsigned char* buffer)
    {
        struct i2c_smbus_ioctl_data ioctl_data;
        union i2c_smbus_data smbus_data;
    
        int rv; 
    
        if(length > I2C_SMBUS_BLOCK_MAX) 
        {
            std::cerr << "Requested Length is greater than the maximum specified" << std::endl;
            return -1;
        }
    
        // First byte is always the size to write and to receive 
        // https://github.com/torvalds/linux/blob/master/drivers/i2c/i2c-core-smbus.c  
        // (See i2c_smbus_xfer_emulated CASE:I2C_SMBUS_I2C_BLOCK_DATA)
        smbus_data.block[0] = length;
    
        if ( read_write != I2C_SMBUS_READ )
        {
            for(int i = 0; i < length; i++)
            {
                smbus_data.block[i + 1] = buffer[i];
            }
        }
    
    
        ioctl_data.read_write = read_write;
        ioctl_data.command = reg;
        ioctl_data.size = I2C_SMBUS_I2C_BLOCK_DATA;
        ioctl_data.data = &smbus_data;
    
        rv = ioctl (fd, I2C_SMBUS, &ioctl_data);
        if (rv < 0)
        {
            std::cerr << "Accessing I2C Read/Write failed! Error is: " << strerror(errno) << std::endl;
            return rv;
        }
    
        if (read_write == I2C_SMBUS_READ)
        {
            for(int i = 0; i < length; i++)
            {
                // Skip the first byte, which is the length of the rest of the block.
                buffer[i] = smbus_data.block[i+1];
            }
        }
    
        return rv;
    }
    
    static int setup_i2c(std::string filename)
    {
        //----- OPEN THE I2C BUS -----
    
        int fd;
        int rv;
    
        if ((fd = open(filename.c_str(), O_RDWR)) < 0)
        {
            //ERROR HANDLING: you can check errno to see what went wrong
            std::cout << "Failed to open the i2c bus. Error code: " << fd << std::endl;
            return fd;
        }
    
        if ((rv = ioctl(fd, I2C_SLAVE, i2c_addr)) < 0)
        {
            std::cout << "Failed to acquire bus access and/or talk to slave. Error code: " << rv << std::endl;
            //ERROR HANDLING; you can check errno to see what went wrong
            return rv;
        }
    
        return fd;
    }
    
    int main()
    {
        int fd_i2c = setup_i2c(i2c_filename);
        int i2c_data_length = 3;
        int rv;
        unsigned char buffer[i2c_data_length + 1] = {0};
    
        if (fd_i2c < 0)
        {
            std::cerr << "Set UP I2C Bus Error. Exit now!" << std::endl;
            return -1;
        }
    
        //std::cout << "File Descriptor: " << fd_i2c << std::endl;
    
        //rv = read_i2c(fd_i2c, buffer, i2c_data_length);
        rv = i2c_rdwr_block(fd_i2c, 0x22, I2C_SMBUS_READ, i2c_data_length, buffer);
    
        if (rv < 0)
        {
            std::cerr << "Reading I2C Bus Error..." << std::endl;
            return -1;
        }
    
        std::cout << "Buffer Value: " ;
    
        for (int i = 0; i < i2c_data_length; i++)
        {
            std::cout << "0x" << std::setfill('0') << std::setw(2) << std::hex << (int) buffer[i] << " " ;
        }
    
        std::cout << std::endl;
    
        unsigned char values[i2c_data_length] = {0};
        values[0] = 0x01;
        values[1] = 0x02;
        values[2] = 0x03;
    
        //rv = write_i2c(fd_i2c, values, i2c_data_length);
        rv = i2c_rdwr_block(fd_i2c, 0x22, I2C_SMBUS_WRITE, i2c_data_length, values);
    
        if (rv < 0)
        {
            std::cerr << "Writing I2C Bus Error..." << std::endl;
            return -1;
        }
    
        return 0;
    }
    

    The key for this block of code is the option I2C_SMBUS_I2C_BLOCK_DATA (which is defined in "linux/i2c-dev.h", also see "linux/i2c.h"). This will translate your SMBus block data into I2C Block data. Specifically, SMBus block data is--"command, block_size, data", while I2C block data is --"command, data" and using the two wire timing to determine the STOP signal.

    Please refer to Linux Kernel source code linux/drivers/i2c/i2c-core-smbus.c And function,

    static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
                       unsigned short flags,
                       char read_write, u8 command, int size,
                     union i2c_smbus_data *data)
    

    (i2c_smbus_xfer is the first function called, and is fallen back to i2c_smbus_xfer_emulated is the adapter does not have native support for the SMBus. Therefore these two functions should implement the same thing.)

    Look at

    case I2C_SMBUS_I2C_BLOCK_DATA:
    

    carefully which shows you how the translation from SMBus to I2C Bus is done.

    Also compare

    case I2C_SMBUS_BLOCK_DATA:
    

    And see how it does not do the translation. (Send SMBus data directly)

    More can be referred to the linux kernel documentation on i2c device interface, as well as the i2c driver source code