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);
  }
}