Complete Beginner's Guide · 2025
A full guide to C++, Python, Arduino Sketches, and hardware control on the UNO Q — the board that packs a Debian Linux computer and a real-time microcontroller into one UNO-sized package.
PART 0
Before writing a single line of code, you need to understand what you are actually programming.
// The Dual-Brain Architecture
The UNO Q is not one computer — it is two processors on a single board, permanently connected by a high-speed internal bridge. This is the most important concept on the entire board. Every programming decision you make will start with the question: which brain does this belong on?
Brain #1 — The Computer
Qualcomm QRB2210 MPU
Brain #2 — The Controller
STMicroelectronics STM32U585 MCU
Golden Rule: The GPIO headers (D0–D13, A0–A5) are wired to the STM32 MCU. You can only control them from C++. Python on the MPU side must ask the MCU for sensor data via the Bridge library. Wi-Fi, the LED matrix, and AI all live on the Python/MPU side.
| Mode | IDE / Tool | Programs Which Brain | Language |
|---|---|---|---|
| App Lab (recommended) | Arduino App Lab | Both MPU + MCU together | Python + C++ |
| Classic Arduino IDE | Arduino IDE 2.x via USB | MCU only | C++ |
| Headless Linux | SSH / VS Code / terminal | MPU only | Python, any |
| Feature | Classic Arduino Uno (R3) | Arduino UNO Q |
|---|---|---|
| Processor | ATmega328P · 8-bit · 16 MHz | STM32U585 (MCU) + QRB2210 (MPU) |
| Operating System | None (bare metal) | Zephyr (MCU) + Debian Linux (MPU) |
| RAM | 2 KB SRAM | 2–4 GB LPDDR4X |
| Language on pins | C++ | C++ (pins always go through MCU) |
| High-level language | N/A | Python 3 on Debian |
| ADC Resolution | 10-bit (0–1023) | 12-bit (0–4095) — call analogReadResolution(12) |
| Wi-Fi / BT | No (Uno R3) | Yes — Wi-Fi 5 + BT 5.1 |
| Sketch serial baud | Typically 9600 | Use 115200 — STM32 is faster |
| LED Matrix | No | 8×13 LEDs + 4 RGB LEDs (MPU side) |
PART 1 — C++ Language
C++ is the language of Arduino sketches. It runs on the STM32U585 microcontroller — the real-time brain that controls all the physical pins.
// 1.1 — C++ Language Outline
C++ is a compiled, statically typed, imperative language. Every variable has a fixed type declared at compile time. You write code → the compiler translates it to machine code → the MCU runs that machine code directly. There is no interpreter, no garbage collector, and no operating system catching your mistakes. What you write is exactly what the chip executes.
Arduino sketches are a thin wrapper around C++. The IDE adds two mandatory functions —
setup() and loop() — and links in the Arduino standard library automatically.
Everything else is standard C++.
// ─── 1. Include headers (libraries you want to use) ─────────────────── #include <Wire.h> // import the I²C library #include <Bridge.h> // import the Arduino Bridge library // ─── 2. Constants and global variables ─────────────────────────────── const int LED_PIN = 13; // const = value never changes int counter = 0; // global: accessible anywhere in the file // ─── 3. setup() — runs ONCE at power-on ────────────────────────────── void setup() { pinMode(LED_PIN, OUTPUT); Serial.begin(115200); } // ─── 4. loop() — runs FOREVER until power-off ──────────────────────── void loop() { digitalWrite(LED_PIN, HIGH); delay(500); digitalWrite(LED_PIN, LOW); delay(500); } // ─── 5. Your own functions (below loop is fine) ────────────────────── void myFunction(int x) { // function body }
| Type | Size | Range / Use | Example |
|---|---|---|---|
bool | 1 byte | true or false | bool on = true; |
byte | 1 byte | 0 – 255 unsigned | byte b = 200; |
int | 2 bytes | –32,768 – 32,767 | int count = 0; |
unsigned int | 2 bytes | 0 – 65,535 | unsigned int u = 50000; |
long | 4 bytes | –2,147,483,648 – 2,147,483,647 | long t = millis(); |
unsigned long | 4 bytes | 0 – 4,294,967,295 | unsigned long ms = millis(); |
float | 4 bytes | Decimal numbers ≈ 6–7 sig. digits | float v = 3.14; |
double | 4 bytes | Same as float on most Arduinos | double d = 2.71828; |
char | 1 byte | Single ASCII character | char c = 'A'; |
String | Variable | Text (Arduino class) | String s = "Hello"; |
void | — | No value (function returns nothing) | void setup() {} |
// Declare: type name = value; int x = 10; float temperature = 23.5; String name = "UNO Q"; bool ledOn = false; // const — value cannot be changed after declaration const int MAX_VAL = 4095; // convention: UPPER_CASE for constants // Scope: variables declared outside functions are GLOBAL int globalCounter = 0; // accessible in setup(), loop(), everywhere void loop() { int localVar = 5; // LOCAL — only exists inside this function globalCounter++; // ++ means add 1 (increment) } // localVar is destroyed when loop() ends
Arithmetic
x + y // add x - y // subtract x * y // multiply x / y // divide x % y // modulo (remainder) x++ // increment (x = x+1) x-- // decrement (x = x-1)
Comparison (returns bool)
x == y // equal (two equals!) x != y // not equal x > y // greater than x < y // less than x >= y // greater or equal x <= y // less or equal
Logical
a && b // AND — both true a || b // OR — either true !a // NOT — flip boolean
Assignment
x = 5 // assign x += 3 // x = x + 3 x -= 1 // x = x - 1 x *= 2 // x = x * 2 x /= 4 // x = x / 4
int val = analogRead(A0); // read sensor: 0-4095 if (val > 3000) { // runs when val is greater than 3000 Serial.println("HIGH"); } else if (val > 1500) { Serial.println("MEDIUM"); } else { Serial.println("LOW"); }
// FOR loop — use when you know the count // for (start; keep-going-while; step-each-iteration) for (int i = 0; i < 10; i++) { Serial.println(i); // prints 0 through 9 } // WHILE loop — use when you don't know the count in advance int n = 0; while (n < 5) { Serial.println(n); n++; } // DO-WHILE — executes at least once, then checks condition do { Serial.println("runs once"); } while (false); // break — exit loop immediately for (int i = 0; i < 100; i++) { if (i == 5) break; // stops at i=5 }
// Syntax: returnType functionName( type param1, type param2 ) { body } // A function that returns nothing (void) and takes no parameters void blinkLED() { digitalWrite(13, HIGH); delay(250); digitalWrite(13, LOW); delay(250); } // A function that takes parameters and returns a value float adcToVolts(int rawADC) { return rawADC * (5.0 / 4095.0); // 12-bit UNO Q } // A function with a default parameter value void flashTimes(int pin, int times = 3) { // default: 3 flashes for (int i = 0; i < times; i++) { digitalWrite(pin, HIGH); delay(100); digitalWrite(pin, LOW); delay(100); } } void loop() { blinkLED(); // call — no arguments float v = adcToVolts(analogRead(A0)); // call — captures return flashTimes(13); // uses default times=3 flashTimes(13, 5); // overrides: flashes 5 times }
// Declare: type name[size] = { values }; int pins[4] = {9, 10, 11, 13}; // 4 LED pins float temps[3] = {22.1, 23.5, 21.8}; // Access by index (starts at 0!) digitalWrite(pins[0], HIGH); // uses pin 9 Serial.println(temps[2]); // prints 21.8 // Loop through all elements for (int i = 0; i < 4; i++) { pinMode(pins[i], OUTPUT); // set all 4 pins as output }
char cmd = 'H'; // pretend this came from Serial switch (cmd) { case 'H': digitalWrite(13, HIGH); break; // MUST break or it falls through! case 'L': digitalWrite(13, LOW); break; default: // runs if no case matches Serial.println("Unknown command"); }
| Function | What It Does | Example |
|---|---|---|
pinMode(pin, mode) | Set pin as INPUT or OUTPUT | pinMode(13, OUTPUT); |
digitalWrite(pin, val) | Write HIGH (5V) or LOW (0V) to a digital pin | digitalWrite(13, HIGH); |
digitalRead(pin) | Read digital pin state (HIGH or LOW) | int s = digitalRead(2); |
analogRead(pin) | Read analog voltage → 0–4095 (12-bit on UNO Q) | int v = analogRead(A0); |
analogWrite(pin, val) | PWM output 0–255 on ~ pins | analogWrite(9, 128); |
analogReadResolution(n) | Set ADC bits: 10 or 12 (UNO Q supports 12) | analogReadResolution(12); |
delay(ms) | Pause sketch for ms milliseconds (blocks everything) | delay(1000); |
millis() | Milliseconds since power-on (non-blocking timing) | unsigned long t = millis(); |
map(val,fL,fH,tL,tH) | Remap value from one range to another | map(raw,0,4095,0,255) |
constrain(val,lo,hi) | Clamp value within a range | constrain(val,0,255) |
Serial.begin(baud) | Start serial communication at baud rate | Serial.begin(115200); |
Serial.print(val) | Print a value (no newline) | Serial.print(temperature); |
Serial.println(val) | Print a value with newline | Serial.println("done"); |
Serial.available() | Bytes waiting in receive buffer | if(Serial.available()>0){} |
Serial.read() | Read one byte from serial | char c = Serial.read(); |
// 1.2 — Hello World
On a microcontroller there is no screen to print to — the "Hello World" is either a blinking LED or a message sent over the Serial port that you read in the IDE's Serial Monitor.
/* * hello_world.ino — C++ Hello World for Arduino UNO Q * This sketch sends "Hello, UNO Q!" over the serial port * AND blinks the built-in LED on D13. * * Upload with: App Lab or Arduino IDE 2 → Upload button * View output: App Lab Console or Arduino IDE → Serial Monitor (115200) */ const int LED = 13; // D13 is wired to the built-in LED on the STM32 MCU void setup() { pinMode(LED, OUTPUT); // configure D13 as an output Serial.begin(115200); // open serial at 115200 baud (use this on UNO Q) // Print the greeting — visible in Serial Monitor Serial.println("Hello, UNO Q!"); Serial.println("C++ sketch is running on the STM32U585 MCU."); } void loop() { digitalWrite(LED, HIGH); // LED on Serial.println("LED is ON"); delay(1000); // wait 1 second digitalWrite(LED, LOW); // LED off Serial.println("LED is OFF"); delay(1000); } // Expected Serial Monitor output: // Hello, UNO Q! // C++ sketch is running on the STM32U585 MCU. // LED is ON // LED is OFF // LED is ON ← repeats forever
How to see the output: In App Lab, open the Console panel. In Arduino IDE, click Tools → Serial Monitor, set baud to 115200.
// 1.3 — Comprehensive Beginner Program
This single sketch intentionally exercises every major C++ and Arduino concept:
variables, constants, arrays, functions, loops, conditionals, switch, Serial, analog read,
PWM, non-blocking timing with millis(), and the 12-bit ADC unique to the UNO Q.
Read the comments — every line is explained.
/* * uno_q_starter.ino * ───────────────────────────────────────────────────────────────────── * Comprehensive beginner program for the Arduino UNO Q (STM32U585 MCU). * Covers: constants, variables, arrays, loops, functions, if/else, * switch, Serial, analogRead (12-bit), analogWrite (PWM), * non-blocking timing with millis(). * * Hardware needed: * • Arduino UNO Q (built-in LED on D13 is enough) * • OPTIONAL: potentiometer on A0 (centre pin → A0, outers → 5V + GND) * • OPTIONAL: LED + 220Ω resistor on D9 (PWM output) * ───────────────────────────────────────────────────────────────────── */ // ══════════════════════════════════════════════════════════════════════ // SECTION 1: CONSTANTS — values that never change // ══════════════════════════════════════════════════════════════════════ const int LED_PIN = 13; // built-in LED (digital output) const int PWM_PIN = 9; // external LED with 220Ω resistor (PWM ~) const int SENSOR_PIN = A0; // potentiometer analog input const int ADC_MAX = 4095; // 12-bit ADC max (UNO Q STM32 feature!) const float V_REF = 5.0; // reference voltage in volts const long BLINK_MS = 500; // blink interval in milliseconds // ══════════════════════════════════════════════════════════════════════ // SECTION 2: GLOBAL VARIABLES — track state across loop() calls // ══════════════════════════════════════════════════════════════════════ unsigned long lastBlink = 0; // timestamp of last blink (millis) bool ledState = false;// current LED state int loopCount = 0; // counts how many times loop() has run // ARRAY — store the last 5 sensor readings int readings[5] = {0, 0, 0, 0, 0}; int readIndex = 0; // which slot to write next // ══════════════════════════════════════════════════════════════════════ // SECTION 3: FUNCTION DECLARATIONS (prototypes) // C++ needs to know a function exists before it is called. // Declaring them here at the top solves that. // ══════════════════════════════════════════════════════════════════════ float adcToVolts(int raw); int averageReadings(); void printSensorReport(int raw, float volts); String voltageLevel(float v); // ══════════════════════════════════════════════════════════════════════ // SECTION 4: setup() — runs ONCE at power-on or reset // ══════════════════════════════════════════════════════════════════════ void setup() { // 4a. Configure pin directions pinMode(LED_PIN, OUTPUT); pinMode(PWM_PIN, OUTPUT); // SENSOR_PIN (A0) is analog — no pinMode needed for analog read // 4b. Tell the STM32 to use its 12-bit ADC (UNO Q feature) analogReadResolution(12); // range is now 0–4095 instead of 0–1023 // 4c. Open serial communication at 115200 baud Serial.begin(115200); delay(200); // small delay so Serial is stable // 4d. Print a banner to the Serial Monitor Serial.println("╔══════════════════════════╗"); Serial.println("║ Arduino UNO Q Starter ║"); Serial.println("╚══════════════════════════╝"); Serial.println("STM32U585 MCU running at 115200 baud."); Serial.println("ADC resolution set to 12-bit (0-4095)."); Serial.println("Send 'H' to turn LED on, 'L' to turn it off."); Serial.println(); // 4e. Short startup flash — 3 quick blinks for (int i = 0; i < 3; i++) { // FOR loop: repeat 3 times digitalWrite(LED_PIN, HIGH); delay(80); digitalWrite(LED_PIN, LOW); delay(80); } } // ══════════════════════════════════════════════════════════════════════ // SECTION 5: loop() — runs forever, over and over // ══════════════════════════════════════════════════════════════════════ void loop() { loopCount++; // ── 5a. NON-BLOCKING BLINK using millis() ──────────────────────── // millis() returns milliseconds since startup (never resets to 0). // We compare the DIFFERENCE to BLINK_MS — this way we don't // freeze the whole sketch with delay() while the LED blinks. unsigned long now = millis(); if (now - lastBlink >= BLINK_MS) { lastBlink = now; // save when we last blinked ledState = !ledState; // toggle: if true→false, false→true digitalWrite(LED_PIN, ledState ? HIGH : LOW); // ternary operator } // ── 5b. ANALOG SENSOR READ ─────────────────────────────────────── int raw = analogRead(SENSOR_PIN); // 0–4095 (12-bit) float volts = adcToVolts(raw); // call our function // ── 5c. STORE IN CIRCULAR BUFFER (array with rolling index) ───── readings[readIndex] = raw; readIndex = (readIndex + 1) % 5; // % keeps index 0–4 // ── 5d. PWM — map sensor to LED brightness ─────────────────────── // map() rescales raw (0–4095) to PWM range (0–255) int brightness = map(raw, 0, ADC_MAX, 0, 255); analogWrite(PWM_PIN, brightness); // ── 5e. SERIAL COMMAND RECEIVE — check for incoming bytes ──────── if (Serial.available() > 0) { char cmd = (char) Serial.read(); switch (cmd) { // SWITCH statement case 'H': digitalWrite(LED_PIN, HIGH); Serial.println("CMD: LED forced ON"); break; case 'L': digitalWrite(LED_PIN, LOW); Serial.println("CMD: LED forced OFF"); break; case 'R': printSensorReport(raw, volts); // print full report break; default: Serial.print("Unknown command: "); Serial.println(cmd); } } // ── 5f. PRINT REPORT every 100 loops ───────────────────────────── if (loopCount % 100 == 0) { // modulo: every 100th loop printSensorReport(raw, volts); } delay(10); // short delay to avoid flooding Serial } // ══════════════════════════════════════════════════════════════════════ // SECTION 6: CUSTOM FUNCTIONS // ══════════════════════════════════════════════════════════════════════ // Convert a 12-bit ADC reading to volts (0.0 to 5.0) // Parameter: raw — the integer from analogRead() // Returns: float voltage float adcToVolts(int raw) { return (float) raw * (V_REF / (float) ADC_MAX); // (float) is a cast — forces integer division to become decimal } // Calculate the average of the last 5 readings // No parameters. Returns: int average value int averageReadings() { int sum = 0; for (int i = 0; i < 5; i++) { // loop over array sum += readings[i]; } return sum / 5; // integer division is fine for average } // Classify voltage level as a descriptive string // Parameter: v — float voltage // Returns: String label String voltageLevel(float v) { if (v > 4.0) return "HIGH"; else if (v > 2.0) return "MEDIUM"; else return "LOW"; } // Print a formatted sensor report to Serial Monitor // Parameters: raw — ADC integer, volts — float voltage void printSensorReport(int raw, float volts) { Serial.println("── Sensor Report ──"); Serial.print (" Raw ADC (12-bit): "); Serial.println(raw); Serial.print (" Voltage: "); Serial.print(volts, 3); Serial.println(" V"); Serial.print (" Level: "); Serial.println(voltageLevel(volts)); Serial.print (" 5-reading avg: "); Serial.println(averageReadings()); Serial.print (" PWM brightness: "); Serial.println(map(raw,0,4095,0,255)); Serial.print (" Loop count: "); Serial.println(loopCount); Serial.println(); }
What this teaches: constants, variables, arrays, for loops, if/else if/else, switch, function parameters, return values, non-blocking timing with millis(), ADC read + conversion, PWM output, serial input, and the map() / modulo operators — all in one runnable sketch.
PART 2 — Python Language
Python runs on the Qualcomm MPU under Debian Linux. It handles networking, AI, the LED matrix, and communicates with the C++ sketch via the Bridge library.
// 2.1 — Python Language Outline
Python is an interpreted, dynamically typed, high-level language. You do not declare
variable types — Python infers them at runtime. There is no compiler step: you run your
.py file and it executes line by line. Python emphasises readability — indentation
is not optional, it is the syntax for defining blocks.
Python vs C++ on the UNO Q: Python runs on the MPU (the Linux side). It cannot call analogRead() directly because the GPIO pins are wired to the MCU. All GPIO pin access from Python goes through the Bridge library.
# ─── 1. Imports (like #include in C++) ─────────────────────────────── import time import math from arduinoio import Bridge, LEDMatrix # from a module, import specific things # ─── 2. Constants / configuration ──────────────────────────────────── BLINK_DELAY = 0.5 # Python convention: UPPER_CASE for constants # ─── 3. Functions — defined with def ───────────────────────────────── def greet(name): print(f"Hello, {name}!") # ─── 4. Main code block ─────────────────────────────────────────────── if __name__ == "__main__": # runs only when this file is executed directly greet("UNO Q") while True: # Python's equivalent of Arduino's loop() print("Running...") time.sleep(1) # sleep in SECONDS (not ms like Arduino)
# No type keyword — Python infers the type from the value x = 10 # int y = 3.14 # float name = "Arduino" # str active = True # bool — capital T/F in Python! nothing = None # like null in other languages # You can check the type: print(type(x)) # <class 'int'> # Multiple assignment on one line: a, b, c = 1, 2, 3 # f-strings — embed variables inside strings (like String.format) voltage = 3.7 print(f"Voltage is {voltage:.2f} V") # :.2f = 2 decimal places → 3.70 V
# LIST — ordered, mutable (changeable), like an array pins = [9, 10, 11, 13] pins.append(6) # add item → [9, 10, 11, 13, 6] print(pins[0]) # 9 — index starts at 0 print(pins[-1]) # 6 — negative index = from the end print(pins[1:3]) # [10, 11] — slicing # TUPLE — ordered, IMMUTABLE (cannot change after creation) coords = (10.5, 20.3) # use () instead of [] x, y = coords # unpack into variables # DICTIONARY — key : value pairs (like a lookup table) sensor = { "name" : "temperature", "pin" : "A0", "voltage" : 3.3, "active" : True } print(sensor["name"]) # temperature sensor["voltage"] = 3.5 # update a value
Arithmetic
x + y # add x - y # subtract x * y # multiply x / y # divide (always float) x // y # integer divide (floor) x % y # modulo (remainder) x ** y # power (x to the y)
Comparison
x == y # equal x != y # not equal x > y # greater x < y # less x >= y # greater or equal x <= y # less or equal x is y # same object identity
Logical
a and b # both true a or b # either true not a # flip boolean
Membership / Augment
x in pins # is x in the list? x += 1 # x = x + 1 x -= 1 # x = x - 1 x *= 2 # x = x * 2
# Note: Python uses elif (not else if), and INDENTATION defines blocks voltage = 3.7 if voltage > 4.0: print("HIGH") elif voltage > 2.0: print("MEDIUM") else: print("LOW") # Ternary (one-liner if/else) level = "HIGH" if voltage > 4.0 else "LOW"
# FOR loop — iterate over a range or a collection for i in range(5): # i = 0, 1, 2, 3, 4 print(i) for pin in [9, 10, 11, 13]: # iterate over a list print(f"Setting pin {pin}") for i, pin in enumerate([9,10,11]): # get index and value print(f"Pin #{i}: {pin}") # WHILE loop — like Arduino's loop() when condition is True count = 0 while count < 10: print(count) count += 1 # break and continue for n in range(100): if n == 5: break # exit loop if n % 2 == 0: continue # skip even numbers print(n)
# Basic function def greet(name): print(f"Hello, {name}!") # Function with return value def adc_to_volts(raw, vref=5.0, resolution=4095): """Convert 12-bit ADC to volts. (Docstring documents the function)""" return raw * (vref / resolution) # Call the function v = adc_to_volts(2048) # uses defaults: 5.0V ref, 12-bit print(f"{v:.3f} V") # 2.502 V # *args — accept any number of positional arguments def sum_all(*numbers): return sum(numbers) # **kwargs — accept any number of keyword arguments def print_info(**data): for key, val in data.items(): print(f" {key}: {val}") print_info(board="UNO Q", pins=14, voltage=5.0)
class Sensor: """Represents a sensor connected to the Arduino UNO Q via Bridge.""" def __init__(self, name, pin): # __init__ is the constructor self.name = name # self. = instance variable self.pin = pin self.readings = [] def add_reading(self, value): self.readings.append(value) def average(self): if not self.readings: return 0 return sum(self.readings) / len(self.readings) def __repr__(self): # how to print the object return f"Sensor({self.name}, pin={self.pin})" # Create instances temp_sensor = Sensor("temperature", "A0") temp_sensor.add_reading(23.5) temp_sensor.add_reading(24.1) print(temp_sensor.average()) # 23.8
try: value = float(bridge.call("sensor").strip()) except ValueError: print("Bridge returned non-numeric data") value = 0.0 except Exception as e: print(f"Unexpected error: {e}") finally: # always runs, even if error occurred print("Sensor read attempted.")
| Function | Does | Example |
|---|---|---|
print() | Output to console | print(f"v = {v:.2f}") |
input() | Read a line from user | cmd = input(">> ") |
int(), float(), str() | Convert types | v = float("3.7") |
len() | Length of a collection | len([1,2,3]) → 3 |
range(n) | Sequence 0 to n-1 | for i in range(5): |
enumerate() | Index + value iterator | for i, v in enumerate(list): |
sum(), min(), max() | Math on iterables | sum([1,2,3]) → 6 |
sorted() | Return sorted list | sorted([3,1,2]) → [1,2,3] |
open() | Open a file | with open("log.csv","a") as f: |
time.sleep(s) | Pause n seconds (float ok) | time.sleep(0.5) |
// 2.2 — Hello World
Python's Hello World on the UNO Q prints to the App Lab console (on the MPU/Linux side) and scrolls text on the 8×13 LED matrix.
""" hello_world.py — Python Hello World for Arduino UNO Q Runs on the Qualcomm MPU (Debian Linux side). Prints to the console AND scrolls text on the 8x13 LED matrix. """ import time from arduinoio import LEDMatrix # pre-installed on UNO Q Debian # Create the LED matrix controller matrix = LEDMatrix() # Print to the console (App Lab / SSH terminal) print("Hello, UNO Q!") print("Python is running on the Qualcomm QRB2210 MPU.") print("Debian Linux — full Python 3 environment.") # Show a greeting on the onboard LED matrix matrix.print_text("HELLO") # scrolls the text across the 8x13 matrix time.sleep(3) matrix.clear() # A simple loop (like Arduino's loop() function) counter = 0 while True: counter += 1 print(f"Loop #{counter} — Hello from Python on UNO Q MPU") time.sleep(1) # Expected output: # Hello, UNO Q! # Python is running on the Qualcomm QRB2210 MPU. # Debian Linux — full Python 3 environment. # Loop #1 — Hello from Python on UNO Q MPU # Loop #2 — ...repeats every second
// 2.3 — Comprehensive Beginner Program
This program exercises every major Python concept — variables, collections, functions, classes, loops, conditionals, exceptions, file I/O, the LED matrix, and Bridge communication — in a real UNO Q context.
""" uno_q_starter.py ──────────────────────────────────────────────────────────────────── Comprehensive beginner Python program for the Arduino UNO Q MPU. Covers: variables, f-strings, lists, dicts, functions, classes, for/while loops, if/elif/else, exceptions, file I/O, the LEDMatrix API, and Bridge communication with the MCU sketch. ──────────────────────────────────────────────────────────────────── """ # ══════════════════════════════════════════════════════════════════════ # SECTION 1: IMPORTS # ══════════════════════════════════════════════════════════════════════ import time import math import csv from datetime import datetime from arduinoio import Bridge, LEDMatrix # ══════════════════════════════════════════════════════════════════════ # SECTION 2: CONSTANTS # ══════════════════════════════════════════════════════════════════════ POLL_INTERVAL = 0.5 # seconds between sensor polls LOG_FILE = "/home/arduino/sensor_log.csv" MAX_READINGS = 100 # max readings to keep in memory ALERT_VOLTAGE = 4.0 # volts — trigger LED matrix alert above this # ══════════════════════════════════════════════════════════════════════ # SECTION 3: UTILITY FUNCTIONS # ══════════════════════════════════════════════════════════════════════ def adc_to_volts(raw, vref=5.0, resolution=4095): """Convert 12-bit ADC integer (0-4095) to voltage (0.0-5.0 V).""" return raw * (vref / resolution) def voltage_level(v): """Classify a voltage into a human-readable level.""" if v > 4.0: return "HIGH" elif v > 2.0: return "MEDIUM" else: return "LOW" def running_average(data): """Return the mean of a list of numbers. Handles empty list.""" if not data: return 0.0 return sum(data) / len(data) def log_reading(timestamp, raw, voltage): """Append one sensor reading to the CSV log file.""" try: with open(LOG_FILE, "a", newline="") as f: # "a" = append mode writer = csv.writer(f) writer.writerow([timestamp, raw, f"{voltage:.3f}"]) except IOError as e: print(f" [WARNING] Could not write log: {e}") def print_banner(): """Print a startup banner using a list of strings and a for loop.""" lines = [ "╔══════════════════════════════╗", "║ UNO Q Python Starter App ║", "║ MPU: Qualcomm QRB2210 ║", "╚══════════════════════════════╝" ] for line in lines: # for loop iterating over a list print(line) # ══════════════════════════════════════════════════════════════════════ # SECTION 4: SENSOR CLASS # ══════════════════════════════════════════════════════════════════════ class AnalogSensor: """ Represents an analog sensor read from the MCU via the Bridge. Stores history and computes statistics. """ def __init__(self, name: str, bridge_cmd: str): self.name = name self.bridge_cmd = bridge_cmd # the command string to call via Bridge self.history = [] # list of raw readings self.alert_count = 0 def poll(self, bridge) -> float: """Ask the MCU for the latest reading via Bridge. Returns voltage.""" try: raw = int(bridge.call(self.bridge_cmd).strip()) volts = adc_to_volts(raw) self.history.append(volts) if len(self.history) > MAX_READINGS: self.history.pop(0) # keep the list bounded return volts except (ValueError, Exception) as e: print(f" [{self.name}] Bridge error: {e}") return 0.0 def average(self) -> float: return running_average(self.history) def summary(self) -> dict: """Return a dictionary summary of sensor stats.""" last = self.history[-1] if self.history else 0.0 return { "name" : self.name, "last_v" : round(last, 3), "avg_v" : round(self.average(), 3), "min_v" : round(min(self.history), 3) if self.history else 0, "max_v" : round(max(self.history), 3) if self.history else 0, "samples" : len(self.history), "level" : voltage_level(last) } def __repr__(self): return f"AnalogSensor('{self.name}', cmd='{self.bridge_cmd}')" # ══════════════════════════════════════════════════════════════════════ # SECTION 5: LED MATRIX DISPLAY HELPERS # ══════════════════════════════════════════════════════════════════════ def matrix_bargraph(matrix, level: float): """ Draw a horizontal bar on the 8x13 LED matrix to show level (0.0-1.0). level = 0.0 → no LEDs lit, 1.0 → all 13 columns lit. Uses a 2D list (list of lists) and nested loops. """ cols_to_light = int(level * 13) # 0-13 cols_to_light = max(0, min(13, cols_to_light)) # clamp # Build an 8-row x 13-col grid as a list of lists grid = [] for row in range(8): # 8 rows row_data = [] for col in range(13): # 13 columns # Light middle 3 rows (rows 2,3,4) for a thick bar lit = (col < cols_to_light) and (2 <= row <= 5) row_data.append(1 if lit else 0) grid.append(row_data) matrix.set_pattern(grid) # ══════════════════════════════════════════════════════════════════════ # SECTION 6: MAIN ENTRY POINT # ══════════════════════════════════════════════════════════════════════ def main(): print_banner() # 6a. Initialise hardware connections bridge = Bridge() bridge.begin() # connect to STM32 MCU matrix = LEDMatrix() # 6b. Create sensor objects sensor_a0 = AnalogSensor("Potentiometer", "sensor_a0") sensors = [sensor_a0] # a list — add more sensors here # 6c. Show startup animation on matrix matrix.print_text("READY") time.sleep(2) matrix.clear() print("\nPolling sensors. Press Ctrl+C to stop.\n") # 6d. MAIN LOOP — runs like Arduino's loop() loop_count = 0 try: while True: loop_count += 1 now = datetime.now().strftime("%H:%M:%S") # Poll all sensors for s in sensors: # iterate over sensor list voltage = s.poll(bridge) # Update LED matrix bar graph matrix_bargraph(matrix, voltage / 5.0) # normalise 0-5V→0-1 # Check alert threshold if voltage > ALERT_VOLTAGE: s.alert_count += 1 print(f" ⚡ ALERT [{now}] {s.name}: {voltage:.3f}V") matrix.print_text("HIGH") bridge.call("alert", "1") # tell MCU to flash LED # Log to CSV log_reading(now, int(voltage/5.0*4095), voltage) # Print a summary every 20 loops — shows dict iteration if loop_count % 20 == 0: print(f"\n── Summary at loop #{loop_count} ──") for s in sensors: info = s.summary() # returns a dict for key, val in info.items(): # iterate dict items print(f" {key:10}: {val}") print() time.sleep(POLL_INTERVAL) except KeyboardInterrupt: # Ctrl+C graceful shutdown print("\n\n── Shutting down gracefully ──") bridge.call("alert", "0") # turn off alert LED matrix.clear() print(f"Ran {loop_count} loops. Log saved to {LOG_FILE}") if __name__ == "__main__": main()
What this teaches: imports, constants, functions with docstrings, classes, constructors, instance variables, methods, lists, dictionaries, for + while loops, if/elif/else, exception handling (try/except/finally), file I/O (with open()), the arduinoio Bridge API, the LED matrix API, and the main-guard pattern — all in one runnable program.
PART 3 — Arduino Sketches
A sketch is the name Arduino uses for a program. On the UNO Q, sketches are C++ programs that run on the STM32U585 MCU — the real-time controller brain.
The word "sketch" comes from Arduino's philosophy of rapid, easy prototyping — like sketching an idea quickly.
But behind the scenes, a sketch is a real C++ program. The Arduino IDE adds two special functions
— setup() and loop() — which every sketch must define.
Compilation process: When you press Upload in App Lab or Arduino IDE, the IDE calls the Arduino C++ compiler (arm-none-eabi-g++), which translates your sketch into STM32 machine code. That binary is then flashed to the MCU's flash memory over USB. The MCU stores it even when powered off and runs it every time it powers on.
The millis()-based approach is the professional way to blink — delay() stops everything including Bridge communication.
const int LED = 13; const long INTERVAL = 500; // milliseconds unsigned long prevMs = 0; bool state = false; void setup() { pinMode(LED, OUTPUT); } void loop() { unsigned long now = millis(); if (now - prevMs >= INTERVAL) { prevMs = now; state = !state; digitalWrite(LED, state); } // Other code here runs freely — not blocked by the blink timing }
// Button on D2 (wire button between D2 and GND — use INPUT_PULLUP) // Debounce: ignore state changes shorter than 50 ms (bounce noise) const int BTN = 2; const int LED = 13; const int DEBOUNCE_MS = 50; int lastBtnState = HIGH; int btnState = HIGH; bool ledOn = false; unsigned long lastDebounce = 0; void setup() { pinMode(BTN, INPUT_PULLUP); // internal pull-up → reads HIGH when not pressed pinMode(LED, OUTPUT); } void loop() { int reading = digitalRead(BTN); if (reading != lastBtnState) { // state changed → restart debounce timer lastDebounce = millis(); } if ((millis() - lastDebounce) > DEBOUNCE_MS) { if (reading != btnState) { btnState = reading; if (btnState == LOW) { // LOW = button pressed (INPUT_PULLUP) ledOn = !ledOn; // toggle LED on each press digitalWrite(LED, ledOn); } } } lastBtnState = reading; }
#include <Wire.h> // I²C library — SDA=A4, SCL=A5 const int SENSOR_ADDR = 0x48; // I²C address (hexadecimal) void setup() { Wire.begin(); // start I²C as master Serial.begin(115200); } void loop() { Wire.beginTransmission(SENSOR_ADDR); Wire.write(0x00); // request register 0 (temperature) Wire.endTransmission(); Wire.requestFrom(SENSOR_ADDR, 2); // read 2 bytes if (Wire.available() >= 2) { int msb = Wire.read(); int lsb = Wire.read(); int raw = (msb << 8) | lsb; // combine bytes with bit shift float temp = raw * 0.0625; // sensor-specific formula Serial.print("Temp: "); Serial.print(temp, 2); Serial.println(" °C"); } delay(1000); }
PART 4 — The Bridge Library
The Bridge is the most powerful and unique feature of the UNO Q. It lets Python (MPU) call functions defined in the C++ sketch (MCU) — and vice versa — over a high-speed internal bus.
Python on the MPU cannot call analogRead() — that function is part of the Arduino core
running on the STM32 MCU chip. The Bridge creates a named function registry: the C++ sketch registers
handlers by name, and Python calls them by name. The internal bus handles the data transfer.
This is a two-file project — one file per brain. They work together as a single system.
#include <Bridge.h> const int LED = 13; const int SENSOR = A0; // Handler: Python calls "sensor_a0" // Reads A0 and sends raw value back. void h_sensor(BridgeClient& c) { analogReadResolution(12); int raw = analogRead(SENSOR); c.println(raw); // send to Python } // Handler: Python calls "alert" // Reads "1" or "0" → flash or clear. void h_alert(BridgeClient& c) { String v = c.readStringUntil('\n').trim(); digitalWrite(LED, (v == "1") ? HIGH : LOW); c.println("OK"); } BridgeServer server; void setup() { Bridge.begin(); pinMode(LED, OUTPUT); server.begin(); server.addHandler("sensor_a0", h_sensor); server.addHandler("alert", h_alert); } void loop() { // MUST be in loop() to keep Bridge alive server.process(); }
from arduinoio import Bridge, LEDMatrix import time bridge = Bridge() bridge.begin() matrix = LEDMatrix() print("Bridge connected. Polling A0...") while True: # Call the MCU handler "sensor_a0" raw_str = bridge.call("sensor_a0") raw = int(raw_str.strip()) volts = raw * (5.0 / 4095) print(f"A0: {raw:4d} raw | {volts:.3f} V") # Show voltage on LED matrix matrix.print_text(f"{volts:.1f}V") # If voltage high → turn on alert LED via Bridge if volts > 4.0: bridge.call("alert", "1") else: bridge.call("alert", "0") time.sleep(0.5)
PART 5 — Engaging the Hardware
This section shows how code turns into electricity and how electricity turns into sensor data in code.
A digital output pin outputs either 5 V (HIGH) or 0 V (LOW). This controls LEDs, relays, buzzers, and transistors. Always add a current-limiting resistor (220Ω–1kΩ) in series with an LED.
| Circuit Element | Connect To | Code |
|---|---|---|
| LED anode (+) | D13 via 220Ω resistor | digitalWrite(13, HIGH); |
| LED cathode (−) | GND | digitalWrite(13, LOW); |
| Buzzer + | D8 via 100Ω resistor | digitalWrite(8, HIGH); |
| Relay signal | D7 via transistor | digitalWrite(7, HIGH); |
// Pattern 1: External pull-down (button → 5V, resistor → GND) // → reads LOW when open, HIGH when pressed pinMode(2, INPUT); int s = digitalRead(2); // HIGH = pressed // Pattern 2: Internal pull-up (button → GND, no resistor needed!) // → reads HIGH when open, LOW when pressed pinMode(2, INPUT_PULLUP); // enables STM32's internal 40kΩ pull-up int s = digitalRead(2); // LOW = pressed (counter-intuitive but simpler wiring) // Pattern 3: External interrupt — reacts INSTANTLY without polling loop() void onButtonPress() { // This function runs immediately on button press Serial.println("Button pressed!"); } attachInterrupt(digitalPinToInterrupt(2), onButtonPress, FALLING);
Analog pins accept a voltage between 0 V and 5 V and convert it to a number.
On the UNO Q's STM32U585, you can choose 10-bit (0–1023) or 12-bit (0–4095) resolution.
Always call analogReadResolution(12) in setup() on the UNO Q.
void setup() { analogReadResolution(12); // set STM32 to 12-bit mode Serial.begin(115200); } void loop() { int raw = analogRead(A0); // 0–4095 float volts = raw * (5.0 / 4095.0); // convert to volts int mapped = map(raw, 0, 4095, 0, 100); // convert to percentage // Practical sensor formulas (depends on sensor datasheet): // NTC thermistor: requires Steinhart-Hart equation // LM35 temp sensor: volts × 100 = °C (e.g., 0.25V → 25°C) // LDR (light): raw → brighter = higher raw value Serial.print("Raw="); Serial.print(raw); Serial.print(" V="); Serial.print(volts, 3); Serial.print(" %="); Serial.println(mapped); delay(100); }
PWM (Pulse Width Modulation) rapidly switches a pin ON and OFF at high frequency. The ratio of ON time to OFF time (the duty cycle) creates an apparent analog voltage. Use it to dim LEDs, control motor speed, and drive servos.
// analogWrite() only works on PWM-capable pins: D3, D5, D6, D9, D10, D11 // Dim LED connected to D9 to 50% brightness analogWrite(9, 128); // 128/255 ≈ 50% duty cycle analogWrite(9, 0); // fully off analogWrite(9, 255); // fully on // Fade in: map potentiometer (0-4095) to brightness (0-255) int brightness = map(analogRead(A0), 0, 4095, 0, 255); analogWrite(9, brightness); // Control a servo (90° sweep). Servo signal pin → D9 // PWM period for servo: 1ms (0°) to 2ms (180°) every 20ms // Use the Servo.h library for proper servo control: #include <Servo.h> Servo myServo; void setup() { myServo.attach(9); } void loop() { myServo.write(90); } // 0° to 180°
void setup() { Serial.begin(115200); while (!Serial); // wait for Serial Monitor to connect } void loop() { // SEND — print formatted data Serial.print("SENSOR:"); Serial.println(analogRead(A0)); // e.g. "SENSOR:2048" // RECEIVE — read one character if (Serial.available() > 0) { String msg = Serial.readStringUntil('\n'); // read until newline Serial.print("Received: "); Serial.println(msg); } delay(200); }
from arduinoio import LEDMatrix import time matrix = LEDMatrix() # 1. Scroll text across the 8×13 matrix matrix.print_text("Hello!") time.sleep(3) # 2. Light a single pixel at row 0, column 0 matrix.clear() matrix.set_pixel(0, 0, True) time.sleep(1) # 3. Draw a pattern using a 2D list (8 rows × 13 cols) # 1 = lit, 0 = dark smiley = [ [0,0,1,0,0,0,0,0,1,0,0,0,0], # eyes [0,0,0,0,0,0,0,0,0,0,0,0,0], [0,1,0,0,0,0,0,0,0,0,1,0,0], # mouth corners [0,0,1,1,1,1,1,1,1,1,0,0,0], # mouth [0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0,0,0,0], ] matrix.set_pattern(smiley) time.sleep(3) # 4. Animated loop — scroll a column of lights left to right while True: for col in range(13): # for each of the 13 columns matrix.clear() for row in range(8): # light all 8 LEDs in that column matrix.set_pixel(row, col, True) time.sleep(0.05) # 50ms per column
| Mistake | Symptom | Fix |
|---|---|---|
| Connecting LED directly to pin without resistor | Pin damaged or dim LED | Always use 220Ω–1kΩ in series |
Forgetting analogReadResolution(12) | Sensor values top out at 1023 | Call it in setup() |
Using delay() with Bridge active | Python calls time out | Use millis() non-blocking timing |
Calling analogRead() from Python | AttributeError — function not found | Call it from C++ sketch; Bridge the result |
| Wire connected to wrong pin numbering | No response | D-pins are digital; A-pins are on the lower header |
Forgetting server.process() in loop() | Bridge calls never answered | Put it as first line in loop() |
| 5V logic into a 3.3V sensor | Sensor damaged | Check sensor datasheet; use level-shifter if needed |
| Expecting Wi-Fi from C++ sketch | Sketch compiles but can't connect | All Wi-Fi code goes in Python on the MPU side |
// Quick Reference
// Comments like this int x = 10; // typed variable const int Y = 5; // constant if (x > 5) { // condition in () // body in { } } else if (x == 3) { } else {} for(int i=0;i<5;i++){ } while(condition){ } void myFn(int param){ } int add(int a,int b){ return a+b; } String s = "hello"; // string int arr[3]={1,2,3}; // array arr[0]; // index Serial.println(x); // print delay(1000); // wait ms
# Comments like this x = 10 # no type keyword Y = 5 # UPPER = convention if x > 5: # no parens needed pass # body INDENTED elif x == 3: pass else: pass for i in range(5): pass while condition: pass def my_fn(param): pass def add(a, b): return a + b s = "hello" # string arr = [1, 2, 3] # list arr[0] # index print(x) # print time.sleep(1) # wait s