Externer ADC MCP3421 mit BASCOM

MCP3421 and AVR

In einem Projekt wurde erstmalig ein externer 18bit-ADC MCP3421 eingesetzt. Dieser wird per I2C vom AVR gesteuert. Von der Hardware aus betrachtet ist es ein Kinderspiel, einen Chip mit zwei Drähten an den AVR zu hängen.

Der MCP3421 hat mit den Anschlüssen Vin+ und Vin- einen Differentialeingang. Er kann damit Spannungen von -2,048 bis +2,048V bezogen auf GND auswerten. Wird Vin- an GND gelegt, reduziert sich der Messbereich auf 0 bis +2,048V. Mit der Halbierung geht damit ein Bit verloren. Die Eingänge sind mit begrenzt belastbaren Dioden geschützt. Übersteuerungen sind also zu vermeiden.

Mit der ausführlichen Beschreibung im Datenblatt [1] sollte die Steuerung über I2C ebenso spielerisch zu realisieren sein. Ganz so simpel war es dann aber doch nicht.

Über I2C werden gesteuert:

  • Einstellen der Auflösung 12, 14, 16 oder 18bit,
  • Einstellen der Vorverstärkung 1x, 2x, 4x, oder 8x,
  • Auslesen des Konversionsergebnisses.

Hier die Codeschnipsel dazu, im besagten Projekt zu einem ATmega32.

Variablenerklärungen:

'External ADC Variables -------------------------------------------------------
Dim bytADCConf As Byte                            'ADC conversion register byte
Dim bytADCRes As Byte                             'ADC resolution 16 or 18bits
Dim bytADCRep As Byte                             'Repetitive ADC runs for mean value
Dim bytADCResult As Byte                          '=1: ADC mean value is avalibable
Dim bytADCOvl As Byte                             '=1: ADC overflow, =0: OK
Dim bytADCNeg As Byte                             '=1: Negative ADC output, =0: Positive
Dim lngADCVal As Long                             '18bit ADC conversion result
Dim bytADCVal(4) As Byte At lngADCVal Overlay
Dim dwdADCMean As DWord                           'Mean ADC conversion result
Dim dwdADCOvl As DWord                            'Max. ADC count for chosen resolution
Const bytADCRead = &B11010001                     'ADC read address, '1101'=device code
Const bytADCWrite = &B11010000                    'ADC write address

Das Auslesen des ADC-Konversionsergebnisses per I2C erfolgt byteweise. Über den Overlay bytADCVal(1…4) auf lngADCVal ist das Ergebnis sofort als Long-Variable verfügbar.

Konfiguration des Software I2C (TWI), ohne Verwendung der TWI-Lib mit dem Vorteil, zwei wahlfreie AVR-Ports, hier PC.0 und PC.1, verwenden zu können:

'Configure TWI on port C ---------------------------------------------------------
CONFIG SCL = PORTC.0                              'TWI SCL
CONFIG SDA = PORTC.1                              'TWI SDA

Einstellen von Verstärkung und Auflösung, hier beschränkt auf bytADCRes = 16 oder 18bit. Es wird jeweils ein Konfigurationsbyte an den MCP3421 (s. Datenblatt) gesandt.

Sub ADCSetGain(byVal bytPGA As Byte)
'Send configuration byte to ADC MCP3421 configuration register
'to set Programmable Gain Amplifier (PGA) gain 1, 2, 4 or 8
'and to set samples per second (SPS) = Resolution 16 or 18bit
'Conversion Mode: Continuous
'Sample Rate:   3.75 SPS (18 bits), variable bytADCRes
'... or        15    SPS (16 bits)
'bit  name  function
'7    RDY   Ready bit indicates the state of conversion
'           Read mode:
'           RDY=1: Output register has not been updated
'           RDY=0: Output register has been updated with the latest conversion data
'           Write mode:
'           Continuous conversion: No effect
'           One-Shot conversion mode:
'           RDY=1: New conversion
'           RDY=0: No effect
'6,5  C1,C0 Channel selection bits, not used (=00) in MCP3421 (1 channel only)
'4    O/C   Conversion Mode Bit
'           O/C=1: Continuous Conversion Mode
'           O/C=0: One-Shot Conversion Mode
'3,2  S1,S0 Sample Rate Selection Bit (SPS=samples per second)
'           00 =  240 SPS (12 bits)
'           01 =   60 SPS (14 bits)
'           10 =   15 SPS (16 bits)
'           11 = 3.75 SPS (18 bits)
'1,0  G1,G0 Programmable Gain Amplifier (PGA) Selection bit
'           00= 1 V/V
'           01= 2 V/V
'           10= 4 V/V
'           11= 8 V/V
'Input:  bytPGA    Gain select:1/2/4/8
'        bytADCRes Resolution (16 or 18bit)
'Output: ./.

   Select Case bytPGA                             'Set ADC configuration bytes
      Case 1                                      'PGA Gain 1
         If bytADCRes = 16 Then
            bytADCConf = &B10011000               'PGA Gain 1/16 bit
         Else
            bytADCConf = &B10011100               'PGA Gain 1/18 bit
         End If
      Case 2                                      'PGA Gain 2
         If bytADCRes = 16 Then
            bytADCConf = &B10011001               'PGA Gain 2/16 bit
         Else
            bytADCConf = &B10011101               'PGA Gain 2/18 bit
         End If
      Case 4                                      'PGA Gain 4
         If bytADCRes = 16 Then
            bytADCConf = &B10011010               'PGA Gain 4/16 bit
         Else
            bytADCConf = &B10011110               'PGA Gain 4/18 bit
         End If
      Case 8                                      'PGA Gain 8
         If bytADCRes = 16 Then
            bytADCConf = &B10011011               'PGA Gain 8/16 bit
         Else
            bytADCConf = &B10011111               'PGA Gain 8/18 bit
         End If
   End Select

   I2CStart
   I2CwByte bytADCWrite                           'Write address
   I2CwByte bytADCConf                            'Write configuration byte
   I2CStop

   If Err = 1 Then                                'Error raised
      LCD "Err Set ADC Gain"
   Else
      LCD "Gain " ; bytPGA ; " / " ; bytADCRes ; "bit"       'Is OK
   End If
   Wait 2

   'Max ADC count depending on resolution, ADC input single ended
   Select Case bytADCRes
      Case 12
         dwdADCOvl = 2047                         '2^11 - 1
      Case 14
         dwdADCOvl = 8191                         '2^13 - 1
      Case 16
         dwdADCOvl = 32767                        '2^15 - 1
      Case Else
         dwdADCOvl = 131071                       '2^17 - 1
   End Select
End Sub

Zum Schreiben in den MCP3421 wird als erstes Byte die Schreibadresse bytADCWrite per I2C gesandt, dann das entsprechend gesetzte Konfigurationsbyte bytADCConf.

Nun noch das Auslesen des Konversionsergebnisses für 18bit-Auflösung.

Sub GetADCMean
'Wait for external ADC to finish a single conversion (bytADCdone = 1)
'calculate the mean value of bytADCRep single conversions
'Input:  bytADCRes      ADC resolution (18 bits)
'        bytADCRep      Number of repetitive runs for mean value
'        dwdADCOvl      ADC overflow count for chosen ADC resolution
'Output: dwdADCMean     ADC Mean value
'        bytADCOvl      =1: at least one ADC overflow detected, =0 else
         bytADCNeg      =1: negative mean value, =0: positive

   bytADCResult = 0
   bytADCOvl = 0                                  'ADC value within range
   dwdADCMean = 0
   bytADCRun = 0

   Do
      'Read MCP3421 ADC conversion result
      'Data bytes  bytADCVal(1...4)are overlayed with lngADCVal (Long)
      'For 18 bit resolution bytes 1, 2 and 3 are read
      I2CStart
      I2CwByte bytADCRead                         'Read address
      I2CrByte bytADCVal(3) , Ack                 'Data byte #3 for 18 bit resolution
      I2CrByte bytADCVal(2) , Ack                 'Data byte #2
      I2CrByte bytADCVal(1) , Ack                 'Data byte #1
      I2CrByte bytADCConf , Nack                  'Configuration byte
      I2CStop

      'Check RDY-bit (#7) of the configuration byte
      If bytADCConf.7 = 0 Then                    'RDY=0 if conversion is complete
         'Valid for 18 bit resolution:
         'bit #1 of bytADCVal(3) is the sign bit, 0=pos., 1=neg.
         If bytADCVal(3).1 = 1 Then               '"-" Sign bit in ADC digital code
            bytADCVal(4) = 255                    'Complete negative long variable
            lngADCVal = -lngADCVal                'ADC conversion result always positive
            bytADCNeg = 1                         'Negative ADC result = neg. Detector
         Else
            bytADCVal(4) = 0                      'Complete positive long variable
            bytADCNeg = 0                         'Positive ADC result = pos. Detector
         End If
         If bytADCRun < bytADCRep Then            'Accumulate bytADCRep times
            dwdADCMean = dwdADCMean + lngADCVal   'Add for mean ADC value
            If lngADCVal >= dwdADCOvl Then        'ADC overflow
               bytADCOvl = 1                      'ADC value out of range
            End If
            Incr bytADCRun                        'Next ADC conversion
         Else                                     'Mean ADC value complete
            dwdADCMean = dwdADCMean / bytADCRep   'ADC mean value
            bytADCRun = 0
            bytADCResult = 1                      'Finish accumulation loop
         End If
      End If
   Loop Until bytADCResult = 1
End Sub

Das Auslesen wird mit Senden der Leseadresse bytADCRead angestoßen. Anschließend werden die Datenbytes, hier 3 für 18bit Auflösung, gelesen. Für 16bit Auflösung wären es nur 2. Das vierte, das Konfigurationsbyte, wird mit "NACK" (!) gelesen.

Der MCP3421 ist in Sub ADCSetGain auf "Continuous Mode" eingestellt, er läuft also ständig. Steht ein neues Konversionsergebnis zur Verfügung, ist das Bit 7 (Ready-Bit) des gelesenen Konfigurationsbytes 0. Dann kann das Konversionsergebnis ausgewertet werden.

Die zusammengesetzten 3 Datenbytes sind im Zweierkomplement kodiert (s. Datenblatt, Kap. 4 und z.B. [2]). Das achtzehnte Bit bytADCVal(3).1 gibt damit bei 18bit Auflösung das Vorzeichen an. Wenn es 1 ist, handelt es sich um eine negativ kodierte Zahl. Die höherwertigen Bits in bytADCVal(3) sind dann auch 1. Ist es 0, entsprechend positiv. Die höherwertigen Bits bytADCVal(3) sind dann auch 0. Um nun das Konversionsergebnis, bestehend aus nur 3 Bytes, in eine ordentliche 4 Byte Long-Variable zu überführen, wird bei negativem Ergebnis (Bit bytADCVal(3).1 = 1) das oberste Byte 4 auf 255 (dezimal) = Binär "11111111" ergänzt. Bei positivem Ergebnis wird das Byte 4 = 0 (dezimal) = Binär "00000000". Über die Overlay-Konstruktion ist damit die Long-Variable lngADCVal unmittelbar gesetzt.

Im vorliegenden Projekt war nur der Betrag des Konversionsergebnisses gewünscht. Somit wird ein negatives lngADCVal im Vorzeichen umgedreht, dabei das Vorzeichen in bytADCNeg =1 (negativ) bzw. =0 (positiv) vermerkt. lngADCVal wird in die vorzeichenlose DWord-Variable dwdADCMean als Ergebnis umgespeichert. Das geht dann doch einfacher. Negative ADC-Konversionsergebnisse, die durch Bitfehler oder negative Rauschspitzen durchaus vorkommen können, werden riguros genullt. Hierzu die Do...Loop aus dem vorherigen Beispiel für eine unsymmetrische ADC-Eingangsspannung ("single ended", Pin 6 im obigen Bild nach GND), Änderungen blau gefärbt:

Do
      'Read MCP3421 ADC conversion result
      'Data bytes  bytADCVal(1...4)are overlayed with lngADCVal (Long)
      'For 18 bit resolution bytes 1, 2 and 3 are read
      I2CStart
      I2CwByte bytADCRead                         'Read address
      I2CrByte bytADCVal(3) , Ack                 'Data byte #3 for 18 bit resolution
      I2CrByte bytADCVal(2) , Ack                 'Data byte #2
      I2CrByte bytADCVal(1) , Ack                 'Data byte #1
      I2CrByte bytADCConf , Nack                  'Configuration byte
      I2CStop

      'Check RDY-bit (#7) of the configuration byte
      If bytADCConf.7 = 0 Then                    'RDY=0 if conversion is complete
         'Valid for 18 bit resolution:
         'bit #1 of bytADCVal(3) is the sign bit, 0=pos., 1=neg.
         bytADCVal(4) = 0
         'Set lngADCVal = 0 if negative, possible voltage ripple < 0
         If bytADCVal(3).1 = 1 Then               '"-" Sign bit in ADC digital code
            lngADCVal = 0                         'negative, set = 0
         End If
         If bytADCRun < bytADCRep Then            'Accumulate bytADCRep times
            dwdADCMean = dwdADCMean + lngADCVal   'Add for mean ADC value
            If lngADCVal >= dwdADCOvl Then        'ADC overflow
               bytADCOvl = 1                      'ADC value out of range
            End If
            Incr bytADCRun                        'Next ADC conversion
         Else                                     'Mean ADC value complete
            dwdADCMean = dwdADCMean / bytADCRep   'ADC mean value
            bytADCRun = 0
            bytADCResult = 1                      'Finish accumulation loop
         End If
      End If
   Loop Until bytADCResult = 1

Wahlweise kann mit der Variablen bytADCRep die Anzahl Einzelkonversionen für eine Mittelwertbildung vorgegeben werden. In den Tests erwies sich aber bereits ein Einzelschuss (bytADCRep = 1) als genau und stabil genug. Bei 18bit Auflösung ist die Konversionsrate 3,75 pro Sekunde (SPS = Samples per second), was einschließlich der Laufzeit im AVR auf ca. 300 msec hinausläuft. Mittelwerte werden dann zur Geduldsprobe. Die Sub GetADCMean wird wegen der Do...Loop Until erst dann verlassen, um ein Ergebnis zu liefern, wenn alle beschriebenen Vorgänge einschl. Mittelwertbildung abgeschlossen sind.

Ein Schnelltest am Anfang eines AVR-Programms könnte noch anzeigen, ob der MCP3421 überhaupt ansprechbar ist.

Function ADCCheck() As Byte
'Check I2C-ADC, try th write configuration byte bytADCWrite to the device
'ADCCheck = 1: OK, 0: else

   I2CInit
   I2CStart
   I2CwByte bytADCWrite                           'Try to write Write address
   I2CStop
   If Err = 1 Then                                'Error raised
      ADCCheck = 0                                'Not OK
   Else
      ADCCheck = 1                                'Is OK
   End If
End Function

Referenzen

[1]  http://ww1.microchip.com/downloads/en/DeviceDoc/22003e.pdf
[2]  https://de.wikipedia.org/wiki/Zweierkomplement

 

Einordung: