Raspberry-Pi-Projekt: 12-Bit-Digital-Analog-Wandler MCP4725

Prof. Jürgen Plate

Raspberry Pi: 12-Bit-Digital-Analog-Wandler MCP4725

Allgemeines

Weder der Raspberry Pi noch die Arduinos (Uno, Mega). Eine Analgoausgabe kann nur mittels Pulsweitenmodulation (PWM) erfolgen. Ab einer genügend hohen PWM-Frequenz können damit LEDs gedimmt oder DC-Motoren drehzahlgesteuert werden, ohne dass funktionell ein Unterschied zu einer echten Analogspannung merkbar ist. Manchmal wird jedoch eine "echte" Analogspannung benötigt. In solchen Fällen wird ein Digital-Analog-Wandler (DAC) benötigt. DACs sind in mannigfachen Ausführungen mit unterschliedlicher Auflösung und Ansteuerung.

Der hier verwendete MCP4725 ist ein 12-Bit-Digital-Analog-Wandler, der über einen internen, per I2C-Bus programmierbaren Konfigurations-EEPROM sowie über eine externe Bus-Adressierung (A0) und integrierten Power-on-Reset und Onboard-Ladungspumpe verfügt. Die Versorgungsspannung darf zwischen 2,7 und 5,5 V liegen. Die Stromaufnahme im Betrieb beträgt 400 μA, im Power-down-Modus 2 μA.

Die Ausgangsspannung kann recht einfach per I2C-Bus eingestellt werden. Zusätzlich gibt es ein EEPROM, in dem ein Startwert festgehalten werden kann, der beim nächsten Einschalten ganz ohne Zutun des RasPi sofort erscheint. Bei 5 V Versorgungsspannung (= Vref) beträgt die Schrittweite 1,22 mV, bei Vref = 3,3 V entsprechend 0,81 mV. Ganz allgemein berechnet sich die zu einem Digitalwert Val gehörende Ausgangsspannung zu:

            Vref * Val
    Vout = -----------       
              4096
Für Vref = 5 V ergibt sich z. B. für Val = 3072 eine Ausgangsspannung von 5 * 3072 / 4096 = 3,75 V. Stellt man die Gleichung um, kann man den Wert Val für die gewünschte Ausgangsspannung nach folgender Formel berechen:
           Vout * 4096
    Val = -------------       
              Vref
Für eine Ausgangsspannung von 2 V egibt sich dann bei Vref = 5 V: Val = 2 * 4096 / 5 = 1638,4 → abgerundet: 1638 (es wird dann eine Spannung von 1,999 V ausgegeben).

Hardware

Von Adafruit, SparcFun und anderen Herstellern wird ein winziges Breakout-Board angeboten, das den MCP4725 nebst Pullup-Widerständen für die Leitungen SDA und SCL und die Adressleitung und einen Blockkondensator für die Versorgungsspannung enthält. Bei 3,3-V-Betrieb kann es direkt mit dem Raspberry Pi verbunden werden, bei 5-V-Betrieb ist für SDA und SCL ein Level-Shifte notwendig - oder man lötet die Pullup-Widerstände für SDA und SCL aus.

Der Anschluss beschränkt sich auf die für I2C üblichen vier Verbindungen:

MCP4725   Raspberry Pi
Vdd       3,3 V   Pin 1
GND       GND     Pin 6
SDA       GPIO2   Pin 3
SCL       GPIO3   Pin 5
Beachten Sie, dass der MCP4725 maximal 25 mA am Ausgang liefern kann. Für höhere Ströme muss ein Verstärker dahinter geschaltet werden.

Software

Die Wandlungszeit (nach Datenblatt 6 μs) würde prinzipiell mehr als 166.000 Wandlungen/s ermöglichen, jedoch ist die zur Übertragung des auszugebenden Wertes über die I2C-Schnittstelle nötige Zeit um ein Vielfaches höher.

Der Chip kennt zwei grundlegende Ausgabe-Modi:

Beachten Sie dabei, dass die Anzahl der Schreibvorgänge ins EEPROM begrenzt ist. Nach Angabe des Datenblatts sind 1 Million Schreibzyklen möglich, was zunächst nach sehr viel klingt. Dazu eine Beispielrechnung: Die Dauer eine Schreibvorgangs ins EEPROM beträgt 50 μs, das EEPROM kann also 200 mal pro Sekunde geschrieben werden. Bei einem Maximum von 1 Mio. Schreibzyklen ist bei andauernden Schreiben nach 5000 s Schluss; das sind ca. 83 Minuten, also nicht einmal anderthalb Stunden.

Schließlich gibt es noch einen Read-Mode, hier liefert der MCP4725 fünf Bytes zurück, ein Statusbyte, den augenblicklichen Ausgabewert (2 Bytes) und den EEPROM-Inhalt (2 Bytes) - Näheres findet man im Datenblatt.

Zum Anstreuern des D/A-Wandlers genügt eine kleine Pyhon-Bibliothek. Zu Beginn der Datei werden die benötigten Konstanten definiert. So ist die Adresse des Chips per Default auf 0x62 festgelegt, kann aber beim Anlegen einer Instanz geändert werden (möglich wäre noch 0x63). Es gibt nur drei Funktionen für den Anwender:

Zu beachten ist, dass die Verteilung der Datenbits bei den beiden Modi zum Setzen des D/A-Wandlers unterschiedlich ist.
#!/usr/bin/env python

import smbus

# Register-Werte fuer Kommandos:
WRITEDAC         = 0x40
WRITEDACEEPROM   = 0x60
WRITEFAST        = 0x00

# Power-down Modi (per fast-mode command):
# (siehe table 5-2 im Datenblatt, table 6-2 fuer fast-mode)
POWER_DOWN = {1: 0x10, 100: 0x20, 500: 0x30}

# Default I2C-Addresse (moeglich sind 0x62 und 0x63):
MCP4725_DEFAULT_ADDRESS  = 0x62


class MCP4725(object):
    def writeList(self, register, data):
        # Write bytes to the specified register.
        self._bus.write_i2c_block_data(self._address, register, data)

    def __init__(self, address = MCP4725_DEFAULT_ADDRESS, busnum = 1):
        # eine Instanz des MCP4725 DAC erzeugen.
        self._address = address
        self._bus = smbus.SMBus(busnum)

    def set_voltage(self, value, persist=False):
        # Ausgangsspannung setzen. value ist eine positive 12-bit-Zahl
        # (0-4095), welche die Ausgangsspannung nach der folgenden Formel
        # festlegt:
        #
        #  Vout =  (VDD*value)/4096
        #
        # Ist der Parameter persist = True, wird der Wert auch im EEPROM
        # gespeichert und steht nach einem Reset am Ausgang an.

        # value auf einem positiven 12-Bit-Wert begrenzen.
        value = value & 0xFFF
        # Register-Bytes erzeugen und senden.
        # Siehe Datenblatt figure 6-2:
        reg_data = [(value >> 4) & 0xFF, (value << 4) & 0xFF]
        if persist:
            self.writeList(WRITEDACEEPROM, reg_data)
        else:
            self.writeList(WRITEDAC, reg_data)

    def set_fast(self, value):
        # Ausgangsspannung im Fast Mode setzen (Formel siehe oben).

        # value auf einem positiven 12-Bit-Wert begrenzen.
        value = value & 0xFFF
        # Kommando erzeugen, das gleichzeitig einen Teil des Werts enthaelt.
        # Siehe Datenblatt figure 6-1
        reg = value >> 8   # leave only the top 4 bits
        reg |= WRITEFAST   # include fast command
        self.writeList(reg, [value & 0xFF])

    def power_down(self,resistor=1):
        # In den Schlaf-Modus schalten, per Fast-Kommando
        if (resistor == 1) or (resistor == 100) or (resistor == 500):
            mode = POWER_DOWN[resistor]
            mode |= WRITEFAST
            self.writeList(mode, [0x00])
            return resistor
        else:
            return -1
Entsprechen einfach gestaltet sich dann auch das erste Testprogramm. Der MCP4725 wird mit 3,3 V versorgt. Das Programm gibt die minimale Spannung (0 V), die Mitte (1,65 V) und die maximale Spannung (3,3 V) aus.
#!/usr/bin/env python

import time
from MCP4725 import MCP4725
dac = MCP4725()

# Endlosschleife mit drei Spannungswerten (VDD = 3,3 V)
while True:
    print('0 V')
    dac.set_voltage(0)
    time.sleep(1)
    print('1,65 V')
    dac.set_voltage(2048)
    time.sleep(1)
    print('3,3 V')
    dac.set_voltage(4095)
    time.sleep(1)
Das zweite Programm vergleicht set_voltage() mit set_fast(). Dazu wird mit jeder Funktion eine Rampe von 0 bis 4095 ausgegeben und die dafür benötigte Zeit gemessen.
#!/usr/bin/env python

import time
from MCP4725 import MCP4725
dac = MCP4725()

while True:
    # Normalbetrieb
    t_start1 = time.time()
    # Spannung schrittweise steigern
    for i in xrange(4096):
        dac.set_voltage(i)
    t_end1 = time.time() - t_start1

    # Betrieb im Fast mode
    t_start2 = time.time()
    # Spannung schrittweise steigern
    for i in xrange(4096):
        dac.set_fast(i)
    t_end2 = time.time() - t_start2

    print ('Ausgabe mit set_voltage() dauerte %d s' % t_end1)
    print ('Ausgabe mit set_fast() dauerte %d s' % t_end2)
Für die C-Programmierer gibt es auch zwei Demo-Programme. Das erste Programm (siehe Linkliste unten) gibt einen Analogwert im Standard-Modus aus. Das folgende Listing zeigt nur die Ausgaberoutine:
int write_dac(int fd, int val)
  /* Send val out of the MCP4725 */
  {
  uint8_t writeBuf[4];

  val = val & 0xFFF;
  writeBuf[0] = 0b01000000;
  /* Control Byte, siehe S. 18 - 19 Datenblatt
   * bits 7-5; 010 write DAC; 011 write DAC and EEPROM
   * bits 4-3 unused
   * bits 2-1 PD1, PD0 PWR down P19 00 normal.
   * bit 0 unused
   */
  writeBuf[1] = val >> 4;
  /* Datenbits 7 - 0: D11 - D4 */
  writeBuf[2] = val << 4;
  /* Datenbits 7 - 4: D3 - D0 */
  if (write(fd, writeBuf, 3) != 3)
    { return -1; }
  return 0;
  }
Das Programm wird mittels gcc -Wall -omcp4725 MCP4725.c übersetzt und kann dann auf der Kommandozeile gestartet werden. Es läuft in einer Endlosschleife und erlaubt die Eingabe eines Wertes zwischen 0 und 4095, der dann auf dem MCP4725 ausgegeben wird. Ersetzt man die Zeile "writeBuf[0] = 0b01000000;" durch "writeBuf[0] = 0b01100000;" wird auch des EEPROM mit den Daten gefüttert.

Die Linkliste führt noch ein weiteres C-Programm auf. Hier wird ein Sinussignal erzeugt udn ausgegeben, das man sich auf dem Bildschirm eine Oszilloskops ansehen kann. Weil die Berechnung des Sinus doch etwas Rechenzeit beanspricht, wurde ein Sinustabelle erzeugt, die dann für die Ausgabe herangezogen wird. Die Tabelle ist insbesondere interessant, wenn man das Programm auf einem "kleinen" Controller, z. B. den Arduino, portiert, der für Gleitpunktoperationen und Sinusberechnung doch beträchtlich länger braucht.

Links


Copyright © Hochschule München, FK 04, Prof. Jürgen Plate
Letzte Aktualisierung: