Compare commits

...

3 Commits

Author SHA1 Message Date
6314313e1f Migrate to warehack.ing deployment convention
Single compose file with profiles, standardized Makefile targets,
.dockerignore for lean builds, production domain mcnanovna.warehack.ing.
2026-03-06 18:19:48 -07:00
5edac3007c Add Diátaxis documentation for multi-VNA features
New pages covering all 4 quadrants:
- Tutorial: tutorials/multi-vna-basics.mdx
- How-To: hardware/trigger-wiring.mdx, hardware/external-clock.mdx
- Explanation: mcnanovna/multi-vna.mdx

Updated sidebar navigation and cross-links in overview, tools,
and quickstart pages.
2026-03-06 18:19:48 -07:00
0e5553c661 Make docs MCP-client agnostic
Replace Claude-specific terminology with generic language:
- "Ask Claude:" → "Say:"
- "Claude will:" → "Your assistant will:"
- "Claude uses:" → "Calls:" / "This uses:"
- Architecture diagrams: "Claude Code" → "MCP Client"
- Installation tabs: "Claude Code" → "MCP Client" (with Claude Code as example)
- Prerequisites: list multiple MCP clients (Claude Code, Cursor, Zed)

Docs now work for any MCP-compatible client or custom implementation.
2026-03-06 18:19:48 -07:00
21 changed files with 1260 additions and 165 deletions

5
.dockerignore Normal file
View File

@ -0,0 +1,5 @@
node_modules
dist
.git
.env
*.md

View File

@ -1,8 +1,3 @@
# Project identifier (prevents container name collisions)
COMPOSE_PROJECT=mcnanovna-docs
# Domain for caddy-docker-proxy
DOMAIN=mcnanovna.l.zmesh.systems
# Mode: prod or dev (used by Makefile to select compose files)
DOMAIN=mcnanovna.warehack.ing
MODE=prod

View File

@ -1,77 +1,45 @@
# mcnanovna docs - Makefile for docker compose management
# Usage:
# make up - Start production (static site)
# make dev - Start development (hot-reload)
# make down - Stop containers
# make logs - Follow container logs
# make build - Rebuild container images
# make shell - Open shell in running container
.PHONY: prod dev down logs build rebuild shell clean help
.PHONY: up down logs build rebuild shell dev clean help
# Load environment variables
include .env
export
# Compose command shortcuts
COMPOSE := docker compose
COMPOSE_DEV := docker compose -f docker-compose.yml -f docker-compose.dev.yml
# Default target
help:
@echo "mcnanovna docs - Astro/Starlight documentation site"
@echo "mcnanovna docs"
@echo ""
@echo "Usage:"
@echo " make up Start production (static Caddy server)"
@echo " make dev Start development (Vite hot-reload)"
@echo " make down Stop all containers"
@echo " make logs Follow container logs"
@echo " make build Build production image"
@echo " make prod Production (static Caddy)"
@echo " make dev Development (Vite hot-reload)"
@echo " make down Stop containers"
@echo " make logs Follow logs"
@echo " make build Build image"
@echo " make rebuild Force rebuild (no cache)"
@echo " make shell Open shell in running container"
@echo " make shell Shell into running container"
@echo " make clean Remove containers, images, volumes"
@echo ""
@echo "Environment:"
@echo " DOMAIN = $(DOMAIN)"
@echo " MODE = $(MODE)"
# Production mode
up: .env
$(COMPOSE) up -d
$(COMPOSE) logs -f
prod:
docker compose up -d --build
# Development mode with hot-reload
dev: .env
$(COMPOSE_DEV) up -d --build
$(COMPOSE_DEV) logs -f
dev:
docker compose --profile dev up --build
# Stop containers
down:
$(COMPOSE) down
$(COMPOSE_DEV) down 2>/dev/null || true
docker compose --profile dev down
# View logs
logs:
$(COMPOSE) logs -f
docker compose logs -f
# Build image
build:
$(COMPOSE) build
docker compose build
# Rebuild without cache
rebuild:
$(COMPOSE) build --no-cache
docker compose build --no-cache
# Shell into running container
shell:
$(COMPOSE) exec docs sh
docker compose exec docs sh
# Clean everything
clean:
$(COMPOSE) down -v --rmi local
$(COMPOSE_DEV) down -v --rmi local 2>/dev/null || true
docker compose --profile dev down -v --rmi local
# Create .env from example if missing
.env:
@echo "Creating .env from .env.example..."
@cp .env.example .env

View File

@ -2,7 +2,7 @@ import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
export default defineConfig({
site: 'https://mcnanovna.l.zmesh.systems',
site: 'https://mcnanovna.warehack.ing',
telemetry: false,
devToolbar: { enabled: false },
integrations: [
@ -15,7 +15,7 @@ export default defineConfig({
replacesTitle: false,
},
social: {
github: 'https://git.supported.systems/rf/mcnanovna',
github: 'https://git.supported.systems/warehack.ing/mcnanovna',
},
sidebar: [
{
@ -32,6 +32,7 @@ export default defineConfig({
{ label: 'Overview', slug: 'mcnanovna/overview' },
{ label: 'Tool Reference', slug: 'mcnanovna/tools' },
{ label: 'Prompts', slug: 'mcnanovna/prompts' },
{ label: 'Multi-VNA Coordination', slug: 'mcnanovna/multi-vna' },
{ label: 'Web UI', slug: 'mcnanovna/webui' },
],
},
@ -49,6 +50,8 @@ export default defineConfig({
{ label: 'Positioner Build', slug: 'hardware/positioner-build' },
{ label: 'Wiring Diagram', slug: 'hardware/wiring' },
{ label: 'Firmware', slug: 'hardware/firmware' },
{ label: 'VNA Trigger Wiring', slug: 'hardware/trigger-wiring' },
{ label: 'VNA External Clock', slug: 'hardware/external-clock' },
],
},
{
@ -57,6 +60,7 @@ export default defineConfig({
{ label: '3D Pattern Measurement', slug: 'tutorials/pattern-measurement' },
{ label: 'VNA Calibration', slug: 'tutorials/calibration' },
{ label: 'Antenna Analysis', slug: 'tutorials/antenna-analysis' },
{ label: 'Multi-VNA Basics', slug: 'tutorials/multi-vna-basics' },
],
},
{

View File

@ -1,27 +0,0 @@
# Development overrides - hot-reload with volume mounts
# Usage: docker compose -f docker-compose.yml -f docker-compose.dev.yml up
services:
docs:
build:
target: dev
volumes:
- .:/app
- /app/node_modules # Anonymous volume to preserve node_modules
environment:
- NODE_ENV=development
- ASTRO_TELEMETRY_DISABLED=1
- VITE_HMR_HOST=${DOMAIN:-mcnanovna.l.zmesh.systems}
labels:
# Override reverse proxy to Vite dev server port
caddy.reverse_proxy: "{{upstreams 4321}}"
# WebSocket support for Vite HMR
caddy.reverse_proxy.flush_interval: "-1"
caddy.reverse_proxy.transport: "http"
caddy.reverse_proxy.transport.read_timeout: "0"
caddy.reverse_proxy.transport.write_timeout: "0"
caddy.reverse_proxy.transport.keepalive: "5m"
caddy.reverse_proxy.transport.keepalive_idle_conns: "10"
caddy.reverse_proxy.stream_timeout: "24h"
caddy.reverse_proxy.stream_close_delay: "5s"

View File

@ -10,10 +10,38 @@ services:
environment:
- ASTRO_TELEMETRY_DISABLED=1
labels:
# caddy-docker-proxy configuration
caddy: ${DOMAIN:-mcnanovna.l.zmesh.systems}
caddy: ${DOMAIN:-mcnanovna.warehack.ing}
caddy.reverse_proxy: "{{upstreams 80}}"
docs-dev:
build:
context: .
target: dev
container_name: ${COMPOSE_PROJECT:-mcnanovna-docs}-dev
restart: unless-stopped
profiles:
- dev
networks:
- caddy
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- ASTRO_TELEMETRY_DISABLED=1
- VITE_HMR_HOST=${DOMAIN:-mcnanovna.l.warehack.ing}
labels:
caddy: ${DOMAIN:-mcnanovna.l.warehack.ing}
caddy.reverse_proxy: "{{upstreams 4321}}"
caddy.reverse_proxy.flush_interval: "-1"
caddy.reverse_proxy.transport: "http"
caddy.reverse_proxy.transport.read_timeout: "0"
caddy.reverse_proxy.transport.write_timeout: "0"
caddy.reverse_proxy.transport.keepalive: "5m"
caddy.reverse_proxy.transport.keepalive_idle_conns: "10"
caddy.reverse_proxy.stream_timeout: "24h"
caddy.reverse_proxy.stream_close_delay: "5s"
networks:
caddy:
external: true

View File

@ -10,10 +10,12 @@ import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
### From PyPI (Recommended)
<Tabs>
<TabItem label="Claude Code">
<TabItem label="MCP Client">
For Claude Code:
```bash
claude mcp add mcnanovna -- uvx mcnanovna
```
For other MCP clients, configure `uvx mcnanovna` as a server.
</TabItem>
<TabItem label="Standalone">
```bash
@ -53,10 +55,12 @@ MCNANOVNA_WEB_PORT=8080 uv run mcnanovna
### From PyPI
<Tabs>
<TabItem label="Claude Code">
<TabItem label="MCP Client">
For Claude Code:
```bash
claude mcp add mcpositioner -- uvx mcpositioner
```
For other MCP clients, configure `uvx mcpositioner` as a server.
</TabItem>
<TabItem label="Standalone">
```bash
@ -94,15 +98,12 @@ mcpositioner requires the ESP32 positioner hardware. See [Hardware Build](/hardw
## Both Servers Together
For automated 3D antenna pattern measurement, run both servers:
For automated 3D antenna pattern measurement, add both servers to your MCP client:
```bash
# Add both to Claude Code
# Claude Code example
claude mcp add mcnanovna -- uvx mcnanovna
claude mcp add mcpositioner -- uvx mcpositioner
# Restart Claude Code to load both
claude
```
Then use the `measure_antenna_range` prompt to run automated pattern sweeps.

View File

@ -7,26 +7,25 @@ import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components';
## Prerequisites
- Claude Code CLI installed
- An MCP client (Claude Code, Cursor, Zed, or any MCP-compatible app)
- NanoVNA-H connected via USB
- Python 3.11+ (handled automatically by uvx)
## Installation
<Steps>
1. **Add the MCP server to Claude Code**
1. **Add the MCP server to your client**
For Claude Code:
```bash
claude mcp add mcnanovna -- uvx mcnanovna
```
2. **Start a new Claude Code session**
For other clients, configure `uvx mcnanovna` as an MCP server per your client's documentation.
```bash
claude
```
2. **Start a new session**
3. **Ask Claude to use your VNA**
3. **Ask your assistant to use your VNA**
Try these prompts:
- "Scan my antenna from 144 to 148 MHz"
@ -42,7 +41,7 @@ The VNA auto-connects on first tool call. No configuration needed if using defau
## Verify Connection
Ask Claude: "Get VNA info"
Try: *"Get VNA info"*
You should see device details like firmware version, serial number, and frequency range.
@ -50,7 +49,8 @@ You should see device details like firmware version, serial number, and frequenc
- [Install mcpositioner](/mcpositioner/overview/) for automated antenna measurements
- [Run a calibration](/tutorials/calibration/) for accurate measurements
- [Explore all 78 tools](/mcnanovna/tools/)
- [Explore all 91 tools](/mcnanovna/tools/)
- [Multi-VNA measurements](/tutorials/multi-vna-basics/) — Use two VNAs together
## Troubleshooting

View File

@ -0,0 +1,264 @@
---
title: VNA External Clock Modification
description: Share a clock reference for Tier 3 phase-coherent measurements
---
import { Steps, Tabs, TabItem, Aside, Card, CardGrid } from '@astrojs/starlight/components';
Phase-coherent operation (Tier 3) locks all VNAs to a shared 26 MHz reference clock, achieving ±1° phase alignment. This is essential for antenna arrays, MIMO testing, and any measurement where relative phase matters.
<Aside type="caution">
This modification requires PCB surgery—cutting traces and soldering to IC pins. It voids your warranty and risks damaging the VNA if done incorrectly. Only proceed if you're comfortable with fine-pitch SMD work.
</Aside>
## Prerequisites
Complete [hardware trigger wiring (Tier 2)](/hardware/trigger-wiring/) first. Phase coherent operation requires both:
1. Shared clock reference (this modification)
2. Hardware trigger synchronization (Tier 2)
## When You Need This
<CardGrid stagger>
<Card title="Antenna Arrays" icon="rocket">
Phased arrays require known phase relationships between elements. Software or trigger sync alone can't provide phase information.
</Card>
<Card title="MIMO Systems" icon="laptop">
Multiple-input-multiple-output antenna characterization needs coherent phase measurement across all paths.
</Card>
<Card title="Phase Noise Measurement" icon="star">
Comparing oscillator phase noise requires a more stable reference than the internal crystal.
</Card>
<Card title="Beamforming Verification" icon="seti:satellite">
Verify beam patterns and null depths on multi-element antennas.
</Card>
</CardGrid>
## Clock Reference Options
| Source | Stability | Cost | Best For |
|--------|-----------|------|----------|
| **OCXO** (oven crystal) | ~1 ppb | $50-200 | Bench work, highest stability |
| **TCXO** | ~1 ppm | $10-30 | Portable, adequate for most |
| **GPS-DO** (GPS disciplined) | ~0.01 ppb | $100-300 | Absolute accuracy, traceable |
| **Single VNA CLK2** | Device-dependent | $0 | Quick hack, tap one VNA's Si5351 |
<Aside type="tip">
For most amateur work, a TCXO is sufficient and much cheaper than an OCXO. The key is that all VNAs share the *same* reference—absolute stability matters less than matching.
</Aside>
## Parts Needed
- 26 MHz reference oscillator (OCXO, TCXO, or GPS-DO module)
- Clock distribution splitter (for 3+ VNAs)
- 100nF DC blocking capacitors (0402 or 0603)
- SMA connectors and coax
- Fine soldering equipment (hot air recommended)
## Modification Steps
<Steps>
1. **Open the enclosure**
Remove the four screws and carefully separate the front panel from the PCB. Note the ribbon cable to the LCD.
2. **Locate the Si5351**
The Si5351 clock generator IC is typically near the MCU. It's a small QFN package marked "Si5351A" or similar.
```
┌──────────────────────────────────────┐
│ │
│ ┌─────┐ │
│ │Si5351│◄── Clock generator │
│ └─────┘ │
│ │ │
│ [Y1]◄── 26 MHz crystal │
│ │
└──────────────────────────────────────┘
```
3. **Cut the crystal trace**
The Si5351 CLKIN pin (pin 1) connects to the on-board 26 MHz crystal. Cut this trace to disconnect the internal crystal:
- Use a sharp blade or micro drill
- Cut between the crystal and pin 1
- Verify with multimeter (no continuity)
<Aside type="caution">
This is the point of no return. Once cut, the VNA won't work without an external clock.
</Aside>
4. **Add DC blocking capacitor**
Solder a 100nF capacitor in series with the external clock input:
```
External ┌───┐
Clock ─────►│100n├────► Si5351 CLKIN (pin 1)
└───┘
```
This blocks any DC offset from your reference source.
5. **Add external clock connection**
Options for bringing the clock signal in:
- **SMA connector**: Drill the enclosure, mount an SMA
- **Wire**: Run a thin coax through an existing opening
- **Test point**: Solder to an accessible pad
Keep the clock path short (< 5cm) to minimize pickup.
6. **Reassemble and test**
- Reconnect the LCD ribbon cable
- Reassemble enclosure
- Apply external 26 MHz reference
- Verify VNA powers up and runs
</Steps>
## Clock Distribution
For multiple VNAs, distribute the reference clock:
```
┌──────────────────────┐
│ 26 MHz Reference │
│ (OCXO or GPS-DO) │
└──────────┬───────────┘
┌────────────────┼────────────────┐
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ VNA #1 │ │ VNA #2 │ │ VNA #N │
│ (Leader) │ │(Follower) │ │(Follower) │
│ │ │ │ │ │
│ CLK_IN ←──│────│── CLK ────│────│── CLK │
│ TRIG_OUT ─│────│→ TRIG_IN │ │→ TRIG_IN │
└───────────┘ └───────────┘ └───────────┘
```
### Distribution options
| Method | VNAs | Notes |
|--------|------|-------|
| **Direct** | 2 | Split with 50Ω resistors |
| **Clock buffer IC** | 2-8 | LMK00101, CDCE913, etc. |
| **RF splitter** | Any | Power divider, adds insertion loss |
**Important**: Use equal-length cables to all VNAs for matched phase.
## Firmware Commands
Custom firmware adds these clock commands:
| Command | Description |
|---------|-------------|
| `clk_ref {internal\|external}` | Select clock source |
| `clk_status` | PLL lock status, phase offset |
### Switching to external clock
```
raw_command(command="clk_ref external")
```
The Si5351 PLLs need ~100ms to lock after switching. Check lock status:
```
raw_command(command="clk_status")
# Output: locked=true, source=external
```
## Using phase_coherent_sweep
Once all VNAs have external clock and trigger wiring:
Say: "Run a phase-coherent sweep on all devices from 430 to 440 MHz"
The assistant calls:
```
phase_coherent_sweep(
device_ids=["0001234567", "0009876543", "0005555555"],
start_hz=430000000,
stop_hz=440000000,
points=101
)
```
This automatically:
1. Switches all devices to external clock
2. Waits for PLL lock (~100ms)
3. Configures trigger (leader + followers)
4. Runs hardware-synced sweep
5. Restores internal clock
## Validation
After completing the modification, verify phase coherence:
<Steps>
1. **Connect a known reference**
Use a power splitter to feed the same signal to S11 on all VNAs.
2. **Measure phase**
Compare S11 phase readings across VNAs at the same frequency. With phase coherence, they should match within ±1°.
3. **Sweep and compare**
Run multiple sweeps. Phase should be stable across sweeps—if it drifts, check clock distribution.
</Steps>
## Troubleshooting
### PLL won't lock
1. Verify 26 MHz signal at CLKIN (oscilloscope)
2. Check signal level: Si5351 expects 0.6-1.6 Vpp
3. Ensure DC blocking cap is present and not shorted
4. Try different clock source
### Phase drifts over time
1. Allow OCXO to warm up (15-30 minutes)
2. Check cable connections for intermittent contact
3. Verify clock splitter isn't introducing phase shift
4. Avoid temperature changes during measurement
### VNA won't start without external clock
1. Reconnect internal crystal (unsolder cut)
2. Use a backup clock source
3. Consider adding a switch for internal/external
### Phase varies with frequency
1. Expected: cables have frequency-dependent phase shift
2. Use matched-length clock distribution cables
3. Calibrate VNAs at each frequency band
## Specifications
| Parameter | Value |
|-----------|-------|
| Reference frequency | 26 MHz (must match Si5351 XTAL spec) |
| Input level | 0.6-1.6 Vpp into CLKIN |
| PLL lock time | ~100 ms |
| Phase accuracy | ±1° at 1 GHz |
| Jitter contribution | < 1 ps RMS (typical) |
## Next Steps
- [Multi-VNA Coordination](/mcnanovna/multi-vna/) — Understand all three tiers
- [Hardware Trigger Wiring](/hardware/trigger-wiring/) — Required for Tier 3
- [Tool Reference](/mcnanovna/tools/) — Phase coherent tools

View File

@ -0,0 +1,232 @@
---
title: VNA Hardware Trigger Wiring
description: Wire multiple NanoVNAs for Tier 2 synchronized measurements
---
import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components';
Hardware trigger synchronization (Tier 2) achieves ±10-50µs timing between VNAs—100x tighter than software coordination. This guide shows you how to wire the trigger lines between devices.
## When You Need This
| Use Case | Tier 1 (Software) | Tier 2 (Hardware Trigger) |
|----------|-------------------|---------------------------|
| Antenna comparison | ✓ | ✓ |
| SWR/impedance | ✓ | ✓ |
| Signal level measurements | ✓ | ✓ |
| Transmission path timing | Limited | ✓ |
| Differential S21 | Limited | ✓ |
| Multi-VNA transfer functions | ✗ | ✓ |
For phase-aligned measurements, you'll also need [external clock (Tier 3)](/hardware/external-clock/).
## Parts Needed
- Jumper wires (Dupont or similar)
- Soldering iron and solder (fine tip recommended)
- NanoVNA-H with custom firmware supporting trigger commands
<Aside type="caution">
This modification requires soldering to small test points. If you're not comfortable with fine soldering, consider having an experienced friend help.
</Aside>
## Pin Locations
The trigger system uses two GPIO pins on the STM32F303:
| Pin | Function | Location | Description |
|-----|----------|----------|-------------|
| **PA0** | TRIG_IN | See board diagram | External trigger input (rising edge) |
| **PB14** | TRIG_OUT | See board diagram | Pulse output at sweep start |
### NanoVNA-H Rev 3.4 Board
```
┌─────────────────────────────────────────┐
│ │
│ ┌───────────────────────┐ │
│ │ │ │
│ │ LCD │ │
│ │ │ │
│ └───────────────────────┘ │
│ │
│ │
│ [PA0]● ●[PB14] │
│ TRIG_IN TRIG_OUT │
│ │
│ ┌──┐ ┌──┐ │
│ │P1│ │P2│ │
│ └──┘ └──┘ │
│ Port 1 Port 2 │
└─────────────────────────────────────────┘
```
<Aside type="note">
Pin locations vary by board revision. On some boards, these pins are on test points. On others, you may need to solder directly to the MCU. Consult your board's schematic.
</Aside>
## Wiring Steps
<Steps>
1. **Identify the leader VNA**
Choose one VNA as the "leader"—it will generate the trigger pulse that starts all sweeps. The others are "followers."
2. **Solder wires to pins**
On the **leader VNA**:
- Solder a wire to **PB14** (TRIG_OUT)
On each **follower VNA**:
- Solder a wire to **PA0** (TRIG_IN)
Use thin wire (30 AWG or similar) to avoid stressing the pads.
3. **Connect the trigger bus**
Wire all followers' TRIG_IN lines to the leader's TRIG_OUT:
```
Leader VNA Follower VNA #1
┌─────────┐ ┌─────────┐
│ PB14 │──────────────────│ PA0 │
│(TRIG_OUT)│ │(TRIG_IN) │
└─────────┘ │ └─────────┘
│ Follower VNA #2
│ ┌─────────┐
└────────│ PA0 │
│(TRIG_IN) │
└─────────┘
```
For more than 2 followers, daisy-chain or use a splitter.
4. **Verify ground reference**
All VNAs share ground via USB (same host computer). No additional ground wire needed.
If using VNAs on different computers, add a common ground wire.
5. **Test trigger signaling**
Use a multimeter or oscilloscope to verify:
- PB14 can be toggled (use `raw_command("trig_out pulse")`)
- PA0 sees the pulse on followers
</Steps>
## Firmware Requirements
Stock NanoVNA firmware doesn't include trigger commands. You need custom firmware with:
| Command | Description |
|---------|-------------|
| `trig_mode {software\|hardware\|leader\|follower}` | Set trigger mode |
| `trig_out {on\|off\|pulse}` | Manual trigger control |
| `trig_status` | Read trigger state |
Check capability with `detect_capabilities()`:
```json
{
"device_id": "0001234567",
"capabilities": {
"hardware_trigger": true,
"external_clock": false
},
"tier": 2
}
```
<Aside type="tip">
See the NanoVNA-H community firmware for trigger-enabled builds, or build your own from the ChibiOS-based source.
</Aside>
## Using hardware_sync_sweep
Once wiring is complete and firmware supports triggers:
Say: "Run a hardware-synced sweep from 144 to 148 MHz with device 0001234567 as leader"
The assistant calls:
```
hardware_sync_sweep(
leader_device_id="0001234567",
follower_device_ids=["0009876543"],
start_hz=144000000,
stop_hz=148000000,
points=101
)
```
This automatically:
1. Sets leader to "leader" mode
2. Sets followers to "follower" mode
3. Runs parallel scans (hardware synchronized)
4. Restores "software" mode
## Testing the Trigger Line
### Manual test via raw_command
```
# On leader - generate pulse
raw_command(device_id="0001234567", command="trig_out pulse")
# On follower - check status
raw_command(device_id="0009876543", command="trig_status")
```
Expected output: `trigger_count` should increment.
### Oscilloscope verification
- Pulse width: ~10µs
- Rising edge: < 1µs
- Voltage: 3.3V logic level
## Troubleshooting
### Trigger not detected
1. Verify wiring continuity with multimeter
2. Check firmware has `trig_mode` command: `raw_command("help")`
3. Ensure ground is shared between VNAs
4. Try shorter trigger wire (< 30cm recommended)
### Erratic triggering
1. Add 100Ω series resistor on TRIG_OUT to reduce ringing
2. Keep trigger wire away from RF paths
3. Use shielded wire for long runs (> 30cm)
4. Check for EMI from nearby equipment
### Sweeps still not synchronized
1. Verify both VNAs have same sweep settings (start, stop, points)
2. Check trigger mode is correctly set before sweep
3. Ensure leader starts sweep before followers timeout
### Capability shows false
1. Firmware may not support triggers
2. Flash custom firmware with trigger support
3. Run `detect_capabilities()` after firmware update
## Electrical Specifications
| Parameter | Value |
|-----------|-------|
| TRIG_OUT pulse width | 10µs |
| TRIG_IN threshold | 1.6V (TTL-compatible) |
| Maximum trigger cable length | 1m (unshielded), 5m (shielded) |
| Edge-to-sweep latency | < 50µs |
## Next Steps
- [External Clock Modification](/hardware/external-clock/) — Add Tier 3 phase coherence
- [Multi-VNA Coordination](/mcnanovna/multi-vna/) — Understand all synchronization tiers
- [Tool Reference](/mcnanovna/tools/) — Hardware trigger tools

View File

@ -26,7 +26,7 @@ import nanoVNA from '../../assets/hardware/NanoVNA-H-2.jpg';
## What is this?
Two MCP servers that let Claude control RF test equipment:
Two MCP servers that let LLMs control RF test equipment:
<CardGrid stagger>
<Card title="mcnanovna" icon="document">
@ -39,7 +39,7 @@ Two MCP servers that let Claude control RF test equipment:
</Card>
<Card title="Cross-Server Workflows" icon="rocket">
Both servers work together for automated 3D antenna pattern measurement.
Claude orchestrates positioning and VNA measurements across the grid.
Your assistant orchestrates positioning and VNA measurements across the grid.
</Card>
<Card title="Web UI" icon="laptop">
Optional Three.js 3D viewer for radiation patterns.
@ -73,12 +73,12 @@ Direct access to professional RF measurement capabilities:
## Quick Install
```bash
# Add both servers to Claude Code
# Add both servers (Claude Code example)
claude mcp add mcnanovna -- uvx mcnanovna
claude mcp add mcpositioner -- uvx mcpositioner
```
Then ask Claude to analyze your antenna, measure a filter, or run a 3D pattern sweep.
Then ask your assistant to analyze your antenna, measure a filter, or run a 3D pattern sweep.
## Hardware
@ -104,7 +104,7 @@ Then ask Claude to analyze your antenna, measure a filter, or run a 3D pattern s
## Example Prompts
Once installed, try asking Claude:
Once installed, try prompts like:
- *"Connect to my NanoVNA and sweep 7.0-7.3 MHz to check my 40m dipole"*
- *"Measure the insertion loss of this BPF from 144-148 MHz"*

View File

@ -0,0 +1,321 @@
---
title: Multi-VNA Coordination
description: Synchronized measurements across multiple NanoVNA devices
---
import { Tabs, TabItem, Aside, Card, CardGrid } from '@astrojs/starlight/components';
mcnanovna can control multiple NanoVNA devices simultaneously, enabling measurements that a single 2-port VNA can't perform. This page explains the three synchronization tiers and when to use each.
## Why Multiple VNAs?
A single NanoVNA provides 2 ports—enough for S11 (reflection) and S21 (transmission) through one device. Multiple VNAs expand your measurement capability:
| VNAs | Ports | Enables |
|------|-------|---------|
| 1 | 2 | S11, S21 (standard) |
| 2 | 4 | True 4-port, A/B comparison, long path loss |
| 3+ | 2N | Phased arrays, MIMO, multi-element antennas |
## The Synchronization Challenge
When measuring across multiple VNAs, timing matters:
- **Amplitude comparison**: ±5ms is fine—signal levels don't change that fast
- **Time-aligned capture**: Need ±10-50µs for transfer functions
- **Phase alignment**: Need shared clock for coherent phase
mcnanovna provides three tiers to match your precision needs.
## The Three Tiers
<CardGrid stagger>
<Card title="Tier 1: Software" icon="laptop">
**±2-5ms timing**
Parallel async execution via software. Works with any firmware, no modifications needed.
Best for: Antenna comparison, SWR checks, signal level surveys
</Card>
<Card title="Tier 2: Hardware Trigger" icon="seti:pipeline">
**±10-50µs timing**
One VNA triggers the others via GPIO. Requires custom firmware and trigger wiring.
Best for: Transfer functions, differential S21, timing-sensitive measurements
</Card>
<Card title="Tier 3: Phase Coherent" icon="rocket">
**±1° phase alignment**
All VNAs share a 26 MHz clock reference plus hardware trigger.
Best for: Antenna arrays, MIMO, beamforming, phase noise comparison
</Card>
</CardGrid>
## Tier Details
<Tabs>
<TabItem label="Tier 1: Software">
### How It Works
mcnanovna sends scan commands to all VNAs in parallel using Python's `asyncio`. The timing depends on USB latency and OS scheduling.
### Requirements
- Multiple VNAs connected via USB
- Stock firmware (any version)
- No hardware modifications
### Limitations
- ±2-5ms timing (good enough for most amplitude measurements)
- No phase relationship between devices
- USB contention can add jitter
### Tools
| Tool | Description |
|------|-------------|
| `sync_sweep` | Parallel sweep across devices |
| `multi_antenna_compare` | Compare S11 metrics |
| `differential_s21` | Two-VNA transmission |
### Example
```
sync_sweep(
device_ids=["0001234567", "0009876543"],
start_hz=144000000,
stop_hz=148000000,
points=101
)
```
</TabItem>
<TabItem label="Tier 2: Hardware Trigger">
### How It Works
One VNA (leader) pulses a GPIO at sweep start. Other VNAs (followers) wait for this trigger before beginning their sweep.
### Requirements
- Custom firmware with trigger commands (`trig_mode`, `trig_out`)
- Physical trigger wire from leader TRIG_OUT to follower TRIG_IN
- See [Hardware Trigger Wiring](/hardware/trigger-wiring/)
### Limitations
- ±10-50µs timing (excellent for amplitude)
- Still no phase coherence (each VNA has its own clock)
- Requires soldering and firmware changes
### Tools
| Tool | Description |
|------|-------------|
| `set_trigger_mode` | Configure leader/follower |
| `hardware_sync_sweep` | Hardware-triggered sweep |
### Example
```
hardware_sync_sweep(
leader_device_id="0001234567",
follower_device_ids=["0009876543"],
start_hz=144000000,
stop_hz=148000000,
points=101
)
```
</TabItem>
<TabItem label="Tier 3: Phase Coherent">
### How It Works
All VNAs receive the same 26 MHz reference clock from an external source (OCXO, GPS-DO). Combined with hardware trigger, sweeps are phase-aligned.
### Requirements
- Tier 2 prerequisites (trigger wiring + firmware)
- External 26 MHz reference oscillator
- PCB modification to inject clock at Si5351
- See [External Clock Modification](/hardware/external-clock/)
### Limitations
- Requires invasive hardware modification
- More expensive (reference oscillator)
- Voids warranty
- Overkill for most measurements
### Tools
| Tool | Description |
|------|-------------|
| `set_clock_reference` | Switch internal/external |
| `phase_coherent_sweep` | Phase-aligned sweep |
### Example
```
phase_coherent_sweep(
device_ids=["0001234567", "0009876543", "0005555555"],
start_hz=430000000,
stop_hz=440000000,
points=101
)
```
</TabItem>
</Tabs>
## Smart Tier Selection
Don't want to think about tiers? Use `coordinated_sweep`:
```
coordinated_sweep(
start_hz=144000000,
stop_hz=148000000,
points=101
)
```
It automatically selects the best method based on detected capabilities:
```
┌─────────────────────┐
│ coordinated_sweep() │
└──────────┬──────────┘
┌───────────────┼───────────────┐
│ │ │
▼ │ ▼
┌─────────────────┐ │ ┌─────────────────┐
│ All have │ │ │ All have │
│ external_clock? │───Yes──┘ │ hardware_trigger?│
└────────┬────────┘ │ └────────┬────────┘
│No │ │Yes
└────────────┐ │ ┌────────┘
▼ ▼ ▼
┌─────────────────┐
│ Use Tier 3 │
│ phase_coherent │
└─────────────────┘
│No
┌─────────────────┐
│ Use Tier 2 │
│hardware_trigger │
└─────────────────┘
│No
┌─────────────────┐
│ Use Tier 1 │
│ software │
└─────────────────┘
```
## Device Management
### Discovery
```
list_devices()
```
Returns all connected VNAs with their capabilities:
```json
{
"devices": [
{
"device_id": "0001234567",
"port": "/dev/ttyACM0",
"board": "NanoVNA-H4",
"connected": true,
"capabilities": {
"hardware_trigger": true,
"external_clock": false
}
}
],
"default": "0001234567"
}
```
### Targeting
**Default device**: When one VNA is connected, it's automatically the default. With multiple, set one:
```
set_default_device(device_id="0001234567")
```
**Explicit targeting**: Override default with `device_id` on any tool:
```
scan(device_id="0009876543", start_hz=144e6, stop_hz=148e6)
```
### Hot-plug Detection
After connecting/disconnecting VNAs:
```
refresh_devices()
```
## Common Workflows
### A/B Antenna Comparison
Connect two antennas to two VNAs, then:
```
multi_antenna_compare(
device_ids=["antenna_a_vna", "antenna_b_vna"],
start_hz=144000000,
stop_hz=148000000
)
```
Returns:
| Metric | Antenna A | Antenna B |
|--------|-----------|-----------|
| Min SWR | 1.15 | 1.32 |
| Resonance | 145.2 MHz | 144.8 MHz |
| Bandwidth | 4.1 MHz | 3.2 MHz |
### Long Cable Path Loss
Measure a cable too long to keep both ends near one VNA:
```
differential_s21(
source_device_id="near_end_vna",
receiver_device_id="far_end_vna",
start_hz=1000000,
stop_hz=500000000
)
```
The source VNA outputs CW while the receiver measures. Returns S21 magnitude at each frequency.
## Guided Prompts
mcnanovna includes prompts for multi-VNA workflows:
| Prompt | Description |
|--------|-------------|
| `multi_device_setup` | Initial device discovery and configuration |
| `hardware_trigger_setup` | Tier 2 wiring and testing guide |
| `phase_coherent_setup` | Tier 3 clock reference setup |
## See Also
- [Multi-VNA Tutorial](/tutorials/multi-vna-basics/) — Hands-on first multi-VNA measurement
- [Hardware Trigger Wiring](/hardware/trigger-wiring/) — Tier 2 physical setup
- [External Clock Modification](/hardware/external-clock/) — Tier 3 clock distribution
- [Tool Reference](/mcnanovna/tools/) — All coordination tools

View File

@ -5,7 +5,7 @@ description: MCP server for NanoVNA-H vector network analyzers
import { Aside } from '@astrojs/starlight/components';
mcnanovna gives Claude direct control of NanoVNA-H vector network analyzers over USB serial. It exposes 78 MCP tools for frequency sweeps, S-parameter measurements, calibration, LCD capture, RF analysis, and 3D antenna radiation pattern visualization.
mcnanovna gives LLMs direct control of NanoVNA-H vector network analyzers over USB serial. It exposes 91 MCP tools for frequency sweeps, S-parameter measurements, calibration, LCD capture, RF analysis, 3D antenna radiation pattern visualization, and multi-VNA coordination.
## Capabilities
@ -34,6 +34,20 @@ mcnanovna gives Claude direct control of NanoVNA-H vector network analyzers over
- Pattern import from CSV, EMCAR, NEC2, Touchstone S1P
- Optional Three.js web viewer
### Multi-VNA Coordination
- Control multiple NanoVNA devices simultaneously
- Three synchronization tiers with increasing precision
- Device targeting via optional `device_id` parameter on all tools
- Automatic sync method selection with `coordinated_sweep`
| Tier | Method | Precision | Requirements |
|------|--------|-----------|--------------|
| 1 | Software | ±2-5ms | None (stock firmware) |
| 2 | Hardware Trigger | ±10-50µs | Custom firmware + trigger wire |
| 3 | Phase Coherent | ±1° | External clock + Tier 2 |
See [Multi-VNA Coordination](/mcnanovna/multi-vna/) for detailed tier comparison and workflow guides.
## Supported Hardware
| Device | Frequency Range | Notes |
@ -49,7 +63,7 @@ Other NanoVNA variants using the same USB serial protocol (VID 0x0483, PID 0x574
```
┌─────────────────────────────────────────────────────────┐
Claude Code
MCP Client
│ │ │
│ MCP Protocol │
│ ▼ │
@ -57,21 +71,23 @@ Other NanoVNA variants using the same USB serial protocol (VID 0x0483, PID 0x574
│ │ mcnanovna server │ │
│ │ ┌─────────┐ ┌────────────┐ ┌─────────────┐ │ │
│ │ │ tools/ │ │ protocol.py│ │calculations │ │ │
│ │ │ 8 mixins│ │ USB serial│ │ S-param │ │ │
│ │ │ 9 mixins│ │ USB serial│ │ S-param │ │ │
│ │ └────┬────┘ └─────┬──────┘ │ math │ │ │
│ │ │ │ └─────────────┘ │ │
│ │ ▼ ▼ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ NanoVNA class │ │ │
│ │ │ (connection lifecycle, auto-reconnect) │ │ │
│ │ │ DeviceRegistry (multi-VNA) │ │ │
│ │ │ ┌────────────┐ ┌────────────┐ │ │ │
│ │ │ │ NanoVNA #1 │ ... │ NanoVNA #N │ │ │ │
│ │ │ └────────────┘ └────────────┘ │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│ │ │
│ USB Serial │
│ ▼ │
┌──────────────────┐
│ NanoVNA-H │
└──────────────────┘
┌──────────────┐ ┌──────────────┐
│ NanoVNA-H #1 │...│ NanoVNA-H #N │
└──────────────┘ └──────────────┘
└─────────────────────────────────────────────────────────┘
```

View File

@ -5,7 +5,7 @@ description: Guided workflow prompts for mcnanovna
import { Aside } from '@astrojs/starlight/components';
Prompts are pre-built conversation templates that guide Claude through multi-step procedures. They set up context and provide step-by-step instructions for common workflows.
Prompts are pre-built conversation templates that guide your assistant through multi-step procedures. They set up context and provide step-by-step instructions for common workflows.
## Available Prompts

View File

@ -1,9 +1,9 @@
---
title: Tool Reference
description: All 78 mcnanovna MCP tools
description: All 91 mcnanovna MCP tools
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
## Measurement Tools
@ -123,6 +123,56 @@ import { Tabs, TabItem } from '@astrojs/starlight/components';
| `import_pattern_s1p` | Import from Touchstone S1P |
| `list_pattern_formats` | List supported import formats |
## Coordination Tools
Manage and synchronize multiple NanoVNA devices simultaneously.
### Device Management
| Tool | Description |
|------|-------------|
| `list_devices` | List all connected VNAs with status and capabilities |
| `refresh_devices` | Re-scan USB ports for hot-plugged devices |
| `set_default_device` | Set default VNA for subsequent calls |
| `clear_default_device` | Clear default, require explicit device_id |
| `detect_capabilities` | Check hardware trigger and clock support |
### Tier 1: Software Synchronization
| Tool | Description |
|------|-------------|
| `sync_sweep` | Software-coordinated parallel sweep (±2-5ms) |
| `multi_antenna_compare` | Compare S11 across multiple antennas |
| `differential_s21` | Two-VNA transmission measurement |
### Tier 2: Hardware Trigger
Requires custom firmware and physical trigger wiring. See [VNA Trigger Wiring](/hardware/trigger-wiring/) for hardware setup.
| Tool | Description |
|------|-------------|
| `set_trigger_mode` | Configure hardware trigger mode |
| `hardware_sync_sweep` | Hardware-triggered sync sweep (±10-50µs) |
### Tier 3: Phase Coherent
Requires external 26 MHz clock reference plus Tier 2. See [VNA External Clock](/hardware/external-clock/) for hardware modification guide.
| Tool | Description |
|------|-------------|
| `set_clock_reference` | Set internal/external clock reference |
| `phase_coherent_sweep` | Phase-coherent sweep with shared clock (±1°) |
### Smart Coordination
| Tool | Description |
|------|-------------|
| `coordinated_sweep` | Auto-selects best available sync method |
<Aside type="tip">
All 78 original tools accept an optional `device_id` parameter to target a specific VNA. Without it, commands go to the default device (or the only connected device).
</Aside>
## Example Usage
<Tabs>
@ -130,30 +180,46 @@ import { Tabs, TabItem } from '@astrojs/starlight/components';
```
User: Scan my antenna from 144 to 148 MHz with 201 points
Claude uses: sweep(144000000, 148000000, 201)
Claude uses: scan(s11=true)
Claude uses: analyze()
Calls: sweep(144000000, 148000000, 201)
Calls: scan(s11=true)
Calls: analyze()
```
</TabItem>
<TabItem label="Filter Analysis">
```
User: Analyze this bandpass filter from 1 to 500 MHz
Claude uses: sweep(1000000, 500000000, 201)
Claude uses: analyze_filter()
Calls: sweep(1000000, 500000000, 201)
Calls: analyze_filter()
```
</TabItem>
<TabItem label="Calibration">
```
User: Calibrate for the 2m band
Claude uses: sweep(144000000, 148000000, 101)
Claude uses: cal("load") # user connects load
Claude uses: cal("open") # user connects open
Claude uses: cal("short") # user connects short
Claude uses: cal("thru") # user connects through
Claude uses: cal("done")
Claude uses: save(0)
Calls: sweep(144000000, 148000000, 101)
Calls: cal("load") # user connects load
Calls: cal("open") # user connects open
Calls: cal("short") # user connects short
Calls: cal("thru") # user connects through
Calls: cal("done")
Calls: save(0)
```
</TabItem>
<TabItem label="Multi-VNA">
```
User: Compare my two 2m antennas simultaneously
Calls: list_devices()
# Returns: [{id: "0001234567", ...}, {id: "0007654321", ...}]
Calls: sync_sweep(
device_ids=["0001234567", "0007654321"],
start_hz=144000000,
stop_hz=148000000,
points=101
)
Calls: multi_antenna_compare()
```
</TabItem>
</Tabs>

View File

@ -5,7 +5,7 @@ description: MCP server for ESP32 dual-axis antenna positioner
import { Aside } from '@astrojs/starlight/components';
mcpositioner gives Claude control of an ESP32-based dual-axis antenna positioner over WiFi. It drives two NEMA 17 stepper motors via TMC2209 drivers for theta (polar, 0-180°) and phi (azimuth, 0-360°) rotation.
mcpositioner gives LLMs control of an ESP32-based dual-axis antenna positioner over WiFi. It drives two NEMA 17 stepper motors via TMC2209 drivers for theta (polar, 0-180°) and phi (azimuth, 0-360°) rotation.
## Purpose
@ -36,7 +36,7 @@ Full build instructions and KiCad schematics are in the [Hardware section](/hard
```
┌─────────────────────────────────────────────────────────┐
Claude Code
MCP Client
│ │ │
│ MCP Protocol │
│ ▼ │

View File

@ -86,12 +86,12 @@ This prompt requires both mcpositioner AND mcnanovna MCP servers to be running.
## Using Prompts
In Claude Code, invoke prompts by name:
Invoke prompts by name:
```
User: Run the measure_pattern_grid prompt for my Yagi on 70cm
Claude: [Uses measure_pattern_grid prompt with antenna_type="yagi", band="70cm"]
[Uses measure_pattern_grid prompt with antenna_type="yagi", band="70cm"]
```
The prompt provides step-by-step guidance, and Claude executes the tools from both mcpositioner and mcnanovna as needed.
The prompt provides step-by-step guidance, and your assistant executes the tools from both mcpositioner and mcnanovna as needed.

View File

@ -11,9 +11,9 @@ This tutorial covers common antenna analysis tasks using mcnanovna.
The most common antenna measurement is SWR (Standing Wave Ratio) and impedance at the feedpoint.
Ask Claude: "Analyze my antenna from 144 to 148 MHz"
Say: "Analyze my antenna from 144 to 148 MHz"
Claude will:
Your assistant will:
1. Run a sweep across the band
2. Find the resonant frequency (minimum SWR)
3. Calculate impedance at resonance
@ -32,17 +32,17 @@ Return loss at resonance: -23.4 dB
For antennas with multiple resonances (like a multi-band antenna):
Ask Claude: "Find all resonances from 1 to 30 MHz"
Say: "Find all resonances from 1 to 30 MHz"
Claude uses `analyze_s11_resonance` to find all points where the antenna is resonant.
This uses `analyze_s11_resonance` to find all points where the antenna is resonant.
## Impedance Matching
If your antenna doesn't match 50Ω, design a matching network:
Ask Claude: "Design a matching network for 35+j25 ohms at 145 MHz"
Say: "Design a matching network for 35+j25 ohms at 145 MHz"
Claude uses `analyze_lc_match` to compute L-network solutions:
This uses `analyze_lc_match` to compute L-network solutions:
```
Solution 1: Series L (27 nH) + Shunt C (18 pF)
Solution 2: Shunt C (12 pF) + Series L (42 nH)
@ -57,7 +57,7 @@ Solution 2: Shunt C (12 pF) + Series L (42 nH)
- Narrow bandwidth
- Figure-8 radiation pattern
Ask Claude: "Analyze my dipole on 20m"
Say: "Analyze my dipole on 20m"
</TabItem>
<TabItem label="Vertical">
**Expected characteristics:**
@ -65,7 +65,7 @@ Solution 2: Shunt C (12 pF) + Series L (42 nH)
- Needs matching network or radials
- Omnidirectional pattern
Ask Claude: "Analyze my vertical on 40m"
Say: "Analyze my vertical on 40m"
</TabItem>
<TabItem label="Yagi">
**Expected characteristics:**
@ -73,7 +73,7 @@ Solution 2: Shunt C (12 pF) + Series L (42 nH)
- Narrow bandwidth
- Directional pattern
Ask Claude: "Analyze my Yagi from 144 to 148 MHz"
Say: "Analyze my Yagi from 144 to 148 MHz"
</TabItem>
<TabItem label="Loop">
**Expected characteristics:**
@ -81,7 +81,7 @@ Solution 2: Shunt C (12 pF) + Series L (42 nH)
- High Q (narrow bandwidth)
- Figure-8 pattern (small loop)
Ask Claude: "Analyze my magnetic loop on 40m"
Say: "Analyze my magnetic loop on 40m"
</TabItem>
</Tabs>
@ -114,7 +114,7 @@ For a dipole, each side is approximately λ/4 in length. At 145 MHz, that's abou
The antenna is resonant but not at 50Ω.
Ask Claude: "Design a matching network for my measured impedance"
Say: "Design a matching network for my measured impedance"
### Narrow bandwidth
@ -127,7 +127,7 @@ High-Q antennas (small loops, loaded verticals) have narrow bandwidth. Options:
For a quick analytical pattern based on antenna type:
Ask Claude: "Show the radiation pattern for my dipole"
Say: "Show the radiation pattern for my dipole"
This uses the S11 data to determine resonance and impedance, then generates an idealized 3D pattern.

View File

@ -34,7 +34,7 @@ An SMA calibration kit includes precision standards. For casual use, a 50Ω term
<Steps>
1. **Set the sweep range**
Ask Claude: "Set sweep from 144 to 148 MHz with 101 points"
Say: "Set sweep from 144 to 148 MHz with 101 points"
Calibration is frequency-specific. Always calibrate at the frequencies you'll measure.
@ -42,43 +42,43 @@ An SMA calibration kit includes precision standards. For casual use, a 50Ω term
Connect the 50Ω load termination to Port 1.
Ask Claude: "Run cal load"
Say: "Run cal load"
3. **Connect the OPEN**
Remove the load, leave Port 1 open (or use an open standard).
Ask Claude: "Run cal open"
Say: "Run cal open"
4. **Connect the SHORT**
Connect the short standard to Port 1.
Ask Claude: "Run cal short"
Say: "Run cal short"
5. **Connect the THROUGH**
Connect Port 1 to Port 2 with a short cable or barrel adapter.
Ask Claude: "Run cal thru"
Say: "Run cal thru"
6. **Optional: Isolation**
Connect 50Ω loads to both ports.
Ask Claude: "Run cal isoln"
Say: "Run cal isoln"
This corrects for crosstalk between ports.
7. **Apply calibration**
Ask Claude: "Run cal done"
Say: "Run cal done"
This computes and applies the error correction coefficients.
8. **Save calibration**
Ask Claude: "Save calibration to slot 0"
Say: "Save calibration to slot 0"
Saves to NanoVNA flash for later recall.
</Steps>
@ -109,7 +109,7 @@ Each slot holds one calibration. Common approach:
## Recalling Calibration
Ask Claude: "Recall calibration from slot 0"
Say: "Recall calibration from slot 0"
The NanoVNA will use the stored calibration. Note that calibration must match the current sweep settings.

View File

@ -0,0 +1,222 @@
---
title: Your First Multi-VNA Measurement
description: Compare antennas using two NanoVNAs simultaneously
---
import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components';
In this tutorial, you'll use two NanoVNA devices together to measure and compare two antennas side-by-side. By the end, you'll understand device discovery, targeting, and software-synchronized sweeps.
## What You'll Learn
- Discover all connected VNAs
- Target specific devices
- Run synchronized sweeps
- Compare antenna performance
## Prerequisites
- Two NanoVNA-H devices connected via USB
- Two antennas to compare (or one antenna with two feedlines)
- mcnanovna MCP server installed
<Aside type="note">
This tutorial uses Tier 1 (software) synchronization, which works with any NanoVNA firmware. No modifications needed.
</Aside>
## Discover Your Devices
<Steps>
1. **Connect both VNAs**
Plug both NanoVNA devices into USB ports. Each needs its own port.
2. **List devices**
Say: "List all connected NanoVNA devices"
The assistant calls `list_devices()` and returns something like:
```json
{
"devices": [
{"device_id": "0001234567", "port": "/dev/ttyACM0", "board": "NanoVNA-H4"},
{"device_id": "0009876543", "port": "/dev/ttyACM1", "board": "NanoVNA-H"}
],
"default": "0001234567",
"count": 2
}
```
3. **Note the device IDs**
Each VNA has a unique `device_id` (its serial number or port path). Write these down:
| Device ID | Connected Antenna |
|-----------|-------------------|
| 0001234567 | Antenna A |
| 0009876543 | Antenna B |
</Steps>
## Set Up Device Targeting
With multiple VNAs, you have two options for targeting:
### Option 1: Set a Default
If you'll mostly use one VNA:
Say: "Set device 0001234567 as the default"
Now all single-device tools (scan, analyze, etc.) will target that VNA unless you specify otherwise.
### Option 2: Specify Each Time
Add `device_id` to any tool call:
Say: "Scan from 144 to 148 MHz on device 0009876543"
<Aside type="tip">
When only one VNA is connected, it becomes the default automatically. Multi-VNA targeting only matters when you have multiple devices.
</Aside>
## Calibrate Each VNA
<Steps>
1. **Calibrate the first VNA**
Say: "Calibrate device 0001234567 for 144 to 148 MHz"
Follow the SOLT calibration steps: load, open, short, through.
Say: "Save calibration to slot 1"
2. **Calibrate the second VNA**
Say: "Calibrate device 0009876543 for 144 to 148 MHz"
Repeat the same procedure with the second VNA's calibration standards.
Say: "Save calibration to slot 1"
</Steps>
<Aside type="caution">
Use the same frequency range for both VNAs. Calibrate each with its own cables and adapters—the ones you'll use for measurement.
</Aside>
## Run a Synchronized Sweep
Now connect your antennas—one to each VNA's Port 1.
<Steps>
1. **Run sync_sweep**
Say: "Run a synchronized sweep on both VNAs from 144 to 148 MHz"
The assistant calls:
```
sync_sweep(
device_ids=["0001234567", "0009876543"],
start_hz=144000000,
stop_hz=148000000,
points=101
)
```
2. **Review the results**
You'll get S11 data from both devices, swept at the same time (within ±2-5ms).
</Steps>
## Compare Antenna Performance
<Steps>
1. **Run multi_antenna_compare**
Say: "Compare my two antennas on the 2m band"
The assistant calls `multi_antenna_compare()` and returns a comparison table:
| Metric | Antenna A (0001234567) | Antenna B (0009876543) |
|--------|------------------------|------------------------|
| Min SWR | 1.15 | 1.32 |
| Resonance | 145.2 MHz | 144.8 MHz |
| Bandwidth (< 2:1) | 4.1 MHz | 3.2 MHz |
| Z at resonance | 48 + j2 Ω | 42 - j8 Ω |
2. **Interpret the results**
- **Min SWR**: Lower is better. Antenna A has better match.
- **Resonance**: Where SWR is lowest. Neither is at band center.
- **Bandwidth**: Wider is more versatile. Antenna A has broader coverage.
- **Z at resonance**: Closer to 50 Ω is better. Both are reasonable.
</Steps>
## Understanding the Results
### What Sync Sweep Gives You
Software synchronization (Tier 1) starts both sweeps within 2-5ms of each other. This is adequate for:
- Antenna comparison
- Signal level measurements
- SWR/impedance analysis
It's **not** precise enough for:
- Phase measurements between devices
- Antenna array characterization
- MIMO testing
For those, you'll need [hardware trigger (Tier 2)](/hardware/trigger-wiring/) or [phase coherent (Tier 3)](/hardware/external-clock/).
### Device Capabilities
Check what sync methods each device supports:
Say: "Check capabilities of device 0001234567"
```json
{
"device_id": "0001234567",
"capabilities": {
"hardware_trigger": false,
"external_clock": false
},
"tier": 1
}
```
Stock firmware shows Tier 1 only. Custom firmware enables Tier 2/3.
## Troubleshooting
### Only one device shows up
1. Check USB connections
2. Try different USB ports (avoid hubs)
3. Run `refresh_devices()` to re-scan
4. Check permissions: `sudo usermod -aG dialout $USER` (Linux)
### Device IDs keep changing
Port paths like `/dev/ttyACM0` can change on reconnect. If available, use the serial number (e.g., `0001234567`) instead—it's stable across reconnects.
### Timing concerns
Tier 1's ±2-5ms is sufficient for most comparisons. If you see inconsistent results, ensure:
- Both VNAs have stable connections
- Neither is being accessed by another program
- USB host isn't under heavy load
## Next Steps
- [Multi-VNA Coordination](/mcnanovna/multi-vna/) — Understand all three synchronization tiers
- [Hardware Trigger Wiring](/hardware/trigger-wiring/) — Achieve ±10-50µs precision
- [Tool Reference](/mcnanovna/tools/) — All coordination tools

View File

@ -11,7 +11,7 @@ This tutorial walks through measuring a complete 3D antenna radiation pattern us
- NanoVNA-H connected via USB
- ESP32 positioner built and on WiFi
- Both MCP servers added to Claude Code:
- Both MCP servers added to your client (e.g., for Claude Code):
```bash
claude mcp add mcnanovna -- uvx mcnanovna
claude mcp add mcpositioner -- uvx mcpositioner
@ -56,27 +56,27 @@ For accurate patterns, the transmit antenna should have a known, broad pattern (
<Steps>
1. **Verify both servers are connected**
Ask Claude: "Check positioner status and VNA info"
Say: "Check positioner status and VNA info"
Claude will call `positioner_status` and `info` to verify both devices respond.
This will call `positioner_status` and `info` to verify both devices respond.
2. **Home the positioner**
Ask Claude: "Home the positioner on both axes"
Say: "Home the positioner on both axes"
This establishes the reference position (theta=0, phi=0).
3. **Calibrate the VNA**
Ask Claude: "Calibrate for the 2m band"
Say: "Calibrate for the 2m band"
Follow the SOLT calibration procedure. This is critical for accurate S21 measurements.
4. **Run the pattern measurement**
Ask Claude: "Measure a 3D radiation pattern for my dipole antenna on 2m"
Say: "Measure a 3D radiation pattern for my dipole antenna on 2m"
Claude uses the `measure_pattern_grid` prompt to guide the measurement:
This uses the `measure_pattern_grid` prompt to guide the measurement:
- Confirms grid resolution (default: 5° theta × 10° phi)
- Moves through each grid point in serpentine order
- Measures S21 at each position
@ -86,7 +86,7 @@ For accurate patterns, the transmit antenna should have a known, broad pattern (
If the mcnanovna web UI is running (`MCNANOVNA_WEB_PORT=8080`), the pattern renders in 3D automatically.
Or ask Claude: "Show the pattern statistics"
Or say: *"Show the pattern statistics"*
</Steps>
## Resolution Tradeoffs