Affichage des articles dont le libellé est rtl-sdr. Afficher tous les articles
Affichage des articles dont le libellé est rtl-sdr. Afficher tous les articles

dimanche 12 décembre 2021

HC-12

Le HC-12 est un petit module radio bon marché pour faire de la communication sans fil entre Arduino et trucs du genre. La communication se fait par ligne série et ne nécessite que 4 fils (GND, VDD, TX, Rx). Il est possible de changer les paramètres de communication au moyen de commandes 'AT' après avoir mis la ligne 'SET' à la masse (elle est équipée d'une pull-up et, par défaut, le module est en mode transmission/réception sur 433.4 MHz et 9600 bauds 8 bits, un stop bit et pas de parité. La communication radio utilise un Si4463 de Silicon Labs piloté par un microcontrôleur STM8S avec un code non public (ici, il se propose d'en modifier le firmware...).

Raspberry Pi

Les quatre fils peuvent facilement être connectés à un Raspberry Pi. La documentation du module semble être une traduction du chinois et l'utilisation des commandes 'AT' n'est pas claire... En principe, il suffirait de prendre minicom et de faire
$ minicom -b 9600 -D /dev/ttyS0
et de taper 'AT' pour voir apparaître 'OK' mais le Diable se cache dans les détails et plusieurs obstacles doivent d'abord être franchis. D'abord, pour pouvoir accéder au ttyS0 comme utilisateur normal, il faut qu'il appartienne au groupe 'dialout' (sudo usermod -a -G dialout <user>). Ensuite, comme rien ne fonctionne comme prévu, revenons à la base et connectons le TX du Raspberry Pi au TX. On s'aperçoit alors qu'il y a un getty qui tourne par défaut. Il faut le désactiver (via sudo raspi-config). Après avoir raccordé le SET du module au GND du Raspberry, on s'aperçoit que le 'T' doit suivre de très près le 'A' pour que le firmware comprenne 'AT' (copier/coller dans minicom). Et donc, tout va bien, le module semble fonctionner. Déconnectons SET et utilisons le module en configuration standard (433.4 MHz, 9600 bauds 8 bits, un stop bit et pas de parité et ...puissance radio maximum).

Il est sûrement possible de communiquer entre deux modules (c'est fait pour ça) mais, ce qui m'intéresse c'est de voir à quoi ressemble le signal radio... Pour cela, je vais émettre de manière répétée une petite chaîne de caractères constante, ou même un caractère unique. Un 'U', par exemple, dont le code ASCII est 0x55 (0101.0101). Comme le message envoyé ne va pas être très long, je peux le répéter très souvent, genre 200 fois par seconde (après avoir testé avec un rythme moins soutenu).
$ while sleep .005; do echo -n "U" > /dev/ttyS0; done

GnuRadio

Pour voir si le module émet quelque chose, j'utilise GnuRadio avec un RTL-SDR (ou équivalent à quelques euros).
Le truc de base : un module source, rtl-sdr, et deux sink QT pour la visualisation, frequency et waterfall. Je prends une fréquence d'échantillonnage raisonnable et centre le tout sur 433.4 MHz.
Donc le module fonctionne et je peux générer du signal à volonté.

rtl-sdr et GNU Octave

Maintenant, enregistrons une courte séquence avec l'utilitaire 'rtl-sdr' sur le portable :
$ rtl_sdr -f 433400000 -s 512000 -n 64000 U.iq8
et utilisons GNU Octave pour visualiser le signal que l'on suppose être un type de modulation de fréquence (GFSK ou GMSK?). On s'intéresse donc à la phase (arg(i+j.q)). On utilise unwrap() pour éviter les saut 0/360°. Et, par tâtonnement, on sélectionne un intervalle contenant le signal recherché.
$ cat print_phase.m 
s=loadFile("U.iq8");
au=unwrap(arg(s));
plot(au(10000:14100));
print -dpng "signal.png"
Et nous obtenons :
NB: loadFile() lit un fichier IQ 8 bits de rtl-sdr pour en faire un vecteur complexe flottant.
$ cat loadFile.m 
function y = loadFile(filename)
%  y = loadFile(filename)
%
% reads  complex samples from the rtlsdr file 
%

fid = fopen(filename,'rb');
y = fread(fid,'uint8=>double');

y = y-127;
y = y(1:2:end) + i*y(2:2:end);
Si j'extrais une partie intéressante, la sors dans un fichier texte et l'affiche avec une grille adéquate dans Gnuplot, cela donne (après avoir zoomé dans le graphique) :
octave> ua2=unwrap(arg(loadFile("U.4399500l.256k.iq8")));
octave> plot(ua2)
octave> zz=ua2(4030:8102);
octave> plot(zz);
octave> csvwrite ('msg.dat', zz);
---
gnuplot> set style line 100 lt 1  lw 2
gnuplot> set grid ls 100
gnuplot> set xtics autofreq 17 
gnuplot> plot 'msg.dat' with lines lw 2
On observe des intervalles où la courbe (qui représente la phase du signal) change de pente et d'autres où elle conserve sa pente.

dimanche 17 février 2019

AIS again

Petite analyse d'une captation d'une minute avec un Orange Pi Zero et une clé RTL-SDR V3 autour de 162 MHz de 16000000 échantillons à 262144 échantillons par seconde (environ une minute donne un fichier de 32 MB).
/usr/bin/rtl_sdr -f 162000000 -s 262144 -n 16000000 core.iq
Je prends juste le quart du fichier pour accélérer le traitement dans GNU Octave (dd). Pour lire les données IQ 8 bits fournies par rtl_sdr, j'utilise une fonction dans loadFile.m :
function y = loadFile(filename)
%  y = loadFile(filename)
%
% reads  complex samples from the rtlsdr file 
%

fid = fopen(filename,'rb');
y = fread(fid,'uint8=>double');

y = y-127;
y = y(1:2:end) + i*y(2:2:end);
Avec plotspec.m,
% plotspec(x,Ts) plots the spectrum of the signal x
% Ts = time (in seconds) between adjacent samples in x
function plotspec(x,Ts)
N=length(x);                               % length of the signal x
t=Ts*(1:N);                                % define a time vector
ssf=(ceil(-N/2):ceil(N/2)-1)/(Ts*N);       % frequency vector
fx=fft(x(1:N));                            % do DFT/FFT
fxs=fftshift(fx);                          % shift it for plotting
subplot(2,1,1), plot(t,x)                  % plot the waveform
xlabel('seconds'); ylabel('amplitude')     % label the axes
subplot(2,1,2), plot(ssf,abs(fxs))         % plot magnitude spectrum
xlabel('frequency'); ylabel('magnitude')   % label the axes
je peux observer que j'ai bien enregistré des messages et qu'ils se situent bien à -25 KHz et + 25 KHz autour de 162 MHz.
Je vais maintenant tacher d'isoler un message à +25 KHz (up.iq8) et un autre à -25 KHz (down.iq8). Pour ce faire, je vais visualiser la magnitude du signal.
Zoomons...
Zoomons encore...
On peut maintenant extraire juste les messages du fichier core.iq8. Sachant qu'il y a deux bytes par échantillon et que le graphe nous donne les offsets absolus.
$ # 134000..141000  & 274000..281000
$ dd if=core.iq8 of=up.iq8 bs=2000 skip=134 count=7
$ dd if=core.iq8 of=down.iq8 bs=2000 skip=274 count=7
En cherchant un peu, on trouve un message à +25 Khz dont on s'intéresse à l'évolution de la phase :
En regardant la phase et en zommant :
Et la même chose pour un message à -25 KHz :
qui donne une évolution de la phase descendante :
On voit que, dans les deux cas, on a des sauts quand la phase passe au dessus de 360° ou en dessous de 0°. On va rendre tout cela continu pour, ensuite, soustraire/ajouter le changement de phase dû au +/- 25 KHz.
%  y = contzup(phi)
%
% rend la phase continue
%
function y = contzup(phi)
N=length(phi);                               % length of the signal x
phi=phi+(pi/2);
y=phi;
t=0;
for i = 1:N-1
        if ( phi(i) > phi(i+1) )
                        t = t + 2*pi;
        endif
        y(i+1) = (phi(i+1) + t)-(((2*pi)*25000*i)/262144);
%       y(i+1) = (phi(i+1) + t);
endfor


Détail, le début avec le préambule et les premiers octets.


avec le grid à 9600 samples/sec :



Comme je ne parviens pas vraiment à écrire un programme me permettant d'obtenir les trames NMEA à partir des IQ sortant de la clé RTL-SDR en bouquinant sur la théorie. J'envisage de partir d'un programme qui fonctionne et de l'analyser... Par exemple, rtl_ais donne d'assez bons résultats. D'un terril au dessus de Liège avec une antenne dipôle, je capte des bateaux sur la Meuse jusque Tihange (Huy) dans un sens et au delà de Maastricht dans l'autre. Et même quelques message provenant du côté de Genk, sur le canal Albert. Le code n'est pas très long et est assez lisible mais, c'est vraiment de la sorcellerie. Il va falloir du temps pour s'en imprégner et comprendre son fonctionnement.

À suivre...

lundi 27 juin 2016

Balise CC1101

Observer passivement les ondes avec une clé TNT (RTL-SDR), c'est bien mais ce serait bien aussi d'avoir une référence paramétrable disponible à tout moment. Une possibilité est d'utiliser ces modules pour Arduino avec un CC1101 de Texas Instruments, que l'on trouve à quelques euros sur eBay. Ils sont conçus pour émettre (et recevoir) dans la bande ISM 433 MHz. Plusieurs types de modulations FSK sont programmables, ainsi divers paramètres comme la puissance d'émission, le baud rate, la variation de fréquence,... Ainsi que différents formats et encodages de trame. La datasheet ne fait pas loin de 100 pages et il existe de nombreuses 'application notes' sur divers aspects de la mise en œuvre. On peut, par exemple, s'en servir pour comparer des antennes. Une autre application, c'est d'observer de près des signaux plus ou moins connus. Par exemple, à partir de la configuration utilisée par la librairie panstamp (générée par l'utilitaire de Texas Instrument 'SmartRF Studio'),
uint8_t panstamp_config[] =
        {
/*00*/  0x2E, 0x2E, 0x06, 0x07, 0xB5, 0x47, 0x3D, 0x06,
        0x05, 0xFF, 0x00, 0x08, 0x00, 0x10, 0xA7, 0x62,
/*10*/  0xCA, 0x83, 0x93, 0x22, 0xF8, 0x35, 0x07, 0x20, /* datarate (CA) = 38.4 kbauds*/
        0x18, 0x16, 0x6C, 0x43, 0x40, 0x91, 0x87, 0x6B,
/*20*/  0xFB, 0x56, 0x10, 0xE9, 0x2A, 0x00, 0x1F, 0x41,
        0x00, 0x59, 0x7F, 0x3F, 0x81, 0x35, 0x09
        };

/*10    0xC7, 0x83, 0x83, 0x22, 0xF8, 0x35, 0x07, 0x20, /* data_rate (C7) = 4800 bauds; 2FSK(83)*/
où R[0x10], MDMCFG4, est 0xCA (et R[0x11], MDMCFG3 = 0x83) qui correspond à 38383 symboles par seconde et R[0x12] est 0x93 (GFSK). Si on remplace ces valeurs par, respectivement, 0xC7 (4800 bauds) et 0x83 (2-FSK), on obtient un signal plus facile à observer/analyser avec une clé RTL-SDR. En zoomant sur la phase (arc tangente(I/Q); ou, serait-ce Q/I?) du signal reçu lors du préambule, on observe :
On observe que la phase décroît jusque A, croît entre A et B, décroît entre B et C pour ensuite recroître (les transitions autour de 0/360 provoquent un saut dont il ne faut pas tenir compte). La phase croît lorsque la fréquence est supérieure à la référence et décroît lorsque la fréquence est inférieure. En regardant de plus près, on note les coordonnées des points : A=(12585,130) B=(12634,150) et C=(12682,8). On compte donc 48 (ou 49) échantillons entre les points. Et, 230000 (échantillons/seconde) divisé par 48, cela donne bien 4792 symboles par seconde (~4800 bauds). En observant le gain (ou la perte) de phase entre deux points, on devrait retrouver l'écart de fréquence programmé pour le 2-FSK. A->B, +1460°; B->C, -1582°. L'asymétrie provient d'une erreur dans l'estimation de la fréquence de référence (ou, plus exactement, de quartz approximatifs; la fréquence choisie étant 433 MHz des deux côtés. Coupons la poire en deux et estimons que la phase progresse de 1500° 4800 fois par seconde, c'est-à-dire, 7200000° ou 20000 'tours', c.-à-d., 20 kHz. Et c'est exactement ce que l'on retrouve dans R[0x15], DEVIATN = 0x35. Une telle déviation est probablement excessive pour 4800 bauds; c'était prévu pour 38400 bauds. C'est aussi ce que l'on observe avec GNU Radio :
Le profil serait probablement plus doux en GFSK; en évitant les changements abrupts de fréquences. À noter que ces affichages FFT et Waterfall sont, un peu, des gadgets : un court message (du genre 8 caractères avant l'ajout du préambule et le CRC) est envoyé environ 7 fois par seconde (duty-cycle ~.25) et il ne s'affiche pas à chaque fois. J'ignore quelle est la proportion de signal non traité mais la traque aux messages courts et rares est un peu aléatoire avec ce genre d'outil. Si je mets 0x00 dans DEVIATN, j'obtiens la déviation minimale, 1587 Hz. À noter que c'est trop grand pour émuler un modem Bell 202. Par exemple, pour simuler un APRS venant d'ISS.
Si on regarde une trame complète en rapprochant la fréquence de réception de la fréquence d'émission (433 001 210 Hz plutôt que 433 000 000) et qu'on évite les sauts du côté de 0-360, cela donne le graphe :
On voit que la fréquence peut rester la même pendant un grand nombre de symboles (par exemple, quand on transmet des 0x00). Une possibilité est d'utiliser la fonction 'whitening' du chip qui fait un XOR avec un nombre pseudo-aléatoire. Une autre est d'utiliser un codage Manchester en mettant le bit 0x08 de R[0x12], MDMCFG2, à 1. On obtient alors le graphe :
...qui ressemble étrangement à celui de la grue du billet précédent. Sauf que dans le cas de la grue, les changement de fréquences sont 'arrondis', il doit s'agir de GFSK (?). On notera également que le codage Manchester engendre une diminution du débit utile.

La clé TNT utilisée ici est la petite : RT2832U avec un tuner R820T :


À suivre...

lundi 28 avril 2014

ADS-B sur piles

Liège-Bastogne-Liège 2014, le jour idéal pour tester dump1090 sur un Rasberry Pi (avec touchscreen) sur piles sur les hauteurs de Liège... Le but est de capter les messages ADS-B envoyés par les hélicoptères de la courses. L'équipement est simple : un pack de 6 piles rechargeables NiMH (ALDI/Top Craft), un petit convertisseur DC-DC pour ramener la tension à 5 volts, un Raspberry Pi, son touchscreen Tontec (MZTX-PI-EXT) et une petite clé USB TNT équipé du chip Realtek RTL2832U permettant le RTL-SDR avec son antenne d'origine (ou une de ses sœurs.


Dump1090 fonctionne dès le départ sur le Raspberry Pi.  Le problème était de rendre celui-ci portable pour aller capter les avions sur les hauteurs...  Pour cela, deux aspects : l'alimentation et le contrôle.  Pour le contrôle, l'idéal est un touchscreen.  Il y en avait un pas cher mais il n'était pas sûr que cela fonctionne.  Si l'affichage semblait ne poser aucun problème grâce à un article de ce blog , la partie 'touch' a nécessité quelques recherches et développements... Mais, donc, cela a fini par fonctionner.  Pour l'alimentation, le pack de six piles et le convertisseur DC-DC offre la solution idéale pour peu que l'on se contente d'une autonomie de quelques heures; le RPi et le touchscreen consomment 0.5A sous 5 volts (sans le dongle TNT).  Le convertisseur permet 2 A et les piles ont une capacité de 2.3 Ah.

Restait juste à développer une petite application graphique pour lancer l'acquisition, la stopper et éteindre le Pi...  Le plus simple est d'écrire quelques lignes en Tcl/Tk faisant appel à des scripts shell.


Bien sûr, ce serait mieux d'avoir une interface montrant les avions, etc...  Mais pour l'acquisition, c'est le plus facile.  Avec une Raspbian, il suffit d'ajouter une ligne à /etc/xdg/lxsession/LXDE/autostart du genre '@wish /home/pi/menu.tcl'.  Un script de quelques lignes :

#!/bin/sh
# the next line restarts using wish \
exec wish "$0" ${1+"$@"}

button .quit -text "Quit!" -command { exit }
button .rec -text "Record" -command { exec /home/pi/rec.sh & }
button .stop -text "Stop" -command { exec /home/pi/stop.sh & }
button .shutdown -text "Shutdown" -command { exec /home/pi/shutdown.sh & }

pack .rec .stop .quit .shutdown
Et le tour est joué. Les scripts contiennent des lignes comme : '/home/pi/bin/dump1090 > dump1090.`/usr/local/bin/now`' (now est 'date "+%g%m%d.%H%M%S"' et permet de suffixer le fichier avec le jour et l'heure); 'pkill dump1090' et 'sudo shutdown -h now'. La captation d'environ une heure entre 14 et 16h30 donne le résultat suivant :
Où l'on peut voir les coordonnées envoyées par les aéronefs : en abscisse, la longitude, en ordonnée, la latitude.  Pour donner une idée, Maline est en 51/4.5, Cologne 54/7, Orléans 48/2 et Bâle 47.5/7.6.  La captation était en 50.6/5.6 avec une bonne vue vers le sud (et moins bonne vers le nord où l'hôpital de la Citadelle masque la vue).

Si on trace la distance à l'aéronef (projeté au sol) calculée à partir des coordonnées géographiques contenues dans certains messages (grâce à la formule trouvée ici), cela donne le graphique suivant :

Les ruptures sont dues au fait qu'il s'agit d'un assemblage de plusieurs captations mais on voit clairement les avions s'approcher puis s'éloigner.  En général, plus les traces sont proches, plus elles sont continues mais certaines traces étonnamment lointaines sont aussi visibles.

Si on s'intéresse à l'altitude des aéronefs, on obtient le graphique suivant :
On observe de nombreux vols à altitude constantes mais aussi des montées et des descentes.  Le plus haut, à 44925 pieds est un jet privé, M-JANP, parti de Londres vers la Turquie, les plus bas sont les hélicoptères de la courses.  À nouveau, il y a des coupures dues à l'assemblage.

Pour la petite histoire, les hélicoptères captés étaient OO-HCZ, OO-HCW et OO-HCP. Mais j'ai aussi capté OO-SEX, le Cessna des parachutistes de Spa; OO-URS et OO-VPD, un petit bi-moteur et près de 300 avions de lignes.  Sur un peu plus d'une heure, entre 14h30 et 16h30, le système a capté 726 713 messages (! peut-être à vérifier, cela fait 200 messages à la seconde !).

Je peux isoler un profil de vol particulier.  Par exemple, pour OO-SEX, cela donne :
Il semble larguer les parachutistes à 2800 pieds (et comme le terrain est à un peu plus de 400 mètres d'altitude, cela fait environ 500 mètres au dessus du sol (?)).  Le cafouillage vers la fin provient probablement d'une perte de communication due à une altitude trop basse et on reprend avec la montée de la rotation suivante (?).  En fait, les mesures ont été prises sans repères temporels (le dump1090 original ne donne pas le temps de la captation du message et le Raspberry Pi n'a pas d'horloge temps-réel à la base; ceci peut être réglé avec un module RTC i2c avec un composant DS1307 (howto (.pdf)) mais il n'était pas branché (et donc, pas lu...)).

Identifications captées : AAF515, AAR501, ABW715, ADH818, AEE601, AEE624, AEE625, AFL2455, AFL2458, AFL2463, AFR1053, AFR112, AFR1147, AFR1211, AFR128, AFR146F, AFR1535, AFR1607, AFR1623, AFR1645, AFR1822, AFR1844, AFR1851, AFR1889, AFR1953, AFR253, AFR389E, AFR508, AFR562, AFR936F, ASR342, AUF171, AUI128, AWE711, AZA88Z, AZA97U, BAF610, BAH7, BAW147, BAW2676, BAW277, BAW347, BAW35, BAW45T, BAW53ZG, BAW547, BAW563, BAW632, BAW64G, BAW679, BAW703, BAW905U, BAW910N, BAW952M, BCS723, BEL2GP, BEL3PR, BEL5HR, BEL5VC, BEL716, BEL7PC, BEL7TX, BER2860, BER527N, BER797Q, BMR16GB, BMR45JR, BMR46CN, BMS123, BOX413, BRU866, CFE41G, CFG8TD, CTN4456, CYP347, DAL245, DAL475, DKGSA, DLH25E, DLH2CH, DLH2F, DLH2MF, DLH2NL, DLH2RX, DLH37E, DLH53T, DLH66A, DLH7TP, DLH8WH, DLH96E, EIN358, EIN359, EIN44T, EIN55N, ETD021, ETH3717, EXS92WX, EZS13EY, EZS82FX, EZS93FY, EZY11JX, EZY1972, EZY1975, EZY2085, EZY2134, EZY263F, EZY31RU, EZY46MN, EZY49VU, EZY5116, EZY51KE, EZY57EV, EZY58UH, EZY62WU, EZY64KC, EZY65XL, EZY67LT, EZY74GK, EZY75HC, EZY83EB, EZY85JM, EZY8832, EZY88PX, EZY8909, FALCN01, FBR14DI, FGVLD, FHY7884, FPO191X, GBYNE, GMI1142, GWI55M, GWI65B, GWI73E, HKY91, HOP3575, HVN16, IBS3731, ICV518, IRL258V, ISF60EU, JAF12Y, JAF29V, JAF3YG, JAF83W, JAF9BJ, JEF11, KAY56, KLM13T, KLM1415, KLM1602, KLM1987, KLM19P, KLM67Z, KLM79K, LAN704, LCO1505, LGL4603, LGL742, LGL7WD, LGL95E, LNX83GP, MJANP, MON1376, MON486G, MON5409, MON812, MON908, MSR725, MSR784, MSX521, MTINK, N3BR, N900HG, NAX2497, NAX5181, NAX92Y, NJE6MA, OMA134, OOHCP, OOHCW, OOHCZ, OOSEX, OOURS, OOVPD, OYMMM, PAL720, PGT502, PGT521, PGT522, PHAML, PHEDM, PRI244, PRI2704, QTR004, QTR040, QTR194, RJA111, ROT383, ROT384, ROT392, RRR2305, RYR10DL, RYR10UB, RYR16NB, RYR18DR, RYR1939, RYR26KZ, RYR30XE, RYR36VT, RYR39RT, RYR4014, RYR40JB, RYR40MX, RYR44BZ, RYR46QZ, RYR54HG, RYR582, RYR70JU, RYR70NW, RYR70SN, RYR754, RYR755, RYR793, RYR87BT, RYR87RK, RYR88QC, RYR973, RYR98EG, SAS559, SNM572, SUS9152, SWR319, SWR31N, SWR324, SWR35T, SWR64, TAP561, TAP686, TAP757W, TAP787B, TCW7922, TCW814E, TCX1588, TCX1628, TCX2549, TCX3425, TFL523, THA931, THY1819, THY1939, THY1966, THY7LT, TOM053, TOM23C, TOM393, TOM3FC, TOM435, TOM4JG, TOM843, TRA1739, TRA347T, TRA512N, TRA6348, TRA653P, TUI6PC, TVS13J, TWI583, UAE205, UAL964, VIR652W, VJS617, VLG1265, VLG1876, VLG6206, VLG8355, VPBDB, VPBZL, VPCYY, VQBFN, VQBHO, WZZ3TF, WZZ7UG, WZZ959.

Voir aussi