Using I2C on the Pyboard with 'machine.I2C'

Contents

What is I2C?

I²C (Inter-Integrated Circuit, pronounced "eye-squared-C") is a common, two-wire serial communication protocol for short-distance data transfer between microcontrollers and peripherals like sensors, displays, or memory.

It uses only two lines, SDA (Serial Data) for data and SCL (Serial Clock) for timing, allowing many devices (masters and slaves, each with a unique address) to share a single bus, making it space-efficient and flexible.

It is synchronous serial communication because there is only a single clock source, maintained by the master, on the I2C bus.

I2C has a written international standard with universal adherence. This provides developers with a high degree of confidence while working on projects involving devices from multiple suppliers.

I2C Hardware on the Pyboard

The Pyboard V1 has two separate hardware I2C units, configured as shown in the following table:

I2C Name SCL Pin SDA Pin
I2C('X') X9 X10
I2C('Y') Y9 Y10

NOTE: They can also be named I2C(1) and I2C(2) but the official MicroPython documentation[1] recommends using the names shown in the table above.

MicroPython machine.I2C Class

This section discusses the machine.I2C class and its methods. This class is specific to MicroPython and provides I2C support for just about all microcontroller boards with a MicroPython port.

An alternative is the pyb.I2C class. However it's very simplistic; providing only a basic set of methods specifically for the Pyboard. For those interested it's covered in this article; Using I2C on the Pyboard with 'pyb.I2C'.

There are two advantages for using the machine.I2C class over the pyb.I2C class:

  • Code is much more portable between different microcontrollers.
  • It offers a finer degree of control over the I2C bus.

Initialising the I2C Object

The first step is to declare and initialise an I2C object.


Syntax:
<i2c_variable> = machine.I2C(id,*, freq=400000)

Creates an I2C object.

Where:
  id : One of 'X' or 'Y' based on the above table.

  freq : The SCL clock rate.
         Usually 400 kHz or 100 kHz for
         some older devices.

Examples:
from machine import I2C

i2c1 = I2C('X')
i2c2 = I2C('Y', freq=100000)

# Print the configurations.
print(i2c1)
print(i2c2)

Output:
I2C(1, scl=B6, sda=B7, freq=336000)
I2C(2, scl=B10, sda=B11, freq=100000)
            

Note: CPU pins B6/B7 are Pyboard pins X9/X10. The CPU pins B10/B11 are Pyboard pins Y9/Y10.

I2C Methods

The I2C object provides the standard bus operations to scan for slaves and read/write to the bus.


Syntax:
I2C.scan()

Scans all I2C addresses from 0x08 to 0x77 and returns
a list of those that respond.
            

Syntax:
I2C.readfrom(addr, nbytes, stop=True, /)

Where:
  addr : The address to receive from.

  nbytes : Number of bytes to receive.

  stop : if True then a STOP condition
         is generated at the end of the transfer.

Returns: Returns a bytes object with the data read.
            

Syntax:
I2C.writeto(addr, buf, stop=True, /)

Where:
  addr : The address to send to.

  buf : The data to send.

  stop : if True then a STOP condition
         is generated at the end of the transfer. 

Returns: The number of ACKs that were received.
            

Note: The sent or received data must be one or more bytes; each byte having a value between 0 and 255 inclusive. This is usually a string or bytes object or bytearray object.

The machine.I2C class also provides methods for primitive (low-level) I2C operations as well as memory operations through the i2C bus. For more details see the official MicroPython documentation[1].

Reading the BMP280 Sensor's Chip ID

The BMP280 is a barometric pressure sensor that remains popular though it is no longer being actively manufactured by Bosch Sensortec.

As with most Bosch Sensortec sensors the BMP280 has a chip ID burnt to non-volatile memory at point of manufacture. The example MicroPython program will read and report this ID value. The algorithm is simple. Write the ID register address of 0xD0 to the BMP280 then read back the ID as a single byte.

The Pyboard's I2C('X') will be used. The hookup to the BMP280 is simple:

Pyboard BMP280
3.3V VCC
GND GND
Pin X9 SCL
Pin X10 SDA

Copy the following code to a suitable IDE such as Thonny and flash to the Pyboard.


Code:
# A BMP280 barometric pressure sensor is attached to the
# 'X' I2C bus of the Pyboard.

# The I2C bus is scanned for the BMP280 address. This will
# be either 0x76 or 0x77.

# Then the BMP280 is queried for its chip ID. This should
# return 0x58.

from machine import I2C

# Command byte to get the Chip ID
CMD_CHIP_ID = bytes([0xD0])

# Initialise I2C
i2c = I2C('X')

# Scan the I2C bus and get the BMP280 address.
L = i2c.scan()
Addr = L[0]
print('I2C slave address found:', hex(Addr))

# Query the BMP280 for its Chip ID
written = i2c.writeto(Addr, CMD_CHIP_ID)
print('Number of bytes written to BMP280:', written)
buf = i2c.readfrom(Addr, 1)
print('Number of bytes read from BMP280:', len(buf))
chipID = hex(buf[0])
print('Chip ID:', chipID)          
          

Output:
I2C slave address found: 0x76
Number of bytes written to BMP280: 1
Number of bytes read from BMP280: 1
Chip ID: 0x58
          
Pyboard's I2C('X') connected to the I2C interface of the BMP280 sensor.
Fig 1 - Pyboard's I2C('X') connected to the I2C interface of the BMP280 sensor

A chip ID of 0x58 was returned. According to the BMP280 datasheet this is the correct value.