← EYTraker home

ESP32 antenna rotator controller

EYRotator

An ESP32-based rotator controller with a built-in web interface, 20×4 LCD, rotary encoder and physical buttons. It reads the rotator’s position feedback on an ADC input, converts it to azimuth from stored calibration points, and drives separate CW and CCW relays.

Includes automatic and manual calibration, park and three presets, WSJT-X grid-bearing integration, USB serial control, an optional temperature sensor with history graph, and OTA firmware updates.

Firmware v97.14 ESP32 Web interface Auto-calibration WSJT-X MQTT OTA
EYRotator web interface showing a compass dial with azimuth 334° and target 335°, and the controls to turn to a target heading

Read this first

Safety: the controller operates an antenna rotator with energised direction relays. Confirm the antenna and rotator can move freely, that wiring and limit arrangements are correct, and that the installation can be stopped safely. Do first tests with the drive supply isolated where possible. Never assume a software limit substitutes for mechanical protection.

Source basis: values and behaviour on this page were reviewed against the v97.14 source (Rotator_Controller_ESP_v97.14.02.11.2026.ino, guide dated 2 November 2026). Hardware supply voltages and exact PCB wiring depend on your installation, so verify against your assembled PCB. Firmware compile and on-hardware behaviour have not been independently validated for this guide.

1System overview

The ESP32 reads the rotator rheostat voltage on ADC1 GPIO34, converts it to antenna azimuth using stored calibration points, and drives separate CW and CCW relays.

The controller supports a 20×4 I²C LCD, a rotary encoder, physical direction and park buttons, a browser interface and USB serial protocols. Optional features include WSJT-X UDP grid-bearing integration, DS18B20 temperature monitoring and a 120-minute temperature graph.

Block diagram of the rotator measurement and control architecture
Figure 1: conceptual rotator measurement and control architecture.

Pin assignments

FunctionESP32 pin / interfaceNotes
Position feedbackGPIO34 (ADC1)Analogue input; external divider/filter and common reference as designed
CW relayGPIO16Default active LOW
CCW relayGPIO17Default active LOW
Brake relayGPIO18Default active LOW
Spare relayGPIO19Optional
Cooling fanGPIO13N-channel MOSFET, active HIGH
Encoder CLK / DT / switchGPIO32 / 33 / 25Switch uses input pull-up
CW / CCW / Park buttonsGPIO26 / 27 / 14Active LOW to ground
LCD I²C SDA / SCLGPIO21 / 22Firmware probes 0x27; 20×4 LCD
DS18B20GPIO4Optional temperature sensor
USB serial9600 baudDefault protocol: EYRotator

1AFirmware installation: full BIN

Before applying motor power, connect the ESP32 to a Windows PC using a USB data cable and identify the serial COM port in Device Manager. In the EY Rotator ESP32 Flash Utility, select the ESP32 chip, choose the matching v97.14 full firmware image and flash it at address 0x0000. Wait for completion, then restart the controller.

The merged full BIN contains the bootloader (0x1000), partition table (0x8000), boot_app0 (0xE000) and application (0x10000). Use the correct 4 MB flash configuration and matching partition scheme.

Full BIN vs OTA BIN: a standalone application BIN is not interchangeable with a merged full BIN. ElegantOTA requires the application-only BIN, not the full image. The firmware .ino file alone is not a flashable BIN.

For a firmware upgrade, the retained NVS configuration normally preserves Wi-Fi, calibration and other saved settings. For true factory defaults, a full factory reset or erased configuration NVS is required. This also removes saved calibration and Wi-Fi credentials.

Example esptool command

Adjust the COM port, esptool location and file path. The filename is illustrative; use your actual full BIN.

esptool.exe --chip esp32 --port COM14 --baud 921600 --before default_reset --after hard_reset write_flash -z 0x0 "EYRotator_v97.14_full.bin"

2First power-up and Wi-Fi setup

  1. Before applying motor power, verify relay polarity, correct GPIO wiring, ADC input voltage and common/isolated references according to the PCB design. The firmware assumes active-LOW relays on a fresh installation.
  2. Power the ESP32. If valid Wi-Fi settings are absent or the connection fails, the controller starts its local configuration access point.
  3. Connect a phone or computer to the open Wi-Fi network Rotator-Setup, then open http://192.168.4.1 in a browser.
  4. Enter your local 2.4 GHz Wi-Fi SSID and password on the Wi-Fi configuration page. Saving credentials restarts the controller. If the details are wrong, the AP is available again after the connection attempt fails.
  5. Once on the LAN, open the ESP32 IP shown on the LCD, or use the configured hostname or network discovery where available. Open Setup to inspect calibration and other settings.
Home Wi-Fi setup page with SSID, password, DHCP or static addressing and a Save Wi-Fi and Restart button
The Home Wi-Fi setup page.

Important: the fallback AP is open (no password). Setup authentication exists in v97.14 but is OFF by default, so enable it and use unique credentials. Do not assume Setup authentication also protects every motor-control endpoint.

3First-use calibration: two paths

A fresh installation or full factory reset requires valid CCW/CW endpoint calibration before ordinary automatic movement is enabled. Wi-Fi configuration is not a prerequisite for commissioning if the AP-mode LCD calibration screen is available.

Flow chart of first-use calibration and normal operation
Figure 2: first-use calibration and normal-operation flow.

3A. Standalone LCD auto-calibration (no Wi-Fi connection)

LCD calibration screens: calibration required, auto calibration running, and calibration complete
Figure 3: LCD calibration screens. ADC values are illustrative.
  1. With the configuration AP active and calibration required, the LCD displays CALIBRATION REQUIRED, WiFi AP: ACTIVE, Press ENC to AutoCal and the AP IP.
  2. Ensure the rotor can safely traverse to both mechanical endpoints. Press and release the encoder switch once to start.
  3. The LCD shows AUTO CALIBRATION, the active CCW/CW leg and the live ADC reading. A deliberate encoder press while running aborts calibration.
  4. The firmware drives to the CCW limit, observes stable feedback, then drives toward the CW limit. Allow the entire sequence to complete.
  5. After success the LCD displays CALIBRATION COMPLETE, both captured ADC values and Press ENC to SAVE. Press and release the encoder switch again to save.
  6. The endpoints are committed to NVS, the calibration interlock clears, and the rotator can be operated locally without a working Wi-Fi connection.

The firmware allows 120 seconds per automatic calibration leg. Endpoint detection uses a stable ADC window of approximately 4 seconds, plus a CW-leg travel/span plausibility check. A failed or aborted sequence does not authorise normal operation. The Save press is separate from the Start press.

3B. Web-based auto-calibration

  1. Connect to the ESP32 web Setup page (LAN or configuration AP).
  2. Select AUTO CALIBRATE and monitor CCW/CW progress and ADC status.
  3. After success, the green SAVE CALIBRATION button appears beside ABORT CALIBRATION.
  4. Press SAVE CALIBRATION to commit the endpoints to NVS. Successful detection alone does not save them.
Mechanical Stop ADC Calibration page with Store Full CCW, Store Full CW, Store North, Auto Calibrate and Abort Calibration buttons
The calibration section of the Setup page.

3C. Manual three-point calibration

Use manual capture if the physical −180°, North 000° and +180° reference positions can be positioned accurately. Capture the CCW and CW endpoints from Setup while the rotator is stationary. Then position at true North and capture the optional North point. The North measurement corrects midpoint asymmetry.

The endpoint captures must be plausible. Changing either endpoint invalidates the previous measured North point.

If North has not been measured, the firmware uses (ADC_CCW + ADC_CW) / 2. The Setup button indicates whether North is calibrated and, when measured, shows the signed Delta ADC = ADC_North − calculated midpoint. The stored keys include adcccw, adccw, adcnorth and northvalid. The calibration-required interlock is tracked separately.

4Position measurement and ADC principles

The 500 Ω rotator rheostat produces a position-dependent voltage. The PCB analogue divider and filter scale it for the ESP32 ADC. The original hardware concept spans about −180° at the CCW endpoint, through North (000°), to +180° at the CW endpoint. Actual ADC counts depend on the divider ratio, reference voltage, wiring and ADC response. Never assume the example count of 2994 applies to every PCB.

The ESP32 ADC is 12-bit nominal. The firmware samples GPIO34 repeatedly, applies trimming and filtering, and can distribute samples across complete nominal mains cycles (50 Hz default) to reduce AC interference. It uses different filtering while stationary and while moving, balancing a stable display against timely motor stopping. Test changes to sampling, filtering and overshoot settings carefully.

4A. Two-segment calibration mapping

Graph of ADC endpoints and the optional measured North calibration point
Figure 4: ADC endpoints and optional measured North calibration.

Let C be the measured CCW ADC, N the North ADC (measured or calculated) and W the CW ADC. For a reading A between C and N, azimuth is interpolated across −180° to 0°. For A between N and W, it is interpolated across 0° to +180°. Negative azimuths are wrapped for the 000–359° compass display. This allows different slopes on the two sides of North.

θ = −180 + 180 × (A − C) / (N − C) for C ≤ A ≤ N
θ = 180 × (A − N) / (W − N) for N ≤ A ≤ W

These explain the three-point method; the actual source also handles wrapping, filtering and limits.

Example: C = 0 and W = 2994 give a calculated N of 1497. If the measured N is 1542, Delta ADC = +45. The midpoint offset is a useful comparison between resistor-divider PCB variants, but it is not itself an angle error without conversion through the calibrated slopes.

4B. Turn and safety functions

4CFirst controlled motor turn

  1. Confirm that CCW and CW calibration has completed and has been saved. Check that the displayed azimuth is stable and matches the physical direction.
  2. Verify that the mechanical end stops and the motor isolation / STOP arrangements work. Keep the antenna and rotator clear of obstacles.
  3. On the Mobile dashboard, enter a target only 5–10° from the present azimuth, well away from the CW and CCW limits.
  4. Press TURN TO (target). Changing the target field alone does not command motion. Observe the actual direction and azimuth feedback.
  5. Confirm the controller stops close to the target. Use STOP or isolate motor power immediately if movement is incorrect.
  6. Repeat a small movement in the opposite direction. Only then test longer moves, PARK, presets and the 180° turn function.

Caution: do not use CW LIMIT, CCW LIMIT, PARK or 180° TURN for the first live motor movement.

5Web interface

The Mobile dashboard, annotated.

Annotated screenshot of the Mobile dashboard explaining the compass, azimuth and target readouts, turn buttons, grid bearing window, stop and limit buttons, presets, park, 180 degree turn, diagnostics and setup
The Mobile dashboard and what each control does.
Expanded Diagnostics panel showing live ADC value, temperature, cooling fan, MQTT, ADC processing, mains sync, stationary smoothing, samples and position lock
The Diagnostics panel expands when selected.
Learning panel showing short and long CW and CCW overshoot values with recent activity
The rotational learning panel.

The rotational learning algorithm can be activated in Setup, allowing the controller to estimate turn overshoot more accurately.

v94.01 compass update: the Mobile compass draws a translucent red pie-shaped keep-out sector from the centre to the circumference between the configured CW and CCW limits. With the defaults (CW 179°, CCW 181°) this is a narrow 2° wedge around south. The screenshot above predates this overlay; the on-device display is authoritative.

The red wedge is a display aid only. Software and physical motion protections remain separate. CW/CCW markers, preset markers and target indicators continue to be displayed.

6Operating controls

ControlTypical use
Web dashboard / Classic viewView azimuth and target; command turns, park, presets and grid-based bearings
USB serialEYRotator or Yaesu-compatible remote control at 9600 baud (select the protocol in Setup)
CW / CCW pushbuttonsManual directional operation; the first press during an auto-turn stops it
Park buttonRequest the stored Park position
Rotary encoder + LCDLocal operation and Setup; during AP commissioning, press to start, abort or save auto-calibration
WSJT-X UDPOptional, disabled by default; reads grid information, then requires user action for the bearing turn

WSJT-X grid bearing integration

To use the WSJT-X interface, first activate the UDP listener in Setup (default is OFF) and enter your own grid location in 6-character format.

  1. In the rotator Setup page, turn Grid Bearing / Distance and WSJT-X UDP Listener ON, enter your 6-character local grid locator and keep the UDP port (default 2237).
  2. In WSJT-X, open Settings → Reporting and add the IP address of the router in the UDP text box. Save.
  3. When you double-click a DX station, its grid locator is transferred automatically to the grid square box on the dashboard. Select Calculate to get the short-path and long-path directions.
Setup options for Grid Bearing and WSJT-X UDP Grid Import, with listener ON and port 2237
Setup options for grid bearing and the WSJT-X UDP listener.
Grid Bearing and Distance panel showing a short path of 334 degrees and a long path of 154 degrees
Short-path and long-path results after selecting Calculate.

7Temperature history and storage

The optional DS18B20 sensor on GPIO4 is read using non-blocking conversions. The Setup page graph covers 120 minutes, with a sample every 30 seconds. Current, minimum and maximum temperatures are shown, and unavailable readings appear as gaps.

The firmware uses a RAM circular buffer (241 positions) and LittleFS snapshots nominally every 5 minutes. It can initialise an unmountable filesystem by formatting the filesystem partition. This may erase other filesystem files but does not intentionally erase the separate NVS partition. History restoration depends on a synchronised clock. The Setup diagnostics report filesystem readiness, time synchronisation, saved sample count and save/restore state. SAVE HISTORY NOW allows an immediate test.

Test persistence: verify LittleFS READY and clock SYNCED, accumulate several samples, press SAVE HISTORY NOW, restart without erasing flash, and verify that earlier timestamped points return. Power-off periods should remain gaps. Use the selected 4 MB flash partition scheme with a usable filesystem region (for example 1.2 MB APP / 1.5 MB SPIFFS).

8Setup options and factory defaults

These are the values used when the corresponding NVS keys do not already exist. Existing stored settings override them. Some values are placeholders or dynamically managed, and are not proof of completed calibration.

Show the full defaults table
Category / settingDefault
Wi-Fi AP / IPRotator-Setup (open) / 192.168.4.1
Wi-Fi modeDHCP; SSID and password initially blank
NTP / UTC offset / DSTpool.ntp.org / +120 minutes / OFF
Calibration CCW / CW ADC0 / 2994 (placeholders; the calibration interlock applies)
North calibrationCalculated midpoint 1497; measured North OFF
CW / CCW travel limits179° / 181° (internal wrapped convention)
Park position000°
Long Cal CW / CCW (overshoot)1.5° / 1.5° (replaced by the learned value in LEARNING/FROZEN)
Short-turn threshold / Short Cal CW / CCW5.0° / 1.5° / 1.5°
Target deadband±1.0°
ADC samples576
ADC processing / stationary smoothingON / ON
Auto-learn overshootOFF (Setup: OFF / LEARNING / FROZEN)
Mains synchronisationON; 20,000 µs period (50 Hz); 250 max samples/cycle
Moving ADC response / update interval95% / 40 ms while turning (100 ms stationary)
Cooling fan (GPIO13)AUTO; fan ON ≥ 40 °C, OFF ≤ 35 °C
Relay active levelLOW
Normal motion timeout / brake delay120 s / 500 ms
Button debounce150 ms
Web refresh500 ms
LCD heartbeat / leading zero / park markerON / ON / ON
LCD digit styleCompact 2×3 (v94 default)
LCD Setup menu / show target TOFF / OFF
DS18B20 temperature displayON
Compact dashboard / mobile compassCompact dashboard OFF / mobile compass ON
Speech mode / announcement intervalOFF / 10°
USB serial protocol / diagnosticsEYRotator / OFF; serial 9600 baud
WSJT-X UDP / portOFF / 2237
Grid-bearing support / local locatorON / blank
Auto-calibration timeout120 seconds per leg (fixed constant)
Temperature history120 min / 30 s sampling / 5 min flash save
Mobile large azimuth/target / compass labelsON / ON
Mobile compass prohibited sectorTranslucent red pie wedge between the CW and CCW limits (v94.01)
Setup authenticationOFF initially; configure a username and password before network use
MQTTOFF by default; broker host, port, username and password blank until set in Setup. Topic prefix: EYRotator/<device id>/, where the device id is the rotator name plus the full 12-hex-digit ESP32 MAC address

9Troubleshooting and verification

SymptomCheck / action
No LAN connectionCheck SSID/password, the 2.4 GHz network and DHCP. Connect to the Rotator-Setup AP at 192.168.4.1
CALIBRATION REQUIREDComplete and explicitly save endpoint calibration via the LCD (AP mode) or web Setup
Calibration failed / timed outCheck physical travel, rheostat wiring, ADC progress, relays and mechanical stops; inspect the LCD/web status
North not calibratedExpected after endpoint auto-calibration; the midpoint is used until a manual North capture
Wrong azimuth or non-linear responseCheck the ADC divider, a stable reference, endpoint capture and the optional North correction
Motor fails to startCheck the interlock, fault status, relay polarity, supply voltage under load and the timeout
Temperature graph empty after rebootCheck LittleFS READY, NTP SYNCED and the saved sample count; use SAVE HISTORY NOW and retest
Unexpected LCD source labelWEB = browser, USB = serial, BTN = local; verify the command source and firmware build

10Commissioning acceptance checklist

Scope and pending improvements: earlier firmware audits identified ADC initialisation, API response, OTA interruption, authentication/CSRF, watchdog and maintenance considerations. These have not all been independently verified on deployed v97.14 hardware. Setup authentication is available but disabled by default; verify protection for motor commands and OTA separately. The configuration AP remains open by design.

Advanced technical background

For builders who want the detail behind the ADC input circuit and mains-synchronous sampling. Figures are calculated from nominal component values; the bench checks below confirm them.

ADC input circuit: technical evaluation

The front end scales the 0 to 14.25 V position-sensor signal to 0.235 to 2.33 V (Option A) or 0 to 2.91 V (Option B) for ESP32 GPIO34 (ADC1, 11 dB attenuation). It low-pass filters the signal with a corner near 30 Hz and protects the pin with an SMAJ15A TVS and a BAT54S clamp.

Circuit description

  1. Input protection. TVS1 (SMAJ15A) connects from the sensor line to ground at the cable entry, ahead of R1. It limits surges to about 24 V and does not conduct at the normal 14.25 V maximum.
  2. Divider and offset (node A). R1 feeds node A in series from the sensor line. R2 connects node A to 3.3 V and R3 connects it to ground. Together they scale the input by 0.147 and add a 0.235 V offset, keeping the signal clear of the ESP32 ADC’s poor response near 0 V. C1 (100 nF) shunts noise to ground.
  3. Filter (node B). Ferrite bead FB1 couples node A to node B. C2 (1 µF) and C3 (10 nF) connect node B to ground.
  4. Pin clamp. D1, a BAT54S dual Schottky, sits at node B: one diode from ground to the node, one from the node to 3.3 V. GPIO34 connects to node B.
ResistorOption A: offset dividerOption B: plain divider
R1 (series from the sensor)33 kΩ39 kΩ
R2 (to 3.3 V)68 kΩnot fitted
R3 (to ground)6.2 kΩ10 kΩ
Schematic of the ADC input circuit with resistor options A and B marked
ADC input circuit with resistor options A and B.

DC transfer function (Option A)

V_pin = 0.1469 × V_in + 0.235 V
ConditionInput (V)Pin (V)
Sensor at 0 V (CCW end)00.235
Mid-scale7.1251.282
Full scale14.252.328
End of linear ADC range at 11 dB (about)15.12.45
ADC saturation (about)19.53.1
Clamp starts to conduct (about)22.93.6

Headroom above full scale before the ADC leaves its linear region is about 5.8%. At roughly 0.75 to 0.85 mV per count, the full span is about 2,500 to 2,800 counts, or about 7 counts per degree (0.14° per count) if 0 to 14.25 V covers 360°. The firmware’s endpoint calibration stores the actual counts, so the exact figures are not critical.

Key points

  • Loading: Option A presents about 38.7 kΩ to the sensor line and draws at most 0.36 mA. With the 500 Ω rheostat wired as a potentiometer, the loading bow is at most about 0.19% of full scale (about 0.7° over 360°). Confirm the rheostat wiring from the rotator documentation.
  • Filtering: a single-pole low-pass with a corner near 30 Hz gives only 6 to 7 dB of attenuation at 50 or 60 Hz mains, so hum rejection relies mainly on the firmware (oversampling, trimmed mean and smoothing). The output settles to one 12-bit step in about 45 ms.
  • RF: the circuit does not stop common-mode RF current on the cable. Fit a snap-on ferrite clamp where the cable enters the enclosure. No rejection of 2.4 GHz Wi-Fi is claimed.
  • Protection: the TVS limits sensor-line surges to about 24 V, and R1 with the BAT54S holds the pin between about −0.3 V and 3.6 V. For lightning-prone installations add a gas-discharge tube at the cable entry.
  • Accuracy: endpoint calibration removes fixed gain and offset errors. The offset follows the 3.3 V rail (71 mV at the pin per volt of rail change), and the ESP32 ADC is non-linear near the ends of its range. Firmware v97.14 has no curve-compensation table (removed in v97.00), so endpoint (and optional North) calibration is essential. Re-run it after fitting these parts.
  • Back-feed: if the sensor line can be energised while the controller is unpowered, D1 and R2 feed up to about 0.4 mA into the 3.3 V rail. Check that this does not partially power the ESP32 or disturb its start-up.

Choosing between Options A and B

Option A is the better electrical design; Option B is the pragmatic choice when you want the simplest circuit and the existing calibration.

AttributeOption AOption BAdvantage
Resistors33k / 68k / 6.2k39k / none / 10kB (one fewer part)
Pin voltage range0.235 to 2.33 V0 to 2.91 Vnone
ADC linearityWhole span inside the linear windowFirst 5% and last 16% of travel outside it (about 76° of 360°)A
Counts over the full spanAbout 2,700About 3,500B
Over-range before the ADC clipsAbout 19.5 VAbout 15.2 VA
3.3 V rail dependence71 mV per V of railNoneB
Mains attenuation (50 / 60 Hz)−5.9 / −7.1 dB−9.4 / −10.8 dBB
Settling to one 12-bit step45 ms73 msA
Sensor-line loading38.7 kΩ; 7 µA pushed back at 0 V49 kΩ; no back-currentB
End-of-travel compressionMinimalPresent (~21% of travel); not software-corrected in v97.14A

Choose Option A if accuracy near the end stops matters, since v97.14 has no curve table to compensate. Choose Option B if you want to keep the existing calibration and the simplest circuit and can accept reduced accuracy near the CCW and CW ends.

Switching options: change R1 and R3 and fit or remove R2, repeat the CCW and CW endpoint calibration from Setup, re-confirm endpoint (and optional North) calibration, then repeat the bench tests below.

Component specification

RefOption AOption BSpecification
TVS1SMAJ15Asame400 W unidirectional TVS, 15 V standoff, SMA. From the sensor line to ground at the cable entry, ahead of R1
R133 kΩ39 kΩ1%, 0.1 W or more, metal film, 100 ppm/°C or better
R268 kΩnot fitted1%, 0.1 W. Offset from 3.3 V (Option A only)
R36.2 kΩ10 kΩ1%, 0.1 W. Divider to ground
C1100 nFsameX7R ceramic, 16 V or more, at node A
FB1Ferrite beadsame600 Ω or more at 100 MHz, DC resistance 1 Ω or less, 0603 to 1206. May be bridged with a 0 Ω link (less RF isolation)
C21 µFsameCeramic X7R, 10 V or more, at the ESP32 pin
C310 nFsameCeramic, 16 V or more, at the ESP32 pin. Keep even if FB1 is bridged
D1BAT54SsameDual Schottky, series pair: pin 1 to GND, pin 2 to 3.3 V, pin 3 to node B. BAS40-04 has the same arrangement (check the datasheet pin-out)

Avoid power Schottkys such as the 1N5817 (high leakage) and 1N4148 diodes (about 0.65 V drop lets the pin reach about 4 V).

Layout and installation

  • Place TVS1 where the sensor cable enters the enclosure, ahead of R1, with leads under about 10 mm. Lead inductance adds directly to the clamp voltage.
  • Place R1, R2, R3 and C1 close to TVS1 with a short ground return. Place FB1, C2, C3 and D1 within about 10 mm of GPIO34.
  • Decouple the 3.3 V end of R2 with 1 µF and 100 nF close to the divider, because rail noise reaches the pin at about 7% of its amplitude.
  • Route the ADC trace away from the Wi-Fi antenna, relay-driver traces and the switching supply.
  • Use twisted or shielded cable for the sensor line, with the shield connected to ground at the controller end, and fit a snap-on ferrite clamp where it enters the enclosure.

Bench verification

  1. With the controller powered and the input at 0 V, measure GPIO34. Expect 0.235 V ± 0.01 V.
  2. Apply 14.25 V (or the sensor’s full-scale voltage). Expect 2.33 V ± 0.04 V.
  3. Apply 7.125 V. Expect 1.28 V ± 0.03 V.
  4. Read the ADC counts on the serial monitor at each point. They should rise steadily at about 0.75 to 0.85 mV per count. Then run the CCW and CW endpoint calibration.
  5. With the rotator idle and Wi-Fi active, watch the Min, Max and Spread values on the serial diagnostic line for a minute. A spread of more than about 10 counts after processing points to a noise problem.
  6. Key the transmitter on each band you use, with the rotator idle. The ADC reading should change by no more than about 2 counts. If it changes more, add or enlarge the cable ferrite clamp, or restore FB1 if it was bridged.
  7. Repeat steps 1 to 4 and re-run endpoint (and optional North) calibration after any change to the divider or filter values.

For Option B, steps 1 to 3 should read 0 V ± 0.01 V at 0 V in, 1.45 V ± 0.03 V at 7.125 V, and 2.91 V ± 0.05 V at 14.25 V.

ADC mains-synchronous sampling

The problem

While the rotator runs, mains-frequency hum (50 or 60 Hz, plus harmonics) couples onto the position-sensor line from the motor and transformer. The ESP32 ADC counts are about six times finer than the old Nano design and its front end filters far less, so the hum shows up as spikes and jitter of tens of counts in the raw readings.

In the older moving mode, 128 back-to-back samples took only about 5 ms, a quarter of a 50 Hz cycle. A short slice of a sine wave averages to a different value depending on where in the cycle it starts, so each reading came out different. Adding a 22 µF capacitor hid the hum but added about 110 ms of lag and caused overshoot.

How the code works

The design intent is to average over a whole number of mains periods. A sine wave integrated over exactly one full period is zero, so the hum cancels out of the average. It does not matter where in the cycle the reading starts, so no zero-crossing detector is needed.

  • Startup calibration: calibrateAdcSync() times 64 analogRead() calls, then chooses a number of samples per mains cycle (50 to 250) and a fixed sample spacing equal to the mains period divided by that number.
  • Evenly spaced sampling: inside readDirectionADC() each sample waits on micros() until its slot, so samples are spread evenly across the window instead of being taken back to back.
  • Window length: moving mode uses exactly one cycle (fast response). Stationary mode uses 1 to 4 cycles, set by the ADCSample value (ADCSample ÷ 250, rounded; 512 to 576 gives 2 cycles).
  • Existing processing is unchanged: the trimmed mean, velocity and smoothing code run afterwards on the cleaner samples. Mains sync adds no lag, because the window is only 16.7 to 40 ms long.

It removes any interference that repeats every mains cycle (hum and its harmonics). It does not remove slow drift, such as a sagging 3.3 V rail on the offset divider, or one-off spikes. The trimmed mean handles outliers.

In simulation, with 40 counts of 50 Hz hum and its harmonics, the spread of a moving-mode reading fell from 29.0 counts to 0.13 counts.

Settings for each mains frequency

Setting50 Hz mains60 Hz mains
ADC_MAINS_PERIOD_US2000016667
ADC_SYNC_MAX_SAMPLES_PER_CYCLE250249
Sample spacing80 µs67 µs (use a rounded interval)
Moving window (1 cycle)20 ms16.7 ms
Stationary window (2 cycles)40 ms33.3 ms

At 60 Hz the interval must be rounded, (period + samples/2) ÷ samples, instead of truncated. Truncating 16667 ÷ 250 gives 66 µs and a window of 0.99 cycles, which leaves about 1% of the hum.

Fixed sample counts (without sync code)

The same cancellation works with a fixed sample count whose window is a whole number of cycles. That depends on the real analogRead() time, so it drifts if the core version, Wi-Fi mode or attenuation changes. These figures assume 35 µs per read, typical for the ESP32; the real figure is printed at startup as [ADC] read xx us.

MainsSamplesWindowCyclesHum leftRating
50 Hz57620.2 ms1.010.8%Good
50 Hz51217.9 ms0.9011%Poor
50 Hz96033.6 ms1.6816%Poor
60 Hz48016.8 ms1.010.8%Good
60 Hz96033.6 ms2.020.8%Good
60 Hz51217.9 ms1.087%Fair
60 Hz57620.2 ms1.2116%Poor

At 50 Hz, 576 samples is the safe count. At 60 Hz the safe counts are 480 and 960; the 576 count that works at 50 Hz leaves about 16% of the hum. The rule is samples = (cycles × mains period) ÷ analogRead time.

Summary

  • Hum cancels because each reading covers a whole number of mains periods, not because of the number of samples.
  • Mains sync fixes the window by time, so it stays correct if the conversion time changes. Set the period to 20000 for 50 Hz or 16667 for 60 Hz and use the 60 Hz settings above.
  • Verify on the bench: with the rotator running, the raw Spread should be 1 to 2 counts or better.