Examples
All examples from the UNI/examples/ folder. Open them via File → Examples → UNI in Arduino IDE.
Basics
Start — minimal sketch
Creates the robot and initialises all peripherals: motors, encoders, OLED, LED and battery monitor. begin() is optional — the first command auto-initialises.
#include <UNI.h>
UniBase robot;
UniDev module;
void setup() {
robot.begin("UNI");
// Your one-time code here. For example:
// robot.moveDist(50, 300);
// robot.rotate(50, 90);
}
void loop() {
// Your repeating code here
}
BasicMovement — blocking movement
The robot drives a 300 mm square: four straight segments and four 90° turns. Every command blocks until it completes.
#include <UNI.h>
UniBase robot("UNI");
void setup() {
robot.begin();
for (int i = 0; i < 4; i++) {
robot.moveDist(50, 300); // forward 300 mm at power 50
robot.rotate(50, 90); // turn right 90 degrees
}
// Extra: reverse and timed movement
robot.moveDist(-50, 200); // reverse 200 mm
robot.moveTime(40, 1000); // forward 1 second
robot.moveTime(-40, 1000); // reverse 1 second
}
void loop() {
}
Movement
PrecisionTurns — rotate vs rotateTo
rotate() turns by a relative angle — errors accumulate. rotateTo() turns to an absolute odometry heading — accumulated drift is corrected every time.
#include <UNI.h>
UniBase robot("UNI");
void setup() {
robot.begin();
// Square using relative turns
robot.displayPrint("rotate");
delay(1000);
for (int i = 0; i < 4; i++) {
robot.moveDist(50, 300);
robot.rotate(50, 90);
}
robot.displayPrint("Angle", robot.getAngle()); // accumulated heading
delay(3000);
// Square using absolute turns
robot.displayPrint("rotateTo");
delay(1000);
robot.setPosition(0, 0, 0); // reset odometry before run
for (int i = 0; i < 4; i++) {
robot.moveDist(50, 300);
robot.rotateTo(50, (i + 1) * 90); // headings 90, 180, 270, 360
}
robot.displayPrint("Angle", robot.getAngle());
}
void loop() {
}
DriveToPoint — waypoint navigation
setPosition() anchors odometry to field coordinates. moveTo() automatically turns toward the target and drives straight to it.
#include <UNI.h>
UniBase robot("UNI");
// Route: array of {x, y} waypoints in millimetres
const float route[][2] = {
{400, 0},
{400, 400},
{0, 400},
{0, 0}, // return to start
};
const int routeLen = sizeof(route) / sizeof(route[0]);
void setup() {
robot.begin();
// Robot is at the origin, facing along the X axis
robot.setPosition(0, 0, 0);
for (int i = 0; i < routeLen; i++) {
robot.displayPrint("Point", i + 1);
robot.moveTo(50, route[i][0], route[i][1]);
delay(300);
}
robot.rotateTo(50, 0); // restore original heading at end
robot.displayPrint("Route", "DONE");
}
void loop() {
robot.printOdometry(); // watch position in Serial Monitor
delay(1000);
}
Arcs — arc movement
moveArcDist() — arc via wheel speed ratio. moveArcRadius() — arc with real geometry: radius and sector angle.
#include <UNI.h>
UniBase robot("UNI");
void setup() {
robot.begin();
// Square with rounded corners:
// straight segments + quarter-circles of radius 100 mm
robot.displayPrint("Round sq");
delay(1000);
for (int i = 0; i < 4; i++) {
robot.moveDist(50, 250);
robot.moveArcRadius(50, 100, 90); // quarter-circle R = 100 mm
}
delay(1000);
// Slalom via wheel speed ratio
robot.displayPrint("Slalom");
delay(1000);
for (int i = 0; i < 3; i++) {
robot.moveArcDist(50, 30, 250); // arc right
robot.moveArcDist(50, -30, 250); // arc left
}
robot.displayPrint("Arcs", "DONE");
}
void loop() {
}
AsyncMovement — non-blocking commands
Commands with the Async suffix start movement and return immediately. isMoving() — check if moving. waitMove(timeout) — wait with a stuck-protection timeout.
#include <UNI.h>
UniBase robot("UNI");
void setup() {
robot.begin();
robot.blinkLED(0);
// 1. Do useful work while the robot moves
robot.moveDistAsync(50, 800);
while (robot.isMoving()) {
robot.displayPrint("Dist", robot.getDistance()); // live progress
delay(100);
}
robot.displayClear();
delay(500);
// 2. Start and just wait
robot.rotateAsync(50, 180);
robot.blinkLED(100); // blink during the turn
robot.waitMove();
robot.blinkLED(0);
delay(500);
// 3. Wait with timeout — guards against getting stuck
robot.moveDistAsync(50, 800);
if (!robot.waitMove(5000)) { // didn't finish within 5 s?
robot.stop(HARD);
robot.displayPrint("STUCK!");
return;
}
delay(500);
// 4. Cancel movement on a custom condition
robot.moveDistAsync(30, 2000); // distant target...
delay(1500); // ...but after 1.5 s
robot.stop(HARD); // changed our mind
robot.displayPrint("Async", "DONE");
}
void loop() {
}
ManualControl — direct motor control
motorsSync() holds the speed ratio via encoders. holdPosition() actively resists being pushed. All commands run until stop() is called.
#include <UNI.h>
UniBase robot("UNI");
void setup() {
robot.begin();
// Straight ahead without stabilisation (may drift)
robot.displayPrint("motors");
robot.motors(40, 40);
delay(1500);
robot.stop(HARD);
delay(500);
// Straight ahead with encoder synchronisation
robot.displayPrint("motorsSync");
robot.motorsSync(40, 40);
delay(1500);
// Arc: right wheel half the speed of the left
robot.motorsSync(50, 25);
delay(1500);
// Reverse with synchronisation
robot.motorsSync(-40, -40);
delay(1500);
robot.stop(HARD);
delay(500);
// Arc via power + steering angle
robot.displayPrint("motorsArc");
robot.motorsArc(40, 20);
delay(2000);
robot.stop(SOFT); // soft stop — no wheel lock
// Active position hold (10 seconds)
robot.displayPrint("HOLD");
robot.holdPosition();
delay(10000);
robot.stop(SOFT);
robot.displayPrint("Done");
}
void loop() {
}
Sensors
ObstacleStop — obstacle reaction
The robot drives forward while watching an ultrasonic sensor. Obstacle closer than 150 mm — stop; obstacle removed — resume. Wiring: sensor on P6 (trig) and P7 (echo).
#include <UNI.h>
UniBase robot("UNI");
UniDev module;
const int OBSTACLE_MM = 150; // stop distance
const int TARGET_MM = 1500; // total travel distance
void setup() {
robot.begin();
robot.resetDistance();
}
void loop() {
// Goal reached — stay stopped
if (robot.getDistance() >= TARGET_MM) {
robot.displayPrint("DONE");
return;
}
int dist = module.ultraSonic(P6, P7);
bool blocked = (dist > 0 && dist < OBSTACLE_MM);
if (blocked) {
if (robot.isMoving()) {
robot.stop(HARD);
robot.displayPrint("Obstacle", dist);
}
} else {
if (!robot.isMoving()) {
// Resume remaining distance asynchronously
int remaining = TARGET_MM - (int)robot.getDistance();
robot.moveDistAsync(50, remaining);
robot.displayClear();
}
}
delay(50);
}
DistanceSensors — ultrasonic sensors
Reads distance in mm from multiple sensors. Wiring: front — P6/P7, side — P3/P4. Open Serial Monitor (115200 baud).
#include <UNI.h>
UniBase robot;
UniDev module;
void setup() {
robot.begin("Dist");
}
void loop() {
int front = module.ultraSonic(P6, P7);
int side = module.ultraSonic(P3, P4);
Serial.print("Front: ");
Serial.print(front);
Serial.print(" mm, Side: ");
Serial.print(side);
Serial.println(" mm");
robot.displayPrint("Front", front);
delay(100);
}
LineSensor — line sensor
Analogue reading 0–4095. Calibrate the threshold between the light floor and dark line. Wiring: sensor on P2.
#include <UNI.h>
UniBase robot;
UniDev module;
const int THRESHOLD = 2000; // calibrate for your floor
void setup() {
robot.begin("Line");
}
void loop() {
int value = module.lineSensor(P2);
bool onLine = (value > THRESHOLD);
Serial.print("Line sensor: ");
Serial.print(value);
Serial.println(onLine ? " [LINE]" : "");
robot.displayPrint(onLine ? "LINE" : "floor", value);
delay(100);
}
ServoControl — servo: angles, attach, detach
Cycles a servo between 0°, 90° and 180°, then detaches it so the arm can move freely without straining the motor. The next servo() call re-attaches automatically. Wiring: servo on P3.
#include <UNI.h>
UniBase robot;
UniDev module;
void setup() {
robot.begin("Servo");
}
void loop() {
// Servo on — hold angles
robot.displayPrint("Servo", "ON");
module.servo(P3, 0);
delay(1000);
module.servo(P3, 90);
delay(1000);
module.servo(P3, 180);
delay(1000);
// Detach: motor goes limp, arm can be moved by hand
robot.displayPrint("Servo", "OFF");
module.servoDetach(P3);
delay(3000);
// Next servo() at the top of the loop re-attaches automatically
}
Configuration
Tuning — precision tuning
All PID parameters are collected in TuningConfig. Platform geometry is set in UniConfig and passed to the constructor.
#include <UNI.h>
UniConfig cfg; // default values
// cfg.trackLengthMM = 106.0f; // override before creating the robot if needed
UniBase robot("UNI", cfg);
void setup() {
robot.begin();
// Fetch current settings, change what's needed, apply
TuningConfig tuning = robot.getTuning();
tuning.rotateTolDeg = 0.5f; // tighter turn tolerance
tuning.moveAccel = 1500.0f; // gentler deceleration on straight
robot.setTuning(tuning);
// Test run with new settings
robot.moveDist(70, 500);
robot.rotate(70, 180);
robot.moveDist(70, 500);
robot.rotate(70, 180);
// Odometry shows return accuracy
robot.displayPrint("X err", robot.getAbsX());
}
void loop() {
}
UniBaseControl — UART control
Examples from UniBaseControl/examples/. Flash the ESP32 with UniBaseControl_Start; program the Arduino Nano with Start or BasicMovement.
UniBaseControl_Start — ESP32 side
Turns the platform into a UART command executor. Flash to ESP32. Wiring: UART2 (RX 16, TX 17, 38400 baud) to the Nano's TX/RX.
#include <UNI.h>
UniBase robot;
void setup() {
robot.begin("UNI");
robot.UniBaseControl(); // start listening for commands
}
void loop() {
// Command reception, movement and display run on core 1.
// loop() is free — add your own logic here.
}
Start — Arduino Nano side
Minimal sketch for the controller board. Flash to Arduino Nano with the UniBaseControl library installed.
#include <UniBaseControl.h>
UniBaseControl robot;
void setup() {
robot.begin();
}
void loop() {
}
BasicMovement — movement from Nano
The robot drives a square; commands are sent from an Arduino Nano over UART.
#include <UniBaseControl.h>
UniBaseControl robot;
void setup() {
robot.begin();
}
void loop() {
for (int i = 0; i < 4; i++) {
robot.moveDist(50, 100);
delay(2000);
robot.rotate(50, 90);
delay(1000);
}
}