Forum: Compiler & IDEs Lange Funktion aus einem Interrupt


von Jürgen S. (jsachs)


Lesenswert?

Hallo,

Sorry für den langen Text erstmal.
Gleich vorab ich habe keinen Einfluss auf das Protokoll und/oder das 
Bustiming, das ist vorgegeben.

Ich binde mich an einen bestehenden Master-Slave 485 Bus an, was auch 
prima klappt im Prinzip.
Allerdings fülle ich im Moment beim Empfang nur einen Empfangspuffer mit 
den Daten vom Puffer. Sobald ich Zeit habe prüfe ich in MAIN einem 
riesigen Switch Case welches Kommando vorliegt und ob alle Bytes 
vorliegen.
Ist das Kommando vollständig schicke ich das ACK auf den BUS und rufe 
eine Funktion in meiner "Applikation" auf, die die Daten auswertet.
In der Zeit kommen natürlich neue Daten vom BUS.

Die Eigentliche Buskommunikation ist in einer API versteckt. Sobald ich 
ein Kommando erkenne rufe ich eine Funktion in meiner Applikation auf, 
die alles weitere tun muss. Die API Headerdatei mit Beschreibung hab ich 
mal anbei, falls dies weiterhilft.

Und hier kommt mein Problem. Brauche ich für die Auswertung der letzten 
Daten zu lange in meiner Applikation, verursache ich "Timingprobleme" 
auf dem BUS.

Nun dachte ich, ich könnte die lange Funktion auf meinem Interrupt der 
RS485 aufrufen. Dann würde aber auch das parsing der Empfangenen Daten 
im Interrupt ablaufen... und wenn ich zu lange brauche für das vorherige 
Kommando rufe ich die gleiche API Funktion eventuell 2 mal zur gleichen 
Zeit auf :-(

Beispiel: wie es sein sollte von Handling
Master schickt Kommando "Mach hell".
Slave schickt "ACK" und wertet das Kommando aus (Dauert jetzt zu lange)
Master schickt Kommando "Mach was anderes"
Slave schickt "ACK" und wertet das Kommando aus ("Mach Hell" wird noch 
bearbeitet)

Nun, bis ich die Kommandos parse und entsprechend reagiere bin ich zu 
langsam für 2 direkt folgende Kommandos wie oben.

In der ganzen Applikation wird nichts in Schleifen gelößt, sondern im 
Zyklischen Durchlauf von MAIN.

Hat jemand eine gute Idee wie ich das Problem lösen könnte ?
Achja, der Switch case enthält so ca 30 Case Anweisungen und enthält ca 
600 Zeilen Code. Leider darf ich den nicht Posten da das Protokoll 
"closed ist". Daher der Gedanke mit der API.

von Jürgen S. (jsachs)


Angehängte Dateien:

Lesenswert?

Die fehlende API Header Datei

von bla (Gast)


Lesenswert?

Die uebliche Vorgehensweise.
1)das ACK aus dem interrupt senden.
2)In Interrupt eine Boolean setzten : RxReady
3)im Main main auf die Boolean Rxready testen
4)in Main nachher die Case abarbeiten und besfehl ausfuehren.

von Klaus F. (kfalser)


Lesenswert?

Hängt auch davon ab, was das ACK im Protokoll für eine Bedeutung hat.

Wenn ACK bedeuten soll, daß das Kommando empfangen, akzeptiert und 
verarbeitet wurde, dann kann ich nicht in der IRQ Routine pauschal das 
ACK schicken.

von Michael Wilhelm (Gast)


Lesenswert?

Und wie wäre es, das ACK erst nach der Abarbeitung zu schicken?

MW

von Nullpointer (Gast)


Lesenswert?

ACK heisst ueblicherweise, das kommando wurde empfangen. Sonst wird es 
nochmals geschickt.

von OliverSo (Gast)


Lesenswert?

Wenn du wirklich jedes Kommando abarbeiten musst, und das Protokoll auch 
keine Möglichkeit bietet, eingehende Befehle mit "ich bin noch nicht 
fertig" zurückzuweisen, dann ist dein Mikro für die Aufgabe schlicht und 
einfach zu langsam.

Worauf läuft das ganze denn?

Oliver

von Klaus F. (kfalser)


Lesenswert?

Lösen kann man das auch mit einem RTOS.
Ein höher prorisierte Task macht den Empfang der Kommandos und kann 
vielleicht 1-2 Kommandos in einer Warteschlange halten.
Eine andere, nieder priorisierte Task macht das Auswerten.
Wenn die Abarbeitung nicht mitkommt, und die Kommando Warteschlange voll 
ist, dann akzeptiert die Kommando-Erkennungs-Task das kommando nicht und 
schickt NACK zurück (das ist wohl auch der Sinn von ACK und NACK)

Klaus

von Bobby (Gast)


Lesenswert?

Hallo  Jürgen,

wie wäre es denn damit:

Nach erkanntem Telegrammende kopierst Du die
Daten in einen extra Puffer und arbeitest damit
weiter. Das Kopieren (verschieben ohne zu löschen :-))
geht ja fix.

Die API Empfangsroutine kann in der Zwischenzeit
weitere Daten reinholen...

von Jürgen S. (jsachs)


Lesenswert?

Das ACK bedeutet ich habe die Daten Empfangen. Bei Aufgaben die sehr 
lange dauern dürfen, wie Firmware laden, setzt man nach Abschluss eine 
Art zusätzliches ACK ab. Dann bekommt man erst das nächste Packet

Das ganze läuft auf einem ATMega16 bzw ATMega32 der mit dem Internen 
Oszillator auf 8MHz läuft. Schneller Takten wäre technisch kein Problem 
würde aber zusätzliche Externe Komponenten benötigen. Da ich bisher 
alles auf Experimentier Platinen Aufbaue für meine Einzelstücke, 
versuche ich Externe Komponenten noch zu vermeiden.

Die Daten in einen Eigenen Puffer zu kopieren habe ich mir auch schon 
überlegt, aber gerade die Kommandos die Lange zum bearbeiten brauchen 
sind bis zu 70 Byte lang (bis zu 64 Byte ASCII). Die gilt es zu parsen 
und die Entsprechende Reaktion auszulösen.
Die kurzen Kommandos sind kein Problem, das ist meist nur ein Flag 
löschen oder setzen.

Da ich für die Applikation auch noch RAM brauche, würde ich mir das 
Kopieren und vorhalten in einem extra RAM gerne sparen. Die 2k Ram des 
Mega32 sind nicht so viel.

Das RTOS ist mir dann doch ne Nummer zu groß für die Applikation :-)
Wenn man bedenkt, das das Original auf einem 68HC11 mit 4 MHz läuft.

Da werde ich wohl doch noch Optimieren versuchen und zur Not den Takt 
hochdrehen. Ein Externer Quarz wäre für den UART sowieso nicht schlecht, 
wobei ich bisher keine Probleme mit dem internen UART habe...

Ich hatte nur die Hoffnung das einer die Zündende Idee hat.
Das ganze lässt sich bestimmt noch Optimieren, da muss ich eben 
ansetzen.

Danke
Jürgen

von Klaus F. (kfalser)


Lesenswert?

Nun, wie schon öfter diskutiert, muss ein RTOS nichts großes sein. AvrX 
ist sogar ausgesprochen klein, das setze ich auf einen ATMega32 mit 
Erfolg ein.

Dafür macht es Ordung in deinem Programm und schafft definierte 
Reaktionszeiten. Solche Timing Probleme mit einem höheren Takt zu 
erschlagen rächt sich langfristig, wenn dann nochmals kompliziertere 
Berechnungen dazukommen.

Das Kopieren des Buffers kann man sich sicher sparen, Du müßtest nur den 
Eingangsbuffer der IRQ groß genug machen, sodass das 2. Kommando auch 
Platz hat und mit einer Art Ringbuffer arbeiten.

Klaus

von Stinkywinky (Gast)


Lesenswert?

Wie wäre es denn, mit einer Statemaschine die emfangenen Daten sofort zu 
parsen und nicht erst, wenn alle geladen sind?

von Stefan (Gast)


Lesenswert?

ich würde auch nach jedem zeichen eine "kurze" auswertung machen - bei 
einsatz des RS485 und HC11 mit 4MHz wird wohl eine gemächliche 
Netzgeschwindigkeit vorliegen :-)

also einfach ne Statemachine oder so implementieren. Vielleicht auch 
eine sortierte Tabelle mit allen möglichen Befehlen implementieren --> 
stelle ich mir jetzt erst mal sehr einfach vor.

von Jürgen S. (jsachs)


Angehängte Dateien:

Lesenswert?

Also,

ich sollte das vielleicht kurz erklären, ich habe das vermutlich nicht 
richtig erklärt:
Alle Daten die vom BUS kommen landen in einem Eingangspuffer, das 
erledigt bisher ein Interrupt.
In "Mainline" passiert dann bisher folgendes:
Als erstes prüfe ich auf das erste Busprotokollbyte, dann weis ich 
wieviele Zeichen folgen. Sobald diese da sind, gibt meine API dies 
Aufbereitet an das "Anwendungsprogramm" weiter.
Es gibt einmal das Parsing des Busprotokolls. Über das Busprotokoll 
kommen Befehle wie "Schalte Ausgang 1 an" und führen zum Aufruf der API 
Funktion
"void processChannel(uint8_t channel, bool val);".

oder auch Text Kommandos wie
"TEXT10-Hier ist der Text für Zeile 10". Diese Applikationsabhängig zu 
Interpretieren !
Das führt zum Aufruf der API-Funktion
"void processCommand(char *data, uint8_t len);"

Jetzt muss mein Anwendungsprogramm das weiter zerlegen und reagieren.
Den Teil darf ich wieder posten, der ist ja Protokollfrei :-)
Da es nur ein Teil ist lässt es sich auch nicht kompilieren.

Wie gesagt ist der Interessante Teil processCommand(). Das ist der Teil 
wo ich wohl zu lange brauche.

API wertet Kommando aus->processCommand() wertet Applikations Kommando 
aus->Löst Aktion aus.
Die Kette ist zu lange. Ich denke die reine API Auswertung ist schnell 
genug für einen Interrupt. Nur der Aufruf von "processCommand()" habe 
ich nicht in der Hand...... Also könnte ich das Kommando 
Zwischenspeichern und in "Mainline" ausführen, wie oben beschrieben. Da 
ich die Ausführungszeit nicht in der Hand habe, könnte ja noch ein 
Kommando eintreffen, also einen Stack aufbauen. uff.

Bin ich nur Paranoid ?!

Ich hoffe es wurde etwas deutlicher :-)

von Klaus F. (kfalser)


Lesenswert?

Ohne mich jetzt durch Dein Programm durchgewühlt zu haben, denke ich 
gibt es 2 Möglichkeiten:
- Die Interrupt Routine (API) ist schnell genug, um das Ende eines 
Kommandos zu erkennen.
In diesem Fall sendet die IRQ das ACK Signal, kopiert das Kommando auf 
eine Ringbuffer, der von der MainProgramm abgearbeitet wird. Wenn im 
Ringbuffer kein Platz mehr ist, weil die MainRoutine nicht mitkommt, 
dann muß NACK zurückgeschickt werden.
- Man verwendet ein RTOS und teilt das ganze in Interrupt Routine, Task 
zur Kommando-Erkennung und Task zur Kommando-Abarbeitung auf. Auch dabei 
braucht es eine Art Ringbuffer, mit dem die erkannten Kommandos an die 
Abarbeitungs-Task weitergereicht werden und der eventuell voll werden 
kann.

Alles andere ist ein Pfusch !

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.