Skip to content

Command Dispatch

The RS-UV3 uses a two-character command protocol. Every command is exactly two ASCII characters, optionally followed by parameters. The firmware implements this with a simple but elegant XOR-based dispatch pattern.

┌──────┬──────┬─────────────────┬────┐
│ Cmd1 │ Cmd2 │ Parameters │ CR │
└──────┴──────┴─────────────────┴────┘
'F' 'S' '146520000' 0x0D
  • Two-character command code — Not case-sensitive (fs = FS)
  • Optional parameters — Numeric or string, depending on command
  • Carriage return terminator — ASCII 13 (0x0D)
  • Baud rate — 19200 8N1 (default)

PIC18 assembly doesn’t have a switch statement. Instead, the firmware uses XOR to test character values:

; Read first character from serial buffer
movff 0x59a, FSR0L ; Load buffer base address
movff 0x59b, FSR0H
movf POSTINC0, W ; Read first byte into W
; Check if it's 'P' (0x50)
xorlw 0x50 ; XOR with 'P'
btfss STATUS, Z ; Skip if zero (matched)
bra not_P ; Branch if not 'P'
; It's 'P' — now check second character
movlw 0x01
addwf 0x9a, W ; Advance buffer pointer by 1
movwf FSR0L
; ... load second byte ...
xorlw 0x44 ; XOR with 'D'
bz handle_PD ; Branch if zero → PD command

The key insight: XOR A, A = 0. If the character matches the expected value, the result is zero and the Z (zero) flag is set.

On PIC18:

  • CPFSEQ (compare, skip if equal) exists but can’t compare with literals
  • SUBLW (subtract literal) works but affects the carry flag
  • XORLW (XOR with literal) is single-cycle, only affects Z flag, and the value is preserved

The XOR pattern is the most efficient way to dispatch on ASCII values.

The firmware splits command handling across two functions:

Handles memory, power, and low-level system commands:

CommandDescription
PDPower down transceiver chip
PWTX power level (high/low)
RCRecall memory channel
RRRead RDA1846S register
RSSet RDA1846S register
STStore to memory channel
CPCopy/report channel parameters
FDFactory defaults
BLEnter bootloader

Handles the bulk of radio configuration:

First CharCommands
AAF, AI, AO
BB1, B2, BC, BM, BS, BT, BW
CCB, CC, CF, CL, CO, CS, CT, CW
DDD, DP, DR, DS
EEX
FF (compound), FM, FW
GGM, GT
HHP, HT
IID, IT
LLD
MMC
SSD, SN, SO, SQ, SR, SS
TTF, TG, TM, TO, TP, TS, TX
VVL, VT, VU, VX
XX1

The F (frequency) command is special — it uses a third character as a sub-command:

SyntaxMeaning
FR146520000Set RX frequency
FT146520000Set TX frequency
FS146520000Set simplex (both)
FU005000Shift frequency up
FD005000Shift frequency down
F?Query current frequencies

This is handled as a single dispatch entry that checks the third character internally:

; First char already matched 'F'
; Check second char for R, T, S, U, D, or ?
xorlw 0x52 ; 'R'
bz freq_rx
xorlw 0x52 ^ 0x54 ; 'T' (XOR trick: 0x52^0x52^0x54 = 0x54)
bz freq_tx
; ... and so on

Commands are received into a small buffer:

AddressPurpose
0x059aBuffer pointer (low byte)
0x059bBuffer pointer (high byte)
0x059dPort selector (1=USB, 2=DE-9)

The buffer is only 2 bytes for the command code itself — parameters are parsed directly from the UART as they arrive.

When a command generates a response, the firmware routes it back to whichever port received the command:

movff 0x59d, 0x5df ; Copy port selector to output port
movlw 0x74 ; String table index for "PD: "
movwf 0xe0
call serial_send_string

This allows both serial ports (USB via JP1 and DE-9) to operate independently.

Most commands support a ? suffix to query the current value:

; After matching command code, check for '?'
movf POSTINC0, W ; Read third character
xorlw 0x3f ; '?'
bnz set_value ; Not '?' → setting a value
; Fall through to query handler
movff 0x59d, 0x5df ; Set output port
movlw 0x8f ; String table: "SQ: "
call serial_send_string
; ... send current value ...

Command processing is fast:

  • Dispatch lookup: ~20 cycles (2.5 µs at 8 MHz)
  • Simple query response: ~100 cycles (12.5 µs)
  • Frequency change (with I2C): ~5,000 cycles (625 µs)

Even the slowest commands complete well under the 52 µs between characters at 19200 baud.