birdcage/docs/g2-nvs-dump.md
Ryan Malloy 7ff91b08ea Refactor probe tool to generic embedded console scanner, document full G2 command inventory
Rewrote hidden_menu_probe.py from Winegard-hardcoded to auto-discovering:
detects prompt, error string, and submenu structure from any firmware console.
Extracted Winegard-specific candidate words to scripts/wordlists/winegard.txt.

Deep probe of all 12 G2 submenus discovered commands across A3981 (driver
diagnostics), ADC (RSSI monitoring + position sweep), DVB (extended help via
man, transponder selection), EEPROM (read/write), GPIO (pin R/W), LATLON
(calculator), MOT (azscan, sw), PEAK (EchoStar switch), and STEP (raw
stepper control). NVS submenu generates false positives — treats any input
as sequential index reads.

Safety: added q/Q to default blocklist, bare-CR check before navigate_to_root
to prevent accidental shell termination between submenus.
2026-02-12 21:05:33 -07:00

1708 lines
67 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Carryout G2 Firmware Exploration
**Firmware:** Version 02.02.48 (Copyright 2013 - Winegard Company)
**Date:** 2026-02-12
**Connection:** DSD TECH SH-U11 USB RS-422 @ 115200 8N1
## Hardware Platform
Discovered via `os``id` command:
```
NXP Kinetis K60 ARM Cortex-M4
Package: 144-pin
Silicon Rev: 2.4
Mask Set: 4N22D
P-Flash: 512 KB
RAM: 128 KB
Core Clock: 96 MHz (CCLK)
Bus Clock: 48 MHz (BCLK)
System ID: TWELINCH
Antenna ID: 12-IN G2
Software: 02.02.48
Flash Base: 0x00010000 (65536)
Flash Size: 458752 bytes (448 KB)
```
The "TWELINCH" system ID = "Twelve Inch", matching the Carryout G2's ~12" dish
diameter. Flash starts at 64 KB offset (first 64 KB is bootloader/vector table),
leaving 448 KB for application firmware.
**Exact part number:** MK60DN512VLQ10
- MK60 = Kinetis K60 family (Cortex-M4 + DSP)
- DN512 = 512 KB program flash (no FlexNVM)
- VLQ = LQFP 144-pin package (20x20mm)
- 10 = 100 MHz max frequency
**Datasheets:** `docs/K60-datasheet.pdf` (K60P144M100SF2V2, Rev 3, 6/2013),
`docs/K60-reference-manual.pdf` (K60P144M100SF2V2RM, ~1800 pages)
### Key Peripherals (from datasheet)
| Peripheral | Count | Notes |
|------------|-------|-------|
| UART | 5 | UART0 = RS-422 console (confirmed) |
| DSPI | 3 | SPI with DMA; DSPI0/1 likely → A3981 motor drivers |
| I2C | 2 | |
| ADC | 2× 16-bit | 863ns conversion; ADC0 likely → RSSI measurement |
| DAC | 2× 12-bit | |
| USB | 1× OTG | On-chip transceiver, no external PHY needed |
| CAN | 2 | Likely unused |
| Ethernet | 1× IEEE 1588 | Likely unused |
| FlexTimer | 3 (12 ch) | Motor PWM / step timing |
| DMA | 16 channel | |
### USB Port (Potentially Accessible)
The K60 has **dedicated USB pins** (not muxable with GPIO):
| LQFP Pin | Signal | Function |
|----------|--------|----------|
| 19 | USB0_DP | USB Data+ |
| 20 | USB0_DM | USB Data- |
| 21 | VOUT33 | USB VREG 3.3V output |
| 22 | VREGIN | USB VREG 5V input (self-power from USB) |
The Trav'ler Pro uses USB A-to-A (`ttyACM0`) for its serial console — this
proves Winegard has USB CDC/ACM firmware for the Kinetis platform. The G2 may
also have a USB connector on the PCB (possibly internal, for field service).
NVS indices 2 ("Debug 2nd Console Port") and 4 ("Debug Port Connection") hint
at multiple console port support — USB could be the second port.
## Root Menu Structure
At the `TRK>` prompt, `?` lists all available submenus:
| Submenu | Command | Description |
|---------|---------|-------------|
| A3981 | `a3981` | Allegro A3981 stepper motor driver IC control |
| ADC | `adc` | Analog-to-digital converter / RSSI / board ID |
| Dipswitch | `dipswitch` | DIP switch configuration readout |
| DVB | `dvb` | BCM4515 DVB receiver / signal analysis |
| EEPROM | `eeprom` | Non-volatile EEPROM storage (separate from NVS) |
| GPIO | `gpio` | MCU pin register dump (5 ports, 98 pins) |
| LATLON | `latlon` | Satellite longitude/elevation parameters |
| MOT | `mot` | Degree-based motor positioning (high-level) |
| NVS | `nvs` | Non-volatile settings (operational parameters) |
| OS | `os` | Operating system / task manager / MCU identification |
| PEAK | `peak` | Signal peaking / DiSEqC switch testing |
| STEP | `step` | Raw microstep motor control (low-level) |
Three-layer motor control architecture:
1. **`step`** — raw microstep commands (ustep/sec, engage/release motors)
2. **`mot`** — degree-based positioning (`a <id> <deg>`, `h <id>`)
3. **Application** — satellite tracking (NVS config, peak, DVB)
## NVS Values
```
Num Name Current Saved Default
---- -------------------------- ---------- ---------- ----------
0) Log ID's 0x00000007 0x00000007 0x00000007
1) Log Device 0x00000001 0x00000001 0x00000001
2) Debug 2nd Console Port 0 0 0
3) Debug 2nd Packet Port 0 0 0
4) Debug Port Connection 0 0 0
16) Pitch Deadband 0.00 0.00 0.00
17) Roll Deadband 0.00 0.00 0.00
18) Yaw Deadband 0.00 0.00 0.00
20) Disable Tracker Proc? TRUE TRUE FALSE ← MODIFIED
21) Tracker Proc Run Mode 0 0 0
22) Conical Alpha Az 200 200 200
23) Conical Alpha El 200 200 200
24) Conical Radius 1.00 1.00 1.00
25) Conical Count Max 20 20 20
26) Conical Test Drift +0 +0 +0
27) Circle RPM 120 120 120
28) Circle Pts/Rev 6 6 6
32) Conical Az Clamp 8.00 8.00 8.00
33) Conical El Clamp 8.00 8.00 8.00
35) Motor Pts/Rev 72 72 72
36) Circle Az Radius 1.00 1.00 1.00
37) Circle El Radius 1.00 1.00 1.00
38) Sleep Mode Timer Secs 420 420 420
40) Motor Type 0 0 0
41) Satellite Scan Velocity 55.00 55.00 55.00
48) Motor Spiral Velocity 55.00 55.00 55.00
49) Motor Gear Ratio 0x00000000 0x00000000 0x00000000
63) GPS Heading Threshold 1.00 1.00 1.00
64) GPS Moving Threshold 5.00 MPH 5.00 MPH 5.00 MPH
66) Spiral Signal In A Row Min +3 +3 +3
67) Spiral Signal In A Row Max +20 +20 +20
68) Signal Odd to Even Offset +0 +0 +0
69) Signal Offset 80 80 80
70) Signal Baseline Angle 65.00 65.00 65.00
71) Signal Re-Peak Degrade Percent 25 25 25
72) Gyro Sensitivity +1110 +1110 +1110
73) Gyro Filter Size +1 +1 +1
74) Gyro Calib Readings 100 100 100
75) Gyro Mount Type 1 1 1
76) Gyro Velocity Offset 4 4 4
77) Gyro Max Accel 600 600 600
80) AZ Max Vel 65.00 65.00 65.00
81) AZ Max Accel 400.00 400.00 400.00
82) AZ Home Velocity 55.00 55.00 55.00
83) AZ Steps/Rev 40000 40000 40000
84) AZ Direction +1 +1 +1
85) EL Max Vel 45.00 45.00 45.00
86) EL Max Accel 400.00 400.00 400.00
87) EL Home Velocity 45.00 45.00 45.00
88) EL Steps/Rev 24960 24960 24960
89) EL Direction +1 +1 +1
95) AZ Low current limit 0x0000ff0c 0x0000ff0c 0x0000ff0c
96) AZ High current limit 0x0000ff30 0x0000ff30 0x0000ff30
97) EL Low current limit 0x0000ff0c 0x0000ff0c 0x0000ff0c
98) EL High current limit 0x0000ff40 0x0000ff40 0x0000ff40
101) Minimum Elevation Angle 18.00 18.00 18.00
102) Maximum Elevation Angle 65.00 65.00 65.00
103) Elevation Home Angle 65.00 65.00 65.00
106) Az Stall Detect 78 78 78
107) El Stall Detect 75 75 75
108) Az Stall Samples 100 100 100
109) El Stall Samples 100 100 100
110) EL Home Current Limit 0x0000ff28 0x0000ff28 0x0000ff28
111) AZ Home Current Limit 0x0000ff40 0x0000ff40 0x0000ff40
112) Disable Dipswitch? FALSE FALSE FALSE
113) Dipswitch Value 101 101 101
114) Dipswitch Front/Rear Mount 0 0 0
115) Mount Offset Angle +0 +0 +0
118) Signal Use LNB Clamp FALSE FALSE FALSE
128) AZ PID Kp +600 +600 +600
129) AZ PID Kv +60 +60 +60
130) AZ PID Ki +1 +1 +1
131) EL PID Kp +250 +250 +250
132) EL PID Kv +50 +50 +50
133) EL PID Ki +1 +1 +1
136) AZ PWM Stall Cnt 6 6 6
137) EL PWM Stall Cnt 5 5 5
143) Tracking Number 0 0 0
```
## Key Parameters for Satellite Tracking
| NVS | Name | Value | Notes |
|-----|------|-------|-------|
| 20 | Disable Tracker Proc? | TRUE | Prevents TV satellite search on boot |
| 83 | AZ Steps/Rev | 40000 | Centidegrees per revolution (400.00°) |
| 88 | EL Steps/Rev | 24960 | ~249.60° per revolution |
| 80 | AZ Max Vel | 65.00 | °/s azimuth max velocity |
| 85 | EL Max Vel | 45.00 | °/s elevation max velocity |
| 101 | Min Elevation | 18.00 | Firmware floor (degrees) |
| 102 | Max Elevation | 65.00 | Firmware ceiling (degrees) |
| 103 | EL Home Angle | 65.00 | Where EL homes to on startup |
| 128-133 | PID Gains | varies | AZ/EL motor PID tuning parameters |
## Boot Sequence Observed
### Bootloader Phase (<50ms, non-interactive)
Captured via `scripts/boot_capture.py` with high-resolution timestamps:
```
[0.050s] 01 00 ← binary status bytes (bootloader→app handshake?)
[0.050s] Bootloader version: 1.01
[0.050s] Application is running...
[0.050s] 98 80 96 ← binary bytes (integrity check? jump address?)
[0.100s] Application Starting Kinetis PCB... power up/reset
```
The bootloader runs at **115200 baud** (same as application — confirmed by
multi-baud capture at 9600/19200/38400/57600/230400/460800). There is **no
interactive window** — ESC, CR, BREAK, 0x55 autobaud, and other interrupt
sequences at 5-30ms delays all failed to stop the boot. The bootloader
checks a flag (likely in EEPROM or a reserved flash sector) and immediately
jumps to the application at 0x10000 if no firmware update is pending.
### Application Phase (~10s to prompt)
```
Version 02.02.48
Copyright 2013 - Winegard Company
Boot Complete
Loc Startup: IDU NOT Present
app_dipswitch:101
Primary Update: 10100
Alternate Update: 11900
Toggle Ability Update: 0
Alternate2 Update: 0
Sat Provider Update: 1
DVB: id:0000, lon:101.00E
Tuner = WIDE
Signal offset = 80
Signal baseline angle = 6500
Signal Re-Peak Pct = 25
NVS Status: 0 Sleep: 420 Dipswitch: 101
Sleep: 420 NVS: 420
NoGpsStartUp: 721
STATIONARY MODE
Enabled LNB ODU 18V
GPS Not Found
```
## Homing Sequence
After boot, the dish homes both motors (EL first, then AZ) using stall detection:
```
MotorHome:1 timeout:2000 ← EL motor homing
Home TwelInch El Velocity: 4500
EL Stall Timeout
El Home Angle: 6500
MotorHome:0 timeout:8000 ← AZ motor homing
Home TwelInch Az
End MotorAzStall:part1
Antenna Facing Front
home:0 wrap_pos:0 wrap_min:-42333 wrap_max:2333
```
## Cable Wrap Limits
From homing output: `wrap_min:-42333 wrap_max:2333`
- In centidegrees: -423.33° to +23.33° from home position
- Total range: 446.66° (~1.24 full rotations)
## Motor Control
### Position Query
In the `MOT>` submenu, `a` returns position with 4-space indentation:
```
a
Angle[0] = 180.00 ← AZ (degrees)
Angle[1] = 45.00 ← EL (degrees)
MOT>
```
### Move Command
`a <id> <deg>` returns a confirmation (no array index) and the prompt immediately
while the motor moves in the background:
```
a 1 46
Angle = 46.00
MOT>
```
### Observed Motor Behavior
| Test | Command | Target | Actual | Overshoot |
|------|---------|--------|--------|-----------|
| EL move out | `a 1 46` | 46.00 | 46.05 | +0.05° |
| AZ move out | `a 0 181` | 181.00 | 181.01 | +0.01° |
| EL return | `a 1 45` | 45.00 | 44.94 | -0.06° |
| AZ return | `a 0 180` | 180.00 | 179.98 | -0.02° |
Direction-dependent overshoot: the motor consistently overshoots in the
direction of travel, undershooting on return. This is classic stepper
backlash + PID settling behavior and is what the leapfrog algorithm
compensates for.
### MOT Submenu — Full Command Reference
```
Available commands:
a Go to angle [[[motor] [[+|-]angle]]]
azscan Scan AZ from EL Min-Max Angle [az_rel_angle] [el_rel_angle] [delay]
azscanwxp Scan AZ from EL Min-Max with all transponders [motor] [span] [resolution] [num_xp]
e Engage motors
ela2s Elevation Law Test - Angle to Steps [angle]
elminmaxhome Display Min, Max & Home Elevation Angles
els2a Elevation Law Test - Steps to Angle [steps]
h Home Motors [motor num or * (both)]
l List Motors in System
life Az/El Life test [az_rel_angle] [el_rel_angle]
ma Set Max Acceleration [[motor] [deg/sec/sec]]
motorboth Both Motor Life test [AZ delta(0-25)] [EL delta(0-25)]
motorlife Motor Life Test [motor_id] [min_angle] [max_angle]
mv Set Max Velocity [motor] [deg/sec]
p Go To Position [motor] [pos]
pid Set PID Parameters [motor] [Kp] [Kv] [Ki]
r Release Motors
sd Stall Detect [motor] [dir] [timeout_ms] [iterations (0=forever)]
sp Set Position [motor] [pos]
sw Set Wrap Position [motor] [pos]
v Goto Velocity [motor] [deg/rev]
vms Goto Velocity For Milliseconds [motor] [deg/rev] [ms]
w Wrap Manager [motor] [ON/OFF]
```
### Relative Moves
`a` supports `+`/`-` prefix for **relative** moves:
```
a 0 +5 ← move AZ 5° CW from current
a 1 -2 ← move EL 2° down from current
```
This is undocumented in the upstream repos and very useful for incremental
positioning during tracking.
### Motor List
```
l
Motors:
0 - AZIMUTH: local
1 - ELEVATION: local
```
"local" means direct A3981 driver control (vs. a networked motor controller).
### Motor Dynamics
```
ma → Accel[0] = 400.0 Accel[1] = 400.0 (deg/sec²)
mv → Max Vel[0] = 65.0 Max Vel[1] = 45.0 (deg/sec)
```
Both axes have identical acceleration (400 deg/sec²). AZ max velocity is
faster (65 deg/sec) than EL (45 deg/sec) — different gear ratios and
mechanical loads.
### Step Position
`p` shows position in microsteps:
```
p → Position[0] = 19998 Position[1] = 3116
```
Cross-check with angle: AZ 179.98° × (40000/360) = 19998 steps. Linear
mapping for both axes (angle = steps × 360 / steps_per_rev).
### Elevation Limits
```
elminmaxhome → Min: 1800 Max: 6500 Home: 6500
```
All values in centidegrees: **Min=18.00°, Max=65.00°, Home=65.00°**. The
home position is at maximum elevation (stow position).
### Elevation Law Conversion
`ela2s` converts angle → steps, `els2a` converts steps → angle:
| Angle | Steps | Notes |
|-------|-------|-------|
| 0° | 1248 | Below min (warning: "Min: 1800") |
| 18° | 1248 | Minimum EL (same steps as 0°) |
| 45° | 3120 | |
| 65° | 4506 | Maximum EL |
| 90° | 4506 | Above max (warning: "Max: 6500") |
The mapping is linear: steps ≈ angle × (24960/360). The "law" is a simple
linear function for this dish — no non-linear linkage compensation.
### Engage / Release
- `e` — Engage motors (enable A3981 drivers, PID loop holds position)
- `r` — Release motors (disable drivers, dish can move freely by hand)
### Sky Scan Commands
**`azscan <az_rel> <el_rel> <delay>`** — Scan AZ while stepping EL from
min to max angle. Parameters are relative angles and dwell delay. Used for
basic signal surveys.
**`azscanwxp <motor> <span> <resolution> <num_xp>`** — Advanced sky scan
that steps in hundredths of a degree and checks all transponders at each
position. This is the core of Davidson's winegard-sky-scan project. The
`resolution` parameter in hundredths of a degree enables 0.01° precision
scanning — far finer than the standard `a` command.
### Stall Detection
`sd <motor> <dir> <timeout> [iterations]` — Run stall detection on a
motor. Default timeouts: AZ=10000ms, EL=2000ms. The firmware drives the
motor in the specified direction until it stalls (current spike from A3981).
Set iterations to 0 for continuous mode. Used during homing and calibration.
### Life / Durability Tests
Factory test commands that continuously exercise the motors:
- `life <az_rel> <el_rel>` — Oscillate both axes by relative angles
- `motorlife <id> <min> <max>` — Sweep a single motor between min/max angles
- `motorboth <az_delta> <el_delta>` — Exercise both motors, max 25° delta each
### Write-Only Commands
These commands require parameters — no read-only mode:
- `pid <motor> <Kp> <Kv> <Ki>` — Set PID gains (no read command)
- `sp <motor> <pos>` — Set step position counter (doesn't move motor)
- `sw <motor> <pos>` — Set wrap position
- `w <motor> ON/OFF` — Enable/disable wrap manager
## DVB Subsystem (BCM4515)
### Hardware
```
BCM Hardware= ID: 0x4515 VER: 0xB0
BCM Firmware= MAJOR VER: 0x71 (113) MINOR VER: 0x25 (37)
BCM Strap Config: 0x25018
```
### Channel Parameters (`dis`)
```
Power Mode: ON
Search Transponders: ON
Auto Search Mode: 1
Shuffle Mode: ON
Frequency List: Non-Stacked
Num Parameter Current Default
1 Frequency 1090640 (kHz) 974000 (kHz)
2 Symbol Rate 0 (PeakScanEnabled) 20000 (ksps)
3 Trans_Mod_CRate blind_scan blind_scan
4 Blind Scan Mode ___trb_dvb_dss_____ ___trb_dvb_dss_____
5 LNB Polarity ODU:13V ---
6 LNB Tone (ODU) off off
7 Roll-off 0.35 0.35
8 LPF Cutoff 0 (auto) 0 (MHz)
9 Carrier Offset 0 (kHz) 0 (kHz)
10 FreqSearchRange 5000 (kHz) 5000 (kHz)
11 DCII Mode dcii_qpsk_comb dcii_qpsk_comb
12 Spectral Inv scan scan
13 PScnSymRtRngMin 18000 (ksps) 18000 (ksps)
14 PScnSymRtRngMax 24000 (ksps) 24000 (ksps)
15 SignalDetectMode off off
```
### RSSI Response Format
```
rssi 5
iterations:5 interval(msec):20
Reads:5 RSSI[avg: 500 cur: 500]
DVB>
```
500 is the noise floor (no signal lock, dish pointed at arbitrary sky).
### LNB Voltage
`lnbdc odu` enables LNA at 13V. `lnbv` streams continuous voltage readings:
```
Reads:1 LNB Voltage (mV): 13239 ( ADC value: 119 )
Reads:2 LNB Voltage (mV): 13182 ( ADC value: 118 )
...
```
Stable at ~13.11V (ADC 117). Boot default is 18V; `lnbdc odu` switches to 13V.
13V = vertical polarization, 18V = horizontal polarization on a standard LNB.
### AGC (Streaming)
`agc` streams RF and IF automatic gain control plus SNR/NID:
```
Reads:1 RF_AGC[avg: 1327353088 cur: 1327353088] IF_AGC[avg: 2684354560 cur: 2684354560] SNR: 0.0 NID: FFFF/none
```
- RF_AGC values are raw BCM4515 32-bit register values
- IF_AGC constant at 0xA0000000 (fixed IF gain)
- SNR: 0.0 when no signal lock
- NID: FFFF/none = no DVB network ID detected
### SNR (Streaming)
`snr` streams signal-to-noise ratio readings:
```
Reads:1 SNR[avg: 0.0 cur: 0.0]
Reads:2 SNR[avg: 0.0 cur: 0.0]
...
Reads:223 SNR[avg: 0.0 cur: 3.1] ← transient RF spike
Reads:250 SNR[avg: 0.0 cur: 2.6] ← another transient
```
At noise floor, average stays 0.0 but occasional transient spikes appear in
the `cur` field — fleeting RF energy. Stays in DVB submenu when interrupted.
### Lock Status (`ls`) — Streaming
`ls` is a STREAMING command that continuously scans all 32 transponders, trying
multiple modulations per frequency. Output is a continuous scan log:
```
Xp:1 Freq:974000 SymRate:20000 Mode:blind_scan ... no_lock
Xp:1 Freq:974000 SymRate:20000 Mode:turbo_qpsk_... ... no_lock
...
```
**When interrupted with CR, `ls` prints "Terminating shell." and drops to
`TRK>` (exits DVB submenu entirely).** This is unique among DVB streaming
commands — all others stay in DVB submenu when interrupted.
### Quick Lock Status (`qls`) — Streaming
`qls` streams compact lock status at ~100ms intervals:
```
Lock:0 rssi:500 cnt:0
Lock:0 rssi:500 cnt:0
...
```
Stays in DVB submenu when interrupted. Ideal for real-time signal monitoring
during dish movement (low bandwidth, high update rate).
### Network ID (`nid`) — Streaming
`nid` streams network identification:
```
nid: FFFF/none
nid: FFFF/none
```
Uses CR-overwrite (carriage return without newline) for in-place updates.
FFFF = no DVB network detected. Can be difficult to interrupt cleanly —
may need multiple CR + flush cycles.
### Signal Statistics (`stats`) — Streaming
`stats` streams signal statistics. Produces no output when there is no signal
lock. Stays in DVB submenu when interrupted.
### Diagnostics (`diag`) — One-Shot
`diag` is a multi-block ONE-SHOT command that outputs detailed per-transponder
diagnostics for each transponder currently being tried:
```
SymRate: 18514984 Freq: 974000
Bit Rate: 29893160
SNR: 0.0 SymRateErr: -1258 CarrierOffset: 0 CarrierErr: 3295097
Tuner LPF: 12 RF_AGC: 2214707264
BER Errors: 0 MPEG Frm Errors: 0 MPEG Frm Count: 0
Reacquisitions: 7
RS Corr/Uncorr: 0 / 0 Pre-Vit: 0
sp inv: scan phase rotation: 0
Acq Time: 4259 msec
trans_mod_coderate: no_lock
Tuner PLL: LOCKED Internal BERT: not locked
Demod: not locked Timing Loop Lock: disabled
```
### Transponder Table (`table`) — One-Shot (Long)
`table` generates a full scan across all 32 transponders. Takes ~136 seconds
(~4.25s per transponder, matching `tabto` acquisition timeout of 4000ms).
The output starts with a configuration summary showing all modifiable parameters
with their current values and the commands to change them, then a detailed
per-transponder table with columns:
```
Xp Freq SigDet SymRate PeakPower SNR LPF RF_AGC AcqTime Mode
NID/SAT XpTime LNBVsens RSSI BitrateOut SymRateError CarrierOffset
CarrierError BER_Errors MPEG_Errors MPEG_Count Reacq
```
With shuffle mode ON, transponders scan in interleaved groups of 4:
1,2,3,4 → 17,18,19,20 → 5,6,7,8 → 21,22,23,24 → 9,10,11,12 → 25,26,27,28
→ 13,14,15,16 → 29,30,31,32.
LNB voltage alternates between ~13V (vertical polarization) and ~20V
(horizontal polarization) across transponder groups.
After scan completes: `Table Completion Time (seconds): 136 Transponders Locked: 0`
then auto-restores LNB to STB mode: `Enabled LNB STB`.
### Frequency List (`freqs`)
Returns the active frequency list name:
```
freqs → Non-Stacked
```
"Non-Stacked" means standard Ku-band IF frequencies without stacking
(stacked LNBs combine multiple bands into one cable).
### Transponder Range (`range`)
```
range → Transponder Range: [ 1 - 32 ]
```
### Power State (`pwr`)
```
pwr → SDS and DiSEqC core power enabled
```
Shows power state of the Satellite Detection System and DiSEqC subsystems.
### Search Mode (`srch_mode`)
```
srch_mode → Auto Search Mode: 1
```
Mode 1 = automatic transponder search with blind scan.
### Timeout Settings
Two separate timeout configurations:
**Table scan timeouts (`tabto`):**
```
Timeout (msec): Acq:4000 NID:20000 Signal Detect:0
```
**Single transponder tune timeouts (`to`):**
```
Timeout (msec): Acq:500 NID:12000
```
`tabto` takes arguments: `tabto <acq> <nid> <sd>` (set 0 to disable NID/SD).
`to` takes arguments: `to <acq> <nid>`.
### Toggle Commands
These commands toggle a mode and print the new state:
| Command | What it toggles |
|---------|----------------|
| `srch` | Search Transponders mode (on/off) |
| `shuf` | Transponder shuffle order (on/off) |
| `tablex` | Extended table mode (on/off) |
### Defaults Reset (`def`)
`def` silently resets all channel parameters to defaults. **No output, no
confirmation.** Use with caution — there is no undo.
### Modulation Switch (`msw`)
`msw` requires arguments but rejects all tested inputs (`msw 0`, `msw 1`,
`msw on`). Format unknown — possibly vestigial or requires a specific
modulation string.
### Channel Parameter Help (`h <n>`)
`h <n>` shows valid values for each of the 13+2 channel parameters:
| Param | Name | Range / Values |
|-------|------|---------------|
| 1 | Frequency | 2500002150000 kHz (L-band IF) |
| 2 | Symbol Rate | 0 (Peak Scan) or 200045000 ksps |
| 3 | Trans_Mod_CRate | 30+ modes (see table below) |
| 4 | Blind Scan Mode | Bitmask: s2, trb, dvb, dss, dcii (see below) |
| 5 | LNB Polarity | 13V (vertical) / 18V (horizontal) |
| 6 | LNB Tone | off / on (22kHz for high-band switching) |
| 7 | Roll-off | 0.20 / 0.35 |
| 8 | LPF Cutoff | 0 (auto) or 140 MHz |
| 9 | Carrier Offset | -5000 to +5000 Hz |
| 10 | FreqSearchRange | 010000 kHz |
| 11 | DCII Mode | comb, i, q (QPSK) / ocomb, oi, oq (OQPSK) |
| 12 | Spectral Inversion | 0=normal, 1=q inv, 2=i inv, 3=scan |
| 13 | PScnSymRtRngMin | 200045000 ksps |
| 14 | PScnSymRtRngMax | (shown in `dis`, no `h 14` help) |
| 15 | SignalDetectMode | (shown in `dis`, no `h 15` help) |
#### Supported Modulations (Param 3)
```
DVB-S: dvbs_qpsk_1_2, dvbs_qpsk_2_3, dvbs_qpsk_3_4,
dvbs_qpsk_5_6, dvbs_qpsk_7_8
DSS: dss_qpsk_1_2, dss_qpsk_2_3, dss_qpsk_6_7
DCII: dcii_qpsk_1_2, dcii_qpsk_2_3, dcii_qpsk_3_4,
dcii_qpsk_5_11, dcii_qpsk_4_5
DVB-S2: s2_qpsk_1_2, s2_qpsk_3_5, s2_qpsk_2_3, s2_qpsk_3_4,
s2_qpsk_4_5, s2_qpsk_5_6, s2_qpsk_8_9, s2_qpsk_9_10,
s2_8psk_3_5, s2_8psk_2_3, s2_8psk_3_4
Turbo: turbo_qpsk_1_2, turbo_qpsk_2_3, turbo_qpsk_3_4,
turbo_qpsk_5_6, turbo_qpsk_7_8,
turbo_8psk_2_3, turbo_8psk_3_4, turbo_8psk_4_5,
turbo_8psk_5_6, turbo_8psk_8_9
Special: blind_scan
```
#### Blind Scan Mode Bitmask (Param 4)
Visual bitmask format: `s2_trb_dvb_dss_dcii`
| Value | Pattern | Standards enabled |
|-------|---------|-------------------|
| `s2` | `s2_______________` | DVB-S2 only |
| `trb` | `___trb___________` | Turbo FEC only |
| `dvb` | `_______dvb_______` | DVB-S only |
| `dss` | `___________dss___` | DSS (DirecTV) only |
| `dcii` | `_______________dcii` | DCII (DigiCipher) only |
| `all` | `s2_trb_dvb_dss_dcii` | All standards |
| `dish` | `___trb_dvb________` | DISH Network (turbo + DVB-S) |
| `3` | `___trb_dvb_dss___` | DirecTV 3-standard |
| `shaw` | `___trb_________dcii` | Shaw (turbo + DCII) |
| `nodcii` | `s2_trb_dvb_dss___` | All except DCII |
| `nos2` | `___trb_dvb_dss_dcii` | All except DVB-S2 |
### DiSEqC Commands
All DiSEqC 2.x read commands fail with `RxReplyTimeout` when no switch is
connected (expected — the G2 has a direct LNB without a multi-switch):
| Command | Function | Result (no switch) |
|---------|----------|--------------------|
| `di2id` | Read LNB hardware ID | `LNB Read HW ID FAIL` |
| `di2stat` | Read LNB status | `LNB Read Status FAIL` |
| `di2conf` | Read LNB config | `LNB Read Config FAIL` |
| `di2sc` | Short circuit test | `LNB Short Circuit FAIL` |
| `di2rcs` | Read switch state | `LNB Switch State FAIL` |
| `di2cs` | Configure switch | `No Parameters Specified` (needs args) |
| `send <3-6 bytes>` | Raw DiSEqC packet | (not tested — no switch) |
### DiSEqC Timing Parameters
| Command | Parameter | Default |
|---------|-----------|---------|
| `ovraddr` | LNB address | 0x11 (standard first LNB) |
| `rrto` | Receive reply timeout | 210 ms |
| `tdthresh` | Tone detect threshold | 110 (units: 0.16 counts/mV) |
| `pretx` | Pre-command TX delay | 15 ms |
### DVB Command Reference
| Command | Type | Description |
|---------|------|-------------|
| `rssi <n>` | One-shot | Average signal strength over n samples |
| `snr` | Streaming | SNR readings (avg + current) |
| `agc` | Streaming | RF/IF AGC + SNR + NID |
| `lnbdc odu` | One-shot | Enable LNB in ODU mode (13V) |
| `lnbv` | Streaming | Continuous LNB voltage monitoring |
| `ls` | Streaming | Full transponder lock scan (exits DVB on interrupt!) |
| `qls` | Streaming | Quick lock status (~100ms updates) |
| `nid` | Streaming | Network ID (CR-overwrite display) |
| `stats` | Streaming | Signal statistics (silent when no lock) |
| `config` | One-shot | BCM hardware/firmware version |
| `dis` | One-shot | Display all channel parameters |
| `diag` | One-shot | Multi-block per-transponder diagnostics |
| `table` | One-shot | Full 32-transponder scan (~136s) |
| `freqs` | One-shot | Frequency list name |
| `range` | One-shot | Transponder scan range |
| `pwr` | One-shot | SDS/DiSEqC power state |
| `srch_mode` | One-shot | Auto search mode value |
| `tabto` | Read/Write | Table scan timeouts (acq/nid/sd) |
| `to` | Read/Write | Single tune timeouts (acq/nid) |
| `t <n>` | Write | Select transponder |
| `e <n> <v>` | Write | Edit channel parameter |
| `h <n>` | Read | Parameter help (valid values for param n, 1-13) |
| `srch` | Toggle | Search transponders mode |
| `shuf` | Toggle | Transponder shuffle order |
| `tablex` | Toggle | Extended table mode |
| `def` | Write | Reset all params to defaults (silent, no undo!) |
| `msw <?>` | Write | Modulation switch (format unknown) |
| `ovraddr [addr]` | Read/Write | DiSEqC LNB address |
| `rrto [ms]` | Read/Write | DiSEqC receive reply timeout |
| `tdthresh [val]` | Read/Write | DiSEqC tone detect threshold |
| `pretx [ms]` | Read/Write | DiSEqC pre-TX delay |
| `di2id` | Read | DiSEqC 2.x: read LNB hardware ID |
| `di2stat` | Read | DiSEqC 2.x: read LNB status |
| `di2conf` | Read | DiSEqC 2.x: read LNB config |
| `di2sc` | Read | DiSEqC 2.x: short circuit test |
| `di2rcs` | Read | DiSEqC 2.x: read switch state |
| `di2cs <args>` | Write | DiSEqC 2.x: configure switch |
| `send <3-6 hex>` | Write | Raw DiSEqC packet |
**Streaming commands:** `snr`, `agc`, `lnbv`, `qls`, `nid`, `stats` run until
CR interrupts. All stay in DVB submenu except `ls` which drops to `TRK>`.
**Toggle commands:** `srch`, `shuf`, `tablex` alternate on/off and print new state.
## Satellite Configuration
```
DVB: id:0000, lon:101.00E ← DirecTV 101°W (stored as East longitude)
Primary Update: 10100 ← 101.00° in centidegrees
Alternate Update: 11900 ← 119.00°
Sat Provider Update: 1 ← Provider ID
Dipswitch Value: 101 ← DirecTV configuration
```
## A3981 Motor Driver IC
The Allegro A3981 is an automotive-grade programmable stepper motor driver
controlled by the K60 MCU via SPI. Two A3981 chips — one per axis (AZ, EL).
**Datasheet:** `docs/A3981-datasheet.pdf` (Allegro Microsystems)
**ECAD files:** `docs/A3981-ecad.kicad_sym`, `docs/A3981-ecad.pretty/`
### A3981 Commands (`A3981>`)
| Command | Type | Description |
|---------|------|-------------|
| `diag` | Read | Fault diagnostics for both axes |
| `sm` | Read | Step size mode (AUTO/MANUAL) |
| `ss` | Read | Current step size (microstepping level) |
| `cm` | Read | Current control mode (AUTO/MANUAL) |
| `st <params>` | Write | Set torque/current parameters |
| `reset` | Write | Clear latched A3981 faults on both axes |
### Diagnostics
```
diag
AZ DIAG: OK
EL DIAG: OK
```
### Microstepping Configuration
```
ss
KEY: FULL-16, HALF-8, QTR-4, EIGHTH-2, SIXTEENTH-1
AZ Step Size:1
EL Step Size:1
```
Both axes at step size 1 = **1/16 microstepping** (finest available). The A3981
supports full, half, quarter, eighth, and sixteenth steps. The inverted key
(FULL=16, SIXTEENTH=1) is the firmware's internal representation — likely a
divisor applied to the full-step pulse count.
### Step Size and Current Modes
```
sm cm
AZ Step Size Mode = AUTO AZ: Mode = AUTO
EL Step Size Mode = AUTO EL: Mode = AUTO
```
AUTO mode means the driver dynamically adjusts microstepping resolution and
current level based on motor speed. At low speeds, fine microstepping (1/16)
provides smooth motion and precise positioning. At higher speeds, the driver
may switch to coarser steps to maintain torque.
### Fault Reset
```
reset
Az/El A3981 Faults Reset.
```
Clears latched fault conditions (overcurrent, open load, thermal). This is a
**write** operation — it actively clears the fault registers, not a read-only
status check. Use `diag` for non-destructive fault checking.
## ADC Subsystem (`ADC>`)
| Command | Type | Description |
|---------|------|-------------|
| `m` | Streaming | Monitor RSSI continuously |
| `rssi` | One-shot | Read RSSI (same noise-floor value as DVB) |
| `scan` | Unknown | Scan ADC on azimuth axis |
| `bdid` | One-shot | Board identification string |
| `bdrevid` | One-shot | Board revision ID |
### Board Identification
```
bdid → STATIONARY
bdrevid → A
```
"STATIONARY" confirms this is the non-mobile (non-in-motion) variant. Board
revision "A" is the first production revision.
### RSSI via ADC
```
rssi → 500
```
Same noise floor value (500) as the DVB `rssi` command. The ADC subsystem reads
the same analog signal path — likely a baseband power detector output from the
BCM4515 routed to a K60 ADC input.
## Dipswitch (`DIPSWITCH>`)
```
dipswitch
val:ffffff01
app_dipswitch:101
```
The raw value `0xFFFFFF01` has the low byte = 0x01. The `app_dipswitch` value
101 corresponds to the DirecTV satellite configuration. Dipswitch values select
the satellite provider (DirecTV, DISH, Bell) and determine which transponder
frequencies the search algorithm tries.
## EEPROM (`EE>`)
| Command | Type | Description |
|---------|------|-------------|
| `ee <idx>` | Read | Read EEPROM value at index |
| `ee <idx> <val>` | Write | Write EEPROM value at index |
| `inv [<idx>]` | Write | **Invalidate** EEPROM index (destructive!) |
| `def` | Write | Restore all EEPROM values to factory defaults |
EEPROM is a separate storage area from NVS. NVS holds operational parameters
(motor tuning, PID gains, satellite config). EEPROM appears to hold calibration
or factory data — likely stored in an external I2C EEPROM (AT24Cxx or similar)
rather than the K60's internal flash.
### EEPROM Structure
The EEPROM has exactly **17 indices (0-16)**. The firmware enforces bounds:
```
ee 17
Index out of bounds:17 Min:0 Max:16
```
Each index stores a 32-bit unsigned integer. Invalid entries return a sentinel
value of **65793 (0x00010101)** — three bytes of 0x01 in the low 24 bits. The
firmware distinguishes between valid and invalid reads:
- **Valid:** `Read value = <decimal>` — data is trusted
- **Invalid:** `Failed to read. val:<decimal>` — data exists but flagged invalid
### Complete EEPROM Dump
Dumped via `scripts/ee_dump.py` (pyserial script scanning all indices):
| Index | Decimal | Hex | Status | Notes |
|------:|--------:|-----|--------|-------|
| 0 | 65793 | 0x00010101 | INVALID | Accidentally invalidated (`inv 0`) |
| 1 | 0 | 0x00000000 | OK | |
| 2 | 22897 | 0x00005971 | OK | |
| 3 | 3748 | 0x00000EA4 | OK | |
| 4 | 4346 | 0x000010FA | OK | |
| 5 | 11637 | 0x00002D75 | OK | |
| 6 | 65793 | 0x00010101 | INVALID | Factory default (never written) |
| 7 | 65793 | 0x00010101 | INVALID | Factory default |
| 8 | 65793 | 0x00010101 | INVALID | Factory default |
| 9 | 65793 | 0x00010101 | INVALID | Factory default |
| 10 | 65793 | 0x00010101 | INVALID | Factory default |
| 11 | 65793 | 0x00010101 | INVALID | Factory default |
| 12 | 65793 | 0x00010101 | INVALID | Factory default |
| 13 | 65793 | 0x00010101 | INVALID | Factory default |
| 14 | 65793 | 0x00010101 | INVALID | Factory default |
| 15 | 65793 | 0x00010101 | INVALID | Factory default |
| 16 | 65793 | 0x00010101 | INVALID | Factory default |
Only **6 indices** (0-5) were ever written with valid data. Indices 6-16 have
never been programmed — all contain the 0x00010101 sentinel. The EEPROM has
capacity for 17 values but only the first 6 are used by this firmware version.
### Value Analysis
The valid EEPROM values (indices 1-5) don't correspond to any obvious motor
positions, NVS indices, or physical constants. They may represent:
- **Factory calibration offsets** — motor encoder corrections, sensor trim
- **Manufacturing data** — serial number components, PCB revision, test results
- **Checksum/signature** — authentication for firmware/hardware pairing
Index 0 (originally valid, now invalidated) was likely a calibration value or
header byte. Its original value is unknown — `def` might restore it.
### Command Notes
**`inv [<idx>]`** — "Invalidate", not "inventory". Marks an index as invalid
by writing the 0x00010101 sentinel. The firmware still returns the raw value
on read but flags it as `Failed to read`. **Destructive and immediate** — no
confirmation prompt.
**`def`** — Restores all EEPROM indices to factory defaults. Not tested on this
unit (would overwrite the accidentally-invalidated index 0 but also reset any
user-modified values). Worth trying if index 0's missing value causes issues.
**`ee <idx> <val>`** — Write a value to an index. Accepts decimal integers.
Can be used to restore invalidated indices if the original value is known.
## GPIO Registers (`GPIO>`)
| Command | Type | Description |
|---------|------|-------------|
| `regs` | Read | Dump all GPIO pin states (0/1) |
| `r <port> <pin>` | Read | Read single GPIO pin (e.g., `r A 5`) |
| `w <port> <pin>` | Write | Write to GPIO pin (toggle) |
| `dir <bit> <val>` | Write | Set pin direction (input/output) |
The `regs` command dumps all MCU GPIO pins across 5 ports (K60 144-pin).
Note: pins A20-A23 and B12-B15 are not enumerated (reserved or unbonded):
| Port | Pins Enumerated | High Pins (=1) |
|------|----------------|----------------|
| A | A0A19, A24A29 (26 pins) | A1, A3, A4, A5, A15, A16, A25A29 |
| B | B0B11, B16B23 (20 pins) | B0, B1, B2, B3, B11 |
| C | C0C19 (20 pins) | C10, C11, C12, C13, C18 |
| D | D0D15 (16 pins) | D11, D12, D13 |
| E | E0E12, E24E29 (19 pins) | E0, E1, E2, E4, E5, E7, E9E12, E24E28 |
Total: 101 pins enumerated. "Unknown bit E29" logged by firmware — pin E29
is defined in hardware but not assigned a function (test point or reserved).
## LATLON (`LATLON>`)
| Command | Type | Description |
|---------|------|-------------|
| `l <satlon1> <satlon2> <satel1> <satel2>` | Read | Calculate dish lat/lon from two satellite observations |
This is the dish's **self-localization algorithm** — it triangulates its own
geographic position by observing two known geostationary satellites. The four
parameters are longitude and elevation pairs for two satellites.
```
l -110 -119 40 38
anglesentered = -11000 -11900 4000 3800
Lat = 4295 Lon = 25655
```
Output is in centidegrees: Lat 4295 = 42.95°N, Lon 25655 = 256.55°E (= 103.45°W).
This is a reasonable US mid-latitude position given DISH Network satellites at
110°W and 119°W with elevation angles of 40° and 38°.
The firmware uses this for automatic satellite look-angle computation when no
GPS is available (the G2 has no GPS module — "GPS Not Found" at boot).
## PEAK Subsystem (`PEAK>`)
| Command | Type | Description |
|---------|------|-------------|
| `ts` | **Streaming (DANGEROUS)** | EchoStar/DiSEqC switch toggle — runs forever, can't be interrupted |
| `pw <az> <el>` | Motor+Signal | Peak wide — sweep around AZ/EL to find signal peak |
| `psnr <az> <el>` | Motor+Signal | Peak SNR — sweep around AZ/EL optimizing for SNR |
| `pxy1 [<n>]` | Motor+Signal | Peak XY1 — 2D cross-pattern peak search, repeat n times (default 1) |
| `stb` | One-shot | STB control test — toggles LNB polarity and compares RSSI |
| `rssits` | **Streaming** | RSSI test — prints every 1 minute (exits submenu on interrupt!) |
### EchoStar Switch Toggle (`ts`) — DANGEROUS!
The `ts` command runs **indefinitely**, probing for a DiSEqC/EchoStar switch
by toggling LNB voltage and reading the switch response. It cannot be stopped
by sending `q` — the running command consumes all input. The only escape is to
close and reopen the serial port.
```
ts
(14000+ reads, all showing: 0b0000 0)
```
All reads returned `0b0000 0` — no switch connected (expected, since the G2's
LNB is directly connected without a multi-switch).
### STB Control Test (`stb`)
Toggles LNB polarization and compares RSSI at each polarity:
```
stb
Enabled LNB ODU 18V
Even_sig = 504
Enabled LNB STB
Odd_sig = 232
Enabled LNB STB
Odd_sig = 233 Ctr = 0
```
- `Enabled LNB ODU 18V` → horizontal polarization (18V), `Even_sig = 504`
- `Enabled LNB STB` → vertical polarization (13V), `Odd_sig = 232`
- `Ctr = 0` → no DiSEqC switch response detected
- The 504 vs 232 difference (~2x) reflects the polarization-dependent noise
floor. "Even" = H-pol transponders (18V), "Odd" = V-pol (13V) — DBS convention.
### RSSI Test (`rssits`)
Starts a background RSSI monitoring task that prints every 1 minute. When
interrupted, **exits the PEAK submenu entirely** (drops to `TRK>` root),
similar to DVB `ls` behavior.
## STEP Subsystem (`STEP>`) — Low-Level Motor Control
| Command | Type | Description |
|---------|------|-------------|
| `e` | Write | Engage motor (enable driver) |
| `r` | Write | Release motors (disable driver, coast to stop) |
| `p` | Read/Write | Go to position (absolute, in microsteps), or read current position |
| `v` | Write | Go to velocity (continuous rotation) |
| `ma` | Read/Write | Set/get max acceleration (ustep/sec/msec) |
| `mv` | Read/Write | Set/get max velocity (ustep/sec) |
| `pid` | Read/Write | Set/get PID tuning values |
This is the raw stepper motor interface — below the `mot` menu's degree-based
abstraction. Commands operate in **microsteps** rather than degrees. The
relationship between microsteps and degrees depends on:
- Gear ratio (NVS 49, currently 0x00000000)
- Steps per revolution (NVS 83: AZ=40000, NVS 88: EL=24960)
- Microstepping level (A3981: both at 1/16)
### Current Values (read mode)
```
p → Step Pos[0] = 19998 Step Pos[1] = 3116
ma → Accel[0] = 44 Accel[1] = 28
mv → Max Vel [0] = 7222 Max Vel [1] = 3120
pid → Kp=250 Kv=50
```
### MOT↔STEP Conversion Table
| Parameter | STEP (microsteps) | MOT (degrees) | Factor |
|-----------|-------------------|---------------|--------|
| AZ position | 19998 | 179.98° | 111.11 steps/° |
| EL position | 3116 | 44.94° | 69.33 steps/° |
| AZ max vel | 7222 ustep/s | 65.0°/s | ÷111.11 |
| EL max vel | 3120 ustep/s | 45.0°/s | ÷69.33 |
| AZ accel | 44 ustep/s/ms | ~396°/s² | ×1000÷111.11 |
| EL accel | 28 ustep/s/ms | ~404°/s² | ×1000÷69.33 |
### PID Tuning
`pid` returns `Kp=250 Kv=50` — proportional and velocity gains. No Ki term,
indicating a PD (proportional-derivative) position loop. This is typical for
stepper motors which don't drift under holding torque. Matches NVS indices
128-129. Note: `pid` is write-only in `MOT>` but **readable in `STEP>`**.
The `e`/`r` commands engage and release the A3981 motor drivers. When released,
the motors are unpowered and the dish can be moved by hand (useful for manual
positioning or emergency stow). When engaged, the PID loop holds position.
## ADC Subsystem (`ADC>`)
| Command | Type | Description |
|---------|------|-------------|
| `bdid` | Read | Board ID — returns `STATIONARY` |
| `bdrevid` | Read | Board revision ID — returns `A` |
| `rssi` | Read | Single RSSI reading (ADC value, not DVB RSSI) |
| `m` | Toggle? | Monitor RSSI — returns immediately, may enable background monitoring |
| `scan` | **Streaming** | Continuous ADC scan with position, RSSI, Lock, SNR, and delta |
### Board Identity
- **Board ID:** `STATIONARY` — distinguishes from mobile/in-motion antenna variants
- **Board Rev ID:** `A` — PCB revision
### RSSI (ADC)
`rssi` returns a single ADC reading: `232` at noise floor. This is the raw
ADC value from an analog RSSI detector (separate from the DVB tuner's digital
RSSI). The ADC RSSI baseline (~232) corresponds to the DVB `Odd_sig` in the
PEAK `stb` test, suggesting both measure the same RF path at V-pol.
### Scan (Streaming)
`scan` performs a continuous RF scan at the current position:
```
Starting position AZ:17998 EL:4494 Stop at:0
Motor:0 Angle:17998 RSSI:232 Lock:0 SNR: 0.0 Scan Delta:0
Motor:0 Angle:17998 RSSI:238 Lock:0 SNR: 0.0 Scan Delta:0
Motor:0 Angle:17998 RSSI:233 Lock:0 SNR: 0.4 Scan Delta:0
...
```
Positions are in centidegrees (17998 = 179.98°). `Scan Delta:0` indicates no
motor movement — the scan reads RSSI at the fixed position. `Stop at:0` suggests
it scans until manually interrupted. Occasional SNR transients (0.10.4 dB)
appear from fleeting RF energy. The scan likely moves the motor when used with
the `azscanwxp` sky-scan command from the MOT menu.
## Dipswitch Subsystem (`DIPSWITCH>`)
| Command | Type | Description |
|---------|------|-------------|
| `dipswitch` | Read | Read physical DIP switch state and interpreted value |
```
dipswitch
val:ffffff01
app_dipswitch:101
```
- `val: ffffff01` — raw GPIO register reading. The `0x01` LSB indicates one
switch position is active; `0xFF` bytes are pulled-up (inactive) switches.
- `app_dipswitch: 101` — firmware-interpreted value. Matches NVS index 113
("Dipswitch Value: 101"), which encodes a DirecTV satellite configuration.
The physical DIP switch on the PCB selects the satellite provider's transponder
list for TV search mode. NVS index 112 ("Disable Dipswitch?") controls whether
the firmware reads the physical switch or uses the NVS-stored value. Since
we've disabled the tracker (NVS 20 = TRUE), the DIP switch setting is ignored.
## A3981 Motor Driver (`A3981>`)
| Command | Type | Description |
|---------|------|-------------|
| `diag` | Read | Read AZ/EL diagnostic pins (fault status) |
| `sm` | Read/Write | Get/set step size mode (AUTO, manual) |
| `ss` | Read/Write | Get/set step size (microstepping divisor) |
| `st` | Read/Write | Get/set torque level (HIGH/LOW current) |
| `cm` | Read/Write | Get/set current control mode (AUTO, manual) |
| `reset` | Write | Reset AZ/EL A3981 diagnostic/fault registers |
### Current State
```
diag → AZ DIAG: OK EL DIAG: OK
sm → AZ: Step Size Mode = AUTO EL: Step Size Mode = AUTO
ss → AZ: Step Size:1 EL: Step Size:1
st → AZ Torq:LOW EL Torq:LOW
cm → AZ: Mode = AUTO EL: Mode = AUTO
```
### Step Size Key
The `ss` value is a microstepping **divisor**, not multiplier:
| Value | Mode | Microsteps per full step |
|-------|------|------------------------|
| 16 | FULL | 1 (full step) |
| 8 | HALF | 2 |
| 4 | QTR | 4 |
| 2 | EIGHTH | 8 |
| 1 | SIXTEENTH | 16 (finest) |
Both motors are at `1` (1/16 microstepping — finest resolution). In `AUTO`
mode, the A3981 IC automatically selects the step size based on speed: finer
steps at low speed for smooth positioning, coarser steps at high speed for
torque.
### Torque and Current Control
`st` shows torque is `LOW` (motors idle/holding). `cm` shows current control
is `AUTO` — the firmware switches between high current (moving) and low current
(holding) automatically. This reduces power consumption and heat when the dish
is stationary.
The `diag` pins expose A3981 fault conditions: overcurrent, overtemperature,
open-load (disconnected motor winding), or short-to-ground. `OK` means no
faults detected. Use `reset` to clear latched fault flags.
## OS Subsystem (`OS>`)
| Command | Type | Description |
|---------|------|-------------|
| `id` | Read | Full MCU and firmware identification |
| `reboot` | Write | Reboot microcontroller (confirmed — full boot cycle ~10s) |
### System Identification (`id`)
```
NVS Version: 1.02.13
System ID: TWELINCH
K60-144pin
Silicon Rev 2.4
Mask Set 4N22D
512 kBytes of P-flash
P-flash only
128 kBytes of RAM
Board Rev ID: A
Board ID: STATIONARY
Ant ID: 12-IN G2
Software version: 02.02.48
CCLK: 96000000
BCLK: 48000000
Flash Base Address: 65536
Flash Size: 458752
```
Key details:
- **System ID:** `TWELINCH` — "Twelve Inch" (12" dish diameter)
- **Ant ID:** `12-IN G2` — Carryout G2 model identifier
- **Silicon:** K60-144pin, Rev 2.4, Mask 4N22D — NXP MK60DN512VLQ10
- **Clocks:** CCLK=96 MHz (core), BCLK=48 MHz (bus)
- **Flash:** Base at 0x10000 (64KB), size 458752 bytes (448KB usable firmware space)
- **NVS Version:** 1.02.13 — the non-volatile storage schema version
Note: `tasks` and `kill` commands (available on other variants like HAL 0.0.00)
are **not present** in the G2's OS submenu. On the G2, the satellite search is
disabled permanently via NVS index 20 instead of killing a running task.
## Firmware Command Behavior Notes
### Command Types
- **One-shot:** Executes once, returns result, shows prompt (`>`). Safe.
- **Streaming:** Runs indefinitely until interrupted. Some accept `q` to stop,
others require closing the serial port. The `ts`, `agc`, and `lnbv` commands
are known streamers.
- **Write:** Modifies state (motor position, NVS values, fault registers). Use
with caution.
### Serial Protocol Notes
- Firmware expects ASCII CR (`0x0D`) as line terminator
- Response terminates with `>` prompt character (ASCII 62)
- Console does not support backspace — press Enter to clear on typo
- Streaming commands consume all serial input while running
- Some commands (e.g., `st`) are setters that return "Invalid params" when
called without arguments — they are not read-only despite appearing in
help listings without obvious setter syntax
## TODO: Physical Board Inspection
Tasks for when the dome housing is opened and the control PCB is accessible.
### USB Port Investigation
- [ ] Inspect LQFP-144 package pins 19-22 (USB0_DP, USB0_DM, VOUT33, VREGIN)
— these are on the corner near pin 1. Look for traces routing to a
connector, test pads, or unpopulated header
- [ ] Look for a USB mini/micro/Type-A connector anywhere on the PCB (may be
behind a panel, under a shield can, or on the back side)
- [ ] Check for a VBUS sense GPIO trace — the K60 needs a GPIO pin to detect
USB cable insertion (ref manual Section 3.9.2)
- [ ] If USB pads found: probe with multimeter for continuity to K60 pins 19-22
### Debug / Programming Interface
- [ ] Look for SWD/JTAG debug header (K60 has dedicated SWD pins: SWDIO on
PTA3/pin 50, SWDCLK on PTA0/pin 46, SWO on PTA2/pin 49, RESET on pin 74).
Could be a 10-pin Cortex Debug connector, 2x5 shrouded header, or
unpopulated pads
- [ ] Check for a UART boot mode pin — K60 supports serial bootloader via
UART if NMI/FOPT bits are configured. Look for jumpers or DIP switches
near the MCU
- [ ] Identify the boot flash at 0x00000-0x0FFFF — is it internal to the K60
or an external SPI flash? Check for small SOIC-8 flash chips near MCU
### Firmware Extraction via SWD
Serial bootloader has no interactive mode (confirmed: no interrupt window,
no baud rate trick, binary-only status bytes `01 00` and `98 80 96`). SWD
is the primary extraction path.
**Equipment:** SWD probe (ST-Link V2, J-Link, or similar) + 4 jumper wires.
**Connections (minimum 3 wires + GND):**
| Signal | K60 Pin | LQFP-144 | Notes |
|--------|---------|----------|-------|
| SWDIO | PTA3 | Pin 50 | Bidirectional data |
| SWDCLK | PTA0 | Pin 46 | Clock (probe drives) |
| GND | — | Multiple | Common ground with probe |
| RESET | — | Pin 74 | Optional but recommended |
| SWO | PTA2 | Pin 49 | Optional trace output |
**Step 1: Check flash security (CRITICAL — do this first)**
```bash
# pyocd (recommended)
pyocd cmd -t mk60dn512xxx10 -c "read32 0x40C"
# or openocd
openocd -f interface/stlink.cfg -f target/k60.cfg \
-c "init; halt; mdw 0x40C; exit"
```
The Flash Security register (FTFL_FSEC) at `0x40C`:
- Bits [1:0] = `10`**SECURED** — SWD reads blocked, need EzPort fallback
- Bits [1:0] = `00` or `11`**UNSECURED** — full flash dump possible
Also check `0x400` (FTFL_FOPT / Flash Configuration Field at 0x400-0x40F):
```bash
pyocd cmd -t mk60dn512xxx10 -c "read8 0x400 16"
```
This 16-byte field (programmed into flash at 0x400) controls boot security,
backdoor key, mass erase enable, and other flash protection settings.
**Step 2: Dump firmware (if unsecured)**
```bash
# Full 512KB flash dump (bootloader + application)
pyocd flash -t mk60dn512xxx10 -r firmware_full.bin \
--address 0x00000 --size 0x80000
# Or just the application (448KB)
pyocd flash -t mk60dn512xxx10 -r firmware_app.bin \
--address 0x10000 --size 0x70000
# Also dump the 128KB RAM (may contain runtime state)
pyocd cmd -t mk60dn512xxx10 \
-c "savemem 0x1FFF0000 0x20020000 sram_dump.bin"
```
**Step 3: If flash is secured — EzPort fallback**
EzPort is an SPI-based flash interface activated by holding PTA4 (pin 51)
low during reset. It may bypass flash security for reads on some K60 silicon
revisions (Rev 2.4 / mask 4N22D — check errata).
```
EzPort SPI wiring:
EZP_CS = PTA4 (pin 51) — hold LOW during reset to enter EzPort
EZP_CLK = PTA0 (pin 46) — same pin as SWDCLK!
EZP_DOUT = PTA2 (pin 49) — same pin as SWO
EZP_DIN = PTA1 (pin 47)
```
EzPort commands: `0x03` = Read, `0x05` = Read Status, `0x0B` = Fast Read.
Use an SPI master (ESP32, RPi, FTDI MPSSE) at ≤ 4 MHz.
**Flash layout (from `os id`):**
```
0x00000 - 0x0FFFF Bootloader v1.01 (64KB)
0x10000 - 0x7FFFF Application v02.02.48 (448KB)
Total: 512KB (0x80000)
```
**Boot sequence bytes (from serial capture):**
```
reboot echo → 0x01 0x00 (binary status) → "Bootloader version: 1.01"
→ "Application is running..." → 0x98 0x80 0x96 (binary, meaning unknown)
→ "Application Starting Kinetis PCB..."
```
The `0x01 0x00` and `0x98 0x80 0x96` are binary protocol bytes between
bootloader and application — possibly a handshake or integrity check.
Understanding these requires disassembling the bootloader.
### Component Identification
- [ ] Photograph both sides of the PCB (high-res, good lighting)
- [ ] Read markings on the BCM4515 DVB tuner IC — confirm package, check for
additional Broadcom support ICs nearby
- [ ] Identify the two A3981 stepper driver ICs (TSSOP-28 with exposed pad,
should be near motor connectors). Note their orientation and surrounding
passives (sense resistors for current measurement)
- [ ] Read crystal oscillator marking — frequency determines PLL configuration
(K60 accepts 3-32 MHz, likely 8 MHz or 16 MHz for clean 96 MHz PLL)
- [ ] Check for external EEPROM (separate from K60 internal flash) — likely
I2C EEPROM near MCU, possibly AT24Cxx or M24Cxx series
- [ ] Look for LNB voltage regulator circuit (13V/18V switching) — probably
near the coax F-connector
### Gyro / IMU Investigation
- [ ] NVS indices 72-77 configure gyro parameters (sensitivity, filter, mount
type) but boot log says nothing about a gyro. Check if there's an
onboard MEMS gyro or an unpopulated footprint for one
- [ ] If present, identify the gyro IC (likely single-axis rate gyro given
the NVS parameters — "Gyro Mount Type: 1" suggests a specific axis)
- [ ] If unpopulated: the G2 may share a PCB design with a larger model
(Trav'ler SK-1000?) that includes a gyro for in-motion tracking
### GPS Investigation
- [ ] Boot log says "GPS Not Found" — look for a GPS module footprint
(populated or empty). NVS 63-64 configure GPS heading/moving thresholds
- [ ] Check for a GPS antenna connector (SMA or U.FL) or ceramic patch antenna
on the PCB
- [ ] If unpopulated: same shared-PCB theory as the gyro — the G2 firmware
has GPS support but the hardware may be DNP (Do Not Populate)
### Motor / Drive Train
- [ ] Trace SPI bus from K60 to A3981 drivers — identify which DSPI peripheral
(DSPI0, DSPI1, or DSPI2) connects to which axis (AZ vs EL)
- [ ] Identify motor sense resistors near A3981 chips — value determines
current limit calibration. Compare with NVS current limit hex values
(indices 95-98, 110-111)
- [ ] Check motor connectors — are they direct stepper connections (4-wire) or
do they go through additional driver stages?
- [ ] Look for limit switches or optical encoders (the G2 uses stall detection
for homing, but there may be unused provisions for position feedback)
### RF Signal Path
- [ ] Trace signal path: F-connector → LNB bias tee → BCM4515 RF input
- [ ] Identify any bandpass filters, LNAs, or frequency conversion stages
between the coax and the BCM4515
- [ ] Check if the ADC RSSI signal is a dedicated analog output from BCM4515
or if it's derived from an AGC control voltage
### Power Supply
- [ ] Identify main voltage rails — the board likely has 12V input (from
RP-SK87 PSU), 5V (for logic), 3.3V (K60 core), and motor supply
- [ ] Check if the USB VREG (K60 pins 21-22) is connected to anything or
if VOUT33 is tied to the board's 3.3V rail directly
- [ ] Identify motor power supply — A3981 supports up to 28V, the board
likely runs motors at 12V from the main supply
## TODO: Firmware Exploration (Serial Console)
Remaining commands to test via the RS-422 console.
### DVB Submenu (Task #13) — COMPLETE
- [x] `ls` — streaming transponder scan (exits DVB submenu on interrupt!)
- [x] `qls` — streaming quick lock status (~100ms: `Lock:0 rssi:500 cnt:0`)
- [x] `snr` — streaming SNR readings (transient spikes in noise floor)
- [x] `diag` — multi-block one-shot per-transponder diagnostics
- [x] `table` — full 32-transponder scan (~136s), detailed per-xp data
- [x] `freqs` — frequency list name ("Non-Stacked")
- [x] `stats` — streaming, silent when no lock
- [x] `nid` — streaming network ID (FFFF/none), CR-overwrite display
- [x] `di2id` / `di2stat` / `di2conf` / `di2sc` / `di2rcs` — all fail (no switch)
- [x] `di2cs` — needs parameters (switch config)
- [x] `send` — needs 3-6 hex bytes (raw DiSEqC packet)
- [x] `h 1-13` — full parameter help documented
- [x] `tabto` / `to` — timeout configs (table vs single tune)
- [x] `ovraddr` / `rrto` / `tdthresh` / `pretx` — DiSEqC timing params
- [x] `srch` / `shuf` / `tablex` — toggle commands
- [x] `pwr` — power state, `srch_mode` — search mode, `range` — scan range
- [x] `def` — silent defaults reset (dangerous!), `msw` — format unknown
- [ ] `e <n> <v>` — edit channel params (not tested — would modify state)
- [ ] `send <hex>` — raw DiSEqC (not tested — no switch connected)
### MOT Submenu (Task #14) — COMPLETE
- [x] Full `?` help listing — 25 commands discovered (see Motor Control section)
- [x] `a` supports relative moves with `+`/`-` prefix (undocumented upstream!)
- [x] `h *` homes both motors simultaneously
- [x] `l` — lists 2 motors: 0=AZIMUTH (local), 1=ELEVATION (local)
- [x] `ma` / `mv` — read motor dynamics (400 deg/s², 65/45 deg/s AZ/EL)
- [x] `p` — read step positions (19998 AZ, 3116 EL)
- [x] `elminmaxhome` — EL min=18°, max=65°, home=65°
- [x] `ela2s` / `els2a` — angle↔step conversion (linear mapping confirmed)
- [x] `e` / `r` — engage/release motors
- [x] `azscan` / `azscanwxp` — sky scan commands documented
- [x] `sd` — stall detection documented
- [x] `life` / `motorlife` / `motorboth` — factory life test commands
- [x] `pid`, `sp`, `sw`, `w`, `v`, `vms` — write-only commands documented
- [ ] `g <az> <el>` — NOT available on G2 (not in help listing)
- [ ] Test `h 0` and `h 1` homing (requires clear space around dish)
- [ ] Test `azscanwxp` with real scan parameters (RF imaging)
### NVS Experiments (Caution)
- [ ] NVS 2 ("Debug 2nd Console Port") — try setting to 1, check if USB
console activates. **Save original value first, restore after test**
- [ ] NVS 4 ("Debug Port Connection") — similar test
- [ ] NVS 112 ("Disable Dipswitch?") — what happens if dipswitch is disabled?
- [ ] NVS 38 ("Sleep Mode Timer") — currently 420s (7 min). Set higher to
prevent sleep during long tracking sessions
### A3981 Submenu (Task #16) — COMPLETE
- [x] Full `?` help listing — 6 commands: `diag`, `sm`, `ss`, `st`, `cm`, `reset`
- [x] `diag` — AZ/EL both OK (no faults)
- [x] `sm` — step size mode: both AUTO
- [x] `ss` — step size: both 1 (1/16 microstepping, finest)
- [x] `st` — torque: both LOW (idle)
- [x] `cm` — current control mode: both AUTO
- [ ] Test `diag` output under motor load (move motor, read diag simultaneously)
- [ ] Test changing step mode from AUTO to manual
- [ ] `reset` — clear fault flags (no faults to clear currently)
### ADC Submenu (Task #16) — COMPLETE
- [x] Full `?` help listing — 5 commands: `bdid`, `bdrevid`, `rssi`, `m`, `scan`
- [x] `bdid` → STATIONARY, `bdrevid` → A
- [x] `rssi` → 232 (noise floor baseline)
- [x] `scan` — streaming: position + RSSI + Lock + SNR + delta per sample
- [x] `m` — returns immediately (toggle for background monitoring?)
### Dipswitch Submenu (Task #16) — COMPLETE
- [x] Full `?` help listing — 1 command: `dipswitch`
- [x] `dipswitch` → val:ffffff01, app_dipswitch:101
### GPIO Submenu (Task #16) — COMPLETE
- [x] Full `?` help listing — 4 commands: `regs`, `r`, `w`, `dir`
- [x] `regs` — full 101-pin dump (5 ports, all pin states)
- [x] `r <port> <pin>` / `w <port> <pin>` / `dir <bit> <val>` — help documented
- [ ] Map specific GPIO pins to hardware functions (SPI→A3981, UART, I2C, etc.)
### LATLON Submenu (Task #16) — COMPLETE
- [x] Full `?` help listing — 1 command: `l <satlon1> <satlon2> <satel1> <satel2>`
- [x] Tested with satellite positions — self-localization algorithm confirmed
### OS Submenu (Task #16) — COMPLETE
- [x] Full `?` help listing — 2 commands: `id`, `reboot` (no `tasks`/`kill` on G2!)
- [x] `id` — full system identification including NVS version, MCU details, clock speeds
- [x] Deep probe: `go`/`date`/`time` initially appeared as hidden commands but were
confirmed as **false positives** — boot sequence output captured after `reboot`
caused a system restart mid-probe. All three return "Invalid command" post-boot.
### PEAK Submenu (Task #16) — COMPLETE
- [x] Full `?` help listing — 6 commands: `ts`, `pw`, `psnr`, `pxy1`, `stb`, `rssits`
- [x] `stb` — LNB polarity switching test (18V=504 RSSI, 13V=232 RSSI)
- [x] `rssits` — streaming every 1 min, exits submenu on interrupt
- [x] `pw` / `psnr` help — both take `<az_deg> <el_deg>` (signal peaking algorithms)
- [x] `pxy1` help — takes `<n>` repeat count (2D cross-pattern peak search)
- [ ] Test `pw` / `psnr` / `pxy1` with a real satellite signal
### EEPROM Submenu (Task #15) — COMPLETE
- [x] Full `?` help listing — 3 commands: `ee`, `inv`, `def`
- [x] Complete EEPROM dump (all 17 indices, 0-16) via `scripts/ee_dump.py`
- [x] Bounds discovery: firmware enforces Min:0 Max:16
- [x] Sentinel value: 0x00010101 for invalid/uninitialized entries
- [x] Only 6 indices (0-5) ever written; 6-16 factory default (invalid)
- [ ] `def` — restore factory defaults (not tested — would reset all values)
- [ ] Determine meaning of valid EEPROM values (indices 1-5)
- [ ] Restore index 0 via `def` or `ee 0 <value>` if original value is discovered
### STEP Submenu (Task #16) — COMPLETE
- [x] `e` — engage motors ("Motors engaged"), verified via MOT
- [x] `r` — release motors (documented via MOT, not tested to avoid losing position)
- [x] `p` — read step positions (AZ=19998, EL=3116)
- [x] `ma` / `mv` — read accel and velocity in raw microstep units
- [x] `pid`**readable in STEP** (write-only in MOT): Kp=250, Kv=50
- [x] MOT↔STEP conversion table verified (linear mapping)
- [ ] `p <motor> <pos>` — test absolute microstep move (40000 steps/rev AZ, 24960 EL)
- [ ] `v <motor> <vel>` — test continuous velocity mode
### Hidden Command Deep Probe — COMPLETE
Systematic brute-force probe of 415 candidate commands across all 13 menu levels
(root + 12 submenus). Script: `scripts/hidden_menu_probe.py --deep`
**Results by submenu:**
| Submenu | Probed | Hits | Known | New | Notes |
|---------|--------|------|-------|-----|-------|
| TRK> | 415 | 3 | 3 | 0 | Only q, Q, help |
| OS> | 415 | 7 | 4 | 0 | `go`/`date`/`time` were false positives (see below) |
| MOT> | 415 | 22 | 22 | 0 | All in help listing already |
| DVB> | 415 | 12 | 12 | 0 | All previously discovered |
| STEP> | 415 | 12 | 12 | 0 | Mirrors MOT (engage/release/pos/vel) |
| A3981> | 415 | 5 | 5 | 0 | All in help listing already |
| ADC> | 415 | 7 | 7 | 0 | |
| GPIO> | 415 | 7 | 7 | 0 | |
| PEAK> | 415 | 3 | 3 | 0 | |
| NVS> | 415 | 412 | n/a | 0 | False positives: auto-advance cursor |
| EE> | 415 | 3 | 3 | 0 | |
| LATLON> | 415 | 5 | 5 | 0 | |
| DIPSWITCH> | 415 | 3 | 3 | 0 | |
**OS false positives explained:** The probe hit `reboot` early in the OS
sequence, causing a full system restart (~10s). The probe script continued
sending commands into the boot stream. `go`, `date`, and `time` appeared as
"hits" because their responses contained non-error text — but this text was
**unsolicited boot output**, not command responses:
- `go` "response" = SPI2 initialization output (BCM4515 firmware transfer at boot)
- `date` "response" = BCM4515 hardware init (AP RAM FW VERIFIED, chip IDs)
- `time` "response" = DVB channel init (dvbPrvChangeChannelInit Complete)
All three return "Invalid command" when tested manually in the OS submenu after
boot completes. The boot output is still valuable — it reveals the SPI2 clock
(6,857,142 Hz = 48 MHz / 7), SPI mode 3 (CPOL=1, CPHA=1), and the full
BCM4515 bring-up sequence.
**NVS false positives:** The NVS submenu returned 412 hits because its parser
treats any unrecognized input as "read next entry" — an internal cursor
auto-advances through the NVS table. These are NOT hidden commands, just the
fallthrough behavior of the NVS `e` (edit/read) command parser.
**Conclusion:** Zero genuinely hidden commands found across all 13 menu levels.
The G2 firmware shell is well-scoped — no memory access, no flash read/dump,
no hidden debug backdoors. Firmware extraction requires physical access
(SWD or EzPort).