Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -471,20 +471,33 @@ If the port is busy or silent, make sure no other CAT application (hamlib, rigct

### Custom CAT commands (PU7FTW extensions)

All commands terminate with `;`. All SET commands echo the new value as a GET reply. Commands are safe to batch in a single write — the firmware processes them in order and concatenates replies, e.g. `FA;MD;AG0;FW;VO;AT;A2;NR;BL;` returns all 9 replies in one read window.
All commands terminate with `;`. All SET commands echo the effective value as a GET reply — if a SET is rejected (out of range), the echo returns the **old** value, which the web app relies on. Commands are safe to batch in a single write — the firmware processes them in order and concatenates replies, e.g. `FA;MD;AG0;FW;VO;AT;A2;NR;SM;DR;BL;AL;` (the app's poll batch) returns all 12 replies in one read window. PA bias (`PM`/`PX`) and TX time-out (`TT`) are deliberately **not** polled — they're read/written on demand only when the advanced settings panel is open, to save the radio cycles on options that don't need live tracking.

| Command | Query | Set | Range | Notes |
|---------|-------|-----|-------|-------|
| **VO** — volume | `VO;` → `VOn;` | `VOn;` | −1…16 | −1 = mute |
| **AT** — ATT1 | `AT;` → `ATn;` | `ATn;` | 0…7 | dB steps: 0/−13/−20/−33/−40/−53/−60/−73 dB |
| **A2** — ATT2 | `A2;` → `A2n;` | `A2n;` | 0…16 | linear index |
| **AT** — analog attenuator | `AT;` → `ATn;` | `ATn;` | 0…7 | dB steps: 0/−13/−20/−33/−40/−53/−60/−73 dB |
| **A2** — digital attenuator | `A2;` → `A2n;` | `A2n;` | 0…16 | −6.02 dB per step |
| **NR** — noise reduction | `NR;` → `NRn;` | `NRn;` | 0…8 | 0 = off |
| **BL** — backlight | `BL;` → `BL0;`/`BL1;` | `BL0;`/`BL1;` | 0…1 | |
| **AG0** — AGC | `AG0;` → `AG0n;` | `AG0n;` | 0…2 | 0=OFF 1=Fast 2=Slow |
| **AG0** — AGC on/off | `AG0;` → `AG0n;` | `AG0n;` | 0…1 | single algorithm (M0PUB fast-attack/slow-decay); values >1 rejected |
| **AL** — AGC level | `AL;` → `ALn;` | `ALn;` | 1…14 | target window: output peaks held in [n·256 … n·384]; default 4 |
| **FW** — filter BW | `FW;` → `FWn;` | `FWn;` | 0…7 | 0=Full 1=3k 2=2.4k 3=1.8k 4=500 5=200 6=100 7=50 Hz |
| **SM** — S-meter | `SM;` → `SMn;` | — | dBm (signed) | read-only; integer-math dBm, ±1 dB vs the old float version. During TX replies an empty `SM;` (no RX signal to measure) |
| **DR** — TX drive | `DR;` → `DRn;` | `DRn;` | 0…8 | pre-clipping input gain (each step doubles the envelope); 8 = always max |
| **TT** — TX time-out | `TT;` → `TTn;` | `TTn;` | 0…255 s | TOT guardrail: force-unkeys the PA past the limit; 0 = disabled, default 30. Not polled — read/written on demand from the advanced settings panel |
| **FV** — firmware version | `FV;` → `FV4.01a;` | — | | read-only; the web app gates its extension controls on this instead of hardcoding the version |
| **PM** — PA bias min | `PM;` → `PMn;` | `PMn;` | 0…max−1 | PWM LUT idle endpoint; SET rebuilds the LUT live |
| **PX** — PA bias max | `PX;` → `PXn;` | `PXn;` | min+1…255 | PWM LUT full-drive endpoint — the real output-power control for digital modes |
| **XF** — ref frequency | `XF;` → `XFnnnnnnnn;` | `XFnnnnnnnn;` | 14–28 MHz | si5351 fxtal calibration; used by the calibration wizard |
| **FD** — factory defaults | `FD;` → one CSV frame | — | 11 values | `FD<vol>,<att>,<att2>,<nr>,<agc>,<filt>,<drive>,<bl>,<pm>,<px>,<md>;` |
| **SR** — soft restart | `SR;` → `SR1;` | — | | acks then watchdog-reboots (≈2–3 s off the wire) |
| **SR2** — factory reset | `SR2;` → `SR2;` | — | | wipes ALL settings incl. band data and calibration, then reboots |
| **TQ** — PTT state | `TQ;` → `TQ0;`/`TQ1;` | `TQ0;`/`TQ1;` | 0…1 | firmware also broadcasts `TQ0;`/`TQ1;` unsolicited on PTT transitions |

Standard TS-480 commands also supported: `FA` (VFO A freq get/set), `MD` (mode), `IF` (37-byte info frame), `TX`/`RX` (PTT), `ID`, `PS`, `AI`, `VX`.
Standard TS-480 commands also supported: `FA` (frequency get/set — single VFO), `MD` (mode; digits 4/5 = FM/AM are selectable for **RX only**, out-of-range digits clamp to LSB), `IF` (status frame), `TX`/`RX` (PTT — **ignored unless mode is LSB/USB**; AM/FM/CW are receive-only in this build), `ID`, `PS`, `AI`, `VX`.

> **Removed vs. earlier 4.00x builds (2026-07-06):** VOX + noise gate, RIT (`RTS` offset command), TX offset (`XO`), VFO B/Split, CW/AM/FM transmit, the Fast AGC variant (replaced by `AL` level control), and all float math (integer dBm S-meter). See [`firmware/usdxBLACKBRICK/README.md`](firmware/usdxBLACKBRICK/README.md) for the complete lineage vs. the original QCX-SSB R1.02w firmware.

## Testing

Expand All @@ -509,7 +522,7 @@ npm run test:coverage

- TS-480 command construction (`FA`, `MD`)
- Response parsing helpers (`parseFrequency`, `parseMode`, `parseIntField`, `parseBoolField`)
- All 8 custom BLACK_BRICK commands (`VO`, `AT`, `A2`, `NR`, `BL`, `AG0`, `FW`, `TQ`)
- All custom BLACK_BRICK commands (`VO`, `AT`, `A2`, `NR`, `BL`, `AG0`, `AL`, `FW`, `SM`, `DR`, `PM`, `PX`, `XF`, `FD`, `SR`, `TQ`)
- Multi-command batch response splitting and 2-char prefix lookup map
- IF frame parsing against the exact frame layout emitted by the firmware
- Range validation for every writable parameter
Expand All @@ -518,8 +531,6 @@ npm run test:coverage
npm test -- src/lib/cat/__tests__/protocol.test.ts
```

Expected result: **35 tests, 35 passed**.

### Testbed setup (live radio required)

The CAT unit tests above need no hardware. If you want to run manual smoke tests or extend the suite with hardware-in-the-loop tests:
Expand Down
109 changes: 109 additions & 0 deletions firmware/usdxBLACKBRICK/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# uSDX BLACK_BRICK firmware — lineage and changes

Customized firmware for the Chinese "black brick" uSDX clone (ATmega328P @ 20 MHz, HD44780 16×2 LCD, class-E PA, SI5351 @ 25 MHz TCXO), operated as a **CAT-controlled digital-modes transceiver**: RX with basic signal controls, TX always SSB fed by external audio (the Signal-Decoder web app or any other encoder) into the mic input, PTT via CAT. It deliberately does **no** decoding of its own.

## Lineage

```
threeme3/QCX-SSB R1.02w (PE1NNZ, 2021-08-23) ← original open-source baseline
└─ GW8RDI/uSDXOpen fork (1.02wA2 … 4.00d, 2022-2023) ← multi-hardware fork
└─ this file (PU7FTW, 4.00e … 4.01a, 2026) ← BLACK_BRICK-only, digital-modes profile
```

This build was not forked directly from R1.02w — it descends through GW8RDI's
uSDXOpen fork — so the tables below describe the *cumulative* difference between
stock R1.02w and the current source.

Stock R1.02w reference: [threeme3/QCX-SSB @ R1.02w](https://github.com/threeme3/QCX-SSB/blob/R1.02w/README.md) ·
Fork lineage: [GW8RDI/uSDXOpen](https://github.com/GW8RDI/uSDXOpen)

## What stock R1.02w shipped (baseline)

Enabled by default: startup diagnostics (DIAG), CW keyer (straight/iambic-A/B + practice mode), TS-480-subset CAT, CW decoder, key-click envelope shaping, semi-QSK, RIT, VOX, CW messages with CQ auto-repeat, 8-band latching-relay LPF switching. Runtime features: LSB/USB/CW/AM/FM (RX **and** TX), VFO A/B + Split, AGC on/off, NR 0–8, analog + digital attenuators, S-meter (OFF/dBm/S/S-bar), TX drive, noise gate, PA bias min/max, CW tone 600/700 Hz, ref-freq + IQ calibration, battery-voltage indicator, CAT RX-audio streaming. Compiled out but present: FAST_AGC, SWR meter, MOX, TX delay, clock, OLED/LCD-I2C variants, multi-hardware (QCX legacy) support.

## Added (vs. R1.02w)

| Feature | Origin | Notes |
|---|---|---|
| Per-band freq/mode memory (KEEP_BAND_DATA) | GW8RDI 1.02wA-era | last frequency + mode restored per band |
| Directional band change, menu wrap-around, error codes | GW8RDI 1.02wA-era | |
| CAT enable menu item (no reboot needed), CAT baud menu (9600–57600) | GW8RDI 4.00a / PU7FTW | |
| CAT frequency error handling | GW8RDI 2.00d | rejects corrupted `FA` payloads (1.5–60 MHz window) |
| CAT mode change incl. AM/FM with IQ-flip tracking | GW8RDI 4.00b | since 2026-07-06 AM/FM are RX-only (see below) |
| **PU7FTW extension commands**: `VO AT A2 NR BL AG0 AL FW SM DR PM PX XF FD SR/SR2` | PU7FTW 4.00e–h | full table in the [project README](../../README.md#custom-cat-commands-pu7ftw-extensions); all SETs echo the effective value, rejects echo the old value |
| Batched multi-command CAT queries | PU7FTW | one write → concatenated replies in order; the web app polls 12 commands per round-trip |
| `SM` live S-meter over CAT | PU7FTW | dBm computed every cycle regardless of the LCD display mode |
| `PM`/`PX` PA-bias live-apply (`build_lut()` on SET) | PU7FTW | same hardware-reapply pattern as the ATT1 fix |
| `XF` ref-frequency calibration over CAT | PU7FTW | drives the web app's receive-only calibration wizard |
| `SR`/`SR2` soft restart / factory reset over CAT | PU7FTW | SR2 realigns EEPROM after param-layout changes |
| **AGC level (`AL`, menu 1.8)** | PU7FTW 2026-07-06 | target window for the M0PUB AGC, 1–14 (default 4 = the algorithm's original 1024…1536 window) |
| **TX gate: only LSB/USB can key the PA** | PU7FTW 2026-07-06 | `switch_rxtx()` refuses TX in CW/AM/FM from PTT and CAT alike — can't transmit over a station you're listening to |
| Baud-rate correction for the 20 MHz crystal | fork/PU7FTW | `Serial.begin(16000000ULL * baud / F_MCU)` |
| Backlight on correct pin (PD3) + CAT `BL` control | PU7FTW 2026-07-04 | stock/fork drove the wrong pin on this hardware |

## Removed (vs. R1.02w)

All removals follow the same pattern: feature flag commented out (or code deleted) with a dated note in the source.

| Feature | Why |
|---|---|
| CW decoder, CW messages/CQ-repeat, CW keyer (iambic + practice mode) | no self-decoding; no key attached |
| **CW transmit** (dsp_tx_cw, sidetone, key-click ramp, semi-QSK, CW tone volume) | TX is always SSB from external audio; CW **RX** kept (narrow filters, `cw_offset` tuning, CW Tone menu) |
| **AM/FM transmit** (dsp_tx_am/dsp_tx_fm) | AM/FM kept as RX-only listening modes |
| **VOX + noise gate** | PTT is CAT-controlled |
| **RIT** (+ `RTS` CAT command) | the PC tunes via CAT |
| **VFO B / Split** (`FREQB`/`MODEB`, VFO menu, A/B swap) | single-VFO; CAT always tuned VFO A anyway |
| **TX offset (`XO` CAT command)** | unused by the web app and by hamlib's TS-480 driver |
| **Fast AGC variant** (`process_agc_fast`) | single algorithm now: M0PUB fast-attack/slow-decay (~60 dB range) as plain AGC ON, plus the `AL` level |
| SWR meter (fork addition), practice mode, MOX, TX delay, CAT audio streaming, QUAD, multi-hardware configs | earlier removals for flash space; BLACK_BRICK is the only supported hardware |
| Startup diagnostics (DIAG), debug/testbench, clock, VSS meter | compiled out |

## Reimplemented / major changes (vs. R1.02w)

| Area | Change |
|---|---|
| **S-meter dBm math** | float + `log10()` → pure integer (MSB position + 4-bit mantissa LUT, 0.376 dB steps). Validated against the float formula: 60 % identical, rest ±1 dB. Removed the only libm-log user. |
| **SI5351 divider math** | `(float)num/denom` → integer `(128*num)/denom` — the datasheet-exact floor; the float could round one step off |
| **AM demod DC blocker** | per-sample float multiply → Q15 integer (~100 cycles/sample saved in AM RX) |
| **Float support library** | fully eliminated (the three sites above were the only live float users) |
| **AGC** | dbm/peak-decay decoupled from LCD display mode (CAT `SM` stays live); `centiGain` backup/restore across TX; target window parameterized (`AL`) |
| **EEPROM layout** | param table shrank with each removal — the layout is positional, so a flash across these changes **requires `SR2;` (factory reset)** unless the version string was bumped (boot then auto-resets) |
| **Menu labels** | fitted to the 16×2 LCD budget: Volume, Noise Reduce, Analog Att, Digital Att, PA bias min/max, AGC (1.7) + AGC level (1.8) |
| ATT1 hardware apply | extracted `apply_att1()`, called from both menu and CAT SET (stock only re-applied from the menu dial) |
| Encoder boot glitch | pull-ups settle + `PCIFR` clear before enabling pin-change interrupts (stock stepped the VFO once at power-on) |

## Known hardware quirk

The LCD shares its data pins with the UART (PD0/PD1); every LCD write briefly
disables the serial port (`pre()`/`post()` in the source, "NOISE LEAK INTO
RX!!!"). Occasional CAT replies carry a stray trailing byte or get dropped —
the web app and the hardware test bed both tolerate/retry this by design. It
is a board-level design constraint, not a firmware bug.

## Flash budget history (2026-07-06 session)

| Build | Flash | Free RAM |
|---|---|---|
| 4.00h baseline | 31 338 (97 %) | 540 |
| − VOX | 30 968 | 544 |
| AGC single-algorithm + `AL` | 30 604 | 536 |
| − RIT, menu renames | 30 300 | 544 |
| − XO | 30 460→30 116 (− CW TX) | 543 |
| − VFO B/Split | 29 404 | 560 |
| − float library (integer dBm) | **28 018 (86 %)** | **560** |

4.01a (same day) additionally sets the PA bias max default to 130 (was 160) for this unit's PA.

A later same-day fix replaced a 1-Hz throttled LCD S-meter redraw (still an
audible periodic tick over the shared LCD/UART pins) with full suppression of
the LCD S-meter/dBm field while a CAT session is active — CAT already delivers
a live reading via `SM;`, so nothing is lost functionally, and `smode` itself
is left untouched so the LCD resumes updating immediately if `cat_active`
is ever cleared.

## Build / flash / test

See the [project README](../../README.md#usdx-black_brick-firmware) for the
full compile/flash/validation workflow. Every flash must pass
`npm run test:cat-hardware` (46 checks against the live radio, RX-only — TX is
never keyed by automated tests).
Loading