This is meant to be a series of short and sweet guide to a few ways to run M17 over RF today. This one focuses on transmitting and receiving M17 with m17-cxx-demod and GnuRadio. This’ll require a little more Linux familiarity than the OpenWebRX guide, but it’s not too bad.
I’d say the hardest part is making sure you have the right dependencies installed - on some linux systems it can be a real bear. If you run into any troubles, make sure to ask for help and we’ll try to sort it out and get it documented.
Update 2023 September
You might prefer to use the M17-Tools fork, which has had some more attention. As compilers are updated and occasionally become more strict, sometimes perfectly working code will stop compiling.
That happened to me in mid September of 2023 while I was testing my Module17 with these very instructions. Switching to m17-tools and submitting a patch of my own fixed it. If you ever try these and these instructions don’t work, please email me!
Introduction
There are a few options for basic testing, that need more work for integration:
- SDR receiver - rtl_fm
- SDR transmitter - GNU Radio Companion
And some that are closer to being useful, if not outright usable already:
- SDR receiver - OpenWebRX
- WX9O’s TNC3 and a capable mobile radio +
m17_kiss_ht
- G4KLX’s M17Client and an MMDVM interface or hotspot
We’ll need a known-good transmitter for testing all of these, so I’m starting with WX9O’s m17-cxx-demod repository and a HackRF.
In doing that, we’ll also try the M17 receiver to make sure both tx and rx work.
Videos are best viewed on a laptop or desktop computer. You’ll have a damn hard time reading the text, much less following along on a mobile phone or tablet.
Download
First - per the instructions in that repository, you’ll need:
This code requires the codec2-devel, boost-devel and gtest-devel packages be installed.
It also requires a modern C++17 compiler (GCC 8 minimum).
Those package names look like Debian-style packages with the -devel suffix for development headers.
Arch Linux (almost) always includes development headers with any package,
so the package names on my system are just codec2
, boost
, and gtest
.
These’re pretty common and it turns out I already had them installed, so no effort on my part as Arch keeps a fairly modern compiler suite too.
You’ll also want sox to handle converting audio files, GNU Radio including GNURadio Companion, and the drivers and libraries for your various SDRs. That’s a little outside the scope of this post, but you can come ask in chat and we’ll unstick you if we can.
If you’re uncomfortable with the command line, this will require a bit more sticktuitiveness than you may like.
Build
I’m going to run these binaries from the build directory instead of installing them to my system proper, so I won’t run “make install”.
Following the instructions, we get something like this to download and compile the software:
git clone https://github.com/mobilinkd/m17-cxx-demod.git
cd m17-cxx-demod
mkdir build
cd build
cmake ..
make
If all goes well, your build/
directory should now have an apps/
subdirectory that contains m17-mod
and m17-demod
, (and some other
files we’ll ignore).
Hopefully all has gone well. If not, don’t give up, read the errors carefully, and if all else fails ask for help.
While that compiles - actually it’s probably already done, it doesn’t take too long - let’s grab the .grc file (GNU Radio Companion graph) from https://github.com/mobilinkd/m17-gnuradio and open it with GNU Radio Companion.
If you have a HackRF instead of a Pluto, you’ll need to replace the sink with an Osmocom Sink. I’ve a screenshot for your perusal and I’ll try to explain the magic numbers.
In “Device Arguments”, you can specify the hackrf to transmit from
with hackrf={serial number}. You can find this serial number with the
hackrf_info
command and copy and paste it into this field in GRC. I’ve
blurred my serial number out because … I don’t know, but it seems
merely prudent on the internet these days.
Sync should be set to “Don’t Sync” because otherwise the osmocom driver will look fruitlessly for a PPS (Pulse Per Second) input that doesn’t exist and try to synchronize to it.
“sample_rate” in the Sample Rate field is a GRC variable, which is generated from multiplying the symbol rate (which must be 4800 symbols per second for M17 simply because that’s the defined value for M17) by the interpolation rate to get a valid sample rate for the SDR. More on that in a moment.
The “Ch0 Frequency, Hz” is going to be the transmit frequency.
Finally, the gain fields are guesses of my own that don’t seem to overload my receiver and informed by a weak memory of the HackRF documentation.
(I never remember which one of the IF or BB gain fields is relevant for TX, but since it does no harm to just have them both in there I just set them both to the same value since only one has any effect.)
I think everything else is untouched and the default value.
Sample Rate
GRC variables allow for referencing common values across multiple processing blocks, which makes modification like we’re about to do much easier. Here’s my working set of values for TX with the HackRF:
I have only changed the interpolation rate, which makes the sample_rate recalculate automatically.
The HackRF does not perform well with low ( < 2Msps ) rates, and using a minimum of 4 or even 8 Msps is recommended. You can see WX9O has documented the minimum sample rate for the PlutoSDR, and the HackRF is just a little pickier. An interpolation value of 1024 brought the sample rate up to just under 5 Msps and that works just fine for me.
There’s one final thing we need to do to transmit, which is to create the input file and tell GRC to use that as the source to transmit.
This right here:
That path controls where to find an M17 bitstream - which we still need to generate. Let’s do that now.
Transmit and Receive: Prep an audio file
I like using historical or meme audio files for testing. This one is from Apollo 14!
You can download it and use it yourself for this, or you can take just about any audio file and make it compatible for this test with this sox command line:
sox input.wav -b 16 -e signed-integer -r 8k -c 1 output.wav
You can read up on the details with man sox
, but this is converting
whatever the input format is to a single channel signed 16bit audio file
sampled at 8000 Hz and storing it in output.wav
You can also add filters and effects to the end of the command. I might
recommend norm
which will effectively autoscale the volume of the file
so you get consistent results:
sox input.wav -b 16 -e signed-integer -r 8k -c 1 output.wav norm
You might also want to normalize and then attenuate the volume just a tad:
sox input.wav -b 16 -e signed-integer -r 8k -c 1 output.wav norm gain -3
Those sox commands should work with just about any standard audio file as input, but I’ve only tried wavs because those are what I happen to have on hand.
You can also opt to record your own voice, if you really believe you’re more interesting than Apollo 14:
arecord -r 8000 -f S16_LE -c 1 out.wav
The -c
is the number of
channels, -r
is the sample rate, and S16_LE
means use 16 bit little
endian integers. We have to have these specific values just because
that’s what m17-mod can take as input, which is itself constrained by
the Codec2 library.
Loopback test
- We’re going to take the wav file, strip it of the wav metadata so it’s just the raw audio, and pass it to m17-mod.
- m17-mod will encode that into an M17 baseband stream and tag it as coming from YOURCALL (Replace with your own callsign please).
- That baseband will then be passed straight into m17-demod, which will decode it to audio again as well as display the LSF data and show debugging information.
- And finally that audio will be decoded and played by sox (
play
binary is part of sox package).
sox apollo11_1.wav -t raw - | ./apps/m17-mod -S YOURCALL | ./apps/m17-demod -l -d | play -q -b 16 -r 8000 -c1 -t s16 -
(If you want the apollo11 wav)
In this video I play part of the original wav first, and then play the M17 loopback audio.
Hopefully that’s just worked for you too - if not, please keep trying and ask for help when you’re well and truly stuck and making no progress and we’ll unstick you and work to prevent others getting stuck in the same way.
Let’s run the same thing, but with real radios in between the m17-mod and m17-demod.
We won’t be able to transmit with m17-mod directly on the command line, since the baseband signal needs a little more processing before it can become the IQ samples expected by an SDR to transmit.
Hardware Clocks
Note: the stock HackRF oscillator is expected to have ~20ppm error, which is not great, not terrible. Similarly, many cheap RTL-SDRs can have errors well north of 100ppm error. Neither are sufficient for our purposes.
If memory serves I’ve added a reasonable reference to my HackRF, but I’m feeding a fairly good 10MHz reference signal in from a cheap GPSDO anyway because I don’t remember for sure. My RTL-SDR (nooelec NESDR) has a .5 PPM TCXO which is plenty good.
If you run into issues, check to make sure your transmitter and receiver are on the same frequency in practical terms. Frequency errors here can mean an absolute error in frequency (so 146.52M becomes 146.45M for instance), or even a drift in frequency.
Drift is going to be impacted heavily by temperature changes, so run both
rx and tx for a while until they’re warmed up and you can use gqrx
or other graphical SDR programs to see what frequency the transmitter
shows up on in the receiver. Use that observed frequency for the receiver
command line instead of the nominal one to improve the likelihood of it
working for you.
RF Transmission
This is for those with legal privileges to transmit, and/or the know-how to avoid the signal getting out and being a liability.
First let’s split up that loopback command line and make some changes:
sox apollo11_1.wav -t raw - | ./apps/m17-mod -S YOURCALL -b > m17.bin
You’ll notice the sox part is identical, and the m17-mod part has had a
-b
added. This generates just the bitstream instead of baseband audio,
and we use typiccal shell processing to dump that output into a file
called m17.bin. This will be used in GNU Radio Companion as our file source.
rtl_fm -E offset -f 146.52M -s 48k | ./apps/m17-demod -l -d | play -q -b 16 -r 8000 -c1 -t s16 -
And here’s the receive side. It’s 100% identical as the loopback test, except it’s now being fed data from rtl_fm.
Go back to this block:
and modify it to point to your newly generated m17.bin, and hit the “play” button in GRC to try to transmit.
It’s reasonably likely that you’ll run into some form of issue with your radio, SDR libraries, or GNU Radio itself if you’re not spending a fair amount of time in GNU Radio already - I don’t spend enough time in it and usually run into something. This time it was the Sync field expecting a PPS input on the HackRF, which I’ve tried to save you from.
Once you’ve got it working, you should have results something like this:
Conclusion
Fantastic work WX9O, and many thanks!