samedi 18 novembre 2017

DCF77

Un petit module DCF77, trouvé sur eBay, fait par des Canadiens (Universal Solder)... : page du constructeur.
Il y a longtemps que ce bidule me faisait envie mais, habitué aux trucs chinois à un euro, je le trouvait cher, 12 euros avec le port (alors qu'on peut trouver des modules GPS chinois à moins de 4 euros port compris). Mais là, j'ai craqué... Et je ne le regrette pas. C'est arrivé bien emballé dans une éprouvette, c'est bien documenté sur leur site (le chip est une radio spécialisée MAF6180C).

Le montage le plus simple, c'est de l'alimenter par deux piles de 1.5 volts, de relier la pin qui contrôle la mise sous tension à la masse et de mettre une led avec résistance entre le signal et la masse. La LED s'allume au départ et si le signal est bien reçu (et je ne suis qu'à 250 km de l'émetteur de Mainflingen qui émet avec une puissance de 30 kW sur des antennes tirées entre des pylônes de 200 mètres de haut).

Ce qui est amusant, c'est la synchronisation entre le tic-tac de ma pendule murale radio-pilotée (aussi DCF77) et la LED qui émet un flash de 100 ou 200 millisecondes toute les secondes (sauf la 59-ième). C'est magique. Il faudrait regarder le signal de plus près, voir si, dans les conditions de réception locales, on peut être beaucoup plus précis que la seconde et que je compare ce signal avec le signal d'un module GPS. Peut-être qu'on peut voir que le DCF77, ici, a près d'une milliseconde de retard sur le temps local vrai (250 km ~ 300 km à la vitesse de la lumière). À supposer que le top horloge du module GPS soit correct.

Ce qui m'intéresserait aussi, c'est de capter/visualiser le signal à 77.5 kilohertz, d'en étudier la phase, etc... J-M Friedt expliquait lors d'une présentation au FOSDEM.2017/SDR qu'ils avaient étudié les variations d'altitude de l'ionosphère avec une carte son de PC en écoutant le DCF77 à Besançon... Bref, il y a moyen de s'amuser.

dimanche 22 octobre 2017

Reference de tension

Un petit 'break-out' sur eBay à moins de trois euros avec un AD584 (signalé par Cyrob). Le composant est un peu oxydé mais on peut lire AD584JH. Sur le site d'Analog Device, neuf, il vaut 10 USD. En principe, il donne des tensions de référence de 10, 7.5, 5 et 2.5 à 3/1000 (avec 30 ppm/° Celsius et 30 ppm pour 1000 heures de fonctionnement avec un Vin de 15 volts. Le bidule est livré avec une espèce de certificat avec des mesures encore plus précises faites avec un Agilent AG24401 ...Et, je n'ai bien sûr aucun moyen de le vérifier.

Premier test. En fait, cela ne fonctionne pas. La LED témoin ne s'allume pas et je n'ai aucune tension en sortie en mettant de 15 volts en entrée... Me serais-je fait arnaquer? Bizarre que la LED ne s'allume pas. Le circuit est trivial : Vin->diode de protection (alim de l'AD584)-> LED -> résistance -> 'terre'. Ni la diode, ni la led ne semblent être à l'envers; mon ANENG AN8002 me dit qu'elle sont correcte. Tiens, c'est une résistance de 220 k! Ça, ce n'est pas normal, c'est beaucoup trop. Il n'y a pas assez de courant pour allumer la LED et même probablement pas assez pour rendre les diodes conductrices. Je remplace la résistance par une 2.2 k et cela va tout de suite beaucoup mieux : la LED s'allume et j'ai les tensions de référence en sortie.

Maintenant, puis-je faire confiance au certificat? Mystère... C'est possible qu'ils aient testé le composant avant de le souder sur la plaquette. Mais s'ils étaient vraiment sérieux, ils se seraient peut-être rendu compte qu'il y avait un problème. Maintenant, pour 2.50 euros, puisque cela fonctionne, je ne vais pas me plaindre. J'irai la mesurer sur un Rigol DM3058E un de ces jours.

dimanche 20 août 2017

Adalm Pluto

Nouveau gadget!
Là, c'est une vue du côté de 929 MHz, vraisemblablement une bande GSM, réalisée à partir du DVD Live GNUradio 'ubuntu-14.04.3-desktop-amd64-gnuradio-3.7.9.iso' (chargé en février 2016). La source utilisée est le canal 1 de 'FMComms2/3/4 Source'. Je ne garanti pas que ce que j'ai fait avec le gnuradio-companion soit optimal (ni même correct) mais cela donne déjà quelque chose (et cela n'a pas été sans mal). Il faudrai ré-essayer avec le DVD 3.7.11 (qui semble dater de février 2017).

Il y a aussi moyen de faire un ssh sur l'Adalm Pluto à l'adresse par défaut 192.168.2.1 avec le login 'root' (analog (ref)). Quand on le branche sur un Linux, il est aussi vu comme un disque USB. Pour faire tourner l'exemple 'ad9361-iiostream.c' (qui se compile facilement avec gcc d9361-iiostream.c -libiio), il faut faire un 'export IIOD_REMOTE="192.168.2.1 ./ad9361-iiostream"' (voir ici).


jeudi 21 juillet 2016

AIS toujours

Évolution de la phase lors d'un message AIS (le 'Curata' de passage à Liège le 19 juillet 2016)
$ rtl_sdr -f 162025000 -s 249600 f.iq
$ rtl_dump -p < f.iq > phase
La phase est obtenue avec rtl_dump. L'allure générale est due à un décalage entre la fréquence d'émission et la fréquence de réception. On a, environ, 5700° sur 5994 échantillons, ce qui correspond à 660 Hz ((5700/360)*(249600/5994)); --à vérifier--

En fait, il faudrait s'intéresser à la stabilité de la fréquence de réception (et sa variation de clé en clé).

$ rtl_sdr -f 162024400 -s 249600 f.iq
rtl_fftmax permet de voir s'il y a quelque chose dans le fichier :
$ /rtl_fftmax -f 162024400 -s 249600 -n 1024 core
On voit que le fichier contient des messages des deux canaux AIS, en 161.975 MHz et 162.025 MHz.

Dans un autre enregistrement d'environ 88 secondes à la Citadelle de Liège centré sur 162.025 MHz, si on applique l'algorithme de Goertzel avec N=512 sur les 'I', on obtient :
Le canal 'A' est obtenu en cherchant la présence de -50 kHz; 'B' à 0 KHz. En regardant de près dans Gnuplot, on peut facilement repérer où sont les messages dans le fichier binaire 'iq'.

Si je ne prends que les IQ des blocs où le test de Goertzel à 25 kHz est positif dans un enregistrement d'une minute centré sur 162 MHz et que je trace la phase en veillant à ce qu'elle soit continue, j'obtiens le graphe :
Ce qui est très encourageant. Les zones montantes concernent le +25 kHz, celles descendantes, le -25 kHz. On a aussi, probablement des collisions entre les deux fréquences qu'il faudrait départager avec un filtre. À noter qu'une translation en fréquence peut se traduire par la multiplication par un sinus. Le IQ étant des e^jwt, si on les multiplie par des e^jdwt, on obtient des e^(j(w+dw)t)... Et donc, l'idée de 'corriger la pente' en ajoutant simplement des a*t à la phase ne semble pas complètement farfelue (bien que probablement un peut trop intuitive (?)).

Notons que le graphe, sur une minute, comprends plus de 200000 points et qu'à 26 points par bits, on arrive à environ 40 messages de 200 bits. Ce qui, si on parvient à les décoder sans erreur, ne serait pas mal du tout.

Voir aussi

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...

samedi 9 avril 2016

RTL-SDR SigInt

Lors du FOSDEM 2016, Felix Wunsch a présenté le SigInt comme une activité ludique et enrichissante... Équipé d'un de ces gadgets à moins de 10 euros sur eBay, une clé TNT avec un chip Realtek RTL2832U, je m'en vais faire un tour avec le LiveDVD GNU Radio sur la bande ISM 433 MHz :
Tiens!, il y a un joli signal assez puissant et bien stable... En fait, ce n'est pas tout le temps mais quand le signal est présent, il reste stable longtemps. Tout porte à croire qu'il s'agit de la télécommande de la grue du chantier en face... (les horaires et l'activité coïncident avec la présence du signal)
Comme on peut le voir sur l'affichage I/Q de la copie d'écran GNU Radio, ce n'est pas une modulation d'amplitude, la trace est un cercle (d'amplitude constante). En y regardant de plus près, en utilisant la commande
$ rtl_sdr -f 433630000 -s 256000 file
et d'un petit programme 'rtl_dump -p' pour sortir la phase en texte, on obtient avec Gnuplot quelque chose comme :
Le gain automatique (AGC) fait varier la puissance du signal qui sature un peu à l'extérieur mais bon. Ce qui est amusant, c'est que la phase semble 'tourner' (à magnitude constante). En fait, c'est dû au fait que la fréquence de réception choisie ne correspond pas exactement à la fréquence d'émission (en charabia : 'frequency offset' -> 'spinning constellation' en démodulation QAM). J'ai estimé la fréquence en regardant le 'waterfall'; ce n'est pas assez précis. Il existe des techniques pour retrouver la fréquence correcte mais on verra ça plus tard... Pour le moment, c'est bon assez pour voir si on peut en tirer quelque chose. Si l'on s'intéresse à l'évolution de la phase, on obtient bien quelque chose de structuré :
En zoomant,
on voit que le signal est très propre mais que les sauts autour de 0-360° rendent l'interprétation difficile. En assurant la continuité et en laissant croître la phase au delà de 360°, cela devient plus clair :
Cette dérive vers le haut est un peu gênante, on peut la supprimer artificiellement en soustrayant la pente (-i*alpha):
Et voilà une jolie trame bien propre (mais un peu asymétrique) qu'il ne reste plus qu'à analyser...

Si j'écris un petit programme qui mesure les montées et les descentes des données (montantes), j'obtiens quatre nuages de points bien séparés représentant les montées/descentes courtes/longues :
On notera également que l'on n'a pas n'importe quelle suite de transitions. On n'a pas de suite {courte descente+longue montée} ni de suite {courte montée+longue descente}. En fait, si on considère 4 positions de hauts et bas de courbe, on peut avoir les transitions 1->{2|4}, 2->1, 3->4 et 4->{3|1}. Seuls les départs de 1 et 4 permettent de coder 1 bits. Une petite transition est d'office suivie d'une petite transition qui ramène l'état à son point de départ. Une difficulté de ce petit programme est de commencer et finir aux bons endroits et éviter les parasites.

On peut déjà regarder si les trames se ressemblent en les mettant côte à côte :


Normalement, il faudrait filtrer pour purifier le signal, utiliser la fréquence correcte et ensuite synchroniser et corréler pour extraire les bits. Mais le signal est propre et semble facile à décoder avec un petit programme ad hoc. Mettons qu'on imprime '1' pour deux 'petites' transitions successives et '0' pour une grande transition. Les dix premières trames donnent :
111111000000000000000000010111010010000000011011011101100010101010101100000010010000000010000000010000000010000000010000000010000000010000111010001111100001011101110100010000000001111
0011000000000000000000010111010010000000011011011101100010101010110010000010010000000010000000010000000010000000010000000010000000011001001010001000100001011101110100010000000001111
111110001000000000000000000010111010010000000011011011101100010101010110100000010010000000010000000010000000010000000010000000010000000010001001101010111010001011101110100010000000001111
1110001000000000000000000010111010010000000011011011101100010101010111010000010010000000010000000010000000010000000010000000010000000011001100100111111010001011101110100010000000001111
111111011000000000000000000010111010010000000011011011101100010101010111100000010010000000010000000010000000010000000010000000010000000010001100011100000100001011101110100010000000001111
1111111011000000000000000000010111010010000000011011011101100010101010000010000010010000000010000000010000000010000000010000000010000000011010100100111001100001011101110100010000000001111
11111101011000000000000000000010111010010000000011011011101100010101010000100000010010000000010000000010000000010000000010000000010000000010010100011100110010001011101110100010000000001111
11111111110001000000000000000000010111010010000000011011011101100010101010001010000010010000000010000000010000000010000000010000000010000000011010001010001110010001011101110100010000000001111
01000000000000000000010111010010000000011011011101100010101010001100000010010000000010000000010000000010000000010000000010000000010010001101010001100001011101110100010000000001111
011000000000000000000010111010010000000011011011101100010101010010010000010010000000010000000010000000010000000010000000010000000011011111101010110100001011101110100010000000001111
C'est amusant, des similarités apparaissent mais, pour une raison inconnue, elles ne sont pas alignées. Si on les aligne, cela donne :
000000000000000000010111010010000000011011011101100010101010101100000010010000000010000000010000000010000000010000000010000000010000111010001111100001011101110100010000000001111
000000000000000000010111010010000000011011011101100010101010110010000010010000000010000000010000000010000000010000000010000000011001001010001000100001011101110100010000000001111
000000000000000000010111010010000000011011011101100010101010110100000010010000000010000000010000000010000000010000000010000000010001001101010111010001011101110100010000000001111
000000000000000000010111010010000000011011011101100010101010111010000010010000000010000000010000000010000000010000000010000000011001100100111111010001011101110100010000000001111
000000000000000000010111010010000000011011011101100010101010111100000010010000000010000000010000000010000000010000000010000000010001100011100000100001011101110100010000000001111
000000000000000000010111010010000000011011011101100010101010000010000010010000000010000000010000000010000000010000000010000000011010100100111001100001011101110100010000000001111
000000000000000000010111010010000000011011011101100010101010000100000010010000000010000000010000000010000000010000000010000000010010100011100110010001011101110100010000000001111
000000000000000000010111010010000000011011011101100010101010001010000010010000000010000000010000000010000000010000000010000000011010001010001110010001011101110100010000000001111
000000000000000000010111010010000000011011011101100010101010001100000010010000000010000000010000000010000000010000000010000000010010001101010001100001011101110100010000000001111
000000000000000000010111010010000000011011011101100010101010010010000010010000000010000000010000000010000000010000000010000000011011111101010110100001011101110100010000000001111
En hexadécimal, cela pourrait donner quelque chose comme (dc; 16o2i ...p; 'pourrait' parce qu'il faut encore aligner et voir si c'est le LSB ou le MSB qui est transmit en premier) :
02E900DBB155604804020100804021D1F0BBA200F
02E900DBB1559048040201008040325110BBA200F
02E900DBB155A048040201008040226AE8BBA200F
02E900DBB155D0480402010080403327E8BBA200F
02E900DBB155E048040201008040231C10BBA200F
02E900DBB1541048040201008040352730BBA200F
02E900DBB1542048040201008040251CC8BBA200F
02E900DBB15450480402010080403451C8BBA200F
02E900DBB1546048040201008040246A30BBA200F
02E900DBB154904804020100804037EAD0BBA200F
On trouve des choses constantes et d'autres qui ne le sont pas. Probablement qu'en analysant un grand nombre de trames dans un grand nombres de circonstances on parviendrait à décoder tout cela. Et, en comparant différentes grues, y découvrir une adresse, un modèle,... (?)

Les tests du billet suivant laissent à penser qu'il pourrait s'agir d'un encodage Manchester en GFSK à 9600 bauds (?; le CC1101 ne semble pas pouvoir du Manchester en MSK (qui 'arrondit' mieux l'évolution de la phase)).

(à suivre?)

mardi 8 décembre 2015

MLX90614

Un petit thermomètre infrarouge en I²C...

Juste 4 fils sur le clone Arduino nano : GND, VCC (3.3V), SCL(A5), SDA(A4)

Lecture assez ardue des datasheets ATmega328p (TWI aka I2C, section 21) et MLX90614 pour finalement pas grand chose : après avoir initialisé l'interface I²C, il 'suffit' d'envoyer <S><SLA+W><reg><S><SLA+R><data-in><data-in><data-in><P>. Cela se fait via le registre TWDR (data), activé par TWCR (control) en vérifiant le TWSR (status dont TW_STATUS est un subset). Avec <S> pour 'Start'; SLA pour 'SLave Address'; R/W, le sens de communication; et <P> pour 'stoP'. Toutes les activations n'utilisent pas les mêmes bits (mais TWINT & TWEN sont toujours présents); TWINT marque également la fin d'exécution de la commande (cela devrait se faire par interruption mais ici, on se contente de 'poller' le bit). Chaque phase présente un TWSR/TW_STATUS différent pour indiquer que l'opération s'est bien déroulée. Le composant renvoie 3 octets : deux octets pour la température (en 1/50-ièmes de degré Kelvin, LSB first) et un CRC non vérifié ici (PEC=Packet Error Code).

#include <avr/io.h>
#define F_CPU 16000000UL
#include <util/delay.h>
#include <util/twi.h>
#include <stdio.h>
#include <string.h>
/*
** UART - stdio
*/
int uart_getchar(FILE *stream)
    {
    while (!(UCSR0A & (1<<RXC0)))
        ;
    return(UDR0);
    }
int uart_putchar(char c, FILE *stream)
    {
    while (!(UCSR0A & (1<<UDRE0)))
        ;
    UDR0 = c;
    if (c == '\n')
        uart_putchar('\r', stream);
    }
void uart_init(void)
    {
    UBRR0H = 0x00;
    UBRR0L = 103;  /* 9600 bps | F_CPU/16/baud-1 */
    UCSR0C = (1<<USBS0) | (3<<UCSZ00); /* 8N1 */
    UCSR0B = (1<<RXEN0) | (1<<TXEN0);
    fdevopen(uart_putchar, uart_getchar); /* stdio init */
    }
/*-----------------------------------------------*/
/*
** TWI - I2C  (single master)
** SCL   A5   PC5
** SDA   A4   PC4
*/
int twi_stop();
void twi_init()
    {
    TWSR = 0;    /* prescaler = 0 */
    TWBR = (F_CPU / 100000UL - 16) / 2;    /* 0x48 -> 100 kHz */
    twi_stop();
    }
#define twi_wait()    { while ((TWCR & _BV(TWINT)) == 0) ; }
#define twi_go(x)    TWCR = _BV(TWINT)|_BV(TWEN)|x
int twi_start() /* send start condition */
    {
    twi_go(_BV(TWSTA));    /* start */
    twi_wait();
    if (TW_STATUS != TW_START && TW_STATUS != TW_REP_START)
        {
        fprintf(stderr, "**** I2C/start : unexpected status 0x%02x\n", TW_STATUS);
        return(-1);
        }
    return(0);
    }
int twi_stop()
    {
    twi_go(_BV(TWSTO));    /* stop */
    return(-1);    /* always */
    }
int twi_sla(uint8_t sla, uint8_t rw)
    {
    TWDR = (sla<<1) | rw;
    twi_go(0);
    twi_wait();
    if ((rw == TW_WRITE && TW_STATUS != TW_MT_SLA_ACK)
        || (rw == TW_READ && TW_STATUS != TW_MR_SLA_ACK))
        {
        fprintf(stderr, "**** I2C/slave address : unexpected status 0x%02x\n", TW_STATUS);
        return(-1);
        }
    return(0);
    }
int twi_wcmd(uint8_t data)
    {
    TWDR = data;    // reg
    twi_go(0);
    twi_wait();
    if (TW_STATUS != TW_MT_DATA_ACK)
        {
        fprintf(stderr, "**** twi_wcmd : unexpected status 0x%02x\n", TW_STATUS);
        return(-1);
        }
    return(0);
    }
int twi_rdata()
    {
    twi_go(_BV(TWEA));
    twi_wait();
    if (TW_STATUS != TW_MR_DATA_ACK)
        {
        fprintf(stderr, "**** twi_rdata : unexpected status 0x%02x\n", TW_STATUS);
        return(-1);
        }
    return(TWDR);
    }
pr_temp(long t)
    {
    char s;
    int a, b;

    t -= 27350; /* Kelvin * 100 -> Celsius * 100 */
    s = (t < 0) ? '-' : '+'; 
    if (t < 0)
        t = -t;
    a = t / 100; b = t % 100;
    printf("%c%d.%02d", s, a, b);
    }
#define MLX_ADD  0x5A    /* MLX90614 SLA (i2c default SLave Address) */
#define CMD_Ta   0x06    /* ambiant temperature */
#define CMD_Tobj 0x07    /* IR object temperature */

long twi_MLX_get100tc(uint8_t sla, uint8_t reg)
    {
    int    a, b;
    long    t;

    if (twi_start() < 0 || twi_sla(sla, TW_WRITE) < 0)
        return(twi_stop());
    if (twi_wcmd(reg) < 0)
        return(twi_stop());
    if (twi_start() < 0 || twi_sla(sla, TW_READ) < 0)
        return(twi_stop());
    if ((a = twi_rdata()) < 0)
        return(twi_stop());
    if ((b = twi_rdata()) < 0)
        return(twi_stop());
    twi_rdata();    /* PEC - don't care */
    twi_stop();
    t = (a + (b << 8));
    return(t*2);    /* Kelvin * 100 */
    }
/*-----------------------------------------------*/
int main(void)
    {
    uart_init();
    twi_init();
    for(;;)
        {
        printf("\nT ambiant : ");
        pr_temp(twi_MLX_get100tc(MLX_ADD, CMD_Ta));
        printf(", T obj : ");
        pr_temp(twi_MLX_get100tc(MLX_ADD, CMD_Tobj));
        _delay_ms(500L);
        }
    }


Mais, bien sûr, on pourrait utiliser la lib Wire de l'Arduino...

Pour la compilation, le téléchargement et le test :
$ avr-gcc  -mmcu=atmega328  -Os -o gy906.elf gy906.c
$ avrdude -c arduino -p atmega328P -P /dev/ttyUSB0 -b 57600 -U flash:w:gy906.elf
$ minicom -D /dev/ttyUSB0 -b 9600