Developing a replacement for the FTS-8 CTCSS sub-tone encoder/decoder

I recently purchased a brand new older than me Yaesu FT-736 ham radio transceiver on eBay from a Japanese seller. It is a nice VHF/UHF rig, with true full duplex and “all mode”s (FM, SSB, CW, packet) capabilities and a nice “facade full of dials and buttons” ergonomics that I missed a bit coming from a computer-controlled SDR.

I plan to use it to setup a satellite base station at home. The trick is that nowadays most if not all FM satellites require CTCSS tones. This transceiver can do that with a little plug-in board that was sold by Yaesu as an option back in the day. Mine does not have it unfortunately.

Original parts are nowhere to be found (or sell for a significant fraction of the price I purchased the radio…) and Piexx sells a somewhat expensive 79$ replacement board that is a modern redesign. Considering the triviality of the function of this board, I looked for homebrew schematics. Homo ludens has already done so using a PIC micro-controller but not having a PIC programmer at home, and finding the design a bit flawed I decided to design my own.

Encoding CTCSS tones

I wanted to keep the design simple and use components I was already familiar with. I pick the ATmega328P micro-controller as it was cheap, powerful enough and I already had the programmer for it. Sub-tone decoding is done using the onboard ADC and encoding is done through PWM. The schematics of the board is the following:

Contrary to homo ludens’ design, the PWM does not output a square wave at the CTCSS frequency. It would have made the filtering of the tone harmonics tedious for two reasons. At first, square waves have strong third harmonics, requiring sharp low-pass filters of high orders to eliminate them. Furthermore, the highest CTCSS frequency, 254.1Hz is more than thrice the lowest CTCSS frequency: the proper elimination of the third harmonics would require filter banking, in a similar way as amplifiers work when working over several bands.

In my implementation, the output frequency is 201 times the CTCSS frequency, with a PWM modulation that samples a sine wave period over 201 points. The low-pass filter must simply be designed to let the highest CTCSS tone (254.1Hz) pass, while blocking the PWM frequency of the lowest tone (13.4kHz) which is much simpler to achieve. A simple second order active filter using a Sallen-Key topology does the trick.

Decoding CTCSS tones

I don’t really need decoding capabilities in my usage of my radio. However, for the sake of the challenge of performing a feature complete re-implementation of the FTS-8 board, I wanted my board to have that.

Decoding CTCSS consist on detecting the strength of a single frequency in the audio input and compare it to a detection threshold. This can be done by computing the Fourier transform at a single frequency value, the one of the CTCSS subtone. The Goertzel algorithm provides an elegant solution to this problem. By sampling the audio input x at a sampling rate of a multiple p of the tone frequency, and over a buffer length of N period (thus sampling N.p points), the Goertzel algorithm computes a sequence s such as:

s n = x n + 2. c o s ( 2 π p ) . s n 1 s n 2

We can then calculate the power term as:

P = s [ N . p ] 2 + s [ N p 1 ] 2 2 c o s ( 2 π p ) . s [ N . p ] . s [ N p 1 ]

The tricky thing to do on a 8-bit micro-controller with no floating-point arithmetic (and no machine instruction for division for that matter) is the multiplication by the constant cos(2π/p). Fixed point arithmetic is out of question because for p much larger than one – which is required to avoid potential aliasing caused by higher frequency tones, cos(2π/p) is fairly closed to 1 and the error would accumulate. The trick to speed up the calculation is to find a couple of integers p and Ω such as:

c o s ( 2 π p ) 1 1 2 Ω

Values of p=201 and Ω=11 are a surprisingly good approximation (the correct value of p is 201.054). This allows the computation to be done solely with integer addition, subtraction and bit shifting:

#define COEFF_MULT(x)       (((x) << 1) - ((x) >> (OMEGA_FACTOR - 1)))

volatile int32_t sprev, sprev2;
volatile uint16_t sample_no = 0;
ISR(ADC_vect) {
    int32_t s = (int32_t)ADC + COEFF_MULT(sprev) - sprev2;
    sprev2 = sprev;
    sprev = s;
    sample_no++;

    if (sample_no >= NUMBER_OF_PERIODS * P_FACTOR) {
        // Power term calculation...

        sprev = sprev2 = 0;
        sample_no = 0;
    }
}

Note that’s also the reason why the encoder output frequency is 201 times the CTCSS tone: both encoding and decoding use the same timer in the same configuration. The decoding is performed over 32 tone periods (478ms at the lowest tone and 126ms at the highest one), as a compromise between tone separation, signal/noise separation and detection speed.

End result

Here is the little board:

I first check the quality of the generated tone, to check that the generated frequency was correct and the waveform had a good sine shape. The result was quite satisfying, with no measurable harmonics.

Once properly installed on my Yaesu FT-736M, I checked the proper deviation of the encoder by checking the emission of the transmitter using a RTL-SDR dongle and Gqrx software. The following screenshot show the transmitter output with CTCSS encoding enabled (bottom waterfall) and without (top). The deviation is about 15-20% of the signal range, which is what is expected for a CTCSS tone.

I checked the tone squelch feature of two ways communications by using an handheld transceiver and checking that both and trigger the CTCSS tone detection of the other.

The code is uploaded on my Github page and is licensed under the GPL v3 license (opensource).

I had to build 5 of those (which was the minimum order quantity), so I have 4 of those for sale on eBay for anyone interested.

EDIT: I ordered some more since it appears that those board were of interest to several OM. They are for sales on eBay.

References

F4VQG

Tutoriel: installer une machine virtuelle OpenBSD sur la Freebox Delta

Nouvellement abonné chez Free, j’ai commandé la dernière Freebox, la Delta. Celle-ci est appelée « serveur » et non « routeur » dans la documentation, et ce, à juste titre puisque Free ont mis le paquet pour permettre l’auto-hébergement. En effet, une des fonctionnalités de l’OS de la Freebox est d’exécuter des machines virtuelles KVM directement sur la box – évitant ainsi d’avoir à faire tourner H24 un PC à la maison. C’est d’ailleurs sur une VM dans ma Freebox que ce blog est hébergé.

Free propose un certain nombre de distributions Linux (Debian, Ubuntu, CentOS notamment) mais j’ai souhaité faire tourner mon serveur personnel sur OpenBSD, parce que c’est cool™. Il y a cependant quelques difficultés à surmonter pour que tout fonctionne convenablement.

Démarrage de l’installateur et installation de l’OS

Le gros de l’installation se fait via l’interface d’administration de la Freebox http://mafreebox.freebox.fr, accessible sur le réseau local. On peut alors explorer le menu « VMs » et commencer la création de la machine virtuelle.

La première difficulté est que la Freebox tourne sur architecture ARM64. Il faut donc se procurer l’installateur OpenBSD adéquat qui, allez savoir pourquoi, n’est pas disponible au format CD-ROM (ISO) mais seulement sous la forme d’image disque. On peut tout à fait contourner ce problème :

  • en choisissant une image ISO bidon dans l’interface de la Freebox, puis en la supprimant après coup dans les paramètres de la machine virtuelle ;
  • en téléchargeant l’installateur pour clé USB, et créant une clé USB amorçable qu’on pourra ensuite brancher sur un des ports USB de la Freebox (la machine virtuelle pourra démarrer depuis l’USB !).

La configuration finale ressemblera à ça sur la Freebox. Notez l’absence d’image CD-ROM et l’ajout du port USB dans le bloc « système ».

On peut alors créer un disque USB d’installation selon la procédure standard, puis l’insérer dans la Freebox :

$ wget https://cdn.openbsd.org/pub/OpenBSD/7.2/arm64/install72.img
# dd if=install72.img of=/dev/<disque_usb> bs=4M

On démarre la VM, et ça boote…

Contourner le crash au démarrage

…et puis ça plante au bout que quelques secondes. Le démarrage reste bloqué sur un message un peu cryptique.

virtio3: couldn't map interrupt

Une petite recherche sur les Internets montre que le problème semble être lié au générateur de nombres aléatoires de QEMU que OpenBSD ne semble pas bien gérer. Après un peu de bidouille, on se rend également compte qu’il y a des problèmes avec le pilote du memory balloon. La solution : mettre sur liste noire ces deux pilotes. On redémarre donc la machine virtuelle, et au démarrage on tape boot -c pour entrer dans le BOOT_CONFIG de OpenBSD. On désactive les pilotes en question :

boot> disable viornd
boot> disable viomb
boot> quit

Et là, miracle, l’installateur démarre et on peut installer le système d’exploitation puis démarrer le nouveau système en ré-effectuant la manipulation à chaque démarrage.

Rendre la bidouille permanente

C’est non seulement pénible de devoir désactiver les pilotes à chaque démarrage, mais également problématique puisqu’en cas de coupure de courant ou de redémarrage inopiné, la machine virtuelle plantera au démarrage et le serveur restera inaccessible.

On va donc rendre cette manipulation permanente en configuration un noyau avec ces pilotes désactivés. C’est là qu’intervient le fichier de configuration /etc/bsd.re-config qui permet de correctement configurer le noyau à chaque démarrage. On y insère alors les deux commandes disable viornd et disable viomb.

On teste ensuite en redémarrant le système non pas une, mais deux fois : en effet, les commandes du fichier /etc/bsd.re-config étant prises en commande au démarrage du système pour le démarrage suivant, le premier redémarrage est garanti de poser problème.