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
voidADC_Init(void){
4
5
uint16_tresult;
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?
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.
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_tstrom=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?
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.
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
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.
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?
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.
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.
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
volatileuint16_tadcresult=ADC_Read(4);
einfach nicht in einen Interrupt.
Irgendwie kann er den nur messen, wenn der Befehl in der Endlosschleife
ausgeführt wird.
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.
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.
...