Skip to content

Reference Design Arduino UNO Q

hippo5329 edited this page Oct 16, 2026 · 20 revisions

Reference Design: Arduino UNO Q

Overview · What you need · Firmware · Hardware · Wiring · Setup · Other motor drivers · Status

Arduino UNO Q board, top view

Arduino UNO Q. Image: Arduino, from arduino/docs-content, CC BY-SA 4.0.

Arduino UNO Q wiring

Config unoq_config.yaml
Drivetrain 4WD mecanum on the MC200 chassis kit (holonomic): 80 mm mecanum wheels, four 25 mm gear motors with Hall encoders
Robot computer The board's own Qualcomm QRB2210 (4 × Cortex-A53, Debian)
MCU The board's STM32U585 (Cortex-M33, 160 MHz), running Zephyr
Firmware image unoq-jazzy / unoq-lyrical release archives (firmware.elf, firmware.bin), flashed over SWD from the board itself; or built from firmware/zephyr/unoq
micro-ROS link The board's internal UART, /dev/ttyHS1, at 4 Mbaud
LiDAR LD19 on the MCU (D0), sent to the robot computer as raw_scan
Status ✅ on a real MC200: Nav2 goals with SLAM and a real LD19, its speeds checked against the LiDAR; ✅ 1-Click and exploration in simulation mode on the board, jazzy and lyrical

Overview

The Arduino UNO Q is the whole robot on one board. Its Qualcomm QRB2210 is the robot computer that runs the cockpit's Docker stack, and its STM32U585 is the base controller. The two are wired together on the board, so micro-ROS needs no cable: the MCU's LPUART1 is Linux's /dev/ttyHS1. Two AT8236 dual H-bridge boards (one per axle, two PWM inputs per motor), wired to the UNO header, drive the four mecanum wheels of an MC200 chassis kit; the IMU is whatever answers the Qwiic connector, an Arduino Modulino Movement (LSM6DSOX) or a BNO085. The LD19 is wired to the MCU, which forwards its packets to the robot computer as raw_scan.

A kit design. The design brings the MC200's chassis with it: picking it on a robot that has a chassis of its own replaces that chassis with the MC200's (change it afterwards on Base and Drive & Motors if yours differs). Its numbers were measured on a built MC200, not copied from the box:

Value How it was found
Wheel diameter 0.075 m (the wheels are 80 mm) The speed check: the floor got 0.94 of what an 80 mm wheel says, the same at 0.10 and 0.20 m/s. Mecanum rollers touch the floor inside the wheel's outer edge, so the wheel rolls as a smaller one
Wheel centres, left to right 0.205 m 0.209 m from the kit's photo; the speed check's spin fits 0.205 + 0.095 best
Wheel centres, front to rear 0.095 m 0.096 m from the plate drawing's motor mounts. A mecanum robot turns on the sum of the two, so the LiDAR settles it: spin and forward then agree within 0.4 %
Motor top speed 177 rpm on a 3S pack Tachometer
Encoder counts per wheel turn 887 Tachometer
Body 200 × 150 mm plate, 1.0 kg; 1.65 kg built The kit's drawing; weighed

Your MC200 will be close but not identical: run the speed check on it before its first Nav2 goal.


What you need

Part Notes
Arduino UNO Q (4 GB recommended) Nav2, SLAM and the stack used 50–80 % of its four cores and left ≥ 380 MB free on the 4 GB board
2 × AT8236 dual-channel motor driver One per axle, two PWM inputs per motor (driver_type: AT8236). A four-channel TB6612FNG board such as the Wheeltec D24A takes the same pins and the same setting, with its PWMA, PWMB and STBY held high; or any 3.3 V-logic driver from the table below
An MC200 4WD mecanum chassis kit 80 mm mecanum wheels, four 25 mm DC gear motors with Hall encoders (9–12 V), 200 × 150 mm plates. Encoder outputs at 3.3 V, or level-shifted (A0/A1 are not 5 V tolerant). Mount the wheels in the X pattern: seen from above, the rollers form an X
Arduino Modulino Movement, or a BNO085 breakout On the Qwiic connector; found by the boot scan (imu: AUTO)
LD19 LiDAR TX to D0; its 5 V from a separate supply (the motor driver's 5 V output, or a 5 V buck converter on the pack), never the UNO Q's 5 V pin; its GND joined to the board's GND
Battery For the motors through the drivers; the board itself runs from USB-C 5 V / 3 A or VIN 7–24 V
A PC with Docker (optional) Only to build the firmware yourself: the 1-Click fetches the release image, and the board does not build it

Firmware: the same firmware, on Zephyr

PlatformIO cannot build for the UNO Q's MCU, so the firmware is built with Zephyr and micro-ROS's Zephyr module. It is not a port: firmware/src/main.cpp and every library are compiled unchanged against a small Arduino layer (firmware/zephyr/unoq/fw), the way the Sim MCU runs them on a PC. So everything a board does, the UNO Q does:

  • simulation mode with every simulated sensor;
  • the env block and its keys;
  • the diagnostic tools;
  • the boot banner, where uid= is the STM32's 96-bit unique ID.
Release Every release carries linorobot2-firmware-unoq-jazzy.tar.gz and linorobot2-firmware-unoq-lyrical.tar.gz: firmware.elf, firmware.bin and a manifest.json with checksums, the commit and the env-block address. One image per distro: micro-ROS is fixed at link time, and the image's distro also sets its cmd_vel type (Twist on jazzy, TwistStamped on lyrical), which the release checks on the image's bytes before publishing it
Build it yourself MICROROS_DISTRO=jazzy|lyrical firmware/zephyr/unoq/build.sh <workdir> (Zephyr 4.4, in Zephyr's CI container), then scripts/package_unoq.py <workdir>/build-fw <distro> for the same layout as the release
Flash From the UNO Q's own Linux side, over SWD with the board's OpenOCD (/opt/openocd); the env block goes in the last flash page (0x081FE000)
micro-ROS link /dev/ttyHS1 at 4 Mbaud, the LPUART's hardware FIFOs on, interrupt-driven; at 115200 one odometry message would outlast the 20 ms control period
Measured on a bare board odom/unfiltered, imu/data 50 Hz, simulated LD19 82 Hz; the MCU 12 % busy

Key Hardware Specifications

Component Specification Notes
Robot computer Qualcomm QRB2210, 4 × Cortex-A53 2.0 GHz, 4 GB, 32 GB eMMC Debian; runs Docker and the cockpit stack
Base controller STM32U585, Cortex-M33 160 MHz, 2 MB flash, 786 KB SRAM 3.3 V I/O, 5 V tolerant in digital mode, except A0/A1 (PA4/PA5, the DAC pins: never 5 V tolerant); A2–A5 (PA6, PA7, PC1, PC0) not while they are analog inputs
Motor driver 2 × AT8236 dual H-bridge (front axle, rear axle) driver_type: AT8236. The setting picks the firmware's drive scheme, not a chip: AT8236 is the two-PWM scheme, where IN1 and IN2 carry the PWM and the config gives no enable pin. A TB6612FNG board (Wheeltec D24A) runs the same way with PWMA/PWMB and STBY held high, as on the Waveshare General Driver
IMU Arduino Modulino Movement (ST LSM6DSOX) or a BNO085 Qwiic (the MCU's I2C4), 0x6A or 0x4A/0x4B; AUTO, nothing there = no IMU
LiDAR LD19 on the MCU TX → D0 (USART1 RX); P5V from a separate 5 V supply, not the UNO Q (see the power note); GND shared; whole packets travel as raw_scan, about 15 % of the 4 Mbaud link together with odometry and the IMU
Chassis MC200 kit: 80 mm mecanum wheels (37 mm wide, 9 rollers), 25 mm gear motors with Hall encoders, 150 rpm ±10 % unloaded at 9 V Measured: rolls as 75 mm, wheel centres 0.205 × 0.095 m, 177 rpm and 887 counts per wheel turn on a 3S pack
Power USB-C 5 V / 3 A, or VIN 7–24 V The motor drivers' supply is separate (their own terminals); a 3S pack for the motors

Wiring

Pins in the config are the UNO Q's Arduino header numbers: D0–D13 are 0–13, A0–A5 are 14–19, and the on-board RGB LED3 is 20 (red), 21 (green), 22 (blue).

Function Header STM32 Notes
Motor 1 (front-left) A / B D9 / D10 PB8 / PB9 AT8236 #1 AIN1 / AIN2; TIM4 CH3 / CH4, 20 kHz
Motor 2 (front-right) A / B D3 / D6 PB0 / PB1 AT8236 #1 BIN1 / BIN2; TIM3 CH3 / CH4
Motor 3 (rear-left) A / B A2 / A3 PA6 / PA7 AT8236 #2 AIN1 / AIN2; TIM3 CH1 / CH2
Motor 4 (rear-right) A / B D11 / D12 PB15 / PB14 AT8236 #2 BIN1 / BIN2; TIM15 CH2 / CH1
Encoder 1 A / B D2 / D4 PB3 / PA12 Pin interrupts, ×4 quadrature; EXTI 3 / 12
Encoder 2 A / B D7 / D8 PB2 / PB4 EXTI 2 / 4
Encoder 3 A / B D13 / A4 PB13 / PC1 EXTI 13 / 1
Encoder 4 A / B A5 / D5 PC0 / PA11 EXTI 0 / 11
LiDAR RX D0 PB7 USART1 RX, 230400 baud
IMU Qwiic, 3.3 V SCL PD12, SDA PD13 I2C4 (the design names it bus: qwiic)
Battery sense A0 PA4 Through a divider to ≤ 3.3 V: A0 is not 5 V tolerant. Unset until fitted
Status LED LED3 green (21) PH11 Active low
  1. Motor driver: the first AT8236 (front axle) AIN1, AIN2, BIN1, BIN2 to D9, D10, D3, D6; the second (rear axle) to A2, A3, D11, D12. (On a TB6612 board, hold both chips' PWMA, PWMB and STBY high.) Their GND to the UNO Q's GND, their motor supply to the battery. These eight header pins are the ones with PWM in this firmware.
  2. Encoders: front-left A/B to D2/D4, front-right to D7/D8, rear-left to D13/A4, rear-right to A5/D5, powered from a 3.3 V LDO of their own (not the UNO Q's 3.3 V pin; see Power the motors and encoders). The pairs are chosen so that no two encoder pins share an interrupt line: an STM32 has one per pin number (PA3 and PB3 would collide).
  3. Wheels: motor n drives wheel n in the cockpit's wheel order; mount the mecanum wheels in the X pattern.
  4. IMU: the Modulino Movement (or a BNO085) on the Qwiic connector. Nothing to set: the boot scan finds it.
  5. LiDAR: LD19 TX to D0, P5V to a separate 5 V supply, GND to the board's GND. Leave its PWM pin unconnected.

Power the LiDAR separately

Do not power the LD19 from the UNO Q's 5 V pin: give it a 5 V supply of its own, such as the motor driver's 5 V output or a 5 V buck converter on the pack, and join its GND to the board's GND so its TX line has a common reference. On an MC200, the LD19 on the UNO Q's 5 V sent no data at all; on the motor driver's 5 V it ran at its full rate, about 380 packets a second with no errors, through a whole SLAM and Nav2 run and on a low pack.


Setup, step by step

The 1-Click flashes the UNO Q's STM32 itself, over SWD from the board's own Linux: it identifies the MCU, asks its flash whether it already holds this release's unoq-<distro> image (OpenOCD's verify_image), installs the image when it does not, writes the env block from your config on every run (read back and compared), and resets the MCU into it. Then bringup, SLAM and Nav2 run on the board.

  1. Prepare the board. Connect the UNO Q to your network and make sure Docker runs on it. The root filesystem is 10 GB: move Docker's data-root to the user partition (/home/arduino, 18 GB on the 4 GB board), which has room for the robot images (about 4–6 GB per distro). The UNO Q runs NetworkManager, which gives up on DHCP after 45 seconds and can then stay "connected" with no IPv4 address: set it to retry forever and add the watchdog, as in Keep the robot computer on the network. Do it over adb shell (USB), so restarting the Wi-Fi does not cut your own session.

  2. Free the link, and keep the MCU booting its firmware. Arduino's arduino-router owns /dev/ttyHS1, and arduino-router-serial starts it again at boot if only the router is disabled, so disable both: sudo systemctl disable --now arduino-router arduino-router-serial. The router also drove the STM32's BOOT0 line low (gpiochip1 line 37) and its reset line high (line 70). Without it those lines float after a power cycle and the STM32 starts its ROM bootloader instead of the firmware: the agent waits on /dev/ttyHS1 and nothing answers. The cockpit ships a one-shot service that sets them at every boot; install it from the cockpit checkout:

    sudo cp docker/unoq/lino-stm32-boot.service /etc/systemd/system/
    sudo systemctl enable --now lino-stm32-boot

    The 1-Click sets the same two lines before every SWD flash (python3 scripts/unoq_swd.py boot-lines does it by hand).

  3. Back up the MCU flash if you want Arduino's own firmware back later:

    sudo /opt/openocd/bin/openocd -s /opt/openocd -f openocd_gpiod.cfg \
      -c "init; reset halt; dump_image unoq_mcu_backup.bin 0x08000000 0x200000; reset run; shutdown"
  4. Start the cockpit on the board with the UNO Q overlay. Install it as on any arm64 computer (Raspberry Pi 5 and other arm64 computers), and start it with docker-compose.unoq.yml on top:

    docker compose -f docker-compose.yml -f docker-compose.unoq.yml up -d

    The overlay lets the container open /dev/ttyHS1 (the micro-ROS link) and the GPIO chips (the MCU's SWD lines), and mounts the board's OpenOCD (/opt/openocd) with the host libraries it needs. Nothing in it applies to any other robot computer.

  5. Name your robot in the header's Robot field and press Enter. For a first run with nothing wired, leave Reference Designs (MCU & Sim, tab 1) at No design: your robot is then a bare module of the UNO Q's STM32, every device simulated, and the header's badge stays on 🧪 Simulation. The design comes in step 7, once the robot is wired.

  6. Press 🚀 Start 1-Click. The run fetches unoq-<distro> for the robot image's distro, flashes it if the MCU holds another image, writes the env, and goes on to bringup, SLAM and Nav2. Its progress, stage by stage with times, is in logs/latest/progress.log.

  7. The real robot: wire it as above, then select the Arduino UNO Q design under Reference Designs on MCU & Sim. The board and the MC200 chassis become your robot's (it is a kit design): the motor pins and the Qwiic I2C bus (so the tool's i2c and sensors run), and the robot is real from then on (🔧 Real). Work through Building a Real Robot from step 4: IMU, motors and encoders with the tool, the floor acc run, the LiDAR mount, teleop, the speed check against the LiDAR, then Nav2. The tool is on Hardware Tests (tab 6), as on any board: Enter tool restarts the MCU into it over SWD, and its terminal runs on /dev/ttyHS1.

Flashing by hand

The same, without the cockpit: get the image with python3 scripts/fetch_prebuilt.py unoq-jazzy (or build it: MICROROS_DISTRO=jazzy firmware/zephyr/unoq/build.sh <workdir>, then scripts/package_unoq.py <workdir>/build-fw jazzy), build the env with python3 scripts/mcu_env.py build --params unoq_config.yaml --out env.bin, and on the board:

sudo /opt/openocd/bin/openocd -s /opt/openocd -f openocd_gpiod.cfg \
  -c "reset_config srst_only srst_push_pull; init; reset halt; program firmware.elf verify;
      flash write_image erase env.bin 0x081FE000 bin; reset run; shutdown"

The image must match the robot image's distro, or the agent never holds a session.


Other motor drivers and shields

Any driver or UNO-header shield that accepts 3.3 V logic works, since the firmware takes its pins from the config. What decides the fit is the input scheme:

Shield Driver Fit
AT8236 dual H-bridge two-PWM This design, one board per axle
Wheeltec D24A (2 × TB6612FNG) 4 channels, two-PWM with PWMA/PWMB and STBY high Drop-in: the same scheme, pins and driver_type
Cytron MDD3A (module) 2 × 3 A, 4–16 V, two-PWM Drop-in: the same scheme and pins
Pololu DRV8835 Dual Motor Driver Shield 2 × 1.2 A, 2–11 V Small motors; IN/IN mode is two-PWM
Pololu Dual TB9051FTG / Dual G2 shields 2.6 A to 20 A+ Larger robots; set their logic level to the board's IOREF
SparkFun Ardumoto (TB6612) 2 × 1.2 A, PWM + direction The PWM + direction scheme (GENERIC_1_IN)
Adafruit Motor Shield V2 4 × 1.2 A over I2C (PCA9685) Not supported: no I2C motor driver in the firmware
Arduino Motor Shield Rev3 (L298P) 2 × 2 A Not recommended: its current-sense outputs sit on A0/A1, which are not 5 V tolerant

None of these reads encoders; wire the encoders to free header pins.


Status

In simulation mode, with the STM32 as base controller and the stack on the QRB2210:

  • The 1-Click, flash included: with docker-compose.unoq.yml, a run on the mecanum unoq config found the STM32 holding another image, installed unoq-jazzy from the release over SWD, wrote and read back the env, then reached 8/8 Nav2 goals and passed 8/8 mecanum drive manoeuvres (/odom 63 Hz, /imu/data 48 Hz, /scan 10 Hz). Probe 21 s, flash and env 26 s.
  • Nav2 reached 8/8 goals behind the obstacle wall, and the drive suite passed 8/8 manoeuvres, for 2WD, skid steer and mecanum, on jazzy and lyrical.
  • Frontier exploration mapped the rooms world (about 62 m²) and returned home on both distros.
  • /scan ran at 10 Hz and the EKF's /odom at 50 Hz.
  • The board ran 50–80 % busy across its four cores, kept ≥ 380 MB available, and peaked at 69 °C.

On a real MC200 (the robot computer and base controller on this board, a real LD19 on D0, a BNO085 on Qwiic):

  • Nav2 goals with SLAM: 2 m out and back home, 20 of 20 legs in a 10-round-trip run.
  • The speed check against the LiDAR set its wheel diameter and wheel centres (the table at the top). Forward, spin and strafe then agree with the LiDAR.
  • The tool: every wheel at 25 % (37–41 rpm, encoders matched), ramp, the IMU, the battery (11.2 V), and acc lifted and on the floor at a 40 % cap, strafe run included.
  • Watch for: a cable on the floor is below the LiDAR's plane; Nav2 cannot see it and the robot will try to drive over it.

All reference designs · Pin Matrix & Wiring · Building a Real Robot

Clone this wiki locally