I2C is a 2-wire protocol for communicating with simple sensors and devices, meaning it uses two connections for transmitting and receiving data. There are many I2C devices available and they're really easy to use with CircuitPython. We have libraries available for many I2C devices in the library bundle. (If you don't see the sensor you're looking for, keep checking back, more are being written all the time!)
In this section, we're going to do is learn how to scan the I2C bus for all connected devices. Then we're going to learn how to interact with an I2C device.
We'll be using the Adafruit TSL2591, a common, low-cost light sensor. While the exact code we're running is specific to the TSL2591 the overall process is the same for just about any I2C sensor or device.
These examples will use the TSL2591 lux sensor breakout. The first thing you'll want to do is get the sensor connected so your board has I2C to talk to.
Wire It UpYou'll need a couple of things to connect the TSL2591 to your board. The TSL2591 comes with STEMMA QT / QWIIC connectors on it, which makes it super simple to wire it up. No further soldering required!
For Gemma M0, Circuit Playground Express and Circuit Playground Bluefruit, you can use use the STEMMA QT to alligator clips cable to connect to the TSL2591.
For Trinket M0, Feather M0 and M4 Express, Metro M0 and M4 Express and ItsyBitsy M0 and M4 Express, you'll need a breadboard and STEMMA QT to male jumper wires cable to connect to the TSL2591.
For QT Py M0, you'll need a STEMMA QT cable to connect to the TSL2591.
We've included diagrams show you how to connect the TSL2591 to your board. In these diagrams, the wire colors match the STEMMA QT cables and connect to the same pins on each board.
Check out the list below for a diagram of your specific board!
Be aware that the Adafruit microcontroller boards do not have I2C pullup resistors built in! All of the Adafruit breakouts do, but if you're building your own board or using a non-Adafruit breakout, you must add 2.2K-10K ohm pullups on both SDA and SCL to the 3.3V.
Circuit Playground Express and Circuit Playground Bluefruit
Trinket M0
Gemma M0
QT Py M0
If using the STEMMA QT cable:
Alternatively, if using a breadboard:
Feather M0 Express and Feather M4 Express
ItsyBitsy M0 Express and ItsyBitsy M4 Express
Metro M0 Express and Metro M4 Express
The first thing you'll want to do after getting the sensor wired up, is make sure it's wired correctly. We're going to do an I2C scan to see if the board is detected, and if it is, print out its I2C address.
In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory CircuitPython_Essentials/CircuitPython_I2C_Scan/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.
Your CIRCUITPY drive should now look similar to the following image:
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries # # SPDX-License-Identifier: MIT """CircuitPython I2C Device Address Scan""" # If you run this and it seems to hang, try manually unlocking # your I2C bus from the REPL with # >>> import board # >>> board.I2C().unlock() import time import board # To use default I2C bus (most boards) i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller # To create I2C bus on specific pins # import busio # i2c = busio.I2C(board.SCL1, board.SDA1) # QT Py RP2040 STEMMA connector # i2c = busio.I2C(board.GP1, board.GP0) # Pi Pico RP2040 while not i2c.try_lock(): pass try: while True: print( "I2C addresses found:", [hex(device_address) for device_address in i2c.scan()], ) time.sleep(2) finally: # unlock the i2c bus when ctrl-c'ing out of the loop i2c.unlock()
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries # # SPDX-License-Identifier: MIT """CircuitPython I2C Device Address Scan""" # If you run this and it seems to hang, try manually unlocking # your I2C bus from the REPL with # >>> import board # >>> board.I2C().unlock() import time import board # To use default I2C bus (most boards) i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller # To create I2C bus on specific pins # import busio # i2c = busio.I2C(board.SCL1, board.SDA1) # QT Py RP2040 STEMMA connector # i2c = busio.I2C(board.GP1, board.GP0) # Pi Pico RP2040 while not i2c.try_lock(): pass try: while True: print( "I2C addresses found:", [hex(device_address) for device_address in i2c.scan()], ) time.sleep(2) finally: # unlock the i2c bus when ctrl-c'ing out of the loop i2c.unlock()
First we create the i2c
object, using board.I2C()
. This convenience routine creates and saves a busio.I2C
object using the default pins board.SCL
and board.SDA
. If the object has already been created, then the existing object is returned. No matter how many times you call board.I2C()
, it will return the same object. This is called a singleton.
To be able to scan it, we need to lock the I2C down so the only thing accessing it is the code. So next we include a loop that waits until I2C is locked and then continues on to the scan function.
Last, we have the loop that runs the actual scan, i2c_scan()
. Because I2C typically refers to addresses in hex form, we've included this bit of code that formats the results into hex format: [hex(device_address) for device_address in i2c.scan()]
.
Open the serial console to see the results! The code prints out an array of addresses. We've connected the TSL2591 which has a 7-bit I2C address of 0x29. The result for this sensor is I2C addresses found: ['0x29']
. If no addresses are returned, refer back to the wiring diagrams to make sure you've wired up your sensor correctly.
Now we know for certain that our sensor is connected and ready to go. Let's find out how to get the data from our sensor!
Installing Project CodeTo use with CircuitPython, you need to first install a few libraries, into the lib folder on your CIRCUITPY drive. Then you need to update code.py with the example script.
Thankfully, we can do this in one go. In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory CircuitPython_Essentials/CircuitPython_I2C_TSL2591/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.
Your CIRCUITPY drive should now look similar to the following image:
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries # # SPDX-License-Identifier: MIT """CircuitPython Essentials I2C sensor example using TSL2591""" import time import board import adafruit_tsl2591 i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller # Lock the I2C device before we try to scan while not i2c.try_lock(): pass # Print the addresses found once print("I2C addresses found:", [hex(device_address) for device_address in i2c.scan()]) # Unlock I2C now that we're done scanning. i2c.unlock() # Create library object on our I2C port tsl2591 = adafruit_tsl2591.TSL2591(i2c) # Use the object to print the sensor readings while True: print("Lux:", tsl2591.lux) time.sleep(0.5)
# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries # # SPDX-License-Identifier: MIT """CircuitPython Essentials I2C sensor example using TSL2591""" import time import board import adafruit_tsl2591 i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller # Lock the I2C device before we try to scan while not i2c.try_lock(): pass # Print the addresses found once print("I2C addresses found:", [hex(device_address) for device_address in i2c.scan()]) # Unlock I2C now that we're done scanning. i2c.unlock() # Create library object on our I2C port tsl2591 = adafruit_tsl2591.TSL2591(i2c) # Use the object to print the sensor readings while True: print("Lux:", tsl2591.lux) time.sleep(0.5)
This code begins the same way as the scan code. We've included the scan code so you have verification that your sensor is wired up correctly and is detected. It prints the address once. After the scan, we unlock I2C with i2c_unlock()
so we can use the sensor for data.
We create our sensor object using the sensor library. We call it tsl2591
and provide it the i2c
object.
Then we have a simple loop that prints out the lux reading using the sensor object we created. We add a time.sleep(1.0)
, so it only prints once per second. Connect to the serial console to see the results. Try shining a light on it to see the results change!
On the SAMD21, SAMD51 and nRF52840, we have the flexibility of using a wide range of pins for I2C. On the nRF52840, any pin can be used for I2C! Some chips, like the ESP8266, require using bitbangio, but can also use any pins for I2C. There's some other chips that may have fixed I2C pin.
The good news is you can use many but not all pins. Given the large number of SAMD boards we have, its impossible to guarantee anything other than the labeled 'SDA' and 'SCL'. So, if you want some other setup, or multiple I2C interfaces, how will you find those pins? Easy! We've written a handy script.
These are the results from an ItsyBitsy M0 Express. Your output may vary and it might be very long. For more details about I2C and SERCOMs, check out our detailed guide here.
In the example below, click the Download Project Bundle button below to download the necessary libraries and the code.py file in a zip file. Extract the contents of the zip file, open the directory CircuitPython_Essentials/I2C_Test_Script/ and then click on the directory that matches the version of CircuitPython you're using and copy the contents of that directory to your CIRCUITPY drive.
Your CIRCUITPY drive should now look similar to the following image:
# SPDX-FileCopyrightText: 2018 Kattni Rembor for Adafruit Industries # # SPDX-License-Identifier: MIT """CircuitPython Essentials I2C possible pin-pair identifying script""" import board import busio from microcontroller import Pin def is_hardware_I2C(scl, sda): try: p = busio.I2C(scl, sda) p.deinit() return True except ValueError: return False except RuntimeError: return True def get_unique_pins(): exclude = ['NEOPIXEL', 'APA102_MOSI', 'APA102_SCK'] pins = [pin for pin in [ getattr(board, p) for p in dir(board) if p not in exclude] if isinstance(pin, Pin)] unique = [] for p in pins: if p not in unique: unique.append(p) return unique for scl_pin in get_unique_pins(): for sda_pin in get_unique_pins(): if scl_pin is sda_pin: continue if is_hardware_I2C(scl_pin, sda_pin): print("SCL pin:", scl_pin, "\t SDA pin:", sda_pin)
# SPDX-FileCopyrightText: 2018 Kattni Rembor for Adafruit Industries # # SPDX-License-Identifier: MIT """CircuitPython Essentials I2C possible pin-pair identifying script""" import board import busio from microcontroller import Pin def is_hardware_I2C(scl, sda): try: p = busio.I2C(scl, sda) p.deinit() return True except ValueError: return False except RuntimeError: return True def get_unique_pins(): exclude = ['NEOPIXEL', 'APA102_MOSI', 'APA102_SCK'] pins = [pin for pin in [ getattr(board, p) for p in dir(board) if p not in exclude] if isinstance(pin, Pin)] unique = [] for p in pins: if p not in unique: unique.append(p) return unique for scl_pin in get_unique_pins(): for sda_pin in get_unique_pins(): if scl_pin is sda_pin: continue if is_hardware_I2C(scl_pin, sda_pin): print("SCL pin:", scl_pin, "\t SDA pin:", sda_pin)
Page last edited January 22, 2025
Text editor powered by tinymce.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4