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