3
votes

I am trying to read one value from a memory location on the I2C bus after writing to it. I am getting strange output when I run it in the terminal.

Here is my program

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <inttypes.h>

    #include <errno.h>
    #include <string.h>

    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>

    #include <linux/i2c.h>
    #include <linux/i2c-dev.h>
    #include <sys/ioctl.h>

    #define I2C_ADAPTER "/dev/i2c-0"
    #define I2C_DEVICE  0x00


    int main (int argc, char *argv[])
    {
        int file;
        int addr = 0X00; /* XGPIOPS_DATA_LOW_OFFSET */

        if((file = open(I2C_ADAPTER, O_RDWR)) < 0) {
            printf("Failed to open the bus");
            return -1;
        }

        if(ioctl(file, I2C_SLAVE, addr) < 0) {
            printf("Unable to open device as slave %s", strerror(errno));
            return -1;
        }

        char buf[10];

        buf[0] = addr;
        buf[1] = 0x10;
        if(write(file, buf, 2) != 2) {
            printf("Failed to write to bus %s.\n\n", strerror(errno));
        }
        else {
            printf("Successful write\n");
            printf(buf);
            printf("\n\n");
        }

        if(read(file, buf, 2) != 2) {
            printf("Failed to read from the i2c bus. %s\n\n", strerror(errno));
        }
        else {
            printf("Successful read\n");
            printf(buf);
            printf("\n\n");
        }

        return 0;
    }

The output from the program looks like this

Successful write

Successful read ��

On my terminal those blocks look more like question marks inside of diamonds. I am not sure what that corresponds to in ASCII.

Why am I not reading back that 0x10 which is the second byte after the address byte that I originally write?

Based on the first set of answers, here is the updated code:

#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>

#include <errno.h>
#include <string.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>

#define I2C_ADAPTER "/dev/i2c-0"
#define I2C_DEVICE  0x00


int main (int argc, char *argv[])
{
    int file;

    long addr, reg_addr;

    char *end;

    if(argc == 3) {
        addr = strtol(argv[1], &end, 16);
        printf("Value of addr is: %ld\n", addr);

        reg_addr = strtol(argv[2], &end, 16);
        printf("Value of reg_addr is: %ld\n", reg_addr);
    }
    else {
        printf("arg failed\n\n.");
        addr = 0x00;
    }


    if((file = open(I2C_ADAPTER, O_RDWR)) < 0) {
        printf("Failed to open the bus\n");
        return -1;
    }

    if(ioctl(file, I2C_SLAVE, addr) < 0) {
        printf("Unable to open device as slave \n%s\n", strerror(errno));
        return -1;
    }

    char buf[10];

    buf[0] = addr;
    buf[1] = reg_addr;
    buf[2] = 0x10;
    if(write(file, buf, 3) != 3) {
        printf("Failed to write to bus %s.\n\n", strerror(errno));
    }
    else {
        printf("Successful write\n");
        printf(buf);
        printf("\n\n");
    }

    if(read(file, buf, 3) != 3) {
        printf("Failed to read from the i2c bus.\n %s\n\n", strerror(errno));
    }
    else {
        printf("Successful read\n");
        printf("Buf = [%02X,%02X,%02X]\n", buf[0], buf[1], buf[2]);
        printf("\n\n");
    }

    return 0;
}

At this point, whenever I use 0x00 as the addr, I get FF, FF, FF as the output, no matter what argv[2] is. Here is the applicable part of the device tree file. Note that this is being emulated, so I cannot probe the physical device.

&i2c0 {
    status = "okay";
    clock-frequency = <400000>;
    pinctrl-names = "default";

    i2cswitch@74 {
        compatible = "nxp,pca9548";
        #address-cells = <1>;
        #size-cells = <0>;
        reg = <0x74>;

        i2c@0 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <0>;
            si570: clock-generator@5d {
                #clock-cells = <0>;
                compatible = "silabs,si570";
                temperature-stability = <50>;
                reg = <0x5d>;
                factory-fout = <156250000>;
                clock-frequency = <148500000>;
            };
        };

        i2c@2 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <2>;
            eeprom@54 {
                compatible = "at,24c08";
                reg = <0x54>;
            };
        };

        i2c@3 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <3>;
            gpio@21 {
                compatible = "ti,tca6416";
                reg = <0x21>;
                gpio-controller;
                #gpio-cells = <2>;
            };
        };

        i2c@4 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <4>;
            rtc@51 {
                compatible = "nxp,pcf8563";
                reg = <0x51>;
            };
        };

        i2c@7 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <7>;
            hwmon@52 {
                compatible = "ti,ucd9248";
                reg = <52>;
            };
            hwmon@53 {
                compatible = "ti,ucd9248";
                reg = <53>;
            };
            hwmon@54 {
                compatible = "ti,ucd9248";
                reg = <54>;
            };
        };
    };
};

Here are a couple of example tests

Try to test the SiLabs clock generator

root@plnx_arm:~# /usr/bin/i2c-test-mem-location 0x54 0x00

Value of addr is: 84

Value of reg_addr is: 0

Unable to open device as slave

Device or resource busy

Try to test the eeprom device

root@plnx_arm:~# /usr/bin/i2c-test-mem-location 0x5d 0x00

Value of addr is: 93

Value of reg_addr is: 0

Unable to open device as slave

Device or resource busy

This is my program on the third try. After taking to mind the notes made in the answers, I have this written

#include <stdio.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>

#include <errno.h>
#include <string.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>

#define I2C_ADAPTER "/dev/i2c-0"
#define DEVICE_ADDRESS 0x54


int main (int argc, char *argv[])
{
    int file;

    uint8_t reg, value;

    char *end;

    printf("The device address on the bus: %d", DEVICE_ADDRESS);

    if(argc == 3) {
        reg = strtol(argv[1], &end, 16);

        printf("Value of register address: %d\n", reg);

        value = strtol(argv[2], &end, 16);
        printf("value to write is: %d\n", value);
    }
    else {
        printf("arg failed\n\n.");
    }


    if((file = open(I2C_ADAPTER, O_RDWR)) < 0) {
        printf("Failed to open the bus\n");
        return -1;
    }

    if(ioctl(file, I2C_SLAVE, DEVICE_ADDRESS) < 0) {
        printf("Unable to open device as slave \n%s\n", strerror(errno));
        return -1;
    }

    char buf[10];

    buf[0] = reg;
    buf[1] = value;

    if(write(file, buf, 2) != 2) {
        printf("Failed to write to bus %s.\n\n", strerror(errno));
    }
    else {
        printf("Successful write\n");
        printf(buf);
        printf("\n\n");
    }

    if(read(file, buf, 2) != 2) {
        printf("Failed to read from the i2c bus.\n %s\n\n", strerror(errno));
    }
    else {
        printf("Successful read\n");
        printf("Buf = [%02X,%02X,%02X]\n", buf[0], buf[1], buf[2]);
        printf("\n\n");
    }

    return 0;
}

Unfortunately, even still, I am getting the same error.

root@plnx_arm:~# /usr/bin/i2c-test-mem-location 0x00 0x10

The device address on the bus: 84Value of register address: 0

value to write is: 16

Unable to open device as slave

Device or resource busy

root@plnx_arm:~# /usr/bin/i2c-test-mem-location 0x30 0x10

The device address on the bus: 84Value of register address: 48

value to write is: 16

Unable to open device as slave

Device or resource busy

4
are you sure the device is write/readable, not all are...old_timer
that could be an issue. I am running on an emulator. when I try the operation, specifying an address of a corresponding device in the device tree, it is unable to open and prints error that "Device is busy". However at address 0, it says the open is successfulJohn Frye
regarding: int main (int argc, char *argv[]) when the parameters to main() are not going to be used, you can avoid a couple of warnings from the compiler by using the signature: int main( void )user3629249
regarding: printf("Failed to open the bus"); 1) error messages should be output to stderr, not stdout. 2) when the error indication is from a system function, then should also output the text that indicates why the function failed. Suggest: perror( "open of I2C failed" );user3629249
This statement: int addr = 0X00; creates problems later in the posted code. Suggest: char addr = 0x00;user3629249

4 Answers

5
votes

EDIT 2: I think you might not be setting your I2C device address correctly. What you have as your I2C_ADAPTER ("/dev/i2c-0") indicates which I2C bus the device is on. You aren't even using your I2C_DEVICE macro, but that's what you should be passing to your ioctl call (e.g. ioctl(file, I2C_SLAVE, I2C_DEVICE);) and it should be the I2C address of the device you want to access (e.g. 0x5D for the Clock Generator) instead of 0x00.

I also think your reads/writes are incorrect. Once you've specified the bus and device via open() and ioctl() you don't need to worry about those anymore. You only need to worry about the register you want to access (if your I2C device uses registers) and the actual data.

To write to your I2C device, assuming it uses a one-byte register, write a buffer of two bytes: The first is the register, the second is the value you want to write:

bool i2cdev_byte_write(int file, uint8_t reg, uint8_t val)
{
    uint8_t bytes[2];

    bytes[0] = reg;
    bytes[1] = val;

    /* Write the register followed by the value */
    if (write(file, bytes, 2) != 2)
        return false;

    return true;
}

To read from your I2C device, assuming it uses a one-byte register, write a buffer of one byte (the register address) then read a buffer of one or more bytes (the value at that register and subsequent registers):

bool i2cdev_bytes_read(int file, uint8_t reg, unsigned int count, uint8_t *out_buf)
{
    if (!out_buf)
        return false;

    /* Write the register */
    if (write(file, &reg, 1) != 1)
    {
        printf("Failed to write register value\n");
        return false;
    }

    /* Read the specified number of bytes */
    if (read(file, out_buf, count) != count)
    {
        printf("Failed to read from the i2c bus\n");
        return false;
    }

    return true;
}

Again, note that all the above comments depend on it being an I2C device that uses a single-byte register address and that it supports auto-incrementing the register address when reading multiple bytes at a time. You'll need to check the datasheet for your I2C device to determine exactly how it needs to be accessed.

EDIT: This is a printf() newbie failure. You can't just try to printf an array of bytes. That's not how printf() works.

Try this:

printf("Buf = [%02X,%02X]\n", buf[0], buf[1]);

Also, as I wrote in my original response, you likely need to write the register address back out again prior to reading the register contents.

2
votes

The i2c protocol requires that you specify a device address (i.e. 0x00) and a register address. You can then write the value (in your case, 0x10) to that register address. Try this instead:

char buf[10];

buf[0] = addr;
buf[1] = [REGISTER ADDRESS];
buf[2] = 0x10;
if(write(file, buf, 3) != 3) {
    printf("Failed to write to bus %s.\n\n", strerror(errno));
}
else {
    printf("Successful write\n");
    printf("Addr: %02x Subaddr: %02x Value: %02x\n\n", buf[0], buf[1], buf[2]);
}

Once this write has been completed, you should be able to read with:

if(read(file, buf, 1) != 1) {
    printf("Failed to read from the i2c bus. %s\n\n", strerror(errno));
}
else {
    printf("Successful read\n");
     printf("Value: %02x\n\n", buf[0]);
}
1
votes

Based on the device tree, the address that needs to be fed to IOCtl is the i2cswitch mux address. This address is 0x74, which can be seen in the device tree. Opening the i2c-0 device file corresponds to the i2c0 entry in the device tree which is parent to the mux. When writing to EEPROM, the first byte in the buffer should be the device address, as mentioned by @AndrewCottrell. This address is 0x54. The second byte should be the data you want to write

#define I2C_ADAPTER                 "/dev/i2c-0"
#define I2C_SWITHC_MUX_ADDRESS      0x74
#define DEVICE_ADDRESS              0x54

...

file = open(I2C_ADAPTER, O_RDWR); /* Check for error */
ioctl(file, I2C_SLAVE_FORCE, I2C_SWITHC_MUX_ADDRESS); /* Check for error */

uint8_t reg, value;

reg = DEVICE_ADDRESS;

buf[0] = reg;
buf[1] = value;

write(file, buf, 2); /* Check for error */
read(file, buf, 1); /* Check for error */
/* buf[0] should be value*/
0
votes

I also have problems reading data from the memory of an NTAG 5 boost component and I found this thread while looking for the reason for this problem. Indeed, for each I2C reading I do on the component, I receive the "FF" value. I didn't find the answer in this thread but maybe you answer my question in this thread.