Hallo zusammen,
ich bin gerade auf der Suche nach einer Lösung eines
programmiertechnischen Problems, und würde mich freuen wenn mir jemand
dazu einen hilfreichen Tipp geben könnte.
Ich schreibe zur Zeit an einem Programm, das AB einem Zeitpunkt eine
gewisse Zeit lang einen Strom regelt.
Mikrocontroller ist ein Atmega8, den ich mit AVR Stuio 6.1 in C
programmiere.
Hier kurz die grobe Funktion meines Programmausschnitts:
Zu einem gewissen Zeitpunkt tritt ein Timer1 Compare A Interrupt (ich
nenne es mal ISR1) auf.
In dieser ISR wird wiederum zur Funktion "Stromregeln" gesprungen.
Der Strom soll für eine gewisse Zeit geregelt werden, danach soll wieder
in ISR1 zurückgesprungen werden.
Dazu lade ich in der Funktion Stromregeln einen Wert ins OCR1B Register,
damit will ich nach Ablauf der Stromregelzeit in die ISR für den Timer1
Compare A Interrupt (ich nenne es ISR2) springen und die Stromregelung
somit beenden.
Das Problem: Nach Auftreten von ISR2 und deren Abarbeitung, wird ja
wieder in das Programm Stromregeln gesprungen (wurde ja durch ISR2
unterbrochen).
Ich möchte aber, dass nach Abarbeiten von ISR2 wieder ISR1 zuende
geführt wird.
Ich muss also irgendwie erreichen, dass bei Eintritt ISR2 NICHT die
Rücksprungadresse zum Programm Stromregeln auf dem Stack abgelegt wird.
Dadurch wäre denn noch die Adresse zu ISR1 auf dem Stack, und nach
Abarbeiten von ISR2 würde ISR1 zuende ausgeführt werden.
Ich weiß zwar, dass es nicht gerne gesehen wird, wenn ich nur
Codeschnipsel poste, aber es geht hier wirklich nur um die ISR's:
1
ISR(TIMER1_COMPA_vect){// ISR1
2
3
PORTB|=(1<<PB1);// Strom an
4
TCNT1=0;
5
Stromregeln();
6
sei();
7
}
8
9
10
ISR(TIMER1_COMPB_vect){// ISR2
11
TIMSK&=~(1<<OCIE1B);// Interrupt deaktivieren
12
// == Hier soll wieder zu ISR1 Gesprungen werden ==
13
}
14
15
voidStromregeln(void){
16
ADMUX|=(1<<MUX2);
17
OCR1B=ICR1/128;// ICR:6 teilen
18
GradDauer=OCR1B*21;
19
OCR1B=GradDauer*Ansteuerdauer;// OCR1A beladen
20
TIMSK|=(1<<OCIE1B);
21
22
while(ADC<=PeakStrom){// Warten bis Peakstrom erreicht
23
}
24
if(ADC>=(HoldStrom+Deadband)){// Strom auf Holdwert regeln
25
PORTB&=~(1<<PB1);
26
}
27
elseif(ADC<=(HoldStrom-Deadband)){
28
PORTB|=(1<<PB1);
29
}
30
}
Nochmal zur Übersicht in Kurzform:
ISR1 Interrupt -> Stromregeln() -> ISR2 Interrupt -> Weiter in ISR1
Vielen Dank schonmal für eure Antwort!
Philipp
Philipp schrieb:
Das geht ganz anders: Du musst ein Programmparadigma verwenden, dass in
ISRs nur ein Flag setzt und die eigentliche Funktion in main abhandelt.
Stichworte: Interrupt, State Machine, Flag, volatile
Siehe hierzu auch die Artikel im Forum.
Daumenregeln:
1. Niemals den Programmfluss durch Stackmanipulation beeinflussen.
2. Niemals lange Funktionen im Interrupt.
ISR2 kann nicht ausgeführt werden, solange ISR1 läuft. Erst danach, es
sei denn du rufst manuell sei() auf. Das sei() am Ende von ISR1 ist
unnötig, denn beim Rückkehren geschieht das automatisch.
Aber bist du sicher, dass du Stromregeln einfach irgendwo unterbrechen
und komplett abwürgen möchtest?
zB OCR1B ist ein 16bit-Register, wenn der Zugriff darauf unterbrochen
wird, hast du den Timer komisch konfiguriert. Warum setzt du das
überhaupt 2x hintereinander?
Was möchtest du überhaupt erreichen? Vielleicht, die
"while(ADC<=PeakStrom)"-Schleife per Timer abzubrechen wenn sie zu lange
dauert?
Das würde ich komplett anders machen:
* Starte in der ISR1 den ADC und konfiguriere OC1B
* Überprüfe das "ADC<=PeakStrom" in jedem ADC-Interrupt
* Wenn die Bedingung im ADC-Interrupt zutrifft, führe deine
Port-Zugriffe aus und schalte den OC1B ab
* Wenn der OC1B Interrupt kommt, schalte den ADC und OC1B ab
Dass man ISR's so kurz wie möglich macht, weiß ich auch schon.
Bei meinem Programm kommt es aber sehr auf Geschwindigkeit an, d.h. die
Funktion Stromregeln muss so schnell wie möglich ausgeführt werden,
daher die geplanten Aktionen direkt im Interrupt.
Würde ich im Hauptprogramm bloß zyklisch in der ISR gesetzen Flags
abfragen, würde das ersten viel zu lange dauern, zweitens könnte ich
denn die Verzögerung bis zum Ausführen der Funktion Stromregeln
nichtmehr vorhersagen.
Im Programm ist dementsprechend sichergestellt, dass in der Zeit, in der
die ISR abgearbeitet wird, kein anderes Interrupt auftreten kann.
@Dr Sommer:
Du hast natürlich Recht, das sei() muss natürlich direkt in der Funktion
Stromregeln() nach dem Beladen des OCR Registers stehen, erst dann kann
ISR2 kommen. Damit ist dann auch sichergestellt, dass das Beladen des
16Bit Registers nicht durch ein anderes Interrupt gestört wird.
Erreichen möchte ich, dass ein Magnetventil mit einem bestimmten
Stromprofil für eine bestimmte Zeit angesteuert wird.
Dabei soll erst so lange Spannung draufgegeben werden, bis der Peakstrom
erreicht wird. Für die restliche Öffnungszeit muss es nur noch mit einem
niedrigeren Hold-Strom gesteuert werden.
Die Gesamtansteuerdauer gebe ich mit OC1B vor.
Korrigiert müsste das Programm also so aussehen:
1
ISR(TIMER1_COMPA_vect){// ISR1
2
3
PORTB|=(1<<PB1);// Strom an
4
TCNT1=0;
5
Stromregeln();
6
7
}
8
9
10
ISR(TIMER1_COMPB_vect){// ISR2
11
PORTB&=~(1<<PB1);// Strom aus
12
TIMSK&=~(1<<OCIE1B);// Interrupt deaktivieren
13
// == Hier soll wieder zu ISR1 Gesprungen werden ==
14
}
15
16
voidStromregeln(void){
17
ADMUX|=(1<<MUX2);
18
OCR1B=ICR1/128;// ICR:6 teilen
19
GradDauer=OCR1B*21;
20
OCR1B=GradDauer*Ansteuerdauer;// OCR1A beladen
21
TIMSK|=(1<<OCIE1B);
22
sei();
23
24
while(ADC<=PeakStrom){// Warten bis Peakstrom erreicht
25
}
26
if(ADC>=(HoldStrom+Deadband)){// Strom auf Holdwert regeln
Wie wär's wenn du in der ISR1 den ADC startest und das ADC<=PeakStrom
dann in der ADC-ISR machst? In der ISR2 setzt du dann ein Flag
stop_stromregelung, das du in der ADC-ISR abfragst.
Danke für deine Antwort Matthias ,
das würde funktionieren wenn ich die Stromregelung abbrechen wollte,
nachdem ein gewisser Strom erreicht ist.
Ich möchte aber -wie in meinem Eingangsthread erwähnt- die Stromregelung
nach einer vorgegebenen ZEIT abbrechen. Dabei spielt es keine Rolle,
welchen Wert der Strom zu dieser Zeit hat.
Philipp schrieb:> Würde ich im Hauptprogramm bloß zyklisch in der ISR gesetzen Flags> abfragen, würde das ersten viel zu lange dauern, zweitens könnte ich> denn die Verzögerung bis zum Ausführen der Funktion Stromregeln> nichtmehr vorhersagen.
Ach ja? Wie kommst Du auf dieses Brett? Wenn
Deine Loop in der Main sehr kurz ist, dann dauert das ziemlich genau so
lange wie wenn Du das in der ISR aufrufst.
Was verstehst Du unter schnell?
Das was Du bisher programmiert hast ist, mit Verlaub, Mist.
Philipp schrieb:> Ich möchte aber -wie in meinem Eingangsthread erwähnt- die Stromregelung> nach einer vorgegebenen ZEIT abbrechen.
Dafür das Flag stop_stromregelung, dass du in der ISR2 setzt und dann in
der ADC-ISR abfragst, oder du deaktivierst einfach den ADC wenn die
Stromregelung stoppen soll, dann kann er auch kein Interrupt mehr
erzeugen.
Wenn die Loop in der Main sehr kurz ist, dann wird natürlich auch sehr
oft das Flag aus der ISR sehr häufig abgefragt. Bei mir ist die Main
aber ziemlich lange, dort werden z.B. noch Benutzereingaben abgefragt,
ein UART gelesen und ein Display bedient.
Da kann man sich denken, wie lange es dauern kann, bis das Programm zur
Abfrage von Flags kommt.
Ich strebe ein Beginn der Ansteuerung von nicht mehr als 20-30
Taktzyklen nach Auslösen des ersten Interrupts an.
@Matthias:
Ok, jetzt verstehe ich was du meinst. Ich kann das Flag ja auch einfach
in der Stromregeln() Funktion abfragen, und diese beenden, wenn das Flag
gesetzt ist. Ist zwar wieder etwas mehr Ballast, aber so werde ich es
machen wenn keiner eine bessere Lösung hat.
Philipp schrieb:> Ich kann das Flag ja auch einfach> in der Stromregeln() Funktion abfragen
Dann bist du aber die ganze Zeit in der ISR1 und verschwendest
Rechenzeit währen der ADC wandelt.
заббэртроль schrieb:> Ist leider Quatsch. Eine ISR muss !! kurz sein.>> Mach eher :>> isr () { flag = 1; } }>> main () {> ..> loop> if flag==1 {> stromregel();> flag=1;> }
Das ist aber ebenso Quatsch ;-)
Wenn man Ereignisse pollen möchte (was durchaus sinnvoll sein kann),
braucht man i.Allg. keine Interrupts. Interrupts sind ja gerade dafür
erfunden worden, die Pollerei zu vermeiden.
Die Abfrage im Hauptprogramm ohne die Verwendung von Interrupts sieht so
aus:
1
if(TIFR&1<<OCF1A){// Timer-Flag gesetzt?
2
stromregel();
3
TIFR|=1<<OCF1A;// Timer-Flag löschen
4
}
Philipp schrieb:> while(ADC<=PeakStrom){ // Warten bis Peakstrom erreicht> }> if(ADC>=(HoldStrom+Deadband)){ // Strom auf Holdwert regeln> PORTB &= ~(1<<PB1);> }> else if (ADC<=(HoldStrom-Deadband)){> PORTB |= (1<<PB1);> }
Müsste die if-else-if-Anweisung nicht auch in einer Schleife stehen?
In dieser Schleife könntest du das Timer-Flag abfragen und, falls es
gesetzt ist, die Schleife und damit ISR(TIMER1_COMPB_vect) verlassen. Da
der Inhalt dieser Schleife sehr kurz ist, ist die Reaktionszeit mit dem
Polling vermutlich nicht länger als der Aufruf einer separaten ISR.
Falls doch, kannst du in die Schleife auch zwei Abfragen einbauen (eine
an den Anfang und eine in die Mitte).
Klaus schrieb:> Daumenregeln:> 1. Niemals den Programmfluss durch Stackmanipulation beeinflussen.> 2. Niemals lange Funktionen im Interrupt.
Bei Regel 1 stimme ich dir zu. Solche Tricks haben nur in der
Programmierung von Betriebssystemkernen etwas zu suchen. Was man in
manchen Fällen machen kann, ist ein Longjump, um über mehrere
Unterprogrammebenen irgendwowin zurück zu springen. Damit macht man die
Stackmanipulation auf standardisierte Weise. Aber auch das würde ich im
vorliegenden Fall nicht empfehlen.
Regel 2 gilt vor allem für Anwendersoftware, die auf Multitasking-
Plattformen läuft. Auf einfachen Mikrocontrollern, wo i.Allg. nur eine
einzelne Anwendung läuft, können lange ISR durchaus sinnvoll sein, es
muss nur das Gesamtkonzept atimmen.
Vollkommend richtig! Auch die IF - ELSE Anweisung müsste in einer
Schleife stehen, sonst würde sie ja nur einmal augeführt! Sorry, hatte
den Programmteil noch nicht simulieren können, eben wegen meinem
"Sprungproblem".
Das Abfragen des Timer Flags ist eine Super Idee, die ADC Konvertierung
bringt ja auch eine gewisse Verzögerung, da macht es dann nichts mehr
aus, noch eine weitere Abfrage zu haben.
So werde ich es dann wohl machen. Tatsächlich ist also die Stack
Manipulation garnicht notwendig gewesen.
Vielen Dank an Alle!
Falls mir noch jemand erklären könnte, wie ich auf geringe
Verzögerungszeiten komme, ohne den kompletten Ansteuerteil in der ISR
durchzuführen, gerne her damit.
In meinem Programm steuere ich auch noch andere Kanäle, diese aber ohne
Stromregelung, dabei erreiche ich durch geschicke Struktur in den ISR's
Verzögerungszeiten um die 20 - 30 Takte. Hatte bisher noch keine Idee,
wie ich das anders schaffen soll, also wenn das quatsch ist, nehme ich
auch gerne andere Vorschläge an.
hmmm, also ich hatte mal ein ähnliches Problem und hatte damals einfach
in der ISR die Sprungadresse des abzuarbeitenden Programmes auf den
Stack gelegt, wodurch diese dann nach Beendigung der ISR angesprungen
wurde,statt z.B. die Main.
Am Ende des abzuarbeitenden Programmes ist man dann mit einem normalen
Return in das ursprünglich unterprochene Programm (z.B. Main)
zurückgekehrt.
Man hat quasi seine ISR ausgelagert und dazwischen geschoben.
Man muss in der ISR nur einen Mechanismus einbauen der sicher stellt das
diese Stackmanipulation nur stattfindet wenn die "ausgelagerte ISR"
verlassen wurde, sonst gibts böse Überraschungen :)
Auf diese Weise hatte ich mir eine automatische Priorisierung und eine
Art Programmswitch mit einer Switchtabelle geschaffen.....war sehr
lustig, hatte schon einen minimalen Hauch eines Multitasking :D
Auf den Stack kommt aber doch schon bei Einsprung in die ISR die
Rücksprungadresse zu dem unterbrochenen Programm, musst du diese nicht
auch löschen? Sonst würdest du je bei jedem ISR Aufruf 2 Adressen auf
den Stack legen, aber nur eine für den Rücksprung verwenden, und
irgendwann läuft dein Stack über?
Philipp schrieb:> Auf den Stack kommt aber doch schon bei Einsprung in die ISR die> Rücksprungadresse zu dem unterbrochenen Programm.....
...das stimmt,diese wird ja dann auch von dem "dazwischen geschobenen"
Programm für den Rücksprung in das unterbrochene Programm genutzt.
Philipp schrieb:> irgendwann läuft dein Stack über?
...nicht wirklich,die ISR springt zu Deinem "Zwischenprogramm" und
dieses dann zum unterbrochenen Programm....macht zwei Rücksprünge :)
Man muss eben nur verhindern,das die ISR erneut den Stack manipuliert
während das Zwischenprogramm noch nicht beendet ist....
Wer mit dem Stack spielt, muss halt aufpassen!
Yalu X. schrieb:> заббэртроль schrieb:>> Ist leider Quatsch. Eine ISR muss !! kurz sein.>>>> Mach eher :>>>> isr () { flag = 1; } }>>>> main () {>> ..>> loop>> if flag==1 {>> stromregel();>> flag=1;>> }>> Das ist aber ebenso Quatsch ;-)>> Wenn man Ereignisse pollen möchte (was durchaus sinnvoll sein kann),> braucht man i.Allg. keine Interrupts. Interrupts sind ja gerade dafür> erfunden worden, die Pollerei zu vermeiden.
Das ist haarscharf an der der eigentlichen Problematik vorbei gedacht,
wenn Du mich fragst. Wenn es nur darum ginge, bräuchte man niemals
Interrupts. Die sind aus meiner Sicht nicht da um komfortables pollen zu
ermöglichen sondern um asynchrone Ereignisse zu erfassen und das
nötigenfalls zeitnah.
> Klaus schrieb:>> Daumenregeln:>> 1. Niemals den Programmfluss durch Stackmanipulation beeinflussen.>> 2. Niemals lange Funktionen im Interrupt.>> Regel 2 gilt vor allem für Anwendersoftware, die auf Multitasking-> Plattformen läuft. Auf einfachen Mikrocontrollern, wo i.Allg. nur eine> einzelne Anwendung läuft, können lange ISR durchaus sinnvoll sein, es> muss nur das Gesamtkonzept atimmen.
Das Gesamtkonzept kennen wir nicht. Wir wissen auch nicht wie schnell
sich der Strom durch Last verändert. Wir wissen nicht warum der TO
dieses Konzept gewählt hat. Klar sind diese Daumenregeln alle relativ
(deswegen heissen sie ja "Daumenregeln") aber wer so anfängt, sollte sie
entweder kennen und klar erklären können, warum er davon abweicht oder
sie lernen und erstmal damit arbeiten.
Nach allem was ich gesehen habe, wäre das beste aus meiner Sicht ohnehin
das Ganze im ADC-Interrupt abzuhandeln. Die Zeit sperrt dann den
ADC-Interrupt bzw. setzt den Zustand auf "Idle" oder sowas.
Das wäre kurz und direkt - im wesentlichen ein Switch-Case-Konstrukt für
eine kleine State-Machine mit einigen if-Anweisungen für die
Schwellwerte. Eigentlich ein Zwei-Punkt-Regler. Und vom Konzept her auch
nicht das was ich ursprünglich riet. So strikt sehe ich das also
durchaus auch nicht.
Klaus schrieb:> Yalu X. schrieb:>> ...>> Wenn man Ereignisse pollen möchte (was durchaus sinnvoll sein kann),>> braucht man i.Allg. keine Interrupts. Interrupts sind ja gerade dafür>> erfunden worden, die Pollerei zu vermeiden.>> Das ist haarscharf an der der eigentlichen Problematik vorbei gedacht,> wenn Du mich fragst. Wenn es nur darum ginge, bräuchte man niemals> Interrupts. Die sind aus meiner Sicht nicht da um komfortables pollen zu> ermöglichen sondern um asynchrone Ereignisse zu erfassen und das> nötigenfalls zeitnah.
Damit sind wir doch exakt der gleichen Meinung, oder nicht?
Es gibt Anwendungsfälle für Interrupts, aber auch welche für Polling,
das steht außer Frage.
Ich habe mich oben nur dagegen ausgesprochen, das Polling mit Hilfe von
Interrupts zu realisieren, nämlich in der Form, dass die ISR nichts
weiter tut, als eine globale Variable von 0 auf 1 zu setzen, und diese
dann im Hauptprogramm zyklisch abgefragt wird. Denn das bringt gegenüber
der direkten Abfrage des entsprechenden Interruptflags nur Nachteile.
Ich habe bewusst nur diesen Ausschnitt aus dem Code gezeigt, weil es das
war, wo ich nichtmehr weiter wusste.
Ob jetzt eine lange ISR gut oder schlecht ist, oder wie gut die
Stromregelung im Detail funktioniert, darum ging es ja hier nicht.
Ich hätte natürlich auch das ganze Programm posten können, aber kann mir
kaum Vorstellen, dass jeder hier Lust hat, sich in 300 Zeilen Code
hereinzudenken. Deswegen hab ich wie gesagt nur den interessierenden
Ausschnitt hier reingestellt.
Den ADC Interrupt zu benutzen hatte ich mir übrigens auch schon
überlegt. Macht aber keinen Unterschied, ich habe in der
Stromregeln-Phase sowieso keine weiteren Aufgaben zu erledigen, daher
macht es auch nichts wenn die meiste Zeit der gleiche ADC Wert gelesen
wird, weil dieser sich ja wegen der Wandlungszeit aus CPU-Sicht selten
ändert.
Wen die Begründung für meine langen ISR interessiert:
Das Programm soll ein 60+2 Geberrad (58 Zähne, 2 Fehlende Zähne)
auswerten, und 2 Magnetventile AB einem Winkel BIS ZU einem Winkel
ansteuern. Diese Winkel sollen vom Benutzer einstellbar sein.
Zudem soll der Winkel auf ein Grad genau wählbar sein, d.h. das
Interpolieren (Zahnabstand ist 6 Grad) braucht auch nochmal einige
Takte.
Die Ventile müssen also so schnell wie möglich nach Auftreten des
Interrupts angesteuer werden, je mehr Takte bis dahin vergehen, desto
größer wird nämlich -gerade bei hohen Drehzahlen- auch der
Winkelfehler.
Ich kann deswegen nicht erst warten, bis in der Main mal nach dem UART
auslesen und verarbeiten, sagen wir mal 100 Takte nach dem Interrupt,
das Flag, das in der ISR gesetzt wurde, erkannt wurde. Das könnte dann
schon einen Fehler von einem ganzen Grad ausmachen.