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


jeudi 26 novembre 2015

Code minimal nRF24L01 pour un Arduino nano

On peut trouver des modules de communication avec des nRF24L01+ pour utiliser avec des Arduino aux alentours de un euro... Il s'agit de composants qui permettent de communiquer par ondes radios à 2.4 MHz en GFSK à 2 Mbits/seconde. Ils comprennent la partie émission et la partie réception mais il font l'un ou l'autre, ils fonctionnent en half-duplex. Il existe une ou plusieurs librairies Arduino mais ça n'empêche pas de chipoter soi-même...

Quelques fils à raccorder : 4 connexions SPI, 2 pour l'alimentation (3.3V!) et un pour une commande du chip (CE).

En pratique, cela donne :

...À réaliser deux fois.

Pour ce qui est du programme, quand on décide de se passer de l'IDE Arduino, de ses librairies et d'utiliser avr libc, il faut commencer par programmer l'UART et le relier à stdio. Ensuite, programmer l'interface SPI en fonction de ce que l'on trouve dans la datasheet du nRF24L01+ et de l'ATmega328p. Il faut configurer certaines pins en sortie : MOSI, /SS et CLK et activer/configurer le SPI. Comme l'horloge est inactive basse et qu'on échantillonne les données au flanc montant, on est en 'Mode 0'; on spécifie un diviseur pour l'horloge SPI et le fait qu'on est 'MASTER'. Reste à programmer la fonction de transfert... En fait, en SPI, chaque fois qu'il y a un bit qui sort, un bit rentre et les transferts comportent un certain nombre d'octets encadrés par la sélection de l'esclave; chaque octet étant géré par le hardware. Quand on met un octet dans le registre SPDR, le transfert démarre. La fin du transfert est signalée dans un bit du registre SPSR. Bref, on a une fonction spix(in <- out, n).

Reste à programmer le nRF24L01... La datasheet fait 74 pages mais si l'on s'en tient à une fonctionnalité de base : transmettre avec l'un et recevoir avec l'autre, il n'y a presque rien à faire. Le chip, initialisé au RESET, est quasi fonctionnel : la transmission se fait à 2 Mbits/s à une fréquence donnée (à puissance maximum), avec un CRC de 1 byte, des adresses (pré-initialisées) de 5 bytes, les paquets envoyés sont retransmis jusqu'à trois fois si un accusé de réception (acknowledgement) n'est pas reçu... Il faut juste préciser la taille des paquets que l'on entend recevoir. Quelques dizaines de lignes de programmation suffisent pour voir les systèmes interagir.

#include <avr/io.h>
#define F_CPU 16000000UL
#include <util/delay.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;
    }
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 */
    }
void xdump(uint8_t *ucp, int len)
    {
    while (--len >= 0)
        printf("%02x ", *ucp++);
    printf("\n\r");
    }
char *getline(char *cp, int n) /* line input with  minimal editing */
    {
    int    i=0;
    char    c;

    for(;;)
        {
        c = fgetc(stdin);
        if (c=='\n' || c=='\r' || i==(n-1))
            {
            cp[i] = '\0';
            return(cp);
            }
        if (c=='\b' && i>0)
            {
            fputc(c, stdout);
            fputc(' ', stdout);
            i -= 1;
            }
        else cp[i++] = c;
        fputc(c, stdout);
        }
    }
/*-----------------------------------------------*/
/*
** SPI
** /SS /CS  D10 PB2
** MOSI     D11 PB3
** MISO     D12 PB4
** SCK      D13 PB5
*/
void spi_init()
    {
    DDRB  |= _BV(PB2)|_BV(PB3)|_BV(PB5);    /* /SS+MOSI+SCK out */
    SPCR   = _BV(SPE)|_BV(MSTR)|_BV(SPR0);  /* Enable SPI, Master, set clock rate fck/16 Mode-0 */
    PORTB |= _BV(PB2);                      /* /CS := 1 spi idle*/
    }
/*
** spix(...) clocks out *ucpout, getting *ucpin
*/
void spix(uint8_t *ucpin, uint8_t *ucpout, int n)  /* IN := spi(OUT) */
    {
    PORTB &= ~_BV(PB2);    /* /CS := 0 */
    while (--n >= 0)
        {
        SPDR = *ucpout++;
        while ((SPSR & (1<<SPIF)) == 0)
            ;
        *ucpin++ = SPDR;
        }
    PORTB |= _BV(PB2);    /* /CS := 1 */
    }
/*-----------------------------------------------*/
/*
** nRF24L01
**    CE = D8 = PB0
*/
/* commands */
#define R_REGISTER(reg) (0x00+reg)  /* read register 'reg' */
#define W_REGISTER(reg) (0x20+reg)  /* write register 'reg' */
#define R_RX_PAYLOAD    0x61        /* read received payload */
#define W_TX_PAYLOAD    0xA0        /* write payload to transmit */

uint8_t bufout[33];   /* Arduino -> nRF24L01 */
uint8_t bufin[33];    /* nRF24L01 -> Arduino */
void ce(int val)
    {
    if (val)
         PORTB |= _BV(PB0);
    else PORTB &= ~_BV(PB0);
    }
void pulse_ce()
    {
    ce(1);
    _delay_us(15L); /* minimum 10 */
    ce(0);
    }
void nrf24_init(int rx)
    {
    if (rx)
        {
        bufout[0] = W_REGISTER(0x11);
        bufout[1] = 32;
        spix(bufin, bufout, 2);        /* R11 = RX_PW_P0 = 32 payload length */
        bufout[0] = W_REGISTER(0x00);
        bufout[1] = (1<<1) + rx;
        spix(bufin, bufout, 2);     /* R00 = CONFIG = PWR_UP+PRIM_RX */
        _delay_ms(2L);            /* PWR_UP delay */
        ce(1);                /* enter listening */
        }
    else{ /* tx */
        bufout[0] = W_REGISTER(0x00);
        bufout[1] = (1<<1);
        spix(bufin, bufout, 2);        /* R00 = CONFIG = PWR_UP (tx default) */
        _delay_ms(2L);            /* PWR_UP delay */
        ce(0);
        }
    }
send(uint8_t *ucp, size_t n)
    {
    memset(bufout, 0x00, sizeof(bufout));
    bufout[0] = W_TX_PAYLOAD;        /* cmd = TX_PAYLOAD */
    n = (n > 32) ? 32 : n;
    memcpy(&bufout[1], ucp, n);
    spix(bufin, bufout, 33);    /* always send 32 bytes */
    pulse_ce();
    }
rcv(uint8_t *ucp, size_t n)
    {
    memset(bufout, 0xff, sizeof(bufout));
    bufout[0] = 0xFF;    /* cmd = NOP */
    do    {
        spix(bufin, bufout, 1);
        } while ((bufin[0] & 0x0e)  == 0x0e);    /* while nothing in */
    bufout[0] = R_RX_PAYLOAD;    /* cmd = R_RX_PAYLOAD */
    spix(bufin, bufout, 33);    /* always read 32 bytes */
    memcpy(ucp, bufin+1, n);
    }
/*-----------------------------------------------*/
int main(void)
    {
    char buf[32];

    spi_init();
    uart_init();
    printf("Test nRF24L01\n\r=============\n\r1. Receive\n\r2. Transmit\n\r> ");
    getline(buf, sizeof(buf));
    if (buf[0] == '1')
        { /* receive loop */
        nrf24_init(1);
        printf("Receiving...\n\r");
        for (;;)
            {
            rcv(buf, sizeof(buf));
            printf("%s\n\r", buf);
            }
        }
    else{ /* transmit loop */
        nrf24_init(0);
        for (;;)
            {
            printf("\n\r> ");
            getline(buf, sizeof(buf));
            send(buf, strlen(buf)+1);
            }
        }
    }
Il faut noter qu'un RESET de l'ATmega328p ne remet pas à zéro le nRF24L01 et que, pour bien faire, il faudrait ajouter un FLUSH_RX et un FLUSH_TX en démarrant pour remettre la transmission à zéro. Pour bien faire, il faudrait faire du contrôle de flux et traiter les erreurs. En fait, cela se passe surtout du côté de l'émetteur; du côté du récepteur, on ne reçoit rien si ce que l'on reçoit n'est pas un message correct (tout au plus peut-on s'apercevoir que la FIFO a débordé si on ne lit pas les messages assez vite). Il y a aussi de nombreuses autres fonctions du composant à explorer... (changement d'adresse, de puissance, sources multiples,...). Mais bon...

À noter aussi que _BV(b) (bit value) est équivalent à (1<<b) et que, pour bien faire, il faudrait utiliser une notation ou l'autre. Ce serait plus élégant... La seconde notation est plus puissante, elle fonctionne avec des champs de plusieurs bits. Par exemple (7<<3) n'est pas simplement représentable dans la première.

Pour ce qui est des commandes, quelque chose comme :
$ avr-gcc  -mmcu=atmega328  -Os -o nrf.elf nrf.c
$ avrdude -c arduino -p atmega328P -P /dev/ttyUSB0 -b 57600 -U flash:w:echo.elf
$ avrdude -c arduino -p atmega328P -P /dev/ttyUSB1 -b 57600 -U flash:w:echo.elf
$ minicom -D /dev/ttyUSB0 -b 9600  # et USB1 dans un autre terminal
Ensuite, on tape '1' dans un des terminaux minicom et '2' dans l'autre. Le premier passe en mode réception et reçoit tout ce que l'on tape dans l'autre (en veillant à garder des lignes de moins de 32 caractères).



Voir aussi



mardi 1 septembre 2015

52pi's 5 inch HDMI Touch Screen

Un écran 5 pouces HDMI à une trentaine d'euros pour mon Raspberry Pi, difficile de résister...

Il y avait un petit risque parce que la documentation est un peu lacunaire. Difficile de savoir s'il faut absolument un 'custom kernel' fourni par le constructeur ou si modifier /boot/config.txt suffit pour l'affichage. Pour la partie 'touch', je n'ai pas encore regardé (et je compte continuer à utiliser mon clavier Bluetooth IPazzPort). Après avoir un peu chipoté, sans succès, de manière générale en regardant la doc RPi. Je suis tombé sur ce forum où quelqu'un propose :
#increase HDMI signal strength (just a black screen if not set!)
config_hdmi_boost=4

#remove black borders
disable_overscan=1

#set specific CVT mode
hdmi_cvt 800 480 60 6 0 0 0

#set CVT as default
hdmi_group=2
hdmi_mode=87
Et, de fait, cela fonctionne maintenant.


Alimenté par le port USB (micro), il semble consommer 0.38 Ampère (0.23 quand le Pi est éteint; d'après mon 'charger doctor' (non qualibré)).

Voilà qui est beaucoup plus confortable que mon écran SPI 2.5 pouces 320x240 MZTX-PI-EXT, bien que le touchscreen reste résistif. C'est un peu plus encombrant avec les deux câbles en plus mais on récupère les GPIO. (Bien sûr, une tablette ou un smartphone...)

Lsusb donne :
0eef:0005 D-WAV Scientific Co., Ltd
Sur mon desktop Ubuntu, dmesg donne :
$ dmesg
[ 1965.392282] usb 1-1.4: new full-speed USB device number 4 using ehci-pci
[ 1965.486613] usb 1-1.4: New USB device found, idVendor=0eef, idProduct=0005
[ 1965.486618] usb 1-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 1965.486621] usb 1-1.4: Product: By ZH851
[ 1965.486623] usb 1-1.4: Manufacturer: RPI_TOUCH
[ 1965.486625] usb 1-1.4: SerialNumber: @\x18B547044417
[ 1965.488263] hid-generic 0003:0EEF:0005.0003: hiddev0,hidraw1: USB HID v1.10 Device [RPI_TOUCH By ZH851] on usb-0000:00:1a.0-1.4/input0
(mais ne permet pas encore de modifier la position du curseur ni de cliquer...; Cela donne néanmoins le sentiment qu'il devrait être possible de le faire fonctionner assez facilement...)

Pas encore testé mais la solution semble se trouver ici (en Python). En effet, /dev/hidraw1 donne des séquences de 22 caractères du type
170   1   1 115  14  73 187   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
170   1   1  90  14  70 187   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
170   0   0   0   0   0 187   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
170   1   1  93   2  70 187   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
170   0   0   0   0   0 187   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
170   1  14 200   2 105 187   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
170   1  14 194   2  98 187   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
170   1  14 200   2 113 187   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
où on devine un marqueur de début, un marqueur de fin, un indicateur de 'touché' et des xy sur deux caractères. En ne gardant que ces trois derniers, on lit des choses comme :
1 283 535
1 285 537
0   0   0
1 3790 3732
1 3795 3748
1 3795 3753
Les coordonnées sont comprises entre 0 et 4096. Il va donc falloir adapter le bidule... J'ai mis un court programme en 'C' sur https://github.com/xofc/hidraw2uinput. Aucun paramètre, tout est 'hardcodé' mais le programme n'est pas très long et devrait être facile à adapter et à installer. Sinon, il reste la solution en Python... (interprété, donc plus lourd et avec plus de dépendances (voir le script d'installation; le programme 'C' ne dépend de rien de particulier))

À noter que le Raspberry Pi possède un 'écran officiel' depuis le 08 septembre 2015.

Pour regarder de la vidéo, Xbmc (maintenant Kodi) est préférable à Vlc (qui manque de ressources sur un Pi-1).

5" HDMI LCD de Waveshare

On trouve un autre écran du même type qui s'enfiche sur le connecteur P1 des Raspberry (fabriqué par WaveShare). Il est fourni avec un adaptateur en 'U' pour le HDMI. Il est bien en face avec le Pi-2. Pour l'autre modèle que je possède (B rev 2?), il n'est pas en face mais cela fonctionne car le début du connecteur est compatible. Le connecteur HDMI est sur le grand côté, au dessus.
Sur certaines photos, il semble également avoir un connecteur micro-USB mais, en fait, il est factice (!) et la communication avec le 'touch' (XPT2046 ?) s'effectue via les GPIO de P1 (notamment SPI). J'ignore s'il est possible d'adapter le kernel, ils fournissent une image complète (que je ne compte pas utiliser). J'utiliserai la souris. Ou, peut-être regarder ici (mais je ne suis pas sûr qu'il y ait bien un XPT2046 et, en tout cas, les pins GPIO ne semblent pas correspondre... Mais cela vaudrait la peine de creuser la question et, éventuellement de faire une émulation de souris comme avec l'autre.

On peut déjà vérifier que P1-22 change d'état quand on touche l'écran avec WiringPi (apt-get install wiringpi) et la commande 'gpio readall' :
On voit également que les connexions annoncées par WaveShare correspondent effectivement à MISO, MOSI, SCLK et CE1. Le pas suivant est d'utiliser spincl(1) pour entrer en communication avec l'éventuel contrôleur XPT2046 (nécessite la libbcm2835).

En fait, spincl(1) est tellement simple qu'il est possible de tester la communication SPI directement en 'C', en quelques lignes. Quelque chose comme :
#include "bcm2835.h"                    

#define MAX_LEN 4
                                         
#define START   0x80    
#define XPOS    0x50
#define YPOS    0x10                    

int getit(int cmd)
    {
    char rbuf[MAX_LEN];
    char wbuf[MAX_LEN];
        
    memset(wbuf, 0, sizeof(rbuf));
    memset(rbuf, 0, sizeof(rbuf));
    wbuf[0] = cmd;
    bcm2835_spi_transfernb(wbuf, rbuf, sizeof(wbuf));
    return((rbuf[1]<<8)+rbuf[2]);
    }
int getxy(int *xp, int *yp)
    {
    bcm2835_spi_begin();
  
    bcm2835_spi_setDataMode(BCM2835_SPI_MODE0);
    bcm2835_spi_setClockDivider(BCM2835_SPI_CLOCK_DIVIDER_1024);
    bcm2835_spi_chipSelect(BCM2835_SPI_CS1);
    bcm2835_spi_setChipSelectPolarity(BCM2835_SPI_CS1, LOW);
        
    *xp = getit(START | XPOS); 
    *yp = getit(START | YPOS); 
        
    bcm2835_spi_end();
    return(1);
    }
donne des valeurs de XY dans l'intervalle 0..32768 quand l'écran est touché. Un petit programme qui envoie les données du XPT2046 dans /dev/uinput se trouve ici. À noter qu'un échange de 3 octets (et non 4) devrait suffire...

En fait, il est difficile de calibrer et ensuite de pointer exactement de petits 'contrôles' sur un écran 'résistif'. Il est probable qu'une émulation de souris (événements relatifs) soit plus efficace qu'une gestion avec des événements absolu. L'écran se comportant alors comme un 'touchpad'. Il n'y a plus qu'à...; le plus dur/incertain est fait.

À suivre...

dimanche 30 août 2015

Digistump de Digispark

Oups! j'en discute déjà sur enfantin...

Un petit bidule amusant : une petite carte Digistump avec juste un ATtiny85 qui communique en USB 1.1 (schéma). L'AVR fonctionne sur son oscillateur interne (pas de quartz) et gère l'USB par software (Littlewire). Un bootloader (micronucleus) permet de charger des applications (avec un utilitaire 'micronucleus'). On trouve des clones du bidule pour moins de deux euros sur eBay. Il faut noter que ce n'est pas tellement le prix qui est intéressant; on trouve des solutions USB 2.0 pour à peine plus cher. C'est l'idée... Digispark fournit un environnement 'Arduino' modifié (un 'blob' de 20 MB (?)) mais il y a moyen de s'en passer et d'utiliser les commandes de base. Ainsi, le Makefile :
blink.elf: blink.c
        avr-gcc  -std=c99 -Wall -Os -mmcu=attiny85 -o blink.elf blink.c

blink.hex: blink.elf
        avr-objcopy -j .text -j .data -O ihex blink.elf blink.hex

flash: blink.hex
        micronucleus --run blink.hex
permet de compiler et télécharger le programme suivant :
#include <avr/io.h>
#define F_CPU 16000000UL
#include <util/delay.h>

int main(void)
        {
        DDRB  |= _BV(PB1);

        for (;;)
                {
                _delay_ms(100);
                PORTB ^= _BV(PB1);
                }
        }
(à vérifier : que le CPU tourne bien à 16 MHz; voir ici pour l'idée de se passer de l'IDE Arduino)

Ce qui serait bien, c'est d'utiliser Little-Wire pour créer une application avec un 'moniteur' capable d'interpréter des commandes que l'on enverrait en utilisant minicom via /dev/ACM0 ... Ce n'est pas standard (USB/CDC n'est pas sensé fonctionner en USB 1.0) mais cela devrait fonctionner. Ce n'est pas trivial (Little-Wire est délicat à utiliser et la documentation USB/CDC-ACM n'est pas très lisible) mais cela devrait être possible. Les codes disponibles ne font que quelques centaines de lignes.

Ce qui serait bien aussi, ce serait d'explorer d'autres AVR implémentant la même solution... (en réduisant le nombre de composants au strict minimum (voir ici)

(à suivre...)

dimanche 16 août 2015

Le DDS AD9850 et le Bus Pirate

HC-SR08 connecté au 'Bus Pirate'. N'étant pas sûr que le Bus Pirate pouvait fournir assez de courant, le module est alimenté par un 'MB102 Breadboard Power Supply Module'.

On trouve sur eBay un breakout, le HC-SR08, avec un générateur de signal sinusoïdal DDS AD9850. La datasheet nous apprend que le composant est configurable en envoyant 40 bits en série : 32 bits pour la fréquence (f=(x * CLKIN)/2^32 avec CLKIN = 125 MHz) et 8 bits de configuration (5 bits de phase et 3 de contrôle). Pour mettre le composant en mode 'série', il faut mettre la pin D2 à la terre et D1 avec D0 sur VDD. Le chip peut être alimenté en 3.3 volts. Les bits sont pris un par un au flanc montant de W_CLK et le tout est pris en compte au flanc montant de FQ_UD. En fait, cela ressemble très fort à du SPI et cela devrait être facile à mettre en œuvre avec un Bus Pirate, espèce de couteau suisse de la communication série pour ce genre de bidule. En utilisant la sortie CS pour FQ_UD, cela devrait même être trivial...

Aussitôt dit, aussitôt fait.
Pirate bus
----------
PB.GND  -- GND
PB.MOSI -----> DDS.DATA    # SPI bus
PB.CS   -----> DDS.FU_UD
PB.CLK  -----> DDS.W_CLK

DDS
---
DDS.GND -- GND

DDS.D0 -- 3.3V   # config serie
DDS.D1 -- 3.3V
DDS.D2 -- GND

DDS.VCC -- 3.3V

DDS.ZOUT1 ----------------> Oscillo.CH1
GND ----------------------> Oscillo.GND
Vient ensuite l'interaction avec le Bus Pirate... Sur Ubuntu, le driver du FTDI (USB-RS232) est natif, il n'y a qu'à utiliser 'minicom(1)' à 115200 bauds sur /dev/USB0, par exemple. Mettre le bidule en mode SPI, configurer les options et envoyer la séquence de bits.
$ minicom -D /dev/ttyUSB0 -b115200
[...]
HiZ>m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. LCD
x. exit(without change)

(1)>5
Set speed:
 1. 30KHz
 2. 125KHz
 3. 250KHz
 4. 1MHz

(1)>1
Clock polarity:
 1. Idle low *default
 2. Idle high

(1)>1
Output clock edge:
 1. Idle to active
 2. Active to idle *default

(2)>1
Input sample phase:
 1. Middle *default
 2. End

(1)>1
CS:
 1. CS
 2. /CS *default

(2)>2
Select output type:
 1. Open drain (H=Hi-Z, L=GND)
 2. Normal (H=3.3V, L=GND)

(1)>2
Ready
SPI>L
LSB set: LEAST sig bit first
SPI>[0 0 0x25 0 0]
/CS ENABLED
WRITE: 0x00
WRITE: 0x00
WRITE: 0x25
WRITE: 0x00
WRITE: 0x00
/CS DISABLED  # ---> 70.42 khz mesuré
SPI>[0 0 0x26 0 0]
/CS ENABLED
WRITE: 0x00
WRITE: 0x00
WRITE: 0x26
WRITE: 0x00
WRITE: 0x00
/CS DISABLED # ---> 72.46 kHz mesuré
SPI>
Théoriquement, [0 0 0x25 0 0] devrait donner une fréquence de (37*256^2/256^4)*125 MHz, c'est-à-dire 70.572 kHz et [0 0 0x26 0 0], une fréquence de (38*256^2/256^4)*125 MHz, c'est-à-dire 72.479 kHz. La mesure à l'oscillo est un peu différente mais pas beaucoup : 2/1000 dans le premier cas et beaucoup moins dans le second. Il est fort probable que l'erreur soit du côté de la mesure plus que du côté de la génération (?). Pour 'qualifier' la fréquence générée, il faudrait utiliser un diviseur de fréquence et intégrer sur une plus longue durée; utiliser un fréquence-mètre calibré.

vendredi 19 juin 2015

Sismo piezo

Passage du bus de 05h09 suivi d'une voiture capté sur la tablette de fenêtre avec une boule de pétanque posée sur un petit capteur piézoélectrique de quelques cents (disque; amplifié 20 fois par un LM386)

Petite exploration avec le genre de capteur piézoélectrique (piezo sensor disc) que l'on trouve sur eBay pour quelques euro-cents... Le bidule se comporte comme un condensateur rempli d'un générateur de charges sensible aux déformations. Sa résistance est très (très) grande et une tension apparaît à ses bornes quand on le déforme (ou qu'il subit une pression (?; voir ici)). Pour éviter une errance totale, on y adjoint une résistance en parallèle. Si la résistance est faible, on écrase complètement le signal; plus elle est forte et plus on aura de signal (ou quelque chose comme ça). Bref, mettons, par exemple, 10 mégaohms (marron-noir-bleu).

Si on soude une petite anse au disque en laiton (?), qu'on y attache un fil auquel on pend une petite masse. En maintenant fixe l'autre côté du disque (vertical) et en faisant balancer la masse, on peut vérifier à l'oscilloscope que la période du pendule est bien 2 Pi racine(longueur/g). Dans ce cas-ci, 0.63 mètre donne une période de 1.59 secondes et on a un signal d'environ 20 millivolts d'amplitude (pour une faible amplitude du pendule).
On peut donc capter des mouvements lents (la pastille n'est pas cantonnée à des signaux en kilohertz) et le niveau de signal est compatible avec ce que l'on peut mesurer avec un Arduino (5 volts sur 10 bits donne une résolution de 5 millivolts).

L'application classique de ce type de capteur avec un Arduino, c'est la détection des chocs. On pose la pastille sur une surface plane, on dépose une petite masse sur le capteur et quand on tapote sur la table, cela génère un signal (parce que la table fait bouger le disque en laiton et que l'inertie de la masse s'oppose au mouvement de la partie supérieure du capteur qui s'en trouve comprimé. C'est très sensible; un choc modéré génère un signal de plusieurs dizaines de millivolts. Pour pouvoir mesurer ce signal tant en positif qu'en négatif, on le 'conditionne' en reliant le disque de laiton au centre d'un diviseur résistif (par exemple, aussi, 100 mégaohms; dans "Make: AVR Programming", il met le capteur en parallèle sur une des branches du diviseur résistif).

Sur Instructables, on trouve un sismomètre pendulaire à base de capteur piézoélectrique pour détecter les mouvements horizontaux. Une tige terminée par une masselotte et soudée à la pastille en laiton fixée verticalement dans un bocal. Les oscillations déforment la pastille et génèrent un signal.

Comme ici, les tremblements de terre sont plutôt rares, je ne suis pas sûr de pouvoir capter des mouvements transversaux et je m'intéresse donc aux mouvements verticaux. Dans un premier temps, je conserve l'idée de la tige soudée à la pastille mais je dispose le tout horizontalement. L'idée est que la pastille rattachée au sol va bouger et que la tige va avoir une certaine inertie et que cela va déformer la pastille. Le problème, c'est que je ne peux pas mettre de masse trop importante au bout de ma tige sans plier la pastille de manière permanente. On fera avec... En fait, cela fait un système oscillant dont la période dépend de l'élasticité, de la masse et de la longueur de la tige... Là, j'utilise un bout de fil de cuivre domestique de 1.5 mm² d'environ 16 cm de long. Cela oscille librement à environ 9 hertz. Pour simuler les tremblements de terre, rien de mieux qu'un pont enjambant une autoroute. Aussitôt dit, aussitôt fait. Et me voilà en route vers un pont de la région avec mon Arduino, mon Raspberry Pi, la tige+capteur sur un petit bloc de ciment, un powerbank et une caisse pour protéger le tout du vent...
À peine à genoux sur le pont, en train de lancer l'application, j'entends une voix derrière moi : "Je peux vous aider?...". Je me retourne; c'est la police... (À juste titre,) mon comportement lui avait paru suspect ;-) . Contrôle d'identité; je lui explique le but de la manœuvre. Rassuré, il s'en va et me laisse faire mes mesures...
Un peu plus d'une demi-heure de mesure avec un zoom sur le spectre du signal (GNU Octave + plotspec.m). Il y a énormément de trafic à cette heure-là sur le pont; il n'y a que quelques zones de calme (comme, par exemple, un peu avant 700 secondes et un peu après 1000. Si on zoome sur le signal, on obtient quelque chose comme :
En s'approchant encore, on voit bien les oscillation de la tige (j'ignore d'où provient le signal à 90 Hz dans le spectre; est-il mécanique (vibration de la pastille) ou électronique?)
Mais donc, en gros, cela fonctionne. Ce n'est pas très étonnant parce que le pont 'vibre' franchement, surtout quand un camion ou un bus passe. Il serait intéressant de connaître le déplacement typique (amplitude et fréquence) d'une telle structure. Mais, il faut noter que l'amplitude ne semble pas directement proportionnelle au poids du véhicule; cela dépend de l'état de la chaussée à l'endroit où il passe (et probablement de sa vitesse, des caractéristiques de ses amortisseurs, etc...). Mais donc, on voit bien les camions et les voitures sur les deux bandes de circulation (le pont fait bloc).

Reste à rendre le dispositif plus sensible et s'affranchir des oscillations libres de la tige. Une solution serait d'amortir son mouvement dans un liquide visqueux. Mais, pour le moment, j'abandonne la tige pour construire un autre dispositif à base d'une boule de pétanque (720 grammes) posée sur deux écrous posés sur le capteur (posé sur la tablette de fenêtre). Un écrou sur la tranche m'offre deux surfaces planes parallèles et un écrou sur le flanc fournit un support stable à ma boule. À noter, la feuille de papier qui isole le capteur de la boule et des écrous qui, sinon, font antenne et captent plein de parasites (la 'terre' (fil blanc relié au radiateur) est mauvaise et cela fonctionne mieux sans...).
Tant qu'à faire, je vais maintenant amplifier le signal avec un LM386 (que je viens de recevoir). La mise en œuvre semble particulièrement simple, il suffit de mettre le piézo (en parallèle avec sa résistance) à l'entrée de l'ampli opérationnel (avec le disque à la terre) et le signal ressort multiplié par 20 et centré sur 2.5 volts (quand le LM386 est alimenté en 5 volts). Aussitôt dit, aussitôt fait. Pas sûr que ce soit l'idéal (tant au niveau du choix du composant que de sa mise en œuvre) mais bon... Et, de fait, j'obtiens le beau signal en tête d'article avec un bus suivi d'une voiture. La configuration des lieux (?) offre parfois des observations curieuses comme :
Les vibrations sont trop rapprochées pour appartenir à des véhicules différents et, à 36 km/h (pourquoi pas), ils parcourent 5 mètres en une demi seconde... Une zone pavée interrompt le bitume sur quelques mètres de la voie à sens unique. Y aurait-il là une explication? Ce patron n'est pas systématique mais il survient régulièrement.

En mettant un condensateur de 10 microfarads entre les pins 1 et 8, on pousse l'amplification à 200 et on obtient quelque chose comme :
Reste à regarder pourquoi le spectre enfle (dans ce cas-ci) vers 200 Hz, minimiser le bruit, augmenter le signal... Je ne suis pas sûr que le dispositif soit suffisamment stable pour résister aux mouvements du pont et c'est sûr qu'à x200, cela va saturer. Ici, le signal dominant est la circulation dans la rue, il y a peu de chance d'y trouver un tremblement de terre; il faudrait essayer dans la cave ou dans un lieu plus reculé. Il est probable que le dispositif soit plus sensible que les accéléromètres (MEMS; ADXL335, MMA8452, MPU-9250, MPU6050,...) qui se trouvent dans les smartphones et que le projet AcceleROB de l'Observatoire Royal de Belgique (Uccle) utilise. Bon marché, ils ont une résolution de l'ordre du milli-g. AcceleROB devrait détecter des séismes de magnitude locale 3. Les sismomètres du réseau de l'ORB sont beaucoup plus sensibles; ils détectent des déplacements de quelques nanomètres comme, par exemple, sur ce rapport. Reste à voir quelles sont les relations entre déplacement, vitesse, accélération, fréquence des mouvements du sol. Le sismomètre à boule de pétanque est sensible à l'accélération (F=ma); la force que la boule exerce sur le capteur varie (il serait peut-être intéressant de mesurer le poids apparent de la boule avec une vraie 'balance' (il faut voir à quelle fréquence on peut échantillonner...; un HX711 est limité à 80 mesures/seconde, ce qui n'est probablement pas assez). Un sismomètre à solénoïde est sensible à la vitesse (c'est la variation de champ magnétique qui génère la tension que l'on mesure). Il faut s'intéresser aux fréquences de vibration propres de l'instrument qui interagissent avec les mouvements du sol. Pour s'en affranchir (?), on utilise des systèmes asservis : on maintient la masse inertielle fixe (mesure interférométrique) avec un champ magnétique variable généré par un courant que l'on mesure (?). Mais donc, il y a moyen de s'amuser. On constate, par exemple, que les mesures de l'ORB sont fortement perturbées par des vibrations d'origine anthropique (pendant la journée en semaine), l'observatoire de Uccle est perturbé par des trains qui passent à plusieurs centaines de mètres de là; celui du Sart-Tilman, par le trafic sur l'autoroute;... Je me demande, si dans ma cave, je pourrais capter le passage des trains dans un tunnel situé à 130 mètres... (?) Il serait également intéressant de capter un 'vrai' tremblement de terre (et pas uniquement le trafic automobile). Le problème, c'est que c'est rare et que pour le moment, j'enregistre tout (environ 400 mesures par seconde envoyées par l'Arduino à 38400 bps vers le Raspberry Pi qui le stocke (environ 6 MB/heure)...

Le breakout i²c avec un accéléromètre MPU-6050 est le moins sensible. Sur la tablette de fenêtre, il ne donne que du bruit; là où passent les bus, quelques bus mais pas tous; au pont sur l'autoroute :
C'est très bruité; on voit bien quelques véhicules lourds; les voitures sont noyées dans le bruit. À noter que X, Y et Z donne des traces similaires : quand il y a une vibration en Z, elle se retrouve sur X et Y. Avec 16 bits pour +/- 2g, Z est à environ 16000 et X,Y sont quasi à zéro; c'est bien la sensibilité maximum. (J'ignore pourquoi le spectre s'épaissit maintenant vers 40 Hz (et 100 Hz); sur la tablette de fenêtre, le bruit est 'blanc')

N'osant pas aller avec la boule de pétanque sur le pont, voici une comparaison avec le MPU-6050 sur un carrefour dont le sous-sol est creux et où passent de nombreux bus. Le sol vibre moins que le pont mais, quand un bus passe, les vibrations sont souvent perceptibles :
Seules les plus fortes vibrations sont captées par le MPU-6050.
La boule de pétanque est beaucoup plus sensible. Trop même. Le signal sature lors des plus fortes vibrations.
Le matin vers 7 heures, le trafic est quasi constant. C'est rarement calme plus de quelques secondes. À 17 heures, les voitures et les bus se suivent :
À noter que le renflement du spectre passe de 200 Hz à 120... C'est peut-être (/probablement) dû au ralentissement du trafic (?). À 10 mètres par seconde, les 10 centimètres d'un pavé sont franchis en 1/100 seconde. Mais c'est peut-être un peu spéculatif... (En plus, en échantillonnant à 400 Hz, un signal à 200 Hz est à la limite de Nyquist)

Le passage des trains semble être à la limite du détectable (?); il faudrait augmenter le signal et diminuer le bruit... (Je pourrais, par exemple, augmenter la masse... (mais il ne faut pas casser le piézo non plus...))

Un kettlebell de 4 kg ne semble pas donner de bon résultats mais je pense avoir capté un passage de train avec la boule de pétanque (x200) dans la cave :
Un événement assez long, plus ou moins constant et de faible amplitude par rapport au passage des véhicules dans la rue...

mercredi 3 juin 2015

ESP8266 et Expect

Module FTDI & Esp-05 sur un breadboard.

L'Esp8266 permet d'accéder au WiFi via des commandes AT sur une connexion série... En principe 'Expect' (tcl) semble indiqué pour ce genre de chipotage... Voici, par exemple, un petit script qui interroge le chip en boucle pour connaître la liste des réseaux présents.
#!/usr/bin/expect -f

spawn -open [open /dev/ttyUSB0 w+]
stty raw -echo < /dev/ttyUSB0
fconfigure $spawn_id -encoding binary
send "AT\r\n"
while 1 {
  expect {
    "ready\r\n" {send "AT+CWLAP\r\n"}
    "OK\r\n"    {send "AT+CWLAP\r\n"}
    timeout     {puts "  ThisIsTimeout  "}
    eof         {puts "  ThisIseof   "}
    }
  }
Comme j'utilise très peu Expect, il est possible que cela ne soit pas vraiment optimal, mais cela donne une idée...

Dans le montage utilisé, on a un module ESP-05, une interface USB-Série (3.3V) FTDI232 et un 'MB102 breadboard power supply 3.3 volts module' et quelques fils (RX/TX & alimentation). Le module 'power supply', équipé d'un régulateur AMS 1117-3.3 semble optionnel, le 3.3 volts pouvant être délivré par le module FTDI. Je pensais que les resets intempestifs de l'esp8266 provenaient d'une faiblesse de l'alimentation par le FTDI mais ils persistent avec le module d'alimentation... Le problème doit venir d'ailleurs; il faut probablement mettre une 'pull-up' sur le reset de l'ESP-05 (?). Sinon, cela semble fonctionner. La version du firmware de l'ESP-05 (0018000902-AI03) communique en 9600 bps, 8-bits, nopar (et pas de contrôle de flux hardware), ce qui correspond aux valeurs par défaut pour /dev/ttyUSB0 sur mon GNU-Linux Ubuntu 14.04. L'Esp8266 fonctionne en 3.3 volts. Il est possible que l'interface USB-série soit autre chose que /dev/ttyUSB0, par exemple /dev/ttyUSBx (avec x =1,2,...) ou même /dev/ttyACM0 (1, 2,...). Il faut consulter dmesg(8) (et/ou faire 'ls -lrt /dev/tty*', lsusb,...).

Il est aussi possible d'utiliser 'minicom -D /dev/ttyUSB0' en terminant les commandes par ^M^J (\r\n) mais la pauvreté de l'interaction rend l'expérience rapidement frustrante (pas d'historique ni d'édition de ligne (?)). Avec Expect, 'spawn minicom ...' semble ajouter des caractères indésirables liés à la gestion d'écran de type VT100 (?) et je n'ai pas trouvé de solutions plus simples (tip?, cu?,...) mais je n'ai pas beaucoup cherché. Si on reçoit bien le prompt mais qu'on ne parvient pas à envoyer des caractères (et donc pas d'écho), vérifier que le contrôle de flux hardware est désactivé.

Le connecteur WiFi de l'Esp-05 est un U.FL (IPX). On trouve sur eBay des pigtails pour SMA et RP-SMA, N,...

Il faut noter qu'en ville, en Belgique, en 2015, les réseaux pullulent; j'en capte une quarantaine de ma table et une courte balade en rue m'en renseigne des centaines. Le wardriving a beaucoup changé depuis le début des années 2000. La plupart sont bien protégés (WAP), d'autres sont volontairement ouverts dans le cadre des offres de type 'Internet Everywhere' des fournisseurs d'accès dominants (PROXIMUS(_AUTO)_FON et VOO_HOMESPOT). Quelques-un sont probablement mal configurés...

On obtient des lignes du genre :
+CWLAP:(0,"PROXIMUS_FON",-44,"06:**:**:99:**:**",1)
+CWLAP:(4,"MonRezo",-87,"**:f8:**:**:**:9c",2)
...


qui renseignent le type de protection ( (encryption): 0 open; 1 WEP; 2 WPA_PSK; 3 WPA2_PSK; 4 WPA_WPA2-PSK), le SSID, la puissance du signal reçu (en dBm?), l'adresse MAC de l'access point et le canal WiFi. À noter que l'adresse MAC renseigne le type d'équipement via les octets qui désigne le 'manufacturer' (et donc probablement aussi les failles de sécurité propres à ce type d'équipement...).

AI-THINKER IoT

Acheté un peu plus de 6€ sur dx.com, ce bidule offre une démo clé en main. Une fois mis trois piles 'AA' (et décoché le jumper qui sert au re-flashage). Il existe (paraît-il) une application Android iot.apk mais il est possible de l'utiliser à partir d'un PC normal. Il suffit de se connecter au réseau AI-THINKER (mot de passe "ai-thinker") et de communiquer avec 192.168.4.1. Parfois cela 'se fait tout seul', sinon, faire quelque chose comme (à titre indicatif) :
$ sudo ifconfig wlan1 192.168.4.3
$ sudo route add -net 192.168.4.0 netmask 255.255.255.0 wlan1
$ # éventuellement utiliser : iwconfig, ifconfig, dmesg, lsusb,...
Le but étant de donner une adresse à l'interface WiFi et y faire transiter les paquets destinés au bidule.

On peut alors accéder aux GPIOs en JSON avec curl(1)
$ curl http://192.168.4.1/client?command=info
{
"Version":{
"hardware":"0.1",
"software":"0.9.2"
},
"Device":{
"product":"Light",
"manufacturer":"Espressif Systems"
}
$ curl http://192.168.4.1/config?command=light
{
"freq":100,
"io0":,
"io2":,
"io4":,
"io5":,
"io12":,
"io13":,
"io14":,
"io15":,
"rgb":{
"red":0,
"green":0,
"blue":200
}
$ curl -X POST -H Content-Type:application/json \
  -d '{"freq":100,"io4":0,"io5":0,"rgb":{"red":0,"green":0,"blue":200}}' \
   http://192.168.4.1/config?command=light


Voir http://www.electrodragon.com/w/ESP8266_IoT

Lua

Il est possible de reflasher les Esp8266 avec un interpréteur Lua. Pour cela, il faut connecter le GPIO-0 à la terre, faire un reset (reset->gnd; cela semble différer d'un power-up) et utiliser un outil comme Esptool (un utilitaire en Python)pour flasher un pre_build de firmware de NodeMCU :
$ sudo python esptool.py --port /dev/ttyUSB0 \
                         --baud 115200 \
                         write_flash 0x00000 \
                         ../lua/nodemcu_latest.bin
Après, voir cet exemple qui montre comment créer un fichier init.lua sur le SoC. Sur Wikipédia, ils conseillent de commencer par un file.format(). Voir l'API NodeMCU.

Voir aussi

Le petit bidule de 5x5mm² comporte un 'core' 32 bits tournant à 80 MHz, 512 Koctets de mémoire FLASH et quelques dizaines (?) de Koctets de RAM (plus tout ce qu'il faut pour recevoir/émettre du WiFi, quelques GPIO, ADC(?),...

dimanche 3 mai 2015

GPS en balade

Un Raspberry Pi, une interface USB-série_TTL_3.3v_FTDI et un module GY-GPS6mv2 alimentés par un 'powerpack'.

Si on enregistre pendant un peu plus d'une demi-heure la position GPS sans le déplacer, la position rapportée varie :
Le GPS est posé sur un mur entre deux maisons. La vue est plus dégagée en est-ouest qu'en nord-sud. Le GPS semble, en effet avoir plus de mal à déterminer la latitude.

Dans un endroit plus dégagé :
La précision nord-sud est améliorée.

Le module donne toute une série de lignes NMEA toutes les secondes à 9600 bps (8N1). La latitude apparaît sous la forme DDMM.yyyyy et la longitude sous la forme DDDMM.xxxxx ('D' pour degré; 'M' pour minute; xxxxx & yyyyy sont des décimales de minutes). Ces coordonnées sont extraites des lignes GPGGA. La traduction en mètres est approximative : 40 000 km/360°; un offset est soustrait pour rendre la lecture plus aisée.

Si on s'intéresse à l'évolution de l'altitude, on obtient le graphe suivant :
On s'aperçoit également que les variations sont moins grandes lorsque la vue sur le ciel est plus dégagée.

La suite des mesures n'est pas la position réelle plus quelque chose d'aléatoire comme on aurait pu s'y attendre si les positions avaient été calculées indépendamment. On a affaire soit à une précision diabolique à la merci d'un phénomène physique continu non maîtrisé, soit, plus vraisemblablement, à un filtrage du genre xn+1 = αX + (1-α)xn (?).

Il faudrait regarder les autres informations (comme le HDOP), étudier l'évolution à plus long terme, essayer de déterminer la position moyenne par les moindres carrés, regarder si elle converge et si elle est stable dans le temps. Les graphes semblent déjà dire qu'il ne suffit pas de faire la moyenne sur cinq minutes pour avoir une meilleure précision. C'est plus complexe que cela...

Avec deux GPS

Une question que l'on pourrait se poser est de savoir si deux modules GPS posés l'un à côté de l'autre se baladent de la même manière... On s'approcherait d'un GPS différentiel à peu de frais. Cela serait plus ou moins le cas si les variations de la vitesse de propagation des signaux était dominante. Mais, il semble que non : les deux GPS se baladent indépendamment :
Une trace en vert, l'autre en rouge...

La différence entre les deux positions n'est pas meilleure que les positions individuelles.

À noter que l'on trouve pas mal de documentation sur ces modules :

dimanche 18 janvier 2015

AIS rtl_sdr frequency correction

La fréquence des quartz des clés TNT/USB bon marché varie d'une clé à l'autre. Cela semble poser problème pour certaines applications. Ce lien en discute pour gr-ais (de manière générale). Le paramètre 'e' de gr-ais permet de corriger la fréquence. Le gif animé (alternance de 3 secondes) suivant compare les spectres obtenus par gnuradio-companion avec deux clés différentes.
Spectres obtenus en quelques minutes dans le port des Yachts à Liège (seul SPN06 émet). ("<clic-droit> -> afficher l'image" permet de la voir en entier)

On peut voir un message dans le canal A dans chaque 'waterfall'. Les lignes vertes dans les spectres montre les maxima atteint au cours de quelques minutes. En principe, les messages sont transmis plus ou moins alternativement sur le canal A (161.975 MHz) et sur le canal B (162.025 MHz). Les messages sont courts (une trentaine de millisecondes), c'est pour cela qu'ils apparaissent sous la forme d'une simple ligne dans le 'waterfall'. La largeur d'un canal provient d'une modulation GMSK à 9600 bauds. La fréquence d'échantillonnage est de 249600 (9600*26) Hertz et le tuner est réglé sur 162 MHz; le spectre affiché couvre donc 162 MHz +/- 249.6/2 KHz (161.8752 - 162.1248 MHz). Le choix de 9600*26 est arbitraire; c'était pour avoir un nombre entier d'échantillons par bit (?).

Voir aussi