Guten Morgen,
ich nutze einen Atmega2560 in einem Modellbauauto mit 16 MHz.
Dieser uC steuert mein Lenkservo mit einer PWM, meinen Motor mit einer
PWM und nimmt über SPI mit einem Funkmodul Befehle von meiner
Fernsteuerung entgegen um mein Auto fahren zu lassen.
Diese zeitkritischen Funktionen sind komplett in Interrupts realisiert,
weil ich die while(1)-Schleife zum Steuern diverser LEDs mit Delays
brauche.
Leider ist hierdurch die ISR, die die Daten aus dem Fifo meines
Funkmoduls liest und diese in eine PWM-Breite für Servo und Motor
umrechnet, etwas länglich. Die ISR dauert auf 16 MHz irgendwas im
kleinen, zweistelligen us Bereich.
Dies führt dazu, dass die Timer ISRs manchmal auflaufen und meine Servo
PWM leicht verreißen. Das Servo zuckt dann kurz.
Mit diesem Bastelprojekt komme ich an die Grenzen eines Atmega2560 auf
16 MHz. Mein Problem ließe sich lösen, wenn die längliche ISR (und nur
diese!) durch nested Interrupts weitere ISRs zulässt. Meine übrigen ISRs
sind alle kurz.
Ich habe den Parameter ISR_NOBLOCK übergeben was dazu führt, dass mein
uC die while(1)-Schleife nicht mehr anläuft.
Kann das überhaupt so funktionieren, dass eine ISR weitere ISRs zulässt?
Habe ich wenn die while(1)-Schleife nicht mehr angelaufen wird sofort
einen Stack-Overflow?
Herzlichen Dank!
Die typischen Dateninterrupts (UART, SPI, I2C) dürfen nicht Interrupts
enablen, da sie ihr Flag nicht beim Eintritt automatisch löschen, d.h.
sie unterbrechen sich sofort selber, bis der Stack überläuft. Der SRAM
wird quasi durch den Wolf gedreht.
Du mußt also erst den betreffenden Dateninterrupt disablen und dann erst
die Interrupts wieder global enablen. Und am Ende dann umgekehrt.
Clever wäre von den AVR-Entwicklern gewesen, daß jeder Interrupt, der
sein Pending-Bit nicht beim Eintritt löscht, dann dafür einfach sein
Enable-Bit löscht. Aber soweit haben sie leider nicht gedacht.
So etwas geht, dann solltest Du aber wissen, was Du tust. Ich habe es in
einem Projekt so gelöst, daß die erlaubten Interrupts das
Status-Register nicht beeinflussen können.
Das sieht dann in etwa so aus:
1
ISR(TIMER1_COMPA_vect){
2
TIMSK&=~(1<<OCIE1A);
3
if(UCSRB&(1<<UDRIE)){
4
UCSRB&=~(1<<UDRIE);
5
statusbyte.udr_ie=1;
6
}
7
sei();
8
9
...viiielCode
10
11
cli();
12
TIMSK|=(1<<OCIE1A);// Enable compare match interrupt.
13
if(statusbyte.udr_ie){
14
UCSRB|=(1<<UDRIE);
15
}
16
17
statusbyte.udr_ie=0;
18
}
19
20
ISR(TIMER0_COMPA_vect,ISR_NAKED){
21
USICR|=(1<<USITC);// Toggle clock output pin for USI
22
asmvolatile("reti");
23
}
24
25
ISR(USI_OVERFLOW_vect,ISR_NAKED){
26
asmvolatile(// disable Timer0 OCR0 Interrupt without affecting the SREG register
27
"push r16""\n\t"// store the auxiliary register in the stack memory
28
"ldi r16, %0""\n\t"// load new value for TIMSK to auxiliary register...
29
"out %1, r16""\n\t"// and put to TIMSK
30
"pop r16""\n\t"// restore the auxiliary register
31
:
32
:"M"((0<<OCIE0A)| \
33
(1<<OCIE1A)),
34
"M"(_SFR_IO_ADDR(TIMSK))
35
);
36
USISR|=(1<<USIOIF);// clear USI counter overflow interrupt flag
37
statusbyte.usi_spi_tx=IDLE;
38
asmvolatile("reti");
39
}
40
41
ISR(INT0_vect,ISR_NAKED){
42
statusbyte.zerocross=true;
43
asmvolatile("reti");
44
}
Wie man sehen kann, wird im Timer1-Interrupt alles außer dem
UART-Interrupt zugelassen. Die erlaubten Interrupts sind dann aber schön
knackig kurz.
Es schwirrt hier auf dieser Seite auch irgend ein Tutorial rum, mußt Du
mal danach suchen, ich bin gerade zu faul dazu. ;-)
Sefco schrieb:> Mein Problem ließe sich lösen, wenn die längliche ISR (und nur> diese!) durch nested Interrupts weitere ISRs zulässt. Meine übrigen ISRs> sind alle kurz.
Die deutlich bessere Lösung wäre, das Konzept etwas zu ändern.
Sefco schrieb:> weil ich die while(1)-Schleife zum Steuern diverser LEDs mit Delays> brauche.
Nimm die Delays da raus. Man kann z.B. auch einen Hardware-Zähler
auslesen, und damit entscheiden, ob eine LED nun lange genug an oder aus
war.
Sefco schrieb:> Leider ist hierdurch die ISR, die die Daten aus dem Fifo meines> Funkmoduls liest und diese in eine PWM-Breite für Servo und Motor> umrechnet, etwas länglich.
Verschiebe das in die Hauptschleife. Setze in der ISR nur ein
Beim-Funkmodul-stehen-Daten-zur-Verfügung-Flag.
> Du mußt also erst den betreffenden Dateninterrupt disablen und dann erst> die Interrupts wieder global enablen. Und am Ende dann umgekehrt.
Genau daran habe ich auch gedacht. Müsste das Ganze dann so gehen?
1
ISR(INT7_vect)
2
{
3
// Disable external interrupt
4
EIMSK&=~(1<<INT7);
5
6
sei();
7
8
// My Code
9
}
> Die deutlich bessere Lösung wäre, das Konzept etwas zu ändern.
Es geht hier konkret um ein selbstgebautes Modell einer amerikanischen
Fire Engine. Ich habe an dem Teil über 50 LEDs, die alle
unterschiedliche Blitzen sollen mir Doppel- und Dreifachblitzen etc. So
wie in der Realität. Sowas bekommt man mit Timern kaum hin.
Trumpeltier schrieb:> Die erlaubten Interrupts sind dann aber schön> knackig kurz.
Und sie sind fehlerhaft, weil sie den Inhalt von Registern verändern
ohne sie zu sichern und wiederherzustellen.
Sefco schrieb:> Wieso noch ein cli() am Ende?
Weil ein Interrupt hinter der letzten sichtbaren Zeile nicht zuende ist
sondern erst noch seinen Epilog (nen Haufen POPs) ausführen muß und sich
dabei wieder selber unterbrechen könnte.
Alternativ habe ich noch überlegt, den 16 MHz Quarz mal ganz unauffällig
durch 20 MHz zu ersetzen. Laut Google haben das schon einige Leute
gemacht und es sollte wunderbar klappen. Hat da jemand Erfahrungen
gemacht?
Stefan E. schrieb:> Nimm die Delays da raus. Man kann z.B. auch einen Hardware-Zähler> auslesen, und damit entscheiden, ob eine LED nun lange genug an oder aus> war.
Delays in der Hauptschleife sind unkritisch - sie sind jederzeit
unterbrechbar und kein Hindernis für die Ausführung von Interrupts.
Sefco schrieb:> Dies führt dazu, dass die Timer ISRs manchmal auflaufen und meine Servo> PWM leicht verreißen. Das Servo zuckt dann kurz.
Da liegt m.E. das Problem. Die PWM Erzeugung sollte besser komplett in
Hardware laufen, so das gar keine ISR benötigt wird. Mit 16-Bit Timern
sollte das in genügender Auflösung zu lösen sein, ohne eine ISR in
Anspruch zu nehmen.
Weiterhin ist es natürlich nicht dumm, die Sache mit dem
'SPI-Umrechnen-auf-PWM' nochmal zu optimieren.
Sefco schrieb:> Hat da jemand Erfahrungen> gemacht?
Das geht auf jeden Fall, solange du den MC mit ausreichender Spannung
versorgst. Du verletzt bei 5V ja keine Specs oder so. Mega328 mit
19,6MHz läuft jedenfalls wie Schmidts Katze.
Sefco schrieb:> Ich habe an dem Teil über 50 LEDs, die alle> unterschiedliche Blitzen sollen mir Doppel- und Dreifachblitzen etc. So> wie in der Realität. Sowas bekommt man mit Timern kaum hin.
Kein wirkliches Problem. Ein Timer, der den Basistakt vorgibt, dann für
jede LED zwei Zähler und ein Array mit dem An-Aus-Muster.
Das mit Delays zu machen ist eine Frickellösung. Und eine Frickellösung
zieht dann die nächste (nested Interrupts) nach sich. Und wenn du dann
noch irgendeine Funktionalität integrieren willst, endet das Ganze
früher oder später im Chaos.
Sefco schrieb:> Es geht hier konkret um ein selbstgebautes Modell einer amerikanischen> Fire Engine. Ich habe an dem Teil über 50 LEDs, die alle> unterschiedliche Blitzen sollen mir Doppel- und Dreifachblitzen etc. So> wie in der Realität. Sowas bekommt man mit Timern kaum hin.
Wenn es mit while(1) und Delays funktioniert, dann funktioniert es schon
3x besser wenn du lediglich ein besseres Systemdesign verwendest. Nutzt
du dann noch die entsprechende HW (Timer, Interrupt etc), funktioniert
es min. 10x besser ;)
Sefco schrieb:> Alternativ habe ich noch überlegt, den 16 MHz Quarz mal ganz unauffällig> durch 20 MHz zu ersetzen. Laut Google haben das schon einige Leute> gemacht und es sollte wunderbar klappen. Hat da jemand Erfahrungen> gemacht?
Ja, funktioniert. Dein Controller könnte aber vorzeitig ableben.
mfg
Zum ATmega2560 kann ich nichts sagen, aber ein ATmega8515 läuft hier
seit Jahren mit 22 MHz (+38 %) und ein ATmega1284P seit einiger Zeit mit
25 MHz (+25 %), beide bei 5.3 V.
Matthias S. schrieb:> Stefan E. schrieb:>> Nimm die Delays da raus. Man kann z.B. auch einen Hardware-Zähler>> auslesen, und damit entscheiden, ob eine LED nun lange genug an oder aus>> war.>> Delays in der Hauptschleife sind unkritisch - sie sind jederzeit> unterbrechbar und kein Hindernis für die Ausführung von Interrupts.
Ohne die Delays hat er aber einen deutlich schnelleren Durchlauf der
Hauptschleife und kann problemlos weitere Sachen dort mit aufnehmen, wie
eben z.B. die Bearbeitung der empfangenen Funknachrichten.
>Da liegt m.E. das Problem. Die PWM Erzeugung sollte besser komplett in>Hardware laufen, so das gar keine ISR benötigt wird. Mit 16-Bit Timern>sollte das in genügender Auflösung zu lösen sein, ohne eine ISR in>Anspruch zu nehmen.
Ich brauche nicht einfach eine PWM, ich brauche eine PWM ihren
Duty-Cycle abhängig von Lenkhebel ändert und eine Periode von 20 ms hat.
Ist habe das schon soweit wie möglich in HW ausgelagert, aber an
irgendeiner Stelle muss ich zumindest mal den Timer zurücksetzen oder
den Wert für den Compare-Match ändern.
>Weiterhin ist es natürlich nicht dumm, die Sache mit dem>'SPI-Umrechnen-auf-PWM' nochmal zu optimieren.
Das sollte ich tatsächlich nochmal versuchen. Eventuell berechne ich das
auf dem Atmega2560 meiner Funke. Der hat nämlich langeweile.
>Kein wirkliches Problem. Ein Timer, der den Basistakt vorgibt, dann für>jede LED zwei Zähler und ein Array mit dem An-Aus-Muster.
Puh...ich bin da ziemlich perfektionistisch und verändere meine Delays
teilweise um 10 ms bei einzelnen LEDs. Das wird ja ein schönes gecode.
Ich glaube da ändere ich lieber den Parameter in _delay_ms(xx) in der
main. Ehrlich gesagt verstehe ich auch nicht so ganz wie du das meinst.
Da muss ich ja zumindest im Timer eine Varibale/den Zählerstand
vergleichen und dann ca. 50 LEDs abhängig davon schalten.
>Ja, funktioniert. Dein Controller könnte aber vorzeitig ableben.
Wieso? Hitze? Kann mir kaum vorstellen das der bei 4 MHz mehr so heiß
wird. Außerdem kann man da noch einen Kühlkörper draufsetzen.
Sefco schrieb:> Puh...ich bin da ziemlich perfektionistisch und verändere meine Delays> teilweise um 10 ms bei einzelnen LEDs.
Und? Dann lässt du halt deinen Basistakt 10ms lang sein.
Sefco schrieb:> Das wird ja ein schönes gecode.
Wieso? Um eine Zeit um 10ms zu ändern, musst du nur einen Eintrag in
einem Array verändern. Und du änderst damit dann auch wirklich nur das
Verhalten dieser einen LED, denn ...
Sefco schrieb:> Ich glaube da ändere ich lieber den Parameter in _delay_ms(xx) in der> main.
Und änderst damit gleich das Verhalten aller LEDs. Denn alle LEDs, die
dann gerade an sind, sind 10ms länger an, und alle die dann gerade aus
sind, sind 10ms länger aus.
Sefco schrieb:> Da muss ich ja zumindest im Timer eine Varibale/den Zählerstand> vergleichen und dann ca. 50 LEDs abhängig davon schalten.
Das machst du dann in der Hauptschleife. Der Timer setzt nur ein Flag.
Sefco schrieb:> Es geht hier konkret um ein selbstgebautes Modell einer amerikanischen> Fire Engine. Ich habe an dem Teil über 50 LEDs, die alle> unterschiedliche Blitzen sollen mir Doppel- und Dreifachblitzen etc. So> wie in der Realität. Sowas bekommt man mit Timern kaum hin.
Gerade dafür eignen sich Timer überaus herausragend. Lass den Timer
laufen, gebe deinen 50+ LEDs jeweils einen eigenen "Bereich" im Timer.
Kostet dann kaum Rechenleistung, Platz, und die 50+ LEDs sind völlig
unabhängig voneinander (nicht wie mit delays in der Main, wo eine
Änderung gleich alle betrifft).
Sefco schrieb:> aber an> irgendeiner Stelle muss ich zumindest mal den Timer zurücksetzen
Öhh, Timer zurücksetzen? Hast du dir evtl. mal den Fast PWM mit TOP in
ICRn Modus angeschaut?
Gehen wir mal von 20 MHz Takt aus. Du setzt Timer 1,3,4 oder 5 (oder
alle) auf Fast PWM Mode 14, setzt den Prescaler auf 8 und schreibst in
ICRn eine 39999. (20Mhz/8/40000 = 50Hz = 20ms).
OCRA, OCRB und OCRC liefern dann Servopulse, wenn du die COM Bits
richtig setzt. Ein Wert von 2000 in OCRn gibt einen 1ms Puls und ein
Wert von 4000 einen 2ms Puls.
Da der Mega2560 4 solche Timer hat, kannst du also theoretisch 12 Servos
rein in Hardware treiben. Du musst lediglich einen neuen OCR Wert
reinschreiben, wenn du Zeit dafür hast und ein Servo eine neue Stellung
einnehmen soll.
Sefco schrieb:>> Jetzt habe ich doch mal für Dich gesucht.>> https://www.mikrocontroller.net/articles/AVR-GCC-T...>> Vielen Dank! Das habe ich selber nicht gefunden, weil ich nach "nested"> gesucht habe :P
ISR_NOBLOCK ist hier aber nur dann geeignet, wenn 100% sicher ist, dass
die ISR sich nicht selbt unterbrechen kann.
Das obige Beispiel mit sei + cli und Löschen + Setzen des entsprechenden
IRQ-Flags würde ich den Vorzug geben.
Am Ende der ISR kann auch getestet werden, ob ihr IRQ-Flag schon wieder
ansteht. Falls das der Fall ist, würde ein ISR-Epilog und dann direkt
wieder ein ISR-Prolog folgen, was ja nach Komplexität der ISR viel Zeit
verschwendet. Stattdessen kann man in dem Falle dann auch einfach zum
Anfang der ISR springen, was einen kompletten ISR-Frame (Prolog +
Epilog) and Zeit einspart.
> Um eine Zeit um 10ms zu ändern, musst du nur einen Eintrag in> einem Array verändern. Und du änderst damit dann auch wirklich nur das> Verhalten dieser einen LED, denn ...> Gerade dafür eignen sich Timer überaus herausragend. Lass den Timer> laufen, gebe deinen 50+ LEDs jeweils einen eigenen "Bereich" im Timer.> Kostet dann kaum Rechenleistung, Platz, und die 50+ LEDs sind völlig> unabhängig voneinander (nicht wie mit delays in der Main, wo eine> Änderung gleich alle betrifft).
Könnt ihr mir bitte nochmal erklären wie das gemeint ist?
Wenn ich die Delays umgehen will, nutze ich einen Timer. Aber statt dem
Delay habe ich dann in der while(1) jede Menge if-Abfragen die abhängig
vom Timer Wert oder der Anzahl der Überläufe (die ich mitzähle) dann
LEDs an und aus machen. Meint ihr das so?
Und was ist mit "Array" gemeint? Wie soll ich denn PORTB |= (1<<7) mit
einem Array machen?
Schreib doch mal ganz genau wie die LEDs geschaltet werden sollen und in
welchen Zeitabständen etc. oder poste den Code. Dann kann man auch eine
vernünftige Antwort geben. Nicht jeder hier besitzt eine Glaskugel...
mfg
Danke dir Felix! Diese Lösung gefällt mir und wird gleich direkt
getestet. Trotzdem würde mich mal interessieren was die Kollegen da mit
einem Array reißen wollten...
Kann man mit der Switch-Case Lösung auch eine PWM realisieren? Ich
brauche nämlich bei manchen LEDs noch eine Software PWM.
Sefco schrieb:> Diese Lösung gefällt mir
...vertrödelt aber wieder Zeit (+ Flash) in der ISR!
'Task_LED()' gehört in die 'main()'. In der ISR wird nur ein Bit in
einer globalen Variable ('StatusISR' oder so ähnlich, jede ISR bekommt
da ein...zwei Bits zugewiesen) gesetzt, welches in der 'main()' in eine
temporäre Variable kopiert wird. Mit Hilfe dieser 'copyStatusISR'
(wieder: oder so ähnlich) werden dann die diversen Routinen aufgerufen.
So, zum Beispiel, auch 'Task_LED()'.
Sichtbarkeit von Daten:
'count' gehört 'static' in die ISR. 'led_delay' würde ich auch so in die
ISR integrieren bzw. nur als Makro definieren. 'state' entsprechend nach
'Task_LED()'.
Ralf G. schrieb:> Sefco schrieb:>> Diese Lösung gefällt mir>> ...vertrödelt aber wieder Zeit (+ Flash) in der ISR!>> 'Task_LED()' gehört in die 'main()'. In der ISR wird nur ein Bit in> einer globalen Variable ('StatusISR' oder so ähnlich, jede ISR bekommt> da ein...zwei Bits zugewiesen) gesetzt, welches in der 'main()' in eine> temporäre Variable kopiert wird. Mit Hilfe dieser 'copyStatusISR'> (wieder: oder so ähnlich) werden dann die diversen Routinen aufgerufen.> So, zum Beispiel, auch 'Task_LED()'.>> Sichtbarkeit von Daten:> 'count' gehört 'static' in die ISR. 'led_delay' würde ich auch so in die> ISR integrieren bzw. nur als Makro definieren. 'state' entsprechend nach> 'Task_LED()'.
Leute wie du gefallen mir....
Nichts Beigetragen aber sofort kritisieren, dass Pseudocode nicht
perfekt ist...
Und wer vertrödelt hier Zeit und Flash?
Du bist doch derjenige, der temporäre Variablen anlegen möchte und alles
dauern in der Main pollen will. Bei meiner Lösung ist die Main aktuell
komplett frei und es muss nichts gepollt werden. Die Task_LED wird nur
aufgerufen, wenn sie auch benötigt wird.
Außerdem habe ich geschrieben, dass es besseres, schöneres gibt. Man
könnte den Timer auch auf 10ms setzen, wenn die HW es mitmacht, dann ist
noch weniger Last vorhanden. Für seinen Zweck ist das aber völlig
ausreichend.
mfg
Felix F. schrieb:> Nichts Beigetragen aber sofort kritisieren
Bis zum 03.03. 16:15 war alles soweit okay, das muss ich doch nicht
wiederholen.
Sefco schrieb:> Dies führt dazu, dass die Timer ISRs manchmal auflaufen
^^^ ^
@Felix F.
Hast du mal nachgerechnet, was der Funktionsaufruf in der ISR kostet?
Ralf G. schrieb:> @Felix F.> Hast du mal nachgerechnet, was der Funktionsaufruf in der ISR kostet?
Leider nicht, im aktuellen Atmelstudio wird die Funktion nämlich
geinlined. Dementsprechend interessiere ich mich nicht dafür.
Aber du hast es doch sicher nachgerecht? Wie viele Befehle extra sind es
denn? Vlt erweist sich das bei einem zukünftigen Projekt mal als
nützlich :)
mfg
Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.
Wichtige Regeln - erst lesen, dann posten!
Groß- und Kleinschreibung verwenden
Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang