Complete Beginner's Guide · 2025

Arduino UNO Q
Programming

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.

C++ Sketches (MCU) Python (MPU · Linux) Arduino Bridge GPIO / Sensors Beginner Friendly
Qualcomm Dragonwing™ QRB2210 MPU Quad-Core ARM Cortex-A53 2.0 GHz · Debian Linux Adreno GPU · Wi-Fi 5 STM32U585 MCU ARM Cortex-M33 Zephyr + Arduino Core 12-bit ADC · GPIO Bridge LPDDR4X RAM 2 GB or 4 GB eMMC Storage 16 GB or 32 GB Wi-Fi 5 + BT 5.1 Dual-band 2.4/5 GHz 8×13 LED Matrix + 4 RGB LEDs USB-C Qwiic PWR Modulino / Qwiic I²C Snap-on Nodes Button·Pixel·Distance Knob·Thermo·Buzzer

PART 0

Understanding the Arduino UNO Q

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

  • Architecture Quad-Core ARM Cortex-A53
  • Clock 2.0 GHz
  • OS Debian Linux (full)
  • RAM 2 GB or 4 GB LPDDR4X
  • Storage 16 GB or 32 GB eMMC
  • GPU Adreno (AI / ML)
  • Network Wi-Fi 5 · Bluetooth 5.1
  • Language Python, any Linux app
  • Controls LED Matrix · Wi-Fi · Camera

Brain #2 — The Controller

STMicroelectronics STM32U585 MCU

  • Architecture ARM Cortex-M33
  • OS Zephyr RTOS + Arduino Core
  • Digital Pins D0–D13 (5 V logic)
  • Analog Pins A0–A5 (12-bit ADC, 0–5 V)
  • PWM D3, D5, D6, D9, D10, D11 (~)
  • I²C SDA (A4) / SCL (A5)
  • SPI D10–D13
  • UART D0 (RX) / D1 (TX)
  • Language C++ (Arduino Sketches)
💡

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.

The Three Development Modes

ModeIDE / ToolPrograms Which BrainLanguage
App Lab (recommended)Arduino App LabBoth MPU + MCU togetherPython + C++
Classic Arduino IDEArduino IDE 2.x via USBMCU onlyC++
Headless LinuxSSH / VS Code / terminalMPU onlyPython, any

UNO Q vs Classic Arduino Uno — Key Differences

FeatureClassic Arduino Uno (R3)Arduino UNO Q
ProcessorATmega328P · 8-bit · 16 MHzSTM32U585 (MCU) + QRB2210 (MPU)
Operating SystemNone (bare metal)Zephyr (MCU) + Debian Linux (MPU)
RAM2 KB SRAM2–4 GB LPDDR4X
Language on pinsC++C++ (pins always go through MCU)
High-level languageN/APython 3 on Debian
ADC Resolution10-bit (0–1023)12-bit (0–4095) — call analogReadResolution(12)
Wi-Fi / BTNo (Uno R3)Yes — Wi-Fi 5 + BT 5.1
Sketch serial baudTypically 9600Use 115200 — STM32 is faster
LED MatrixNo8×13 LEDs + 4 RGB LEDs (MPU side)

PART 1 — C++ Language

C++ for the Arduino UNO Q MCU

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

The C++ Language — Complete Overview

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. Program Structure

C++ — overall sketch structure
// ─── 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
}

2. Data Types

TypeSizeRange / UseExample
bool1 bytetrue or falsebool on = true;
byte1 byte0 – 255 unsignedbyte b = 200;
int2 bytes–32,768 – 32,767int count = 0;
unsigned int2 bytes0 – 65,535unsigned int u = 50000;
long4 bytes–2,147,483,648 – 2,147,483,647long t = millis();
unsigned long4 bytes0 – 4,294,967,295unsigned long ms = millis();
float4 bytesDecimal numbers ≈ 6–7 sig. digitsfloat v = 3.14;
double4 bytesSame as float on most Arduinosdouble d = 2.71828;
char1 byteSingle ASCII characterchar c = 'A';
StringVariableText (Arduino class)String s = "Hello";
voidNo value (function returns nothing)void setup() {}

3. Variables — Declaration and Scope

C++ — variables
// 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

4. Operators

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

5. Control Flow — if / else / else if

C++ — conditionals
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");
}

6. Loops — for, while, do-while

C++ — loops
// 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
}

7. Functions — Defining and Calling

C++ — functions
// 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
}

8. Arrays

C++ — arrays
// 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
}

9. Switch Statement

C++ — switch
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");
}

10. Key Arduino Standard Library Functions

FunctionWhat It DoesExample
pinMode(pin, mode)Set pin as INPUT or OUTPUTpinMode(13, OUTPUT);
digitalWrite(pin, val)Write HIGH (5V) or LOW (0V) to a digital pindigitalWrite(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 ~ pinsanalogWrite(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 anothermap(raw,0,4095,0,255)
constrain(val,lo,hi)Clamp value within a rangeconstrain(val,0,255)
Serial.begin(baud)Start serial communication at baud rateSerial.begin(115200);
Serial.print(val)Print a value (no newline)Serial.print(temperature);
Serial.println(val)Print a value with newlineSerial.println("done");
Serial.available()Bytes waiting in receive bufferif(Serial.available()>0){}
Serial.read()Read one byte from serialchar c = Serial.read();

// 1.2 — Hello World

C++ Hello World for UNO Q

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.

C++hello_world.ino
/*
 * 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

C++ Starter Program — Covers the Full Language

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.

C++ — complete beginner sketchuno_q_starter.ino
/*
 * 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 for the Arduino UNO Q MPU

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

The Python Language — Complete Overview

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. Program Structure — No setup() or loop()

Python — overall structure
# ─── 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)

2. Variables and Types — Dynamic Typing

Python — variables — no type declaration needed
# 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

3. Collections — Lists, Tuples, Dictionaries

Python — collections
# 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

4. Operators

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

5. Control Flow

Python — if / elif / else
# 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"

6. Loops

Python — loops
# 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)

7. Functions

Python — functions
# 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)

8. Classes and Objects

Python — classes
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

9. Exception Handling

Python — try / except
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.")

10. Key Python Built-in Functions

FunctionDoesExample
print()Output to consoleprint(f"v = {v:.2f}")
input()Read a line from usercmd = input(">> ")
int(), float(), str()Convert typesv = float("3.7")
len()Length of a collectionlen([1,2,3]) → 3
range(n)Sequence 0 to n-1for i in range(5):
enumerate()Index + value iteratorfor i, v in enumerate(list):
sum(), min(), max()Math on iterablessum([1,2,3]) → 6
sorted()Return sorted listsorted([3,1,2]) → [1,2,3]
open()Open a filewith open("log.csv","a") as f:
time.sleep(s)Pause n seconds (float ok)time.sleep(0.5)

// 2.2 — Hello World

Python Hello World for UNO Q

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.

Pythonapp/hello_world.py
"""
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

Python Starter Program — Covers the Full Language

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.

Python — complete beginner programapp/uno_q_starter.py
"""
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

What Is a Sketch?

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 Upload Flow

Your .ino File C++ source code arm-none-eabi C++ Compiler Machine Code ARM binary .bin STM32U585 Flash Runs on power-on

Sketch 1 — LED Blink with Non-Blocking Timing

The millis()-based approach is the professional way to blink — delay() stops everything including Bridge communication.

C++ — non-blocking blinksketch/blink_nonblocking.ino
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
}

Sketch 2 — Read a Button with Debounce

C++ — button debouncesketch/button_debounce.ino
// 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;
}

Sketch 3 — I²C Sensor with Wire Library

C++ — I²C with Wire.hsketch/i2c_sensor.ino
#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

Connecting Python and C++ on the UNO Q

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.

Why the Bridge Exists

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.

Python (MPU) bridge.call("sensor") Qualcomm Linux Bridge Bus (RPC) high-speed internal link named function calls C++ Sketch (MCU) addHandler("sensor", fn) STM32 Zephyr call request response

Complete Bridge Project — Sensor + LED Control

This is a two-file project — one file per brain. They work together as a single system.

MCU Side — C++ Sketch
C++sketch/sketch.ino
#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();
}
MPU Side — Python App
Pythonapp/main.py
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

Controlling Real-World Electronics

This section shows how code turns into electricity and how electricity turns into sensor data in code.

Digital Output — Turning Things ON and OFF

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 ElementConnect ToCode
LED anode (+)D13 via 220Ω resistordigitalWrite(13, HIGH);
LED cathode (−)GNDdigitalWrite(13, LOW);
Buzzer +D8 via 100Ω resistordigitalWrite(8, HIGH);
Relay signalD7 via transistordigitalWrite(7, HIGH);

Digital Input — Reading Buttons and Switches

C++ — digital input patterns
// 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 Input — Reading Sensors (A0–A5)

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.

C++ — analog read + conversions
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 Output — Analog-Like Control

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.

C++ — PWM patterns
// 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°

Serial Communication — Talking to Python / PC

C++ — serial send and receive
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);
}

The UNO Q LED Matrix — Python Side

Python — LED matrix patternsapp/matrix_demo.py
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

Common Hardware Mistakes to Avoid

MistakeSymptomFix
Connecting LED directly to pin without resistorPin damaged or dim LEDAlways use 220Ω–1kΩ in series
Forgetting analogReadResolution(12)Sensor values top out at 1023Call it in setup()
Using delay() with Bridge activePython calls time outUse millis() non-blocking timing
Calling analogRead() from PythonAttributeError — function not foundCall it from C++ sketch; Bridge the result
Wire connected to wrong pin numberingNo responseD-pins are digital; A-pins are on the lower header
Forgetting server.process() in loop()Bridge calls never answeredPut it as first line in loop()
5V logic into a 3.3V sensorSensor damagedCheck sensor datasheet; use level-shifter if needed
Expecting Wi-Fi from C++ sketchSketch compiles but can't connectAll Wi-Fi code goes in Python on the MPU side

// Quick Reference

C++ vs Python — Side-by-Side Syntax

C++ (MCU Sketch)
C++ syntax cheatsheet
// 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
Python (MPU App)
Python syntax cheatsheet
# 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