Forum: Mikrocontroller und Digitale Elektronik ADC und Interrupt


von Jan Heidtmann (Gast)


Lesenswert?

Hi Leute,

Ich verwende die ADC-Routine aus dem AVR-GCC Tutorial, welche sehr gut 
bei mir funktioniert.

Nun möchte ich nach jeder abgeschlossenen AD-Wandlung einen Interrupt 
auslösen, in der ich eine Regelung durchführen möchte.

Zur Interrupt-Freigabe habe ich in der ADC-Initialisierung das ADIE Bit 
gesetzt,
1
 
2
// AD-Wandler initialisieren
3
void ADC_Init(void) {
4
 
5
  uint16_t result;
6
 
7
  ADMUX = (0<<REFS1) | (1<<REFS0);      // AVcc als Referenz benutzen
8
  ADCSRA = (1<<ADPS2) | (1<<ADPS1);     // Frequenzvorteiler
9
  ADCSRA |= (1<<ADEN);                  // ADC aktivieren
10
  ADCSRA |= (1<<ADSC);                  // eine ADC-Wandlung
11
  ADCSRA |= (1<<ADIE);      // Interrupt-Auslösung 
12
  while (ADCSRA & (1<<ADSC) ) {}        // auf Abschluss der Konvertierung warten
13
  result = ADCW;
14
}

sodass ich mit
1
// Interrupt-Routine für abgeschlossene AD-Wandlung
2
ISR(ADC_vect)
3
{
4
}

die Regelung durchführen kann.

Ist das zunächst so i.O.?

Was genau muss mit in die Interrupt-Routine rein?

von Dennis (Gast)


Lesenswert?

Jan Heidtmann schrieb:
> Was genau muss mit in die Interrupt-Routine rein?

Am besten nur das Setzen eines Flags, welches in der Hauptroutine 
abgearbeitet wird. Berechnungen usw. sollten da nach Möglichkeit nicht 
rein.

von Jan Heidtmann (Gast)


Lesenswert?

Danke für die Antwort.

Dennis schrieb:
> Am besten nur das Setzen eines Flags

Meinst du, zum Beispiel durch Verändern einer Variable?

In der Endlosschleife soll die Wandlung, bzw. die Regelung durch das 
Drücken eines Tasters gestartet werden.
1
 uint16_t strom = ADC_Read(4);
2
3
if( bounce( PINC, PC3 ) )
4
{
5
// Regelung + Berechnung  
6
}

Kann man in der if-Abfrage eine Variable umschreiben, die dann im 
Interrupt wieder aufgegriffen wird?

z.B:
1
if( bounce( PINC, PC3 ) )
2
{
3
A=1;
4
}
5
6
// ...
7
8
ISR(ADC_vect)
9
{
10
if(A == 1)
11
{//Regelung+Berechnung}
12
}

Ist das so möglich oder gibt es "schlauere" Alternativen?

von Krapao (Gast)


Lesenswert?

Du brauchst noch ein sei(); im Programm damit Interrupts überhaupt 
kommen können.

Das ADC Verfahren hat mehrere Elemente: Die einmalige Initialisierung im 
Userprogramm ggf. öfteres Umschalten der Kanäle/Referenzen etc. im 
Anwendungsprogramm, periodisches Auftreten des ADCs (entweder per 
Interrupt im ISR-Teil oder per Polling in der Endlosschleife des 
Anwendungsprogramms) und das Verwerten des ADC Ergebnisses

Das ADC Ergebnis liest man optimal dann aus, wenn es vorhanden ist. Also 
in der ISR, wenn man mit Interrupts arbeitet. Man kann das Ergebnis dann 
auch dort weiter verarbeiten.

Allerdings wenn die Weiterverarbeitung länger dauert, gehen das/die 
nächsten Ergebnisse verloren. Auch - wichtiger - sind andere Interrupts 
gesperrt, wenn sich das Programm in der ISR befindet.

I.d.R. hält man die ISR kurz und knackig: ADC Ergebnis lesen und in 
einer globalen volatile speichern und ein globales volatile Flag 
setzen, dass ein Ergebnis vorhanden ist.

Im Userprogramm schaut man regelmäßig nach, ob ein ADC Ergebnis 
vorhanden ist, d.h. ob das Flag gesetzt ist. Dann hat man im Prinzip 
Zeit weitere Berechnungen durchzuführen.

von Krapao (Gast)


Lesenswert?

Das Vorgehen mit IRQ und Flag ist im Artikel Interrupt beschrieben. 
Dort sind auch Erklärungen für atomare Datenzugriffe.

von Dietrich L. (dietrichl)


Lesenswert?

Eine "richtige" Regelung mit einem µC macht man üblicherweise 
zeitdiskret, siehe:
http://de.wikipedia.org/wiki/Regelungstechnik#Zeitdiskrete_Regelung

Dafür braucht man einen festen Zeittakt, mit dem geregelt wird. Dazu 
eignet sich besonders ein Timer-Interrupt. Ob der ADC-Interrupt dazu 
geeignet ist, hängt davon ab,
- ob der Interrupt in konstantem Zeitabstand kommt
- ob diese Zeit ausreicht, die Rechnungen für die Regelung 
durchzuführen.

Man kann zeitunkritische Hilfsrechnungen in das Hauptprogramm auslagern, 
die eigentliche Regelung aber nicht.

Gruß Dietrich

von xXx (Gast)


Lesenswert?

Dennis schrieb:

> Am besten nur das Setzen eines Flags, welches in der Hauptroutine
> abgearbeitet wird. Berechnungen usw. sollten da nach Möglichkeit nicht
> rein.

Spult doch nicht immer eure Standardantworten runter, deren Sinn ihr 
selbst nicht verstanden habt.

von Jan Heidtmann (Gast)


Lesenswert?

Krapao schrieb:
> Du brauchst noch ein sei(); im Programm damit Interrupts überhaupt
> kommen können.

Das sei(); hatte ich immer im main-file.

Ist es auch möglich, es in der Endlosschleife zu setzen?

von Jan Heidtmann (Gast)


Angehängte Dateien:

Lesenswert?

Also, eigentlich will ich nur:

1. Einen Kanal in der Endlosschleife auslesen.
>>> Klappt sehr gut

2. Einen anderen Kanal in einem Interrupt auslesen und diesen Wert 
verwerten.
>>> Lese ich diesen Kanal ebenfalls in der Endlosschleife aus, klappt das auch 
sehr gut.
Ich benötige aber einen Interrupt, da die Regelung in der Endlosschleife 
zu langsam ist.

Im Anhang ist das Konzept zu finden, nicht der wirkliche Code.

von Krapao (Gast)


Lesenswert?

Sorry :-) Das ist kein Konzept. Das ist eine Codewüste mit viel 
Standardkram, von dem man nicht auf den ersten Blick sieht, ob du ihn 
richtig verwendest. Und es sind viele ... an Stellen, an denen man 
erwartet, dass dort beschrieben ist, was du machen willst.

Soweit ich verstanden habe, hast du ein endlos laufendes System, bei dem
du in einem

P=0 Zustand ADC Mittelwerte auf Kanal 0 ermittelst und auf LCD aus gibst

oder in einem

P=1 Zustand ADC Einzelwerte auf Kanal 4 ermittelst und auf eine 
möglicherweise prellende Eingabe reagierst. Bei anliegender Eingabe soll 
irgendwas mit Regelung gemacht werden.

Wie zwischen P=0 und P=1 Zustand gewechselt wird ist nicht beschrieben. 
Wie komplex die Regelung ist ist auch nicht beschrieben.

Angenommen die Regelung ist wenig komplex (Beispiel Ventil an/aus, wenn 
ADC Wert kleiner/größer Schwellwert mit Hysterese), dann kann das 
problemlos in der ISR gemacht werden.

Angenommen du hast eine Regelung, die vorausgegangene ADC Werte und 
deren Änderungen in letzter Zeit in die Regelparameter einbezieht, dann 
ist das vielleicht schon zu komplex um das in der ISR abzuarbeiten.

Derzeit machst du die ADC Abfrage per Polling, d.h. Aufruf der 
entsprechenden Funktionen. Drumherung ist nicht viel al Code gezeigt.

Wenn du den ADC Wert im Interrupt bekommst, benutzt du natürlich die 
pollenden derzeitigen Funktionen nicht mehr. Der komplette Start- und 
Warteteil dort kann ja entfallen, weil du beim Aufruf weisst, dass dir 
die ISR ein Ergebnis geliefert hat.

Der Rest des gezeigten Codes ist zudem ziemlich spärlich, Der macht nix 
großartiges. Ich erwarte nicht, dass du beim Wechsel von Pollen auf 
Interrupt sehr große Geschwindigkeitsboosts bekommst.

Wo ich angreifen würde: an der Entprellung der Eingabe bei dem 
bounce. Wenn das mit einfacher Warteschleife programmiert ist, werden 
dort u.U. wertvolle ms verbraten. Ich schätze das ist deine Bremse.

von Michael Lambertz (Gast)


Lesenswert?

Danke schonmal für deine Mühe!

Du hast den Code eigentlich komplett richtig verstanden.

Wenn ich was weggelassen habe, dann deshalb, weil ich weiß, dass diese 
Dinge gut funktionieren. (Wie zum Beispiel Entprellung, Regelung im 
Interrupt, Timereinstellungen, usw.)

Ich krieg den Wert aus
1
 volatile uint16_t adcresult = ADC_Read(4);
einfach nicht in einen Interrupt.
Irgendwie kann er den nur messen, wenn der Befehl in der Endlosschleife 
ausgeführt wird.

von Jan Heidtmann (Gast)


Lesenswert?

Der obige Beitrag ist von mir.
Hab ihn nur von einem anderen Rechner geschrieben :)

von Krapao (Gast)


Lesenswert?

Tritt dein Interrupt nicht auf oder was? Normal bekommst du den ADC Wert 
in der ISR, wenn du das entsprechende Register auslesen tust
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/atomic.h>
4
#include lcd...
5
6
volatile uint16_t adcwert;
7
volatile uint8_t adcflag; 
8
9
ISR(ADC_vect)
10
{
11
  adcwert = ADCW;
12
  adcflag = 1;
13
}
14
15
void ADC_Init_IRQ(void) 
16
{
17
  ADMUX = (0<<REFS1) | (1<<REFS0);      // AVcc als Referenz benutzen
18
  ADCSRA = (1<<ADPS2) | (1<<ADPS1);     // Frequenzvorteiler
19
  ADCSRA |= (1<<ADFR);                  // Set ADC to Free-Running Mode
20
  ADCSRA |= (1<<ADEN);                  // ADC aktivieren
21
  ADCSRA |= (1<<ADIE);                  // Interrupt-Auslösung 
22
  ADCSRA |= (1<<ADSC);                  // eine ADC-Wandlung starten
23
  // Init startet mit Kanal #0
24
}
25
26
void ADC_Set_Channel( uint8_t channel )
27
{
28
  // Kanal waehlen, ohne andere Bits zu beeinflußen
29
  ADMUX = (ADMUX & ~(0x1F)) | (channel & 0x1F);
30
31
  // Wechsel wird erst aktiv, wenn laufende AD-Wandlung fertig ist!
32
  // daher eine Wandlung verwerfen
33
  adcflag = 0; 
34
  while( !adcflag ) {}
35
  adcflag = 0;
36
}
37
38
int main(void)
39
{
40
  ADC_Init_IRQ();
41
  sei();
42
43
  while(1)
44
  {
45
    if ( adcflag )
46
    {
47
      static uint8_t channel = 0;
48
      uint16_t temp_adcwert;
49
50
      ATOMIC_BLOCK(ATOMIC_FORCEON)
51
      {
52
        temp_adcwert = adcwert;
53
        adcflag = 0;
54
      }
55
56
      // ausgabe channel und temp_adcwert...
57
58
      // Ein übers andere Mal den Kanal wechseln
59
      if ( channel == 0 )
60
        channel = 4;
61
      else
62
        channel = 0;
63
      ADC_Set_Channel( channel );
64
    }
65
  }
66
}

von Krapao (Gast)


Lesenswert?

Achte auf die Zeile
>  ADCSRA |= (1<<ADFR);                  // Set ADC to Free-Running Mode

von Krapao (Gast)


Lesenswert?

Nachtrag #1

Beim Simulieren (AVR STudio 4) ist mir aufgefallen, dass bei dem Code 
oben nur der erste IRQ in der ISR landet.

Eigentlich sollte das Bit ADSC bei gesetztem Bit ADFR immer auf 1 
bleiben, so dass kontinuierlich IRQs erezugt werden und nur die erste 
Messung von Hand angestossen werden muss.

In meinem Simulator ist Bit ADSC aber gelöscht, wenn die ISR betreten 
wird. Man kann in der ISR das Bit ADSC wieder setzen und dann kommen die 
IRQs auch brav nacheinander.

Nachtrag #2

Das Wechseln des Kanals in meinem Code oben war von mir nicht sauber 
durchdacht.

Es gibt dafür einen Abschnitt im Datenblatt. Dort ist genau beschrieben, 
wann und wie der Kanalwechsel erfolgen sollte, um stets gültige 
Messungen zu haben.

Ebenso ist zu beachten, dass die erste ADC Messung nach dem Einschalten 
des ADC sich anders verhält als die folgenden Messungen.

von Hannes L. (hannes)


Lesenswert?

Nur ein paar Gedanken zum Konzept:

Den ADC aller benötigten Kanäle würde ich im Interrupt auslesen. Damit 
dies sehr wenig Rechenzeit benötigt, würde ich:
- ein Array für die Ergebnisse anlegen
- ein Array für die ADMUX-Werte der verschiedenen Quellen anlegen
- einen Statuszähler (als Index auf die Quelle) anlegen
- ein Flag (Boolean) einrichten, das den neuen Messzyklus meldet
- den ADC im Single-Mode mit Interrupt betreiben.

In der ADC-ISR würde ich Folgendes tun:
- ADC-Wert auslesen und über Index ins Array legen
- Index erhöhen und bei Überlauf zurücksetzen, bei Rücksetzen
  Flag setzen, das der Main "die neue Runde" meldet
- ADMUX-Wert (zu neuem Index) aus Array holen und in ADMUX schreiben
- ADSC setzen, um neue Messung zu starten
- fertig...

Die Mainloop kann nun im Takt des gesetzten Flags:
- Das Flag löschen (Job wird ja ausgeführt)
- auf alle ADC-Werte (lesend) zugreifen
- in aller Ruhe die Regelung berechnen
- die Ergebnisse an das Stellglied ausgeben

Dabei gibt es in der Mainloop kein ADC-Busywait, der Mainloop steht also 
die komplette verbliebene Rechenzeit zur Verfügung.

Ein C-Beispiel kann ich nicht geben, ich werkele in Assembler.

...

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.