DIY Servo-Tuned AM/FM Spirit Box (Arduino Nano + SG90)
Build a mechanical spirit box that sweeps an analog radio using an SG90 micro-servo and an Arduino Nano. Adjustable C-bracket, field-tough, and tunable via a front-panel knob.

Parts
- Arduino Nano V3.0 (ATmega328P, 5V/16MHz) + WYPH Nano I/O Expansion Shield
- SG90 micro-servo (180°, non-continuous) + cross horn
- yogurttime VT-8 AM/FM radio kit
- 3D-printed C-bracket (long top slot + vertical side slots) and N20 bracket as servo holder
- 50kΩ panel pot (vr2) & 10kΩ pot (optional fine range → 1)
- 2-position SPDT slide switch (power/mode)
- 3.5 mm headphone jack (audio out)
- 9 V battery + snap, hookup wire, screws/bolts
How it Works
The servo is mounted upside-down under the C-bracket. A cross horn is glued to the radio’s dial shaft; the servo spline drops into the horn. The 10k pot sets hop duration and the Arduino makes short randomized moves that mimic a classic spirit box sweep.
Mechanical Setup

- Install the C-bracket in the project box; leave bolts slightly loose.
- Bolt the servo holder under the top slot; mount the servo with shaft pointing down.
- Glue the cross horn to the VT-8 tuning shaft (perfectly centered).
- Drop the servo spline into the horn; slide/level via the slots; tighten hardware.
Inside the Box


Wiring
- Servo: SIG → D9, +5V → 5V, GND → GND (share ground with radio).
- 50k pot: wiper → A0, ends to 5V/GND (controls hop duration).
- 10k pot (optional): wiper → A1 (fine range).
- SPDT slide: Pos.1 = Radio only; Pos.2 = Radio + Nano/servo.
- Power: 9 V → VIN on Nano; servo on stable 5 V (shared ground).
- Headphone jack: connect to VT-8 audio out.
Arduino Code
#include <Servo.h>
Servo tunerServo;
const int MIN_ANGLE=10, MAX_ANGLE=170;
const int MIN_HOP_DEG=8, MAX_HOP_DEG=40;
const int MIN_SEG_MS=150, MAX_SEG_MS=800;
const int STEP_INTERVAL_MS=12;
int currentAngle=(MIN_ANGLE+MAX_ANGLE)/2;
static inline int irand(int a,int b){return random(a,b+1);}
int smoothReadA0(){long a=0;for(int i=0;i<5;i++){a+=analogRead(A0);delay(2);}return a/5;}
void setup(){tunerServo.attach(9);tunerServo.write(currentAngle);delay(300);randomSeed(analogRead(A2)^micros());}
void hopSegment(){
int segMs=map(smoothReadA0(),0,1023,MIN_SEG_MS,MAX_SEG_MS);
segMs=constrain(segMs,MIN_SEG_MS,MAX_SEG_MS);
int dir=(random(0,2)==0)?-1:+1;
int hopDeg=irand(MIN_HOP_DEG,MAX_HOP_DEG);
int target=constrain(currentAngle+dir*hopDeg,MIN_ANGLE,MAX_ANGLE);
int delta=target-currentAngle; if(delta==0) return;
int steps=max(1,segMs/STEP_INTERVAL_MS); float stepDeg=(float)delta/steps, pos=currentAngle; unsigned long t0=millis();
for(int i=0;i<steps;i++){pos+=stepDeg;tunerServo.write((int)round(pos));delay(STEP_INTERVAL_MS);}
tunerServo.write(target); currentAngle=target; unsigned long el=millis()-t0; if(el<(unsigned long)segMs) delay(segMs-el);
}
void loop(){hopSegment();}
Demo Video
Watch the sweep demo on TikTok (@sandiegohaunted)
Tips
- If audio dips when the servo moves, power the servo from a dedicated 5 V regulator and share grounds.
- Typical sweet spot: 200–350 ms per hop; adjust with the 50k knob.

© SanDiegoHaunted.com — Dan Scott.