Add firmware internals section documenting RE work
New reference pages covering the V2.4a reverse engineering: - Overview with architecture diagram and memory layout - Command dispatch: XOR pattern, two dispatchers, compound commands - Memory map: RAM variables, EEPROM layout, SFR definitions - I2C & synthesizer: RDA1846S communication, frequency programming - Source files: links to annotated assembly, C port, Python tools These document the 19,053-line annotated assembly and 12,020-line readable C port produced by Phases 1-3 of the RE project.
This commit is contained in:
parent
5d468cd86a
commit
1b105f5632
@ -65,6 +65,17 @@ export default defineConfig({
|
||||
{ label: 'Connectors', slug: 'reference/connectors' },
|
||||
{ label: 'Schematics', slug: 'reference/schematics' },
|
||||
{ label: 'Hardware Revisions', slug: 'reference/hardware-revisions' },
|
||||
{
|
||||
label: 'Firmware Internals',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ label: 'Overview', slug: 'reference/firmware' },
|
||||
{ label: 'Command Dispatch', slug: 'reference/firmware/command-dispatch' },
|
||||
{ label: 'Memory Map', slug: 'reference/firmware/memory-map' },
|
||||
{ label: 'I2C & Synthesizer', slug: 'reference/firmware/i2c-synthesizer' },
|
||||
{ label: 'Source Files', slug: 'reference/firmware/source-files' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
172
src/content/docs/reference/firmware/command-dispatch.md
Normal file
172
src/content/docs/reference/firmware/command-dispatch.md
Normal file
@ -0,0 +1,172 @@
|
||||
---
|
||||
title: Command Dispatch
|
||||
description: How the RS-UV3 firmware parses and routes serial commands
|
||||
---
|
||||
|
||||
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
|
||||
|
||||
```
|
||||
┌──────┬──────┬─────────────────┬────┐
|
||||
│ 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
|
||||
|
||||
PIC18 assembly doesn't have a `switch` statement. Instead, the firmware uses XOR to test character values:
|
||||
|
||||
```asm
|
||||
; 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.
|
||||
|
||||
### Why XOR Instead of Compare?
|
||||
|
||||
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.
|
||||
|
||||
## Two Dispatchers
|
||||
|
||||
The firmware splits command handling across two functions:
|
||||
|
||||
### 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)
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
```asm
|
||||
; 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
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
When a command generates a response, the firmware routes it back to whichever port received the command:
|
||||
|
||||
```asm
|
||||
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.
|
||||
|
||||
## Query Pattern
|
||||
|
||||
Most commands support a `?` suffix to query the current value:
|
||||
|
||||
```asm
|
||||
; 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 ...
|
||||
```
|
||||
|
||||
## 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.
|
||||
226
src/content/docs/reference/firmware/i2c-synthesizer.md
Normal file
226
src/content/docs/reference/firmware/i2c-synthesizer.md
Normal file
@ -0,0 +1,226 @@
|
||||
---
|
||||
title: I2C & Synthesizer
|
||||
description: Communication with the RDA1846S transceiver chip
|
||||
---
|
||||
|
||||
The RS-UV3's RF section is built around the **RDA1846S** — a single-chip transceiver that handles frequency synthesis, modulation, demodulation, and audio processing. The PIC18 communicates with it over I2C.
|
||||
|
||||
## RDA1846S Overview
|
||||
|
||||
The RDA1846S is a highly integrated FM transceiver IC:
|
||||
|
||||
| Feature | Specification |
|
||||
|---------|---------------|
|
||||
| **Frequency range** | 134–174 MHz, 200–260 MHz, 400–520 MHz |
|
||||
| **Channel spacing** | 12.5 kHz or 25 kHz |
|
||||
| **TX power** | Adjustable, up to +8 dBm at chip |
|
||||
| **RX sensitivity** | -122 dBm typical |
|
||||
| **Interface** | I2C (7-bit address 0x2E) |
|
||||
| **Supply** | 3.3V |
|
||||
|
||||
The chip contains:
|
||||
- Fractional-N PLL frequency synthesizer
|
||||
- PA driver (external PA for full power)
|
||||
- LNA, mixer, IF filter
|
||||
- Audio codec with pre/de-emphasis
|
||||
- CTCSS/DCS encoder and decoder
|
||||
- RSSI measurement
|
||||
|
||||
## I2C Protocol
|
||||
|
||||
The RDA1846S uses standard I2C with 7-bit addressing:
|
||||
|
||||
```
|
||||
┌─────┬───────────────┬─────┬──────────────┬─────┬──────────────┬─────┐
|
||||
│START│ 0x2E + W (0) │ ACK │ Register Addr│ ACK │ Data (16-bit)│STOP │
|
||||
└─────┴───────────────┴─────┴──────────────┴─────┴──────────────┴─────┘
|
||||
```
|
||||
|
||||
All registers are 16 bits. The firmware bit-bangs I2C rather than using the PIC's MSSP hardware in I2C mode:
|
||||
|
||||
```asm
|
||||
; i2c_write_byte — Send one byte over I2C
|
||||
i2c_write_byte:
|
||||
movlw 0x08 ; 8 bits
|
||||
movwf bit_count
|
||||
write_loop:
|
||||
bcf LATC, SDA ; Data low (default)
|
||||
btfsc i2c_data, 7 ; Check MSB
|
||||
bsf LATC, SDA ; Set data high if bit=1
|
||||
bsf LATC, SCL ; Clock high
|
||||
rlcf i2c_data, F ; Shift left
|
||||
bcf LATC, SCL ; Clock low
|
||||
decfsz bit_count
|
||||
bra write_loop
|
||||
; Read ACK
|
||||
bsf TRISC, SDA ; SDA as input
|
||||
bsf LATC, SCL ; Clock high
|
||||
btfsc PORTC, SDA ; Check ACK (low = ACK)
|
||||
bsf i2c_status, 0 ; Set error if NACK
|
||||
bcf LATC, SCL ; Clock low
|
||||
bcf TRISC, SDA ; SDA as output
|
||||
return
|
||||
```
|
||||
|
||||
## Frequency Programming
|
||||
|
||||
To tune to a new frequency, the firmware must:
|
||||
1. Calculate PLL divider values from the target frequency
|
||||
2. Write to the RDA1846S frequency registers
|
||||
3. Wait for PLL lock
|
||||
|
||||
### Frequency Calculation
|
||||
|
||||
The RDA1846S uses a fractional-N synthesizer. For a target frequency `F`:
|
||||
|
||||
```
|
||||
F = F_ref × (N + K/65536) / 2
|
||||
```
|
||||
|
||||
Where:
|
||||
- `F_ref` = 12.8 MHz (crystal reference)
|
||||
- `N` = integer divider (11 bits)
|
||||
- `K` = fractional divider (16 bits)
|
||||
|
||||
The firmware performs this calculation using 32-bit math:
|
||||
|
||||
```c
|
||||
// Pseudocode for frequency calculation
|
||||
uint32_t freq_khz = 146520; // 146.52 MHz in kHz
|
||||
uint32_t divider = (freq_khz * 1000 * 2) / 12800;
|
||||
uint16_t N = divider >> 16;
|
||||
uint16_t K = divider & 0xFFFF;
|
||||
```
|
||||
|
||||
### Register Writes
|
||||
|
||||
Setting a new RX frequency requires writing several registers:
|
||||
|
||||
| Register | Address | Purpose |
|
||||
|----------|---------|---------|
|
||||
| 0x29 | RX frequency high | N divider |
|
||||
| 0x2A | RX frequency low | K divider |
|
||||
| 0x30 | Control | PLL enable, band select |
|
||||
|
||||
```asm
|
||||
; set_rx_frequency — Program new RX frequency
|
||||
set_rx_frequency:
|
||||
; Write N divider to register 0x29
|
||||
movlw 0x29
|
||||
call i2c_start_write
|
||||
movff freq_n_hi, i2c_data
|
||||
call i2c_write_byte
|
||||
movff freq_n_lo, i2c_data
|
||||
call i2c_write_byte
|
||||
call i2c_stop
|
||||
|
||||
; Write K divider to register 0x2A
|
||||
movlw 0x2A
|
||||
call i2c_start_write
|
||||
movff freq_k_hi, i2c_data
|
||||
call i2c_write_byte
|
||||
movff freq_k_lo, i2c_data
|
||||
call i2c_write_byte
|
||||
call i2c_stop
|
||||
|
||||
; Trigger PLL recalibration
|
||||
; ...
|
||||
return
|
||||
```
|
||||
|
||||
## Key RDA1846S Registers
|
||||
|
||||
### Frequency Control
|
||||
|
||||
| Reg | Name | Bits | Description |
|
||||
|-----|------|------|-------------|
|
||||
| 0x29 | RX_FREQ_H | 15:0 | RX frequency high word |
|
||||
| 0x2A | RX_FREQ_L | 15:0 | RX frequency low word |
|
||||
| 0x39 | TX_FREQ_H | 15:0 | TX frequency high word |
|
||||
| 0x3A | TX_FREQ_L | 15:0 | TX frequency low word |
|
||||
|
||||
### Audio
|
||||
|
||||
| Reg | Name | Description |
|
||||
|-----|------|-------------|
|
||||
| 0x44 | VOL | RX audio volume (0–15) |
|
||||
| 0x45 | AUDIO_DAC | DAC configuration |
|
||||
| 0x59 | VOICE | Voice scrambler settings |
|
||||
|
||||
### CTCSS/DCS
|
||||
|
||||
| Reg | Name | Description |
|
||||
|-----|------|-------------|
|
||||
| 0x4A | CTCSS_FREQ | CTCSS tone frequency |
|
||||
| 0x4B | CDCSS_CODE | DCS code |
|
||||
| 0x63 | TONE_SEL | Tone mode select |
|
||||
|
||||
### Status
|
||||
|
||||
| Reg | Name | Description |
|
||||
|-----|------|-------------|
|
||||
| 0x5F | RSSI | Received signal strength |
|
||||
| 0x5C | SQ_OPEN | Squelch status |
|
||||
|
||||
## RR and RS Commands
|
||||
|
||||
The `RR` and `RS` commands provide direct access to RDA1846S registers:
|
||||
|
||||
```
|
||||
RR29 → Read register 0x29
|
||||
RS2900FF → Write 0x00FF to register 0x29
|
||||
```
|
||||
|
||||
:::caution[Direct Register Access]
|
||||
Writing arbitrary values to RDA1846S registers can cause RF interference, loss of calibration, or other unintended behavior. Use `RR`/`RS` only if you understand the chip's register map.
|
||||
:::
|
||||
|
||||
## Initialization Sequence
|
||||
|
||||
At power-on, `init_radio` (`0x000400`) performs a lengthy initialization:
|
||||
|
||||
1. **Soft reset** — Write 0x0001 to register 0x00
|
||||
2. **Clock config** — Set reference divider, enable PLL
|
||||
3. **Band select** — Configure for 2m/1.25m/70cm
|
||||
4. **Filter setup** — Set IF bandwidth (12.5 or 25 kHz)
|
||||
5. **Audio setup** — Configure volume, pre/de-emphasis
|
||||
6. **CTCSS init** — Load default tone (if enabled)
|
||||
7. **Calibration** — Trigger automatic calibration routines
|
||||
8. **Squelch** — Set initial squelch level
|
||||
|
||||
The full initialization is about 1,782 bytes of code — roughly 5% of the entire firmware.
|
||||
|
||||
## Band Selection
|
||||
|
||||
The RDA1846S supports three bands:
|
||||
|
||||
| Band | Frequency | Register 0x30 bits |
|
||||
|------|-----------|-------------------|
|
||||
| VHF Low | 134–174 MHz | `0x00xx` |
|
||||
| VHF High | 200–260 MHz | `0x01xx` |
|
||||
| UHF | 400–520 MHz | `0x02xx` |
|
||||
|
||||
The firmware automatically selects the correct band based on the programmed frequency:
|
||||
|
||||
```c
|
||||
if (freq_mhz < 200)
|
||||
band = VHF_LOW;
|
||||
else if (freq_mhz < 300)
|
||||
band = VHF_HIGH;
|
||||
else
|
||||
band = UHF;
|
||||
```
|
||||
|
||||
## Timing Constraints
|
||||
|
||||
I2C transactions have specific timing requirements:
|
||||
|
||||
| Parameter | Value |
|
||||
|-----------|-------|
|
||||
| Clock frequency | ~100 kHz |
|
||||
| Start setup | 4.7 µs minimum |
|
||||
| Stop setup | 4.0 µs minimum |
|
||||
| Data hold | 0 µs minimum |
|
||||
| PLL lock time | ~5 ms after frequency change |
|
||||
|
||||
The firmware inserts appropriate delays between operations. A complete frequency change (command parse → PLL lock → audio enabled) takes approximately 10 ms.
|
||||
108
src/content/docs/reference/firmware/index.md
Normal file
108
src/content/docs/reference/firmware/index.md
Normal file
@ -0,0 +1,108 @@
|
||||
---
|
||||
title: Firmware Internals
|
||||
description: Reverse-engineered documentation of the RS-UV3 V2.4a firmware
|
||||
---
|
||||
|
||||
This section documents the internal architecture of the RS-UV3 V2.4a firmware, derived from reverse engineering the original HEX file using Ghidra and gputils.
|
||||
|
||||
:::caution[For Advanced Users]
|
||||
This documentation is for developers who want to understand, modify, or learn from the firmware internals. Normal operation doesn't require any of this information.
|
||||
:::
|
||||
|
||||
## Firmware Overview
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Version** | 2.4a |
|
||||
| **MCU** | PIC18F46J11-I/PT |
|
||||
| **Package** | TQFP-44 |
|
||||
| **Flash** | 64KB (36,296 bytes used) |
|
||||
| **RAM** | 3,776 bytes |
|
||||
| **Clock** | Internal oscillator, 8 MHz |
|
||||
| **Voltage** | 3.3V |
|
||||
|
||||
The firmware handles:
|
||||
- Serial command parsing (66 commands across two dispatchers)
|
||||
- I2C communication with the RDA1846S transceiver chip
|
||||
- EEPROM storage for channel memories and configuration
|
||||
- CTCSS/DCS tone encoding and decoding
|
||||
- CW and DTMF generation
|
||||
- Beacon timing and transmission
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Main Loop (0x000400) │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
||||
│ │ Check UART │→ │ Parse Cmd │→ │ Dispatch A or B │ │
|
||||
│ │ RX Buffer │ │ 2-char code │ │ based on 1st char │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────┴───────────────┐
|
||||
▼ ▼
|
||||
┌──────────────────────────┐ ┌──────────────────────────┐
|
||||
│ Dispatcher A (0x0042b0) │ │ Dispatcher B (0x005972) │
|
||||
│ 5,826 bytes │ │ 10,632 bytes │
|
||||
│ ────────────────────────│ │ ────────────────────────│
|
||||
│ PD, PW, RC, RR, RS │ │ AF, AI, AO, B1, B2... │
|
||||
│ ST, CP, FD, BL │ │ (bulk of commands) │
|
||||
└──────────────────────────┘ └──────────────────────────┘
|
||||
│ │
|
||||
└───────────────┬───────────────┘
|
||||
▼
|
||||
┌──────────────────────────────┐
|
||||
│ Hardware Abstraction Layer │
|
||||
│ ─────────────────────────── │
|
||||
│ • I2C to RDA1846S (synth) │
|
||||
│ • EEPROM read/write │
|
||||
│ • UART TX/RX │
|
||||
│ • ADC for RSSI, temp, volts │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
## Memory Layout
|
||||
|
||||
| Region | Address | Size | Purpose |
|
||||
|--------|---------|------|---------|
|
||||
| Reset vector | `0x000000` | 4B | Jump to init |
|
||||
| High-priority ISR | `0x000008` | 16B | Serial RX entry |
|
||||
| Low-priority ISR | `0x000018` | 874B | Timers, buffers |
|
||||
| Init + Main | `0x000400` | 2,098B | Setup and event loop |
|
||||
| Dispatcher A | `0x0042b0` | 5,826B | Memory/power commands |
|
||||
| Dispatcher B | `0x005972` | 10,632B | Radio config commands |
|
||||
| String tables | `0x008B38` | ~1,800B | Response strings |
|
||||
| **Total code** | — | 36,296B | 55% of 64KB flash |
|
||||
|
||||
## Tools Used
|
||||
|
||||
The reverse engineering was performed with:
|
||||
|
||||
| Tool | Version | Purpose |
|
||||
|------|---------|---------|
|
||||
| **Ghidra** | 12.0.1 | PIC-18 decompilation, cross-reference analysis |
|
||||
| **GhydraMCP** | — | Ghidra integration for Claude Code |
|
||||
| **gputils** | 1.5.2 | `gpdasm` disassembly, `gpasm` reassembly |
|
||||
| **objcopy** | 2.45.1 | Intel HEX to raw binary conversion |
|
||||
| **Python 3** | — | Annotation scripts (annotate.py, c-port.py) |
|
||||
|
||||
## Verification
|
||||
|
||||
All reverse-engineered assembly was verified byte-identical to the original firmware:
|
||||
|
||||
```bash
|
||||
# Convert both to raw binary and compare
|
||||
objcopy -I ihex -O binary reassembled.hex /tmp/verify.bin
|
||||
cmp original.bin /tmp/verify.bin
|
||||
# Output: (no output = identical)
|
||||
```
|
||||
|
||||
This verification runs automatically via `make verify` in the source directory.
|
||||
|
||||
## Section Contents
|
||||
|
||||
- **[Command Dispatch](./command-dispatch/)** — How the firmware parses and routes serial commands
|
||||
- **[Memory Map](./memory-map/)** — RAM variables, EEPROM layout, and register definitions
|
||||
- **[I2C & Synthesizer](./i2c-synthesizer/)** — Communication with the RDA1846S transceiver chip
|
||||
- **[Source Files](./source-files/)** — Annotated assembly, C port, and analysis tools
|
||||
197
src/content/docs/reference/firmware/memory-map.md
Normal file
197
src/content/docs/reference/firmware/memory-map.md
Normal file
@ -0,0 +1,197 @@
|
||||
---
|
||||
title: Memory Map
|
||||
description: RAM variables, EEPROM layout, and register definitions
|
||||
---
|
||||
|
||||
The PIC18F46J11 has 3,776 bytes of RAM organized into banks, plus 1KB of internal EEPROM for persistent storage. This page documents the key memory locations used by the RS-UV3 firmware.
|
||||
|
||||
## RAM Organization
|
||||
|
||||
### Serial Communication
|
||||
|
||||
| Address | Name | Description |
|
||||
|---------|------|-------------|
|
||||
| `0x059a` | `cmd_char0` | Serial buffer pointer (low byte) |
|
||||
| `0x059b` | `cmd_char1` | Serial buffer pointer (high byte) |
|
||||
| `0x059d` | `port_selector` | Which port received command: 1=USB, 2=DE-9 |
|
||||
| `0x059f` | `cmd_param` | Parsed numeric parameter |
|
||||
| `0x05a0` | `response_port` | Output port for response |
|
||||
| `0x05df` | `output_port` | Current serial output destination |
|
||||
|
||||
### String Table Interface
|
||||
|
||||
| Address | Name | Description |
|
||||
|---------|------|-------------|
|
||||
| `0x05e0` | `str_tbl_lo` | String table index (low byte) |
|
||||
| `0x05e1` | `str_tbl_hi` | String table index (high byte) |
|
||||
| `0x05e2` | `str_length` | String length for transmission |
|
||||
|
||||
### Frequency Storage
|
||||
|
||||
| Address | Name | Description |
|
||||
|---------|------|-------------|
|
||||
| `0x045d–0x0460` | `freq_main` | Main frequency (4 bytes, kHz × 100) |
|
||||
| `0x0459–0x045c` | `freq_sub` | Sub/offset frequency (4 bytes) |
|
||||
| `0x0461–0x0464` | `freq_tx` | TX frequency when split |
|
||||
| `0x0467` | `power_level` | TX power: 0=low, 1=high |
|
||||
|
||||
### Configuration Flags
|
||||
|
||||
| Address | Name | Description |
|
||||
|---------|------|-------------|
|
||||
| `0x0423` | `io_config` | AI/AO analog I/O configuration |
|
||||
| `0x0425` | `config_flags` | Audio filter, power, misc flags |
|
||||
| `0x0454` | `squelch_mode` | Squelch sensitivity: L/M/H |
|
||||
| `0x046a` | `tone_mode` | CTCSS mode: 0=off, 1=encode, 2=enc+dec |
|
||||
|
||||
### Math Library Workspace
|
||||
|
||||
The firmware implements 32-bit multiply and divide using shift-and-add. These addresses are the working registers:
|
||||
|
||||
| Address | Name | Description |
|
||||
|---------|------|-------------|
|
||||
| `0x0000–0x0003` | `Common_RAM` | 32-bit accumulator (bank 0) |
|
||||
| `0x05a1–0x05a4` | `math_op_a` | First operand |
|
||||
| `0x05a5–0x05a8` | `math_op_b` | Second operand |
|
||||
| `0x05a9–0x05ac` | `math_result` | Result storage |
|
||||
|
||||
### I2C Communication
|
||||
|
||||
| Address | Name | Description |
|
||||
|---------|------|-------------|
|
||||
| `0x05e3` | `i2c_addr` | Target I2C address |
|
||||
| `0x05e4` | `i2c_data` | Data byte for transfer |
|
||||
| `0x05e5` | `i2c_status` | Transfer status/error code |
|
||||
|
||||
## EEPROM Layout
|
||||
|
||||
The PIC18F46J11 has 1KB of internal EEPROM. The RS-UV3 uses it for persistent configuration:
|
||||
|
||||
### Channel Memories (0x00–0x5F)
|
||||
|
||||
Each channel uses 10 bytes:
|
||||
|
||||
| Offset | Size | Content |
|
||||
|--------|------|---------|
|
||||
| +0 | 4 | RX frequency (kHz × 100) |
|
||||
| +4 | 4 | TX frequency (kHz × 100) |
|
||||
| +8 | 1 | CTCSS tone index |
|
||||
| +9 | 1 | Flags (power, squelch mode) |
|
||||
|
||||
Channels 1–9 are stored at `0x00`, `0x0A`, `0x14`, etc.
|
||||
Channel 0 is the power-on default configuration.
|
||||
|
||||
### System Configuration (0x5A–0x6F)
|
||||
|
||||
| Address | Content |
|
||||
|---------|---------|
|
||||
| `0x5A` | Squelch level (0–9) |
|
||||
| `0x5B` | Squelch sensitivity |
|
||||
| `0x5C` | Volume level (0–39) |
|
||||
| `0x5D` | Audio filter flags |
|
||||
| `0x5E–0x5F` | Beacon timer (seconds) |
|
||||
| `0x60–0x63` | Reserved |
|
||||
| `0x64` | Bandwidth setting |
|
||||
| `0x65` | VOX sensitivity |
|
||||
| `0x66` | Hang time (×10 ms) |
|
||||
| `0x67` | COR inhibit time |
|
||||
|
||||
### Beacon Message (0x70–0x8F)
|
||||
|
||||
32 bytes for the beacon message text (null-terminated ASCII).
|
||||
|
||||
### Callsign (0x90–0xA3)
|
||||
|
||||
20 bytes for the station callsign (null-terminated ASCII).
|
||||
|
||||
## Special Function Registers
|
||||
|
||||
Key PIC18F46J11 SFRs used by the firmware:
|
||||
|
||||
### UART (Serial)
|
||||
|
||||
| Register | Address | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `TXSTA1` | `0xFAC` | UART1 transmit status |
|
||||
| `RCSTA1` | `0xFAB` | UART1 receive status |
|
||||
| `TXREG1` | `0xFAD` | UART1 transmit data |
|
||||
| `RCREG1` | `0xFAE` | UART1 receive data |
|
||||
| `SPBRG1` | `0xFAF` | UART1 baud rate generator |
|
||||
| `TXSTA2` | `0xF6C` | UART2 transmit status |
|
||||
| `RCSTA2` | `0xF6B` | UART2 receive status |
|
||||
|
||||
### I2C (MSSP)
|
||||
|
||||
| Register | Address | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `SSPCON1` | `0xFC6` | MSSP control 1 |
|
||||
| `SSPCON2` | `0xFC5` | MSSP control 2 |
|
||||
| `SSPSTAT` | `0xFC7` | MSSP status |
|
||||
| `SSPBUF` | `0xFC9` | MSSP data buffer |
|
||||
| `SSPADD` | `0xFC8` | I2C address/baud |
|
||||
|
||||
### ADC
|
||||
|
||||
| Register | Address | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `ADCON0` | `0xFC2` | ADC control 0 |
|
||||
| `ADCON1` | `0xFC1` | ADC control 1 |
|
||||
| `ADRESH` | `0xFC4` | ADC result high |
|
||||
| `ADRESL` | `0xFC3` | ADC result low |
|
||||
|
||||
### Interrupts
|
||||
|
||||
| Register | Address | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `INTCON` | `0xFF2` | Interrupt control |
|
||||
| `PIR1` | `0xF9E` | Peripheral interrupt flags 1 |
|
||||
| `PIE1` | `0xF9D` | Peripheral interrupt enable 1 |
|
||||
|
||||
## Memory Access Patterns
|
||||
|
||||
### Reading from String Table
|
||||
|
||||
The firmware stores response strings in program memory. To send a string:
|
||||
|
||||
```asm
|
||||
movlw 0x74 ; String index low
|
||||
movwf str_tbl_lo
|
||||
movlw 0x00 ; String index high
|
||||
movwf str_tbl_hi
|
||||
call serial_send_string ; Sends until null terminator
|
||||
```
|
||||
|
||||
### EEPROM Read
|
||||
|
||||
```asm
|
||||
movlw 0x5A ; EEPROM address
|
||||
movwf EEADR
|
||||
bcf EECON1, EEPGD ; Select data EEPROM
|
||||
bcf EECON1, CFGS ; Not configuration
|
||||
bsf EECON1, RD ; Start read
|
||||
movf EEDATA, W ; Read result
|
||||
```
|
||||
|
||||
### EEPROM Write
|
||||
|
||||
```asm
|
||||
movlw 0x5A ; Address
|
||||
movwf EEADR
|
||||
movlw 0x05 ; Data to write
|
||||
movwf EEDATA
|
||||
bcf EECON1, EEPGD
|
||||
bcf EECON1, CFGS
|
||||
bsf EECON1, WREN ; Enable writes
|
||||
; Required unlock sequence
|
||||
movlw 0x55
|
||||
movwf EECON2
|
||||
movlw 0xAA
|
||||
movwf EECON2
|
||||
bsf EECON1, WR ; Start write
|
||||
; Wait for completion
|
||||
btfsc EECON1, WR
|
||||
bra $-2
|
||||
bcf EECON1, WREN ; Disable writes
|
||||
```
|
||||
|
||||
The unlock sequence (0x55, 0xAA) is a hardware requirement to prevent accidental writes.
|
||||
207
src/content/docs/reference/firmware/source-files.md
Normal file
207
src/content/docs/reference/firmware/source-files.md
Normal file
@ -0,0 +1,207 @@
|
||||
---
|
||||
title: Source Files
|
||||
description: Annotated assembly, C port, and analysis tools
|
||||
---
|
||||
|
||||
The reverse engineering effort produced several source files that document the RS-UV3 V2.4a firmware internals. All files are available in the project repository.
|
||||
|
||||
## Primary Sources
|
||||
|
||||
### Annotated Assembly
|
||||
|
||||
**`src/rs-uv3-v2.4.asm`** — 19,053 lines
|
||||
|
||||
The complete firmware in PIC18 assembly, annotated for readability:
|
||||
|
||||
- **97 named functions** with descriptive labels
|
||||
- **34 section headers** dividing the code into logical regions
|
||||
- **71 RAM variable definitions** with meaningful names
|
||||
- **247 ASCII character annotations** on XOR comparisons
|
||||
- **Byte-identical to original** — verified via `make verify`
|
||||
|
||||
```asm
|
||||
; Example: Command dispatcher entry
|
||||
cmd_dispatch_a: ; address: 0x0042b0
|
||||
movff 0x59a, FSR0L ; Load serial buffer pointer
|
||||
movff 0x59b, FSR0H
|
||||
movf POSTINC0, W ; Read first command character
|
||||
xorlw 0x50 ; 'P'
|
||||
btfss STATUS, Z
|
||||
bra not_P_command
|
||||
; ... handle P commands (PD, PW) ...
|
||||
```
|
||||
|
||||
### Readable C Port
|
||||
|
||||
**`src/rs-uv3-v2.4-readable.c`** — 12,020 lines
|
||||
|
||||
Ghidra's decompiled C output, cleaned up for human readability:
|
||||
|
||||
- **89 function renames** from `FUN_CODE_XXXXXX` to descriptive names
|
||||
- **130 variable renames** from `DAT_DATA_XXXX` to register names
|
||||
- **34 section headers** matching the assembly
|
||||
- **35 algorithm annotations** explaining complex routines
|
||||
|
||||
```c
|
||||
// Example: Frequency setting function
|
||||
void set_frequency(void) {
|
||||
// Calculate PLL divider from frequency in kHz
|
||||
math_op_a = freq_main;
|
||||
math_op_b = 0x00001388; // 5000 (scaling factor)
|
||||
math_multiply_32();
|
||||
|
||||
// Write to RDA1846S via I2C
|
||||
i2c_addr = 0x29; // RX frequency register
|
||||
i2c_data = math_result >> 16;
|
||||
i2c_write_word();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
:::note[Not Compilable]
|
||||
The C port is a reference document, not compilable source. Ghidra's decompilation includes constructs that aren't valid C (like `CONCAT11` macros). Use it to understand algorithms, not to build firmware.
|
||||
:::
|
||||
|
||||
## Analysis Tools
|
||||
|
||||
### annotate.py
|
||||
|
||||
**`analysis/annotate.py`** — 250 lines
|
||||
|
||||
Transforms raw gpdasm output into annotated assembly:
|
||||
|
||||
- Applies the code/data boundary (code ends at `0x8B38`)
|
||||
- Replaces misinterpreted data bytes with `db` directives
|
||||
- Adds ASCII character annotations to XOR/SUB instructions
|
||||
- Inserts function labels from `label-mapping.py`
|
||||
|
||||
```python
|
||||
# Key function: annotate_instruction
|
||||
def annotate_instruction(line, address):
|
||||
# Add ASCII annotation to xorlw/sublw
|
||||
if 'xorlw' in line or 'sublw' in line:
|
||||
match = re.search(r'0x([0-9a-f]{2})', line)
|
||||
if match:
|
||||
value = int(match.group(1), 16)
|
||||
if 0x20 <= value <= 0x7E:
|
||||
return f"{line.rstrip():<52}; '{chr(value)}'"
|
||||
return line
|
||||
```
|
||||
|
||||
### c-port.py
|
||||
|
||||
**`analysis/c-port.py`** — 690 lines
|
||||
|
||||
Transforms Ghidra's decompiled C into readable code:
|
||||
|
||||
- Renames functions using mappings from `label-mapping.py`
|
||||
- Renames data references to meaningful variable names
|
||||
- Handles thunk functions (trampolines to real functions)
|
||||
- Adds section headers and documentation comments
|
||||
|
||||
```python
|
||||
# Thunk handling
|
||||
THUNK_NAMES = {
|
||||
"thunk_FUN_CODE_001344": "thunk_parse_number",
|
||||
"thunk_FUN_CODE_002aee": "thunk_set_baud_rate",
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
### label-mapping.py
|
||||
|
||||
**`analysis/label-mapping.py`** — 241 lines
|
||||
|
||||
Central database of all named symbols:
|
||||
|
||||
- **Function names** — 97 entries with addresses and descriptions
|
||||
- **Thunk mappings** — 4 trampolines to their targets
|
||||
- **RAM variables** — 71 data addresses with names
|
||||
- **Section boundaries** — 34 code region markers
|
||||
|
||||
```python
|
||||
FUNCTION_NAMES = {
|
||||
"function_001": ("reset_vector", "0x000000", "Reset vector - jumps to init"),
|
||||
"function_002": ("high_isr", "0x000008", "High-priority interrupt"),
|
||||
"function_003": ("low_isr", "0x000018", "Low-priority interrupt"),
|
||||
"function_004": ("init_radio", "0x000400", "Main init and event loop"),
|
||||
"function_005": ("cmd_dispatch_a", "0x0042b0", "Dispatcher A: PD,PW,RC,RR,RS,ST,CP,FD,BL"),
|
||||
"function_006": ("cmd_dispatch_b", "0x005972", "Dispatcher B: AF through X1"),
|
||||
# ... 91 more entries
|
||||
}
|
||||
```
|
||||
|
||||
## Build System
|
||||
|
||||
### Makefile
|
||||
|
||||
**`src/Makefile`**
|
||||
|
||||
Builds and verifies the assembly:
|
||||
|
||||
```makefile
|
||||
TARGET = rs-uv3-v2.4.hex
|
||||
ORIGBIN = ../downloads/firmware/RS-UV3_V2-4.bin
|
||||
|
||||
build:
|
||||
gpasm -p p18f46j11 -o $(TARGET) rs-uv3-v2.4.asm
|
||||
|
||||
verify: build
|
||||
@objcopy -I ihex -O binary $(TARGET) /tmp/rs-uv3-verify.bin
|
||||
@cmp $(ORIGBIN) /tmp/rs-uv3-verify.bin && \
|
||||
echo "MATCH: Output identical to original firmware" || \
|
||||
(echo "MISMATCH: Differences found"; exit 1)
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET) *.lst *.cod
|
||||
```
|
||||
|
||||
Run `make verify` to confirm the assembly produces byte-identical output.
|
||||
|
||||
## Supporting Files
|
||||
|
||||
### Analysis Documents
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `analysis/command-map.md` | Complete command dispatch documentation |
|
||||
| `analysis/communicator-analysis.md` | Windows control software analysis |
|
||||
| `analysis/collaboration-post.md` | Reverse engineering project summary |
|
||||
|
||||
### Ghidra Project
|
||||
|
||||
| Path | Contents |
|
||||
|------|----------|
|
||||
| `ghidra-project/RS-UV3-firmware.gpr` | Ghidra project file |
|
||||
| `ghidra-project/RS-UV3-firmware.rep/` | Analysis database |
|
||||
| `ghidra-project/export/` | Exported listings and decompilation |
|
||||
|
||||
### Original Firmware
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `downloads/firmware/RS-UV3_V2-4.hex` | Original Intel HEX |
|
||||
| `downloads/firmware/RS-UV3_V2-4.bin` | Raw binary (for verification) |
|
||||
|
||||
## Verification
|
||||
|
||||
The annotated assembly is verified byte-identical to the original firmware after every modification:
|
||||
|
||||
```bash
|
||||
$ cd src && make verify
|
||||
gpasm -p p18f46j11 -o rs-uv3-v2.4.hex rs-uv3-v2.4.asm
|
||||
MATCH: Output identical to original firmware
|
||||
```
|
||||
|
||||
Labels, comments, and section headers don't generate machine code — they're stripped during assembly. This allows aggressive annotation without any risk of changing the firmware behavior.
|
||||
|
||||
## Contributing
|
||||
|
||||
To add annotations or improve the documentation:
|
||||
|
||||
1. Edit `analysis/label-mapping.py` to add/rename symbols
|
||||
2. Run `python analysis/annotate.py` to regenerate assembly
|
||||
3. Run `make verify` to confirm no byte changes
|
||||
4. Run `python analysis/c-port.py` to regenerate C port
|
||||
|
||||
The verification step is critical — it ensures your changes don't accidentally affect the assembled output.
|
||||
Loading…
x
Reference in New Issue
Block a user