Command Reference

The board speaks a line-oriented ASCII protocol over its BLE UART. Every line ends with CR/LF. You send plain text commands; the firmware replies with short coded lines, and the client translates those codes into readable messages. That asymmetry is deliberate — it keeps traffic off a slow link and keeps strings out of a 32 KB flash.

You do not need this page to use the instrument. It is here so you can drive the board from a terminal, write your own client, or understand what the app is doing.

Reply prefixes

The first character of every reply identifies the message type.

PrefixMeaningPayload
+Command acceptednone
-Command rejecteda single-digit error code
$Status linespace-separated key value tokens
@Samplesigned integer, millivolts
*Asynchronous eventspace-separated key value tokens
#Informational messagehuman-readable text

The firmware does not echo the command for + and - replies — a client must match the reply to the command it just sent. $, @, * and # messages are self-describing.

* events are sent unprompted whenever a relay, the multiplexer, or a fault state changes, and carry a subset of the status keys.

Error codes

CodeMeaning
-1Unknown command
-2Value out of range
-3Command is for CH1 only — the active channel is CH2
-4Wrong operating mode — the request is impossible in the current mode
-5Checksum or CRC error

-3 and -4 are the two you will meet in normal use, and both are the shared analog path asserting itself. The app avoids them by disabling the affected control and telling you which mode or channel would allow it.

Function generator — Channel 1

CH1’s own-waveform commands are guarded by the active channel. If CH2 is active, they return -3; select CH1 first.

CommandMeaning
f<hz>CH1 frequency in Hz
w<0-5>CH1 waveform — 0 sine, 1 square, 2 triangle, 3 sawtooth, 4 haversine, 5 custom
ph<0-359>CH1 phase in degrees
dc<1-99>CH1 square-wave duty cycle in percent
fm<fc>,<fm>,<dev>Configure and enable CH1 FM — carrier ≤ 10000 Hz, modulating ≤ 1000 Hz, deviation ≤ 5000 Hz
fmo<0-1>Toggle CH1 FM without reconfiguring
fmw<0-3>CH1 modulating waveform — 0 sine, 1 square, 2 triangle, 3 sawtooth

Function generator — Channel 2

CH2’s equivalents carry no active-channel guard.

CommandMeaning
bf<hz>CH2 frequency in Hz
bw<0-5>CH2 waveform
bph<0-359>CH2 phase in degrees
bdc<1-99>CH2 square-wave duty cycle
bfm<fc>,<fm>,<dev>Configure and enable CH2 FM
bfmo<0-1>Toggle CH2 FM
bfmw<0-3>CH2 modulating waveform

Analog level control

CommandMeaning
a<0-99>Attenuator wiper — 0 is maximum attenuation
g<0-99>Gain wiper — 99 is maximum gain
ba<0-99>CH2 / offset-chain attenuator wiper
bg<0-99>CH2 / offset-chain gain wiper
d<hex4>Write a raw 16-bit hex value to the CH1 signal DAC
bd<hex4>Write a raw 16-bit hex value to the offset DAC

bd is refused with -4 in Dual mode and in CH2 Generator + Scope mode: in both, the offset DAC is generating CH2’s waveform and has no spare capacity to hold a static level.

Remember that a/g are the same wipers the oscilloscope’s gain stage uses. Setting them means something different depending on which role the hardware is currently playing.

Mode and routing

CommandMeaning
m<0-3>Operating mode — 0 generator, 1 oscilloscope, 2 dual independent, 3 CH2 generator + scope
c<1-2>Active channel
x<0-2>Multiplexer source — 0 direct, 1 low-pass filter, 2 scope loop
e<0-3>Channel-enable mask, Dual mode only — bit 0 CH1, bit 1 CH2
sRequest a status line
cfClear a latched fault
l / lnLED control

e<mask> is a single atomic command rather than two per-channel toggles, so the wire can never carry a half-applied state. Disabling a channel stops ticking its synthesiser — returning the surviving channel to the full 40 kSa/s — and energises that channel’s gate relay to ground its BNC rather than leaving a frozen DC level on the output. e0 grounds both.

Oscilloscope

CommandMeaning
oSingle sample via the 16-bit SAR
os<n>Capture n samples via the SAR
oi / oi<n>Single sample / capture of n samples via the internal 10-bit ADC
om<0-1>Capture method — 0 SAR, 1 internal ADC
ob<0-1>Capture format tier — 0 full-precision ASCII, 1 compact 8-bit binary

o and os reply -4 outside Oscilloscope mode. oi works in both Oscilloscope mode and CH2 Generator + Scope mode.

ob affects streaming captures only. A bare o or oi always replies in full-precision ASCII.

Samples arrive as @<millivolts> lines, signed, in the full-precision format.

Arbitrary waveform upload

CommandMeaning
wl<i>,<h4>,<ck>CH1 table point — index i (0-255), 16-bit sample in hex, checksum in hex
wv<crc>Verify CH1 table with a CRC-16/CCITT-FALSE over all 256 points
bwl<i>,<h4>,<ck>CH2 table point — index i (0-127)
bwv<crc>Verify CH2 table with a CRC-16 over all 128 points

The per-line checksum is (i + (h4 >> 8) + (h4 & 0xFF)) & 0xFF.

Send one line, wait for its + or -, then send the next. This stop-and-wait handshake is what prevents the 32-byte receive ring buffer overflowing. A -5 means re-send that one line. After the final point, send the CRC command; a -5 there means the table is rejected and must be uploaded again from the start.

Status line

The reply to s is a $ line of space-separated key value tokens. Selected keys:

KeyMeaning
f, w, p, dcCH1 frequency, waveform, phase, duty
bf, bw, bph, bdcCH2 frequency, waveform, phase, duty
fm, bfmFM enabled flags
mOperating mode
cActive channel
xMultiplexer source
a, g, ba, bgDigital potentiometer wiper positions
kRelay states K1-K5, one bit each, 1 = energised
eLatched fault flags for CH1 and CH2
enChannel-enable mask
om, obCapture method and format tier
ubCurrent UART rate

The app sends s on connect and treats the reply as authoritative. The hardware is always the source of truth — in particular the wiper positions, which the app has no other way of knowing, since the X9C parts cannot be read back and their positions persist in EEPROM across power cycles.

Updated · Full project report (PDF)