All docsStellar LinkRF bench · by Stellar Systems v0.1.0

Control & Automation

RF Profiles

The satlink.profile/v1 format field by field, what each field does to the hardware, validation rules, applying and pushing profiles, and the shipped profiles.

A profile is a YAML file describing a complete radio configuration: frequency, gains, modulation, symbol rate, framing and coding. Applying it reconfigures the modem and the AD9361 without touching the bitstream.

A complete example#

This is dsl/profiles/rf_loopback.yaml without its comments: QPSK, uncoded, over the real RF path with an external cable loopback.

YAML
version: "satlink.profile/v1"

metadata:
  name: "rf_loopback"
  display_name: "RF loopback (bench, external cable)"
  description: "QPSK uncoded over the real RF path — TX1 -> pad -> splitter -> RX1"
  vendor: "Stellar Systems"
  family: "bring-up"
  revision: "1.0"
  author: "F. du Cray"
  tags: ["bring-up", "loopback", "rf", "test"]

radio:
  rf_port: "RF1"
  duplex: "full"
  center_frequency_hz: 437325000
  tx_enabled: true
  rx_enabled: true
  loopback: false
  tx_power_dbm: -20
  rx_gain_db: 20

phy:
  modulation: "QPSK"
  symbol_rate_baud: 39062.5
  samples_per_symbol: 4
  encoding: "nrz"

framing:
  preamble:
    type: "fill"
    pre_symbols: 512
    post_symbols: 2048
  sync_word:
    value_hex: "1ACFFC1D"
  mtu_bytes: 16
  scrambler:
    enabled: false
  fec:
    type: "none"
  interleaver:
    enabled: false

packet:
  protocol: "raw"
  mtu_bytes: 16

Field reference#

The Effect column says what each field actually does. Fields marked descriptive are parsed, validated for type, stored and displayed, and reach no register: changing them changes nothing on the air.

Top level#

FieldRequiredEffect
versionyesMust start with satlink.profile/ (satlink.profile/v1)
metadatayesSee below
radioyesRF front end
phyyesModulation and symbol rate
framingyesFrames, marker, scrambler, coding, preamble, transfer frames
packetnoDescriptive: packets are built per request by the PS, not configured here
busnoDescriptive: the host link is the daemon's configuration
timingnoDescriptive: nothing enforces turnaround or timeouts
constraintsnoDescriptive: no rate limiter or duty-cycle enforcement exists
observabilitynoDescriptive: the daemon publishes a fixed metric set

metadata#

FieldRequiredEffect
nameyesThe identifier used by apply, scenarios (profile_ref) and campaigns. Profiles are keyed by this name, not by file name
display_name, description, author, tagsnoShown in listings and the web console
vendor, family, revisionnoDescriptive

radio#

FieldTypeEffect
center_frequency_hzinteger, > 0Tunes both AD9361 LOs below this centre and sets both NCOs to bring the signal back onto it (see RF Fundamentals). Also written to a descriptive chan_cfg register
tx_enabledbool, default falseOpens (true) or closes the TX egress (tx.egress.CTRL). With the egress closed the modem discards every block it is given
rx_enabledbool, default trueEnables the RX ingress
loopbackbool, default falseWrites the PL loopback mux (GLOBAL_CTRL[0]) on every apply. true routes TX through the channel emulator into RX; the AD9361 is out of the path
rx_gain_dbnumberSets the AD9361 RX gain in dB and switches its AGC to manual
tx_power_dbmnumberApplied as the AD9361 TX attenuation when ≤ 0 (0 = full drive, down to −89.75). A positive value is not applied and the apply says so: the part cannot produce a positive output power on its own
rf_portstringDescriptive. The modem always uses TX1/RX1
duplexhalf / fullDescriptive. Chain conditioning puts the ENSM in FDD for on-air work

phy#

FieldTypeEffect
modulationBPSK, QPSK, GMSK (OQPSK, MSK, π/4-DQPSK, FSK, GFSK are refused)Selects and enables one modulator and the matching demodulator, sets the deframer's bits per symbol
symbol_rate_baudnumber, one of the ladderSets the DUC interpolation and DDC decimation, sizes the AD9361 analog filters, the LO offset and the carrier loop's sweep bounds
samples_per_symbolintegerMust be 4 or absent
btnumberGMSK only; required for GMSK and must be 0.5
freq_deviation_hzintegerDescriptive (FSK is not implemented)
encodingstringDescriptive; line coding is not implemented

framing#

FieldTypeEffect
mtu_bytesinteger, default 256Framer FRAME_LEN; the deframer's length is derived from it and the code (see Modulation & Coding)
sync_word.value_hex32-bit hexThe ASM, written to the marker inserter and the deframer. Absent means no marker is inserted and the receiver cannot frame
scrambler.enabledboolEnables the CCSDS randomiser at both ends
scrambler.seedinteger8-bit seed, default 0xFF
scrambler.polynomialstringDescriptive; the polynomial is fixed
fec.typenone, conv, rs, rs+convConfigures fec_tx and fec_rx. ldpc, turbo, polar are accepted by the schema but not implemented: the chain is configured uncoded
fec.ratestringRequired when type is not none; informative
preambleobjectPreamble and inter-frame fill: type (fill or pattern), value_hex, pre_symbols, post_symbols. Absent disables the fill (see Modulation & Coding)
interleaverobjectDescriptive; there is no interleaver
transfer_frameobjectCCSDS transfer frames composed by the link service: type (tm, aos, uslp), scid, vcid, fecf (see Modulation & Coding)

What an apply does#

POST /api/v1/profiles/{name}/apply (satlinkctl profile apply <name>):

  1. Validate the profile (rules below). A refusal names the field and the reason.
  2. Translate it into a list of PL register writes (about 55): loopback mux, channel reset to pass-through, DDC, AGC, matched filter, timing and carrier recovery, slicer, demodulators, deframer, FEC, framer, scrambler, marker inserter, fill, modulators, DUC, TX egress, RSSI.
  3. Stage and commit them through the profile sequencer, which waits for a safe point.
  4. Tune the AD9361: analog filter bandwidths, LOs and NCO offsets, RX gain and TX attenuation. Failures here do not fail the apply (the modem is configured); they are reported in the result's tuning sentence.
  5. Record a profile revision in the store.

An illustrative result:

JSON
{
  "profile_name": "rf_loopback",
  "cfg_epoch": 12,
  "tuning": "RX gain 20 dB (AGC → manual), TX attenuation -20 dB; analog BW 200 kHz; LOs tuned to 437292041 Hz with a +32959 Hz NCO offset — centre 437325000 Hz (+0 Hz from the profile's 437325000), leakage 32959 Hz off the band"
}

Some consequences worth knowing:

  • An apply resets the channel emulator to pass-through and re-opens the TX egress when tx_enabled is true, including after the RF kill switch.
  • An apply performed while traffic flows can time out in WAIT_SAFE. After a successful loopback measurement the datapath may stay busy and later applies time out until a reboot: apply before you measure, never after.
  • A later manual change (frequency, gain, register) marks the profile dirty: GET /api/v1/profiles/active returns {"name": "...", "dirty": true, "dirty_reason": "..."}.
  • A scenario applies its own profile at the start of every run (see Scenarios).

Validation rules#

RuleMessage names
version starts with satlink.profile/version
center_frequency_hz > 0radio.center_frequency_hz
symbol_rate_baud is an available ladder ratethe nearest available rate
samples_per_symbol is 4 or absentphy.samples_per_symbol
GMSK has bt = 0.5phy.bt
Modulation is BPSK, QPSK or GMSKwhy the others are not implemented
sync_word.value_hex parses as 32-bit hexframing.sync_word.value_hex
fec.rate present when fec.type is not noneframing.fec.rate
rs or rs+conv requires mtu_bytes = 223mtu_bytes
Preamble pattern ≥ 8 bits from every sync-word image, mean ≤ ¼ full scale, post_symbols ≠ 0, lengths ≤ 65535framing.preamble
Transfer frame: mtu_bytes set and divides 4096, SCID/VCID fit the frame type, no RSframing.transfer_frame, framing.mtu_bytes

Managing profiles#

Shell
satlinkctl profile list
satlinkctl profile get rf_loopback       # the profile as JSON
satlinkctl profile apply rf_loopback
satlinkctl profile active

The daemon loads every *.yaml directly inside [daemon] profiles_dir (/opt/satlink/dsl/profiles on the board) at startup; files in subdirectories are not loaded, and a file that fails to parse is skipped with a warning in the log.

Pushing a profile without a firmware release#

PUT /api/v1/profiles/{name} registers a profile sent as YAML (or JSON) in the request body:

Shell
curl -X PUT --data-binary @my_profile.yaml \
     -H 'content-type: text/plain' \
     http://192.168.2.1:8080/api/v1/profiles/my_profile
JSON
{"name": "my_profile", "replaced": false, "persisted": false,
 "note": "registered in memory only — it is gone at the next daemon restart, and nothing was written to profiles_dir"}

The profile is held in memory only and disappears at the next daemon restart. Keep your file as the source of truth. To ship a profile permanently, add it to dsl/profiles/ and build a release.

GET /api/v1/profiles/{name}/revisions lists when a profile was applied (newest first), from the run store.

Shipped profiles#

ProfilePathModulationRate (baud)CodingNotes
pl_loopbackPL loopbackQPSK19 531.25noneThe bench's known-good baseline; 16-byte frames, fill enabled
rf_loopbackairQPSK39 062.5noneSame as pl_loopback over the external cable loopback
raw_txairBPSK19 531.25noneNo marker, no scrambler: pushed bytes are what goes out. Transmit-only, for spectrum work
ccsds_tm_loopbackPL loopbackQPSK19 531.25none1024-byte frames with CCSDS TM transfer frames and FECF
hc_qpsk and hc_*PL loopbackQPSK/BPSK/GMSKvariousnone/conv/rs/rs+convHealth-check arms, each differing from hc_qpsk in one thing
hc_air_qpsk, hc_air_bpsk, hc_air_gmskairQPSK/BPSK/GMSK19 531.25noneOn-air health-check arms
hc_dev_qpskdevice loopbackQPSK19 531.25noneAD9361 digital loopback arm
spaceinventor_uhf_cspairGMSK19 531.25noneA mission-style profile (CSP over UHF)
spaceinventor_uhf_csp_loopbackPL loopbackQPSK19 531.25noneIts bench counterpart
endurosat_uhfairGMSK19 531.25rsDeclares tx_power_dbm: 27, which is refused at apply

Mission profiles cannot match their spacecraft's real 9600-baud links: the modem's lowest rate is 19 531.25 baud. The profile files say so in their comments.

Writing your own profile#

  1. Start from pl_loopback.yaml (PL loopback) or rf_loopback.yaml (air), which have a measured baseline behind them.
  2. Change one thing at a time and prove it with the PL loopback before going on air.
  3. For air profiles: enable the scrambler, set radio.loopback: false, set rx_gain_db for your cabling (see RF Fundamentals) and tx_power_dbm as a negative attenuation.
  4. Keep mtu_bytes a divisor of 4096 unless you use Reed-Solomon (which forces 223).
  5. Push it with PUT /api/v1/profiles/{name}, apply it, and read the tuning sentence and satlinkctl chain status.

Stellar Link · v0.1.0

↑↓ to moveEnter to open