Nous proposons d’utiliser un oscilloscope radiofréquence comme source de données GNU Radio pour les applications nécessitant une large bande passante, telles que les mesures de temps de vol. Cette exploration sera l’occasion de découvrir la nouvelle mouture de GNU Radio attendue depuis 6 ans, la version 3.8, avec son lot de nouveautés et d’incompatibilités.
GNU Radio, environnement libre de traitement de signaux radiofréquences, encourage le développement de blocs de traitements dédiés (Out Of Tree modules – OOT). Alors qu’une multitude d’interfaces matérielles dédiées sont supportées par GNU Radio [1], elles ont toutes en commun de fournir un flux de données continu de bande passante limitée par les débits de communication vers l’unité de traitement – Ethernet ou USB le plus souvent. Lors de traitements RADAR de distance de cibles, deux conditions sont respectées par les oscilloscopes radiofréquences : le flux de données n’a pas besoin d’être continu, nous n’avons besoin que de traiter une profondeur mémoire déterminée par la plus grande distance (temps de vol) de la cible à détecter ; et plus la bande passante est grande, meilleure est la résolution. Une bande passante B d’un gigahertz permet d’espérer une résolution en distance ΔR = c / (2B) de l’ordre de 15 cm, objectif typique pour des mesures de glissement de terrain par exemple. Or, aucune interface de radio logicielle accessible au grand public ne fournit une telle bande passante – alors que « n’importe quel » oscilloscope radiofréquence fournit actuellement allègrement quelques Géchantillons/s. De plus, nombre d’oscilloscopes fournissent 4 voies, permettant donc 4 flux d’acquisition, propices aux mesures de direction d’arrivée des signaux.
Fort de ce constat nous convainquant de la pertinence d’un oscilloscope pour le traitement de signaux radiofréquences, nous désirions interfacer les oscilloscopes communiquant par interface Ethernet selon le protocole VXI11 [2] à GNU Radio, pour faciliter l’acquisition et le prétraitement des données avant d’alimenter GNU/Octave pour les traitements plus poussés.
Alors que ce projet végétait depuis un peu plus de 1,5 an, GNU Radio 3.8 vient d’être publié : le bloc d’acquisition de signaux d’oscilloscopes gr-oscilloscope [3] sera donc non seulement l’opportunité d’apprendre à écrire un bloc avec un nombre variable de sources (1 à 4 voies) paramétrables par l’utilisateur et communiquant par VXI11, mais aussi de découvrir les quelques subtilités de GNU Radio 3.8 et nous rendre compte que finalement, le passage du vénérable 3.7 à 3.8 ne sera pas aussi douloureux que le passage à Qt5 et Python3 laisse penser. Les dépôts des programmes présentés dans cet article sont disponibles sur http://github.com/jmfriedt/gr-oscilloscope pour GNU Radio 3.7 et http://github.com/jmfriedt/gr-oscilloscope38 pour GNU Radio 3.8, auxquels le lecteur se référera pour mettre en contexte les extraits de code décrits ici.
1. Principes généraux
La plupart des oscilloscopes radiofréquences ont aujourd’hui plusieurs Méchantillons de profondeur mémoire. L’expérience montre que l’ordonnanceur GNU Radio préfère manipuler des paquets de quelques milliers d’échantillons tout au plus. Notre philosophie sera donc de mémoriser de gros paquets de données dans le bloc source que nous allons développer et ensuite, distiller ces données acquises simultanément sur toutes les voies de l’oscilloscope selon les besoins de GNU Radio. Lorsque la réserve de données sera épuisée, car traitée par GNU Radio, nous requérons un nouveau paquet de données. L’hypothèse de synchronisme de toutes les voies de l’oscilloscope est un point clé pour le traitement ultérieur de mesures RADAR, et nous avons eu quelques déboires avec les modèles les plus anciens d’oscilloscopes qui désynchronisaient parfois d’un échantillon deux voies de mesure, mais les instruments les plus récents ne semblent plus affectés par de tels dysfonctionnements.
VXI11 est un protocole qui s’appuie sur RPC (Remote Procedure Calling [4]), lui-même un protocole permettant d’exécuter des procédures sur les ordinateurs distants connectés par réseau, facilitant ainsi les échanges entre processus (Inter Process Control – IPC), que ce soit par échanges UDP ou TCP, selon que la vitesse des échanges ou leur intégrité soit favorisée. Nous ne nous intéresserons pas nécessairement à l’implémentation, puisqu’une bibliothèque parfaitement fonctionnelle est disponible [5]. Notre objectif sera donc d’envelopper les appels aux commandes GPIB (General Purpose Interface Bus) par protocole VXI11 (implémentation de LXI pour LAN-based eXtensions for Instrumentation) vers l’oscilloscope dans un bloc GNU Radio, afin de fournir un flux de données compréhensible par l’ordonnanceur qui alimentera ensuite les blocs de traitement ultérieurs. Plutôt que de nous focaliser sur une application de RADAR qui nécessite d’ajouter aux entrées de l’oscilloscope des amplificateurs, voire une chaîne de transposition de fréquence que le lecteur n’a pas forcément envie d’assembler pour expérimenter, nous nous intéresserons à une application de réflectométrie [6] grâce à laquelle nous mesurerons avec précision la longueur de câbles coaxiaux, et ce, en y injectant une source de bruit large bande. Une telle application pourra rappeler le principe du RADAR à bruit, dans lequel une source de bruit est émise tout en étant enregistrée par une voie de référence, et la mesure sur une seconde antenne (ou la sortie d’un circulateur dans une configuration monostatique) permet, par corrélation, de trouver le temps de vol avec une résolution temporelle inversement proportionnelle à la bande passante du signal émis (figure 1). Le bénéfice de cette approche, par rapport au RADAR impulsionnel classique, est que générer une impulsion brève (donc de fort encombrement spectral, puisque la bande passante de l’impulsion est de l’ordre de l’inverse de sa durée) et énergétique est techniquement complexe : l’intégration sur une longue durée lors de la corrélation du bruit avec le signal mesuré nous permet d’intégrer la puissance du bruit sur une longue durée, et l’énergie étant l’intégrale de la puissance par la durée, nous obtenons l’effet équivalent d’une impulsion énergétique, mais technologiquement simple à réaliser. Les RADAR à bruit ont par ailleurs le bon goût de se glisser discrètement dans le fouillis des émissions électromagnétiques et d’être difficiles à repérer, un point positif lorsque nous expérimentons en espace libre.
Nous avions largement développé dans ces pages [7] le principe du RADAR passif qui s’apparente au RADAR à bruit, dans lesquels une source large bande a priori inconnue est enregistrée. Pour rappel, la mesure de retard induit par l’aller-retour du signal entre l’émetteur, la cible supposée statique dans ces exemples, et le récepteur est alors obtenue par intercorrélation entre le signal de référence ref et le signal dit de surveillance sur :
Et nous avions montré qu’en passant par le théorème de convolution, cette expression s’implémente efficacement dans le domaine spectral grâce à la transformée de Fourier rapide FFT (et son inverse iFFT) par :
Ici * est le complexe conjugué. Cette expression nous avait permis d’implémenter la corrélation dans GNU Radio grâce aux blocs FFT, tel que nous l’avons vu en figure 1. Cette fois, au lieu de deux récepteurs de télévision numérique terrestre, nous avons 4 voies d’oscilloscope, une de référence et trois de surveillance, qui en plus sont supposées être toutes synchrones. Ainsi, nous pourrons ajouter à la mesure de temps de vol et de vitesse de la cible une mesure de direction d’arrivée, en mesurant la phase entre les corrélations issues des diverses voies.
Le seul degré de liberté dans cette équation est la durée d’intégration : l’infini étant relativement grand, il est courant de le tronquer en discrétisant l'équation (1) sous forme de :
Où il faut choisir N « judicieusement ». Dans l’exemple de la figure 1, nous avons un a priori sur le fait que la longueur d’un câble coaxial ne fait pas plus de (choix arbitraire) 10 m, ou un retard de 50 ns. Si nous échantillonnons à 5 GS/s (200 ps de période d’échantillonnage), alors ce retard se traduit par 50 / 0,2 = 250 points. Il ne sera donc pas utile de faire des corrélations sur plus de 500 points pour rechercher des retards de l’ordre de 10 m. D’un autre côté, le rapport signal à bruit s’améliore comme le produit BT de la bande passante du signal mesuré B par le temps d’intégration T : dans un contexte de faible rapport signal à bruit tel qu’observé dans les RADAR, il peut être judicieux d’augmenter N = BT (traduisant l’augmentation de T), ici en supposant que la bande passante du signal (source de bruit large bande) est uniquement limitée par la fréquence d’échantillonnage.
Ces concepts généraux étant acquis et visant à démontrer l’utilité d’une source de signaux radiofréquence large bande discontinue, nous allons nous efforcer de les mettre en œuvre comme bloc source intégré dans GNU Radio, d’abord dans sa version 3.7 encore disponible dans la majorité des distributions GNU/Linux, puis dans sa version 3.8 fraîchement sortie au mois de juillet 2019.
2. Bloc source pour GNU Radio 3.7
La transition de GNU Radio 3.7 à 3.8 vient d’être annoncée : nos développements initiaux se sont portés sur la première version que nous allons aborder ici, sans douter que cette version reste active dans les années à venir, avant que la transition vers 3.8 ne soit effective.
2.1 Premiers pas : gr_modtool
Les blocs de traitement sont écrits en Python ou en C++. Par souci d’efficacité, nous nous focaliserons sur le second langage que nous maîtrisons mieux. L’arborescence d’un squelette de bloc de traitement GNU Radio est initialisée par l’outil gr_modtool dans lequel nous déclarons tout d’abord le nom de l’ensemble des blocs que nous allons déclarer et ensuite, le nom de chaque bloc individuel (par exemple, nous pourrions vouloir créer l’ensemble des blocs « arithmétiques » qui contiendrait les divers blocs « addition », « multiplication », « division » et « soustraction »). Dans notre cas, les choses seront simples : la classe sera oscilloscope contenant un unique bloc de traitement nommé lui aussi oscilloscope :
Nous avons comme type de bloc source, comme langage cpp, pas d’argument et pas de code de gestion de qualité. Nous voilà donc équipés de l’arborescence pour travailler, avec dans l’ordre d’importance :
- lib le répertoire contenant le code source en C++ du bloc ainsi que le fichier d’en-tête associé déclarant les variables privées et publiques de la classe ;
- include contenant le prototype du constructeur de la classe ;
- grc contenant la description, au format XML, des entrées et sorties ainsi que les paramètres modifiables par l’utilisateur du bloc pour informer l’interface graphique gnuradio-companion de ces interfaces.
Nous ne toucherons pas pour le moment aux autres répertoires (cmake pour les scripts de compilation, python pour le code d’instanciation du bloc dans une chaîne de traitement, swig pour exporter les symboles du C++ vers Python [8] ou examples et doc, dont l’utilité est évidente) de l’arborescence.
En commençant par regarder lib/oscilloscope_impl.cc, nous voyons un certain nombre de motifs de la forme <+MIN_OUT+>, <+MAX_OUT+> ou <+OTYPE+> qu’il faudra remplacer par le nombre minimum et maximum d’interfaces – dans notre cas entre 1 et 4 dans l’hypothèse qu’au moins une voie de l’oscilloscope est utilisable et au maximum 4 sont accessibles – et la nature des interfaces – GNU Radio manipule classiquement des nombres à virgule flottante compris entre -1 et +1, donc nous remplaçons <+OTYPE+> par float. Comme c’est un bloc source, il ne prend aucune entrée et gr_modtool a déjà déclaré gr::io_signature::make(0, 0, 0) indiquant l’absence de données lues.
Il est judicieux à ce point de valider son installation de GNU Radio en compilant ce bloc de traitement totalement inutile, puisque nous n’avons que déclaré les interfaces, mais néanmoins fonctionnel. Comme dans tout projet faisant appel à cmake, nous créons build, y entrons et lançons cmake ../ qui génère un Makefile permettant la compilation par make. Si l’environnement de développement est fonctionnel, la compilation s’achève en quelques secondes par [100%] Built target doxygen_target : nous voilà avec une arborescence exploitable.
Attention !
Le bloc que nous venons de compiler n’est pas visible de GNU Radio ni de gnuradio-companion. Pour ce faire, il nous faudra faire un make install afin de copier les fichiers utiles dans l’arborescence de GNU Radio.
Noter que pour le moment, nous n’avons pas renseigné les interfaces dans le fichier XML disponible dans le répertoire grc pour décrire les interfaces et les paramètres du bloc, opération que nous compléterons ultérieurement. On pourra toutefois déjà se convaincre de la facilité de générer ce fichier XML en se plaçant dans le répertoire gr-oscilloscope et en lançant gr_modtool makexml ; après avoir autorisé à écraser le contenu de l’exemple initial (qu’il peut être bon de conserver dans un coin pour référence), nous constatons que grc/oscilloscope_oscilloscope.xml contient une description fidèle de notre bloc :
Donc, notre bloc se trouvera dans le menu Oscilloscope de gnuradio-companion, se charge dans Python 2.7 par import oscilloscope et ne contient que des sorties au nombre de 4, communiquant des nombres à virgule flottante. Nous verrons plus tard comment rendre le nombre d’interfaces paramétrable. Le squelette de fichier XML de configuration issue de l’analyse des fichiers d’en-tête de l’implémentation du bloc devra de toute façon être corrigé pour répondre à toutes nos attentes (nombre variable de voies de mesure, fonctions appelées pour modifier dynamiquement les paramètres d’acquisition).
Notre travail se limite donc à remplir le constructeur (oscilloscope_impl::oscilloscope_impl()), éventuellement le destructeur (oscilloscope_impl::~oscilloscope_impl()), mais surtout la fonction principale qu’est la méthode oscilloscope_impl::work(). Tous ces prototypes de fonctions sont décrits dans le fichier d’en-tête dans le répertoire lib et l’implémentation de ces fonctions se trouve dans le fichier d’extension .cc dans ce même répertoire.
2.2 Paramétrage du bloc
Un oscilloscope est paramétré par trois valeurs qui vont nous intéresser ici : l’amplitude des signaux (range), la fréquence d’échantillonnage (sampling rate) et la durée de l’acquisition (duration). Le produit de la fréquence d’échantillonnage par la durée d’acquisition détermine le nombre de points acquis, limité par la profondeur mémoire de l’instrument. Plus la durée est longue, plus la cible peut être loin (équivalent du taux de répétition de l’impulsion – PRR pour Pulse Repetition Rate – dans un RADAR impulsionnel), mais plus le nombre de points à traiter sera important à bande passante (double de la fréquence d’échantillonnage) donnée. Par ailleurs, nous voulons pouvoir définir l’adresse IP de l’oscilloscope avec lequel nous communiquons et le nombre de voies acquises, et ce, évidemment sans avoir à recompiler le bloc de traitement. Nous avons ainsi établi la liste des paramètres communiqués au constructeur :
Ceci est donc une source n’acceptant aucune entrée (signature de l’entrée avec 0 voies) et propose entre 1 et 4 sorties (signature de la sortie (1,4)) de type nombre à virgule flottante. Les cinq paramètres sont la chaîne de caractères représentant l’adresse IP, les trois paramètres de configuration de l’oscilloscope, et le nombre de voies mesurées. Le prototype de la fonction dans le fichier d’en-tête include/oscilloscope/oscilloscope.h est mis à jour en accord avec cette définition : static sptr make(char*,float,float,float,int);. On notera que cette syntaxe, valable en C, ne plaît pas à gr_modtool qui désire avoir le nom des variables associées à chaque paramètre, ou tout au moins, un espace après chaque type de variable, sans laquelle l’analyseur syntaxique échoue.
Le lien entre fichiers de configuration XML et C++ est décrit dans le bout de code ci-dessous :
GNU Radio Companion sait ainsi qu’un paramètre rate est ajustable par l’utilisateur dans le bloc source, qu’il s’agit d’un entier, et que ce paramètre est fourni au constructeur comme troisième argument.
De la même façon, la fonction principale de travail est adaptée à cette configuration avec :
Dans ce code, noutput_items informera du nombre de données que nous renvoyons – en accord avec la demande de l’ordonnanceur qui indique dans cette variable le nombre de données qu’il voudrait recevoir – dans le tableau output_items. Ce tableau de tableau pointe sur quatre sous-tableaux qui contiendront chacun les données des voies de mesure de l’oscilloscope : nous avons nommé ces tableaux outi, i [0..3], et ces pointeurs visent les entrées de output_items.
Nous complétons lib/oscilloscope_impl.h avec les variables privées et publiques de la classe, en proposant dès à présent un découpage du programme dans lequel les fonctions définissant les paramètres ajustables par l’utilisateur sont séparées, découpage dont nous verrons l’intérêt plus bas. Ainsi, lorsque nous voulons modifier par exemple la fréquence d’échantillonnage, nous appellerons la fonction publique void set_rate(float); qui stocke dans la variable privée float _rate la valeur fournie par l’utilisateur en vue de la distribuer aux autres fonctions de la classe.
2.3 Remplissons le bloc de traitement : communication par VXI11
La majorité des instruments récents communique sur bus Ethernet au travers du protocole VXI11 (ou LXI, http://www.lxistandard.org/). Ainsi, notre constructeur sera principalement chargé d’initialiser le socket pour ouvrir le port de communication et de configurer l’instrument. Pour ce faire, nous nous inspirons de n’importe quel exemple de https://github.com/applied-optics/agilent_vxi11/blob/master/library/agilent_vxi11.c, par exemple avec l’ouverture de la communication par vxi11_open_device qui prend en argument la chaîne de caractères de l’adresse IP et renvoie la structure VXI11_CLINK * qui sera utilisée au cours de toutes les transactions. Nous avons encapsulé les fonctions de lecture et d’écriture vxi11_receive() et vxi11_send() de façon à les rendre facilement adaptables à tout autre protocole de communication. Adapter le programme à un nouveau type d’instrument se résume donc à appréhender la liste des instructions GPIB fournies dans le manuel de l’utilisateur (par exemple https://scdn.rohde-schwarz.com/ur/pws/dl_downloads/pdm/cl_manuals/user_manual/1326_1032_01/RTE_UserManual_en_15.pdf avec sa section 17 intitulée « Remote Control Commands »). Nous faisons bien entendu l’hypothèse que l’utilisateur a convenablement configuré les interfaces réseau de son ordinateur, en particulier en définissant l’adresse IP de l’interface Ethernet sur le même sous-réseau que l’oscilloscope.
Serveur TCP simulant une source de données
Tous les lecteurs n’ont pas forcément un oscilloscope radiofréquence à leur disposition. Par ailleurs, il peut être intéressant d’exploiter gr-oscilloscope avec des sources de données autres qu’un oscilloscope – par exemple, transmis sur Internet par TCP/IP – voire pour synthétiser des signaux simulant divers types de bruits ou de sources difficiles à observer expérimentalement. Nous nous proposons donc de fournir un serveur TCP/IP générant un flux de données alimentant gr-oscilloscope. Ce dernier appliquera le protocole implémenté par ce serveur si l’adresse IP est 127.0.0.1 (localhost), et le protocole VXI11 d’un oscilloscope sinon.
Notre serveur TCP/IP reçoit, à la connexion du client, le nombre de voies attendues, et se contente ensuite d’envoyer le nombre de données attendues par le client.
Il s’agit de l’archétype du serveur TCP/IP sans aucune subtilité : un socket est créé en mode connecté (TCP, SOCK_STREAM) compatible Internet (IP, AF_INET), associé (bind()) au port 9999, et attend (listen()) une connexion d’un client. Lors de la connexion, nous récupérons le descripteur de fichier associé (accept()) et échangeons (recv() et send()) jusqu’à interruption des échanges, lorsque le client requiert un nombre négatif d’échantillons. Une fois la connexion rompue, le serveur attend une nouvelle connexion.
Dans cet exemple, nous remplissons séquentiellement les tampons contenant les données de chaque voie de sinusoïdes de fréquence croissante, mais il serait très simple de fournir des séquences aléatoires décalées dans le temps pour simuler le retard introduit par le temps de vol du signal entre l’émetteur, la cible et le récepteur.
Dans le cas du serveur TCP/IP qui émule l’oscilloscope, nous ferons explicitement appel à la séquence de connexion du client par l’habituel :
En prenant soin de bien passer tous les paramètres avec l’endianness du réseau :
Nous pouvons immédiatement remplir le destructeur qui libère les ressources : de nouveau dans le cas du serveur TCP/IP, cela se résume par :
Ceci informe le serveur de la fin des transactions (requête d’un nombre négatif de données) et ferme le socket.
L’initialisation étant achevée, la méthode work est celle sollicitée systématiquement par l’ordonnanceur GNU Radio et donc la plus importante. Notre choix a été de collecter une trace sur l’oscilloscope répondant aux attentes de l’utilisateur en termes de durée de la mesure, et de distiller ces points à l’ordonnanceur selon ses besoins. En effet, l’ordonnanceur GNU Radio requiert un nombre variable de points selon le débit des traitements et la fréquence d’échantillonnage, et nous ne saurions prédire cette valeur ou voir si elle est compatible avec les paramètres d’acquisition d’un oscilloscope. Par ailleurs, nous voulons limiter le temps perdu en transactions sur le bus Ethernet et l’acquisition de gros paquets de données limitera les latences induites par les communications.
Le code ci-dessous contient la fonction de collecte des informations : si une variable _num_values indexant le nombre de données restant en réserve dans le tampon a atteint 0, il faut acquérir un nouveau jeu de points en transmettant la commande GPIB adéquate (RUNSINGLE chez Rohde & Schwarz), attendre la fin de l’acquisition (OPC?) et lire les mesures (CHANx:WAV:DATA? pour la voie x) pour les stocker dans des tableaux temporaires _tabx, x [0..3].
Dans le cas contraire où assez de points sont encore disponibles en mémoire pour répondre aux demandes de l’ordonnanceur GNU Radio, nous transférons des tampons vers les tableaux renvoyant les mesures pour poursuivre la chaîne de traitements :
Cette séquence de mesures est encore plus simple avec le serveur TCP/IP qui renvoie les informations à la demande, sans contrainte physique de configuration de l’instrument. Encore une fois, nous prendrons simplement soin de convertir tous les échanges en endianness du réseau IP (host to network) par ntohl(). Un exemple d’une telle transaction est illustré en figure 2
Les grandes lignes du code étant appréhendées, il nous reste à compiler le programme. Alors que RPC est au cœur de nombreux protocoles (incluant NFS), Sun RPC n’est plus installé par défaut depuis la version 2.26 de glibc [9]. Pour continuer à accéder à ces fonctionnalités, la bibliothèque doit être compilée avec l’option –enable-obsolete-rpc. Sur certaines distributions, telle que Debian, cette option semble active puisque /usr/include/rpc/rpc.h est toujours présent. Dans d’autres, comme Gentoo, rpc.h n’est disponible qu’après avoir installé libtirpc, avec comme effet de bord que rpc.h ne se trouve plus dans /usr/include/rpc/, mais dans /usr/include/tirpc/rpc/. Dans ce cas, il faut aider cmake à trouver les bons chemins pour accéder à l’en-tête et à la bibliothèque. Nous avons donc ajouté dans cmake/Modules un fichier Findlibtirpc.cmake (le nom est imposé par cmake) :
Ce fichier a pour rôle de chercher les répertoires où se trouvent rpc.h et libtirpc.so pour ensuite ajouter ceux-ci dans les variables utilisées par cmake et Makefile lors de la compilation.
Pour utiliser ce fichier, et ajouter les chemins lors de la compilation, le fichier lib/CMakeLists.txt doit être modifié afin d’ajouter :
Il faut changer la ligne suivante :
Et la remplacer par :
RPC étant accessible, il reste à ajouter les bibliothèques de communication VXI11 que nous avons clonées depuis https://github.com/applied-optics/vxi11 pour les placer dans le répertoire lib de notre projet – les sources nécessaires sont donc dans lib/vxi11/library – en complétant CMakeLists.txt du répertoire lib/ par :
Le programme résultant permet de collecter continûment des mesures de plusieurs voies d’un oscilloscope pour les traiter en temps réel par GNU Radio (figure 3), en particulier pour des traitements un peu plus subtils que le simple affichage, permettant de mettre en évidence des défauts de l’instrument.
En effet, la corrélation de deux voies alimentées par le même signal périodique présente des sauts de phase qui ne peuvent être attribuées qu’à une désynchronisation d’un échantillon des flux de données transmis par l’instrument (figure 4), un effet catastrophique pour un traitement cohérent des mesures.
3. Fonctions de callback
Le programme de base est fonctionnel, nous sommes capables de collecter des mesures d’un oscilloscope sur 1 à 4 voies et d’alimenter ainsi une chaîne de traitement GNU Radio définie graphiquement dans GNU Radio Companion. Cependant en l’état, toute modification des paramètres d’acquisition nécessite d’interrompre l’exécution de la chaîne de traitement, de modifier le paramètre dans le bloc source et de relancer la chaîne, une situation peu confortable qui ne répond pas nécessairement aux attentes de l’utilisateur qui désire interagir dynamiquement avec son instrument.
GNU Radio propose un mécanisme dans lequel les modifications de paramétrage du bloc source sont détectées et transmises à des fonctions spécifiquement associées à chaque paramètre : il s’agit des fonctions de callback que nous avions pris soin de séparer de la fonction principale work lors de la conception du programme d’acquisition des données (p. ex. la fonction set_rate() que nous avions mentionnée). Ce lien est créé dans le fichier XML de configuration de GNU Radio Companion en cohérence avec les fonctions implémentées en C++. Ainsi, quatre fichiers sont mis à jour pour implémenter les fonctions de callback :
- gr-oscilloscope/grc/oscilloscope_oscilloscope.xml définit la nature (entier, nombre à virgule flottante, complexe, chaîne de caractères, etc.) et le nom des paramètres ;
- gr-oscilloscope/include/oscilloscope/oscilloscope.h définit dans le prototype du constructeur les paramètres fournis au C++ au lancement du bloc ;
- gr-oscilloscope/lib/oscilloscope_impl.h définit les variables privées qui contiennent les valeurs des paramètres qui seront mises à jour par les fonctions de callback ;
- gr-oscilloscope/lib/oscilloscope_impl.cc définit l’implémentation en C++ des fonctions de callback et en particulier, les actions à prendre sur la configuration de l’oscilloscope par commande VXI11, en réponse aux changements de paramètres requis par l’utilisateur.
Nous avons mis en œuvre les fonctions de callback pour changer les paramètres de mesure tels que la fréquence d’échantillonnage, la durée de la mesure ou la gamme de tensions acquises. À titre d’illustration, le changement de fréquence d’échantillonnage se traduit par :
Ainsi, changer la fréquence d’échantillonnage, à durée de mesure donnée, modifie le nombre de points acquis et nécessite donc de réallouer la mémoire occupée par les divers tableaux contenant les points de mesure. Cette fonction reste valable lors de l’initialisation puisque realloc() se comporte comme malloc si le pointeur fourni en argument est NULL. La commande GPIB configurant la fréquence d’échantillonnage de l’oscilloscope est transmise par VXI11 et finalement, la nouvelle valeur de la fréquence d’échantillonnage est stockée dans la variable privée _rate de la classe, pour être disponible par les autres méthodes.
Il nous reste à faire le lien entre Python et C++ : les fonctions de callback sont déclarées comme publiques dans oscilloscope_impl.h du répertoire lib :
Et le fichier de configuration XML de GNU Radio Companion est renseigné du nom de la fonction de callback associée au paramètre modifiable (la déclaration du paramètre restant la même que vue précédemment) :
À l’issue de ces modifications, les paramètres sont désormais modifiables dans la chaîne de traitement en cours d’exécution (figure 5), par exemple au moyen d’un menu déroulant (Qt GUI Chooser pour limiter les choix à quelques valeurs prédéfinies) ou d’un curseur glissant (Qt GUI Range).
Nous voici donc munis d’un bloc fonctionnel pour traiter un flux de données issu d’une ou plusieurs voies d’oscilloscope pour GNU Radio 3.7.
4. Passage à GNU Radio 3.8
GNU Radio 3.8 (https://github.com/gnuradio/gnuradio/releases/tag/v3.8.0.0) est en pleine effervescence. Compte tenu du nombre de modifications encore apportées pour stabiliser cette version, il semble judicieux de compiler depuis les sources au lieu de s’appuyer sur une distribution binaire (Debian/testing et Debian/sid fournissent désormais GNU Radio 3.8) qui aura toutes les chances d’être obsolète, face à la version de développement. Néanmoins, nombre de blocs sources (OsmoSDR, PlutoSDR) ou de traitement sont en cours de portage vers 3.8, donc conserver une version de 3.7 fonctionnelle pour ses activités quotidiennes reste utile.
Certaines recettes (fichiers .lwr, voir ci-dessous) proposées par PyBOMBS ne sont compatibles qu’avec GNU Radio 3.7 et doivent être mises à jour. C’est le cas pour installer gr-iio qui fournit les blocs de communication IIO, notamment pour la PlutoSDR : pybombs install gr-iio indique que libad9361 et libiio vont être installées et dépendent de gnuradio qui est actuellement la version 3.8. Cependant, la branche sélectionnée par défaut de gr-iio inclut des en-têtes de GNU Radio 3.7. Cette erreur se corrige en remplaçant dans gr-iio.lwr : gitrev: tags/v0.3 par gitbranch: upgrade-3.8. C’est également le cas pour OsmoSDR qui fournit le support des récepteurs DVB-T gr-osmosdr : nous modifions gr-osmosdr.lwr pour remplacer gitbranch: master par gitbranch: gr3.8 et le contenu du champ source par https://github.com/mvaenskae/gr-osmosdr.git. La figure 6 montre le bon fonctionnement de ces blocs.
La compilation de GNU Radio par PyBOMBS (https://github.com/gnuradio/pybombs) permet d’isoler tout l’environnement de développement dans un répertoire donné, sans risquer de polluer son installation « stable » : PyBOMBS est un utilitaire écrit en Python qui fait usage de fichiers .lwr pour fournir les règles pour compiler l’ensemble d’un environnement (liste des paquets) ou pour compiler un paquet spécifique (dépendances dudit paquet, URL du dépôt à télécharger, options de compilation, quel est le paquet équivalent dans la distribution hôte...).
La première étape est d’installer PyBOMBS lui-même. Nos tests sur une installation à l’aide de pip3 (version dédiée à Python3) ayant montré des soucis, il semble, en l’état, préférable de se baser sur le dépôt.
Noter le --user pour installer PyBOMBS dans $HOME/.local et non dans /usr/local. Il est nécessaire de s’assurer que $HOME/.local/bin est dans le $PATH.
Ensuite, nous devons configurer PyBOMBS par :
Pour générer le fichier $HOME/.pybombs/config.yml adapté à la distribution (principalement le gestionnaire de la distribution).
Finalement, la commande suivante va télécharger dans $HOME/.pybombs/recipes les deux dépôts de recettes gr-etcetera et gr-recipes, fournissant les recettes .lwr :
Avant de continuer et de lancer la compilation de GNU Radio 3.8, plusieurs étapes doivent être réalisées (qui ne seront à terme plus nécessaires, dès lors que https://github.com/gnuradio/gr-recipes/pull/155 aura été appliqué). La première est d’installer manuellement les paquets click-plugins et pyqtgraph (python3-click-plugins et python3-pyqtgraph en Debian/testing) avec le gestionnaire de paquets de sa distribution. En effet, GNU Radio dépend de ceux-ci, mais ces derniers ne sont pas listés comme dépendances.
La recette gnuradio-default.lwr dépend de gnuradio.lwr qui pointe sur le dépôt maint-3.8. Selon [10], depuis la sortie de la première version stable de GNU Radio 3.8, la branche master est dédiée à la future 3.9 et une branche spécifique (maint-3.8) a été créée pour suivre les évolutions de 3.8 (exclusivement sur un aspect corrections et optimisations). L’utilisateur désireux de tenter cette toute nouvelle version (3.9) peut modifier, dans $HOME/.pybombs/recipes/gr-recipes/gnuradio38.lwr la ligne :
Par :
Mais contentons nous pour le moment de la version 3.8, en lançant la compilation par :
En supposant que nous voulions installer GNU Radio 3.8 dans le sous-répertoire gnuradio38 de son répertoire maison ($HOME). Noter que les utilisateurs de la distribution Fedora (s’il y en a) risquent de rencontrer une difficulté lors de cette compilation, tel que décrit sur https://github.com/gnuradio/gnuradio/issues/1444, nécessitant d’ajouter l’option -DENABLE_CTRLPORT_THRIFT=OFF dans la recette gnuradio38.lwr. Les utilisateurs de l’interface graphique IceWM pourront lier (ln -s) pkexec à sudo pour installer les paquets des dépendances en super-utilisateur, l’agent d’authentification ne semblant pas disponible pour ce gestionnaire de fenêtres. Parmi les déboires rencontrés, le paquet libqwt-dev n’existe plus dans Debian/sid et Debian/testing : on pensera à retirer cette dépendance de .pybombs/recipes/gr-recipes/qwt6.lwr (règle satisfy:deb : libqwt-dev >= 6.1 sans quoi PyBOMBS tentera de compiler une version obsolète de libqwt pour Qt5).
Les mises à jour ultérieures s’obtiendront par :
À l’issue de la compilation d’une version donnée de GNU Radio, et 10 GB pour 3.8.0 de moins sur son disque dur, cette version sera utilisée en chargeant les variables d’environnement associées fournies par PyBOMBS, au moyen de source setup_env.sh.
Attention !
GNU Radio Companion charge toujours la même configuration, à savoir les chaînes de traitement précédemment en cours d’édition, qu’il s’agisse de 3.7 ou de 3.8. Or, la description des chaînes de traitement n’est pas compatible entre 3.7 et 3.8 : toute chaîne (fichier grc) ouverte en 3.8 devient illisible en 3.7. On pensera donc à bien fermer tous les onglets avant de quitter GNU Radio Companion, tant que les deux versions de GNU Radio Companion doivent cohabiter.
L’environnement de travail de GNU Radio 3.8 ne diffère pas significativement de celui de 3.7 à quelques subtilités près [10], la plus visible étant probablement le passage d’une description des fichiers de configuration de GNU Radio Companion de XML vers YAML, brisant tout espoir de compatibilité avec les anciennes versions. Néanmoins, le passage d’un format à l’autre est simplifié par la méthode gr_modtool makeyaml qui se charge de créer le fichier de configuration de GNU Radio Companion, en analysant le fichier d’en-tête de nos blocs. Évidemment, ce générateur automatique de code ne peut pas savoir que nous désirons dynamiquement modifier le nombre de sorties du bloc en fonction de la configuration de l’oscilloscope – entre 1 et 4 voies de mesure – et nous devons modifier le fichier YAML généré en conséquence :
Ici, nous avons remplacé le champ multiplicity de sa valeur initiale (4 dans notre configuration) par la variable ${channels} qui définit dans notre interface le nombre de voies de mesure (paramètre aussi fourni à l’implémentation du bloc en C++).
Attention !
Il est tentant de cp -r un bloc GNU Radio 3.7 pour le porter à 3.8. Parmi les déboires que nous avons rencontrés par cette approche, une subtile modification du comportement de Python 3 par rapport à Python 2 porte sur la syntaxe de SWIG : nous prendrons soin de vérifier que python/__init__.py importe la classe du nouveau bloc par from .oscilloscope_swig import * : la version Python 2 utilisée par GNU Radio 3.7 omettait le « point » devant oscilloscope_swig, se traduisant par une erreur d’objet introuvable lors de l’exécution du bloc. La création d’un bloc par gr_modtool de GNU Radio 3.8 ne souffre pas de ce problème, en créant un python/__init__.py avec le bon format d’appel à l’objet.
Modification dynamique des paramètres : callbacks
Pour conclure ce tour d’horizon de l’écriture d’un bloc pour GNU Radio 3.8, nous désirons ajouter la capacité à dynamiquement modifier certains paramètres de l’oscilloscope comme nous le ferions avec un vrai instrument, tel que la durée de la trace, la fréquence d’échantillonnage ou la gamme de tensions. Nous avons déjà pris soin de séparer les fonctions de configuration du programme principal : il nous suffit de déclarer ces fonctions comme callback appelées chaque fois que GNU Radio se rendra compte que nous voulons modifier un paramètre depuis un ascenseur (Qt GUI Range) ou une série d’onglets (Qt GUI Chooser). Nous nous inspirons de gnuradio38/gr-trellis des sources de GNU Radio en observant les appels à la fonction set_O(), pour constater que 4 fichiers sont à adapter pour implémenter une fonction de callback :
- dans include/block/block.h qui est inclus par SWIG pour déclarer les liens entre Python et C++, nous déclarons le prototype de la fonction de callback comme un virtual void ;
- dans lib/block_impl.h, nous déclarons le prototype de la fonction de callback qui est implémentée dans lib/block_impl.cc ;
- finalement, GNU Radio Companion est informé dans le fichier de configuration YAML du lien entre une variable et cette fonction de callback par le mot clé callbacks, après les motifs définis dans le sous-ensemble templates :
Par exemple (figure 7) pour modifier la fréquence d’échantillonnage (noter qu’il est peu judicieux d’utiliser rate qui semble être un mot clé de GNU Radio Companion).
5. Quelques applications
Nous avons introduit l’article par la démonstration du temps de vol d’un signal électromagnétique dans une série de câbles coaxiaux pour en retrouver la longueur par corrélation d’un bruit large bande enregistré sur les divers canaux d’un oscilloscope radiofréquence. Nous proposons ici quelques applications plus courantes d’analyse spectrale.
5.1 Affichage de spectre
Un spectre n’a aucune prétention de mesure continue du flux de données acquises à un débit radiofréquence. Ainsi, en collectant des paquets de données, nous pourrons identifier les diverses sources radiofréquences émettant à divers instants : moins la latence entre deux paquets de données est importante, meilleure est notre chance de capturer un émetteur intermittent.
L’application est certes un peu triviale, mais permet de valider le bon fonctionnement de l’application dans un contexte bien connu (figure 8).
5.2 Spectre de bruit
Le corollaire de l’affichage d’un spectre est d’afficher un spectre de bruit d’un composant. Il s’agit d’une mesure classique pour établir la limite de détection d’un instrument. Ici, l’intérêt de l’analyse porte sur le passage d’une mesure de tension en unité arbitraire (valeurs sur 16 bits) en valeurs quantitatives, avant de passer au spectre des fluctuations de tension. Pour rappel, on passe d’une tension V à une puissance V2/Z en divisant par l’impédance de charge Z (habituellement 50 Ω en radiofréquence), et l’affichage dans le domaine spectral au lieu du domaine temporel s’obtient par |FFT(V)|2/Z. Enfin, la densité spectrale de bruit s’obtient en normalisant par la largeur RBW d’un intervalle de FFT, soit la fréquence d’échantillonnage fs divisée par le nombre de points N sur lequel est calculée la transformée de Fourier : RBW=fs/N permet d’obtenir S=10 log10(|FFT(V)|2/Z) - 10log10(fs/N) + 30 dBm/Hz (le +30 vient de la conversion de W en mW).
Conclusion
Nous avons pris le prétexte d’acquérir des signaux radiofréquences d’un oscilloscope pour présenter la séquence de compilation de blocs de traitement dédiés pour GNU Radio 3.7 et 3.8, permettant notamment d’alimenter la chaîne d’analyse par des signaux synthétiques issus d’un serveur TCP/IP. Cette exploration a été l’occasion d’identifier le dysfonctionnement de certains instruments qui perdent des échantillons entre leurs voies supposées synchrones. L’application envisagée de réflectométrie, aussi applicable aux cas des RADAR passifs ou RADAR à bruit, démontre qu’un flux de données large bande discontinu peut avoir un intérêt. Pour rappel, les dépôts de gr-oscilloscope pour GNU Radio 3.7 et 3.8 se trouvent respectivement à http://github.com/jmfriedt/gr-oscilloscope et http://github.com/jmfriedt/gr-oscilloscope38.
Remerciement
J.-MF a découvert l’utilisation de l’oscilloscope radiofréquence comme source de données dans les applications de RADAR passif dans le laboratoire du Pr. M. Sato au CNEAS à Sendai, Japon (voir par exemple la figure 1 de [11]).
Références
[1] J.-M FRIEDT, « Matériel pour la radio logicielle », GNU/Linux Magazine France n°224, mars 2019 : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-224/Materiel-pour-la-radio-logicielle
[2] J.-M FRIEDT, « Contrôle d’instruments scientifiques : les protocoles GPIB, VXI11 et USBTMC », GNU/Linux Magazine France n°124, février 2010 : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-124/Controle-d-instruments-scientifiques-les-protocoles-GPIB-VXI11-et-USBTMC
[3] https://www.rtl-sdr.com/gr-oscilloscope-using-an-oscilloscope-as-a-software-defined-radio/
[4] J. BLOOMER, « Power Programming with RPC, UNIX Network Programming », O’Reilly & Associates, 1992.
[5] S.D. SHARPLES, « VXI11 Ethernet Protocol for Linux » : http://optics.eee.nottingham.ac.uk/vxi11/ (mis à jour le 30 juillet 2015, accédé en août 2019) et https://github.com/applied-optics/vxi11
[6] D. BODOR, « Mesurez la vitesse de la lumière dans les câbles ! », Hackable n°25 pp. 64–71, juillet-août 2018 : https://connect.ed-diamond.com/Hackable/HK-025/Mesurez-la-vitesse-de-la-lumiere-dans-les-cables
[7] J.-M FRIEDT, « RADAR passif par intercorrélation de signaux acquis par deux récepteurs de télévision numérique terrestre », GNU Linux Magazine France n°212, février 2018 : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-212/RADAR-passif-par-intercorrelation-de-signaux-acquis-par-deux-recepteurs-de-television-numerique-terrestre
[8] W. DANIAU, « Interfaçage de code C++ pour Ruby et Python avec SWIG », GNU/Linux Magazine France n°226, mai 2019 : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-226/Interfacage-de-code-C-pour-Ruby-et-Python-avec-SWIG
[9] https://sourceware.org/ml/libc-alpha/2017-08/msg00010.html
[10] https://radiochirurgie/index.php/GNU_Radio_3.8_OOT_Module_Porting_Guide
[11] W. FENG, J.-M. FRIEDT, G. CHERNIAK, Z. HU, et M. SATO, « Direct path interference suppression for short range passive bistatic SAR imaging based on atomic norm minimization and Vandermonde decomposition », IET Radar, Sonar and Navigation, 2019, DOI : 10.1049/iet-rsn.2018.5214