The copyright year is 2026. The thread table of the tinySA Ultra did not
line up: the font is proportional and a space is narrower than a digit or
letter, so padding with spaces moved the columns (e.g. "idle", priority 1).
The numbers are now zero padded, the name follows them and the state is
drawn at a fixed position.
Tested on a ZS407. tinySA Ultra +16 B, BASIC unchanged.
When test 3 found no signal the hint was drawn directly on the screen and
the next redraw of the test list overwrote it (only "Did Y" remained). The
waiting line of the test list now shows "Cable connected? Touch screen to
continue" instead of "Touch screen to continue", so it is redrawn with the
list. Test 3 itself already shows the signal level fail.
Tested: on a ZS407 test 3 forced to no signal through the debugger, the
hint stays visible; the tinySA BASIC self test passes all tests and ends
with "Touch screen to continue". BASIC net about -4 B flash, +4 B RAM.
BANDS->SD stored start, end and level only, so the band names were lost.
The name is now written as a quoted fourth field (older firmware ignores
it) and loaded when present (files of older firmware keep the current
names). A line needs start, end and level: the level was read from lines
with only two fields as well.
Tested on a ZS407: band named FMRADIO stored as
0, 900000000, 0.000000000, "FMRADIO"
renamed, loaded from SD: FMRADIO again. tinySA Ultra only.
Loading a preset from SD did not call update_rbw() as caldata_recall()
does, so the RBW menu showed the preset's RBW while the sweep kept using
the previous one.
Tested on a ZS407: preset stored with RBW 30 kHz, RBW set to 300 kHz,
preset loaded from SD: "rbw" and the status panel show 30 kHz.
tinySA Ultra only, +4 B.
"ext_gain" without an argument now prints the current external gain after
the usage line, as "attenuate" and "rbw" do.
Tested on a ZS407 and a tinySA BASIC: 0.0, 12.5 after "ext_gain 12.5". +16 B.
draw_cal_status() clears the whole left panel, the battery included. The
battery is redrawn when a sweep completes, which does not happen while
paused, so after the "pause" command (it draws the status panel directly)
the battery and voltage stayed blank. draw_cal_status() now also requests
the battery redraw (not in output mode, which shows no battery).
Tested on a ZS407 and a tinySA BASIC: battery and voltage shown after
"pause". +8 B.
All units reported the ChibiOS kernel version "400" as USB serial number,
so Windows could not tell two tinySAs apart: with two of them on the same
VID/PID only one enumerated, and a unit could get another unit's COM port.
The serial number is now 12 hex digits made from the MCU unique ID the way
the STM32F303 DFU bootloader makes its serial number (UID0 + UID2, then the
upper half of UID2): a tinySA Ultra has the same serial as COM port and in
DFU mode. It is filled at every USB reset, before the host asks for it.
Windows assigns every unit a new COM port once.
Tested: ZS407 enumerates as 1781:0A44\205D346A2039 (its DFU serial), the
tinySA BASIC as 0483:5740\2056325F2039, both work side by side.
BASIC about +80 B flash, +26 B RAM.
The tinySA ULTRA enumerates as VID 0x1781 PID 0x0A44 instead of the ST
virtual COM port pair 0x0483 0x5740; the tinySA keeps 0x0483 0x5740.
The IDs now follow the model (TINYSA4) instead of the USB descriptor
variant (__USE_USB_MSC__). bcdDevice is unchanged.
Tested on a ZS405 (HW V0.4.5.1) under Windows 11: the composite device,
the serial port and the mass storage interface bind to the inbox drivers
(usbccgp, usbser, USBSTOR) without an INF, the console works on the new
COM port and "reset dfu" enters the bootloader (0483:DF11) as before.
tinySA-App with both IDs accepted connects to either firmware.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Report: in output mode, with each command sent as soon as the prompt
arrives, a command right after "output" or "modulation" (e.g. "level -30"
after "output off", or "mode low input" after "output off" twice) got no
prompt and was dropped with "Command timeout" after 20 s; the next
command worked.
Cause: after a direct command the shell set operation_requested = true,
which is OP_LEVER. The sweep thread left sweep(), found no command yet,
and ui_process() handled that "lever event" and then set
operation_requested = OP_NONE. When the shell had parsed the next command
in between (shell_function set, OP_CONSOLE set), that clear wiped
OP_CONSOLE. A zero span output mode sweep only returns when
operation_requested is set, so the command never ran. Caught on hardware
with a breakpoint on the clear: operation_requested 0x05 with cmd_level /
cmd_mode waiting, every time.
- ui_process() clears only the bit it handled, locked, so OP_CONSOLE
survives lever and touch handling (a touch, e.g. a remote "touch" from
tinySA-App, hit the same clear).
- The sweep thread clears OP_CONSOLE together with taking shell_function,
which the shell sets together, so a request left by a direct or timed
out command does not keep aborting sweeps.
- A direct command asks for the sweep restart with OP_CONSOLE instead of
a fake lever event.
- SI4432_Listen() keeps OP_CONSOLE on entry, so a command ends listen
mode instead of being lost the same way.
Tested on a ZS405 (HW V0.4.5.1): both reported sequences 15 rounds each
without a hang (release firmware: 7 of 7 hung), every reply < 0.5 s.
In zero span output mode the sweep thread loop passes once per direct
command ("output off/on", "level") and not while idle. Remote touches
mixed with commands in output mode: 10 rounds without a hang. Input mode
touch menu and sweeping work. Listen mode and the jog lever not tested
on hardware. ULTRA +88 bytes, BASIC +84 bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Report: after PRESET > LOAD FROM SD and STORE > STORE 1 the name keypad
opened empty, and ENTER on an empty name kept the name stored inside the
.prs file (often the old name of the slot), so the user had to look up
and retype the name of the file they had just loaded.
The keypad now starts with the name of the active preset, the same way
STORE > SD already defaults to the file the preset was loaded from:
- loaded from SD: the file name without ".prs"
- otherwise: setting.preset_name, e.g. of a recalled slot
cut to PRESET_NAME_LENGTH - 1 characters so the keypad shows what the
slot stores. An unnamed preset still opens an empty keypad. A remote
"menu" invoke ignores the default as before (ui_process_keypad).
Tested on a ZS405 (HW V0.4.5.1): an unnamed preset opens empty, a
recalled slot "RESR1" offers RESR1, and with sd_preset_path set through
the ST-Link (this unit's SD card does not mount) "FMBAND.prs" offers
FMBAND and "AIRBAND_118.prs" offers AIRBAND_1; ENTER stores the slot
under the offered name. ULTRA +68 bytes, BASIC builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Report: switching an Ultra (ZS405) on showed a momentary all white screen,
an Ultra+ (ZS406, ZS407) did not.
- The backlight came on before ili9341_init(), so the panel was lit in
reset and during its init; the ZS405 panel shows white then (the
ZS406/ZS407 panels, the ones with display inversion, stay black). It
now comes on at DEFAULT_BRIGHTNESS once the screen is cleared.
- That left a very brief flash: the backlight came on before the panel
had settled. The init now waits the 120 ms the controller needs after
SLEEP_OUT before DISPLAY_ON (LCD_WAKE_UP_MS). Only with
__LCD_BRIGHTNESS__: without backlight control a longer wait would
only show the uninitialised panel longer, so the BASIC is unchanged.
Tested on a ZS405 (HW V0.4.5.1) with the ST-Link: DAC2 at 0 darkens the
backlight, the backlight is dark with the CPU held in reset, the
backlight switched on 2 s after the init shows no flash, and with this
build power-ons show no flash. Start-up 120 ms longer; ULTRA +12 bytes,
BASIC binary unchanged in size.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Remote editing of a file name with tinySA-App became very slow after a
character or two, and the shell could end up not reading any input.
CMD_WAIT_MUTEX commands are run by the sweep thread, but while a keypad
is open that thread stays in ui_process_keypad() and never picks them
up, so the shell waited 4 x 5 s for each before "Command timeout". The
app polls vbat every 6 s outside live remote control, and every touch
queued behind those waits; local editing was not affected.
ui_process_keypad() now sets keypad_open while it waits for input, and
VNAShell_executeLine() answers a CMD_WAIT_MUTEX command with "keypad
active" instead of waiting, like "usb disk mode active". touch, release,
text, refresh, version and capture (CMD_RUN_IN_UI) run as before.
Tested on HW V0.4.5.1: with a keypad open vbat, pause and sweep answer
in 1 ms, capture rle and remote typing work, and after closing vbat and
sweep answer normally. tinySA-App clicking the screen without live
remote control (vbat polls every 6 s) typed all 8 characters, each poll
answered within 3 ms; before, the first poll stalled the rest.
Flash: ULTRA +44 bytes, BASIC +52.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
File, band and preset names could only be typed and erased at the end.
The text keypad now has a cursor where characters are inserted and
deleted.
- Touching the text line moves the cursor to the nearest character
boundary and it follows the finger while touched (remote touch too).
- Keys insert at the cursor (still at most TXTINPUT_LEN characters),
backspace deletes the character before it and does nothing at the
start; a long backspace still erases the whole name and backspace on
an empty name still cancels.
- The cursor is a blue 2 pixel bar in its own 3 pixel gap that sticks
out 4 pixels above and below the letters, so it is never read as an
I (the glyphs are only 1 pixel apart). The input line is now cleared
up to the screen edge, so a shorter name leaves nothing behind.
- The text keypad exists only with __USE_SD_CARD__: IS_TXT_KEYBOARD is
a constant false without it, which drops the text keypad code from
the tinySA (BASIC).
Tested on HW V0.4.6 with remote touch and screen captures: insert,
backspace, drag to the end, edits at the start, long press, cancel and
a full 27 character name edited in the middle; the edit logic also in a
host build with AddressSanitizer. Flash: ULTRA +376 bytes, BASIC -164.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
releaselog.txt (an old generated version line) is removed; changelog.txt
holds the release notes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
"refresh rle" is a third refresh mode next to off and on (auto_capture 2,
__REFRESH_RLE__). Plain "refresh on" is unchanged, so existing hosts keep
working; firmware without the mode answers "usage: refresh off|on".
- A bulk push of 256 pixels or more is sent as "rle8\r\n" + x, y, w, h,
u16 palette size + the region's own sorted palette, then the w * h
palette indices as one PackBits stream without lengths (DiSlord's
packbits(), 480 indices at a time). Background runs continue over the
rows. Smaller pushes fit the USB queue and stay "bulk", as do regions
with more than 256 colours or that might not get smaller.
- The waterfall scrolls the LCD as without remote desktop (4 lines at a
time) while remote_hold drops the pushes, then sends one "scrl\r\n" +
x, y, w, h + int16 dy instead of pushing every moved row again.
Tested on HW V0.5.4 (ZS407) with tinySA-App: the screen rebuilt from a
capture and all pushes equals a raw capture (0 pixels differ), every
stream is byte-identical to packbits(). Normal screen 289 -> 34 KB/s,
big waterfall 368 -> 23 KB/s at 2.12 instead of 1.75 sweeps/s.
Costs about 530 bytes of flash and 544 bytes of RAM.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- 'reset dfu' and enter_dfu() are now also built for the tinySA4 (the
DFU menu stays tinySA3 only); the reply is sent before the reset
- F303 __early_init(): the jump to the system bootloader failed and the
bootloader reset the MCU. crt0 calls __early_init() on the process
stack with interrupts disabled, so switch to the main stack
(CONTROL = 0) and enable interrupts before the jump, as in the F072
board.c. Also enable the SYSCFG clock for the system memory remap and
replace the logical || that made it write 1 to SYSCFG_CFGR1
Tested on HW V0.4.5.1: 'reset dfu' enters the STM32 BOOTLOADER and
dfu-util can flash it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
'capture' with any argument (tinySA-App sends 'capture rle') sends the
screen in the NanoVNA-D (DiSlord) RLE8 format: "BM", width, height,
bpp 8, compression 1, the palette, then per row the PackBits packed
palette indices. Plain 'capture' still sends raw RGB565.
The palette holds all colours the tinySA draws, so the screen is read
once: the LCD palette, the waterfall colours and the trace colours ORed
over each other and over the colours drawn before them in draw_cell().
The image viewer and the display test set capture_verify, as does a
colour not in the palette: the next capture then reads the screen twice
to add its colours, or sends raw when there are more than 256.
The screen is now read as one DMA stream (ili9341_read_memory_start /
next / end) so the next row is read while the current one is packed or
sent. Tested on HW V0.4.5.1, pixel exact against raw on normal, 4 trace,
waterfall, menu and keypad screens:
- capture rle: about 28 KB, 253 ms (was 482 ms raw)
- capture: 345 ms (was 482 ms)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found by an adversarial review of last week's changes.
- disk_ioctl(GET_SECTOR_COUNT): add READ_BL_LEN and C_SIZE_MULT of CSD v1
cards (<= 2 GB) instead of ORing them. USB DISK mode reported half or a
quarter of the card size. Mask the reserved bits of C_SIZE for CSD v2.
- fw_update_ui() is not inlined into browser_open_file(): its locals
enlarged the browser's stack frame from 280 to 416 bytes for every file
type, leaving little headroom on the sweep thread stack.
- Firmware update: the image trailer is a C constant built from the
fw_update.h values, the linker script only places it; the RAM range check
uses the linker's RAM symbols; SD_SPI_SPEED and SD_INIT_SPI_SPEED move to
spi.h and are shared with the flasher, with a build check that the LCD
uses the same speed; the linker script checks that the flasher's write
limit (SAVE_CONFIG_ADDR) is the end of flash0.
STORAGE -> UPDATE FIRMWARE installs a firmware .bin from the SD card.
The shell command "fwupdate {file}" checks a file, "fwupdate {file} flash"
installs it. Config, calibration and presets (0x0803C000 and up) are never
touched; ROM DFU (jog held at power-on) stays the recovery.
- Image format: STM32F303xC.ld inlines the code rules to place a header
(magic, model, size, version) directly after the vector table and a
trailer (end magic, size, CRC field left unset) at the end of the image.
Only images with the header are accepted.
- Before anything is erased, the running firmware reads the whole file with
FatFs (page CRCs, image CRC, card sector map from FIL.clust) and reads it
again with the raw sector reader used by the update (dry run).
- The flasher runs from CCM RAM with interrupts off and VTOR in CCM. It keeps
page 0 in RAM, erases page 0 first and programs it last after a full image
CRC check, skips unchanged pages, retries errors without user input and
initializes the SD card again after it was removed or keeps failing.
It draws a progress bar and resets only after jog is released, because
jog pressed at reset starts the ROM bootloader.
The tinySA ULTRA now enumerates as a composite device: the CDC serial
port plus a mass storage interface (Bulk-Only Transport, SCSI). The SD
card is only presented to the PC in USB DISK mode (STORAGE menu or shell
command 'usbdisk on|off'), which stops sweeping and screen updates so the
mass storage thread owns SPI1 and spi_buffer. Outside the mode the PC
sees an empty card reader, so the serial port never re-enumerates. The
mode ends on touch, jog button, eject from the PC, card removal or
'usbdisk off'.
- usb_msc.c/h: mass storage thread, BOT state machine, SCSI commands
- usbcfg.c: composite descriptors (IAD, bcdDevice 0x0201), bulk EP3,
requests hook, packet memory reuse on a repeated SET_CONFIGURATION and
data toggle reset on CLEAR_FEATURE (both specific to this ChibiOS fork)
- ui.c: USB DISK menu item and mode screen
- main.c: 'usbdisk' command; shell commands that could use SPI1 are
refused while the mode is active
F072 builds are unchanged. Tested on HW V0.5.4 with Windows 11: about
620 KB/s read and 370 KB/s write, data verified against FatFs reads.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- SD_SendCmd(): send STOP_TRANSMISSION without waiting for not busy and
skip its stuff byte, as in ChaN's reference driver. During a multiple
block read the card sends data, which can contain no 0xFF at all, so
CMD12 was not sent and the card kept streaming
- Wait up to 200 ms for the read data token, SDHC/SDXC read access time
can be up to 100 ms and 50 ms made reads fail
- disk_read(): convert the sector to a byte address only once for byte
addressed (<= 2 GB) cards
- disk_read()/disk_write(): reject a zero sector count
- disk_ioctl(): GET_SECTOR_COUNT available without FF_USE_MKFS
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>