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.
Protocol Basics
Section titled “Protocol Basics”┌──────┬──────┬─────────────────┬────┐│ 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)
The XOR Dispatch Pattern
Section titled “The XOR Dispatch Pattern”PIC18 assembly doesn’t have a switch statement. Instead, the firmware uses XOR to test character values:
; Read first character from serial buffermovff 0x59a, FSR0L ; Load buffer base addressmovff 0x59b, FSR0Hmovf 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 charactermovlw 0x01addwf 0x9a, W ; Advance buffer pointer by 1movwf FSR0L; ... load second byte ...xorlw 0x44 ; XOR with 'D'bz handle_PD ; Branch if zero → PD commandThe key insight: XOR A, A = 0. If the character matches the expected value, the result is zero and the Z (zero) flag is set.
Why XOR Instead of Compare?
Section titled “Why XOR Instead of Compare?”On PIC18:
CPFSEQ(compare, skip if equal) exists but can’t compare with literalsSUBLW(subtract literal) works but affects the carry flagXORLW(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.
Two Dispatchers
Section titled “Two Dispatchers”The firmware splits command handling across two functions:
Dispatcher A — 0x0042b0 (5,826 bytes)
Section titled “Dispatcher A — 0x0042b0 (5,826 bytes)”Handles memory, power, and low-level system commands:
| Command | Description |
|---|---|
| PD | Power down transceiver chip |
| PW | TX power level (high/low) |
| RC | Recall memory channel |
| RR | Read RDA1846S register |
| RS | Set RDA1846S register |
| ST | Store to memory channel |
| CP | Copy/report channel parameters |
| FD | Factory defaults |
| BL | Enter bootloader |
Dispatcher B — 0x005972 (10,632 bytes)
Section titled “Dispatcher B — 0x005972 (10,632 bytes)”Handles the bulk of radio configuration:
| First Char | Commands |
|---|---|
| A | AF, AI, AO |
| B | B1, B2, BC, BM, BS, BT, BW |
| C | CB, CC, CF, CL, CO, CS, CT, CW |
| D | DD, DP, DR, DS |
| E | EX |
| F | F (compound), FM, FW |
| G | GM, GT |
| H | HP, HT |
| I | ID, IT |
| L | LD |
| M | MC |
| S | SD, SN, SO, SQ, SR, SS |
| T | TF, TG, TM, TO, TP, TS, TX |
| V | VL, VT, VU, VX |
| X | X1 |
Compound Commands
Section titled “Compound Commands”The F (frequency) command is special — it uses a third character as a sub-command:
| Syntax | Meaning |
|---|---|
FR146520000 | Set RX frequency |
FT146520000 | Set TX frequency |
FS146520000 | Set simplex (both) |
FU005000 | Shift frequency up |
FD005000 | Shift 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_rxxorlw 0x52 ^ 0x54 ; 'T' (XOR trick: 0x52^0x52^0x54 = 0x54)bz freq_tx; ... and so onSerial Buffer
Section titled “Serial Buffer”Commands are received into a small buffer:
| Address | Purpose |
|---|---|
0x059a | Buffer pointer (low byte) |
0x059b | Buffer pointer (high byte) |
0x059d | Port 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.
Response Routing
Section titled “Response Routing”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 portmovlw 0x74 ; String table index for "PD: "movwf 0xe0call serial_send_stringThis allows both serial ports (USB via JP1 and DE-9) to operate independently.
Query Pattern
Section titled “Query Pattern”Most commands support a ? suffix to query the current value:
; After matching command code, check for '?'movf POSTINC0, W ; Read third characterxorlw 0x3f ; '?'bnz set_value ; Not '?' → setting a value; Fall through to query handlermovff 0x59d, 0x5df ; Set output portmovlw 0x8f ; String table: "SQ: "call serial_send_string; ... send current value ...Timing
Section titled “Timing”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.