Author: Wojciech Kaczmarski

  • LinHT – its internal structure and a quick explanation on how it all works

    While many people already know what LinHT is, not many are aware how it works. Yes, it’s an SDR radio – but what’s inside?

    The core of the device is a System on Module running carefully crafted Yocto Linux image. It offers all the Linux things, including a file system, GPIO/ADC ports, beloved ALSA, and many other higher-level goodies, such as Python3 and gcc with make/cmake. The SoM uses the NXP i.MX9352: dual-core ARM Cortex-A55, clocked at 1.7GHz. There is also a quite powerful Neural Processing Unit available, but we are going to pretend it;s not there for now πŸ™‚

    The system can run GNU Radio. Along with ssh/scp, it creates a powerful tool for amateur radio enthusiasts and RF engineers. This is definitely the best part, as the combination basically gives you a pocket computer with a full-blown SDR. It is no longer a black-box FM-only radio. This is actually a true, user-defined SDR. And it comes with a battery! Obsolescence is not an issue anymore. If a new protocol shows up in 5 years, it will most likely be possible to implement it on the LinHT. As GNU Radio blocks. It is that simple.

    Let’s take a look at the internal structure of the device.

    The GNU Radio flowgraph (as defined by a .grc file) can not do much on its own. The flowgraph has to be translated into Python code first. This is what the GR built-in grcc compiler does. So: after the flowgraph is prepared, it can be turned into an executable Python file (.py). The python code can be spawned by the Interface Daemon.

    What the hell is that daemon for? Well – as a user, you need a way to tell the radio what to do. It is also good to know what the radio is actually doing, and if it’s aligned with the user’s intentions πŸ™‚ You can not just turn the radio on (by turning the volume knob) and expect the OS to know what you want. This is what the daemon handles – your intent (by reading the keypad, PTT, etc.). It also uses the display to tell you want the radio is up to. But its function is not limited to that – it also controls the SX1255 RF front end chip and selects which flowgraph to run. It pre-configures the front end based on whatever settings are in the YML configuration file.

    The daemon also communicates with the flowgraph and ALSA. The flowgraph is a mere state machine – it needs to know what to do and when (state transitions). Which mode are we using? Are we transmitting? Are we receiving? Has an M17 packet with a text message arrived? Or are we going to reply to it? The daemon tells the flowgraph what and when to do it. The flowgraph can not just simply stay free-running all the time.

    Linux makes things easy here – the daemon works as a system service.

    Now the ZeroMQ Proxy. Since ALSA can be a pain in the rear to configure and work with, we decided to put a very simple additional layer between the SX1255 (again – disguised as ALSA sound device) and the GNU Radio flowgraph. There is a simple C code doing the proxy work: bi-directional ALSA-ZMQ bridge for baseband samples. Now, instead of using Audio Source/Sink blocks to access baseband (that did not work) we can now do the same task with ZMQ PUB/SUB Sink/Sources, connected to a pair of IPC sockets using very little CPU resources. Clever, right?

    The flowgraph can notify the daemon that it received something – this can be anything – in our case it’s an M17 packet reception. The flowgraph uses ZeroMQ with an IPC socket (again) to announce RF events. This path is used for non-audio streams (data). Audio streams are routed directly to ALSA through an Audio Sink.

    Microphone signal is handled by ALSA as well. It is sampled by the flowgraph directly.

    PTT signal handler is simple as well – we use the daemon here again. In an infinite loop, we read the keypad state (and the PTT). PTT press and release events trigger a ZeroMQ message transmission from the daemon to the flowgraph. The state machine in the flowgraph (M17 Coder/Decoder blocks have appropriate handlers built-in) reacts to the message and either starts reception or transmission. SX1255 (and the rest of the RF front end) is configured in the same way – by the daemon, upon user input.

    I hope this article is understandable and clarifies how LinHT works under the hood. It is not very detailed, as I did not want it to be too long. I just wanted to cover the core idea.

    Have fun experimenting!

  • gr-m17 and the improved Codec 2 blocks

    After a long and painful process, gr-m17 now contains Codec2-mod blocks. The difference between those and the “stock” Codec2 encoder/decoder blocks is that ours are much faster and support internal state reset. They have been designed specifically for the LinHT. This is very important when you run a flowgraph and expect repeated streams (each received transmission – internal state reset, same with PTT key press event for transmission).

    Andreas OE3ANC – thanks for helping me out with this.

  • LinHT M17 reception stability tests

    Today, I decided to stress-test my LinHT Rev A prototype. The aim was to assess the battery life in the following conditions: minimal transmission time – mostly reception. I used a CC1200 hotspot connected to M17-KCW A as a solid M17 RF source (with stable traffic). The handheld’s backlight (including keypad) was kept on for the whole test duration. Stock Retevis C62 2S Li-ion battery was used, fully charged on the previous day (in the evening). A modified charger was used – I replaced the 0.47ohm resistor with a 0.33ohm one* (a stack of three 1ohm resistors) for increased charge current. Silicon thermal pads were used to allow the SoC to dissipate heat through the aluminum part of the chassis (see this entry for details).

    I started the test by switching the volume knob at 9:17 LT. The starting battery voltage was 8.05V.

    At 19:17 LT (t+10h), the battery was at 6.7V.

    htop – CPU utilization and process list

    # uptime
    19:18:14 up 10:02, 2 users, load average: 0.49, 0.50, 0.47
    # ./LinHT-utils/scripts/temp.sh
    40.85

    CPU slightly below 41 deg. C after 10 hours of continuous DSP work? I think this is a very good result. This would definitely not be possible without some DSP tweaks we introduced in the M17 GNU Radio flowgraph. I had to quickly reset the flowgraph in the first 30 minutes due to an unexpected segfault πŸ™‚ The cause is yet to be found.

    Last but not least – big shout out to the rest of our team – Andreas OE3ANC and Vlastimil OK5VAS, and every financial contributor out there. Let’s keep this up πŸ™‚

    *the values are taken off the top of my head – I believe they are correct. The idea is to slightly reduce the resistance to increase the current.

  • rpi-dashboard 1.0.0 is out

    GitHub: https://github.com/M17-Project/rpi-dashboard

    The dashboard works in conjunction with Jim N1ADJ’s M17 Go gateway: https://github.com/jancona/m17

    Let your CC1200 hotspot’s LEDs blink happily!

  • CARI specification updates

    The Common Amateur Radio Interface (CARI) protocol has been updated with important clarifications and formal refinements. The update removed ambiguities in device selection, command framing, and supervision telemetry data transfer. The control and supervision planes are now explicitly defined. No functional changes were introduced, the update improves clarity only, therefore the protocol’s version number has not been incremented.

    Full changelog is available on GitHub:

    https://github.com/M17-Project/CARI/blob/main/docs/commands.md

  • m17-texting tool

    m17-texting tool just received long-waiting bug fixes. The app supports UTF-8 encoding. Go ahead and give it a spin.

    GitHub: https://github.com/M17-Project/m17-texting

    And yes, it works flawlessly with Digirig!

  • LinHT update

    We have decided to assemble a set of Rev B prototypes in Czech Republic due to long lead times at PCBWay.

    All of the electronic components are already there. A small batch of 10 PCBs has been ordered today. Stay tuned for more news.

    Big shout out to Vlastimil OK5VAS for a substantial support in making some real progress here.

  • M17 on the orbit (again)?

    FOSM-1 satellite might be capable of M17 RX/TX:

    FOSM-1 is designed by an non-profit organisation FΓ©dΓ©ration Open Space Makers. This is a hamradio spacestation payload that should flight starting Q2. It has received IARU coordination.

    That’s amazing news!

    More details: https://db.satnogs.org/satellite/JDIR-9578-9095-6812-4322

  • libm17 and gr-m17 updates

    libm17 received a few polishes recently, bumping up its version to 1.1.9. Changes include:

    • improved Viterbi decoder (in terms of speed, memory use, and robustness)
    • slight speed improvement of the Golay decoder
    • improved symbol slicer (should be faster)
    • improved lat/lon/radius encoding
    • tiny API changes – using char* for callsigns now
    • stricter unit tests
    • cmake fix
    • various cosmetic clean-ups

    gr-m17 uses libm17 1.1.9 now. Running make should result with a clean compilation. Nice! We are in the process of adding Codec 2 Out of Tree blocks, as the built-in set of Codec2 blocks depends on libcodec2. The native blocks can not be reset externally, they are not meant for “repeated” use. For the LinHT, we need improved blocks – computationally optimized and externally resettable. This is what we provide within gr-m17’s dev branch. As soon as the work is done, the experimental dev branch will be merged with main.

    Stay tuned for more news.

  • Codec2 vs. ETSI ACELP – spectral distortion

    After a few days of tweaking Codec2 guts, one question appeared. How much worse the LSP quantizer in ACELP (EN 300 395-2) is, as compared to that in Codec2? Both use the same excitation->filter model. The latter has excellent spectral distortion, much below of what could be called “transparent”. I remember comparing it back in 2022, but the results were never published.

    Now, back to theory. As per Wai Chu “Speech Coding Algorithms”, the formant filter’s transparency criteria are as follows:

    • average spectral distortion of less than 1 dB
    • less than 2% outliers having a spectral distortion above 2 dB
    • no outliers with spectral distortion larger than 4 dB

    Spectral distortion is defined as a “distance metric” between frequency responses of two filters (we are using log magnitude spectra here):

    SD=1n1βˆ’n0βˆ‘n=n0n1βˆ’1[20log⁑H1(n)βˆ’20log⁑H0(n)]2SD=\sqrt{\frac{1}{n_1-n_0}\sum_{n=n_0}^{n_1-1}\left[20\log{H_1(n)} – 20\log{H_0(n)}\right]^2}

    HH (frequency responses) are based on discrete Fourier transforms of the measured and reference LPC filters. FFT bins are usually set to 256. Typically, at 8 kHz sample rate, n1=100n_1=100 and n0=4n_0=4, giving 125..3,125 Hz range. Distortion outside this band is not considered perceptually critical.

    Now back to Codec2 and ETSI ACELP. We are only going to focus on the 3,200 bps rate of the former (20 ms frames, 64 bits each). ACELP runs at around 4,567bps (30 ms frames, 137 bits each).

    Codec2’s bit allocation for quantized LSPs is generous at 5 bits per LSP, yielding 50 bits total. ACELP isn’t as generous, spending only about half of that – 26 bits only.

    Codec2 uses delta-encoded spectral frequencies in frequency domain – Ο‰\omega. ACELP utilizes split vector quantizer with LSPs in cosine domain – cos(Ο‰)cos(\omega).

    Let’s look at the spectral distortion of both codecs:

    Now, the cumulative distribution (probability of SD below given threshold) for both:

    Pretty much the same information, just from another angle. Both plots were generated using LibriSpeech English speech corpus.

    Conclusion? Codec2 outperforms ACELP in terms of formant reconstruction but ACELP offers much, much more sophisticated excitation model. Excitation is Codec2’s biggest weakness. I really hope to be able to propose a better model in the coming months.