All docsStellar LinkRF bench · by Stellar Systems v0.1.0

Getting Started

Hardware Setup

The supported boards, booting from SD, reaching the board on the network, resolving it by identity, and cabling the RF path safely.

SatLink runs on a Zynq-7020 (xc7z020clg400-1) board carrying an AD9361 transceiver. This page covers the boards, how the board boots, how to reach it, and how to cable its RF ports.

Two boards, two pinouts#

BoardRoleConstraint file
LibreSDR / zynqsdr rev5The SatLink product PCBpl/top/constraints/zynqsdr_rev5.xdc
"fishball" (a Pluto-class clone, fishball7020)The development board all on-board validation has been done onpl/top/constraints/zynqsdr_fishball.xdc

Both use the same Zynq package, but the AD9361 is wired to different pins. A bitstream built with one board's constraints does not work on the other: on the fishball, a rev5 bitstream leaves the AD9361 without SPI, the driver reports Unsupported PRODUCT_ID 0x0, and the whole RF and clock chain stalls. Always build with the constraint file of the board you deploy to (see Building the Bitstream).

Booting from the SD card#

The board boots from the SD card, never from QSPI flash, so a bad image can always be recovered by rewriting the card. The firmware is built by the separate stellar-fw repository on top of the official Analog Devices plutosdr-fw v0.39 (Linux 6.1-adi, u-boot, Buildroot), into which it injects the SatLink bitstream and satlinkd.

The SD card carries a single FAT partition labelled SATLINK. u-boot loads the bitstream (system_top.bin), then the kernel, the device tree (devicetree.dtb) and the root filesystem (uramdisk.image.gz).

The root filesystem is a RAM disk. Anything written to /tmp, /usr/bin, /var/lib/satlink or /opt/satlink is lost at the next reboot. The one persistent writable place is /mnt/jffs2, a small (about 900 KB) data partition on the QSPI flash, outside the boot chain. The daemon keeps campaign cursors there; the watchdog keeps its reset log there.

What ships on the board:

PathContent
/usr/bin/satlinkdThe daemon, started at boot by /etc/init.d/S99satlinkd
/etc/satlinkd/satlinkd.tomlIts configuration (see Daemon Configuration)
/opt/satlink/dsl/profiles/Radio profiles (copied from dsl/profiles/ at firmware build time)
/opt/satlink/dsl/scenarios/Scenarios (from dsl/scenarios/, loaded recursively)
/opt/satlink/dsl/campaigns/Campaigns (from dsl/campaigns/)
/var/log/satlinkd.logThe daemon's log (RAM disk)
/mnt/jffs2/Persistent: campaign state, reset-log, heartbeat, stellar.conf overrides

Reaching the board#

The board answers on two network interfaces at once:

InterfaceAddressNotes
USB gadget (RNDIS/ECM over the OTG port)192.168.2.1Point to point; the host side is on 192.168.2.0/24
eth0 (the Zynq's own Ethernet)DHCP, hostname satlinkNeeds a cable; the lease arrives after Starting network: OK, so a lookup issued too early finds nothing

Services on the board:

PortService
8080REST API (/api/v1/...) and WebSockets (/ws/...)
5555ZeroMQ PULL: frames in, relayed to the modem
5556ZeroMQ PUB: frames received by the modem
5557ZeroMQ PUB: telemetry snapshots
9090Prometheus metrics exporter
22SSH (Dropbear), user root, the stock Pluto password analog

Point the CLI at the board with SATLINK_API:

Shell
export SATLINK_API=http://192.168.2.1:8080     # or http://<eth0 address>:8080
satlinkctl status

If the API stops answering, check which interface you are using before suspecting the daemon: the USB gadget can drop off the host's bus while the board keeps serving on eth0. One curl per address separates the two.

Resolve the board by identity, not by address#

An address is a hint. Other Pluto-class boards answer on 192.168.2.1 as well, and a bench can have more than one board on the same network. Reconfiguring the wrong one drives someone else's radio and reports the result as your measurement.

The board's IIO context is named SatLink SDR. List the IIO contexts on the network and pick the one with that name, then check its serial number:

Shell
iio_info -S                              # lists contexts: name, serial, address
pl/top/bringup/resolve-board.sh          # prints the board's IP, or refuses

resolve-board.sh takes the unique context named SatLink SDR, cross-checks the serial pinned in the script, retries while mDNS discovery is still settling, and refuses when two different serials answer to the same name. The same board appearing several times (USB gadget, IPv6 link-local, raw USB) is not ambiguous: it is grouped by serial. Edit the pinned serial when you set up a new board.

The board's own safeguards#

Hardware watchdog#

The firmware arms the Zynq's hardware watchdog at boot (10 s timeout, petted every 5 s). A board that reboots on its own is therefore not necessarily a crash: it may be the watchdog recovering a freeze. Before theorising about a reboot, read the reset log:

Shell
ssh root@192.168.2.1 cat /mnt/jffs2/reset-log

It records the SoC's reset reason at each boot (bit 16 = watchdog, bit 22 = power-on, bit 19 = software reboot). /mnt/jffs2/heartbeat holds what the previous boot knew about a minute before it stopped (uptime, load, whether satlinkd was alive, DMA interrupt counts).

To keep a hang visible on the serial console instead of being reset silently, write WATCHDOG=off into /mnt/jffs2/stellar.conf. The setting survives reboots.

The clock#

The board has no real-time clock. At power-on its clock reads 1 January 1970, and every timestamp the board emits counts from boot. The firmware can persist the time across reboots (S26clock) and query NTP when NTP_SERVERS is set, but it must be seeded once, for example with date -s or ntpd -q against a reachable server. Until then, treat every board timestamp as uptime. See Telemetry and Metrics.

Cabling the RF path#

The modem uses TX1 and RX1 of the AD9361, on input port A (A_BALANCED). The device tree locks the RX port selection; ports B and C carried no signal on the reference bench. The second channel (TX2/RX2) is not used by the modem.

The reference bench cabling is:

text
board TX1 --> 40 dB pad --> 2-way splitter --+--> spectrum analyser
                                             +--> board RX1

With both splitter arms terminated, the measured loss from TX1 to either arm was about 44 dB at 437 MHz (40 dB of pad plus the splitter). The board's own output was calibrated as P_board ≈ att + 15.8 dBm, where att is the AD9361 TX attenuation (0 to −89.75 dB). With TX attenuation between −30 and −10 dB, RX1 then sees roughly −58 to −38 dBm, far from the input's limit.

Useful rules when cabling:

  • Characterise, do not assume. A pad's marking and a splitter's datasheet do not add up to the real loss, and the loss is frequency dependent. Re-measure whenever a connector changes (see Measurement Tools).
  • A spectrum analyser's reference offset is additive. A pad of N dB is compensated with +N dB, never −N.
  • Match the receive gain to the level at RX1. The profile's radio.rx_gain_db must bring the signal into the PL's AGC range. If the telemetry shows agc_gain pinned near 16.0 while traffic flows, the receiver is starved (see Telemetry and Metrics).

The RF kill switch#

POST /api/v1/radio/rf/kill (also a button in the web console header) takes RF off the connector: it puts the AD9361's state machine in alert, closes the TX egress, stops the TX chain and disarms RX, reading each step back. TX attenuation alone does not remove RF: at −89.75 dB the part still emits, and an idle DAC still leaks the LO.

To come back, run satlinkctl chain up (see Chain Conditioning). Note that a profile apply re-opens the TX egress: if you engaged the kill switch deliberately for cabling work, engage it again after any apply.

Optional bench equipment#

EquipmentUsed for
A switchable USB hub (for example a YKUSH)Cutting the board's power from a script, so a campaign can recover from a freeze. Some freezes need all ports cut, not just the board's
A JTAG probeLoading debug bitstreams and reading ILA captures (see Building the Bitstream)
A spectrum analyserCharacterising the RF path and checking the transmitted spectrum
A second SDR (for example a bladeRF)An independent reference transmitter or receiver, to tell a transmitter defect from a receiver defect

Stellar Link · v0.1.0

↑↓ to moveEnter to open