NanoVNA-H and NanoVNA-H4 are very tiny handheld Vector Network Analyzers (VNA). They are standalone portable devices with LCD and battery.
NanoVNA-X is maintained by @momentics (https://github.com/momentics/) and optimised for the memory-constrained STM32F072 and STM32F303 platforms. While the project was initially bootstrapped from the public NanoVNA firmware, the current codebase is a purpose-built architecture focused on responsiveness, deterministic sweeps, and clean subsystem boundaries. The firmware remains GPLv3 so that every contributor, past and present, keeps their rights.
This repository contains the source code of the improved NanoVNA-H and NanoVNA-H4 firmware used in the NanoVNA-X project. The documentation describes the build and flash process on a macOS or a Linux (Debian or Ubuntu) system, other Linux (or even BSD) systems may behave similar.
- Respect to @edy555 and @DiSlord for open-sourcing the original NanoVNA firmware; their work made a community-grade VNA possible in the first place.
- NanoVNA-X has since been re-architected and the sweep/UI subsystems now share very little code with the legacy tree; every release deliberately moves further away from the blocking design.
- The goal is to keep the familiar UX while delivering a maintainable, modular implementation with first-class SD features (S‑parameter capture, screenshots, firmware dumps, scripted commands, on-device formatting).
- Layered runtime instead of a monolithic
main.c. The sweep thread, CLI, and services are wired together insrc/runtime/via explicit ports, so control flow is inspectable and unit-testable rather than hidden insideapp_main. - Zero-copy sweep pipeline. RF orchestration now revolves around
sweep_service_snapshot_tsnapshots and ameasurement_enginecoordinator, letting the UI, USB CLI, and SD-card jobs consume captured buffers without re-triggering hardware sweeps. - Infrastructure services in
infra/. The event bus, cooperative scheduler, configuration/calibration persistence, and autosave-aware state manager replace the hard-coded globals used in v0.9.1, making background work predictable and recoverable. - Clean interfaces. CLI, USB transport, UI hooks, and DSP helpers sit behind
include/interfaces/*contracts, removing the legacy#include "*.c"patterns and making it obvious where transport-agnostic logic lives. - UI/input rework. presenters, controllers, input adapters, fonts, and icons live under
src/ui/, enabling SD-card browsing, remote desktop, and better on-device messaging without touching hardware drivers. - Tooling/documentation refresh. the single
VERSIONfile feeds builds and release automation, the Makefile tracks the new folder layout, and both English/Russian docs explain the divergence so downstream forks know what changed relative to DiSlord’s tree. - Predictable, event-driven architecture. The sweep engine, UI, USB CDC shell, and measurement DSP cooperate through the ChibiOS event bus and watchdog-guarded timeouts. A hung codec, synthesiser, or PC host can no longer freeze the instrument mid-calibration.
- Measurement-focused DMA budget. SPI LCD transfers and TLV320 I²S captures use DMA, while the UART console was intentionally moved to an IRQ driver so that DMA channels are always available for RF data paths.
- Unique USB identity by default. Every unit now enumerates with a serial number derived from its MCU unique ID (System -> Device -> MORE -> USB DEVICE UID still allows opting out for legacy workflows).
- Integrated SD workflow. The internal micro-SD slot supports calibration slots, S1P/S2P exports, BMP or compact RLE-TIFF screenshots, firmware dumps, scripted command playback, and a deterministic
FORMAT SDroutine that uses FatFs mkfs parameters aligned with Keysight/R&S service practices.
The firmware was rewritten around a layered ChibiOS stack that emphasises responsiveness, standalone usability, and reliable sweeps on constrained hardware.
The core of the firmware was reworked from blocking calls to a fully asynchronous, event-driven architecture using ChibiOS primitives (Mailboxes, Events, and Semaphores).
- Non-Blocking USB. The USB CDC (serial) stack is now fully asynchronous. The firmware no longer hangs if a host PC connects, disconnects, or stalls during a data transfer. This resolves the most common source of device freezes.
- Timeout-Driven Recovery. Critical subsystems, including the measurement engine and I²C bus, are guarded by timeouts. A stalled operation will no longer lock up the device; instead, the subsystem attempts to recover cleanly.
- RTOS-based Concurrency. Busy-wait loops and polling have been replaced with efficient RTOS-based signaling, reducing CPU load and improving battery life. The measurement thread, UI, and USB stack now cooperate without race conditions or deadlocks.
- Persistent State. A dedicated
infra/state/state_managermodule snapshots sweep limits, UI flags, and calibration slots to flash. Changes are auto-saved after you stop editing (or immediately via Save Config), so editing the sweep on the device or over USB survives a cold boot without hammering flash. - UI/Sweep Sync. The UI and sweep engine are now decoupled. The UI remains responsive even during complex calculations, and on-screen data is always synchronized with the underlying measurement state.
- Targeted Memory Optimization Static RAM consumption has been significantly reduced, especially for the 16 kB STM32F072 (NanoVNA-H) target. This was achieved by tuning buffer sizes, disabling features like trace caching on low-memory models, and moving key buffers to CCM RAM on the STM32F303.
- Strategic DMA Usage The DMA architecture was refined to prioritize measurement stability. DMA is used for the most demanding data paths:
- LCD Interface (SPI) Ensures smooth, high-speed UI and graph rendering.
- Measurement Pipeline (I²S) Guarantees sample delivery from the codec without dropping data.
- To free up DMA channels for these critical tasks, the UART console was intentionally moved to a non-DMA, interrupt-driven driver. This prevents resource conflicts and prioritizes measurement integrity.
Measured on production NanoVNA-H hardware with a 101-point sweep.
| Scenario | Effective throughput |
|---|---|
| 50 kHz–300 MHz span without harmonic retune breaks | 175–178 points per second (variance stems from Si5351 PLL settling time). |
| 50 kHz–2.7 GHz full span with harmonic retunes (50–300 / 300–600 / 600–900 MHz fundamental bands plus harmonic hops) | 223–232 points per second thanks to the pipelined sweep engine and optimised IF bandwidth scheduling. |
STM32F303-based NanoVNA-H4 units benefit from the larger SRAM pool and typically exceed these numbers, but the F072 figures above define the guaranteed baseline for field deployments.
The STM32F303 platform is technically faster than the STM32F072:
- Display SPI frequency: 36 MHz (F303) vs 24 MHz (F072), resulting in 1.5x faster rendering.
- Mathematics (DSP): >10x faster due to the hardware FPU.
However, the "useful work" (calculation and rendering) takes less than 5% of the total sweep time, so the real-world speed difference is small (~9%):
- The main measurement loop (
sweep_orchestrator.c) callssweep_service_wait_for_capture(), which waits for the audio ADC buffer to fill. - Configuration: 192 kHz sampling rate, 48-sample buffer (
nanovna.h). - This generates an interrupt every 0.25 ms.
- To achieve base accuracy (1 kHz bandwidth), the device waits for approximately 4 buffers, resulting in 1.0 ms of pure waiting time per point.
Resulting Cycle Time per Point:
- F072: 1.0 ms (wait) + 0.1 ms (calc/draw) = 1.1 ms/point.
- F303: 1.0 ms (wait) + 0.01 ms (calc/draw) = 1.01 ms/point.
- Difference: ~9% (e.g., 111 ms vs 102 ms for 101 points).
While the speed per point is limited by the physics of the measurement loop, the STM32F303's larger memory allows for 4x more measurement points in a sweep.
- PLL Stabilization The Si5351 synthesizer programming sequence was optimized to reduce frequency overshoot and drift, improving measurement repeatability, especially at the start of a sweep.
- Deterministic USB serial numbers The firmware now enables the USB unique-ID mode at boot, so every unit enumerates with a stable serial number that host software (NanoVNA-App, NanoVNA Saver, etc.) can display without extra configuration.
- Automated Versioning The firmware version is now automatically embedded during the build process from a single
VERSIONfile.
- Improved CI/CD The GitHub Actions workflows have been optimized for faster, more reliable builds with better caching.
- DFU File Generation A Python-based script for creating DfuSe-compatible firmware files has been integrated into the release process.
NanoVNA-X embraces a layered architecture that separates bootstrapping, platform integration, measurement processing and the user interface so the firmware remains maintainable while fitting into the tight flash and RAM budgets of the STM32F072 and STM32F303 families.
- Boot and application runtime. The firmware starts in
src/runtime/main.c, which hands off toruntime_main()so that all higher level wiring lives insrc/runtime/runtime_entry.c. That entry point initialises ChibiOS/RT, configures the USB console and synthesiser drivers, brings up the measurement pipeline, and spins a dedicated sweep thread that coordinates measurements, UI updates and shell commands. - Platform abstraction.
src/platform/platform_hal.cinvokes the registered board driver table fromsrc/platform/boards/so each supported target can publish aPlatformDriversstructure with its initialisation hooks and peripheral descriptors. This keeps the core application agnostic of whether it is running on the NanoVNA-H (STM32F072) or NanoVNA-H4 (STM32F303) hardware variants. - Services layer. Shared infrastructure lives in
src/infra/. The event bus provides a publish/subscribe mechanism with a fixed topic list and optional mailbox-backed delivery so code running in interrupt context can queue work for later dispatch. The scheduler helper wraps ChibiOS thread creation and termination, while the configuration service persists user settings and calibration slots in MCU flash with checksum protection. - Measurement pipeline and DSP.
src/rf/pipeline/measurement_pipeline.cprovides a slim façade that bridges platform drivers and the measurement routines insidesrc/rf/sweep/. It forwards sweep execution to the application layer and reports the current channel mask. Numerical helpers and calibration maths reside insrc/processing/, keeping compute-heavy code isolated from hardware access. - Drivers and middleware. Low-level device interactions are implemented in
src/platform/peripherals/for the LCD, Si5351 synthesiser, TLV320 codec and USB front-end, whilesrc/middleware/houses small integration shims such as thechprintfbinding for ChibiOS streams. ChibiOS itself is vendored underthird_party/and configured through the headers inconfig/and top-levelchconf.h/halconf.h. - User interface layer. The sweep thread initialises the UI toolkit (
src/ui/), processes hardware inputs, refreshes plotting primitives and marks screen regions for redraw. Fonts and icon bitmaps that back the rendering code are stored insrc/ui/resources/.
Complementary headers live in include/, while board support files, linker scripts and
startup code reside in boards/. This structure lets the same measurement and UI engines run
across both memory profiles with only targeted platform overrides.
The event bus (infra/event/event_bus.[ch]) is a small publish/subscribe helper that avoids
dynamic allocation. Call event_bus_init() with a static subscription array, optional
mailbox storage (msg_t buffer plus queue length), and optional queue nodes that back the
mailbox entries. When the mailbox buffers are provided, event_bus_publish() posts messages
through the mailbox so a dedicated worker can call event_bus_dispatch() and fan them out to
listeners. Without a mailbox the bus falls back to immediate, synchronous dispatch. Interrupt
handlers should invoke event_bus_publish_from_isr(), which acquires queue slots in a
lock-aware fashion. The predefined topics (EVENT_SWEEP_STARTED, EVENT_SWEEP_COMPLETED,
EVENT_TOUCH_INPUT, EVENT_STORAGE_UPDATED, EVENT_CONFIGURATION_CHANGED, and
EVENT_USB_COMMAND_PENDING) cover the current coordination needs; adding new topics
requires extending the event_bus_topic_t enum.
The scheduler helper (infra/task/scheduler.[ch]) keeps a fixed pool of four slots that wrap
chThdCreateStatic()/chThdTerminate(). scheduler_start() returns a handle containing the
assigned slot so callers can later stop the worker through scheduler_stop(). It does not
implement time slicing or cooperative yielding; task priorities and timing remain under the
control of ChibiOS.
The primary target MCUs are the STM32F072xB and STM32F303 (NanoVNA-H/H4); the F072 board employs an 8 MHz crystal plus USB, SPI, I²C, and I²S lines for the display, codec, and touch sensor, as described in board.h.
The Si5351A clock generator (or a compatible part) drives the RF synthesizers and also produces reference frequencies for the audio ADC; its driver implements frequency caching, power management, and PLL initialization over I²C.
The TLV320AIC3204 acts as a dual-channel audio ADC/DAC (I²S), with PLL configuration and input routing; the firmware controls it via I²C and streams the working data over SPI/I²S.
Display controllers ILI9341/ST7789 (320×240) and ST7796S (480×320) are selected per board; DMA is supported, along with brightness control (for the H4), inversion, and text shadows to improve readability.
Additional features include an RTC, persistent calibration/configuration in internal flash, USB UID, remote control, and a measurement module (LC matching, cable analysis, and resonance analysis).
The source tree has been reorganised so it is no longer a line-for-line fork of the legacy NanoVNA firmware:
| Path | Purpose |
|---|---|
include/runtime, src/runtime |
Application entry points, feature flags, CLI plumbing, and sweep orchestration. |
include/rf, src/rf |
Sweep orchestration, measurement pipeline, and RF analytics. |
include/processing, src/processing |
DSP kernels and math helpers shared by the measurement and UI layers. |
include/infra, src/infra |
Cross-cutting services (event bus, scheduler, configuration, persistent state). |
include/interfaces, src/interfaces |
Clean ports for the CLI, USB transport, UI hooks, and processing adapters. |
include/ui, src/ui |
Rendering, controllers, input adapters, and UI resources (fonts/icons). |
src/platform, boards/STM32F072/STM32F303 |
Low-level board support code shared with ChibiOS. |
This separation lets you trace dependencies easily (e.g. ui/ depends on infra/state/state_manager.h but not vice versa) and removes duplicated helper tables from multiple files. When porting patches from older firmware trees, map the functionality onto the closest module instead of copying entire source files.
NanoVNA-X uses a standard GNU Make workflow and the Arm GNU GCC toolchain.
See doc/building.md for a fully detailed guide; the
Russian translation is available in doc/building_ru.md.
There are several numbers of great companion PC tools from third-party.
- GoVNA. A robust and secure Go library for controlling NanoVNA vector network analyzers. Features multi-protocol support (V1, V2/LiteVNA), automatic device detection, and optimized for high-performance server-side applications.
- PyVNA. Multi-protocol Python library for NanoVNA V1/V2/LiteVNA, derived from GoVNA for high performance & secure server-side applications. Supports auto-detection, robust device pooling, and comprehensive data handling.
- NanoVNA-App software by OneOfEleven
- 12-Point Calibration Guide (English)
- NanoVNA-X menu structure
- NanoVNA-X menu & user workflow reference
- NanoVNA User Guide(ja) by cho45. (en:google translate)
- NanoVNA user group on groups.io.
Hardware design material is disclosed to prevent bad quality clone. Please let me know if you would have your own unit.
- @momentics – NanoVNA-X maintainer and integrator.