Repository navigation
Reference Design Arduino UNO Q
Overview · What you need · Firmware · Hardware · Wiring · Setup · Other motor drivers · Status
Arduino UNO Q. Image: Arduino, from arduino/docs-content, CC BY-SA 4.0.
| 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 |
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.
| 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 |
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 |
| 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 |
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 |
- 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.
- 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).
- Wheels: motor n drives wheel n in the cockpit's wheel order; mount the mecanum wheels in the X pattern.
- IMU: the Modulino Movement (or a BNO085) on the Qwiic connector. Nothing to set: the boot scan finds it.
- LiDAR: LD19 TX to D0, P5V to a separate 5 V supply, GND to the board's GND. Leave its PWM pin unconnected.
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.
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.
-
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-rootto 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 overadb shell(USB), so restarting the Wi-Fi does not cut your own session. -
Free the link, and keep the MCU booting its firmware. Arduino's
arduino-routerowns/dev/ttyHS1, andarduino-router-serialstarts 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/ttyHS1and 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-bootThe 1-Click sets the same two lines before every SWD flash (
python3 scripts/unoq_swd.py boot-linesdoes it by hand). -
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" -
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.ymlon 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. -
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.
-
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 inlogs/latest/progress.log. -
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
i2candsensorsrun), 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 flooraccrun, 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.
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.
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.
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 mecanumunoqconfig found the STM32 holding another image, installedunoq-jazzyfrom the release over SWD, wrote and read back the env, then reached 8/8 Nav2 goals and passed 8/8 mecanum drive manoeuvres (/odom63 Hz,/imu/data48 Hz,/scan10 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.
-
/scanran at 10 Hz and the EKF's/odomat 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), andacclifted 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