A receiver the size of a key fob that hears long wave to 108 MHz — and answers a PC as though it were a Kenwood.
AM, FM, shortwave, long wave and single sideband on a LILYGO T-Embed SI4732. This is the firmware: the upstream PU2CLR sketch brought up on Arduino ESP32 core 3.x, reworked around what the hardware actually does rather than what the example assumed, and given remote control over USB and WiFi so Hamlib, Flrig and WSJT-X can drive it.
the radio

Every broadcast band, and the ham bands in between.
- 153 kHz to 30 MHz, plus 64–108 MHz. Twenty presets: long and medium wave, the tropical and broadcast shortwave bands, 80/40/20/15/10 m, CB, and one continuous sweep of everything below VHF.
- Single sideband. LSB and USB with a 10 Hz fine-tune for zero-beating a voice or a CW note, and filters down to 0.5 kHz for when the band is crowded.
- Per-band memory. Frequency, step and filter are kept separately for each band and restored when you come back to it.
- RDS station names and a stereo indicator on FM; AGC, manual attenuation and soft mute everywhere else.
- Built for a pocket. The screen dims after twenty seconds, a long press sleeps the board, and an LED ring around the encoder follows the direction you are turning.
It identifies as a Kenwood TS-480.
That is the best-supported protocol in free ham software, so anything speaking Hamlib drives it without a driver — Flrig, WSJT-X, JS8Call, Fldigi, CQRLOG. Two transports, independent, both off until you switch them on, both remembered across a power cycle:
- USB serial, sharing the port with the boot log — which falls silent while remote control owns the stream, because interleaving the two corrupts every reply.
- WiFi TCP, with a captive-portal setup page for the first connection, five saved networks, and mDNS so it answers to
t-embed-cat.local. Plug in a USB host and the WiFi hardware powers down on its own — tethered, the radio does not need it, and the RF is better off quiet next to a shortwave front end.
What it is not is an SDR. The SI4732 is a hardware DSP receiver with no IQ output, so no waterfall program can draw a spectrum from it. What you get is rig control: the PC sets frequency, mode and volume and reads the S-meter, the radio does the demodulating, and the audio still comes out of the headphone jack — feed that into a sound card and FT8 or WSPR decode like they would from any other rig.
Stock board, no modifications.
An ESP32-S3 with 16 MB of flash and 8 MB of PSRAM, a Si4732-A10 DSP receiver on I²C, a 1.9 inch 320×170 ST7789V display, a rotary encoder and a seven-LED APA102 ring. Everything below is stock LILYGO hardware — the work is entirely in firmware.
pinmap

Encoder_Button — BOOT (IO00) on the right. That one pin is behind the first field note below. Diagram © LILYGO.Five things that were not in the datasheet.
The reset gesture that reset everything
The upstream sketch wipes your stored settings if you hold the encoder while powering on. On this board the encoder button is GPIO0 — the boot strapping pin, and also the deep-sleep wake source. Hold it at a cold boot and the chip enters download mode, so the sketch never runs and the check is unreachable. The one reset that did reach it was a wake from deep sleep, where that same button is being held by definition. Switching the radio on erased everything. It is a menu entry behind a confirmation now.
A rig that answered too much
Flrig kept reporting “Transceiver not responding” over a socket that was demonstrably healthy. A real Kenwood acknowledges a set command with silence; this was answering unknown ones with ?;. Flrig sends a pile of menu sets on connect, banked all those replies, and then read one of them in place of the frequency. Unknown queries get ?;, unknown sets get nothing at all — that asymmetry is the entire fix.
The lag was never the radio
The frequency on the PC trailed the knob by seconds, badly enough to look like the wrong frequency. The radio answers in about seven milliseconds. Flrig sleeps a fixed delay after every command rather than waiting for the reply, and its poll cycle was twenty-one commands — 8.4 seconds per refresh. Underneath that, saving settings called EEPROM.commit() once per band, and EEPROM here is flash-backed, so each call rewrote the whole blob: twenty-one flash transactions that stalled replies by up to 1.4 seconds. One commit and a polling setting took the refresh to 1.47 s.
Two cores, one receiver
Remote control runs on the core the radio loop does not use, answering from a snapshot so a reply never waits behind a screen redraw or an I²C read. That split has to be enforced rather than assumed, and it was not: the display was inspecting network socket handles the other core could free underneath it, and switching WiFi off could tear the network stack out from under a task still reading from it. Both now pass through a barrier that waits for the other side to let go. Neither fault was visible in use — they surface as a reboot minutes later with no obvious cause, which is the worst kind.
The comment that was true when it was written
The WiFi setup page kept vanishing eleven seconds after it opened — long before anyone could type a network name into it. The line responsible had been correct for months: if the portal is up it was probably how we got here, so give the browser eight seconds to show the result and then take the access point down. That held right up until the radio was taught to keep retrying saved networks in the background. After that, opening the setup page on a radio whose own network was in range reconnected within seconds and started the countdown by itself. Two more faults surfaced the same way, and neither was visible from reading the code: the retry meant to rescue the radio was being rejected outright by the WiFi driver, which had quietly kept auto-retrying the dead network and refuses to be reconfigured while it does — so with one bad network saved the radio sat in the setup page forever, unable to reach the good one sitting next to it. And the driver’s complaint about that was printing to the same USB port the remote control speaks over, arriving spliced into the middle of a reply. Three bugs, all found in the first hour of actually using the thing.
In use, and tested on the hardware rather than in principle. Reception, SSB, the band memories and the menu all work. Remote control is verified over both transports: a hundred round trips at 22 ms worst case on USB and 5.6 ms median over WiFi, seventy commands written as one burst answered as seventy well-formed frames, a browser pointed at the control port hung up on rather than obeyed, and a fifth client evicting the least recently used rather than being turned away. The setup page is exercised end to end too — captive-portal redirect on Android, Apple and Windows probes, every malformed input refused, networks saved and deleted, and the open access point verifiably off the air once it times out.
Source, and the full engineering notes including every wrong turn on the way: github.com/anducampu/ESP32-S3-LILYGO-T-EMBED-SI4732
Product photograph and pinmap diagram are © LILYGO, reproduced from their wiki to identify the hardware. The firmware in this project is GPL-3.0.
