MATLAB¶
What to install for MATLAB and Simulink, where MathWorks' ADALM-Pluto support
package falls short on this board, and the fishball package and blocks this
repository adds to get around it. To check MATLAB and run the first example,
see use MATLAB; then the
MATLAB examples.
./devkit matlab checks MATLAB and the board in a few seconds; from a MATLAB
prompt the same check is fishball.doctor. If MATLAB is not on PATH, set
MATLAB_BIN to the binary.
- firmware update offer: never accept: that image is for a different board, and there is no undo
- full scale:
int16: ±2047;double/single: ±1.0; transmit: ±32767 - channels:
sdrrx/sdrtxsee only RX1/TX1; RX2 and TX2 through thefishballpackage - changing a setting: release and re-create: a property set on a running object does nothing
- Simulink blocks: must run with Simulate using = Interpreted execution
- listening: engage the FPGA ÷8 decimator, or the sound card starves
Never let MATLAB update your firmware¶
Never accept MATLAB's offer to \"switch the firmware version\"
The ADALM-Pluto support package is tested against Pluto firmware v0.39, sees
this board's version, and makes the offer through the Hardware Setup App. That
image is for an ADALM-Pluto (Zynq-7010, AD9363); this board is a Zynq-7020 with
an AD9361 and, on the common variant, a power amplifier. There is no undo.
fishball.connect suppresses that offer and prints the safe half of the warning
once per session; sdrrx used directly shows MathWorks' original, offer and all.
What you need¶
| MATLAB | tested with R2026a |
| Communications Toolbox | required |
| ADALM-Pluto support package | required for live radio; about 1 GB. Tested with 26.1.7 |
| DSP System Toolbox | audioDeviceWriter in example 02's listening mode |
| Simulink | example 06, and the RxSource/TxSink blocks |
| HDL Coder | not used |
Without the support package every analysis still runs on a capture file; see No support package.
Three things that will bite you¶
1. Full scale differs by data type. The converters are 12-bit,
sign-extended into int16; full scale is the largest representable value, and
every dBFS level is relative to it.
| what you asked for | what you get | full scale |
|---|---|---|
OutputDataType int16 |
raw converter counts | ±2047 |
OutputDataType double or single |
counts ÷ 2048 | ±1.0 |
| transmit | MSB-aligned into a 12-bit DAC | ±32767 |
Dividing receive counts by 32768 makes every absolute level 24.09 dB low
Uniformly, so nothing looks wrong and every ratio (SNR, EVM) is unchanged.
fishball.spectrum takes a FullScale argument and defaults to 2047.
2. Setting a property on a running object does nothing, without an error.
Changing .Gain on a locked System object is ignored; release and re-create:
% run from: the MATLAB prompt
rx.Gain = 40; % silently ignored
release(rx); rx = fishball.connect('Gain', 40); % what you meant
3. sdrrx and sdrtx see only one channel. Both reject anything but
ChannelMapping = 1 (ChannelMapping must be equal to 1): the support package
is written for the 1R1T ADALM-Pluto. This board is 2R2T, so RX2 and TX2 exist,
only through the fishball package:
| RX1 / TX1 | RX2 / TX2 | |
|---|---|---|
| receive | sdrrx |
fishball.capture2 → iio_readdev |
| transmit | sdrtx |
fishball.safeTransmit → iio_writedev -c |
Both return something release() stops, so your code does not branch.
No support package¶
Every analysis in the examples runs on a capture file, so base MATLAB plus Communications Toolbox is enough:
# run from: the repo root
./tools/sigmf-capture.py record air --rate 3e6 --freq 868e6 --seconds 2
% run from: the MATLAB prompt, with the repo root as the current folder
addpath matlab
[x, meta] = fishball.readSigMF('air.sigmf-meta');
[db, f] = fishball.spectrum(x, meta.SampleRate, 'FullScale', meta.FullScale);
plot((meta.CenterFrequency + f)/1e6, db); grid on
readSigMF reads exactly what tools/sigmf-capture.py writes, full scale
included, and matches numpy on the same file.
Streaming, and letting the fabric help¶
| At 2.4 MS/s, 0.2 s frames | |
|---|---|
| reading (real-time limited) | 179.7 ms |
| DSP | 11.3 ms |
| each 200 ms of audio costs | ~212 ms: the sound card starves indefinitely |
The fix is the ÷8 decimating filter in the FPGA fabric (the programmable logic in front of the ARM cores): the host then reads 288 kHz instead of 2.304 MHz.
3.2 s of audio 3.39 s of wall clock -> 2.64 s
40 s of listening 113 underruns -> 0
There is no "filter on" attribute: writing the ADC device's sampling_frequency
to one eighth of the converter rate engages it (GP_CONTROL bit 0, the bypass
mux).
It is only safe on both receivers because of patch 0021
On a STOCK_RX_FILTER=1 build it aliases RX2 by about 70 dB. See
both-receive-channels.md.
Transmitting¶
Three examples transmit: 03 (the modulated link), 04 with TxChannel
(PadDb then required), and 06's fishball_qam16.slx (PadDb defaults to
20).
Read transmitter-safety.md before anything radiates
The board reaches about +19 dBm and its receive input is rated +2.5 dBm absolute maximum, so a loopback needs at least 20 dB of attenuation.
fishball.safeTransmit |
Why |
|---|---|
will not start without PadDb |
nothing on the board senses the transmit port, so the number has to come from you |
| reads the attenuation back off the chip after the buffer starts, and stops the transmitter if it disagrees by more than 0.5 dB | patch 0005 restores a cached attenuation when a buffer opens |
releasing a transmitter waits for its iio_writedev to exit |
without the wait the kernel's close hook mutes the transmitter after the next object has set its gain, and every second transmitter comes up silent at −89.75 dB |
Simulink¶
Two blocks in matlab/+fishball/ replace the stock ADALM-Pluto block, which
also enforces ChannelMapping must be equal to 1. Drop a MATLAB System block
and point it at the class:
fishball.RxSource |
receive. RX1, RX2 or both; the FPGA ÷8 decimator; a telemetry output |
fishball.TxSink |
transmit. TX1 or TX2; the sample-locked header pins; a pad guard |
Parameters use the usual SDR names (BasebandSampleRate, RFBandwidth,
RFPort, GainSource, SamplesPerFrame);
example 06 lists every dialog group.
Set "Simulate using" to Interpreted execution¶
Required
These blocks reach the radio through system(), which has no generated
equivalent. With the default, Code generation, the model fails to compile
with An error occurred in the block '...' during compile.
In the Block Parameters dialog, set the Simulate using dropdown at the bottom
to Interpreted execution; it saves with the model. The generators in example 06
set it for you. From the command line, with the block selected (gcb is "get
current block"):
% run from: the MATLAB prompt, with the model open and the block selected
set_param(gcb, 'SimulateUsing', 'Interpreted execution')
The model can drive the radio¶
ControlPorts turns settings into input ports:
ControlPorts |
inputs |
|---|---|
'none' |
none; dialog values are fixed |
'tune' |
Fc, centre frequency in Hz |
'full' |
Fc, gain (dB), BW (Hz) |
'all' |
Fc, gain1, gain2, BW, gainMode, RFport |
gainMode 0 manual 1 AGC slow attack 2 AGC fast attack 3 hybrid
RFport 1 A Balanced ... 9 C_P, 10 TX Monitor 1, 11 TX Monitor 2
(this firmware accepts only 1; see below)
gain1andgain2are separate because the receivers differ by ~1.5 dB.NaNon a port means "leave this alone".- A value is pushed only when it changes, at 10–30 ms per
iio_attrround trip plus a stream rebuild. Drive these from something slow (a slider, a staircase), not a per-frame signal. BasebandSampleRateandFabricDecimationare not inputs;releaseand re-create the block to change them.- A change rebuilds the stream, and must: samples taken at the old setting
sit in
iio_readdev, the FIFO, the socket and the DMA ring. Without a rebuild a 500 kHz retune shows the old offset for 34 more frames.
examples/matlab/06-simulink/fishball_scanner.slx walks the oscillator across
88–108 MHz in 70 looks of 288 kHz.
Two levers this firmware refuses¶
Both are offered in the dialog; the firmware rejects both with
Invalid argument (22):
RFPort: onlyA_BALANCED, althoughrf_port_select_availablelists twelve.EnableRxFIR: nothing to enable until coefficients are loaded throughfilter_fir_config; design taps withfirmware/scripts/gen_fir_coe.m.
The quadrature, RF DC and baseband DC tracking settings do apply. A refused write warns once per attribute with the chip's own message.
Rules for System objects that touch the radio¶
- Do not open the radio in
setupImpl. Simulink also calls it at compile, so a transmitter set up there starts, stops and starts again. Open lazily on the first step; keep argument checks in setup (TxSinkchecksPadDbthere, so a bad pad fails before anything radiates). - Write the transmit gain until the chip agrees. Patch
0005restores a cached attenuation when the hardware buffer starts.TxSinkrewrites until the read-back matches, up to twelve attempts, then stops withAsked for ... dB, chip reports ... dB after 12 attempts. - Measure a constellation the way it is drawn. Normalising symbols before comparing measures tightness, not position: a constellation at 1.42× the reference reads 6.3 % normalised and 42.3 % as plotted. Check the amplitude ratio too.
examples/matlab/06-simulink/fishball_qam16.slx is a worked 16-QAM link (TX1 →
20 dB pad → RX1, 900 MHz, 144 ksym/s): 6.7 % EVM as plotted, amplitude ratio
1.003, peak 324 of 2047. It relies on the ÷8 decimator; at 2.304 MSPS MATLAB
falls ~34 frames behind and the constellation is a blob.
The firmware version string¶
On any version other than v0.39 the support package raises
plutoradio:sysobj:FirmwareIncompatible, a warning whose construction throws
for some version strings and aborts the connection, even with
warning('off','all'):
In 'plutoradio:sysobj:FirmwareIncompatible', data type supplied is incorrect
for parameter {1}.
It depends on the shape of fw_version in /etc/libiio.ini:
fw_version |
result |
|---|---|
v2.0, 2.0, v2.0.1, v2.0-dirty, v0.39, v0.39-9-gdeadbeef |
connects |
v2.0-9-g5ae29d94-dirty, v2.0-9-g5ae29d94, v2.0-9-gabcdef |
fails |
So fishball-identity on the board publishes the release in fw_version and the
full git describe in fw_build (e.g. fw_version=v2.0,
fw_build=v2.0-9-g5ae29d94-dirty). If a board still reports a describe string,
fix it on the board:
# run from: the board (Debian root, v2.x)
/usr/local/sbin/fishball-identity # rewrites /etc/libiio.ini
systemctl restart iiod # iiod reads it when it starts
v1.x (Buildroot) is untested with MATLAB; the fix is the same if it breaks.
Troubleshooting¶
No board answered on fishball.local… |
Not on the network. BOARD=192.168.2.1 matlab on the USB cable, or ./devkit status |
| MATLAB offers to update the firmware | Refuse. See above |
data type supplied is incorrect for parameter {1} |
The fw_version shape |
already owned by a block, block dialog, or System object |
Left over from an earlier failed setup. clear all |
-16 EBUSY |
A killed client holds the DMA on the board; killall iiod over ssh |
| Everything reads near zero | Antenna on the other port? 'RxChannel', 2 |
| Audio underruns while listening | See streaming |
Simulink: An error occurred in the block '...' during compile |
Interpreted execution |
| A licence error naming a toolbox | The examples say which; the SigMF path needs only Communications Toolbox |
Where things are¶
matlab/+fishball/ |
connect, capture2, spectrum, phase, evm, qam, safeTransmit, readSigMF, doctor, and the blocks RxSource and TxSink |
examples/matlab/ |
six examples, receive-first; 06 is Simulink |
tools/matlab.sh |
what ./devkit matlab runs |