Correct DiSEqC analysis and add complete timing chain documentation

Major correction: All firmware versions use GPIO bit-banging for DiSEqC,
NOT I2C-based control as previously reported. Deep decompilation of the
sub-functions (byte transmit, bit symbol, tone burst) across v2.06, Rev.2,
and v2.13 reveals identical Manchester encoding algorithms with only the
data GPIO pin changed per PCB revision:
  - v2.06: P0.7, Rev.2: P0.4, v2.13: P0.0
  - P0.3 (22kHz carrier gate) unchanged across all versions

New section 7: Complete DiSEqC timing chain analysis including:
  - Timer2 configuration (RCAP2=0xF82F, 4MHz clock, 500us tick)
  - Manchester encoding waveforms (3 ticks/bit, 1.5ms/bit, 667 baud)
  - Byte transmission (8 data + odd parity = 13.5ms)
  - Tone burst timing (25 ticks = 12.5ms)
  - CPU clock compensation in delay function
  - External 22kHz oscillator architecture
This commit is contained in:
Ryan Malloy 2026-02-11 11:11:54 -07:00
parent da08d1b099
commit 782f5a0e8d

View File

@ -8,7 +8,7 @@ v2.13 is a significant evolution of the v2.06 firmware with 21 additional functi
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 implementation** using I2C-based bus control
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)
---
@ -99,17 +99,19 @@ None were removed -- all commands that were STALL in v2.06 remain STALL in v2.13
- 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 (significantly changed)**
- v2.06: `LCALL 0x23e0; LCALL 0x1e41` -- calls a GPIO-based DiSEqC implementation (0x1e41) that directly manipulates P0 port pins and uses timer-based bit-banging with FUN_CODE_2098 for tone modulation
- v2.13: `LCALL 0x231e; LCALL FUN_CODE_0dbc` -- calls an I2C-based DiSEqC implementation that:
1. Reads wLength (0xE6BE) as message byte count
2. Pulls P0.3 low (power enable for DiSEqC bus)
3. Delays 15 units via FUN_CODE_14b9
4. If message bytes present: iterates through EP0BUF data, sending each byte via `func_0x2060` (I2C-based DiSEqC bus write)
5. If wValue == 0 and no bytes: calls `func_0x22b0` (tone burst command)
6. If wValue != 0 and no bytes: calls `func_0x2060(0, 0xFF)` (continuous tone)
**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
This is a major architectural change: v2.06 uses GPIO bit-banging for DiSEqC, while v2.13 delegates DiSEqC to a dedicated I2C-connected controller chip.
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
@ -153,7 +155,7 @@ This is a major architectural change: v2.06 uses GPIO bit-banging for DiSEqC, wh
| -- | `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 I2C-based DiSEqC controller |
| -- | `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)
@ -339,12 +341,16 @@ Direct I2C register read of demodulator register 0xF9, returned to host. This en
### 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 I2C-Based DiSEqC Control
v2.13 uses a dedicated I2C-connected DiSEqC controller chip rather than GPIO bit-banging:
- More precise DiSEqC timing (hardware-generated rather than software-timed)
- Support for reading DiSEqC reply messages (the I2C controller can buffer responses)
- Lower CPU overhead during DiSEqC transactions
- Better compatibility with DiSEqC 1.2 motor positioning commands that require precise timing
### 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.
@ -371,8 +377,195 @@ v2.13's 0x9D handler can trigger a full demodulator reset sequence (FUN_CODE_1e3
| Descriptor base | 0x1200 | 0x0E00 |
| Config byte (IRAM) | 0x6D | 0x4F |
| INT0 purpose | USB re-enumerate | Demod probe |
| DiSEqC method | GPIO bit-bang | I2C controller |
| 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):**
```c
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):**
```c
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:
```c
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:
```c
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:
```c
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 |