for connected embedded systems
![]() |
![]() |
![]() |
I2C (Inter-Integrated Circuit) Framework
This note includes:
Overview
I2C (Inter-Integrated Circuit) is a simple serial protocol that connects multiple devices in a master-slave relationship. Multiple master devices may share a single bus. The same device may function as both a master and a slave in different transactions. The I2C specification defines these transfer speed ranges:
- ≤ 100 Kbit/s
- ≤ 400 Kbit/s
- ≤ 3.4 Mbit/s
The I2C framework is intended to facilitate consistent implementation of I2C interfaces. The framework consists of the following parts:
- hardware/i2c/*
- The hardware interface.
- lib/i2c
- The resource manager layer.
- <hw/i2c.h>
- A public header file that defines the hardware and application interfaces.
The most common application of the I2C bus is low-bandwidth access to a slave device's registers, such as:
- programming an audio codec
- programming a RTC
- reading a temperature sensor
- reading from an EPROM
Typically, only a few bytes are exchanged on the bus.
You can implement the I2C master as a single-threaded resource manager, or as a dedicated application. The primary advantages of a resource manager interface are:
- It presents a clear, easy-to-understand interface to the application developer.
- It mediates between accesses by multiple applications to one or more slave devices.
- It enforces consistency between different I2C interfaces.
For a dedicated I2C-bus application, a hardware access library is more efficient. The hardware interface, which defines the interface to this library, is useful as a starting point for developers and facilitates maintenance and code portability.
Hardware interface
This is the interface to the code that implements the hardware-specific functionality of an I2C master. It's defined in <hw/i2c.h>.
Function table
The i2c_master_funcs_t structure is a table of pointers to functions that you can provide for your hardware. The higher-level code calls these functions.
typedef struct {
size_t size; /* size of this structure */
int (*version_info)(i2c_libversion_t *version);
void *(*init)(int argc, char *argv[]);
void (*fini)(void *hdl);
i2c_status_t (*send)(void *hdl, void *buf, unsigned int len,
unsigned int stop);
i2c_status_t (*recv)(void *hdl, void *buf, unsigned int len,
unsigned int stop);
int (*abort)(void *hdl, int rcvid);
int (*set_slave_addr)(void *hdl, unsigned int addr, i2c_addrfmt_t fmt);
int (*set_bus_speed)(void *hdl, unsigned int speed, unsigned int *ospeed);
int (*driver_info)(void *hdl, i2c_driver_info_t *info);
int (*ctl)(void *hdl, int cmd, void *msg, int msglen,
int *nbytes, int *info);
} i2c_master_funcs_t;
![]() |
|
The functions are described in the sections that follow:
version_info function
The higher-level code calls the version_info function to get information about the version of the library. The prototype for this function is:
int version_info( i2c_libversion_t *version );
The version argument is a pointer to a i2c_libversion_t structure that this function must fill in. This structure is defined as follows:
typedef struct {
unsigned char major;
unsigned char minor;
unsigned char revision;
} i2c_libversion_t;
This function should set the members of this structure as follows:
version->major = I2CLIB_VERSION_MAJOR; version->minor = I2CLIB_VERSION_MINOR; version->revision = I2CLIB_REVISION;
The function must return:
- 0
- Success.
- -1
- Failure.
init function
The init function initializes the master interface. The prototype for this function is:
void *init( int argc, char *argv[]);
The arguments are those passed on the command line.
The function returns a handle that's passed to all other functions, or NULL if an error occurred.
fini function
The fini function cleans up the driver and frees any memory associated with the given handle. The prototype for this function is:
void fini( void *hdl);
The argument is the handle that the init function returned.
send function
The send function initiates a master send. An optional event is sent when the transaction is complete (and the data buffer can be released). If this function fails, no transaction has been initiated.
The prototype for this function is:
i2c_status_t send(
void *hdl,
void *buf,
unsigned int len,
unsigned int stop );
The arguments are:
- hdl
- The handle returned by the init function.
- buf
- A pointer to the buffer of data to send.
- len
- The length, in bytes, of the data to send.
- stop
- If this is nonzero, the function sets the stop condition when the send completes.
The function returns one of the following:
- I2C_STATUS_DONE
- The transaction completed (with or without an error).
- I2C_STATUS_ERROR
- An unknown error occurred.
- I2C_STATUS_NACK
- Slave no-acknowledgement.
- I2C_STATUS_ARBL
- Lost arbitration.
- I2C_STATUS_BUSY
- The transaction timed out.
- I2C_STATUS_ABORT
- The transaction was aborted.
recv function
The recv function initiates a master receive. An optional event is sent when the transaction is complete (and the data buffer can be used). If this function fails, no transaction has been initiated.
The prototype for this function is:
i2c_status_t recv(
void *hdl,
void *buf,
unsigned int len,
unsigned int stop );
The arguments are:
- hdl
- The handle returned by the init function.
- buf
- A pointer to the buffer in which to put the received data.
- len
- The length, in bytes, of the buffer.
- stop
- If this is nonzero, the function sets the stop condition when the receive completes.
The function returns one of the following:
- I2C_STATUS_DONE
- The transaction completed (with or without an error).
- I2C_STATUS_ERROR
- An unknown error occurred.
- I2C_STATUS_NACK
- Slave no-acknowledgement.
- I2C_STATUS_ARBL
- Lost arbitration.
- I2C_STATUS_BUSY
- The transaction timed out.
- I2C_STATUS_ABORT
- The transaction was aborted.
abort function
The abort function forces the master to free the bus. It returns when the stop condition has been sent. The prototype for this function is:
int abort(
void *hdl,
int rcvid );
The arguments are:
- hdl
- The handle returned by the init function.
- rcvid
- The receive ID of the client.
The function must return:
- 0
- Success.
- -1
- Failure.
set_slave_addr function
The set_slave_addr function specifies the target slave address. The prototype for this function is:
int set_slave_addr(
void *hdl,
unsigned int addr,
i2c_addrfmt_t fmt );
The arguments are:
- hdl
- The handle returned by the init function.
- addr
- The target slave address.
- fmt
- The format of the address; one of:
- I2C_ADDRFMT_7BIT
- I2C_ADDRFMT_10BIT
The function must return:
- 0
- Success.
- -1
- Failure.
set_bus_speed function
The set_bus_speed function specifies the bus speed. If an invalid bus speed is requested, this function should return a failure and leave the bus speed unchanged. The prototype for this function is:
int set_bus_speed(
void *hdl,
unsigned int speed,
unsigned int *ospeed );
The arguments are:
- hdl
- The handle returned by the init function.
- speed
- The bus speed. The units are implementation-defined.
- ospeed
- NULL, or a pointer to a location where the function should store the actual bus speed.
The function must return:
- 0
- Success.
- -1
- Failure.
driver_info function
The driver_info function returns information about the driver. The prototype for this function is:
int driver_info(
void *hdl,
i2c_driver_info_t *info );
The arguments are:
- hdl
- The handle returned by the init function.
- info
- A pointer to a i2c_driver_info_t structure where the
function should store the information:
typedef struct { _Uint32t speed_mode; _Uint32t addr_mode; _Uing32t reserved[2]; } i2c_driver_info_t;
For the speed_mode member, OR together the appropriate values from the following list to indicate the supported speeds:
- I2C_SPEED_STANDARD
- Up to 100 Kbit/s.
- I2C_SPEED_FAST
- Up to 400 Kbit/s.
- I2C_SPEED_HIGH
- Up to 3.4 Mbit/s.
Set the addr_mode to one of the following to indicate the supported address format:
- I2C_ADDRFMT_7BIT
- I2C_ADDRFMT_10BIT
The function must return:
- 0
- Success.
- -1
- Failure.
ctl function
The ctl function handles a driver-specific devctl() command. The prototype for this function is:
int ctl(
void *hdl,
int cmd,
void *msg,
int msglen,
int *nbytes,
int *info );
The arguments are:
- hdl
- The handle returned by the init function.
- cmd
- The device command.
- msg
- A pointer to the message buffer. The function can change the contents of the buffer.
- msglen
- The length of the message buffer, in bytes.
- nbytes
- The number of bytes being returned. This must not be greater than msglen.
- info
- A pointer to a location where the function can store extra status information returned by devctl().
The function must return:
- EOK
- Success.
- Any other errno value
- Failure.
Access function
This function is used by higher-level code (such as the resource manager interface) to access the hardware-specific functions. It must be implemented.
int i2c_master_getfuncs(i2c_master_funcs_t *funcs, int tabsize);
This function must fill in the given table with the hardware-specific functions.
The arguments are:
- funcs
- The function table to fill in. The library initializes this table before calling this function. If you haven't implemented a function in the table, leave its entry unchanged; don't set it to NULL.
- tabsize
- The size of the structure that funcs points to, in bytes.
![]() |
Don't change the size member of the structure. |
To set an entry in the table, use the I2C_ADD_FUNC() macro:
#define I2C_ADD_FUNC(tabletype, table, entry, func, tabsize) …
For example:
I2C_ADD_FUNC( i2c_master_funcs_t, funcs, init, my_init, tabsize);
The function must return:
- 0
- Success.
- -1
- Failure.
Sample calls
A typical sequence of hardware library calls is as follows:
#include <hw/i2c.h>
i2c_master_funcs_t masterf;
i2c_libversion_t version;
i2c_status_t status;
void *hdl;
i2c_master_getfuncs(&masterf, sizeof(masterf));
masterf.version_info(&version);
if ((version.major != I2CLIB_VERSION_MAJOR) ||
(version.minor > I2CLIB_VERSION_MINOR))
{
/* error */
...
}
hdl = masterf.init(...);
masterf.set_bus_speed(hdl, ...);
masterf.set_slave_addr(hdl, ...);
status = masterf.send(hdl, ...);
if (status != I2C_STATUS_DONE) {
/* error */
if (!(status & I2C_STATUS_DONE))
masterf.abort(hdl);
}
status = masterf.recv(hdl, ...);
if (status != I2C_STATUS_DONE) {
/* error */
...
}
masterf.fini(hdl);
Application interfaces
Shared-library interface
When an I2C master device is dedicated for use by a single application, you can compile the hardware interface code as a library and link it to the application.
To increase code portability, the application should call i2c_master_getfuncs() to access the hardware-specific functions. Accessing the hardware library through the function table also makes it easier for an application to load and manage more than one hardware library.
The resource manager interface uses the hardware library in a similar way.
Resource manager interface
The resource manager interface is designed to mediate accesses to a single master. In a system with multiple masters, a separate instance of the resource manager should be run for each master.
The resource manager layer registers a device name (usually /dev/i2c0). Applications access the I2C master by issuing devctl() commands to the device name.
The supported devctl() commands and their formats are defined in <hw/i2c.h>. The commands include:
The following commands are deprecated:
Supporting data types
Many of the devctl() commands use the i2c_addr_t structure, which is defined as:
typedef struct {
_Uint32t addr; /* I2C address */
_Uint32t fmt; /* I2C_ADDRFMT_7BIT or I2C_ADDRFMT_10BIT */
} i2c_addr_t;
DCMD_I2C_DRIVER_INFO
The DCMD_I2C_DRIVER_INFO command returns information about the hardware library.
- Input
- None.
- Output
- An i2c_driver_info_t structure that contains driver information; see the description of the driver_info function, earlier in this technote.
If an error occurs, the command returns:
- EIO
- The driver query failed.
DCMD_I2C_SEND
The DCMD_I2C_SEND command executes a master send transaction. It returns when the transaction is complete.
- Input
- i2c_send_t — the message header:
typedef struct { i2c_addr_t slave; /* slave address */ _Uint32t len; /* length of send data in bytes */ _Uint32t stop; /* send stop when complete? (0=no, 1=yes) */ } i2c_send_t; - _Uint8t[] — the data buffer
- i2c_send_t — the message header:
- Output
- None.
If an error occurs, the command returns:
- EIO
- The master send failed. The causes include: bad slave address, bad bus speed, bus is busy.
- EFAULT
- An error occurred while accessing the data buffer.
- EINVAL
- Bad message format.
- ENOMEM
- Insufficient memory.
- EPERM
- The master is locked by another connection.
DCMD_I2C_RECV
The DCMD_I2C_RECV command executes a master receive transaction. It returns when the transaction is complete.
- Input
- i2c_recv_t — the message header
- _Uint8t[] — the receive buffer
- Output
- i2c_recv_t — the message header (unchanged)
- _Uint8t[] — the receive data in the buffer
The i2c_recv_t structure is defined as:
typedef struct {
i2c_addr_t slave; /* slave address */
_Uint32t len; /* length of receive data in bytes */
_Uint32t stop; /* send stop when complete? (0=no, 1=yes) */
} i2c_recv_t;
If an error occurs, the command returns:
- EIO
- The master send failed. Causes include: bad slave address, bad bus speed, bus is busy.
- EINVAL
- Bad message format.
- ENOMEM
- Insufficient memory.
- EPERM
- The master is locked by another connection.
DCMD_I2C_SENDRECV
The DCMD_I2C_SENDRECV command executes a send followed by a receive. This sequence is typically used to read a slave device's register value. When multiple applications access the same slave device, it is necessary to execute this sequence atomically to prevent register reads from being interrupted. Although this functionality is also provided by DCMD_I2C_LOCK and DCMD_I2C_UNLOCK, the implementation of this functionality is much simpler.
- Input
- i2c_sendrecv_t — the message header
- _Uint8t[] — a buffer, containing the send data, that's large enough to hold the receive data
- Output
- i2c_sendrecv_t — the message header (unchanged)
- _Uint8t[] — the receive data in the buffer
The i2c_sendrecv_t structure is defined as:
typedef struct {
i2c_addr_t slave; /* slave address */
_Uint32t send_len; /* length of send data in bytes */
_Uint32t recv_len; /* length of receive data in bytes */
_Uint32t stop; /* set stop when complete? */
} i2c_sendrecv_t;
If an error occurs, the command returns:
- EIO
- The master send failed. Causes include: bad slave address, bad bus speed, bus is busy.
- EFAULT
- An error occurred while accessing the data buffer.
- EINVAL
- Bad message format.
- ENOMEM
- Insufficient memory.
- EPERM
- The master is locked by another connection.
DCMD_I2C_SET_BUS_SPEED
The DCMD_I2C_SET_BUS_SPEED command sets the bus speed for the current connection. You should set the bus speed before attempting a data-transfer operation.
- Input
- _Uint32t — the bus speed
- Output
- None.
If an error occurs, the command returns:
- EINVAL
- Bad mesage format.
DCMD_I2C_SET_SLAVE_ADDR (deprecated)
The DCMD_I2C_SET_SLAVE_ADDR command sets the slave device address for the current connection. You should set the slave device address before attempting a master send or receive transaction.
- Input
- i2c_addr_t — the slave device address and the address format
- Output
- None.
This command doesn't return any error codes.
DCMD_I2C_MASTER_SEND (deprecated)
The DCMD_I2C_MASTER_SEND command execute a master send transaction, using the slave device address and bus speed specified for the current connection.
- Input
- i2c_masterhdr_t — the message header:
typedef struct { _Uint32t len; /* length of data to send/recv, in bytes (not including this header) */ _Uint32t stop; /* send stop when complete? (0=no, 1=yes) */ } i2c_masterhdr_t; - _Uint8t[] — the data buffer
- i2c_masterhdr_t — the message header:
- Output
- None.
If an error occurs, the command returns:
- EIO
- The master send failed. Causes include: bad slave address, bad bus speed, bus is busy.
- EFAULT
- An error occurred while accessing the data buffer.
- EINVAL
- Bad message format.
- ENOMEM
- Insufficient memory.
- EPERM
- The master is locked by another connection.
DCMD_I2C_MASTER_RECV (deprecated)
The DCMD_I2C_MASTER_RECV command executes a master receive transaction, using the slave device address and bus speed specified for the current connection.
- Input
- i2c_messagehdr_t — the message header
- Output
- _Uint8t[] — the receive data

The message header is overwritten.
If an error occurs, the command returns:
- EIO
- The master receive failed. Causes include: bad slave address, bad bus speed, bus is busy.
- EINVAL
- Bad message format.
- ENOMEM
- Insufficient memory.
- EPERM
- The master is locked by another connection.
Resource manager design
The resource manager layer is implemented as a library that's statically linked with the hardware library. We currently provide a single-threaded manager, libi2c-master.
On startup, the resmgr layer does the following:
i2c_master_getfuncs(&masterf) masterf.init() masterf.set_bus_speed()
The resource manager then makes itself run in the background.
Here's how the resource manager handles these devctl() commands:
- DCMD_I2C_DRIVER_INFO calls masterf.driver_info().
- DCMD_I2C_SET_BUS_SPEED and DCMD_I2C_SET_SLAVE_ADDRESS only update the state of the current connection.
- DCMD_I2C_SEND, DCMD_I2C_RECV,
DCMD_I2C_SENDRECV, DCMD_I2C_MASTER_SEND,
and DCMD_I2C_MASTER_RECV result in the following:
if (bus_speed has changed) masterf.set_bus_speed() masterf.set_slave_address() masterf.send() or masterf.recv()
The resource manager thread remains occupied until the transaction is complete and replies to the client.
You can terminate the resource manager by sending a SIGTERM to it.
![]() |
![]() |
![]() |

![[Previous]](prev.gif)
![[Contents]](contents.gif)
![[Next]](next.gif)