pi_i2c.c provides a comprehensive I2C library for the Raspberry Pi. Unlike the Pi's built-in I2C controller, any number of GPIOs can be defined as the SCL and SDA line thus allowing flexible configuration and additional I2C busses to be defined. Additional features of this library include multi-byte read & write functions, scan function, clock stretching support, error handling, and useful statistics.
This library requires the following dependencies (projects also authored by me):
- pi_lw_gpio.c
- "Lightweight GPIO Interface Library for the Raspberry Pi"
- pi_microsleep_hard.c
- "Hard Microsleep Library Via System Timer for the Raspberry Pi"
They are not required to compile pi_i2c.c into a shared library but are required to be linked when building your project.
pi_i2c.c is provided four ways for flexibility:
- C source and header files that can be compiled along with your program
- C shared library
- Python package
- Interface with pi_i2c.c using Python 3!
- A Bash executable
- Interface with pi_i2c.c using the CLI!
A test script is included in this repository to checkout the library working on your Pi. You can also checkout this LIS3MDL example I wrote to test out the library on a specific device.
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. These steps will configure, make, install, and provide instructions on uninstall for the C shared library, python package, and Bash executable.
First, clone this repository.
$ git clone https://github.com/besp9510/pi_i2c.git
Alternatively, download the repository from Git.
Once obtaining a copy on your local machine, navigate to the top directory and run the configure script to generate a Makefile.
$ ./configure
By default, files will be installed under /usr/local/
. Note that passing the option --help
will display available configuration options such as installation directory prefix and debug symbols.
Next, navigate to the cli
directory and run the configure script to generate a Makefile for the Bash executable.
$ cd cli
$ ./configure
Return to the top directory after doing so.
$ cd ../
Compile pi_i2c.c into a shared library.
$ make
Then install files to the installation directory. You must run the following either as root or with root privileges.
$ sudo make install
Next, navigate to the cli
directory to compile the Bash executable.
$ cd cli/
$ make
Then install files to the installation directory.
$ make install
To use pi_i2c.c in your project, simply include the header file pi_i2c.h
and link to the shared library -lpii2c
, -lpilwgpio
, & -lpimicrosleephard
. See the test script's test_pi_i2c.c
Makefile under the test/
directory as a example.
If using the Pi's default SDA & SCL pins (BCM pin 2 & 3), ensure that Raspian I2C interface is disabled via rasp-config or otherwise risk unpredictable behavior!
At anytime, to uninstall pi_i2c.c, use the same Makefile used for compiling or a Makefile generated using the configuration script with the same options as root or with root privileges.
$ sudo make uninstall
test_pi_i2c.c is a test script to check and see the I2C library working on your Pi with some I2C device. The outline of the test is:
- Defining I2C address and registers to test with
- Setting up pi_i2c()
- Writing and reading 1 and multiple bytes one-shot and iteratively
- Bit rate read and write tests
- Testing get config and statistics functions
Prior to running the test, make sure to update the I2C address and registers for your specific device.
// Ensure that Raspian I2C interface is disabled via rasp-config otherwise
// risk unpredictable behavior!
int sda_pin = 2; // UPDATE
int scl_pin = 3; // UPDATE
int speed_grade = I2C_FULL_SPEED;
// Addresses & data to use when testing write:
int write_device_address = 0x1C; // UPDATE
int write_register_address = 0x21; // UPDATE
int write_data[1] = {0x00}; // UPDATE
int write_bytes = 1; // UPDATE
int write_iterations = 10;
int write_device_address_multiple = 0x1C; // UPDATE
int write_register_address_multiple = 0x23; // UPDATE
int write_data_multiple[2] = {0x0, 0x0}; // UPDATE
int write_bytes_multiple = 2;
int read_device_address = 0x1C; // UPDATE
int read_register_address = 0x0F; // UPDATE
int read_bytes = 1; // UPDATE
int read_iterations = 10;
int read_data[1]; // UPDATE
int read_device_address_multiple = 0x1C; // UPDATE
int read_register_address_multiple = 0x28; // UPDATE
int read_bytes_multiple = 2; // UPDATE
int read_data_multiple[read_bytes_multiple]; // UPDATE
This test requires the following dependencies (projects also authored by me):
- pi_lw_gpio.c
- "Lightweight GPIO Interface Library for the Raspberry Pi"
- pi_microsleep_hard.c
- "Hard Microsleep Library Via System Timer for the Raspberry Pi"
To compile the test script, first navigate to the test directory test/
. Next, run the configure script to generate the Makefile:
$ ./configure
By default, the pi_i2c.c shared library will be looked for under the standard directories (e.g. /usr/local/
). If this is not the case, pass the option --help
to learn how to specify a directory to be searched. Additionally, --help
will also display available configuration options such as debug symbols and debug logs.
Next, compile the test script:
$ make
This will create an executable called test_pi_i2c
under bin/
.
pi_i2c.c implements I2C according to the UM10204 I2C-bus specification and user manual. Specifically, the following I2C bus protocol features are supported for single controller configuration only:
- START condition
- STOP condition
- Acknowledge
- Clock Stretching
- 7-bit device address
- Software reset
- See error handling for more details
- Standard-mode and full-speed mode
- Single and multiple byte read and write functionality
Additionally, timing minimums are respected according to this table and diagram:
pi_i2c.c performs bus error detection and bus recovery (if able) to safe guard against hardware damage. This is done at every step of a message transaction:
- Transition from IDLE to BUSY bus
- Start, middle, and end of write or read message transaction
- Transition from BUSY to IDLE bus
If an error is detected at any of these steps and cannot be resolved, no further action is taken by pi_i2c. An error code is reported and it is then responsibility of the caller to recover the bus (power cycle of the offending component is typical).
The following table summarizes the types of bus errors detected, actions taken by pi_i2c.c, the error number returned to the caller, and suggested recovery actions. Suggested recovery actions are to be performed by the caller.
Bus Error | Action Taken | Error Number | Further Recovery Actions |
---|---|---|---|
Device did not acknowledge device address | Halt transaction and return error number | ENACK |
Check if device is responsive; otherwise power cycle device |
Device did not acknowledge register address | Halt transaction and return error number | EBADREGADDR |
Check if device is responsive; otherwise power cycle device |
Device did not acknowledge during byte transfer | Halt transaction and return error number | EBADXFR |
Check if device is responsive; otherwise power cycle device |
Device not responsive after clock stretch timeout (device forcing SCL line held low) | Halt transaction and return error number | ECLKTIMEOUT |
Check if device is responsive; otherwise power cycle device |
Device did not respond after repeated start | Halt transaction and return error number | ENACKRST |
Check if device is responsive; otherwise power cycle device |
Device is forcing SDA line low (device hung) | Reset bus to recover; if still hung, halt transaction and return error number | EdeviceHUNG |
Power cycle device |
Device is holding SDA and SCL lines low causing bus to be locked | Halt transaction and return error number | EBUSLOCKUP |
Power cycle device |
Device is forcing SDA and/or SCL line low causing START condition writes to fail | Halt transaction and return error number | EFAILSTCOND |
Power cycle device |
Bus is an unexpected state following an unknown error | Halt transaction and return error number | EBUSUNKERR |
Power cycle device |
Bottom line: if pi_i2c.c sees the bus in an expected state at any time then the transaction will be cancelled. This is to prevent any accidental and undefined data transfers to take place which may cause hardware damage (ask me about my broken IMU if would like to know more about this).
Bit rate achievable by pi_i2c.c is primarily a function of the clock accuracy, minimum I2C timings, and I2C protocol messaging overhead:
- pi_microsleep_hard.c provides a hard microsleep function with a resolution of 1 us
- Minimum I2C timings (found in the above table) rounded up to the nearest 1 us (per pi_microsleep_hard.c)
- Messaging overhead: STOP, START, Repeated START conditions, ACKS, and device & register addresses
Using these constraints, pi_i2c.c achieves a total and useful bit rate that will be lower than ideal. I2C full-speed mode (400 kHz), for example, would have the following timings:
Actual frequency is less than the desired 400 kHz due to the 1 us rounding on T_Low & T_High required by the hard microsleep function. Additionally, consider the minimum I2C timings have also been rounded resulting is longer wait times. End result is smaller bit rate at the trade of flexibility and overall features pi_i2c.c provides.
The theoretical useful bit rate can be then calculated for a n-byte read and write transaction using these achievable timings. Useful bit rate is defined as how much "useful" data is being transferred in a given read or write transaction. Not all data in an I2C message is useful in the sense that some of it is overhead and not the data we are actually trying to transfer between the controller and device. Theoretical useful bit rates are calculated and plotted for the I2C full-speed mode (400 kHz) example discussed above:
Note that these are theoretical useful bit rates. In practice, pi_i2c.c will provide lower rates than these theoretical values because of the operating system's overhead and scheduler.
The asymptotic trend lines of write and read is the result of transferring more data in one transaction while messaging overhead remains constant (number of STARTs, STOPs, ACKs, and register addresses do not change). As the number of bytes read/written increases, the smaller penalty the messaging overhead imposes resulting in a useful bit rate that asymptotically approaches ~348 kbps for write and ~297 kbps for read. I2C transactions that transfer only a couple of bytes at a time are much more inefficient than ones that transfer 10s of bytes.
Setup the library prior to using pi_i2c.c. Define which GPIO pins will be used for the SDA & SCL lines and what the speed grade (bit rate) is. This function must be called prior to using any other functions or otherwise they will return the EI2CNOTCFG
error number. Any GPIO pins can be used but if you select the Pi's default SDA & SCL pins (BCM pin 2 & 3), ensure that Raspian I2C interface is disabled via rasp-config or otherwise risk unpredictable behavior. Available speed grades to choose from are:
I2C_STANDARD_MODE
(100 kHz)I2C_FULL_SPEED
(400 kHz)
int config_i2c(unsigned int sda, unsigned int scl, unsigned int speed_grade);
unsigned int sda
is the GPIO pin to use for the SDA line (BCM numbering).
unsigned int scl
is the GPIO pin to use for the SCL line (BCM numbering).
unsigned int speed_grade
is the speed grade (bit rate) to use for the I2C bus according to standard I2C definition.
config_i2c()
returns 0 upon success. On error, an error number is returned.
Error numbers:
ENOPIVER
: Could not get Pi board revision.MAP_FAILED
: Memory map failed (most likely due to permissions)EINVAL
: Invalid argument (e.g. sda, scl, or speed grade out of range)
Scan the bus for any I2C devices. Note that only 7-bit addresses are supported. The function requires a pointer to an address book (a 127-element integer array) to be passed as an argument. The indices of this address book correspond directly to an I2C address; for example, index 0x1 of the array corresponds to an I2C device at an address of 0x1.
address_book[127] = {
[Index/Address] = 0 (Not present) / 1 (Detected)
}
The results of the bus scan will be stored back into this address book. If the value of an element at index i equals 1 then a I2C device at an address equaling index i is present.
int scan_bus_i2c(int *address_book);
The address book int *address_book
argument is a pointer to a 127-element integer array where the results of the bus scan are stored back into.
scan_bus_i2c()
returns 0 upon success. On error, an error number is returned.
Error numbers:
EI2CNOTCFG
: pi_i2c has not yet been configuredEDEVICEHUNG
: Device forcing SDA line lowECLKTIMEOUT
: Device not responsive after clock stretch timeoutEBUSLOCKUP
: Bus is locked: SDA and SCL lines are being held low by deviceEBUSUNKERR
: Bus is in an unexpected state following an unknown errorECLKTIMEOUT
: Device not responsive after clock stretch timeoutEFAILSTCOND
: Failed to write a START condition to the bus. Most likely, error occurred during a previous STOP condition.
Write n-bytes to a device's register address. Data to write to the device's register address is passed into the function as a pointer to an n-byte integer data array.
int write_i2c(unsigned int device_address, unsigned int register_address, int *data, unsigned int n_bytes);
The unsigned int device_address
argument is the device's I2C address.
The unsigned int register_address
argument is the specific register data will be written to.
Data to write to the device's register address is stored in the int *data
argument: a n_bytes
integer array that is passed into the function as a pointer.
Number of bytes unsigned int n_bytes
argument is the number of bytes to write to the device's register address. int *data
must be at least n_bytes
large.
write_i2c()
returns 0 upon success. On error, an error number is returned.
Error numbers:
EI2CNOTCFG
: pi_i2c has not yet been configuredEDEVICEHUNG
: Device forcing SDA line lowECLKTIMEOUT
: Device not responsive after clock stretch timeoutEBUSLOCKUP
: Bus is locked: SDA and SCL lines are being held low by deviceEBUSUNKERR
: Bus is in an unexpected state following an unknown errorECLKTIMEOUT
: Device not responsive after clock stretch timeoutEFAILSTCOND
: Failed to write a START condition to the bus. Most likely, error occurred during a previous STOP condition.ENACK
: Device did not acknowledge device addressEBADXFR
: Device did not acknowledge during byte transfer (read or write)EBADREGADDR
: Device did not acknowledge register addressEINVAL
: Invalid argument (e.g. device_address or register address out of range; negative n_bytes)
Read n-bytes from a device's register address. Data read from the device will be stored back into the n-byte integer data array passed into the function by pointer.
int read_i2c(unsigned int device_address, unsigned int register_address, int *data, unsigned int n_bytes);
The unsigned int device_address
argument is the device's I2C address.
The unsigned int register_address
argument is the specific register data will be read from. Note, when more than 1 byte is read from A register, the register address will automatically increase by 1 each time a byte is read. This results in the n-byte of data being read from int register_address
+ n (indexed from 0).
Data read from device's register address is stored back in the int *data
argument: a n_bytes
integer array that is passed into the function as a pointer.
Number of bytes unsigned int n_bytes
argument is the number of bytes to read to the device's register address. int *data
must be at least n_bytes
large.
read_i2c()
returns 0 upon success. On error, an error number is returned.
Error numbers:
EI2CNOTCFG
: Pi I2C has not yet been configuredEDEVICEHUNG
: Device forcing SDA line lowECLKTIMEOUT
: Device not responsive after clock stretch timeoutEBUSLOCKUP
: Bus is locked: SDA and SCL lines are being held low by deviceEBUSUNKERR
: Bus is in an unexpected state following an unknown errorECLKTIMEOUT
: Device not responsive after clock stretch timeoutEFAILSTCOND
: Failed to write a START condition to the bus. Most likely, error occurred during a previous STOP condition.ENACK
: Device did not acknowledge device addressEBADXFR
: Device did not acknowledge during byte transfer (read or write)EBADREGADDR
: Device did not acknowledge register addressENACKRST
: Device did not respond after repeated start device addressEINVAL
: Invalid argument (e.g. device_address or register address out of range; negative n_bytes)
Reset I2C bus by issuing 9 clock pulses. Typically used to un-stuck the SDA line after a device is forcing it low. This function is automatically called in the case of error handling but is available to used at any time.
int reset_i2c(void);
reset_i2c()
returns 0 upon success. On error, an error number is returned.
Error numbers:
EI2CNOTCFG
: Pi I2C has not yet been configuredECLKTIMEOUT
: Device not responsive after clock stretch timeout
Return a structure of statistics recorded by pi_i2c.c.
struct pi_i2c_statistics get_statistics_i2c(void);
get_statistics_i2c()
always returns 0 upon success.
Return a structure of internal configurations of pi_i2c.c.
struct pi_i2c_configs get_configs_i2c(void);
get_configs_i2c()
always returns 0 upon success.
The bash executable version of pi_i2c is a CLI interface with the C shared library of pi_i2c.c. This executable takes in options and arguments that are then passed to the respective pi_i2c.c functions (defined above). Output is then directed back to the terminal. This interface is useful for one-off debugging, inspections, or any time it makes sense to interact with a device on a more impromptu basis.
The following is the usage statement printed anytime pi_i2c
is invoked with the help option. By default, the executable is installed to the usr/local/bin/
directory which should be on your $PATH
environmental variable. pi_i2c
will be callable from any directory if that is the case.
$ pi_i2c --help
Usage: pi_i2c [OPTIONS]
Inter-Integrated Circuit (I2C) Library for the Raspberry Pi
Examples:
pi_i2c --sda 2 --scl 3 -- speed-grade i2c_standard_mode --scan
pi_i2c -r --sda 2 --scl 3 --speed-grade 400 --device 0x1C --register 0x23 --bytes 2
pi_i2c -w --sda 2 --scl 3 --speed-grade 100 --device 0x3D --register 0x01 --bytes 1 --data 0xFF,0x1C
pi_i2c -r -a 2 -c 3 -g i2c_full_speed -e 0x70 -i 0x0 -n 1
pi_i2c -w -a 2 -c 3 -g 400 -e 0x70 -i 0x0 -n 1 -d 0x8F
Main operation mode:
-r, --read read N bytes from a device and register address
-w, --write write N bytes from a device and register address
-a, --sda GPIO pin to use for the SDA line (BCM numbering)
-c, --scl GPIO pin to use for the SCL line (BCM numbering)
-g, --speed-grade I2C bus speed grade (bit rate) as defined by pi_i2c.h
valid speed grades are i2c_standard_mode (100) or i2c_full_speed (400)
-e, --device device I2C address as a hex number (e.g., 0xFF)
-i, --register device's register address read from or written as a hex number (e.g., 0xFF)
-s, --scan scan the bus for any I2C devices
-n, --n-bytes number of bytes to read or write to the device's register address
-d, --data data to write to the device's register address in a comma delimited list
data type is hex (e.g., 0xFF)
-v, --debug display useful information
-h, --help display this help and exit
User's notes:
- If using the --scan option, device, register, n_bytes, data, read, and write options are silently ignored
Need any help? find a bug? Checkout https://github.com/besp9510/pi_i2c
Note, data read from a device is only printed to the terminal.
Examples:
$ pi_i2c -a 2 -c 3 -g i2c_full_speed -s
A table is printed along with a list of detected devices.
pi_i2c: I2C bus scan completed
0 1 2 3 4 5 6 7 8 9 A B C D E F
00 x - - - - - - - - - - - - - - -
10 - - - - - - - - - - - - - - - -
20 - - - - - - - - - - - - - - - -
30 - - - - - - - - - - - - - - - -
40 x - - - - - - - - - - - - - - -
50 - - - - - - - - - - - - - - - -
60 - - - - - - - - - - - - - - - -
70 x x x - x - - - - - - - - - - -
devices detected at [0x0, 0x40, 0x70, 0x71, 0x72, 0x74]
$ pi_i2c -w -a 2 -c 3 -g 400 -n 1 -e 0x70 -i 0x0 -d 0x2F
Data written to registers is echo'd to the terminal. Then, the registers are read to confirm successful write and their contents are printed to the terminal.
pi_i2c: wrote 1 byte(s) to device 0x70 at register 0x0
pi_i2c: wrote data = [0x2F]
pi_i2c: reading back 1 byte(s) from device 0x70 at register 0x0
pi_i2c: register values = [0x2F]
$ pi_i2c -r -a 2 -c 3 -g i2c_full_speed -e 0x70 -i 0x0 -n 6
The data read from the registers are printed to the terminal.
pi_i2c: reading 6 byte(s) from device 0x70 at register 0x0
pi_i2c: register values = [0x2F, 0x4, 0xE2, 0xE4, 0xE8, 0xE0]
Follow the "fork-and-pull" Git workflow.
- Fork the repo on GitHub
- Clone the project to your own machine
- Commit changes to your own branch
- Push your work back up to your fork
- Submit a Pull request so that your changes can be reviewed
Be sure to merge the latest from "upstream" before making a pull request!
Feel free to email at the email address under my account name if you have any questions.
Benjamin Spencer
This project is licensed under the MIT License - see the LICENSE.md file for details