(Atmel-) Mikrocontroller: Interrupts und "lange" ISRs

Aug 13, 2019 Last reply: 7 years ago 81 Replies
Jump to replies (81)

Ich bin dabei derzeit eine Atomuhr zu basteln (Physics Package ist noch unterwegs), und bin gerade beim Zählen und auswerten der Pulse angelangt.



Ich werde vermtulich erst mal einen 8-Bit-Atmel verwenden (für den Prototypen einen Arduino Mega 2560, fürs fertige Gerät vielleicht eine Teensy 2.0 mit 32u4). Ja, wie mich jemand zu recht belehrt hat ist das "Geriatronik", es gibt ARM-Controller die das alles mit links erschlagen, aber trotzdem würde mich eine "saubere" Vorgehensweise interessieren. Das ganze mit roher Rechenpower lösen kann ich ja noch immer. Ein Teensy 4.0 mit



600MHz-Arm Cortex hat sicher keinerlei Probleme, wie unelegant man auch immer programmiert.

Die AVR-Controller können einen Zähler an einen externen Pin hängen, mit oder ohne Prescaler und recht schnell zählen, ohne daß die eigentliche CPU belästigt wird. Das hab ich mittlerweile geschafft (nach einigem Datenblatt- Studium).



Meine Idee wäre die Pulse unterhalb der Sekunde so zu zählen (egal ob die vollen 10MHz oder schon was runtergeteiltes, z.B. 1000Hz für eine Millisekundenanzeige), und jede Sekunde, d.h. beim Überlauf des Zählers der Sekundenbruchteile, einen ausgelösten Interrupt mit einer ISR zu behandeln.



Jetzt habe ich folgendes Problem: Das ganze geht "meistens" sehr schnell (Sekunden erhöhen, fertig, Counter läuft alleine auf 0 zurück). Bei vollen Minuten muß ich die Minute erhöhen, das tut auch nicht weh.



Nur zu manchen Zeitpunkten (Jahresanfang Mitternacht GMT, Anfang Sommerzeit, Schaltjahre, Schaltsekunden) muß ich eventuell langwierigere Dinge machen. Gibt es außer "ganz ganz sicher sein, daß die Berechnung keine Sekunde dauert" noch Möglichkeiten wie man sowas abhandelt? Z.B. wie man den zeitkritischen Teil (Sekunden, Minuten) vom weniger zeitkritischen Teil (Stunden, Tage, Wochentag, Monat ...) getrennt abzuarbeiten?



Oder ist die Idee mit dem Interrupt überhaupt schlecht?



/ralph


Du machst den Rest einfach außerhalb des IRQ. Zum Beispiel setzt Du Dir im IRQ eine Variable, die Du von außerhalb abfragst. Und dann kannst Du in Ruhe z.B. das Datum berechnen, während die Sekunden weiter laufen.

Wobei das auch nicht so arg lange dauern sollte.

Einfach nur einen Zähler im IR hochzählen, ggf. per SW verlängern.

Dann irgendwann in der (Display) Routine diesen Zähler abfragen, und zwar mehrfach, bis 2 Abfragen nacheinander den gleichen Wert haben (weg. IR Bearbeitung könnte der sich gerade ändern!) dann vergleichen mit dem letzten Wert aus dem vorherigen Durchgang. Dann alle Berechnungen anstellen samt Ablage des aktuellen Wertes als Altwert und fertig ist die Laube. Das ganze Geraffel sollte nicht länger sein, als der Display Anzeige zyklus. Aber auch dann gibt es noch weitere Tricks wie Zeitscheiben im Foreground Programm.

Falls die CPU auch SW-IR erzeugen kann (z.B. wie der Z80 mit RST0..7), dann ein SW-IR in der Zähler IRQ setzen, falls msec voll. Der erzeugte SW- IR wird wird dann in einer msecIRQ verarztet, der dann ggf. einen 1secIR triggert. die 1seq IR Routine löst dann ggf. einen 1min SW-IR aus... usw.usf.

Das eine IRroutine sauber die IR Anforderung zurücksetzt, ist Ehrensache :) Auch die benutzen REG schön sauber wegschreiben und vorkramen (mit IR- OFF IR-ON. Das übt und ist in Assembler übersichtlich.

So kannste wg. mir auch einen Weiteren Timer aufsetzen, der periodisch ein Display update Ausgabe zusammenbaut. Krumme Frequenz zu 50Hz Netzbrumm ist da angebracht, damit es nicht flackert und schnell genug fürs Auge.

ggf. auch den Display MUXER per periodischem SW-IR erledigen

Dann brauch sich das Foreground Programm nur noch um den Userinput kümmern, die tippen selten so schnell, dass es da überfordert wird.

Mein wildestes Gerät hatte so mal 14 verschiedene IR-Ebenen 64180 (Z80 Abart) um die verschiedenen völlig asynchronen Vorgänge zu verarzten.

Messfrequenz schneller/langsamer als Display update, serielle Schnittstelle, Uhrzeit und Dutzend andere Sachen, alle munter Durcheinander.

Das war mein 1. Gerät mit FORTH. (IR) Routinen in HLL, debuggen, wenn die zu langsam waren, dann eben in CODE (Assembler) umgefrickelt. Ging super, da man ja am Algorithmus nix mehr nachdenken muss, nur noch HLL in ASS nachbilden. Parallel beide HLL und CODE in (Endlos)Schleife antriggern, und Ergebnisse vergleichen, bei Abweichungen den CODE debuggen.

OK ich war Hardcore Echtzeit Programmierer, der vorher nur in ASS gearbeitet hat. 3 Dutzend verschiedenste CPU, MiniComputer (ja echter, z.B. Hp2114 bis hp2100(mein pers. Liebling), AEG, SIEMENS Prozessrechner), Grossrchner TR440 (Telefunken), Nova (DG), TANDEM, PDP8, PDP11) und dann aberetliche uC von intel, über Motzorola, AMD, RTX ...

Langwierige Sachen wurden mal in Fortran und Basic programmiert. C kam gerade auf, aber GSD hab ich da FORTH gefunden und so eine Scheisse nie benutzt. Auch Algol, ADA, COBOLd und hunderter andere 'moderne' Sprachen/ Methoden immer per FORTH outperformed :)

So genug gebrunzt :) Mein Spitzname war Microprofessor :)

Saludos (an alle Vernünftigen, Rest sh. sig) Wolfgang

Ich bin in Paraguay lebender Trollallergiker :) reply Adresse gesetzt! Ich diskutiere zukünftig weniger mit Idioten, denn sie ziehen mich auf ihr Niveau herunter und schlagen mich dort mit ihrer Erfahrung! :p (lt. alter usenet Weisheit) iPod, iPhone, iPad, iTunes, iRak, iDiot

Jau ;-)

Eine übliche Vorgehensweise ist es mit dem scope die zeitlichen Längen der Funktionen und die Abarbeitung der IRs sichtbar zu machen, indem man ein paar Pins des Micros als logig flags dem scope zuführt und einen Portpin hi schaltet beim Funktionseintritt und beim verlassen den pin wieder runterzieht, toggeln etc. Man kann dann auf dem scope schön sehen ob eine Firmware rund läuft, insbesondere mit meherern IRs...

Bei dem Abfragen der Variablen (Speicherbereich des RAMs) aufpassen, falls diese Variablen länger sind als 8bit. Da der IR Dir just genau in dem Moment neue Werte in den Speicher schreiben kann, wenn die Berechnungsroutine den Wert ausliest. Abhilfe dazu z.B. den IR sperren und nach dem Lesen wieder freigeben.

Ah, das ist clever. Gerade wenn das Timing gefragt ist, dann ist Ausgeben von Debug-Statements über die serielle Schnittstelle keine wirkliche Option.

/ralph

Was für ein Polling Aufwand ;-)

Im IR selbst einfach ein Flag setzen, dass eine Berechnung nötig geworden ist. Nachdem die Berechnung fertig ist, wird das Flag gelöscht. Dann wird auch nicht soviel Rechenzeit unnütz verbraten.......

;-O

[Zählen der Pulse und Update der Uhrzeit in TimerISR]

Keine dieser Zeitpunkte dürfte grosse Rechenzeit zum Ermitteln erfordern. Vieles kann man vorab ermitteln und in einer Tabelle halten, z.B. die beweglichen Feiertage. Da sich der Wert für einen Tag immer erst um 0:00 Uhr ändert, hat der Controller fast den ganzen Tag Zeit zum Ausrechnen. Den Termin für die Schaltsekunden kannst du sowieso nicht errechnen; der wird bei uns von der PTB festgelegt.

Interessanter wird die initiale Synchronisation mit der exakten Atomzeit. Da wirst du wohl ein GPS Zeitnormal, das auf 1ms genau ist, brauchen. Eine Handeinstellung wie bei einer normalen Digitaluhr geht ja nicht.

Können die 10MHz nicht die CPU takten ? Hängt oft vom Prescaler der UART ab ob man noch sinnvolle Baudrate erhält. Hier sicherlich unkritisch. Aber oft vermeidet man, wenn möglich, weitere asynchrone Takte. Liefern manchmal merkwürdige Effekte die schwer zu debuggen sind wenn sie selten auftreten.

Interrupts erhöhen die Komplexität. Wenn man sie aus Geschwindigkeitsgründen nicht benötigt verwendet man sie nicht. Alle meine simplen Anwendungen a la Fertigungs-Prüfgeräte laufen in simpler Endlosschleife die bei Tastendruck am Terminal in die Kommandozeile zurückkehrt:

: RUN \ ( --- ) BEGIN < Anwendung >

TERMINAL? UNTIL ;

FORTH ist eben etwas anders. Aber keep it simpel ist wohl für alle Programmiersprachen empfehlenswert.

MfG JRD

Ich hätte gedacht vom "Amt für die Erdrotation" in Paris ;)

Aber es stimmt, ich könnte einiges in vorberechneten Tabellen ablegen. Mal sehen.

Ja ;)

Ich werde dazu einen GPS-Empfänger mit PPS-Ausgang nehmen, die sind so 15-150 ns genau relativ zu UTC, sofern man ein brauchbares Signal hat. Gibt es mittlerweile recht kostengünstig, z.B.:

formatting link
Die Idee ist, daß ich einen "Set"-Knopf mache, und wenn der gedrückt wird, dann wird:

  1. das an der seriellen Schnittstelle anliegende NMEA-Zeitsignal auf Plausibilität geprüft
  2. wenn das OK war beim nächsten PPS-Signal der Counter (und/oder externe Vorteiler resetted)
3 die aus dem NMEA-Signal geparste Uhrzeit und das Datum in den internen Zähler übernommen. Ja ;)

Ich werde dazu einen GPS-Empfänger mit PPS-Ausgang nehmen, die sind so 15-150 ns genau relativ zu UTC, sofern man ein brauchbares Signal hat. Gibt es mittlerweile recht kostengünstig, z.B.:

formatting link
Die Idee ist, daß ich einen "Set"-Knopf mache, und wenn der gedrückt wird, dann wird:

  1. das an der seriellen Schnittstelle anliegende NMEA-Zeitsignal auf Plausibilität geprüft
  2. wenn das OK war beim nächsten PPS-Signal der Counter (und/oder externe Vorteiler resetted)
3 die aus dem NMEA-Signal geparste Uhrzeit und das Datum in den internen Zähler übernommen.

/ralph

Die moderne Alternative zum AVR ist der Cortex-M und die laufen nicht mit 600 MHz, sondern eher so mit 48...200 MHz, je nach Modell. Selbst der aller aller aller schlechteste Cortex-M0 (z.B. STM32F030) schlägt den Atmel noch um LÄNGEN in Geschwindigkeit, Peripherie und Flexibilität. Eine gescheite Open Source Toolchain, statt diesem avr-gcc Gefummel gibt's obendrein. Und viel billiger als diese AVRs ist er auch noch.

Das schicke ist aber, dass man kein ätzendes Rumgefummel mehr mit der ekelhaften AVR Harvard Architektur mehr hat. Lookuptable im ROM? Einfach per Pointer drauf zugreifen. Endlich hat PROGMEM und read_pgm_byte() ein Ende. Bah hat mich das immer genervt. Würde ich mir echt NIE wieder antun, wenn ich in 2019 das µC Programmieren neu anfangen würde, NIE NIE wieder.

Trotzdem ist es nicht so, dass du mit grober Rechenleistung so schlecht programmieren kannst, wie du willst. Gerade wenn dir Latenz wichtig ist, musst du schon nachdenken, wie du's hinschreibst.

Stichwort hier ist ISR/DSR. Im ISR machst du nur die unbedingt notwendigen Sachen, in der DSR (Deferred Service Routine) dann das, wofür du Zeit hast. Anderes Stichwort ist WCET-Berechnung/Modellierung.

Aber ehrlichgesagt klingen deine "langwierigen" Aufgaben nicht so, als ob du sie nicht alle im ISR abhandeln könntest. Sagen wir du hast einen

2560, du clockst den mit 16 MHz. Du wolltest 1 kHz ISR-Frequenz. In den zwei (!) Millisekunden die du da also hast, bevor du IRQs verlierst, kannst du also 32000 Clock Cycles ausführen. Viele Instuktionen des AVR sind single cycle. Das ist UNGLAUBLICH viel Leistung, die du da hast.

Wenn du die eine Millisekunde überschreitest, während der ISR noch läuft, wird lediglich das IF gesetzt. Der ISR macht munter weiter und sobald du den dann verlässt, wird er sofort wieder angesprungen und IF gecleared. Interrupts verlierst du nur, wenn ein IRQ kommt wärend IF = 1 ist.

Viele Grüße, Johannes

Ja, auch das überlege ich, aber:

  1. Müßte ich da einige "convenience functions" beim Arduino anpassen oder auf sie verzichten (die laufen entweder mit 16MHz oder 8MHz), z.B. läuft dann die serielle Schnittstelle mit der falschen Baudrate.
  2. bin ich mir nicht sicher ob das ohne Auslöten des Quarzes (oder ähnliche Modifikationen am Board) geht.

Aber ja, das ist auch eine Sache die ich probieren möchte.

Manche Leute schreiben, daß das zumindest nicht trivial ist, wenn man kein rundes Verhältnis zu 16MHz hat.

Ich werde es zumindest probieren, sobald ich die 10MHz da liegen habe ;)

/ralph

Am 13.08.2019 um 13:25 schrieb Wolfgang Allinger:

Sorry, das ist furchtbarer Murks :-(

Zugriffe auf Variablen, die in Interrupts geändert werden können, müssen atomar erfolgen, d.h. Interrupts ausschalten, lokale Kopie erzeugen, Interrupts wieder einschalten.

DoDi

Das ist mindestens genauso aufwendig wie 2mal lesen und hat noch mehr Chancen für Race-Conditions, bzw. umzingeln der Abfrage mit DI und EI IR auf und zu. Auch Semaphoren hickhack ist aufwendiger...

Es sei denn, Du hast sowas wie JBC (8051), dann ist das sauberer und schneller, setzt aber voraus, dass Du genügend häufig das Bit abfragst, nicht das da mal 2 IRs waren. Mit der Alt/Neu Zählerstand Methode, funzt das sogar, wenn man mal einen verschlabbert, auch Überläufe musse latürnicht da korrekt behandeln. Latürnicht das SETB in der IRserv nach dem Ändern des Zählers...

:)))

Saludos (an alle Vernünftigen, Rest sh. sig) Wolfgang

Ja, das ist mit atomar auch ok, aber jedenfalls aufwändiger.

KISS \ meine Lösung ist idiotensicher (hoffe ich mal)

vZakt @ DUP vZakt @ <> IF DROP vZakt @ THEN \ ?race condition vZalt @ = IF DROP EXIT THEN DUP vZalt ! \ weiterverwursten FEDDICH

\ Deine atomar Lösung (theoretisch korrekt(er) :o )

DiIr vZakt @ EiIr \ IR auf und nieder, verhindert race condition DUP vZalt @ = IF DROP EXIT THEN DUP vZalt ! \ weiterverwursten FEDDICH

Deine Lösung ist auf den 1. Blick kürzer, aber wehe das DiIr wird erst ein (paar) Takte später wirksam, so wie beim Pleassy MICPROC 16AS (zeigt den Stinkefinger hinter seinem Rücken :o) dann bisse gEiIrt :p

Ich erinnere mich noch schwach, dass da auch ein paar andere uC da Fallen aufgestellt haben. Dann musse noch ein paar NOP einbauen und wenn einer das Quarz ändert, fängste u.U. wieder an zu suchen. Vor allem weiss das nach ein paar Jahren keiner mehr.

Einkauf: die CPU mit doppelter Taktfrequenz ist halb so teuer... ja kein Problem. Und irgendwan nörgelt ein Kunde Allinger, dass es da manchmal Hickup bei den neuen Geräten gibt. :]

WIMRE ist das mit Verzögerungen auch eine beliebte Falle bei pipelined CPU und wenn dann noch mehrere Kerne rumfuchteln, gehts leicht bergab. Kann man machen, muss man aber nicht.

Ich steh auf KISS, das sollte jeder Depp verstehen, sogar ich. Q.E.D. .

Irgenswie den Faden verloren, oder ich hab noch was übersehen. Schon sehr lange nicht mehr programmiert. Im Ruhestand bin ich so unruhig, dass ich keine Zeit mehr dazu hab :)

So und noch schnell zum Abschluss geätzt (Insider Witz):

Bei C hat der Programmierer den Compiler im Kopf, bei FORTH hat der Programmierer den Compiler im Target!

Saludos (an alle Vernünftigen, Rest sh. sig) Wolfgang

Ja. Insbesondere weil C auch zwei hintereinander folgende "lese" Abfragen schlicht wegoptimieren darf. D.h. man fängt dann mit volatile gemurkse an.

Bzw IRQ aus, lesen, ändern, schreiben, IRQ an. Viel einfacher zu implementieren als IRQ aus, lesen, IRQ an, ändern, IRQ aus, prüfen ob schreiben OK, schreiben/wiederholen, IRQ an.

Gruß, Johannes

snipped-for-privacy@gmx.de (Johannes Bauer) am 13.08.19 um 18:52:

Mithin jedesmal, wenn die ISR zweimal nacheinander zu lange dauert.

Aber welche ISR soll ständig mehr als 16.000 instruktionen abarbeiten, oder meinetwegen 10.000, wenn man im Schnitt mit 1.6 Takten pro Befehl rechnet?

Die muß doch in der Anwendung nur bei jedem IRQ ein paar Register updaten, Warteschleifen legt man da ja nicht rein.

Der Rest läuft außerhalb.

Rainer

Geht oft auch mit einer geschickt eingesetzten LED. Geht sie nie an oder nie aus (je nach Implementierung) ist was faul. Flimmert oder glimmt sie vor sich hin, ist alles schön ;-)

Rainer

Naja, Du hast geschrieben "dann alle Berechnungen anstellen". Das hab ich halt genau so verstanden.

Nein, Du hast vollkommen Recht. Es war mein Fehler dass ich nicht wusste was Du gedacht hast, sondern nur das gesehen hab was Du geschrieben hast.

Die Auswahl an ARMs in DIP oder 50 mil SO Gehäusen ist aber recht gering, was den Selbstbau etwas erschwert, so man nicht vom freundlichen Chinesen ein preiswertes Board erwirbt. Für den ARM spricht allerdings die Tatsache, daß viele bereits eine RTC mit Kalenderfunktion eingebaut haben. Auch die höhere Wortbreite von 16 oder 32 Bit erleichtert einiges.

Das hat aber weniger mit Harvard Architektur zu tun, sondern mit der Implementierung in gcc. Andere Compiler können das deutlich besser.

Muß nicht sein: Man legt den uC mit freigeschalteten Interrupts schlafen: sei(); sleep_mode(); Er kehrt dann aus dem "sleep_mode()" zurück, wenn die ISR beendet ist.

Leider ist der Spareffekt abhängig von der "Schlaftiefe": insbesondere die ATmegas lassen sich für einige Interrupt-Quellen nicht in einen Tiefschlaf versetzen.

Josef

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required