A collection of I2C drivers for various integrated circuits.
The idea is to abstract away the hardware details (the data link layer) and create drivers that implement the API required by each device.
Nothing here is particularly complex, but it is aggregated and standardized for ease of use over multiple and diverse projects.
Similar libraries for other protocols:
The hardware is abstracted through the i2c_driver_t
structure and its 5 fields:
device_address
: I2C address for the required IC. The address should be human readable as in 8 bits long, the first being changed for read and write operations according to the I2C protocol.i2c_transfer
: pointer to a function that sends some data on the I2C network and then reads the answer. It is hardware dependant and should be provided by the user.ack_polling
: a peculiar function used only on some EEPROM ICs. It should continuously poll the device, waiting for a response. Not strictly necessary.delay_ms
: pointer to a function that blocks for the required time in milliseconds.arg
:void*
user argument that is passed to each function pointer.
The following example defines a FreeRTOS task that reads temperature and humidity values every second. The hardware abstraction layer should be already initialized and ready.
#include "i2c_devices.h"
#include "i2c_common/i2c_common.h"
#include "i2c_ports/esp-idf/esp_idf_i2c_port.h"
#include "i2c_devices/temperature/SHTC3/shtc3.h"
static void delay_ms(unsigned long ms);
i2c_driver_t shtc3_driver = {
.device_address = SHTC3_DEFAULT_ADDRESS,
.i2c_transfer = esp_idf_i2c_port_transfer,
.delay_ms = delay_ms,
};
// Periodically read a temperature value
static void temperature_task(void *args) {
(void)args;
shtc3_wakeup(shtc3_driver); //Wake up the device
for (;;) {
int16_t temperature = 0;
int16_t humidity = 0;
if (shtc3_start_temperature_humidity_measurement(shtc3_driver) == 0) {
vTaskDelay(pdMS_TO_TICKS(SHTC3_NORMAL_MEASUREMENT_PERIOD_MS));
if (shtc3_read_temperature_humidity_measurement(shtc3_driver, &temperature, &humidity) == 0) {
printf("Sensor values: %iC %i%%", temperature, humidity);
} else {
printf("Error in reading temperature measurement!\n");
}
} else {
printf("Error in starting temperature measurement\n");
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
vTaskDelete(NULL);
}
static void delay_ms(unsigned long ms) {
vTaskDelay(pdMS_TO_TICKS(ms));
}
There are three main folders:
i2c_common
: module for functions and data structures common to all device drivers. Contains the typedefi2c_driver_t
.i2c_devices
: folder with all device driversi2c_ports
: optional hardware abstraction layer implementations for some common architectures.
Each directory under i2c_devices
contains a driver for a different device. Currently supported devices are:
-
accelerometer
: Accelerometer devicesWSEN_ITDS
: Wurth 3-Axis accelerometer
-
dac
: Digital to analog convertersMCP4018
: Microchip digital potentiometer
-
eeprom
: EEPROM memories24LC16
: Microchip 1Kbit EEPROM memory24AA32
: Microchip 32Kbi EEPROM memory24LC1025
: Microchip 1024Kbit EEPROM memory
-
io
: Port expandersMCP23008
: Microchip 8-bit port expander (i2c version)TCA9534
: Texas Instruments 8-bit port expander
-
light_proximity
: Light and proximity sensorsLTR559ALS
: Liteon optical sensorVCNL4010
: Vishay proximity and light sensor
-
rtc
: Real Time ClocksDS1307
: Analog RTCM41T81
: STM RTCPCF85063A
: NXP RTCPCF8523
: NXP RTCRX8010
: Epson RTC
-
temperature
: Temperature, humidity and pressure sensorsMCP9800
: Microchip temperature sensorMS5837
: TE temperature and pressure sensorSHT21
: Sensirion temperature and humidity sensorSHT3
: Sensirion temperature and humidity sensorSHT4
: Sensirion temperature and humidity sensorSHTC3
: Sensirion temperature and humidity sensorTC74
: Microchip temperature sensorZS05
: Winsen temperature and humidity sensor
As mentioned, this library is hardware agnostic and requires an hardware abstraction layer in the form of the i2c_transfer
callback to work.
In practice this means instantiating the i2c_driver_t
structure.
const i2c_driver_t sht31_driver =
{ .device_address = 0x88, .i2c_transfer = esp_idf_i2c_port_transfer };
device_address
and i2c_transfer
are the only two mandatory fields (although some drivers, in some cases, also require the delay_ms
callback).
The device address is then passed to i2c_transfer
invokations during the driver's work.
i2c_transfer
should point to a function with the following signature:
int (*i2c_transfer)(uint8_t devaddr, uint8_t *writebuf, size_t writelen, uint8_t *readbuf, size_t readlen, void *arg);
devaddr
is the device address as specified inside the structurewritebuf
is the buffer containing data that should be written on the I2C network. Can beNULL
if no write is requiredwritelen
is the number of bytes to write (taken fromwritebuf
)readbuf
is the buffer where data will be transferred from the I2C network after the write operation. Can beNULL
if no read is requiredreadlen
is the number of bytes to read (transferred inreadbuf
)arg
is the user pointer specified in the driver structure
Then, every function that makes up the corresponding device driver will take the i2c_driver_t
instance as first parameter.
The i2c_ports
folder contains some notable ports:
dummy
: a simple port with an HAL that does nothing. Useful for stubs, tests and simulations.esp-idf
: port to the ESP-IDF framework.PIC
: ragged bitbang driver for PIC microcontrollers. I've found that often times the processor internal modules for networks like SPI and I2C simply don't work as advertised, and even when they do they are simply unusable. Bitbang routines are less efficient but cause no trouble.posix
: port that emulates EEPROMs through posix files.
As of now only fairly simple devices are included; documention should be studied from the comments in the header files.
This library uses SCons
as a build tool. To include it in your project just refer to the SConscript
and expose two variables:
i2c_env
should contain the compilation environment and i2c_selected
should be a string list with the modules that should be included.
Invoking the SConscript
returns a tuple containing the compiled library as a static archive and a list of directories that should
be added to the include path.
i2c_env = env
i2c_selected = ['temperature/SHT3',
'temperature/SHT21', 'LTR559ALS', 'dummy']
(i2c_lib, include) = SConscript(
f'{I2C}/SConscript', exports=['i2c_env', 'i2c_selected'])
env['CPPPATH'] += [include]
prog = env.Program(PROGRAM, sources + i2c_lib)
Aside from SCons
this is also a ready-to-use ESP-IDF component. Just add this repository as a submodule in the components
folder of your project
and configure the required module through the KConfig
interface.
Each device driver is composed by very few source files with no dependencies beside i2c_common
; porting any of those to another build system should be trivial.
- Raise all code level up to standard (e.g. PIC ports)
- Add a generic bitbang port