Input Analogico SPI: gli integrati MCP300x

Da raspibo.
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Proviamo ora a collegare un integrato della serie MCP300x all'interfaccia SPI master del Raspberry PI.

L'esempio si riferisce al MCP3008 (venduto per esempio da Adafruit). E' un convertitore Analogico Digitale a 8 canali con 10 bit di precisione.

Occorre caricare il driver come descritto nella pagina Collegare dispositivi SPI.

Per collegare un MCP3008 come unita' SPI 0 occorre collegare i fili come segue:

        MCP3008       PIN Raspberry PI
        +--v--+
in-ch0--|1  16|---VDD (al pin 1 +3.3V)
in-ch1--|2  15|--Vref (al pin 1, o a un riferimento come spiegato nel testo )
in-ch2--|3  14|--AGND (al pin 6)
in-ch3--|4  13|---CLK (SPI CLK pin 23)
in-ch4--|5  12|--DOUT (SPI MISO pin 21)
in-ch5--|6  11|---DIN (SPI MOSI pin 19)
in-ch6--|7  10|----CS (SPI CS0 pin 24, oppure CS1 pin 26)
in-ch7--|8   9|--DGND (al pin 6)
        +-----+

Se il pin 10 del MCP3008 viene collegato al pin 24 del Raspberry PI l'unità verrà riconosciuta come /dev/spi0.0, se invece viene collegata al pin 26 del Raspberry PI sarà /dev/spi0.1. E' possibile collegare due MCP3008 per avere 16 ingressi analogici mettendo in parallelo gli altri pin verso il Raspberry PI tranne il pin 10.

VREF (pin 15) e' la tensione di riferimento. I valori di tensione che verranno misurati saranno compresi tra 0 (AGND) e VREF. La conversione fra il valore numerico a 10 bit e la tensione e':

VIN = (valore_letto * VREF) / 1024.

Se si collega il pin 15 in parallelo al pin 16, pertanto alla tensione di 3.3v la conversione sara':

   0 -> 0V
   1 -> 0.00322V
   2 -> 0.00645V 
...
1023 -> 3.29678V

il passo di campionamento e' di 3.2mV.

Se si vuole una precisione maggiore (restringendo il range) occorre un circuito che fornisca al pin 15 una tensione precisa. Io propongo il seguente (che fa uso dell'integrato LM385-1.2Z, uno zener di precisione a 1.235V, come resistenza dovrebbe andare bene una da 10Kohm):

Mcp3008vref.png

in questo modo i valori analogici saranno fra 0 e 1.235V

   0 -> 0V
   1 -> 0.00121V
   2 -> 0.00242V
...
1023 -> 1.23379V

il passo di campionamento sarà di circa 1mV. E' una precisione sufficiente per poter misurare le correnti con questo sensore amperometrico oppure per misurare le temperature con il sensore LM35.

Gli integrati MCP3004 e MCP3002 sono molto simili, hanno rispettivamente 4 e 2 entrate analogiche. Basta collegarli secondo lo stesso schema del MCP3008 seguendo i nomi dei segnali (il PIN sara' diverso perche' i contenitori sono diversi, 14 pin per l'MCP3004 e 8 pin per il 3002). Nell'MCP3002 VDD e VREF sono collegati assieme.

MCP3008 in C

Per leggere il valore di un input analogico occorre spedire tre byte:

+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|0 0 0 0 0 0 0 1| |B|C C C|x x x x| |x x x x x x x x|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+

Dove il bit B e' 1 se si vuole l'input e' sbilanciato (normalmente) 0 se bilanciato. (si puo' leggere la differenza di potenziale fra due input successivi, ma la tensione deve essere sempre >= AGND per entrambi gli input). I tre bit C selezionano l'ingresso da leggere. I bit x vengono ingorati.

La risposta verra' restituita negli ultimi 10 bit:

+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|x x x x x x x x| |x x x x x x|R R| |R R R R R R R R|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+

Ecco il programma per leggere il valore dell'ingresso 0 (con Vref 3.3v).

/*
 * Inspired by spidev.c
 * Copyright (c) 2007  MontaVista Software, Inc.
 * GPLv2
 */

#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

static const char *device = "/dev/spidev0.0";
static uint8_t mode;
static uint8_t bits = 8;
static uint32_t speed = 500000;
static uint16_t delay;

static int read3008(int fd, int line, int bil)
{
        int ret;
        uint8_t tx[]={1,0,0};
        uint8_t rx[]={1,0,0};
        struct spi_ioc_transfer tr = {
                .tx_buf = (unsigned long)tx,
                .rx_buf = (unsigned long)rx,
                .len = ARRAY_SIZE(tx),
                .delay_usecs = delay,
                .speed_hz = speed,
                .bits_per_word = bits,
        };
        tx[1] = ((bil & 1)?0x00:0x80) | ((line & 0x7)<< 4);
        ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
        if (ret < 1) {
                perror("can't send spi message");
                abort();
        }
        return ((rx[1]&0x3)<<8)+rx[2];
}

int main(int argc, char *argv[])
{
        int ret = 0;
        int fd;

        fd = open(device, O_RDWR);
        if (fd < 0) {
                perror("can't open device");
                abort();
        }

        int rv=read3008(fd,0,0);
        printf("%d -> %dmV\n",rv,rv*3300/1024);

        close(fd);

        return ret;
}

MCP3008 in Python

Ecco l'equivalente programma python:

#!/usr/bin/env python

import spidev
import time

mcp3008 = spidev.SpiDev(0,0)

adcnum=0
ret=mcp3008.xfer2([1,(8+adcnum)<<4,0])

adcout = ((ret[1]&3) << 8) + ret[2]

print adcout, " = ", adcout * 3.3 / 1024, "mV"

mcp3008.close()

MCP3204 in Python

Ho recentemente acquistato degli integrati MCP3204. Essendo 04 hanno solo 4 input e quindi hanno solo 14 piedini

                   +--v--+
           in-ch0--|1  14|---VDD (al pin 1)
           in-ch1--|2  13|--Vref (al pin 1, o a un riferimento come sopra)
           in-ch2--|3  12|--AGND (al pin 6)
           in-ch3--|4  11|---CLK (SPI CLK pin 23)
           NC------|5  10|--DOUT (SPI MISO pin 21)
           NC------|6   9|---DIN (SPI MOSI pin 19)
(al pin 6) DGND----|7   8|----CS (SPI CS0 pin 24, oppure CS1 pin 26)
                   +-----+

Il programma va variato in questo modo:

#!/usr/bin/env python

import spidev
import time

mcp3204 = spidev.SpiDev(0,0)

adcnum=0
ret=mcp3204.xfer2([6,adcnum<<6,0])

adcout = ((ret[1]&0xf) << 8) + ret[2]

print adcout, " = ", adcout * 3.3 / 4096, "mV"

mcp3204.close()

Motivo del cambiamento: il convertitore MCP3204 (e tutti quelli della serie MCP32xx) ha una precisione di 12 bit, due in piu' della serie MCP30xx.

Il treno di impulsi da inviare e' quindi:

+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|0 0 0 0 0 1|B|C| |C C|x x x x x x| |x x x x x x x x|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+

Dove il bit B e' 1 se si vuole l'input e' sbilanciato (normalmente) 0 se bilanciato. (si puo' leggere la differenza di potenziale fra due input successivi, ma la tensione deve essere sempre >= AGND per entrambi gli input). I tre bit C selezionano l'ingresso da leggere. (per il 3208, per il 3204 sono solo 2 bit). I bit x vengono ingorati.

e la risposta sara':

+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
|x x x x x x x x| |x x x x R R R R| |R R R R R R R R|
+-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+