Stock firmware at 0x0DDD reads back each init block from register A7
after writing and verifies the data matches (with bit 7 XOR'd on the
block address byte). Without this readback, the BCM4500 may not
finalize the write — our init blocks were "silently failing" (I2C
succeeds, status reports booted, but SNR reads all zeros).
Changes:
- bcm_write_init_block: add pre-write bcm_poll_ready(), post-write
A7 readback with XOR(0x80) on byte[0] and full data comparison
- i2c_rd buffer: expand from 8 to 24 bytes for 16-byte block readback
This is the most likely root cause of the BCM4500 "boots but doesn't
work" issue (Task #5). Needs hardware test to confirm.
Two root causes prevented BCM4500 init block writes from completing:
1. Init block data arrays included length prefix bytes from the stock
firmware's XDATA format (17-byte blocks at code:0x0B4F). The stock
firmware reads byte 0 as length and writes bytes 1..N to A7.
Blocks 0 and 1 had the length prefix (0x06, 0x07) as the first
data byte, corrupting the DSP's indirect register FIFO.
2. The BCM3440 gateway's A8 register does not clear bit 0 after
indirect write commands (0x03), even though the BCM4500 processes
them successfully (confirmed via direct address 0x08 where A8
transitions from 0x03 → 0x02). bcm_poll_ready() now treats
gateway timeout as success with a settling delay.
Boot now completes reliably in ~0.96s through all stages:
GPIO → power → reset → PLL/DSP load → init blocks 0,1,2 → 0xFF.
Addresses all remaining Apollo review items:
- C-3: DiSEqC Timer2 spin loops now have timeout protection via
diseqc_wait_ticks() with per-tick I2C_TIMEOUT countdown. Tone burst
refactored to use the shared helper instead of bare while(!TF2) loops.
New ERR_DISEQC_TIMER (0x0C) error code on Timer2 failure.
- I-3: Hotplug scan hp_prev copy moved from start to end of scan.
Aborted scans no longer corrupt the previous-good baseline, preventing
false "removed" events on consecutive scan failures.
- S-1: Watchdog-fired detection in main loop — when wdt_armed==2 (ISR
fired), sets ERR_WDT_FIRED (0x0D) readable via 0xBC and clears
BM_STARTED|BM_FW_LOADED|BM_ARMED so host sees system is down.
- I-2: Comment explaining (WORD)sd_poll_count cast as SDCC optimization.
- I-1: CKCON bit ownership comment in diseqc_tone_burst() Timer2 setup.
Code: 13,079 / 15,360 bytes (85%). XRAM: 218 / 512. Stack: 132 bytes.
Safety review identified infinite-hang paths in do_tune(), GPIF streaming,
EP0/EP2 FIFO waits, and the 0xB4 I2C bus scan. The firmware controls an LNB
power supply (750mA at 18V) on a Cypress FX2LP with no hardware watchdog.
Key changes:
- Software watchdog via Timer0 ISR (~2s timeout, cuts LNB power on stall)
- Replace fx2lib i2c_write() in do_tune() with timeout-protected helper
- ep0_wait_data() helper replaces 7 bare EP0 BUSY spin loops
- GPIF idle wait and EP2 FIFO full wait now have timeouts
- 0xB4 I2C bus scan uses i2c_wait_done()/i2c_wait_stop() instead of bare spins
- Return-value checks on all I2C writes in sweep/scan functions
- EP0 payload length validation on all vendor commands with data phase
- Zero-fill EP0BUF before indirect reads (0x87, 0xB7) for deterministic output
- i2c_wait_stop() now sets last_error on timeout
- New error codes: ERR_TUNE_FAIL through ERR_DISEQC_LEN
- BCM_LOCK_BIT constant replaces hardcoded 0x20 in lock checks
- DiSEqC Tone Burst B rejected with ERR_NOT_SUPPORTED
- DiSEqC message length error sets ERR_DISEQC_LEN
- hp_changes counter saturates instead of wrapping
- stream_diag_poll() only updates status on successful I2C reads
- do_tune() forward declaration eliminates implicit-function warning
- do_tune() sets ERR_BCM_NOT_READY when demod not booted
- wdt_kick() in all long-running sweep/scan loops
Code: 13,057 / 15,360 bytes (85%). XRAM: 218 / 512 bytes (43%).
Stack: 132 bytes free. Zero new SDCC warnings.
Phase D firmware hardening: vendor commands 0xBD (streaming diagnostics)
and 0xBE (I2C hot-plug detection) with Python library, bridge, and demo
support. All I2C operations use timeout-protected helpers, BCM4500 reads
are rate-limited during streaming, and frame counter reads use atomic
read-verify-reread pattern. Counters saturate instead of wrapping.
Firmware v3.02.0 adds three new vendor commands:
- 0xB7 SIGNAL_MONITOR: fast 8-byte combined signal read
- 0xB8 TUNE_MONITOR: tune + dwell + read in one round-trip
- 0xB9 MULTI_REG_READ: batch read up to 64 indirect registers
New tools/skywalker.py provides five modes that use the BCM4500's
AGC registers as a crude power detector across 950-2150 MHz IF,
even without demodulator lock:
- spectrum: sweep analyzer with ASCII/waterfall/matplotlib display
- scan: automated transponder scanner (sweep + peak detect + blind scan)
- monitor: real-time signal strength for dish alignment
- lband: direct input analyzer with L-band allocation annotations
- track: carrier/beacon tracker with CSV/JSON logging and drift detection
Extracts shared SkyWalker1 class and constants into skywalker_lib.py;
tune.py now imports from the shared library.
Removed I2CS bmSTOP "bus reset" from bcm4500_boot() and debug modes.
Sending STOP with no active transaction puts the FX2 I2C controller
into an inconsistent state where subsequent START+ACK detection fails.
Root cause identified through incremental debug modes (wValue 0x80-0x85)
on live hardware: mode 0x82 (with bmSTOP) fails, mode 0x85 (identical
but without bmSTOP) succeeds. Raw I2C reads confirm BCM4500 is alive
the entire time -- only the controller state is corrupted.
BCM4500 now boots successfully in ~90ms. Three I2C devices found on
bus: 0x08 (BCM4500), 0x10 (tuner/LNB), 0x51 (EEPROM).
Also in this commit:
- Timeout-protected I2C functions replacing fx2lib bare while loops
- I2C bus scan and debug mode infrastructure
- Kernel driver blacklist for dvb_usb_gp8psk
- Test tools for incremental boot debugging
- Technical findings documented in docs/boot-debug-findings.md
Correct BCM4500 I2C address from 0x10 (8-bit wire) to 0x08 (7-bit)
since fx2lib shifts internally. Add i2c_combined_read() with repeated
START for proper BCM4500 register access. Add I2C bus scan (0xB4),
raw read (0xB5), and indirect protocol diagnostic (0xB6) commands.
Single-transaction indirect reads/writes for BCM4500 register protocol.
Verified on hardware: BCM4500 ACKs at 0x08, BOOT_8PSK returns config
0x03. Register reads still return zeros — BCM4500 needs DSP firmware
loaded via LOAD_BCM4500 (0x88) before registers become functional.
Custom firmware (SDCC + fx2lib) implements all stock vendor commands
(0x80-0x94) plus new commands for spectrum sweep (0xB0), raw BCM4500
register access (0xB1/0xB2), and blind scan (0xB3). Compiles to 6.3KB
of code with healthy RAM margins.
RAM loader (fw_load.py) uses the FX2 0xA0 vendor request to load
firmware into RAM without touching EEPROM -- power cycle restores
factory firmware. Supports Intel HEX and raw binary formats.