Reference
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.
| Prefix | Meaning | Payload |
|---|---|---|
+ | Command accepted | none |
- | Command rejected | a single-digit error code |
$ | Status line | space-separated key value tokens |
@ | Sample | signed integer, millivolts |
* | Asynchronous event | space-separated key value tokens |
# | Informational message | human-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
| Code | Meaning |
|---|---|
-1 | Unknown command |
-2 | Value out of range |
-3 | Command is for CH1 only — the active channel is CH2 |
-4 | Wrong operating mode — the request is impossible in the current mode |
-5 | Checksum 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.
| Command | Meaning |
|---|---|
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.
| Command | Meaning |
|---|---|
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
| Command | Meaning |
|---|---|
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
| Command | Meaning |
|---|---|
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 |
s | Request a status line |
cf | Clear a latched fault |
l / ln | LED 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
| Command | Meaning |
|---|---|
o | Single 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
| Command | Meaning |
|---|---|
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:
| Key | Meaning |
|---|---|
f, w, p, dc | CH1 frequency, waveform, phase, duty |
bf, bw, bph, bdc | CH2 frequency, waveform, phase, duty |
fm, bfm | FM enabled flags |
m | Operating mode |
c | Active channel |
x | Multiplexer source |
a, g, ba, bg | Digital potentiometer wiper positions |
k | Relay states K1-K5, one bit each, 1 = energised |
e | Latched fault flags for CH1 and CH2 |
en | Channel-enable mask |
om, ob | Capture method and format tier |
ub | Current 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)