Forum: Mikrocontroller und Digitale Elektronik Atmega Differential ADC


von Steffen H. (stef_fen)


Lesenswert?

Hallo Zusammen,

welche Atmegas können denn Differential ADC? Ich habe was von Atmega 16, 
32 und 128 gefunden. Funktioniert das bei denen auch im PDIP Package?

Das war bis jetzt mein Code um Spannungen mit einem Atmega 8 zu messen:
1
void ADC_Init(void) 
2
{
3
  //Initialisierung fuer Spannungsmessung
4
  uint16_t result;
5
 
6
  // interne Referenzspannung als Refernz für den ADC wählen:
7
  ADMUX = (1<<REFS1) | (1<<REFS0);
8
  ADCSRA = (1<<ADPS2) | (1<<ADPS1);     // Frequenzvorteiler, Dummyreadout
9
  ADCSRA |= (1<<ADEN);                  // ADC aktivieren
10
  ADCSRA |= (1<<ADSC);                  // eine ADC-Wandlung 
11
  while (ADCSRA & (1<<ADSC)) {         // auf Abschluss der Konvertierung warten
12
  }
13
  result = ADCW;
14
}
15
 
16
uint16_t ADC_Read(uint8_t channel)
17
{
18
  // Initialisierung fuer Spannungseinzelmessung
19
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F); //Kanal waehlen, ohne andere Bits zu beeinflußen
20
  ADCSRA |= (1<<ADSC);            // eine Wandlung "single conversion"
21
  while (ADCSRA & (1<<ADSC) ) {   // auf Abschluss der Konvertierung warten
22
  }
23
  return ADCW;
24
}
25
26
int main (void)
27
{
28
  uint16_t  spannung; //Spannungsmessung
29
  ADC_Init();
30
  spannung = ADC_Read(0);
31
}


Wie läuft das dann beim Differential ADC?

Vielen Dank. Gruß Steffen

von Peter II (Gast)


Lesenswert?

Steffen Ha schrieb:
> Wie läuft das dann beim Differential ADC?

genauso, nur das halt nicht gegen GND sonder gegen einen anderen ADC 
gemessen wird. Steht eigentlich recht gut beschrieben im Datenblatt.

von Steffen H. (stef_fen)


Lesenswert?

Eine Formel zur Berechnung des ADC Werts habe ich schon mal gefunden:

ADC = ((VPOS – VNEG) ⋅ GAIN ⋅ 512) / VREF

mit Werten:

ADC = ((3,32V - 1,81V)  1  512) / 2,56V
ADC = 300.64

Die Tabelle vom Atmega 1284P auf Seite 259 verwirrt mich noch ein 
bischen. Ich verstehe noch nicht ganz wie ich das im Programm ändern 
muss. Über  MUX3 / MUX 4 müsste ich doch festlegen welchen Port mit 
welcher Verstärkung zum Messen genommen werden soll oder?

Gruß Steffen

von Peter II (Gast)


Lesenswert?

Steffen Ha schrieb:
> Die Tabelle vom Atmega 1284P auf Seite 259 verwirrt mich noch ein
> bischen. Ich verstehe noch nicht ganz wie ich das im Programm ändern
> muss. Über  MUX3 / MUX 4 müsste ich doch festlegen welchen Port mit
> welcher Verstärkung zum Messen genommen werden soll oder?

du musst einfach in MUX(0..4) den Wert eintragen der in der Tabelle 
steht.

Wenn du zwischen ADC3 und ADC2 mit 10 facher verstärkung messen willst 
dann must du für MUX 01101 eintragen.

von Steffen H. (stef_fen)


Lesenswert?

Wenn ich den ADC0 und den ADC1 für den Differential Mode nutzen wollte 
wäre das doch richtig oder?
1
ADMUX = (1<<MUX4) | (0<<MUX3) | (0<<MUX2) | (0<<MUX1) | (0<<MUX0);

Dies würde dann doch das ersetzen:
1
ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F); //Kanal waehlen, ohne andere Bits zu beeinflußen

von Karl H. (kbuchegg)


Lesenswert?

> Wenn ich den ADC0 und den ADC1 für den Differential Mode nutzen
> wollte wäre das doch richtig oder?

>
1
ADMUX = (1<<MUX4) | (0<<MUX3) | (0<<MUX2) | (0<<MUX1) | (0<<MUX0);

Das ist nichts anderes als die Zahl 16. (das binäre Muster)
Müsste auch im Datenblatt im Grunde so stehen, weil die von dir gewählte 
Kombination in der Zeile 16 steht (bei 0 angefangen zu zählen)

> Dies würde dann doch das ersetzen:
>
>
1
ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F); //Kanal waehlen, ohne > andere Bits zu beeinflußen


Lass das einfach so.
Setzte channel auf 16 und alles ist gut.
Nimm das Wort 'channel' nicht zu wörtlich. Lies es als 'Messmodus'. Im 
Regelfall ist das tatsächlich einfach nur die Kanalnummer. Bei dir eben 
nicht. Bei dir ist das dann eben ein "Spezialkanal", der nicht nur einen 
Eingang berücksichtigt sondern deren 2 (samt Gain). Aber in den ADC 
Routinen behandelst du den wie jeden anderen Kanal auch.

von Steffen H. (stef_fen)


Lesenswert?

Vielen Dank. Man lernt immer wieder was dazu. Eins würde mich noch 
interessieren. Wie funktioniert das Kanal wählen ohne andere Bits zu 
beeinflussen?

von Karl H. (kbuchegg)


Lesenswert?

Steffen Ha schrieb:
> Vielen Dank. Man lernt immer wieder was dazu. Eins würde mich noch
> interessieren. Wie funktioniert das Kanal wählen ohne andere Bits zu
> beeinflussen?


ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);



0x1F ist binär                             0b00011111
also lauter 1 Bits dort, wo die neuen Kanalbits reinkommen

~0x1F ist die Umkehrung davon, also        0b11100000

Das verundet mit ADMUX ergibt ein Byte, welches an den Stellen an denen 
in diesem Maskenbyte ein 0-Bit steckt, ebenfalls auf jeden Fall ein 
0-Bit steht

Hatte ADMUX also den Bitinhalt (ich benenne die Bits mal mit Buchstaben)
abcdefgh, dann kommt mit der Verundung raus abc00000. Das ist: die Bits 
abc stammen noch aus dem Original von ADMUX. Die restlichen Bits (dortr 
wo die Kanalbits sitzen) sind auf jeden Fall 0. Damit ist die alte 
Kanalinfo aus dem aus ADMUX ausgelesenen Byte eliminiert.


2. Teil:
channel & 0x1F

Die Verundung mit 0x1F ist nur zur Vorsicht, damit im Ergebnis diesmal 
die 3 höherwertigsten Bits auf jeden Fall 0 sind. die 5 unteren Bits, 
die die Kanalinformation tragen, bleiben durch diese Operation 
unangetastet. Die 8 Originalbits des Kanalbytes wären zb gewesen 
ABCDEFGH. Nach der Verundung hat man 000DEFGH


Jetzt hat man also 2 Einzelteile.

   aus ADMUX entstanden             aus dem Kanalbyte entstanden

    abc00000                            000DEFGH

und die beiden werden zusammengeodert. Die in dem einen Byte jeweils ein 
0-Bit sitzt, wo im anderen ein Nutzbit sitzt (und vice versa), kommt es 
da auch bei den Bits zu keinen INteraktionen. Sie werden sauber durch 
das Oder zusammengeführt zu einem Byte

           abcDEFGH

bei dem die obersten 3 Bits unverändert aus dem alten ADMUX stammen und 
die unteren 5 Bits aus der channel-Angabe.

Et voila: ADMUX kann damit neu gesetzt werden. An der Einstellung der 
Modus Bits (die 3 oberen) hat sich nichts geändert. Aber die 5 unteren 
Bits für die Kanaleinstellung sind die 5 unteren Bits aus channel.

von Steffen H. (stef_fen)


Lesenswert?

Ich habe noch mal eine Frage zum differential ADC. Wenn ich aus der 
ersten while-Schleife rausspringe habe ich eine kleines Testprogramm 
welches mir signalisiert ob der ADC-Wert über oder unter 250 liegt. 
Benutze ich aber den differential Modus in meinem Hauptprogramm (Pfeil) 
ist er immer so bei 1018 - 1019. Wie kann das sein? Normal hat der 
differtial Modus doch nur eine Auflösung bis 512. Am ADC0 liegt über ein 
Poti maximal 2,56 V an und ADC1 liegt auf GND.

Vielen Dank. Gruß Steffen

1
void ADC_Init(void) {
2
  uint16_t result;
3
  ADMUX = (1<<REFS1) | (1<<REFS0);    //interne Referenzspannung (2,56V)
4
  ADCSRA = (1<<ADPS2) | (1<<ADPS1);     //Frequenzvorteiler, Dummyreadout
5
  ADCSRA |= (1<<ADEN);                  //ADC aktivieren
6
  ADCSRA |= (1<<ADSC);                  //eine ADC-Wandlung 
7
  while (ADCSRA & (1<<ADSC)) {      //auf Abschluss der Konvertierung warten
8
  }
9
  result = ADCW;
10
}
11
 
12
uint16_t ADC_Read(uint8_t channel) {
13
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F); //Kanal waehlen, ohne andere Bits zu beeinflußen
14
  ADCSRA |= (1<<ADSC);              //eine Wandlung "single conversion"
15
  while (ADCSRA & (1<<ADSC) ) {          //auf Abschluss der Konvertierung warten
16
  }
17
  return ADCW;
18
}
19
 
20
int main (void) {
21
  sei();          //erlaubt globale Interrupts
22
  i2c_init();
23
  i2c_start_wait(0x40 | 0);
24
  lcd_init();
25
  lcd_print("Reglersoft");
26
  PWM_Empfaenger();
27
  PWM_Mosfet();
28
  Strommessung();
29
  InputOutput();
30
  StickWay();
31
  //OptionalBrake();
32
  EepromRead();
33
  WaitForUserReady();
34
  ADC_Init();
35
  
36
  while(1) {  
37
    if(currentOverload == 1) {
38
      OCR3A = 0;
39
      MosfetDriver2_PORT &= ~(1 << MosfetDriver2_DB);
40
      lcd_clear();
41
      lcd_print("Überstrom");
42
      WaitForUserReady();
43
    }
44
    overTemperature = ADC_Read(2);
45
    if (overTemperature >= 250) {
46
      OCR3A = 0;
47
      MosfetDriver2_PORT &= ~(1 << MosfetDriver2_DB);
48
      lcd_clear();
49
      lcd_print("Übertemperatur");
50
      WaitForUserReady();  
51
    }
52
    else {
53
      voltage = ADC_Read(16);
54
      lcd_clear();
55
      lcd_print_integer(voltage); <---------
56
      _delay_ms(500);
57
      if (voltage <= 125)
58
      {
59
        break;
60
      }
61
      rechenwert = stick - unten;
62
      rechenwert = rechenwert * deltaStick;
63
      sollwert = rechenwert;
64
      if(stick >= oben) {
65
        sollwert = 2400;  
66
      }
67
      if(stick <= unten) {
68
        sollwert = 0;
69
      }
70
      if (voltage <= 250) {
71
        if (sollwert >= 1200) {
72
          sollwert = 1199;
73
        }
74
      }
75
      OCR3A = sollwert;
76
      //break;
77
    }
78
  }
79
  while(1) {
80
    OCR3A = 0;
81
    MosfetDriver2_PORT &= ~(1 << MosfetDriver2_DB);
82
    //LEDGruen_PORT &= ~(1 << LEDGruen_DB);
83
    //LEDRot_PORT |= (1 << LEDRot_DB);
84
    voltage = ADC_Read(16);
85
    if (voltage >= 250)
86
    {
87
      LEDGruen_PORT &= ~(1 << LEDGruen_DB);
88
      LEDRot_PORT |= (1 << LEDRot_DB);
89
    }
90
    if (voltage <= 249)
91
    {
92
      LEDGruen_PORT |= (1 << LEDGruen_DB);
93
      LEDRot_PORT &= ~(1 << LEDRot_DB);
94
    }
95
    lcd_clear();
96
    lcd_print_integer(voltage);
97
    _delay_ms(1000);
98
    
99
  }
100
  return 0;
101
}

von Cyblord -. (cyblord)


Lesenswert?

Dein Problem liegt halt einfach in keinster Weise beim ADC oder der 
differentiellen Betriebsart desselben. Sondern dir fehlen ganz gehörig 
die Grundlagen.

Beim diff. ADC geht auch von 0 bis 1024, logisch. Aber 512 ist dann halt 
0. Aus einem 16 Bit Register wird halt nicht einfach mal so ne negative 
Zahl. Da musst du schon was für tun.

gruß cyblord

von Steffen H. (stef_fen)


Lesenswert?

Wenn ich aber am Poti drehe passiert gar nichts was aber bei der 
Testschaltung mit den LEDs funktioniert. Außerdem brauch ich keine 
negative Spannung. Wie soll ein Akku leerer als leer werden.

von Cyblord -. (cyblord)


Lesenswert?

Steffen Ha schrieb:
> Wenn ich aber am Poti drehe passiert gar nichts was aber bei der
> Testschaltung mit den LEDs funktioniert. Außerdem brauch ich keine
> negative Spannung. Wie soll ein Akku leerer als leer werden.

Negative Spannungen gehen auch nicht (bezogen auf GND). Trotz diff. ADC 
darf an keinem Pin deines Controllers eine Spannung kleiner GND oder 
größer VCC+0,5 Volt anliegen. Die differentielle Komponenten bezieht 
sich auf VREF bzw. auf die beiden ADC-Eingänge die gewählt sind.

gruß cyblord

von Steffen H. (stef_fen)


Lesenswert?

Hallo Zusammen,

ich habe immer noch das Problem mit der differentiellen Messung. Wenn 
ich wie unten im Programm (siehe Pfeile an markierter Stelle) den ADC2 
normal messe und dann den ADCO 0 und den ADC1 differentiell wird mir auf 
dem Display für test1 168 und für test2 1019 angezeigt egal was ich 
mache. Wenn ich dann aber test1 auskommentiere funktioniert die 
differentielle Messung. Zwischen ADC0 und ADC1 hängen drei Widerstände 
in Reihe, der mittlere Widerstand von den dreien ist ein Poti. Woran 
kann das liegen?

Vielen Dank. Gruß Steffen

1
void ADC_Init(void) {
2
  uint16_t result;
3
  ADMUX = (1<<REFS1) | (1<<REFS0);    //interne Referenzspannung (2,56V)
4
  ADCSRA = (1<<ADPS2) | (1<<ADPS1);     //Frequenzvorteiler, Dummyreadout
5
  ADCSRA |= (1<<ADEN);                  //ADC aktivieren
6
  ADCSRA |= (1<<ADSC);                  //eine ADC-Wandlung 
7
  while (ADCSRA & (1<<ADSC)) {      //auf Abschluss der Konvertierung warten
8
  }
9
  result = ADCW;
10
}
11
12
uint16_t ADC_Read(uint8_t channel) {
13
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F); //Kanal waehlen, ohne andere Bits zu beeinflußen
14
  ADCSRA |= (1<<ADSC);              //eine Wandlung "single conversion"
15
  while (ADCSRA & (1<<ADSC) ) {          //auf Abschluss der Konvertierung warten
16
  }
17
  return ADCW;
18
}
19
20
int main (void) {
21
  ADC_Init();
22
23
  while(1) {
24
    test1 = ADC_Read(2); <----------------
25
    test2 = ADC_Read(16); <---------------
26
    if (test2 >= 250)
27
    {
28
      LEDGruen_PORT &= ~(1 << LEDGruen_DB);
29
      LEDRot_PORT |= (1 << LEDRot_DB);
30
    }
31
    if (test2 <= 249)
32
    {
33
      LEDGruen_PORT |= (1 << LEDGruen_DB);
34
      LEDRot_PORT &= ~(1 << LEDRot_DB);
35
    }
36
    lcd_clear();
37
    lcd_print_integer(test1);
38
    _delay_ms(1000);
39
    lcd_clear();
40
    lcd_print_integer(test2);
41
    _delay_ms(1000);
42
  }
43
  return 0;
44
}

von Steffen H. (stef_fen)


Lesenswert?

Hat keiner eine Idee?

Schönen Abend. Gruß Steffen

von Maddin (Gast)


Lesenswert?

Steffen Ha schrieb:
> Wie soll ein Akku leerer als leer werden.

Einen Akku kannst du doch einfach gegen Ground messen. Wo ist denn das 
Problem?

von Steffen H. (stef_fen)


Lesenswert?

Ich möchte gerne die Spannung über einen Isolationsverstärker HCPL-7840 
messen und der hat einen differentiellen Ausgang. Deswegen habe ich mir 
den Testaufbau mit den drei Widerständen gemacht. Mir kommt es so vor 
als könnte der Atmega zwischen der normalen Messung und der 
differentiellen Messung nicht umschalten. Aber warum?

Gruß Steffen

von Magnus M. (magnetus) Benutzerseite


Lesenswert?

Auf Seite 250 des Datenblattes steht etwas, das dich vielleicht weiter 
bringen könnte:

"Special care should be taken when changing differential channels.
Once a differential channel has been selected, the gain stage may
take as much as 125μs to stabilize to the new value. Thus conversions
should not be started within the first 125μs after selecting a new
differential channel. Alternatively, conversion results obtained
within this period should be discarded."

von Steffen H. (stef_fen)


Lesenswert?

Vielen Dank für den Hinweis. Das Datenblatt habe ich mir jetzt auch noch 
mal durchgelesen. Im Programm habe ich jetzt mal ein _delay_ms zwischen 
die beiden Messungen geschrieben aber der Fehler bleibt der gleiche. Ich 
verstehe das nicht.

1
while(1) {
2
    test1 = ADC_Read(2);
3
    _delay_ms(500);
4
    test2 = ADC_Read(16);
5
    if (test2 >= 250)
6
    {
7
      LEDGruen_PORT &= ~(1 << LEDGruen_DB);
8
      LEDRot_PORT |= (1 << LEDRot_DB);
9
    }
10
    if (test2 <= 249)
11
    {
12
      LEDGruen_PORT |= (1 << LEDGruen_DB);
13
      LEDRot_PORT &= ~(1 << LEDRot_DB);
14
    }
15
    lcd_clear();
16
    lcd_print_integer(test1);
17
    _delay_ms(1000);
18
    lcd_clear();
19
    lcd_print_integer(test2);
20
    _delay_ms(1000);
21
  }
22
  return 0;
23
}

von Magnus M. (magnetus) Benutzerseite


Lesenswert?

Steffen Ha schrieb:
> Vielen Dank für den Hinweis.

Bitteschön.

> Das Datenblatt habe ich mir jetzt auch noch mal durchgelesen.
> Im Programm habe ich jetzt mal ein _delay_ms zwischen die
> beiden Messungen geschrieben aber der Fehler bleibt der gleiche.
> Ich verstehe das nicht.

Ich verstehe das schon...

>     test1 = ADC_Read(2);
>     _delay_ms(500);
>     test2 = ADC_Read(16);

Erst beim Aufruf von ADC_Read(16) wird ein Quellenwechsel auf 
differentiell vorgenommen. Direkt danach startest du die Wandlung.

Du musst innerhalb von ADC_Read() ein kurzes Delay zwischen dem Switchen 
und dem Starten der Messung einbauen.

So z.B.:
1
uint16_t ADC_Read(uint8_t channel) {
2
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F); //Kanal waehlen, ohne andere Bits zu beeinflußen
3
  _delay_ms(1);
4
  ADCSRA |= (1<<ADSC);              //eine Wandlung "single conversion"
5
  while (ADCSRA & (1<<ADSC) ) {          //auf Abschluss der Konvertierung warten
6
  }
7
  return ADCW;
8
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.