Skip to content

DiSEqC Protocol

The SkyWalker-1 implements DiSEqC (Digital Satellite Equipment Control) via Timer2-based GPIO bit-bang on the FX2 microcontroller. The algorithm is identical across all firmware versions — only the data pin assignment changes per PCB revision.

Download DiSEqC PDF

The following diagrams document the Genpix BDA driver’s DiSEqC extended property interface:

DiSEqC Protocol Diagram Page 1

DiSEqC Protocol Diagram Page 2

The firmware does not generate the 22 kHz carrier directly. GPIO P0.3 gates an external 22 kHz oscillator circuit on the PCB:

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 firmware | | |
| (firmware only) | | logic only) | | |
+------------------+ +--------------------+ +------------------+

The data pin (P0.7 / P0.4 / P0.0 depending on version) is used only internally by the firmware’s Manchester encoding logic. It controls whether the carrier gate signal is cut short or held for the full bit period.

All firmware versions configure Timer2 identically during USB descriptor setup:

ParameterValueNotes
T2CON0x04Auto-reload mode, timer running
RCAP2H0xF8Reload high byte
RCAP2L0x2FReload low byte (reload = 63535)
CKCON.T2M0Timer2 clock = 48 MHz / 12 = 4 MHz

Tick period calculation:

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.

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

Data ‘0’ — 2/3 tone, 1/3 silence (1.0 ms carrier + 0.5 ms 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)

Decompiled from Rev.2 FUN_CODE_213c:

DiSEqC bit symbol function
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
}

The wait_TF2() function is the lowest-level timing primitive — a busy-wait on the Timer2 overflow flag:

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

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

DiSEqC byte transmit (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) {
data_pin = 1; // Set data = '1'
diseqc_bit_symbol();
ones_count++;
} else {
data_pin = 0; // Set data = '0'
diseqc_bit_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

The full command flow for sending a DiSEqC message via vendor command 0x8D (SEND_DISEQC_COMMAND):

  1. Read wLength from USB SETUP packet (0xE6BE) to determine message byte count

  2. Disable 22 kHz carrier by clearing P0.3 (ensuring a clean start state)

  3. Pre-transmission delay of 15 Timer2 ticks (7.5 ms) for LNB voltage settling

  4. Transmit message bytes if wLength > 0: iterate through EP0BUF, sending each byte via Manchester-encoded bit-bang (8 data bits + odd parity, 3 Timer2 ticks per bit)

  5. Or send tone burst if wLength == 0:

    • wValue == 0: Tone Burst A (25 Timer2 ticks = 12.5 ms of unmodulated carrier)
    • wValue != 0: Tone Burst B (transmitted as 0xFF byte pattern through the bit-bang function)
USB SETUP:
bmRequestType = 0x40 (vendor, host-to-device)
bRequest = 0x8D (SEND_DISEQC_COMMAND)
wValue = msg[0] (framing byte, typically 0xE0 or 0xE1)
wIndex = 0x0000
wLength = message length (3-6)
EP0 Data:
Byte 0: Framing byte (e.g., 0xE0 = command from master, no reply expected)
Byte 1: Address byte (e.g., 0x10 = any LNB, 0x11 = LNB 1)
Byte 2: Command byte (e.g., 0x38 = Write N0, committed switch port)
Byte 3: Data byte 0 (optional, port selection bits)
Byte 4: Data byte 1 (optional)
Byte 5: Data byte 2 (optional)
USB SETUP:
bmRequestType = 0x40
bRequest = 0x8D
wValue = 0x00 (Burst A) or 0x01 (Burst B)
wIndex = 0x0000
wLength = 0 (zero length signals tone burst mode)

The Windows driver exposes DiSEqC through a BDA extended property GUID:

BDA Extended Property GUID
// {0B5221EB-F4C4-4976-B959-EF74427464D9}
#define STATIC_KSPROPSETID_BdaExtendedProperty \
0x0B5221EB, 0xF4C4, 0x4976, 0xB9, 0x59, 0xEF, 0x74, 0x42, 0x74, 0x64, 0xD9

The DiSEqC command structure:

DISEQC_COMMAND structure
typedef enum enSimpleToneBurst {
SEC_MINI_A,
SEC_MINI_B
} SIMPLE_TONE_BURST;
typedef struct __DISEQC_COMMAND {
UCHAR ucMessage[6]; // Framing, Address, Command, Data[0..2]
UCHAR ucMessageLength; // 3-6 for DiSEqC; 1 for tone burst
} DISEQC_COMMAND;

For tone burst commands, set ucMessageLength = 1 and ucMessage[0] to either SEC_MINI_A (0x00) or SEC_MINI_B (0x01).

The DiSEqC algorithm is identical across all firmware versions. Only the data pin changes per PCB revision:

Firmware VersionData PinCarrier PinByte Transmit FunctionBit Symbol FunctionTimer Wait
v2.06P0.7P0.30x20980x23B50x24C6
Rev.2 v2.10P0.4P0.3FUN_CODE_07d1FUN_CODE_213cFUN_CODE_225f
v2.13 FW1P0.0P0.3FUN_CODE_2060FUN_CODE_22f3func_0x2431
Custom v3.01.0P0.7P0.3diseqc_tone_burst()(inline)(inline)

The delay function used before DiSEqC transmission adjusts its loop count based on the FX2 CPU clock speed:

Clock-aware delay function
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.

ParameterValueSource
Timer2 clock4 MHz (48 MHz / 12)CKCON default, T2M=0
Timer2 reload0xF82FRCAP2H:RCAP2L
Tick period500.25 us(65536 - 63535) / 4 MHz
Bit period1.5 ms (3 ticks)DiSEqC Manchester encoding
Byte period13.5 ms (9 bits)8 data + 1 parity
Tone burst duration12.5 ms (25 ticks)Mini-command A/B
Pre-TX settling delay7.5 ms (15 ticks)Voltage stabilization
Data ‘0’1.0 ms tone + 0.5 ms silence2/3 duty cycle
Data ‘1’0.5 ms tone + 1.0 ms silence1/3 duty cycle
Carrier frequency22 kHz (external oscillator)Gated by P0.3
3-byte DiSEqC message~48 ms total7.5 ms settle + 3 x 13.5 ms
6-byte DiSEqC message~88.5 ms total7.5 ms settle + 6 x 13.5 ms