skywalker-1/firmware-analysis-v206-vs-v213.md
Ryan Malloy bbdcb243dc Normalize line endings to LF across entire repository
Apply .gitattributes normalization to convert all CRLF line
endings inherited from Windows-origin source files to Unix LF.
175 files, zero content changes.
2026-02-20 10:55:50 -07:00

28 KiB

Genpix SkyWalker-1 FX2 Firmware Comparative Analysis: v2.06 vs v2.13 FW1

Executive Summary

v2.13 is a significant evolution of the v2.06 firmware with 21 additional functions (82 vs 61). The key changes are:

  1. Three new vendor commands (0x99, 0x9A, 0x9C) for LNB/tuner control
  2. Restructured INT0 handler with active satellite front-end polling
  3. I2C-based initialization with retry logic for the satellite demodulator
  4. Version-aware code paths (hardware revision detection via descriptor byte)
  5. Refactored DiSEqC GPIO pin assignment (data pin moved from P0.7 to P0.0, same bit-bang algorithm)
  6. Simplified BCM4500 status polling (consolidated from 3 register reads to 1)

1. Vendor Command Dispatch Table Comparison

Both versions use the same two-stage USB control request dispatch:

  • Stage 1 (FUN_CODE_032a / FUN_CODE_034e): Handles standard USB requests (bRequest 0x00-0x0B) via a 12-entry jump table at CODE:033B / CODE:035F. These handle GET_STATUS, CLEAR_FEATURE, SET_FEATURE, SET_ADDRESS, GET_DESCRIPTOR, SET_DESCRIPTOR, GET_CONFIGURATION, SET_CONFIGURATION, GET_INTERFACE, SET_INTERFACE, SYNCH_FRAME, and FW_VERSION_READ (0x0B).

  • Stage 2 (FUN_CODE_0056, identical in both versions): Handles vendor requests (bmRequestType bit 6 set, bRequest 0x80-0x9D) via a 30-entry jump table at CODE:0076. Range check: (bRequest + 0x80) < 0x1E, dispatching to JMP @A+DPTR at 0x76 + (bRequest - 0x80) * 2.

Vendor Command Jump Table (0x80-0x9D)

bRequest Name v2.06 Target v2.13 Target Status
0x80 GET_8PSK_CONFIG 0x00B2 0x00B2 Both: Read config byte to EP0BUF (v2.06: IRAM 0x6D, v2.13: IRAM 0x4F)
0x81 SET_8PSK_CONFIG 0x0326 (STALL) 0x034A (STALL) Both STALL - not implemented in either
0x82 (reserved) 0x0326 (STALL) 0x034A (STALL) Both STALL
0x83 I2C_WRITE 0x00F1 0x00F1 Both: Same I2C write handler
0x84 I2C_READ 0x0102 0x0102 Both: Same I2C read handler
0x85 ARM_TRANSFER 0x0110 0x0110 Both: Same ARM transfer handler
0x86 TUNE_8PSK 0x012E 0x012E Both: Same tuning handler
0x87 GET_SIGNAL_STRENGTH 0x0140 0x0162 CHANGED - see section below
0x88 LOAD_BCM4500 0x0326 (STALL) 0x034A (STALL) Both STALL - BCM4500 loading via different mechanism
0x89 BOOT_8PSK 0x00C4 0x00C4 Both: Same boot handler
0x8A START_INTERSIL 0x019C 0x01BE Relocated but functionally similar
0x8B SET_LNB_VOLTAGE 0x01CB 0x01ED Relocated but functionally similar
0x8C SET_22KHZ_TONE 0x01DD 0x01FF Relocated but functionally similar
0x8D SEND_DISEQC_COMMAND 0x01EF 0x0211 CHANGED - different DiSEqC implementation
0x8E SET_DVB_MODE 0x0326 (STALL) 0x034A (STALL) Both STALL
0x8F (unknown) 0x01FC 0x021E Both: Similar read-back function
0x90 GET_SIGNAL_LOCK 0x020B 0x022D Relocated but functionally similar
0x91 (unknown) 0x022C 0x024E Both: I2C read-back
0x92 (unknown) 0x024A 0x026C Both: I2C read-back
0x93 GET_SERIAL_NUMBER 0x026F 0x0293 Relocated but functionally similar
0x94 (unknown) 0x01B9 0x01DB Both: LNB-related
0x95 (unknown) 0x02DF 0x0303 Both: Read-back function
0x96 (unknown) 0x02B4 0x02D8 Both: Similar handler
0x97 (unknown) 0x02C1 0x02E5 Both: Similar handler
0x98 (unknown) 0x02CB 0x02EF Both: Similar handler
0x99 NEW: GET_DEMOD_STATUS 0x0326 (STALL) 0x0317 ADDED in v2.13
0x9A NEW: INIT_DEMOD 0x0326 (STALL) 0x0140 ADDED in v2.13
0x9B (reserved) 0x0326 (STALL) 0x034A (STALL) Both STALL
0x9C NEW: DELAY_COMMAND 0x0326 (STALL) 0x032B ADDED in v2.13
0x9D SET_MODE_FLAG 0x02FA 0x033A CHANGED - different implementation

Commands Added in v2.13

0x99 - GET_DEMOD_STATUS (new read command)

LCALL FUN_CODE_2421          ; calls FUN_CODE_2239(0x3F, 0xF9)
                             ; -> I2C read from device 0x3F, register 0xF9
MOV EP0BUF[0], R7            ; return I2C read result
EP0BCL = 1                   ; send 1 byte back

Reads demodulator register 0xF9 via I2C (device address 0x3F) and returns the value to the host. This is a status/diagnostics register read for the satellite demodulator IC.

0x9A - INIT_DEMOD (new control command)

LCALL 0x231E                 ; EP0 flush/prepare
if (config_flags.0 == 1) {   ; check if demodulator is present
    counter = 0;
    while (counter < 3) {
        LCALL FUN_CODE_1977  ; initialization step
        if (success) break;
        counter++;
    }
}
EP0BCL = 0                   ; no data returned

Performs up to 3 attempts to initialize the demodulator, but only if the demodulator-present flag (bit 0 of DAT_INTMEM_4F) is set. This provides host-triggered re-initialization capability.

0x9C - DELAY_COMMAND (new control command)

R7 = wValue                  ; delay parameter from USB SETUP packet
LCALL FUN_CODE_1ac6          ; tuning/acquisition delay function
EP0BCL = 0                   ; no data returned

Calls FUN_CODE_1ac6 with the wValue parameter from the USB SETUP packet. FUN_CODE_1ac6 performs an I2C-based tuning acquisition sequence: it reads demod register 0xF9, writes control values to registers 0xF8 and 0xF9, then polls register 0xF9 up to 40 times (0x28) waiting for bit 0 to be set (lock acquired). If lock fails, it calls FUN_CODE_1e3c which performs a full demodulator reset sequence.

Commands Removed from v2.13

None were removed -- all commands that were STALL in v2.06 remain STALL in v2.13.

Changed Command Implementations

0x87 - GET_SIGNAL_STRENGTH (modified)

  • v2.06 at 0x0140: Checks DAT_INTMEM_6D bit 0 (demod active), reads three I2C status registers (0xA2, 0xA8, 0xA4) to compute signal quality, loops up to 6 iterations polling for demod readiness
  • v2.13 at 0x0162: Checks DAT_INTMEM_4F bit 0 (demod active), reads I2C but uses different function call chain (FUN_CODE_1278 vs FUN_CODE_0c97). Same overall logic with relocated internal variables.

0x8D - SEND_DISEQC_COMMAND (GPIO pin reassignment)

  • v2.06: LCALL 0x23e0; LCALL 0x1e41 -- GPIO bit-bang DiSEqC via FUN_CODE_2098, data on P0.7, carrier on P0.3
  • v2.13: LCALL 0x231e; LCALL FUN_CODE_0dbc -- GPIO bit-bang DiSEqC via FUN_CODE_2060, data on P0.0, carrier on P0.3

Both versions use the identical algorithm:

  1. Read wLength (0xE6BE) as message byte count
  2. Clear P0.3 (disable 22kHz carrier)
  3. Delay 15 ticks via delay function (7.5ms settling time)
  4. If message bytes present: iterate through EP0BUF, sending each byte via Manchester-encoded bit-bang (8 data bits + odd parity, 3 Timer2 ticks per bit)
  5. If wValue == 0 and no bytes: tone burst A (25 Timer2 ticks = 12.5ms)
  6. If wValue != 0 and no bytes: tone burst B via byte transmit with 0xFF pattern

CORRECTION: Earlier analysis incorrectly identified v2.13 as using "I2C-based DiSEqC." Deep decompilation of the sub-functions (FUN_CODE_2060, FUN_CODE_22f3, FUN_CODE_22b0) reveals they are GPIO bit-bang implementations identical in algorithm to v2.06's FUN_CODE_2098 and FUN_CODE_2372. The only change is the data pin assignment (P0.7 -> P0.0), reflecting a different PCB layout.

0x9D - SET_MODE_FLAG (different logic)

  • v2.06: Reads byte at descriptor_base + 10, checks if value is 4, 5, or 6, and conditionally sets bit flag 0x06 based on wValue - 1
  • v2.13: Simply checks if wValue != 0, and if so calls FUN_CODE_21d1 which performs a conditional demodulator reset (calls FUN_CODE_1e3c if _1_4 flag isn't already set, then writes I2C control registers 0xFC on both device 0x7F and 0x3F)

2. Key Function Correspondence

v2.06 Function v2.13 Function Role
main (0x188D) main_entry (0x170D) RESET vector - clears IRAM, processes init table, jumps to init
FUN_CODE_09a7 (0x09A7) FUN_CODE_0800 (0x0800) Main init + main loop
FUN_CODE_13c3 (0x13C3) FUN_CODE_11ab (0x11AB) USB/peripheral descriptor setup
FUN_CODE_032a (0x032A) FUN_CODE_034e (0x034E) Standard USB request handler
FUN_CODE_0056 (0x0056) FUN_CODE_0056 (0x0056) Vendor request dispatcher (identical code)
FUN_CODE_2297 (0x2297) FUN_CODE_21ec (0x21EC) Main loop poll (USB IRQ processing)
FUN_CODE_21ed (0x21ED) FUN_CODE_2189 (0x2189) EP2CS setup + PCON idle
FUN_CODE_211d (0x211D) FUN_CODE_20b9 (0x20B9) CPUCS reset pulse (EP2 management)
FUN_CODE_2174 (0x2174) FUN_CODE_2110 (0x2110) USB descriptor type walker (identical code)
FUN_CODE_1919 (0x1919) FUN_CODE_1800 (0x1800) GPIF/FIFO management (identical logic)
FUN_CODE_1d4f (0x1D4F) -- v2.06 demod init (GPIO-based, complex)
-- FUN_CODE_1d4b (0x1D4B) v2.13 demod init (I2C write 4 bytes to 0x7F/0xF0)
FUN_CODE_1da8 (0x1DA8) -- v2.06 I2C read with timeout (uses FUN_CODE_1556)
-- FUN_CODE_0eea (0x0EEA) v2.13 I2C read with retry (20 attempts)
FUN_CODE_1dfb (0x1DFB) FUN_CODE_14b9 (0x14B9) Delay loop (clock-dependent timing)
FUN_CODE_1cf3 (0x1CF3) FUN_CODE_1c44 (0x1C44) Configuration update function
FUN_CODE_12ea (0x12EA) FUN_CODE_1000 (0x1000) USB endpoint configuration
FUN_CODE_0ddd (0x0DDD) FUN_CODE_0ca4 (0x0CA4) BCM4500 firmware loader
FUN_CODE_2000 (0x2000) FUN_CODE_208d (0x208D) BCM4500 status polling
FUN_CODE_1556 (0x1556) FUN_CODE_0eea (0x0EEA) I2C multi-byte transfer
FUN_CODE_24d2 (0x24D2) FUN_CODE_243d (0x243D) SET_DVB_MODE config store
FUN_CODE_2419 (0x2419) FUN_CODE_2357 (0x2357) GET config byte to EP0BUF
FUN_CODE_23cb (0x23CB) FUN_CODE_2309 (0x2309) Read descriptor byte to EP0BUF
FUN_CODE_1a0e (0x1A0E) -- v2.06 serial number reader (EEPROM)
INT0_vec (0x0003) INT0_vector (0x0003) INT0 interrupt handler (significantly different)
-- FUN_CODE_2239 (0x2239) v2.13 I2C single-byte read helper
-- FUN_CODE_2031 (0x2031) v2.13 USB reconnect function
-- FUN_CODE_1799 (0x1799) v2.13 demod checksum/signature verify
-- FUN_CODE_1ca0 (0x1CA0) v2.13 descriptor checksum verify
-- FUN_CODE_1ac6 (0x1AC6) v2.13 tuning acquisition sequence
-- FUN_CODE_1e3c (0x1E3C) v2.13 demodulator full reset
-- FUN_CODE_10d9 (0x10D9) v2.13 demod status polling/init
-- FUN_CODE_0dbc (0x0DBC) v2.13 DiSEqC GPIO bit-bang wrapper (data on P0.0)

USB Descriptor Setup (FUN_CODE_13c3 vs FUN_CODE_11ab)

Both functions are structurally identical:

  1. Disable USB disconnect (0xE605 bit 1 clear)
  2. Configure IFCONFIG (0xE600) for internal clock, 48MHz
  3. Set REVCTL (0xE601) to 0xCA
  4. Configure GPIFIDLECTL, PORTACFG
  5. Set PORT registers (P0, P3, IPL1)
  6. Configure GPIFCTLCFG, FIFORESET, FIFOPINPOLAR
  7. Configure Timer2 (RCAP2H=0xF8, RCAP2L=0x2F -> ~2ms period at 48MHz/12)
  8. Initialize subsystem modules

Key difference: v2.13 calls INT0_vector() (the INT0 handler) during initialization as a probing step. This runs the demodulator availability check during USB setup, before enabling interrupts. v2.06 does not do this.

Descriptor pointer offsets:

  • v2.06: BANK1_R4:R5 = 0x1200 (descriptor base)
  • v2.13: BANK1_R4:R5 = 0x0E00 (descriptor base, lower due to code restructuring)

3. Structural Differences

3.1 v2.13 Retry Loops (FUN_CODE_1799 and FUN_CODE_1ca0)

In the main init function FUN_CODE_0800, v2.13 has:

// Retry loop 1: FUN_CODE_1799 - demodulator signature verification
DAT_INTMEM_36 = 0x14;  // 20 attempts
while (DAT_INTMEM_36 != 0 && FUN_CODE_1799() fails) {
    DAT_INTMEM_36--;
}
if (DAT_INTMEM_36 == 0) {
    FUN_CODE_1ac6(100);  // tuning acquisition with 100ms delay
}

// Retry loop 2: FUN_CODE_1ca0 - descriptor checksum verification
DAT_INTMEM_36 = 0x14;  // 20 attempts
while (DAT_INTMEM_36 != 0 && FUN_CODE_1ca0() fails) {
    DAT_INTMEM_36--;
}
if (DAT_INTMEM_36 == 0) {
    FUN_CODE_1ac6(100);  // tuning acquisition with 100ms delay
}

FUN_CODE_1799 - Demodulator Signature Verification:

  1. Calls FUN_CODE_1d4b() which writes 4 bytes via I2C to device 0x7F, register 0xF0 (demodulator control)
  2. Saves parameters DAT_INTMEM_39:3A
  3. Checks if parameters match 0x021C (known good value) - returns early if match
  4. Reads 5 I2C bytes via FUN_CODE_0718, each at register 0x0A offset (stepping by 2)
  5. Subtracts 0x30 ('0') from each byte (ASCII to binary conversion)
  6. Sums the values and compares sum to the saved parameters
  7. Returns success (carry set) if checksum matches

This verifies the demodulator responds correctly with the expected identification pattern. The ASCII-to-binary conversion suggests the demod returns a readable version string at register 0x0A.

FUN_CODE_1ca0 - Descriptor Checksum Verification:

  1. Iterates bytes 6 through 0x29 (36 bytes) of the BANK2_R6:R7 descriptor block
  2. Computes running sum, compares against expected value 0x0706
  3. If first block passes, iterates bytes 0x2C through 0x4F (36 bytes) of same block
  4. Computes second running sum, compares against expected value 0x0686
  5. Returns success only if both checksums match

This validates the integrity of a 2-block descriptor/configuration structure (possibly EEPROM-loaded calibration data).

v2.06 equivalent: v2.06 does NOT have these retry loops. It calls FUN_CODE_1a0e (serial number/EEPROM reader) directly without verification, then proceeds immediately. There is no signature check or checksum validation.

3.2 Version Byte Check (Hardware Revision Detection)

After the retry loops, v2.13 performs:

// Read byte at descriptor_base + 10
byte version_byte = *(BANK1_R4:R5 + 10);  // 0x0E0A
if (version_byte == 0x03) {
    bVar4 = 0x80;  // flag = set
} else {
    bVar4 = 0;     // flag = clear
}
_1_3 = bVar4 >> 7;  // store as bit flag _1_3

This reads byte offset 10 from the USB descriptor base address. Offset 10 in a USB device descriptor is bMaxPacketSize0 in standard USB, but since this is a custom descriptor area, it likely encodes a hardware revision. The value 0x03 sets bit flag _1_3, creating a hardware-revision-aware code path.

Impact: The _1_3 flag is used elsewhere in v2.13 to conditionally execute different initialization sequences, supporting multiple hardware revisions of the SkyWalker-1 board.

3.3 FUN_CODE_2031 - USB Reconnect Before Main Loop

void FUN_CODE_2031(void) {
    if (_0_0 == 0) {
        CPUCS |= 0x08;          // Set CPUCS.3 (8051 reset bit? Or re-enumerate)
    } else {
        CPUCS |= 0x0A;          // Set CPUCS.3 + CPUCS.1
    }
    FUN_CODE_14b9(5, 0xDC);     // Delay ~1500 cycles
    EPIRQ = 0xFF;               // Clear all endpoint interrupts
    USBIRQ = 0xFF;              // Clear all USB interrupts
    DAT_SFR_91 &= 0xEF;        // Clear EXIF.4 (USB interrupt flag)
    CPUCS &= 0xF7;              // Clear CPUCS.3
}

This performs a controlled USB re-enumeration by pulsing CPUCS.3, then clearing all pending USB/endpoint interrupts. The conditional on _0_0 adds CPUCS.1 when the flag is set (possibly switching between 12MHz and 48MHz operation).

v2.06 equivalent: In v2.06, this exact same logic exists as INT0_vec (the INT0 interrupt handler at 0x0003). The critical difference is that in v2.06 this code runs as an interrupt handler, while in v2.13 it's called as a normal function (FUN_CODE_2031) before the main loop starts, AND the INT0 vector is repurposed for demodulator polling (see section 4).


4. INT0 Handler Difference

v2.06 INT0 (CODE:0003) - USB Re-enumeration

void INT0_vec(void) {
    if (_0_7 == 0) {
        CPUCS |= 0x08;       // CPUCS bit 3
    } else {
        CPUCS |= 0x0A;       // CPUCS bits 3+1
    }
    FUN_CODE_1dfb(5, 0xDC);   // Delay
    EPIRQ = 0xFF;             // Clear endpoint IRQs
    USBIRQ = 0xFF;            // Clear USB IRQs
    DAT_SFR_91 &= 0xEF;      // Clear external interrupt flag
    CPUCS &= 0xF7;            // Clear CPUCS bit 3
}

Simple USB reconnect/re-enumeration handler. Pulses CPUCS.3, clears all pending interrupts.

v2.13 INT0 (CODE:0003) - Demodulator Availability Polling

void INT0_vector(void) {
    for (DAT_INTMEM_37 = 0x28; DAT_INTMEM_37 != 0; DAT_INTMEM_37--) {
        // Read I2C device 0x7F (demod A), checking for response
        byte result = FUN_CODE_2239(0x7F);   // I2C read from 0x7F
        if (result != 0x01) {
            // Try I2C device 0x3F (demod B)
            result = FUN_CODE_2239(0x3F);    // I2C read from 0x3F
            if (result != 0x01) break;       // Neither responded normally
        }
    }
    _1_4 = (DAT_INTMEM_37 == 0);  // Set flag if loop exhausted (no demod found)
}

This is a complete replacement of INT0's purpose. Instead of USB re-enumeration, INT0 now polls two I2C devices (0x7F and 0x3F) up to 40 times (0x28). These are two possible addresses for the satellite demodulator IC.

FUN_CODE_2239 decompiled:

undefined1 FUN_CODE_2239(byte device_addr) {
    DAT_INTMEM_48 = 0xE1;      // buffer address high
    DAT_INTMEM_49 = 0;          // buffer address low
    FUN_CODE_0eea(1, device_addr, 0x51);  // I2C read 1 byte from device
    return DAT_EXTMEM_e100;     // return the read byte
}

The function performs an I2C single-byte read from the specified device address, using address 0x51 as a parameter (likely selecting a specific I2C bus or mode via the FX2's auxiliary I2C controller at 0xE678). It stores the result at 0xE100 (XRAM buffer).

Behavioral meaning: The flag _1_4 is set to 1 if neither demodulator responded after 40 attempts - indicating no demodulator hardware is present. This flag is later checked by:

  • FUN_CODE_21d1 (command 0x9D handler) - skips demodulator reset if _1_4 != 1
  • Various initialization paths to avoid hanging on missing hardware

Why this matters: v2.06 assumes the demodulator is always present. v2.13 can detect and gracefully handle boards where the demodulator is absent or unresponsive, making it more robust for manufacturing QC and field failures.


5. What Can v2.13 Do That v2.06 Cannot?

5.1 Demodulator Hardware Detection

v2.13 probes two I2C addresses (0x7F, 0x3F) at startup to determine which demodulator variant is installed, or if none is present. v2.06 blindly assumes the hardware configuration.

5.2 Host-Initiated Demodulator Re-initialization (Command 0x9A)

The host can trigger a demodulator re-initialization via USB vendor command 0x9A, with up to 3 retry attempts. v2.06 has no mechanism for the host to request re-initialization.

5.3 Demodulator Status Read (Command 0x99)

Direct I2C register read of demodulator register 0xF9, returned to host. This enables diagnostic/monitoring software to check demodulator status without going through the full signal quality pipeline.

5.4 Host-Controlled Tuning Delay (Command 0x9C)

Allows the host to invoke the tuning acquisition sequence with a configurable delay parameter. In v2.06, the tuning timing is entirely firmware-controlled with no host influence.

5.5 DiSEqC GPIO Pin Reassignment

All firmware versions use the same GPIO bit-bang algorithm for DiSEqC signaling. The only change is the data pin assignment per PCB revision:

Version Data Pin Carrier Pin Byte Transmit Bit Symbol Timer Tick
v2.06 P0.7 P0.3 0x2098 0x23B5 0x24C6
Rev.2 v2.10 P0.4 P0.3 FUN_CODE_07d1 FUN_CODE_213c FUN_CODE_225f
v2.13 FW1 P0.0 P0.3 FUN_CODE_2060 FUN_CODE_22f3 func_0x2431

The algorithm is identical across all versions: Manchester-encoded bit-bang with Timer2-based timing, odd parity per byte, and 25-tick tone bursts for mini-commands.

5.6 Firmware/Descriptor Integrity Verification

v2.13 validates demodulator identification (ASCII version string checksum) and descriptor block integrity (two 36-byte checksums) before proceeding. If verification fails after 20 attempts, it falls back to a recovery sequence. v2.06 does no integrity checking.

5.7 Hardware Revision Awareness

The version byte check (descriptor offset 10, value 0x03) creates conditional code paths allowing a single firmware image to support multiple SkyWalker-1 hardware revisions. v2.06 has a single code path for one hardware revision.

5.8 Simplified BCM4500 Status Polling

v2.06's FUN_CODE_2000 polls three separate BCM4500 registers (0xA2, 0xA8, 0xA4 via I2C) to determine demodulator readiness. v2.13's FUN_CODE_208d polls only one register (0xA4), suggesting either the demodulator firmware was updated to consolidate status, or the additional checks were found to be redundant.

5.9 Conditional Demodulator Reset (Command 0x9D)

v2.13's 0x9D handler can trigger a full demodulator reset sequence (FUN_CODE_1e3c -> register writes to 0x18 bus) controlled by the host via wValue. This is useful for error recovery without full device re-enumeration.


6. Architecture Summary

Aspect v2.06 v2.13
Total functions 61 82 (+21)
RESET vector 0x188D 0x170D
Stack pointer 0x72 0x50
Init data table CODE:0B46 CODE:0B88
Descriptor base 0x1200 0x0E00
Config byte (IRAM) 0x6D 0x4F
INT0 purpose USB re-enumerate Demod probe
DiSEqC data pin P0.7 (GPIO bit-bang) P0.0 (GPIO bit-bang)
Demod init Direct, no retry 20-attempt retry
Integrity checks None Checksum verification
HW revision support Single Multi-revision (flag _1_3)
New vendor cmds -- 0x99, 0x9A, 0x9C

7. DiSEqC Timing Chain Analysis

7.1 Timer2 Configuration (Identical Across All Versions)

All firmware versions configure Timer2 identically during USB descriptor setup:

T2CON  = 0x04   ; Auto-reload mode, internal clock, TR2=1 (running)
RCAP2H = 0xF8   ; Reload high byte
RCAP2L = 0x2F   ; Reload low byte  -> RCAP2 = 0xF82F = 63535
CKCON  = 0x00   ; Default (T2M=0 -> Timer2 clock = CLKOUT/12)

Timer2 Clock Derivation:

FX2 master clock = 48 MHz
CKCON.T2M = 0 -> Timer2 clock = 48 MHz / 12 = 4 MHz
Count per overflow = 65536 - 63535 = 2001
Tick period = 2001 / 4,000,000 = 500.25 us ~ 500 us
Tick frequency ~ 2.0 kHz

Timer2 runs continuously from power-on and is never stopped or reconfigured. It serves as a stable 500 us timebase for all DiSEqC operations.

7.2 DiSEqC Signal Architecture

FX2 Firmware              External Hardware             Coax Cable
+------------------+     +--------------------+     +------------------+
|                  |     |                    |     |                  |
| P0.3 (carrier)  |---->| 22 kHz oscillator  |---->| LNB power line   |
| (enable/disable) |     | (gated by P0.3)    |     | (13V/18V + tone) |
|                  |     |                    |     |                  |
| P0.x (data bit)  |     | (internal to FX2   |     |                  |
| (firmware only)  |     |  firmware logic)    |     |                  |
+------------------+     +--------------------+     +------------------+

The firmware does NOT generate the 22 kHz carrier directly. P0.3 gates an external 22 kHz oscillator circuit on the PCB. The data pin (P0.7/P0.4/P0.0 depending on version) is used only internally by the firmware to control the Manchester encoding logic -- it tells the bit-symbol function whether to cut the carrier short or leave it on for the full period.

7.3 Manchester Encoding (DiSEqC Bit Symbol)

Each DiSEqC bit consists of 3 Timer2 ticks (3 x 500 us = 1.5 ms):

Data '0' (2/3 tone, 1/3 silence):

          Tick 1       Tick 2       Tick 3
        (500 us)     (500 us)     (500 us)
P0.3:  _____|========|========|________|
             ^tone ON          ^tone OFF
       (setup gap)   (1.0 ms carrier)  (0.5 ms silence)

Data '1' (1/3 tone, 2/3 silence):

          Tick 1       Tick 2       Tick 3
        (500 us)     (500 us)     (500 us)
P0.3:  _____|========|________|________|
             ^tone ON ^tone OFF early
       (setup gap)   (0.5 ms carrier)  (1.0 ms silence)

Implementation (decompiled from Rev.2 FUN_CODE_213c):

void diseqc_bit_symbol(void) {
    wait_TF2();                // Tick 1: inter-bit gap (500 us)
    P0 |= 0x08;               // P0.3 = 1 -> 22 kHz carrier ON
    wait_TF2();                // Tick 2: carrier period (500 us)
    if (data_pin != 0) {       // If data = '1':
        P0 &= 0xF7;           //   P0.3 = 0 -> carrier OFF (short pulse)
    }
    wait_TF2();                // Tick 3: final period (500 us)
    P0 &= 0xF7;               // P0.3 = 0 -> carrier always OFF at end
}

7.4 Byte Transmission (8 Data Bits + Odd Parity)

Each DiSEqC byte is 9 bits: 8 data (MSB first) + 1 parity (odd).

Implementation (decompiled from Rev.2 FUN_CODE_07d1):

void diseqc_send_byte(char first_byte, byte data) {
    byte ones_count = 0;
    if (first_byte == 0) TF2 = 0;     // Sync timer on first byte

    for (char i = 8; i > 0; i--) {     // 8 bits, MSB first
        if (data & 0x80) {              // Test MSB
            data_pin = 1;               // Set data = '1'
            diseqc_bit_symbol();        // Transmit '1' symbol
            ones_count++;
        } else {
            data_pin = 0;               // Set data = '0'
            diseqc_bit_symbol();        // Transmit '0' symbol
        }
        data <<= 1;                     // Next bit
    }

    data_pin = ~ones_count & 1;         // Odd parity: '1' if even count
    diseqc_bit_symbol();                // Transmit parity bit
}

Timing per byte: 9 bits x 1.5 ms = 13.5 ms

7.5 Tone Burst (Mini DiSEqC Command)

For legacy 2-way satellite switches, a simple tone burst is used instead of a full DiSEqC message. The burst is 25 Timer2 ticks of continuous carrier:

void tone_burst_A(void) {
    TF2 = 0;                          // Sync timer
    wait_TF2();                        // One tick gap
    P0 |= 0x08;                       // P0.3 = 1 -> carrier ON
    for (char i = 25; i > 0; i--) {
        wait_TF2();                    // 25 x 500 us = 12.5 ms
    }
    P0 &= 0xF7;                       // P0.3 = 0 -> carrier OFF
}

Burst duration: 25 x 500 us = 12.5 ms (matches DiSEqC spec)

7.6 Timer Tick Wait (TF2 Polling)

The lowest-level timing primitive is a busy-wait on Timer2 overflow:

void wait_TF2(void) {
    while (TF2 == 0) {}    // Poll Timer2 overflow flag
    TF2 = 0;               // Clear flag for next tick
}

This is identical across all versions (v2.06: 0x24C6, Rev.2: FUN_CODE_225f, v2.13: func_0x2431). Timer2 overflows every 500.25 us, providing the fundamental DiSEqC timebase.

7.7 CPU Clock Compensation (Delay Function)

The delay function used before DiSEqC transmission adjusts for CPU clock speed:

void delay(byte high, byte low) {
    byte clkspd = CPUCS & 0x18;       // CPUCS[4:3] = clock speed bits
    if (clkspd == 0x00) {              // 12 MHz: halve the count
        // Adjust high:low /= 2
    } else if (clkspd == 0x10) {       // 48 MHz: double the count
        // Adjust high:low *= 2
    }
    // 24 MHz (0x08): use count as-is
    while (high:low > 0) {
        wait_TF2();
        high:low--;
    }
}

The pre-DiSEqC delay call is delay(0, 0x0F) = 15 ticks x 500 us = 7.5 ms. This allows the LNB voltage to stabilize before DiSEqC signaling begins.

7.8 Complete DiSEqC Timing Summary

Parameter Value Source
Timer2 clock 4 MHz (48 MHz / 12) CKCON default, T2M=0
Timer2 reload 0xF82F RCAP2H:RCAP2L
Tick period 500.25 us (65536 - 63535) / 4 MHz
Bit period 1.5 ms (3 ticks) DiSEqC Manchester encoding
Byte period 13.5 ms (9 bits) 8 data + 1 parity
Tone burst 12.5 ms (25 ticks) Mini-command A/B
Pre-TX delay 7.5 ms (15 ticks) Voltage settling
Data '0' 1.0 ms tone + 0.5 ms silence 2/3 duty cycle
Data '1' 0.5 ms tone + 1.0 ms silence 1/3 duty cycle
Carrier frequency 22 kHz (external oscillator) Gated by P0.3
Carrier enable P0.3 All versions
Data pin (v2.06) P0.7 PCB revision A
Data pin (Rev.2) P0.4 PCB revision B
Data pin (v2.13) P0.0 PCB revision C