Amateurfunk verbindet die Welt

Externer ADC MCP3421 mit BASCOM

Erstellt: DL6GL, 07.12.2015, letzte Änderung 

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 Klacks, 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.

Wie mit einer gleitenden Mittelung ein Ergebnis rascher zur Anzeige kommen kann, ist hier beschrieben. Die vollständige Anwendung in einem HF-Powermeter ist hier zu sehen.

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