Smart Radio Telescope
Radio Telescope Mount – Project Context
Project Overview
Azimuth/elevation/polarization mount for a satellite dish used as a radio-telescope antenna. Goal: a collaborative school demonstrator project.
System architecture
┌─────────────────────────────────────────────────────┐
│ Raspberry Pi │
│ - Stellarium protocol (TCP port 10001) │
│ - Web UI (Flask/FastAPI, port 80) │
│ - Coordinate math (RA/Dec ↔ Az/El) │
│ - GNSS directly over UART │
│ - WiFi │
│ - NTP / system time │
└──────────────┬──────────────────────────────────────┘
│ UART (ASCII protocol)
│ HOME / GOTO az el pol / STATUS / STOP
┌──────────────▼──────────────────────────────────────┐
│ Raspberry Pi Pico (RP2040) │
│ - Real-time motor control (3 axes) │
│ - Trapezoidal velocity profile │
│ - Homing routine (3-phase) │
│ - PIO for precise STEP pulse generation │
│ - Core 0: UART protocol / Core 1: motor control │
└──────┬──────────────┬──────────────┬─────────────────┘
│ │ │
┌──────▼──────┐ ┌─────▼──────┐ ┌────▼───────┐
│ DRV8825 │ │ DRV8825 │ │ DRV8825 │
│ Azimuth │ │ Elevation │ │ Polariza- │
│ Vref=0.15V │ │ Vref=0.15V│ │ tion │
└──────┬──────┘ └─────┬──────┘ │ Vref=? │
│ │ └────┬───────┘
┌──────▼──────┐ ┌─────▼──────┐ │
│ 17HS010-03N │ │ 17HS010-03N│ ┌────▼───────┐
│ Azimuth │ │ Elevation │ │ Bipolar │
└─────────────┘ └────────────┘ │ 4-wire │
│ pol. motor│
└────────────┘
Hardware
Stepper motors
Moons’ 17HS010-03N (×2, azimuth + elevation) - NEMA 17, hybrid stepper - Step angle: 1.8° (200 steps/rev) - Current: ~0.3A per phase - 6 wires (unipolar with center taps)
Wire assignment:
Black → Winding A end 1 (DRV8825: 1A)
Red → Winding A end 2 (DRV8825: 1B)
White → Center tap A → DO NOT CONNECT
Yellow → Winding B end 1 (DRV8825: 2A)
Blue → Winding B end 2 (DRV8825: 2B)
Orange → Center tap B → DO NOT CONNECT
Operation: bipolar (leave center taps open) → more torque.
IMPORTANT – check coil pairs with an ohmmeter before connecting! On the 6-wire motor the coils run diagonally in the connector (not sequentially):
Coil A: Pin 1 ──── Pin 2 (center tap, open) ──── Pin 4 → ~7Ω between Pin 1 and 4
Coil B: Pin 3 ──── Pin 5 (center tap, open) ──── Pin 6 → ~7Ω between Pin 3 and 6
Measurement: ~7Ω between the ends of one coil, ∞ between coil A and B.
Bipolar motor (polarization axis, ×1) - 4 wires, bipolar, no center tap - Rated current unknown → measure Vref before commissioning! - Measure coil resistance with a multimeter (between 1A↔︎1B and 2A↔︎2B) - Conservative starting value: Vref = 0.15V, then raise gradually - Operation: directly bipolar, connect all 4 wires
Motor drivers
DRV8825 (×3, purple breakout modules — A1=azimuth, A2=elevation, A3=polarization)
- Bipolar driver, 3.3V-logic compatible (VIH_min = 1.6V)
- Set Vref:
I_max = Vref / 0.5→ for 0.3A:Vref = 0.15V - R-sense on the board: R100 (0.1Ω)
- Before commissioning: 100µF electrolytic + 100nF ceramic between VMOT and GND
Microstepping (M0/M1/M2):
M2=GND M1=3.3V M0=3.3V → 1/8 step → 1600 steps/rev ← recommended
DRV8825 pinout:
Left (top→bottom): DIR, STP, SLP, RST, M2, M1, M0, EN
Right (top→bottom): GND, FLT, 2A, 1A, 1B, 2B, GND, VMOT
FLT pin: on the present clone breakout boards it is not broken out — pin 2 is VDD there (3.3V logic supply). No-connect in the schematic. The DRV8825 chip has internal OCP/OTP protection and shuts down internally on a fault, but without feedback to the MCU.
Between DRV8825 and motor: nothing required. For cable length > 40cm: 33Ω series resistors on each motor line.
Endstops
- 1× per axis (all three axes), active-LOW (switch closes to GND)
- External pull-ups on the board (R1/R2/R3); internal pull-up additionally enabled
- HW RC debounce (C1/C4/C5) is defective → debouncing must be done in software
- Pins: AZ=GP14, EL=GP15, ROT=GP16 (see pin assignment above)
Status LEDs
6× LEDs each with a 330Ω series resistor to GND (3.3V logic):
| LED | Function |
|---|---|
| LED AZI | azimuth endstop reached |
| LED ELE | elevation endstop reached |
| LED POL | polarization endstop reached |
| LED HOMING | homing routine active |
| LED MOVING | motor in motion |
| LED TRACKING | tracking mode active |
Microcontroller
Target architecture: Raspberry Pi Pico (RP2040)
- Dual-core Cortex-M0+, 133 MHz
- PIO (Programmable I/O) for hardware STEP pulse generation
- 264KB RAM, 2MB flash, built-in USB
- 3.3V logic → directly compatible with the DRV8825
Pico support circuit:
VSYS → 5V (USB or external 5V)
3V3 → 3.3V output for logic (DRV8825 VDD, SLP, M0/M1)
GND → GND
USB → built in, no external chip needed
SWD → 3-pin header (SWDIO, SWDCLK, GND) for J-Link debugging
Hard-wired on the board:
DRV8825 SLP (all 3) → 3.3V (never sleep)
DRV8825 M0 (all 3) → 3.3V ┐
DRV8825 M1 (all 3) → 3.3V ├ 1/8 step fixed
DRV8825 M2 (all 3) → GND ┘
Pico GPIO pin assignment (authoritative — from the KiCad netlist hardware/motor/):
GP0 UART0 TX → Raspberry Pi RX
GP1 UART0 RX ← Raspberry Pi TX
Axis DIR STEP RST EN SW (endstop, active-LOW)
AZ GP6 GP7 GP8 GP9 GP14
EL GP10 GP11 GP12 GP13 GP15
ROT GP5 GP4 GP3 GP2 GP16 (ROT = polarization / feed rotation)
GP26 ADC0 = AnalogIN1 GP27 ADC1 = AnalogIN2 GP28 ADC2 = AnalogIN3
SDA / SCL I2C (connector J8 / J11)
GP25 onboard LED (heartbeat / debug)
- EN and RST are active-LOW; RST must be driven actively HIGH (reset released) — glitch-free:
gpio_putBEFOREgpio_set_dir. - Endstops are active-LOW; the HW RC debounce on the board is defective → software debounce required.
- ROT is wired in descending order (EN=GP2, RST=GP3, STEP=GP4, DIR=GP5).
✅
firmware/pico_motor/config.hwas corrected to this netlist on 2026-06-25 (pins + RST + DIR invert + step limits). Earlier pin lists (AZI=GP2/3/4…) are obsolete.
Axis calibration (bench bring-up, 2026-06-25)
Bench-measured with firmware/bringup_console/ (steps at 1/8 microstep = 1600/rev):
| Axis | DIR flip vs. default | Travel after homing (steps) | Note |
|---|---|---|---|
| AZ | flipped | 0 … 16500 | — |
| EL | flipped | 0 … 5800 | — |
| ROT | no flip | 0 … 22000 | high gear reduction → homing backoff 2× |
The max values are hard limits (do not exceed). DIR flips and soft limits are stored in the production firmware (config.h). Bring-up homing constants: fast 640 / slow 160 / timeout 64000, backoff 400 (AZ/EL) or 800 (ROT).
Division of labor (decided 2026-06-25): the MC keeps the degree interface (GOTO/POS in degrees) and converts degrees↔︎steps itself — via a two-point calibration (*_STEPS_PER_DEG + *_HOME_OFFSET_DEG = angle at the endstop), no longer via gear ratios. The RasPi does the coordinate math (RA/Dec→Az/El) and sends degrees. steps = (deg − offset)·steps_per_deg. Calibration values still TODO (measure at two known angles per axis).
GNSS receiver
- NMEA 0183 over UART, 9600 baud (typically NEO-6M/7M/8M/M10)
- Provides: UTC time + date, lat/lon, altitude, satellite count, HDOP
- Replaces NTP and hard-coded coordinates
- On the final board: directly on the Raspberry Pi UART
Wiring:
GNSS TX → RPi/ESP32 RX
GNSS RX → RPi/ESP32 TX (optional)
GNSS VCC → 3.3V
GNSS GND → GND
Raspberry Pi
- Handles: Stellarium protocol, web UI, coordinate math, GNSS, WiFi
- Communicates with the Pico over UART (ASCII protocol)
- Python software (still to be written)
Communication protocol RPi → Pico (UART, ASCII)
The complete, authoritative protocol reference is in firmware/pico_motor/UART_PROTOCOL.md — transport (115200 8N1, CR/LF tolerance), commands (HOME, GOTO, MOVE, STOP, STATUS, SLEEP/WAKE, ENABLE/DISABLE, SET ADC_RATE), telemetry/events (POS, ADC, EVT), and the HOMED lifecycle.
Coordinate math
Local sidereal time (LST)
jd = 2440587.5 + unix_timestamp / 86400.0
T = (jd - 2451545.0) / 36525.0
gst = 280.46061837 + 360.98564736629*(jd-2451545) + 0.000387933*T² - T³/38710000
lst = (gst + lon_deg) % 360.0 # in degrees, then → radiansEquatorial → Horizontal
ha = lst - ra
alt = asin(sin(dec)*sin(lat) + cos(dec)*cos(lat)*cos(ha))
az = acos((sin(dec) - sin(alt)*sin(lat)) / (cos(alt)*cos(lat)))
if sin(ha) > 0: az = 2π - az
Horizontal → Equatorial
dec = asin(sin(alt)*sin(lat) + cos(alt)*cos(lat)*cos(az))
ha = acos((sin(alt) - sin(dec)*sin(lat)) / (cos(dec)*cos(lat)))
if sin(az) > 0: ha = -ha # eastern sky
ra = (lst - ha) % 2π
Stellarium Telescope Protocol (TCP, binary, little-endian)
Client → Server (GOTO, 20 bytes):
[0-1] uint16 length = 20
[2-3] uint16 type = 0
[4-11] int64 timestamp µs (ignored)
[12-15] uint32 RA (0=0h, 0xFFFFFFFF≈24h, full circle=2^32)
[16-19] int32 Dec (+0x40000000=+90°, -0x40000000=-90°)
Server → Client (CURRENTPOS, 24 bytes):
[0-1] uint16 length = 24
[2-3] uint16 type = 0
[4-11] int64 server timestamp µs
[12-15] uint32 RA
[16-19] int32 Dec
[20-23] int32 status = 0 (OK)
Encoding:
RA uint32 = ra_rad / (2π) * 4294967296
Dec int32 = dec_rad / (π/2) * 1073741824
Existing code files
1. telescope_mount.ino
ESP32 standalone (no WiFi): - StepperAxis class with trapezoidal profile - Homing (3-phase) - Serial interface: home, goto az el, pos, stop, sleep, wake
2. telescope_mount_wifi.ino
ESP32 with WiFi + Stellarium + web UI: - Everything from version 1 - WiFi + NTP - HTTP web UI (port 80) - Stellarium TCP server (port 10001) - RA/Dec ↔︎ Az/El coordinate conversion
3. telescope_mount_gnss.ino
ESP32 with GNSS + WiFi + Stellarium + web UI: - Everything from version 2 - TinyGPS++ library (UART2, GPIO16/17) - GNSS replaces NTP and hard-coded coordinates - System time set via settimeofday() from GPS - GNSS status in the web UI (fix, lat/lon, alt, sats, HDOP) - NTP as a fallback when there is no GNSS fix
Firmware status: - ✅ Pico firmware written: firmware/pico_motor/ (3 axes, UART protocol, PIO, ADC) — compiles. config.h brought to the correct netlist + bring-up calibration (pins, RST, DIR invert, step limits) on 2026-06-25. - ✅ Bench bring-up tool: firmware/bringup_console/ (interactive USB-CDC console, see its README). - ✅ Endstop bring-up tool: firmware/bringup_endstops_uart/ (reports debounced endstop state over UART with all drivers disabled). - ✅ Bench hardware bring-up verified 2026-07-04 — UART protocol (both directions), all 3 endstops, per-axis + combined homing, multi-axis GOTO, and SCAN in both sweep-axis roles. Full protocol: docs/test-logs/2026-07-04-motor-controller-bench-test.md. - ⬜ Raspberry Pi Python software (Stellarium + UART + web UI) — still to be written.
Motion Parameters (all three .ino files)
MICROSTEPS = 8 // M0=H M1=H M2=L
STEPS_PER_REV = 1600 // 200 * 8
MIN_SPEED_SPS = 80 // start/stop speed
MAX_SPEED_SPS = 1600 // 1 rev/s cruise
PIO_BLOCK_STEPS = 25 // speed-update granularity (ramp/block levels)
AZ/EL/POL_ACCEL_STEPS = 200 // per-axis ramp length (was global 800; 1/4 since 2026-07-15)
HOMING_SPEED = 320 // 0.2 rev/s
HOMING_TIMEOUT = 32000 // 20 revolutions max
HOMING_BACKOFF = 200 // steps after first endstop contact
// Step<->degree calibration (two-point linear) — replaces gear ratios:
AZ/EL/POL_STEPS_PER_DEG = STEPS_PER_REV/360 // TODO: measure per axis
AZ/EL/POL_HOME_OFFSET_DEG = 0.0f // TODO: angle at the endstop
AZ/EL/POL_MAX_STEPS = 16500 / 5800 / 22000 // hard travel limitsKiCad Board (in progress)
KiCad version: 8.0.9 on Ubuntu 24.04
Current state: - Root sheet present with 2× DRV8825 + capacitors - Second sheet created - STM32F411CEUx symbol: library MCU_ST_STM32F4 → STM32F411CEUx
Planned sheet structure:
Root
├── power.kicad_sch – 12V input, decoupling caps, 3.3V LDO
├── pico.kicad_sch – Raspberry Pi Pico, SWD header, UART connector
├── motor_azi.kicad_sch – DRV8825 AZI + motor connector + caps
├── motor_ele.kicad_sch – DRV8825 ELE + motor connector + caps
├── motor_pol.kicad_sch – DRV8825 POL + motor connector + caps
└── leds.kicad_sch – 6× status LED + 330Ω series resistors
Useful KiCad shortcuts:
A Add symbol
S Add sub-sheet (drag a rectangle!)
E Properties of the selected element
P Draw wire
Q Add power symbol
R Rotate symbol
G Grab wire (moves connected wires along)
Open items / Next Steps
Hardware / KiCad
Software
-
- UART protocol handler (Core 0)
- StepperAxis class with trapezoidal profile (Core 1)
- PIO for STEP pulse generation
- Homing routine with internal pull-up
- Status-LED control
-
- Serial interface to the Pico
- Stellarium TCP server (port 10001)
- GNSS (pyserial + pynmea2)
- Coordinate math
- Web UI (FastAPI + uvicorn recommended)
Motion / Noise
Test sequence
- Pico blink test (verify the toolchain) ✅
- Single-axis stepper test (1 DRV8825) ✅
- Set Vref (multimeter at TP) → 0.15V for 0.3A
- Verify the homing routine (endstops) ✅ (2026-07-04; endstops + per-axis & combined homing)
- All 3 axes simultaneously ✅ (2026-07-04; combined
HOME+ concurrent multi-axisGOTO+SCAN) - Test the RPi ↔︎ Pico UART protocol ✅ (2026-07-04; protocol verified via USB-TTL adapter — RPi backend integration still pending)
- Connect Stellarium and test GOTO
- Verify the GNSS fix
Bench-test details for steps 4–6:
docs/test-logs/2026-07-04-motor-controller-bench-test.md
Dependencies / Libraries
Arduino (ESP32)
TinyGPS++ – GNSS NMEA parser (Arduino Library Manager)
WebServer.h – included in the ESP32 Arduino core
WiFi.h – included in the ESP32 Arduino core
Pico C SDK
pico_stdlib – GPIO, UART, timing
hardware_pio – PIO for STEP pulse generation
hardware_uart – UART0 to the Raspberry Pi
pico_multicore – dual-core (Core 0 / Core 1)
Python (Raspberry Pi, still to install)
pyserial – UART to the Pico
pynmea2 – GNSS NMEA parser
fastapi – web UI
uvicorn – ASGI server
astropy – optional, for more precise coordinate math
References
- DRV8825 datasheet: https://www.ti.com/product/DRV8825
- RP2040 datasheet: https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
- Stellarium Telescope Protocol: https://github.com/Stellarium/stellarium/blob/master/plugins/TelescopeControl/src/TelescopeClient.hpp
- Moons 17HS010-03N: NEMA17 hybrid stepper, 0.3A, 1.8°/step ```