Qwiic RGB LEDs
[diy
electronics
claude-light
raspberrypi
micropython
i2c
]
The QWIIC/STEMMA-QT ecosystem is kind of neat…standard connectors for I2C gadgets to allow you to solderlessly connect hardware to your microcontroller. But it is suprisingly difficult to find an RGB LED in this ecosystem…
Goals
-
The current claude-light relies on some PWM driven RGB LEDs attached by protoboard.
-
Our goal is to turn this into a completely solderless plug-and-play system to make it easy for others to replicate. Eventually make a 3d-printed case for the sensor, LED, and camera.
-
Use a Sparkfun QWIIC/Stemma-QT SHIM for the raspberry pi($2, also sold by Adafruit for $2.50) for the electrical connections and send information by I2C. In the Sparkfun reviews, some folks found it to be unreliable, and recommended instead just using a female jumper to Qwiic ($2) connector instead. Still admits solderless assembly approach with everything on the same digital I2C bus.
- In my testing of this, I found that the SHIM made a nice press-fit on an old Raspberry Pi 2, but was too loose to make a reliable connection on a Raspberry Pi 4b (the pins have a slightly different pitch?)
- More reliable was to use the Qwiic HAT for Raspberry Pi ($8) instead; this also removes any need for daisychaining devices
Background on standards
-
First, there are many standards for this type of plug-and-play work besides QWIIC and STEMMA-QT—two common ones are Grove and Gravity. See comparison chart. Warning: While DFRobot Gravity and STEMMA use the same shaped connectors, they are electrically incompatible (different wiring order)
-
The trick is that even the “digital” versions of RGB LED carrier boards are not necessarily I2C based…
Product landscape (01 Aug 2025)
- Sparkfun Qwiic LED Strip ($12.50) – would fit the bill, but out of stock (and has 10 LEDs)
- Sparkfun Qwiic Green LED button ($5) – only green (and is a button). Also available in red
- Modulino Pixels ($11 on Amazon) – best option 8 addressable RGB on a QWIIC-compatible substrate. (Modulino is drop-incompatible with QWIIC)
- DFRobotics I2C RGB LED Button ($10) – but note electrical incompatibility and also its a button…
- Sparkfun Qwiic RGB Rotary Encoder (Sparkfun $25) – but its really a button that happens to glow.
- M5 Stack RGB LED Unit ($5) — not what you want; has a grove connector, but uses some other 1-wire digital signal, not I2C
- Adafruit NeoRGB Stemma - NeoPixel to RGB PWM LED ($5) — not what you want, as this is a 3-pin (non-I2C) system
- Sparkfun BlinkM I2C Controlled RGB ($25, out of stock at SF, but in stock at Digikey) — almost right, but not the right connector, so you’re back to soldering. You could always attach it to a Qwiic adapter board ($2) but then you’re back to soldering.
- Zio Qwiic RGB LED APA02 (Tindie $9, but also RobotShop) — almost right, but only has 5-bit (32 value) settings for the LEDs, which makes it incompatible with the earlier claude-light development (where we want 8-bit settings).
- Seeed Grove Chainable RGB LED – uses some other type of digital signal, not I2C
- Adafruit NeoDriver I2C to Neopixel (Adafruit $7.50) — second best option This allows you to drive a (potentially big) Neopixel LED setup. We only need one, so in principle we can get away with powering it over the RPi’s STEMMA 5V connection (each pixel requires 10-30 mA of current, and our Pi can reliably give 20x that on the 5V GPIO pin). You’ll either need to add a Neopixel breakout with JST SH connectors ($1.50) (and cut devise your own cable to connect it to the terminal blacok) or buy a Neopixel button PCB ($5/5) and solder on wires (which gets us back into soldering territory again)
- IO Rodeo sells a variety of single-wavelength LED sources with Stemma-QT connectors, although it looks like they are intended to just be constant on. And they are only a single wavelength.
Programming the Modulino Pixels
Let’s assume that we’re going to use the Modulino and want to connect it to a Raspberry Pi. How do we control it?
- Enable I2C on the Raspberry Pi and
sudo apt-get install -y i2c-tools
- Use
i2c-tools
to scan the bus and confirm connected devices - Either Use
py-smbus
to programmatically interact with the i2c devices from Raspberry Pi- and then read the datasheet and micropython code and just code up the raw py-smbus calls.
- OR use blinka to use premade circuitpython libraries on the Raspberry Pi. This is probably preferable, as the existing claude-light code uses blinka to interface with the spectral sensor
- and then translate existing Modulino micropython code to circuitpython
- read up on i2c control in CircuitPython
Under the hood of the Modulino Pixels
It’s easier to understand what is happening by reading the C code than the micropython code:
- Each LED has a setting of R / G/ B/ Brightness. R, G, B are uint8 values (0-255, of 0x00-0xFF), sent in that order. Brightness is only 5 bits (0-31 or 0x00-0x1F). It’s unspecified how final global brighness is implemented under the hood, but my guess is that it just scales the PWM values again.
- We pad out the remaining preceding (highest) 3-bits in the last byte with 0xE0 (11100000)
- In practice this looks like:
r << 24 | g<<16| b << 8 | brightness | 0xE0
- Store an array of
NUM_LED
uints32 (i.e.,NUM_LED * 4
uint8s) containing the state of all pixels - A show() command just writes all 8x4 bytes down the I2C command.
- But device expects these send in little-endian order, whereas python’s
bytearray
is naturally big-endian (at least on Raspberry Pi), so you can just send these asbytearray([ brightness|0xE0, b ,g,r]*NUM_LEDS)
- By default (unless you reprogram it), the Modulino Pixels is I2C device ID
0x36
Minimal Circuit Python / Blinka Sample Code:
import board
from adafruit_bus_device.i2c_device import I2CDevice
PIXELS_ADDRESS = 0x36
NUM_LEDS = 8
i2c = board.I2C()
print("found i2c devices:", [hex(x) for x in i2c.scan()])
pixels = I2CDevice(i2c, PIXELS_ADDRESS)
# simply cycle around
with pixels:
while True:
clear_all = bytearray([0xE0, 0x00, 0x00, 0x00] * NUM_LEDS )
print("clear")
pixels.write(clear_all)
input("Press Enter to continue...")
red = bytearray([0x1F | 0xE0, 0x00, 0x00, 0xFF]*NUM_LEDS )
print("red")
pixels.write(red)
input("Press Enter to continue...")
green = bytearray([ 0x1F | 0xE0, 0x00, 0xFF, 0x00]*NUM_LEDS)
print("green")
pixels.write(green)
input("Press Enter to continue...")
blue = bytearray([0x1F | 0xE0, 0xFF, 0x00, 0x00]*NUM_LEDS )
pixels.write(blue)
input("Press Enter to continue...")
print("done")