Blog der Heimetli Software AG

Spannungen messen mit dem Raspberry PI

Weil der Raspberry keinen AD-Wandler hat, muss bei Bedarf ein externer AD-Wandler angeschlossen werden.

Für einen ersten Versuch habe ich einen MCP3202 an den Raspberry angeschlossen. Der MCP3202 ist ein 12Bit AD-Wandler von Microchip.

Warum der MCP3202 ?

Diesen Chip habe ich aus folgenden Gründen ausgewählt:

  • Serieller Anschluss, SPI-kompatibel
  • Versorgung mit 3.3V
  • Interne Referenz
  • DIL-Gehäuse mit 8 Pins

Das Gehäuse war mir wichtig, damit ich das IC auf ein Prototyping PI Plate Board auflöten könnte. Die meisten Chips sind heute nur noch als SMD erhältlich.

Dank der internen Referenz wird die Schaltung sehr einfach. Wie ich erst später herausgefunden habe, benutzt der Chip einfach die Versorgungsspannung als Referenz. Das dürfte nicht wirklich für höchste Präzision reichen.

Die Schaltung

Anschluss-Schema für den MCP3202 am Raspberry PI

Die beiden unbelegten Pins sind die analogen Eingänge. Der folgende Code benutzt den Eingang an Pin 2.

Für ein praxistaugliches System sollte ein Kondensator zwischen GND und +3.3V geschaltet werden um Störimpulse auszufiltern. Siehe Datenblatt des MCP3202.

Den Device-Driver für SPI installieren

Bei Raspbian ist ein Device-Driver für den SPI-Anschluss dabei, aber er muss zuerst aktiviert werden. Im File /etc/modprobe.d/raspi-blacklist.conf wird dafür die Zeile blacklist spi-bcm2708 auskommentiert.

Die Software

Der SPI-Anschluss des MCP3202 ist recht eigenwillig. Er wartet auf ein Startbit und ein paar Bits die den Messmodus bestimmen.

Zum Glück gibt's im Datenblatt einen Hinweis, dass im ersten Byte das LSB das Startbit sein soll. So kommen die Bits des Messwerts sauber in die beiden folgenden Bytes.

Beachten Sie, dass der Code das recht haben muss, auf /dev/spidev0.0 zu lesen und schreiben. Da dieses File normalerweise root gehört, kann ein Prozess von einem normalen User das nicht.

Die einfachste Version, die nötigen Rechte zu bekommen, ist ein Aufruf im Stil von sudo ./measure.

Eine andere Variante ist es, den Owner des Programms auf root zu setzen und ihm das Setuid-Bit zu geben. Dann läuft der Prozess mit den Rechten von root.

/******************************************************************************/
/*                                                                            */
/*                                                          FILE: measure.cpp */
/*                                                                            */
/*    Measures an analog value using an MCP3202 wired to a Raspberry PI       */
/*    =================================================================       */
/*                                                                            */
/*    The Microchip MCP3202 is a two channel 12bit AD converter. It was       */
/*    connected to the SPI bus of the Raspberry PI as shown at                */
/*    http://blog.heimetli.ch/raspberry-pi-mcp3202-12bit-ad-converter.html    */
/*                                                                            */
/*    This code was compiled with g++ and run under the Raspbian OS. It       */
/*    uses the spidev driver bundled with current Raspbian releases.          */
/*                                                                            */
/*    The program uses the device file spidev0.0 which belongs to root        */
/*    by default. For a first test run it like this: sudo ./measure           */
/*                                                                            */
/*    V0.01  07-SEP-2013   Te                                                 */
/*                                                                            */
/******************************************************************************/

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

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

/**********/
 int main()
/**********/

{
   int fd = open( device, O_RDWR ) ;
   if( fd < 0 )
   {
      fprintf( stderr, "can't open device\n" ) ;
      return 1 ;
   }

   int ret = ioctl( fd, SPI_IOC_WR_MODE, &mode ) ;
   if( ret == -1 )
   {
      close( fd ) ;
      fprintf( stderr, "can't set mode\n" ) ;
      return 2 ;
   }

   ret = ioctl( fd, SPI_IOC_WR_BITS_PER_WORD, &bits ) ;
   if( ret == -1 )
   {
      close( fd ) ;
      fprintf( stderr, "can't set bits\n" ) ;
      return 3 ;
   }

   ret = ioctl( fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed ) ;
   if( ret == -1 )
   {
      close( fd ) ;
      fprintf( stderr, "can't set speed\n" ) ;
      return 4 ;
   }

   // Read channel 1, unsigned mode
   unsigned char tx[]   = { 0x01, 0x80, 0x00 } ;
   unsigned char rx[]   = { 0x00, 0x00, 0x00 } ;

   struct spi_ioc_transfer spi[1];
   memset( &spi, 0, sizeof(spi) ) ;
 
   spi[0].tx_buf        = (unsigned long)tx ;
   spi[0].rx_buf        = (unsigned long)rx ;
   spi[0].len           = 3 ;
   spi[0].delay_usecs   = 0 ; 
   spi[0].speed_hz      = speed ;
   spi[0].bits_per_word = bits ;
   spi[0].cs_change     = 0 ;
 
   ret = ioctl( fd, SPI_IOC_MESSAGE(1), &spi ) ;
   if( ret == -1 )
   {
      close( fd ) ;
      fprintf( stderr, "can't transfer data\n" ) ;
      return 5 ;
   }
 
   close( fd ) ;

   printf( "%d\n", ((rx[1] & 0x0F) << 8) | rx[2] ) ;

   return 0 ;
}
measure.cpp herunterladen