Forum: Mikrocontroller und Digitale Elektronik ATMega8 ADC Interrupt - lesen mehrerer Eingänge liefert Schrott


von Karsten W. (lsmod)


Angehängte Dateien:

Lesenswert?

Mit einem ATMega8 soll eine Phasenanschnitts-Steuerung unter Beachtung 
des Ausgangssignals realisiert werden.
Dafür wird aus der Wechselspannung ein Trigger-Signal hergestellt, 
welches hier mit einem Oszilloskop dargestellt ist (Sync).

Darüber hinaus wird dann zusätzlich die Ausgangsspannung und der 
Ausgangsstrom überwacht, wozu 2 weitere AD-Eingänge verwendet werden.
Es werden also nacheinander insgesamt 3 AD-Eingänge abwechselnd gelesen, 
hier wird aber nur das erste Signal als Beispiel dargestellt.

Wenn man das Signal mit einer Single-Shot-Conversion jeweils für die 3 
Eingänge einliest funktioniert das wunderbar (Single-Conversion).

Nun soll dieser Vorgang jedoch über den ADC-Interrupt im Hintergrund 
erfolgen, alleine schon um das lästige Polling zu vermeiden.

Im ersten Versuch wurde der Free-Running-Modus verwendet, beim dem 
jedoch ein Umschalten der Eingänge scheinbar nicht vorgesehen ist.
Egal wie man es macht, es kommt immer kompletter Datenschrott raus 
(Free-Running).

Nun gut - dann also mit Single-Conversion, jedoch führt dies ebenfalls 
nicht zu brauchbaren Ergebnissen sobald man den Interrupt verwendet 
(Interrupt-Single-Shot).

Der Quellcode dazu sieht folgendermaßen aus:

Initialisierung
1
volatile uint16_t Oszi[256];
2
volatile uint8_t  CountOszi;  
3
volatile uint8_t  CurADC;
4
volatile uint16_t ADCWert[3];
5
6
  ADMUX = (1 << REFS0);
7
  ADCSRA = (1 << ADPS2) | (1 << ADPS1);        // Frequenzvorteiler 64
8
  ADCSRA |= (1<<ADEN) | (1 << ADIE);                // ADC mit Interrupt aktivieren (benötigt 25 ADC Takte)

Interrupt
1
ISR(ADC_vect) {
2
  ADCWert[CurADC - 2] = ADCW;                          // ADC auslesen und speichern
3
  CurADC ++;
4
  if (CurADC > 4) CurADC = 2;
5
  
6
  ADMUX = (ADMUX & ~(0x1F)) | (CurADC & 0x1F);        // AD-Eingang wählen
7
  _delay_us(10);                    // Änderung des Eingangs abwarten
8
  ADCSRA |= (1 << ADSC);                     // Eine Wandlung "single conversion"
9
10
if (CurADC == 3) {
11
  Oszi[CountOszi] = ADCWert[0];
12
  CountOszi ++;
13
}
14
15
// Weitere Auswertung
16
}

Das Array Oszi[CountOszi] wird dann später ausgegeben und als Diagramm 
visualisiert wie in den Bildern zu sehen ist.

Hat jemand eine Idee was am Code falsch ist oder woran dies liegen 
könnte?

: Bearbeitet durch User
von Karl M. (Gast)


Lesenswert?

Hallo,
Was soll das Auslesen des ADC Wert im Interrupt Betrieb? Hast du dir mal 
Gedanken gemacht, wie man so einen Eingang umschalten kann?
Steht natürlich im Datenblatt.
Darüber hinaus ist unklar, wie die ADC Eingänge beschaltet sind.
Sind sie nieder ohmig genug?

von Stefan F. (Gast)


Lesenswert?

Spontan fällt mir das auf:
1
volatile uint16_t ADCWert[3];
2
volatile uint8_t  CurADC;
3
4
ISR(ADC_vect) {
5
  ADCWert[CurADC - 2] = ADCW; 
6
  ...
7
}

Daraus ergibt sich, dass CurADC die Werte 2 bis 4 haben muss. Du fängst 
aber mit 0 an, da du die Variable nicht initialisiert hast. Innerhalb 
der ISR wirst du daher irgendwo falsch in RAM schreiben, da kann alles 
Mögliche passierten.

Der delay hat in der ISR nichts verloren.

Bedenke, dass der ADC im freilaufenden Modus bereits mit der nächsten 
Messung beschäftigt ist, während die ISR abgearbeitet wird. Der Kanal, 
den du in der ISR einstellt, wirkt sich daher erst auf die übernächste 
Messung aus.

Ich hatte damit auch Schwierigkeiten und es damals bei meiner ersten 
Anwendung ganz ähnlich wie du umgesetzt. Der folgende Code war für einen 
Atmega16:
1
// Werte vom ADC
2
// Index 0-7 entsprechen Kanal 0-7
3
static volatile uint16_t adc_value[8];
4
5
// Unterbrechung vom ADC
6
ISR(ADC_vect) {
7
8
    // Messergebnis auslesen
9
    uint16_t value = ADC;
10
    uint8_t channel = ADMUX & 7;
11
    adc_value[channel]=value;
12
13
    // Zähle von 0-7
14
    if (++channel > 7) {
15
        channel=0;
16
    }
17
18
    // Schalte zum nächsten Kanal
19
    ADMUX=(ADMUX & ~7) | channel;
20
21
    // Starte nächste Messung
22
    ADCSRA |= (1<<ADSC); 
23
}
24
25
void init() {
26
    // Aktiviere ADC Konvertierung im Einzelschritt Modus 
27
    // mit Taktfrequenz (F_CPU/128)
28
    ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0) | (1<<ADIE) | (1<<ADSC); 
29
    ADMUX = REF_VCC;
30
31
    // Erlaube Unterbrechungen
32
    sei();
33
}

So funktioniert das jedenfalls tadellos, nur eben nicht freilaufend. Ist 
bei deiner Anwendung wichtig, dass die Messintervalle in 100% 
gleichmäßigen Intervallen stattfinden? Wenn ja, dann solltest du den 
freilaufenden Modus benutzen.

von Stefan F. (Gast)


Lesenswert?

Karl M. schrieb:
> Was soll das Auslesen des ADC Wert im Interrupt Betrieb?

Irgendwie muss er ihn ja zu den richtigen Zeitpunkten auslesen. Wie 
hättest du das denn anders gemacht?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Karsten M. schrieb:
> jedoch führt dies ebenfalls nicht zu brauchbaren Ergebnissen sobald man
> den Interrupt verwendet (Interrupt-Single-Shot).
Was sieht man denn überhaupt auf den Bildern? Und was stimmt damit 
nicht?

Karl M. schrieb:
> Darüber hinaus ist unklar, wie die ADC Eingänge beschaltet sind.
> Sind sie nieder ohmig genug?
Wenn nicht, dann verschleppt man sich gern Teile des Messwerts vom zuvor 
gewandelten Kanal auf den eigentlichen Messwert, weil der S&H 
Kondensator nicht komplett umgeladen wird.

von Karsten W. (lsmod)


Lesenswert?

Stefan ⛄ F. schrieb:

> Daraus ergibt sich, dass CurADC die Werte 2 bis 4 haben muss. Du fängst
> aber mit 0 an, da du die Variable nicht initialisiert hast. Innerhalb
> der ISR wirst du daher irgendwo falsch in RAM schreiben, da kann alles
> Mögliche passierten.

Stimmt - aber daran liegt es (leider) nicht.


> Der delay hat in der ISR nichts verloren.

Die wurde später testweise hinzugefügt und ist überflüssig.

> Bedenke, dass der ADC im freilaufenden Modus bereits mit der nächsten
> Messung beschäftigt ist, während die ISR abgearbeitet wird. Der Kanal,
> den du in der ISR einstellt, wirkt sich daher erst auf die übernächste
> Messung aus.

Ja genau - daher ist ein Umschalten der Eingänge nicht vorgesehen.

> Ich hatte damit auch Schwierigkeiten und es damals bei meiner ersten
> Anwendung ganz ähnlich wie du umgesetzt. Der folgende Code war für einen
> Atmega16:
>
>
1
...
2
>
>
> So funktioniert das jedenfalls tadellos, nur eben nicht freilaufend.

Ein prinzipieller Unterschied ist zu dem Code für den ATMega8 nicht zu 
sehen.
Dennoch ist das Ergebnis beim Einlesen in der Interrupt-Routine 
fundamental anders.

> Ist bei deiner Anwendung wichtig, dass die Messintervalle in 100%
> gleichmäßigen Intervallen stattfinden? Wenn ja, dann solltest du den
> freilaufenden Modus benutzen.

Nein - das Messintervall muß ja nur innerhalb 20 ms einigermaßen 
gleichmäßig sein, wobei die Interruptroutine ja sehr gleichmäßig läuft, 
da die Auswertung erst später erfolgt.

von Karsten W. (lsmod)


Lesenswert?

Lothar M. schrieb:
> Karsten M. schrieb:
>> jedoch führt dies ebenfalls nicht zu brauchbaren Ergebnissen sobald man
>> den Interrupt verwendet (Interrupt-Single-Shot).
> Was sieht man denn überhaupt auf den Bildern? Und was stimmt damit
> nicht?

Geben die Bilder Single-Conversion.png und Interrupt-Single-Shot.png für 
Dich den gleichen Signalverlauf wieder?

> Karl M. schrieb:
>> Darüber hinaus ist unklar, wie die ADC Eingänge beschaltet sind.
>> Sind sie nieder ohmig genug?
> Wenn nicht, dann verschleppt man sich gern Teile des Messwerts vom zuvor
> gewandelten Kanal auf den eigentlichen Messwert, weil der S&H
> Kondensator nicht komplett umgeladen wird.

Mag alles sein, aber das erklärt nicht das recht perfekte Ergebnis bei
single-conversion im Vergleich zu dem Ergebnis der gleichen 
Vorgehensweise in einem ADC-Interrupt.

: Bearbeitet durch User
von Karsten W. (lsmod)


Angehängte Dateien:

Lesenswert?

Hier noch ein weiteres aktuelles Ergebnis mit einer korrekten 
Initialisierung
1
volatile uint8_t CurADC = 2;

und ohne die 10µs verschwendete Wartezeit.

Die grüne Linie ist übrigens ein gleitender Mittelwert über 5 Werte.
Das ist hier ebenfalls nicht von Bedeutung.

: Bearbeitet durch User
von Torn (Gast)


Lesenswert?

Karsten M. schrieb:

>> Daraus ergibt sich, dass CurADC die Werte 2 bis 4 haben muss. Du fängst
>> aber mit 0 an, da du die Variable nicht initialisiert hast. Innerhalb
>> der ISR wirst du daher irgendwo falsch in RAM schreiben, da kann alles
>> Mögliche passierten.

> Stimmt - aber daran liegt es (leider) nicht.

Den seine Beiträge musst du irgnorieren.

von Oliver S. (oliverso)


Lesenswert?

Karsten M. schrieb:
> Im ersten Versuch wurde der Free-Running-Modus verwendet, beim dem
> jedoch ein Umschalten der Eingänge scheinbar nicht vorgesehen ist.
> Egal wie man es macht, es kommt immer kompletter Datenschrott raus
> (Free-Running).

Datasheet:
1
If both ADFR and ADEN is written to one, an interrupt event can occur at any time. If the
2
ADMUX Register is changed in this period, the user cannot tell if the next conversion is based
3
on the old or the new settings. ADMUX can be safely updated in the following ways:
4
1. When ADFR or ADEN is cleared
5
2. During conversion, minimum one ADC clock cycle after the trigger event
6
3. After a conversion, before the Interrupt Flag used as trigger source is cleared
7
When updating ADMUX in one of these conditions, the new settings will affect the next ADC
8
conversion.

Oliver

von Stefan F. (Gast)


Lesenswert?

Oliver S. schrieb:
> 2. During conversion, minimum one ADC clock cycle after the trigger
> event

Ich denke, das ist erfüllt, wenn man den Multiplexer innerhalb der ISR 
umschaltet.

von Karsten W. (lsmod)


Lesenswert?

Torn schrieb:
> Den seine Beiträge musst du irgnorieren.

Dennoch wurden konkrete Fehler aufgezeigt.
Das ist näher an der Lösung drann als die anderen Tips.

Die Impedanz am Eingang liegt übrigens unter 47 KOhm.

von Stefan F. (Gast)


Lesenswert?

Karsten M. schrieb:
> Die Impedanz am Eingang liegt übrigens unter 47 KOhm.

Sie soll unter 10kΩ liegen!

von Udo S. (urschmitt)


Lesenswert?

Karsten M. schrieb:
> Hier noch ein weiteres aktuelles Ergebnis mit einer korrekten
> Initialisierung

Wie wäre es wenn du mal anfängst auf die Rückfragen zu antworten:
1. Wie ist die Eingangsbeschaltung
2. Wie sehen die gemessenen Werte aus, und welch Werte erwartest du?
3. Woher weisst du daß deine gemessenen Werte falsch sind, was ist deine 
Referenz?

Wenn die grüne Kurve die "schlechte" sein soll, dann könnte das genau 
der Effekt einer zu hohen (dynamischen) Impedanz der Quelle sein.

von Oliver S. (oliverso)


Lesenswert?

REFS1 REFS0 ADLAR – MUX3 MUX2 MUX1 MUX0 ADMUX

Karsten M. schrieb:
> ADMUX = (ADMUX & ~(0x1F)) | (CurADC & 0x1F);

Hm...

Oliver

von Karsten W. (lsmod)


Angehängte Dateien:

Lesenswert?

Ja - aber es bleibt dabei - das nächste sample & hold ist bereits im 
Gange.
Hier kann man nicht sinnvoll für jede einzelne Messung den Eingang 
umschalten.

Höchstens in mit einem Timer in einem anderen getimten Interrupt und 
selbst dann ist es fragwürdig ob das sauber funktioniert.

von Stefan F. (Gast)


Lesenswert?

Oliver S. schrieb:
> Hm...

Bitte verrate uns dein Geheimnis!

Der Filter 0x1F ist mir auch aufgefallen, aber ich glaube nicht, dass er 
hier ein Problem auslöst.

von Stefan F. (Gast)


Lesenswert?

Karsten M. schrieb:
> Hier kann man nicht sinnvoll für jede einzelne Messung den Eingang
> umschalten.

Doch kann man. Man muss halt nur beachten, dass das Ergebnis erst im 
übernächsten Interrupt eintrifft. Der Multiplexer-Wert muss einfach nur 
um zwei Positionen versetzt rotieren.

von Karsten W. (lsmod)


Lesenswert?

Stefan ⛄ F. schrieb:
> Oliver S. schrieb:
>> Hm...
>
> Bitte verrate uns dein Geheimnis!
>
> Der Filter 0x1F ist mir auch aufgefallen, aber ich glaube nicht, dass er
> hier ein Problem auslöst.

Diese Zeile wurde aus irgendeiner Library einfach übernommen.
Das lässt sich geschickter ausdrücken.

Aber es funktioniert ja wenn man in der Main-Loop damit die Eingänge 
setzt und ausliest wie man oben sehen kann.

von Karsten W. (lsmod)


Lesenswert?

Stefan ⛄ F. schrieb:
> Doch kann man. Man muss halt nur beachten, dass das Ergebnis erst im
> übernächsten Interrupt eintrifft. Der Multiplexer-Wert muss einfach nur
> um zwei Positionen versetzt rotieren.

Könntest Du Deine Rotation bitte in einem Beispiel ausdrücken?

von Stefan F. (Gast)


Lesenswert?

> Könntest Du Deine Rotation bitte in einem Beispiel ausdrücken?

Horizontal läuft die Zeit (=ISR Aufrufe).

falsch:
1
CurADC       2  3  4  2  3  4  2  3  4
2
Array-Index  0  1  2  0  1  2  0  1  2

richtig:
1
CurADC       2  3  4  2  3  4  2  3  4
2
Array-Index  2  0  1  2  0  1  2  0  1

Wobei CurADC hier nicht anzeigt, welchen Channel du gerade gemessen 
hast, sondern welcher Channel in diesem ISR Aufruf in den MUX 
geschrieben wird.

Also erst CurADC inkrementieren, dann ADC auslesen, dann gemäß obigem 
Mapping ins Array schreiben, dann den neuen Wert ins ADMUX Register 
schreiben.

Oder meinetwegen auch: Erst CurADC inkrementieren, den neuen Wert ins 
ADMUX Register schreiben, dann ADC auslesen, dann gemäß obigem Mapping 
ins Array schreiben.

von Karsten W. (lsmod)


Lesenswert?

Verstehe - dies wurde folgendermaßen abgebildet:
1
  /*
2
  * Kanal 2 wird unter ADCWert[0] gespeichert
3
  * CurADC = 3 wird gesetzt
4
  * K   [S]   case    (ADC-Kanal  [im Array]   case)
5
  * 2 -> 0 -> 3       Phase Wechselspannung 20V
6
  * 3 -> 1 -> 4       Strommessung Hochspannung
7
  * 4 -> 2 -> 2       Spannungsmessung Hochspannung
8
  */
9
  switch (CurADC) {
10
    case ...
11
  }

Zugegebenermaßen von der Programmierlogik her nicht sehr elegant. :-)

: Bearbeitet durch User
von Karsten W. (lsmod)


Angehängte Dateien:

Lesenswert?

Stefan ⛄ F. schrieb:
> Karsten M. schrieb:
>> Die Impedanz am Eingang liegt übrigens unter 47 KOhm.
>
> Sie soll unter 10kΩ liegen!

Wo steht das?

Bei 100 MOhm Eingangswiderstand?

Außerdem funktioniert es ja prächtig wenn man single-conversion 
außerhalb des Interrupt durchführt.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Stefan ⛄ F. schrieb:
> Bitte verrate uns dein Geheimnis!
>
> Der Filter 0x1F ist mir auch aufgefallen, aber ich glaube nicht, dass er
> hier ein Problem auslöst.

Ok, ich hab da was verwechselt.

Stefan ⛄ F. schrieb:
> Karsten M. schrieb:
>> Hier kann man nicht sinnvoll für jede einzelne Messung den Eingang
>> umschalten.
>
> Doch kann man. Man muss halt nur beachten, dass das Ergebnis erst im
> übernächsten Interrupt eintrifft. Der Multiplexer-Wert muss einfach nur
> um zwei Positionen versetzt rotieren.

Das führt dann aber nur dazu, daß die Kanäle komplett rotiert sind, 
nicht zur (vermuteten) Versmischung der Daten.

Karsten M. schrieb:
> Die grüne Linie ist übrigens ein gleitender Mittelwert über 5 Werte.
> Das ist hier ebenfalls nicht von Bedeutung.

Da es ja offensichtlich an all dem, was du bisher gezeigt hast, eher 
nicht liegt, wird es Zeit, mal daß ganze Programm zu zeigen.

Oliver

von Stefan F. (Gast)


Lesenswert?

> Zugegebenermaßen von der Programmierlogik her nicht sehr elegant. :-)

Deswegen: Wie gefällt Dir das?:
1
volatile uint8_t  curChannel=7;
2
volatile uint8_t  muxValue=1;
3
volatile uint16_t adcValues[8];
4
5
ISR(ADC_vect) {    
6
    adcValues[curChannel] = ADCW;
7
    ADMUX = (ADMUX & 0xF0) + muxValue;
8
9
    curChannel = (curChannel+1) & 7;
10
    muxValue = (muxValue+1) & 7;  
11
}

Starte den ADC auf Channel 0, dann wird folgendes Passieren:
1
1. IRQ, curChannel=7, muxValue=1, ADC0 -> adcValues[7]   falsch zugeordnet
2
2. IRQ, curChannel=0, muxValue=2, ADC0 -> adcValues[0]   ab hier richtig zugeordnet 
3
3. IRQ, curChannel=1, muxValue=3, ADC1 -> adcValues[1]
4
4. IRQ, curChannel=2, muxValue=4, ADC1 -> adcValues[2]
5
5. IRQ, curChannel=3, muxValue=5, ADC1 -> adcValues[3]
6
6. IRQ, curChannel=4, muxValue=6, ADC1 -> adcValues[4]
7
7. IRQ, curChannel=5, muxValue=7, ADC1 -> adcValues[5]
8
8. IRQ, curChannel=6, muxValue=0, ADC1 -> adcValues[6]
9
9. IRQ, curChannel=7, muxValue=1, ADC1 -> adcValues[7]
10
10.IRQ, curChannel=0, muxValue=2, ADC1 -> adcValues[0]
11
11.IRQ, curChannel=1, muxValue=3, ADC1 -> adcValues[1]

Das erste Messergebnis musst du verwerfen weil es im falschen 
Array-Index gespeichert wird. Alle folgenden sind verwendbar.

Der ADC ist schnell genug, um einfach alle 8 Kanäle Zyklisch zu messen. 
Wenn du willst, kannst du das natürlich auch auf weniger Kanäle 
reduzieren.

von Stefan F. (Gast)


Lesenswert?

Karsten M. schrieb:
>>> Die Impedanz am Eingang liegt übrigens unter 47 KOhm.
>> Sie soll unter 10kΩ liegen!
> Wo steht das?

Weiß ich nicht auswendig. ich glaube in irgendeiner Appnote. Je höher 
die Impedanz der Quelle ist, umso ungenauer die Messergebnisse und umso 
mehr verschleift man Spannungen von einem Eingang zum nächsten, weil der 
Sample&Hold Kondensator im ADC Überreste der vorherigen Messung enthält.

> Außerdem funktioniert es ja prächtig wenn man single-conversion
> außerhalb des Interrupt durchführt.

Im Freilaufenden Modus ist die Zeitspanne zwischen dem Umschalten des 
Multiplexers und der Messung kürzer.

von Stefan F. (Gast)


Lesenswert?

Scheiße, copy-paste Fehler bei den ADCx Nummern. Hier die Korrektur:
1
Starte den ADC auf Channel 0, dann wird folgendes Passieren:
2
3
1. IRQ, curChannel=7, muxValue=1, ADC0 -> adcValues[7]   falsch zugeordnet
4
2. IRQ, curChannel=0, muxValue=2, ADC0 -> adcValues[0]   ab hier richtig zugeordnet 
5
3. IRQ, curChannel=1, muxValue=3, ADC1 -> adcValues[1]
6
4. IRQ, curChannel=2, muxValue=4, ADC2 -> adcValues[2]
7
5. IRQ, curChannel=3, muxValue=5, ADC3 -> adcValues[3]
8
6. IRQ, curChannel=4, muxValue=6, ADC4 -> adcValues[4]
9
7. IRQ, curChannel=5, muxValue=7, ADC5 -> adcValues[5]
10
8. IRQ, curChannel=6, muxValue=0, ADC6 -> adcValues[6]
11
9. IRQ, curChannel=7, muxValue=1, ADC7 -> adcValues[7]
12
10.IRQ, curChannel=0, muxValue=2, ADC0 -> adcValues[0]
13
11.IRQ, curChannel=1, muxValue=3, ADC1 -> adcValues[1]

von Karsten W. (lsmod)


Lesenswert?

Ja - das ist eleganteres C.

Allerdings hat das IC nur 6 AD-Eingänge und es werden nur 3 benutzt.

von Oliver S. (oliverso)


Lesenswert?

Stefan ⛄ F. schrieb:
> Karsten M. schrieb:
>>>> Die Impedanz am Eingang liegt übrigens unter 47 KOhm.
>>> Sie soll unter 10kΩ liegen!
>> Wo steht das?
>
> Weiß ich nicht auswendig. ich glaube in irgendeiner Appnote.

Das steht auch im Datenblatt, wo sonst.

Oliver

von Stefan F. (Gast)


Lesenswert?

Karsten M. schrieb:
> Allerdings hat das IC nur 6 AD-Eingänge und es werden nur 3 benutzt.

Das ist egal, dann nutzt du eben nicht alle Werte aus dem Array. Das 
sind doch nur ein paar Bytes!

Dafür hast du eine schöne 1:1 Zuordnung von ADC Kanal zu Array-Index und 
kannst alle Kanäle Nutzen.

Wenn du das auf die 3 Kanäle reduzierst, wird der Code schwer 
verständlich und vermutlich auch größer.

von S. Landolt (Gast)


Angehängte Dateien:

Lesenswert?

> Das steht auch im Datenblatt, wo sonst.

von Karsten W. (lsmod)


Angehängte Dateien:

Lesenswert?

Zwischendurch kam die Kritik das nicht der vollständige Code 
veröffentlich worden ist.
Da ist grundsätzlich was drann, daher hier eine Minimalversion mit ihrem 
dazugehörigen Ergebnis.

Der Fehler lag einfach daran das bei der Ausgabe der Werte auf der UART 
der Interrupt nicht deaktiviert worden ist!
Dadurch wurde das Array Oszi während der Ausgabe überschrieben.

Hier der Democode (bis auf die UART) der zeigt das es funktioniert.
Daher gilt wie immer: Kaum macht man es richtig und schon 
funktioniert's.
1
// VARIABLEN ================================================
2
3
volatile uint16_t Oszi[256];        // Oszilloskop
4
volatile uint8_t  CountOszi;        // Aktuelles Puffer-Byte
5
volatile uint8_t  CurADC;        // Aktueller AD-Kanal
6
volatile uint16_t ADCWert[3];        // Gemessene ADC-Werte
7
8
static char spuffer[17];          // String 
9
10
11
/******************************************************//**
12
 * ADC initialisieren
13
 **********************************************************/
14
void ADC_Init(void) {
15
  ADMUX = (1 << REFS0);
16
17
  ADCSRA = (1 << ADPS2) | (1 << ADPS1);        // Frequenzvorteiler 64
18
  ADCSRA |= (1<<ADEN) | (1 << ADIE);                // ADC mit Interrupt aktivieren (benötigt 25 ADC Takte)
19
20
  ADCSRA |= (1 << ADSC);              // Eine ADC-Wandlung starten
21
  while (ADCSRA & (1 << ADSC) ) {}          // Auf Abschluss der Konvertierung warten
22
23
  if (ADCW);
24
  
25
  CurADC = 2;
26
}
27
28
29
/******************************************************//**
30
 * MAIN
31
 **********************************************************/
32
int main(void) {
33
34
  DDRB  = 0b00111111;
35
  DDRC  = 0b00000000;
36
  DDRD  = 0b11111010;
37
  
38
  PORTB = 0b00111101;
39
  PORTC = 0b00000000;
40
  PORTD = 0b11110111;
41
42
  uart_init();
43
  ADC_Init();
44
45
  sei();
46
47
  uart_string("Oszi-Debug");
48
  uart_string(CR);
49
50
// HAUPTSCHLEIFE =====================================================
51
  while(1) {
52
    _delay_ms(20000);
53
    cli();
54
    for(uint8_t i = 0; i < 255; i ++) {
55
      utoa(Oszi[i], spuffer, 10);
56
      uart_string(spuffer);
57
      uart_string(CR);
58
    }
59
    uart_string("=== Ende ===");
60
    uart_string(CR);
61
    sei();
62
  }
63
64
}
65
66
67
/******************************************************//**
68
 * ADC Interrupt
69
 **********************************************************/
70
ISR(ADC_vect) {
71
  ADCWert[CurADC - 2] = ADCW;                          // ADC auslesen und speichern
72
  CurADC ++;
73
  if (CurADC > 4) CurADC = 2;
74
  
75
  ADMUX = (ADMUX & ~(0x1F)) | (CurADC & 0x1F);        // AD-Eingang wählen
76
  ADCSRA |= (1 << ADSC);                     // Eine Wandlung "single conversion"
77
  
78
  /*
79
  * Kanal 2 wird unter ADCWert[0] gespeichert
80
  * CurADC = 3 wird gesetzt
81
  * K   [S]   case    (ADC-Kanal  [im Array]   case)
82
  * 2 -> 0 -> 3       Phase Wechselspannung 20V
83
  * 3 -> 1 -> 4       Strommessung Hochspannung
84
  * 4 -> 2 -> 2       Spannungsmessung Hochspannung
85
  */
86
  switch (CurADC) {                  // AD-Werte immer nach Erfassung auswerten
87
    case 3:                    // Detektion der Phase
88
      Oszi[CountOszi] = ADCWert[0];
89
      CountOszi ++;
90
      break;
91
    case 4:                    // Strommessung
92
//       Oszi[CountOszi] = ADCWert[1];
93
//       CountOszi ++;
94
      break;
95
    case 2:                    // Spannungsmessung
96
//       Oszi[CountOszi] = ADCWert[2];
97
//       CountOszi ++;
98
      break;
99
  }
100
}

Danke für die Unterstützung!

: Bearbeitet durch User
von Soul E. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:

> Karsten M. schrieb:
>> Die Impedanz am Eingang liegt übrigens unter 47 KOhm.
>
> Sie soll unter 10kΩ liegen!

Wichtiger ist deren dynamisches Verhalten, und das verbessert man durch 
Stützkondensatoren direkt an den ADC-Pins. Jeweils 4,7 nF nach Masse, 
mit weniger als 5 mm Leitungslänge angebunden.

Sonst gibt es beim Umladen des Sample&Hold-Kondensators einen kurzen 
Spannungseinbruch am Pin (den man teilweise sogar von aussen mit dem 
Oszilloskop sehen kann), und der Messwert hängt immer ein bisschen vom 
vorherigen Kanal ab (Übersprechen).

von Georg G. (df2au)


Lesenswert?

soul e. schrieb:
> Stützkondensatoren direkt an den ADC-Pins. Jeweils 4,7 nF nach Masse,

Ein toller Beitrag. Hast du dir mal die Impedanz von 4,7nF bei nur 10kHz 
ausgerechnet? Nicht jeder User will Gleichstrom messen. Ein Kondensator 
am ADC-Pin ist die schlechteste Variante. Du bekommst bei 
nicht-sinusförmigen Signalen Verzerrungen. Ein vernünftig ausgelegter 
OpAmp ist da angesagt.

von Wolfgang (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Wenn du das auf die 3 Kanäle reduzierst, wird der Code schwer
> verständlich und vermutlich auch größer.

Mit 4 Kanälen  lässt sich der Code genauso kompakt formulieren und er 
wird kein Bisschen größer.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Mit ein paar defines wird das ganze sehr übersichtlich und funktioniert 
gut:
1
// definitions for the ADC
2
3
//! The ADC channel where the small plate control is connected.
4
#define ADC_CHANNEL_1   0
5
//! The ADC channel where the large plate control is connected.
6
#define ADC_CHANNEL_2   1
7
//! The ADC channel where the oven control is connected.
8
#define ADC_CHANNEL_3   2
9
//! The ADC channel where the NTC input is connected.
10
#define ADC_CHANNEL_4   3
11
12
13
//! ADC clock prescaler 2 setting.
14
#define ADC_PRESCALER_2        ((0 << ADPS2) | (0 << ADPS1) | (1 << ADPS0))
15
16
//! ADC clock prescaler 4 setting.
17
#define ADC_PRESCALER_4        ((0 << ADPS2) | (1 << ADPS1) | (0 << ADPS0))
18
19
//! ADC clock prescaler 8 setting.
20
#define ADC_PRESCALER_8        ((0 << ADPS2) | (1 << ADPS1) | (1 << ADPS0))
21
22
//! ADC clock prescaler 16 setting.
23
#define ADC_PRESCALER_16        ((1 << ADPS2) | (0 << ADPS1) | (0 << ADPS0))
24
25
//! ADC clock prescaler 64 setting.
26
#define ADC_PRESCALER_64        ((1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0))
27
28
//! ADC clock prescaler 64 setting.
29
#define ADC_PRESCALER_128        ((1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0))
30
31
//! ADC clock prescaler used in this application.
32
#define ADC_PRESCALER           ADC_PRESCALER_16
33
34
//! ADC internal 1.1 bandgap voltage reference channel value.
35
#define ADC_REFERENCE_VOLTAGE_11INTERNAL    ((1 << REFS1) | (0 << REFS0))
36
//! ADC VCC voltage reference channel value.
37
#define ADC_REFERENCE_VOLTAGE_VCC           ((0 << REFS1) | (0 << REFS0))
38
//! ADC AREF voltage reference channel value.
39
#define ADC_REFERENCE_VOLTAGE_AREF          ((0 << REFS1) | (1 << REFS0))
40
41
/*! ADC voltage reference used in this application.
42
 *
43
 *  \todo Select ADC voltage reference channel.
44
 */
45
#define ADC_REFERENCE_VOLTAGE   ADC_REFERENCE_VOLTAGE_VCC
46
47
//! ADMUX settings for channel 1
48
#define ADMUX_CH1  (ADC_REFERENCE_VOLTAGE | (ADC_CHANNEL_1 << MUX0))
49
50
//! ADMUX settings for channel 2
51
#define ADMUX_CH2  (ADC_REFERENCE_VOLTAGE | (ADC_CHANNEL_2 << MUX0))
52
53
//! ADMUX settings for channel 3
54
#define ADMUX_CH3  (ADC_REFERENCE_VOLTAGE | (ADC_CHANNEL_3 << MUX0))
55
56
//! ADMUX settings for channel 4
57
#define ADMUX_CH4  (ADC_REFERENCE_VOLTAGE | (ADC_CHANNEL_4 << MUX0))
58
59
// variables
60
// ADC results
61
volatile uint8_t channel1, channel2, channel3, channel4;
62
/* ADC complete interrupt 
63
 * this version scans 4 channels (0 to 3) and 
64
 * places the results in globals channel1 to channel4 
65
 * Note that this relies on a left adjusted result and we only use 8 bits
66
 * resolution which is ample for this application */
67
ISR(ADC_vect)
68
{
69
  switch (ADMUX)
70
    {
71
      case ADMUX_CH1:
72
          channel1 = (channel1 + ADCH) >> 1; // do some averaging with the last value obtained
73
          ADMUX = ADMUX_CH2;
74
          break;
75
      case ADMUX_CH2:
76
          channel2 = (channel2 + ADCH) >> 1;
77
          ADMUX = ADMUX_CH3; 
78
          break;        
79
      case ADMUX_CH3:
80
          channel3 = (channel3 + ADCH) >> 1;
81
          ADMUX = ADMUX_CH4; 
82
          break;        
83
      case ADMUX_CH4:
84
          channel4 = (channel4 + ADCH) >> 1;
85
          ADMUX = ADMUX_CH1; 
86
          break;        
87
      default: ADMUX = ADMUX_CH1;
88
        break;
89
   }
90
// restart the ADC
91
   ADCSRA = (1 << ADEN) | (1 << ADSC) | (0 << ADATE) | (1 << ADIF) | (1 << ADIE) | ADC_PRESCALER;
92
}
Die Glättung usw muss man ja nicht nutzen und selbstverständlich klappt 
das auch mit 10 Bit Werten.

: Bearbeitet durch User
von Karsten W. (lsmod)


Lesenswert?

Das ist schön übersichtlich.

von Stefan F. (Gast)


Lesenswert?

Wolfgang schrieb:
> Mit 4 Kanälen  lässt sich der Code genauso kompakt formulieren und er
> wird kein Bisschen größer.

Stimmt, nur ünglücklicherweise nutzt er die Kanäle 2, 3 und 4. Kanal 4 
liegt gerade über der Schwelle.

Die Lösung mit dem Switch-Case finde ich auch Ok. Ist nicht ganz so 
kompakt aber gut lesbar, und das ist am wichtigsten.

von Wolfgang (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Stimmt, nur ünglücklicherweise nutzt er die Kanäle 2, 3 und 4. Kanal 4
> liegt gerade über der Schwelle.

Das kostet dich genau ein "+1" in der Zeile
1
    ADMUX = (ADMUX & 0xF0) + muxValue;

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ist nicht ganz so
> kompakt aber gut lesbar, und das ist am wichtigsten.

Ist so eben auch easy, für verschiedene Kanäle verschiedene Setups zu 
machen, da kann man Referenzspannung oder bei anderen AVR auch auf 
bipolar usw. umschalten, ohne am Programm irgendwas zu ändern. Define 
ändern und fertisch.

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.