I2C Device Drivers
include/nuttx/i2c/i2c_master.hAll structures and APIs needed to work with I2C drivers are provided in this header file.struct i2c_ops_s. Each I2C device driver must implement an instance ofstruct i2c_ops_s. That structure defines a call table with the following methods:Binding I2C Drivers. I2C drivers are not normally directly accessed by user code, but are usually bound to another, higher level device driver. In general, the binding sequence is:
Get an instance of
struct i2c_master_sfrom the hardware-specific I2C device driver, andProvide that instance to the initialization method of the higher level device driver.
Examples:
arch/z80/src/ez80/ez80_i2c.c,arch/z80/src/z8/z8_i2c.c, etc.
I2C Bit-Bang Driver
The I2C bit-bang driver provides a software implementation of the I2C protocol using GPIO pins. This is useful when hardware I2C peripherals are not available or when additional I2C buses are needed.
Overview
include/nuttx/i2c/i2c_bitbang.hGeneric upper-half I2C bit-bang driver interface.drivers/i2c/i2c_bitbang.cGeneric upper-half implementation that handles the I2C protocol timing.Platform-specific lower-half drivers control the GPIO pins (SDA and SCL).
IO Expander-Based I2C Bit-Bang
A generic lower-half implementation is provided for systems using IO expanders to control GPIO pins. This eliminates the need for platform-specific code when implementing I2C bit-bang via IO expander pins.
Configuration
CONFIG_I2C_BITBANG- Enable I2C bit-bang driver frameworkCONFIG_I2C_BITBANG_IOEXPANDER- Enable IO expander-based lower-half (depends onCONFIG_IOEXPANDER)
Header Files
include/nuttx/i2c/i2c_bitbang_ioexpander.hIO expander-based lower-half driver interface.
API
-
FAR struct i2c_master_s *i2c_bitbang_ioexpander_initialize(FAR struct ioexpander_dev_s *ioe, int scl_pin, int sda_pin, int busnum);
Initialize an I2C bit-bang driver using IO expander pins.
- Parameters:
ioe – Pointer to the IO expander device
scl_pin – IO expander pin number for SCL (clock line)
sda_pin – IO expander pin number for SDA (data line)
busnum – I2C bus number to register (use negative value to skip registration)
- Returns:
Pointer to
struct i2c_master_son success, NULL on failure
The pins will be configured as open-drain outputs, which is required for proper I2C operation. If busnum >= 0, the I2C bus is automatically registered and accessible via standard I2C APIs.
Usage Example
#include <nuttx/ioexpander/ioexpander.h>
#include <nuttx/i2c/i2c_bitbang_ioexpander.h>
#include <nuttx/i2c/i2c_master.h>
/* Assume we have an IO expander device */
FAR struct ioexpander_dev_s *ioe = /* ... get IO expander ... */;
FAR struct i2c_master_s *i2c;
/* Initialize I2C bit-bang using IO expander pins 10 (SCL) and 11 (SDA) */
/* Register as I2C bus 0 */
i2c = i2c_bitbang_ioexpander_initialize(ioe, 10, 11, 0);
if (i2c == NULL)
{
/* Initialization failed */
return -1;
}
/* Now use the I2C master device normally */
/* For example, with I2C character driver or directly */
/* If registered (busnum >= 0), can also access via /dev/i2c0 */
Use Cases
GPIO Expansion: When using I2C or SPI IO expanders for GPIO expansion, and need to implement additional I2C buses using those expanded pins.
Multi-Master Scenarios: Software bit-bang can be useful in multi-master configurations where hardware I2C has limitations.
Pin Flexibility: Implement I2C on any GPIO pins, not limited to hardware I2C peripheral pins.
Testing and Debugging: Use IO expander pins for I2C communication during prototyping and debugging.
Hardware I2C Unavailable: When hardware I2C peripherals are exhausted or not available on specific pins.
Features
Uses standard IO expander API (
IOEXP_WRITEPIN,IOEXP_READPIN)Automatic open-drain configuration
Supports clock stretching (via pin reading)
Platform-independent implementation
Works with any IO expander that implements the standard interface
Automatic I2C bus registration
Limitations
Software timing (slower than hardware I2C)
Timing accuracy depends on system load and IO expander response time
Limited to standard I2C speeds (fast-mode and high-speed may not be reliable)
Implementation Details
The IO expander-based implementation provides these callbacks:
initialize: Configures SCL and SDA pins as open-drain outputsset_scl/set_sda: Controls pin output valuesget_scl/get_sda: Reads current pin states (for clock stretching detection)
The driver automatically manages the IO expander pin states and handles the bit-bang protocol timing according to I2C specifications.
I2C Slave Device Drivers
include/nuttx/i2c/i2c_slave.hDeclaration of all macros and ops andint i2c_slave_register, used to bind the lowerhalf driver to the upper one and register an I2C slave device.Binding I2C Slave Drivers. Use
int i2c_slave_registerin your BSP to register your slave device. Before that, you need to get the instance ofstruct i2c_slave_sfrom the hardware-specific I2C Slave driver.Using I2C Slave in your Application. I2C slave drivers are normally directly accessed by user code, We can read and write to device nodes using posix interfaces. The device is registered as
/dev/i2cslv%d, where%dis a number provided in the BSP initialization phase.BSP initialization example (STM32 I2C Slave):
int stm32_i2cs_setup(void)
{
i2cs = stm32_i2cbus_slaveinitialize(1);
if (i2cs != NULL)
{
return -ENOENT;
}
return i2c_slave_register(i2cs, 1, 0x01, 7);
}
I2C Slave Driver API
-
int i2c_slave_register(FAR struct i2c_slave_s *dev, int bus, int addr, int nbit);
Bind the lowerhalf
devdriver to the upperhalf driver and register a device. The name of the device is/dev/i2cslv%d, where%dis bus. The address the I2C slave is specified inaddr.nbitis either 7 or 10 and it specifies the address format.
struct i2c_slaveops_s. Each I2C slave lowerhalf driver must implement an instance ofstruct i2c_slaveops_s. That structure defines a call table with the following methods:setownaddress: the address the slave responds to,write: sets the tx buffer pointer,read: sets the rx buffer pointer,registercallback: registers the callback function which should be called from a service routine. Signals the received or fully transferred I2C packet.setup: initializes the peripheral,shutdown: shutdowns the peripheral.