clock

This guide covers various approaches for dealing with issues that arise when I2C devices use clock stretching on a Raspberry Pi.

What Is Clock Stretching?

There is a good general overview of clock stretching in this guide:

Keep in mind that clock stretching is a feature of I2C. It's not unique to Raspberry Pi's. The issue is simply how Pi's handle (or don't) I2C clock stretching.

Why Are Raspberry Pi's an Issue?

The same guide linked above discusses this. It's a known hardware issue with a lot of history. The articles referenced in that guide are linked again here for convenience:

Let's start by demonstrating the general issue. There is a list of known troublesome chips here:

The BNO55 and BNO085 sensors are used to demonstrate since these commonly come up as a source of problems.

BNO055 Demonstration

The main guide for the BNO055 can be used as a starting point:

The wiring used here is identical to what is shown in that guide.

NOTE: There is a fair bit of initial software setup involved - mainly with the Blinka setup. Be sure to go through all of the required steps in the guide linked above.

Once Blinka is installed and configured and you have installed the BNO055 CircuitPython library, now to see if the BNO055 can be used on the Pi.

First double check the connections with a quick I2C scan:

pi@raspberrypi:~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- 28 -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --

Cool! It's showing up at the expected I2C address of 0x28. So the BNO055 is found. Try running the simple example from the library:

pi@raspberrypi:~ $ python3 bno055_simpletest.py
Temperature: -106 degrees C
Accelerometer (m/s^2): (0.0, 0.0, 0.0)
Magnetometer (microteslas): (36.25, -29.5, -66.0)
Gyroscope (rad/sec): (0.0, -0.003272492347489368, 0.002181661564992912)
Euler angle: (0.0, 0.0, 0.0)
Quaternion: (0.0, 0.0, 0.0, 0.0)
Linear acceleration (m/s^2): (0.0, 0.0, 0.0)
Gravity (m/s^2): (0.0, 0.0, 0.0)

Temperature: -106 degrees C
Accelerometer (m/s^2): (0.16, -0.41000000000000003, 9.89)
Magnetometer (microteslas): (35.875, -29.0625, -64.1875)
Gyroscope (rad/sec): (0.0, 0.0, 0.001090830782496456)
Euler angle: (0.0, 0.8125, 2.4375)
Quaternion: (0.999755859375, -0.02154541015625, -0.00750732421875, 0.0)
Linear acceleration (m/s^2): (0.0, 0.0, 0.17)
Gravity (m/s^2): (0.14, -0.42, 9.790000000000001)

Well...it's running OK without any errors....but what's up with those sensor values? A temperature of -106 deg C? Something doesn't seem right. And it's not. Due to the poor handling of clock stretching, erroneous data is being read in.

BNO085 Example

Now to try the BNO085, which is similar to the BNO055. The main guide for the BNO085 covers how to get everything initially installed:

Once that is done, we again start with a quick I2C scan:

pi@raspberrypi:~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- 4a -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --

Yep. That's the expected 0x4A address for the BNO085.

Now to try running the example from the library:

pi@raspberrypi:~ $ python3 bno08x_simpletest.py
Acceleration:
X: 0.230469  Y: -0.039062 Z: 9.843750  m/s^2

Gyro:
X: 0.001953  Y: 0.001953 Z: 0.001953 rads/s

Magnetometer:
X: 22.187500  Y: 28.375000 Z: -47.750000 uT

Rotation Vector Quaternion:
I: 0.002136  J: -0.011108 K: 0.335449  Real: 0.942017

Acceleration:

Traceback (most recent call last):
  File "/home/pi/bno08x_simpletest.py", line 29, in <module>
    accel_x, accel_y, accel_z = bno.acceleration  # pylint:disable=no-member
  File "/home/pi/blinka/lib/python3.9/site-packages/adafruit_bno08x/__init__.py", line 594, in acceleration
    self._process_available_packets()
  File "/home/pi/blinka/lib/python3.9/site-packages/adafruit_bno08x/__init__.py", line 790, in _process_available_packets
    self._handle_packet(new_packet)
  File "/home/pi/blinka/lib/python3.9/site-packages/adafruit_bno08x/__init__.py", line 848, in _handle_packet
    raise error
  File "/home/pi/blinka/lib/python3.9/site-packages/adafruit_bno08x/__init__.py", line 843, in _handle_packet
    _separate_batch(packet, self._packet_slices)
  File "/home/pi/blinka/lib/python3.9/site-packages/adafruit_bno08x/__init__.py", line 358, in _separate_batch
    required_bytes = _report_length(report_id)
  File "/home/pi/blinka/lib/python3.9/site-packages/adafruit_bno08x/__init__.py", line 347, in _report_length
    return _AVAIL_SENSOR_REPORTS[report_id][2]
KeyError: 133

It gave a reading (or several) that look OK, then it exited with a cryptic error. What going on? It's clock stretching again.

Changing the I2C clock speed is the first thing to try, since it allows continuing to use the existing hardware I2C peripheral on the Raspberry Pi. It also generally does not require any changes to user code. The changes are done using a system configuration file to directly alter that I2C hardware peripheral behavior.

The default I2C clock speed is 100000Hz (100kHz).

Editing config.txt

Changing the I2C clock speed is done by adding a new line to the config.txt file. The location of this file has changed with time. For Raspberry Pi OS releases prior to bookworm, the file is at /boot/config.txt. Starting with bookworm, the location is /boot/firmware/config.txt. Update the examples below to match the location for the OS release being used.

Log in to a terminal on your Pi and open that file in Nano, or your text editor of choice:

sudo nano /boot/config.txt

Scroll down until you find a block like:

# Uncomment some of all of these to enable the optional hardware interfaces
dtparam=i2c_arm=on
dtparam=i2s=on
dtparam=spi=on

This block might vary depending on what you've enabled in raspi-config. Directly below it, add lines that looks like (the line starting with # is a comment):

# Set I2C Clock Speed
dtparam=i2c_arm_baudrate=REPLACE_WITH_VALUE

Replace REPLACE_WITH_VALUE with the desired clock speed value in Hz. Picking a value is discussed in the following sections.

Next, save the file and exit (in Nano, press Ctrl-Xy for yes, and Enter).

Now reboot the Pi for the setting to take effect:

sudo reboot
Be sure to set an actual value for i2c_arm_baudrate before rebooting.

Slow Down The Clock

This seems to be the most universal fix. The general idea is to slow down the clock enough that any clock stretching simply gets buried within a given clock cycle. That is - the stretching happens faster than the main clock itself, so it just gets missed and is never even seen.

The suggested starting value is 10000 Hz, so the line in /boot/config.txt would be:

# Clock stretching by slowing down to 10KHz
dtparam=i2c_arm_baudrate=10000

If you still get bad data, try slowing it down more, maybe to 5 KHz or 1 KHz rate. Reboot after each change.

Speed Up The Clock

There are a few sensors, like the BNO085, for which this seems the better fix. This is done the same way as slowing down the clock, but the value is simply larger than the 100kHz default. I2C has various defined speed "ranges", and the next one above 100kHz is 400kHz, so the line in /boot/config.txt would be:

# Clock stretching by speeding up to 400kHz
dtparam=i2c_arm_baudrate=400000

Don't forget to reboot after saving the change.

Now to test the suggested fixes for slowing down / speeding up the I2C clock and see if it fixes the demonstrated issues with the BNO055 and BNO085.

Clock Slow Down Fix for BNO055

With the line:

dtparam=i2c_arm_baudrate=10000

added to /boot/config.txt and the Pi rebooted, try again the same library example:

pi@raspberrypi:~ $ python3 bno055_simpletest.py
Temperature: 22 degrees C
Accelerometer (m/s^2): (0.13, -0.33, 9.89)
Magnetometer (microteslas): (37.75, -27.6875, -65.25)
Gyroscope (rad/sec): (-0.001090830782496456, -0.003272492347489368, -0.001090830782496456)
Euler angle: (0.0, 0.0, 0.0)
Quaternion: (1.0, 0.0, 0.0, 0.0)
Linear acceleration (m/s^2): (0.15, -0.33, 0.06)
Gravity (m/s^2): (0.0, 0.0, 9.8)

Temperature: 22 degrees C
Accelerometer (m/s^2): (0.15, -0.34, 9.96)
Magnetometer (microteslas): (39.1875, -26.5625, -64.25)
Gyroscope (rad/sec): (-0.001090830782496456, 0.0, -0.001090830782496456)
Euler angle: (0.0, 0.8125, 1.875)
Quaternion: (0.99981689453125, -0.01666259765625, -0.00726318359375, 0.0)
Linear acceleration (m/s^2): (0.0, -0.01, 0.12)
Gravity (m/s^2): (0.14, -0.32, 9.8)

Temperature: 22 degrees C
Accelerometer (m/s^2): (0.13, -0.34, 9.99)
Magnetometer (microteslas): (38.75, -26.25, -64.6875)
Gyroscope (rad/sec): (0.0, 0.001090830782496456, -0.002181661564992912)
Euler angle: (0.0, 0.8125, 1.875)
Quaternion: (0.99981689453125, -0.01666259765625, -0.00726318359375, 0.0)
Linear acceleration (m/s^2): (0.0, 0.0, 0.15)
Gravity (m/s^2): (0.14, -0.32, 9.8)

Much better.

Clock Speed Up Fix for BNO085

With the line:

dtparam=i2c_arm_baudrate=400000

added to /boot/config.txt and the Pi rebooted, try running the same library example:

pi@raspberrypi:~ $ python3 bno08x_simpletest.py
Acceleration:
X: 0.574219  Y: -0.113281 Z: 10.000000  m/s^2

Gyro:
X: 0.000000  Y: 0.000000 Z: 0.000000 rads/s

Magnetometer:
X: 13.812500  Y: 22.375000 Z: -45.875000 uT

Rotation Vector Quaternion:
I: 0.003662  J: -0.025330 K: 0.294250  Real: 0.955383

Acceleration:
X: 0.613281  Y: -0.113281 Z: 10.031250  m/s^2

Gyro:
X: 0.000000  Y: 0.000000 Z: -0.001953 rads/s

Magnetometer:
X: 14.187500  Y: 23.125000 Z: -43.187500 uT

Rotation Vector Quaternion:
I: 0.003662  J: -0.028625 K: 0.316528  Real: 0.948120

And now it's working.

Since the underlying issue with I2C clock stretching on a Raspberry Pi is the hardware I2C peripheral, another option is to simply not use it. Instead, use a software based implementation. On a Raspberry Pi, this is available via the i2c-gpio device tree overlay.

There is a little more work to be done with this approach. An additional library must be installed and some minor code changes need to be done to user code.

The i2c-gpio Overlay

Information about the i2c-gpio overlay can be found in this README:

The same information is also likely on the Pi itself under /boot/overlays/README. This is a large README that covers all of the overlays. Search for "i2c-gpio" to find the entry for the software I2C overlay. It should look like this:

This describes the general syntax used to enable the overlay (the Load: line) as well as the four parameters that can be used to customize the resulting behavior.

Enabling the i2c-gpio Overlay

To enable the i2c-gpio overlay, the /boot/config.txt is updated to add a line that enables it and optionally configures settings.

To use the defaults settings, the line can simply be:

dtoverlay=i2c-gpio

To specify specific pins, for example 16 and 20, use:

dtoverlay=i2c-gpio,i2c_gpio_sda=16,i2c_gpio_scl=20

After rebooting, the resulting I2C interface will show up as a /dev/i2c* entry. A number will automatically be assigned at boot time. To specify a specific bus number, for example 8, use:

dtoverlay=i2c-gpio,bus=8

The parameters can be combined as needed. For example, to specify specific pins and a fixed bus number, use:

dtoverlay=i2c-gpio,i2c_gpio_sda=16,i2c_gpio_scl=20,bus=8
Don't forget to reboot the Pi for the settings to take effect.

Finding the I2C Port

After editing /boot/config.txt to add the i2c-gpio overlay, and rebooting, run the following command:

ls /dev/i2c*

This will list all of the I2C buses created. Here is example output using the configuration from the previous section:

pi@raspberrypi:~ $ ls /dev/i2c*
/dev/i2c-1  /dev/i2c-20  /dev/i2c-21  /dev/i2c-8

There are several entries shown. The /dev/i2c-8 entry is the one created via the i2c-gpio overlay. The number 8 is a result of using the bus=8 parameter.

Extended Bus Library

This is the first bit of extra work that needs to be done to allow using the i2c-gpio overlay. The main Blinka installation works with known specific hardware I2C ports and pins. It's not aware of any additional I2C ports that may be setup using the i2c-gpio overlay. To access these, the following library is needed:

It can be installed like any other Python/CircuitPython library using pip:

pip3 install adafruit-extended-bus

Update Code

The next bit of work needed is to modify code to use the Python Extended Bus library to access the software I2C port. The changes are limited to the initial setup part of the code. Once the I2C port has been created in code, it can be used in the same manner as the typical hardware I2C port.

Here is a brief summary of the two main lines needed:

# import the library
from adafruit_extended_bus import ExtendedI2C as I2C

# access the I2C port by bus number
i2c=I2C(8)

The library is imported and then the I2C bus is created. It is referenced using the bus number, 8 in this example. This number may be different for different setups and should be changed as needed. See previous section for details.

Now the i2c instance can be used in the same manner as the regular hardware i2c instance seen in most example code.

Let's look at some specific examples using the BNO055 and BNO085.

NOTE: These example assume the i2c-gpio overlay has been enabled and configured with the following line added to /boot/config.txt:

dtoverlay=i2c-gpio,i2c_gpio_sda=16,i2c_gpio_scl=20,bus=8

Software I2C with BNO055

Let's try using the software I2C port with the BNO055.

Wiring used for BNO055 example. Note the pins used for SCL and SDA.

We again use the example from the BNO055 library. However, we make the following modifications to use the software I2C port:

import time
import board
import adafruit_bno055
from adafruit_extended_bus import ExtendedI2C as I2C

i2c=I2C(8)
sensor = adafruit_bno055.BNO055_I2C(i2c)

Only the top part of the code listing is shown above. The remainder of the example code is unchanged.

Now running the updated example:

pi@raspberrypi:~ $ python3 bno055_simpletest.py 
Temperature: 23 degrees C
Accelerometer (m/s^2): (0.0, 0.0, 0.0)
Magnetometer (microteslas): (38.5, -27.6875, -67.0625)
Gyroscope (rad/sec): (-0.002181661564992912, -0.001090830782496456, 0.00545415391248228)
Euler angle: (0.0, 0.0, 0.0)
Quaternion: (0.0, 0.0, 0.0, 0.0)
Linear acceleration (m/s^2): (0.0, 0.0, 0.0)
Gravity (m/s^2): (0.0, 0.0, 0.0)

Temperature: 23 degrees C
Accelerometer (m/s^2): (0.15, -0.34, 10.0)
Magnetometer (microteslas): (39.1875, -26.25, -65.0625)
Gyroscope (rad/sec): (-0.001090830782496456, 0.0, 0.0)
Euler angle: (0.0, 0.8125, 2.0625)
Quaternion: (0.99981689453125, -0.018310546875, -0.0072021484375, 0.0)
Linear acceleration (m/s^2): (-0.01, 0.01, 0.14)
Gravity (m/s^2): (0.14, -0.35000000000000003, 9.790000000000001)

Temperature: 23 degrees C
Accelerometer (m/s^2): (0.15, -0.33, 9.92)
Magnetometer (microteslas): (39.5625, -26.5625, -64.375)
Gyroscope (rad/sec): (-0.001090830782496456, -0.001090830782496456, 0.0)
Euler angle: (0.0, 0.8125, 2.0625)
Quaternion: (0.99981689453125, -0.018310546875, -0.0072021484375, 0.0)
Linear acceleration (m/s^2): (0.01, 0.02, 0.17)
Gravity (m/s^2): (0.14, -0.35000000000000003, 9.790000000000001)

The readings look good.

Software I2C with BNO085

Now to try using the software I2C port with the BNO085.

Wiring used for BNO085 example. Note the pins used for SCL and SDA.

Again to use the example from the BNO085 library. However, make the following modifications to use the software I2C port:

import time
import board
import busio
from adafruit_bno08x import (
    BNO_REPORT_ACCELEROMETER,
    BNO_REPORT_GYROSCOPE,
    BNO_REPORT_MAGNETOMETER,
    BNO_REPORT_ROTATION_VECTOR,
)
from adafruit_bno08x.i2c import BNO08X_I2C
from adafruit_extended_bus import ExtendedI2C as I2C

i2c=I2C(8)
bno = BNO08X_I2C(i2c)

Only the top part of the code listing is shown above. The remainder of the example code is unchanged.

Now running the updated example:

pi@raspberrypi:~ $ python3 bno08x_simpletest.py
Acceleration:
X: 0.078125  Y: 0.078125 Z: 9.542969  m/s^2

Gyro:
X: -0.001953  Y: 0.000000 Z: 0.000000 rads/s

Magnetometer:
X: 18.250000  Y: 31.312500 Z: -51.437500 uT

Rotation Vector Quaternion:
I: 0.005066  J: -0.002808 K: 0.271362  Real: 0.962463

Acceleration:
X: 0.078125  Y: 0.078125 Z: 9.582031  m/s^2

Gyro:
X: 0.000000  Y: 0.000000 Z: -0.003906 rads/s

Magnetometer:
X: 18.687500  Y: 32.000000 Z: -51.500000 uT

Rotation Vector Quaternion:
I: 0.005554  J: -0.002014 K: 0.267334  Real: 0.963623

Readings look good and (hopefully) it should continue running without hitting any errors.

This guide was first published on Oct 26, 2022. It was last updated on Mar 26, 2024.