Skip to content

Instantly share code, notes, and snippets.

@JamesDunne
Last active May 2, 2024 17:20
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save JamesDunne/9b7fbedb74c22ccc833059623f47beb7 to your computer and use it in GitHub Desktop.
Save JamesDunne/9b7fbedb74c22ccc833059623f47beb7 to your computer and use it in GitHub Desktop.
C library for reading/writing I2C slave device registers from Raspberry Pi 1 Model B
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
// Terrible portability hack between arm-linux-gnueabihf-gcc on Mac OS X and native gcc on raspbian.
#ifndef I2C_M_RD
#include <linux/i2c.h>
#endif
typedef unsigned char u8;
// Global file descriptor used to talk to the I2C bus:
int i2c_fd = -1;
// Default RPi B device name for the I2C bus exposed on GPIO2,3 pins (GPIO2=SDA, GPIO3=SCL):
const char *i2c_fname = "/dev/i2c-1";
// Returns a new file descriptor for communicating with the I2C bus:
int i2c_init(void) {
if ((i2c_fd = open(i2c_fname, O_RDWR)) < 0) {
char err[200];
sprintf(err, "open('%s') in i2c_init", i2c_fname);
perror(err);
return -1;
}
// NOTE we do not call ioctl with I2C_SLAVE here because we always use the I2C_RDWR ioctl operation to do
// writes, reads, and combined write-reads. I2C_SLAVE would be used to set the I2C slave address to communicate
// with. With I2C_RDWR operation, you specify the slave address every time. There is no need to use normal write()
// or read() syscalls with an I2C device which does not support SMBUS protocol. I2C_RDWR is much better especially
// for reading device registers which requires a write first before reading the response.
return i2c_fd;
}
void i2c_close(void) {
close(i2c_fd);
}
// Write to an I2C slave device's register:
int i2c_write(u8 slave_addr, u8 reg, u8 data) {
int retval;
u8 outbuf[2];
struct i2c_msg msgs[1];
struct i2c_rdwr_ioctl_data msgset[1];
outbuf[0] = reg;
outbuf[1] = data;
msgs[0].addr = slave_addr;
msgs[0].flags = 0;
msgs[0].len = 2;
msgs[0].buf = outbuf;
msgset[0].msgs = msgs;
msgset[0].nmsgs = 1;
if (ioctl(i2c_fd, I2C_RDWR, &msgset) < 0) {
perror("ioctl(I2C_RDWR) in i2c_write");
return -1;
}
return 0;
}
// Read the given I2C slave device's register and return the read value in `*result`:
int i2c_read(u8 slave_addr, u8 reg, u8 *result) {
int retval;
u8 outbuf[1], inbuf[1];
struct i2c_msg msgs[2];
struct i2c_rdwr_ioctl_data msgset[1];
msgs[0].addr = slave_addr;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = outbuf;
msgs[1].addr = slave_addr;
msgs[1].flags = I2C_M_RD | I2C_M_NOSTART;
msgs[1].len = 1;
msgs[1].buf = inbuf;
msgset[0].msgs = msgs;
msgset[0].nmsgs = 2;
outbuf[0] = reg;
inbuf[0] = 0;
*result = 0;
if (ioctl(i2c_fd, I2C_RDWR, &msgset) < 0) {
perror("ioctl(I2C_RDWR) in i2c_read");
return -1;
}
*result = inbuf[0];
return 0;
}
@jhaberly
Copy link

Hi James,

This code works on our platform and was very helpful! Thanks!
Can this code be copied and used freely?
Any license requirements for any reason?

Cheers!
Jim

@JamesDunne
Copy link
Author

Glad you found it useful!. No license restrictions of any kind.

@rajmehta28599
Copy link

Hello,

Aim: To change the slave device i2c address.

issue: I have 4 devices that have the same i2c Address (like 0x29). so I want to assign a new i2c address. (setAddress). to differentiate them.

Thank you.

@rawatsaurabh
Copy link

rawatsaurabh commented Oct 21, 2021

The slave address(slave_addr) here is 7 bit address or 8 bit address?

@rawatsaurabh
Copy link

why msgs[0].len = 2; in i2c_write??

@rajmehta28599
Copy link

The slave address(slave_addr) here is 7 bit address or 8 bit address?

8bit(0 to 7) address 2^8=256(0 to 255)

@rajmehta28599
Copy link

why msgs[0].len = 2; in i2c_write??

Here length is use for how many bytes/char read or write. msgs[0].len = 2; means we write 2byte

@rawatsaurabh
Copy link

Thanks it worked as expected ,
few more questions :
msgs[0].addr = slave_addr;
msgs[0].flags = 0; > what is 0 value? i mean which flag?

@JamesDunne
Copy link
Author

https://www.kernel.org/doc/Documentation/i2c/dev-interface

Flags are documented there. For example:

The function will write or read data to or from that buffers depending on whether the I2C_M_RD flag is set in a particular message or not.

@wukaihua119
Copy link

wukaihua119 commented Apr 27, 2022

Thanks for this example.
A question:
Is the I2C_M_NOSTART in msgs[1].flags = I2C_M_RD | I2C_M_NOSTART; needed ?
I don't understand the explanation that I read from the document.

@JamesDunne
Copy link
Author

JamesDunne commented Apr 27, 2022

I guess I2C_M_NOSTART usage depends on your use case.

https://www.kernel.org/doc/Documentation/i2c/i2c-protocol

I2C_M_NOSTART:
    In a combined transaction, no 'S Addr Wr/Rd [A]' is generated at some
    point. For example, setting I2C_M_NOSTART on the second partial message
    generates something like:
      S Addr Rd [A] [Data] NA Data [A] P
    If you set the I2C_M_NOSTART variable for the first partial message,
    we do not generate Addr, but we do generate the startbit S. This will
    probably confuse all other clients on your bus, so don't try this.

    This is often used to gather transmits from multiple data buffers in
    system memory into something that appears as a single transfer to the
    I2C device but may also be used between direction changes by some
    rare devices.

It worked for the particular I2C device I was talking to and there were no other I2C devices on the bus. It's probably better to remove it in general.

@grv12345
Copy link

Hi..
I am new to this ioctl calling..
in the below function declaration
int i2c_read(u8 slave_addr, u8 reg, u8 *result)
what is reg here..If it is register then how do we find out this register for some i2c?

@JamesDunne
Copy link
Author

Yes, reg is the register number to write to or read from. Which register number you want is documented by your I2C device you're talking to and what you're trying to achieve. It's device specific.

@andreaz70
Copy link

I've put

memset(msgs, 0, sizeof(msgs));
memset(msgset, 0, sizeof(msgset));

after declaration, to prevent valgrind from running wild for each call. Hope, it is not that wrong.

@Milana-B
Copy link

Milana-B commented Jan 2, 2023

Hi,

It is possible to write and read in one operation ioctl(i2c_fd, I2C_RDWR, &msgset)?
I mean, write value to reg and read it to verify it was written correctly.

@aayushic01
Copy link

aayushic01 commented Apr 12, 2023

How to get the reg address for the i2c slave device?

@janual57
Copy link

I've just singed in to github to thank you Sr. this helped me a lot. I was using the example below but it did not work.
if (read(file, buf, 2) != 2) {
/* ERROR HANDLING: i2c transaction failed
std::cout << "Failed to Read Address " << std::endl;
}
It was just until i read this post that I realized that I was doing something something wrong. Now the communication with the peripheral from my beaglebone black seems to be working.

@dogukanarat
Copy link

This code works very well. Thank you for sharing with us

@PeterBan11
Copy link

I get erroes:
--Multiple definition of 'i2c_fd'
--Multiple definition of 'i2c_fname'
How can i fix it?..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment