Hi, I’m hambreros. I’m 15, and this is my sister, tamadillo ’s entry into the Element14 EZ-EV challenge — my big sister is helping me out as the official 18 year old entry person for the competition. The plan is to build a little robot that can drive itself down a hallway, and eventually do useful stuff on its own. This post is about the first big milestone: getting the wheels moving.
What we’ve actually built
The brains of the robot are an Arduino UNO Q — it’s got a Linux side and a real-time STM32 side glued together, which is pretty cool because it means we get a full web server and precise motor timing on the same board.
For wheels, we’re using two continuous-rotation servos. If you haven’t met these before: they look exactly like a normal hobby servo (the kind that turns to a specific angle and holds it), except someone’s popped the case open and disconnected the little potentiometer that tells the servo where it’s pointing. Without that feedback, the servo can’t “aim” anymore — so instead of turning to an angle, it just spins continuously, and the angle signal becomes a speed-and-direction signal instead. That makes them perfect cheap wheel motors: no separate motor driver board needed, just a signal wire straight from the STM32.
Here’s the whole trick, in code. Each servo wants a pulse every 20 milliseconds (50 times a second) — 1.5ms means “stop”, 1ms means “full speed one way”, 2ms means “full speed the other way”, and everything in between is a speed in that direction:
#define PULSE_STOP 1500
#define PULSE_FWD 1000
#define PULSE_BACK 2000
static unsigned int speedToPulseUs(int speed) {
speed = constrain(speed, -100, 100);
// speed=100 -> 1000us (full forward), speed=-100 -> 2000us (full back)
return (unsigned int)(PULSE_STOP - speed * ((PULSE_STOP - PULSE_FWD) / 100));
}
One thing that tripped us up: the UNO Q’s normal PWM hardware pins are locked to 500Hz in the board’s config, which is way too fast for servos — they expect a pulse every 20ms, not every 2ms. So instead we’re “bit banging” it — just toggling the pin HIGH and LOW ourselves with precise microsecond delays, in a loop, which turns out to work great and means we can use basically any digital pin, not just the “official” PWM ones.
The web page
We didn’t want to have to plug a laptop into the robot every time we wanted it to move, so the STM32 side exposes two functions — set_wheel1(speed) and set_wheel2(speed) — over the UNO Q’s built-in bridge, and the Linux side runs a little Flask web page with a slider for each wheel. Drag a slider up, that wheel spins forward; drag it down, it goes backward; let go, and it springs back to the middle and stops — like a throttle stick, not a light switch. There’s also one big STOP ALL button because, well, it’s a robot with wheels and you should always have a big red button.
We also added a small safety net that I’m pretty proud of: if the web page loses its connection (phone goes to sleep, wifi drops, whatever) and no command has arrived for a full second, the STM32 stops both wheels on its own. So the worst case if my browser tab crashes isn’t “robot drives itself off the desk,” it’s just “robot stops.”
The mystery clicking noise
Here’s the annoying bit. With both sliders sitting dead center at “stop,” the wheels are supposed to just… sit there. Instead we’re getting a faint little clicking sound, like the servo is “self-correcting” even though nothing is telling it to move. Turns out this is a pretty well-known thing with continuous-rotation servos: because they’re built from a normal position-holding servo, there’s still a tiny bit of the original control circuit inside trying to hold a “center” position. If our 1500us stop signal doesn’t land exactly on the point the servo was trimmed to when its feedback pot got disconnected, it thinks it’s very slightly off target and keeps nudging the motor to correct — click, click, click.
The fix isn’t code so much as calibration — most of these servos have a tiny trim potentiometer on the back for exactly this. But since we’d rather tune it in software than hunt for a jeweler’s screwdriver every time, next step is adding a per-servo trim offset so we can dial each one in separately without touching the hardware:
#define SERVO1_TRIM_US 0 // tweak until wheel 1 is silent at speed=0
#define SERVO2_TRIM_US 0 // tweak until wheel 2 is silent at speed=0
static unsigned int speedToPulseUs(int speed, int trimUs) {
speed = constrain(speed, -100, 100);
return (unsigned int)(PULSE_STOP + trimUs
- speed * ((PULSE_STOP - PULSE_FWD) / 100));
}
A few microseconds either way should be enough to quiet it down completely.
What’s next
Right now the robot can be driven around from a phone or laptop on the same network, which already feels like magic. Next up, roughly in order:
- Camera + remote driving — stick a camera on it and add WASD/on-screen controls to the web page, so it’s basically a little puppy on a leash you can drive from anywhere.
- Simple autonomy — a strip of tape down the hallway and some basic light-sensor logic so it can follow the line by itself.
- Manual override — a mode switch so it can drive itself but I can grab the wheel (well, the sliders) whenever I want.
- Telemetry / a face — battery level, current mode, maybe even a little animated face on an OLED screen so it has some personality.
Long term, the goal is a robot that can scoot around the house on its own and do small useful things — not just drive in a straight line, but actually be handed simple tasks. One step at a time though. Wheels first!