GPIO & hardware hacking
on your uConsole.
From blinking an LED to reading environmental sensors over I2C — everything you need to turn your uConsole into a portable hardware hacking station.
GPIO pinout for the uConsole.
easyThe ClockworkPi uConsole exposes a standard 40-pin GPIO header via the CM4 extension board. This is the same pinout as a Raspberry Pi — meaning most Pi-compatible HATs, sensors, and breakout boards will work. The header sits on the back of the device, accessible when the rear panel is removed.
⚠ CRITICAL: All GPIO pins operate at 3.3V logic levels. Applying 5V to any GPIO pin will permanently damage the CM4 module. There is no 5V tolerance — use a logic level converter if interfacing with 5V devices.
The uConsole uses BCM numbering (Broadcom SoC channel) for GPIO identification, not the physical pin numbers on the header. Both are shown in the diagram below. When writing code with libgpiod, you'll reference the BCM numbers.
Safe-to-use pins:
Not all 26 GPIO pins are free to use. The uConsole's internal display and keyboard controller may reserve certain lines depending on your firmware version. Here's what's generally safe:
GPIO 4, 5, 6, 12, 13, 16, 17, 22, 23, 24, 25, 26, 27
GPIO 2 (SDA), GPIO 3 (SCL)
GPIO 8, 9, 10, 11 (SPI0) — GPIO 7 (CE1)
GPIO 0, 1 (EEPROM), GPIO 14, 15 (UART — console)
GPIO 14/15 are typically used for the serial console on the uConsole. If you disable console output on UART, they become available — but you'll lose serial debugging. Pins 0 and 1 are reserved for HAT EEPROM identification and should never be used for general I/O.
Enabling GPIO on the uConsole.
easyBefore you can read or write GPIO pins, you need to ensure the right kernel overlays are loaded and your user has the correct permissions. The uConsole ships with most GPIO support built into the kernel, but some bus interfaces (I2C, SPI) require explicit activation.
raspi-gpio vs libgpiod:
There are two main approaches to GPIO on Linux:
The old /sys/class/gpio interface and RPi.GPIO Python library. Deprecated since Linux 4.8. Still works, but has race conditions, no event handling, and is being removed from newer kernels.
The character device interface /dev/gpiochip*. Proper kernel-level locking, edge detection, event timestamps, and a clean Python API. This is what you should use.
Step-by-step setup:
Install libgpiod and Python bindings
$ sudo apt update $ sudo apt install -y gpiod libgpiod-dev python3-libgpiod # Verify installation $ gpiodetect gpiochip0 [pinctrl-bcm2711] (58 lines) gpiochip1 [raspberrypi-exp-gpio] (8 lines)
Add your user to the gpio group
$ sudo usermod -aG gpio $USER # Log out and back in, then verify $ groups cpi adm dialout cdrom sudo audio video plugdev games users gpio i2c spi
Without this, you'll need sudo for every GPIO operation. Adding yourself to the gpio, i2c, and spi groups is recommended.
Configure kernel overlays
Edit /boot/config.txt to enable the bus interfaces you need. GPIO pins work out of the box, but I2C and SPI require overlays.
# Enable I2C bus 1 (GPIO 2/3) dtparam=i2c_arm=on # Enable SPI bus 0 (GPIO 7-11) dtparam=spi=on # Optional: enable hardware PWM on GPIO 12/13 dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4 # Optional: disable serial console to free GPIO 14/15 # enable_uart=0
Reboot and verify
$ sudo reboot # After reboot, check available GPIO chips $ gpiodetect gpiochip0 [pinctrl-bcm2711] (58 lines) gpiochip1 [raspberrypi-exp-gpio] (8 lines) # List all lines and their current state $ gpioinfo gpiochip0 | head -20 line 0: "ID_SDA" unused input active-high line 1: "ID_SCL" unused input active-high line 2: "SDA1" unused input active-high line 3: "SCL1" unused input active-high line 4: "GPIO_GCLK" unused input active-high line 5: "GPIO5" unused input active-high ... # Quick test: read a pin value $ gpioget gpiochip0 17 0
✓ TIP: If gpiodetect shows gpiochip0 with 58 lines, your GPIO is working. If you see fewer lines or errors, double-check your kernel version and config.txt overlays.
LED blink — hello world for hardware.
easyThe blinking LED is the hardware equivalent of print("Hello, World!"). It confirms your GPIO is working, your wiring is correct, and your software stack is properly configured. If you can blink an LED, you can control motors, read sensors, and drive displays.
Parts needed:
Wiring:
uConsole GPIO Header (back of device)
┌─────────────────────────────────────────────┐
│ │
│ Pin 11 (GPIO 17) ──── 330Ω ──── LED(+) │
│ resistor anode │
│ │ │
│ Pin 9 (GND) ──────────────── LED(-) │
│ cathode │
│ │
└─────────────────────────────────────────────┘
Physical connections:
1. GPIO 17 (physical pin 11) → one leg of 330Ω resistor
2. Other leg of resistor → LED anode (longer leg)
3. LED cathode (shorter leg, flat side) → GND (physical pin 9)
┌──────┐ ┌─────┐ ┌─────┐
│GPIO17├────┤330Ω ├────┤ LED ├────┐
│Pin 11│ │ │ │ (+) │ │
└──────┘ └─────┘ └──┬──┘ │
│ │
┌──────┐ │ │
│ GND ├──────────────────┘ │
│Pin 9 │ (cathode ─) │
└──────┘ │
│
Current flow: GPIO17 → R → LED → GND⚠ IMPORTANT: Always use a current-limiting resistor. GPIO pins on the CM4 source a maximum of ~16mA per pin. A standard LED with no resistor will draw too much current and could damage the pin. 330Ω at 3.3V gives you ~10mA — plenty bright and safe.
Python script:
#!/usr/bin/env python3
"""LED blink on uConsole GPIO17 using libgpiod."""
import gpiod
import time
import sys
# Configuration
CHIP = "gpiochip0"
LED_PIN = 17 # BCM numbering
BLINK_INTERVAL = 0.5 # seconds
def main():
# Open the GPIO chip
chip = gpiod.Chip(CHIP)
# Request the LED pin as output, default LOW
config = gpiod.LineSettings(
direction=gpiod.line.Direction.OUTPUT,
output_value=gpiod.line.Value.INACTIVE,
)
request = chip.request_lines(
consumer="led-blink",
config={LED_PIN: config},
)
print(f"Blinking LED on GPIO {LED_PIN}...")
print("Press Ctrl+C to stop.")
try:
while True:
# Turn LED ON
request.set_value(LED_PIN, gpiod.line.Value.ACTIVE)
time.sleep(BLINK_INTERVAL)
# Turn LED OFF
request.set_value(LED_PIN, gpiod.line.Value.INACTIVE)
time.sleep(BLINK_INTERVAL)
except KeyboardInterrupt:
print("\nStopped.")
finally:
# Release the line
request.release()
chip.close()
if __name__ == "__main__":
main()Save the script and run it
$ python3 blink.py Blinking LED on GPIO 17... Press Ctrl+C to stop.
Troubleshooting:
Check polarity — the longer leg (anode) goes toward GPIO, shorter leg (cathode) toward GND. Try flipping the LED.
Ensure you're in the gpio group: `groups | grep gpio`. If not, run `sudo usermod -aG gpio $USER` and log out/in.
Install with: `sudo apt install python3-libgpiod`. Don't use `pip install gpiod` — that's a different, incompatible package.
Your resistor value may be too high. Try 220Ω instead of 330Ω. Also check your LED — some colors (blue, white) need higher forward voltage.
Reading sensors over I2C.
moderateI2C (Inter-Integrated Circuit) is a two-wire serial bus that lets you connect multiple sensors, displays, and peripherals using just two GPIO pins: SDA (data) and SCL (clock). It's the easiest way to add environmental sensors to your uConsole — and the most popular protocol for hobbyist electronics.
We'll use the BME280 as our example sensor — it reads temperature, humidity, and barometric pressure in a single tiny package. It's cheap (~$3-5), well-documented, and runs at 3.3V. Perfect for the uConsole.
Enable I2C:
Add the I2C overlay to config.txt
# Enable I2C bus 1 dtparam=i2c_arm=on # Optional: increase bus speed from 100kHz to 400kHz # dtparam=i2c_arm_baudrate=400000
Install I2C tools and reboot
$ sudo apt install -y i2c-tools python3-smbus2 $ sudo reboot
Wiring the BME280:
BME280 Sensor uConsole GPIO Header ┌──────────┐ ┌──────────────────┐ │ VCC ├──────────┤ Pin 1 (3.3V) │ │ GND ├──────────┤ Pin 9 (GND) │ │ SDA ├──────────┤ Pin 3 (GPIO 2) │ │ SCL ├──────────┤ Pin 5 (GPIO 3) │ │ CSB ├── NC │ │ │ SDO ├── GND* │ │ └──────────┘ └──────────────────┘ * SDO pin sets I2C address: - SDO → GND = address 0x76 (default) - SDO → VCC = address 0x77 Only 4 wires needed: VCC, GND, SDA, SCL
Detect the sensor with i2cdetect
$ i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --
# 0x76 = BME280 detected! ✓If you see -- everywhere, check your wiring. The most common mistake is swapping SDA and SCL.
Read sensor data with Python
$ pip3 install smbus2 RPi.bme280
#!/usr/bin/env python3
"""Read BME280 sensor data on uConsole via I2C."""
import smbus2
import bme280
import time
# I2C bus 1, BME280 at address 0x76
I2C_PORT = 1
BME280_ADDR = 0x76
def main():
bus = smbus2.SMBus(I2C_PORT)
# Load calibration data
calibration = bme280.load_calibration_params(bus, BME280_ADDR)
print("BME280 Sensor — uConsole I2C Demo")
print("─" * 40)
print("Press Ctrl+C to stop.\n")
try:
while True:
data = bme280.sample(bus, BME280_ADDR, calibration)
print(f" Temperature: {data.temperature:.1f} °C")
print(f" Humidity: {data.humidity:.1f} %")
print(f" Pressure: {data.pressure:.1f} hPa")
print(f" Timestamp: {data.timestamp}")
print("─" * 40)
time.sleep(2)
except KeyboardInterrupt:
print("\nDone.")
finally:
bus.close()
if __name__ == "__main__":
main()Run it
$ python3 read_bme280.py BME280 Sensor — uConsole I2C Demo ──────────────────────────────────────── Temperature: 22.4 °C Humidity: 48.2 % Pressure: 1013.2 hPa Timestamp: 2026-03-07 14:32:18.442359 ──────────────────────────────────────── Temperature: 22.5 °C Humidity: 48.1 % Pressure: 1013.3 hPa ...
✓ MULTI-DEVICE: I2C supports up to 127 devices on the same two wires. You can connect a BME280, an OLED display, and a light sensor simultaneously — as long as each has a unique address. Check addresses with i2cdetect -y 1 after adding each device.
SPI displays & accessories.
moderateSPI (Serial Peripheral Interface) is faster than I2C and ideal for displays, SD cards, and high-bandwidth peripherals. The uConsole's CM4 exposes SPI0 on the GPIO header with two chip-select lines (CE0 and CE1), letting you connect up to two SPI devices simultaneously.
Enable SPI:
# Enable SPI bus 0 dtparam=spi=on # For specific SPI overlays (e.g., SPI display): # dtoverlay=spi0-1cs # Only one chip-select # dtoverlay=spi0-2cs # Two chip-selects (default)
SPI pin mapping:
| Function | GPIO (BCM) | Physical Pin | Direction |
|---|---|---|---|
| MOSI (Data Out) | GPIO 10 | 19 | Output |
| MISO (Data In) | GPIO 9 | 21 | Input |
| SCLK (Clock) | GPIO 11 | 23 | Output |
| CE0 (Chip Select 0) | GPIO 8 | 24 | Output |
| CE1 (Chip Select 1) | GPIO 7 | 26 | Output |
Example: SPI OLED display (SSD1306):
SSD1306 OLED (128x64) uConsole GPIO Header ┌──────────────┐ ┌──────────────────┐ │ VCC ├────────┤ Pin 1 (3.3V) │ │ GND ├────────┤ Pin 6 (GND) │ │ DIN (MOSI) ├────────┤ Pin 19 (GPIO 10) │ │ CLK (SCLK) ├────────┤ Pin 23 (GPIO 11) │ │ CS ├────────┤ Pin 24 (GPIO 8) │ │ DC ├────────┤ Pin 18 (GPIO 24) │ │ RST ├────────┤ Pin 22 (GPIO 25) │ └──────────────┘ └──────────────────┘
$ pip3 install luma.oled
# Quick test script
$ python3 -c "
from luma.core.interface.serial import spi
from luma.oled.device import ssd1306
from luma.core.render import canvas
serial = spi(device=0, port=0, gpio_DC=24, gpio_RST=25)
device = ssd1306(serial)
with canvas(device) as draw:
draw.rectangle(device.bounding_box, outline='white')
draw.text((10, 20), 'uConsole GPIO!', fill='white')
print('Display updated!')
"uConsole-specific pitfalls:
SPI is sensitive to cable length. On the uConsole's compact form factor, keep jumper wires under 15cm. Longer cables introduce noise and signal degradation, especially at higher clock speeds.
The default SPI clock is often too fast for cheap displays. Start with 1MHz and increase. Some SSD1306 modules max out at 8MHz.
An SPI display + SD card reader can draw 80-150mA combined. This is near the safe limit of the 3.3V rail. See Section 06 for power budgeting.
SPI displays need Data/Command (DC) and Reset (RST) pins beyond the standard SPI bus. Choose GPIO pins that aren't reserved. GPIO 24 and 25 work well.
Powering external hardware safely.
advancedThe uConsole runs on two 18650 lithium cells (~7.4V nominal, regulated down to 5V and 3.3V). Tapping into GPIO power for external hardware is possible — but you need to understand the current limits to avoid brownouts, instability, or damage.
⚠ WARNING: Drawing too much current from the GPIO header can cause the uConsole to crash, reboot, or damage the power regulator. Always calculate your power budget before connecting external devices.
Power budget reference:
| Rail | Voltage | Safe Draw | Notes |
|---|---|---|---|
| 3.3V (Pin 1, 17) | 3.3V | ~50mA total | Shared with CM4 peripherals |
| 5V (Pin 2, 4) | 5.0V | ~300mA* | Shared with USB ports |
| Individual GPIO | 3.3V | ~16mA per pin | Source or sink |
| Total GPIO | 3.3V | ~50mA combined | All active GPIO pins total |
* The 5V rail shares the same supply as USB. If you have USB devices plugged in, your available current drops accordingly. The internal WiFi and display also draw from the 5V bus.
Sample power budget:
uConsole GPIO Power Budget Calculator ═══════════════════════════════════════ 3.3V Rail (max ~50mA available for GPIO): BME280 sensor : 0.3 mA (sleep) / 1.0 mA (active) SSD1306 OLED (I2C) : 10.0 mA Status LED (w/ 330Ω) : 10.0 mA ───────────────────────────────── Total 3.3V draw : 21.0 mA ✓ SAFE (42% of budget) 5V Rail (max ~300mA available via GPIO): Servo motor (SG90) : 150.0 mA (stall) NeoPixel strip (5px) : 60.0 mA (full white) ───────────────────────────────── Total 5V draw : 210.0 mA ⚠ CAUTION (70% of budget) Rules of thumb: ✓ Under 50% = comfortable headroom ⚠ 50-80% = works but monitor for brownouts ✗ Over 80% = use external power supply
When to use external power:
- • Single I2C sensor (BME280, MPU6050)
- • Small OLED display (SSD1306)
- • A few LEDs with resistors
- • Low-power logic ICs
- • GPS module (idle mode)
- • Servo motors / stepper motors
- • LED strips (NeoPixel, WS2812B)
- • TFT displays larger than 1.8"
- • Relay modules
- • Multiple sensors simultaneously
Protection circuits:
When powering external hardware, add these protection components to avoid damaging your uConsole:
Place a 100nF ceramic capacitor between VCC and GND near each sensor/IC. Filters high-frequency noise that can cause I2C/SPI errors.
A larger electrolytic cap on the power rail absorbs current spikes from motors and displays during startup or mode changes.
Place inline on external power supply to prevent reverse current flow into the uConsole if both power sources are connected simultaneously.
Inline on the 3.3V line — if something shorts, the fuse trips and resets when the fault is cleared. Much cheaper than a new CM4 module.
Recommended accessories.
easyYou don't need much to start hardware hacking with the uConsole. Here's the gear we recommend — tested for compatibility and sized for the uConsole's portable form factor.
Breakout & Breadboard
- ›40-pin GPIO ribbon cable + T-cobbler breakout
- ›Half-size breadboard (fits in uConsole carry case)
- ›Male-to-female jumper wires (20cm, 40-pack)
Logic Level Converters
- ›Bi-directional 3.3V ↔ 5V converter (4-channel)
- ›BSS138-based modules (I2C safe)
- ›TXB0108 for SPI (8-channel, auto-direction)
USB-to-GPIO Alternatives
- ›Adafruit FT232H — USB to GPIO/SPI/I2C
- ›MCP2221A — USB-C to GPIO/I2C/ADC
- ›Great when you don't want to open the case
Beginner Sensors
- ›BME280 — temp, humidity, pressure (I2C)
- ›MPU6050 — accelerometer + gyroscope (I2C)
- ›BH1750 — ambient light sensor (I2C)
Displays
- ›SSD1306 0.96" OLED (I2C or SPI)
- ›ST7789 1.3" IPS TFT (SPI, 240×240)
- ›E-ink 2.13" (SPI — great for low-power status)
Protection & Power
- ›100nF + 10µF capacitor assortment
- ›1N5817 Schottky diodes (10-pack)
- ›Resettable polyfuses (100mA, 250mA)
✓ STARTER KIT TIP: If you just want to get going, grab a BME280 sensor, a half-size breadboard, and a jumper wire pack. Total cost under $10 — and you'll have everything you need for the I2C project in Section 04.
Overclocking Guide
Squeeze more performance from CM4 & A06 for GPIO-intensive projects.
Read guide →Battery Life Guide
Manage power draw when running sensors and peripherals off GPIO.
Read guide →i3 & Sway Setup
Lightweight tiling WM — free up resources for hardware projects.
Read guide →Retro Gaming & Emulation
Turn your uConsole into a portable retro gaming machine.
Read guide →Want GPIO libraries pre-installed?
The Pocket Forge Toolkit includes libgpiod, Python bindings, I2C/SPI tools, and example scripts — all pre-configured and tested on the uConsole. Plus 30+ curated packages, an optimized desktop, and hardware-specific tuning. Flash and go in 15 minutes.
Not ready to buy?.
Join the waitlist for Pocket Forge updates — new hardware hacking guides, GPIO project templates, and early pricing. No spam, just signal.
Your email is stored securely. Unsubscribe anytime.