A CircuitPython library for the M5Stack Unit-QRCode module, a compact QR code and barcode reader controlled over I2C. Developed and tested on a Raspberry Pi Pico 2W running CircuitPython.
| Module pin (Grove) | Pico pin |
|---|---|
| Yellow — SCL | GP5 |
| White — SDA | GP4 |
| Red — 3V3 | 3V3 |
| Black — GND | GND |
Default I2C address: 0x21
The module contains an STM32F030 MCU and supports 3 QR code types and 14 barcode types. It has a physical trigger button on the unit itself.
Copy lib/m5_unit_qrcode.py to the /lib folder on your CIRCUITPY drive.
No additional dependencies — only CircuitPython built-ins (busio, struct, time).
import board
import busio
from m5_unit_qrcode import UnitQRCode, AUTO_SCAN
i2c = busio.I2C(board.GP5, board.GP4)
scanner = UnitQRCode(i2c)
scanner.scan_mode = AUTO_SCAN
while True:
if scanner.ready:
result = scanner.read()
if result:
print("Scanned:", result)UnitQRCode(i2c, address=0x21)scanner.scan_mode = AUTO_SCAN # continuous scanning (default)
scanner.scan_mode = MANUAL_SCAN # scan only when triggeredscanner.ready # True when a decoded result is waiting
scanner.read() # returns decoded string, or None
scanner.decode_length # byte length of the last decoded payload
scanner.clear_status() # discard current result so the module scans againIn MANUAL_SCAN mode, a scan can be started from code or by pressing the physical button on the module.
scanner.trigger(start=True) # start scanning
scanner.trigger(start=False) # stop scanning
scanner.trigger_key # 0 = module button pressed, 1 = not pressedscanner.firmware_version # firmware version byte
scanner.i2c_address # current I2C address
scanner.i2c_address = 0x22 # change I2C address (persists across power cycles)
scanner.jump_to_bootloader() # enter STM32 DFU for firmware updates| File | Description |
|---|---|
example_auto.py |
Continuous scanning — prints every QR code detected |
example_manual.py |
Press GP15 or the module's own button to trigger a scan |
- The STM32 sets the status flag before it has finished writing the decoded data to the length/data registers. The
read()method polls for up to 300 ms to wait for a valid length. If the length register does not stabilise in that window (which happens with certain complex QR code formats such as MATMSG), the result is discarded andNoneis returned — the module will scan again automatically. - The status register is cleared by reading the data register (0x1000), not by reading the status register itself.
- A physical power cycle of the module is sometimes needed if it has been left in a bad state by interrupted I2C transactions.
Register map (I2C address 0x21, firmware V2):
| Register | R/W | Description |
|---|---|---|
| 0x0000 | R/W | Trigger (manual mode: 1 = start, 0 = stop) |
| 0x0010 | R/W | Status (0 = not ready, 1 = data good, 2 = read again; cleared by reading 0x1000 or writing 0) |
| 0x0020 | R | Data length (uint16 little-endian) |
| 0x0030 | R/W | Scan mode (0 = auto, 1 = manual) |
| 0x0040 | R | Trigger button (0 = pressed, 1 = not pressed) |
| 0x00FE | R | Firmware version |
| 0x00FF | R/W | I2C address |
| 0x1000 | R | Decoded data (up to 512 bytes) |
Register addresses are sent little-endian (LSB first). Reads use a repeated-start transaction.
MIT — see lib/m5_unit_qrcode.py for full copyright notice.
Original Arduino library by M5Stack Technology CO LTD.
CircuitPython port by Jerry Vos.