Programming Tutorial
The UNO Q is unlike any Arduino before it. It combines a full Debian Linux computer with a real-time microcontroller — all on a single board. This tutorial teaches you how to program both sides using Python and C++.
// 00 — The Board
The Arduino UNO Q (released late 2025) is not a classic microcontroller board. It packs two completely separate processors on one board, connected internally through a software bridge. Understanding this "dual-brain" architecture is the first and most important concept in UNO Q programming.
Brain #1 — The Computer
Qualcomm Dragonwing™ QRB2210
MPU · Runs Debian Linux
Brain #2 — The Controller
STMicroelectronics STM32U585
MCU · Runs Zephyr + Arduino Core
Mental model: Think of the UNO Q as a Raspberry Pi (MPU) and an Arduino Uno (MCU) soldered onto the same board and able to talk to each other. Python lives on the Pi side. C++ sketches live on the Arduino side. The Bridge library lets them call each other's functions.
| Mode | Tool | What Gets Programmed | Best For |
|---|---|---|---|
| App Lab (recommended) | Arduino App Lab IDE | Both MPU (Python) + MCU (C++) together | Full projects, beginners, AI features |
| PC-connected IDE | Arduino IDE 2.x via USB | MCU only (C++ sketch) | Classic Arduino workflows |
| Headless Linux | SSH + CLI (VS Code, vim…) | MPU only (Python / any language) | Servers, AI, networking |
// 01 — Setup
STEP 01
Power Up the Board
Connect the UNO Q to your computer using a USB-C cable. This single cable handles power, programming, HDMI video output, and USB peripherals (via a dongle). A 5 V / 3 A power supply ensures stability when using multiple peripherals.
The first boot takes 1–2 minutes as the Debian OS initialises. The 8×13 LED matrix will display a startup animation — wait for it to settle before connecting from App Lab.
STEP 02
Install Arduino App Lab (Recommended IDE)
App Lab is the official IDE for the UNO Q. It lets you write Python (MPU side) and C++ sketches (MCU side) in one project, and deploy both with a single click. Download it at app.arduino.cc — compatible with Windows 10+, macOS 11+, and Ubuntu 22.04+. It also comes pre-installed on the UNO Q's own Debian OS if you use it as a standalone computer.
Prefer classic Arduino IDE 2.x? That still works — but it can only program the MCU side (C++ sketches). You will not be able to deploy Python to the MPU from Arduino IDE.
STEP 03
Select Your Board in App Lab
Open App Lab → New Project → choose Arduino UNO Q from the board selector. App Lab will auto-detect the board over USB and show both the MPU and MCU subsystems in the project explorer.
# The UNO Q runs an SSH server on its Debian OS by default. # Connect your PC and UNO Q to the same Wi-Fi network, then: ssh arduino@arduino-uno-q.local # Once in, verify the Linux environment: uname -a # Linux arduino-uno-q 6.x.x #1 SMP aarch64 GNU/Linux python3 --version # Python 3.11.x
STEP 04
Understand the Project Structure in App Lab
A UNO Q project in App Lab contains three folders:
| Folder | Runs On | Language | Purpose |
|---|---|---|---|
sketch/ | STM32 MCU | C++ (.ino) | Real-time GPIO, sensors, motors |
app/ | Qualcomm MPU | Python (.py) | AI, networking, logic, UI |
bricks/ | Both | YAML config | Pre-built feature modules (Bricks) |
// 02 — C++ Sketch (STM32 MCU)
The MCU runs your C++ sketch via the Arduino Core on Zephyr OS.
Every sketch you already know works here — pinMode(), digitalWrite(),
analogRead() — but now the STM32U585 is much more capable than a classic Uno's ATmega328P.
Key difference from classic Uno: The sketch runs on the MCU subsystem — the STM32 chip. The GPIO headers (D0–D13, A0–A5) are connected to this MCU, NOT to the Qualcomm MPU. Always write pin control code in C++, not Python.
Every sketch has the same two required functions — nothing has changed here from classic Arduino:
// sketch/sketch.ino // This file runs on the STM32U585 MCU void setup() { // Runs ONCE at power-on or reset. // Configure pin modes, start Serial, initialise sensors. } void loop() { // Runs FOREVER in a tight loop. // Read sensors, drive outputs, respond to events. }
The UNO Q has a built-in LED on pin 13 (connected to the MCU) plus an 8×13 LED matrix driven by the MPU side. Start with pin 13 to confirm your sketch uploads correctly.
// Blink built-in LED — confirms MCU sketch is running const int LED = 13; // D13 → MCU GPIO void setup() { pinMode(LED, OUTPUT); Serial.begin(115200); // higher baud than classic Uno — STM32 is faster Serial.println("UNO Q MCU ready"); } void loop() { digitalWrite(LED, HIGH); delay(500); digitalWrite(LED, LOW); delay(500); }
Wire a potentiometer (or any 0–5 V analog source) to pin A0. The STM32U585 has a 12-bit ADC — values range from 0 to 4095 (vs. 0–1023 on classic Uno).
// Read A0 (12-bit: 0–4095) and print to Serial const int SENSOR = A0; void setup() { analogReadResolution(12); // enable 12-bit mode (UNO Q specific!) Serial.begin(115200); } void loop() { int raw = analogRead(SENSOR); float volts = raw * (5.0 / 4095.0); Serial.print("Raw: "); Serial.print(raw); Serial.print(" Volts: "); Serial.println(volts); delay(200); }
UNO Q gotcha: Always call analogReadResolution(12) in setup(). Without it, you may get 10-bit behaviour. Scale accordingly: 12-bit → divide by 4095, 10-bit → divide by 1023.
PWM is available on pins D3, D5, D6, D9, D10, D11 — the same pins marked with ~ on the UNO headers.
// PWM fade on D9 — 0 to 255 and back const int PWM_PIN = 9; void setup() { pinMode(PWM_PIN, OUTPUT); } void loop() { // Fade in for (int i = 0; i <= 255; i++) { analogWrite(PWM_PIN, i); delay(8); } // Fade out for (int i = 255; i >= 0; i--) { analogWrite(PWM_PIN, i); delay(8); } }
| Pin | Mode | Notes |
|---|---|---|
| D0 (RX) / D1 (TX) | Digital / UART | Serial communication — avoid for GPIO if using Serial.print |
| D2 | Digital / Interrupt | INT0 external interrupt |
| D3~ | Digital / PWM / Interrupt | INT1 + 8-bit PWM |
| D4 – D8 | Digital I/O | Standard GPIO, 5 V logic |
| D9~, D10~, D11~ | Digital / PWM | 8-bit PWM; D10/D11 also SPI CS/MOSI |
| D12, D13 | Digital / SPI | D13 = built-in LED + SPI CLK |
| A0 – A5 | Analog In / Digital | 12-bit ADC (0–5 V); call analogReadResolution(12) |
| SDA (A4) / SCL (A5) | I²C | Use Wire library for I²C sensors |
// 03 — Python (Qualcomm MPU)
The MPU runs full Debian Linux with Python 3 pre-installed. You write Python in the
app/main.py file inside your App Lab project.
This side handles everything high-level: networking, file I/O, AI models,
web servers, computer vision — anything you'd do on a Linux PC.
# app/main.py — runs on the Qualcomm MPU (Debian Linux) import time print("Hello from UNO Q Linux side!") print(f"Running on Debian, Python {__import__('sys').version}") while True: print("MPU is alive...") time.sleep(1)
The UNO Q has an 8-row × 13-column LED matrix and 4 RGB LEDs that are connected to the
MPU side (not the MCU). Control them in Python using the
arduinoio library (pre-installed on the board).
LIVE DEMO — 8×13 LED Matrix (click to toggle)
# Control the 8x13 LED matrix from Python (MPU side) # Uses the arduinoio library pre-installed on Debian from arduinoio import LEDMatrix import time matrix = LEDMatrix() # Turn on a single LED at row 0, column 0 matrix.set_pixel(0, 0, True) # Write text scrolling across the matrix matrix.print_text("UNO Q") # Draw a pattern using a 2D list (8 rows x 13 cols) pattern = [ [1,0,1,0,1,0,1,0,1,0,1,0,1], # row 0 [0,1,0,1,0,1,0,1,0,1,0,1,0], # row 1 # ... rows 2-7 ... ] matrix.set_pattern(pattern) time.sleep(3) matrix.clear()
You cannot call analogRead() directly from Python — that function belongs to the MCU.
Instead, you use the Bridge library to ask the MCU for sensor values.
See Section 04 — Bridge for the full explanation.
# The UNO Q runs full Debian — use standard apt and pip sudo apt update sudo apt install python3-opencv # computer vision sudo apt install python3-flask # web server pip3 install numpy pandas # data science # Check what's pre-installed: pip3 list | grep arduino # arduinoio 1.x.x ← the Bridge + hardware library
// 04 — The Bridge Library
The Bridge is Arduino's RPC (Remote Procedure Call) library that lets your Python app on the MPU call functions defined in your C++ sketch on the MCU — and vice versa. This is the magic that makes the UNO Q unique.
This is a complete two-file project. First the C++ sketch exposes a function via Bridge, then Python calls it.
#include <Bridge.h> const int SENSOR_PIN = A0; // This function is called by Python through the Bridge. void getSensorValue(BridgeClient& client) { analogReadResolution(12); int raw = analogRead(SENSOR_PIN); float volts = raw * (5.0f / 4095.0f); client.println(volts, 3); // send 3-decimal float back to Python } // Register the function so Python can find it by name. BridgeServer server; void setup() { Bridge.begin(); server.begin(); server.addHandler("sensor", getSensorValue); } void loop() { server.process(); // keeps the Bridge running — must be in loop() }
from arduinoio import Bridge import time bridge = Bridge() bridge.begin() # connect to MCU over the internal bus print("Polling analog sensor from Python via Bridge...") while True: # Call the "sensor" handler registered in the C++ sketch response = bridge.call("sensor") voltage = float(response.strip()) print(f"A0 voltage: {voltage:.3f} V") if voltage > 3.5: print(" ⚡ High voltage detected — trigger Python-side alert!") time.sleep(0.5)
The Bridge pattern in one sentence: C++ sketch registers named handlers → Python calls them by name → MCU executes the GPIO operation → returns data to Python. The two processors stay in sync without any manual serial wiring.
#include <Bridge.h> const int LED = 13; void setLED(BridgeClient& client) { String state = client.readStringUntil('\n').trim(); digitalWrite(LED, (state == "1") ? HIGH : LOW); client.println("OK"); } BridgeServer server; void setup() { Bridge.begin(); pinMode(LED, OUTPUT); server.begin(); server.addHandler("led", setLED); } void loop() { server.process(); }
from arduinoio import Bridge import time bridge = Bridge() bridge.begin() print("Python blinking D13 via Bridge...") for _ in range(10): bridge.call("led", "1") # LED on time.sleep(0.4) bridge.call("led", "0") # LED off time.sleep(0.4) print("Done.")
// 05 — Projects
PROJECT A
Smart Temperature Monitor
Wire a DS18B20 or NTC thermistor to A0.
The C++ sketch reads the sensor and exposes it via Bridge.
The Python app polls it every second, logs to a CSV on the eMMC,
and hosts a live Flask web page on the local Wi-Fi network so you can see the graph from any device.
Use pandas and matplotlib on the Python side for analysis.
The UNO Q's 2 GHz quad-core makes data science libraries fast enough for real-time plotting.
PROJECT B
Object Detection with a USB Camera
Plug a USB webcam into the UNO Q USB-C port via a dongle.
Use opencv-python on the Python (MPU) side to run object detection.
When a target is detected, Python calls a Bridge function in the C++ sketch to
trigger an LED or buzzer on the GPIO pins — pure hardware response to a vision event.
import cv2 from arduinoio import Bridge bridge = Bridge(); bridge.begin() cap = cv2.VideoCapture(0) # USB webcam while True: ret, frame = cap.read() # ... run detection model ... if person_detected: bridge.call("alert", "1") # buzzer ON via MCU
PROJECT C
Qwiic / Modulino Sensor Chain
The UNO Q has a Qwiic connector that speaks I²C. Snap in Modulino nodes
(no soldering!) for buttons, distance sensors, RGB LEDs, knobs, and more.
Control them from either the Python side (via arduinoio) or the C++ sketch
via the standard Wire library.
| Modulino Node | Sensor/Actuator | Best Controlled From |
|---|---|---|
| Modulino Pixels | 8× RGB LED strip | Python (effects) or C++ (timing) |
| Modulino Buttons | 3 tactile buttons | C++ (interrupt-driven) or Python |
| Modulino Distance | VL53L4CD ToF | C++ for low-latency robotics |
| Modulino Knob | Rotary encoder + LED | C++ (precise angle tracking) |
| Modulino Thermo | HS3003 temp + humidity | Python (CSV logging) |
PROJECT D
Wi-Fi Remote Control Dashboard
Use Python's Flask to host a web app on the UNO Q's dual-band Wi-Fi.
Any browser on your network can toggle LEDs, read sensors, and stream the LED
matrix state — no extra hardware needed beyond the board itself.
from flask import Flask, jsonify, request from arduinoio import Bridge import threading app = Flask(__name__) bridge = Bridge(); bridge.begin() lock = threading.Lock() @app.route("/led", methods=["POST"]) def led(): state = request.json.get("state", "0") with lock: bridge.call("led", state) return jsonify(ok=True) @app.route("/sensor") def sensor(): with lock: v = float(bridge.call("sensor").strip()) return jsonify(voltage=v) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000) # Access at: http://arduino-uno-q.local:5000
// 06 — Quick Reference
| Task | Language | Side | Key Function / Tool |
|---|---|---|---|
| Read a pin (D2–D13) | C++ | MCU | digitalRead(pin) |
| Write a pin | C++ | MCU | digitalWrite(pin, HIGH/LOW) |
| Read analog sensor | C++ | MCU | analogRead(A0..A5) — set resolution first |
| PWM output | C++ | MCU | analogWrite(pin, 0–255) on ~pins |
| I²C sensor | C++ | MCU | #include <Wire.h> |
| Expose data to Python | C++ | MCU | Bridge.h + BridgeServer.addHandler() |
| Call MCU function | Python | MPU | bridge.call("handler_name") |
| LED matrix | Python | MPU | arduinoio.LEDMatrix |
| Wi-Fi / networking | Python | MPU | standard socket, requests, flask |
| Computer vision | Python | MPU | cv2 (OpenCV) on Debian |
| AI inference | Python | MPU | tflite / ONNX + Adreno GPU |
| Deploy full project | Both | Both | Arduino App Lab → Run button |
| Mistake | Why it Fails | Fix |
|---|---|---|
Calling analogRead() in Python | Python runs on MPU; GPIO is MCU-only | Use Bridge to ask MCU for the value |
Forgetting analogReadResolution(12) | May default to 10-bit, wrong scale | Add to setup() in every sketch |
| Opening Serial Monitor in Arduino IDE while App Lab is running | Port conflict; both try to own the port | Use only one tool at a time |
Forgetting server.process() in loop() | Bridge never receives Python calls | Always put it in the loop |
| Expecting Wi-Fi from the MCU sketch | Wi-Fi is on the MPU side | All wireless code goes in Python |
Using delay() in loop with Bridge active | delay() blocks server.process() | Use millis()-based non-blocking timing |