Hallo Forum,
ich schicke über UART Kommando's, also z.B. Klartext oder auch einige
Bytes als Prefix + einige Bytes oder auch lesbare Zahlen als Parameter
an einen Atmega328. Je nach Kommando soll dann eine Funktion ausgelöst
werden, die dann Led's mit diversen Mustern ansteuern.
Ich krieg das auch hin, aber es ist ein unelegantes Gewürge durch
If-Schleifen. Ich bin Hobbist und Anfänger und kenne vielleicht auch die
richtig schönen Stringfunktionen noch nicht.
Was ich suche, ist ein Program, welches das mal so richtig elegant und
schön löst, wo man sich was von abkucken kann. Google wirft zumindest
mit meinen Suchbegriffen immer was knapp daneben aus.
Danke für jeden Link.
Grüsse
Danke Euch beiden, aber ich glaub, was mir Parsen so an Beispielen
schickt, ist nun schon wieder eine Stufe zu komplex, und scheint auch
auf lesbaren Text zu gehen. Und Switch-case kenne ich, trifft es aber
auch nicht ganz.
Um vielleicht etwas tiefer in ein Beispiel einzusteigen:
Ich empfange eine Hex-Folge, die mit 68 E0 E0 68 anfängt und irgendwo
mit 16 68 aufhört mit einigen Bytes dazwischen mit unterschiedlicher
Länge. Für Interessierte - das ist ein KNX FT1.2 Protokollmitschnitt.
Jetzt geh ich da mit meinen if's dran und sage so etwas wie:
if (a[0]==x68) {
if (a[1]==xE0) {
if (a[2)==xE0) { // ab hier Annahme, dass valides Kommando
dann liess Bytes solange nicht if (a[x]==16)
... if (a[x+1]==68) ist.
Das ist grausig. Das muss und wird einfacher und besser gehen. Nur fehlt
mir grad die erleuchtende Idee.
Kann mir jemand Hinweise geben, wo ich weiterforschen kann?
Danke
Grüsse
Du könntest die If Bedingungen mit einem "und" verknüpfen und in eine
Zeile scheiben
etwa so:
if ( (a[0]==x68) && (a[1]==xE0) && (a[2)==xE0) )
{ ...}
schade, nur Zweiter. kondadswar ein paar Sekunden schneller :-)
Mal ne Frage an die C Experten:
Konrad S. schrieb:> if ( a[0]==0x68 && a[1]==0xE0 && a[2]==0xE0 ) { ...
Setzt das nicht voraus das dein Empfangspuffer mit diesem Zeichen
anfängt?
Ist:
pos = (strstr (ReceiveBuffer, KNXcommand));
if (pos) {....
nicht einfacher?
Ich würde auch den Zustandsautomaten bevorzugen. Damit lässt sich recht
einfach ein Parser schreiben.
Realisiert sich mit einem switch/case für den Zustand recht gut. Jedes
Byte wird dann einmal durch den Automaten gereicht, der dann weiß, was
er damit zu tun hat (z.B. in einen Datenpuffer schreiben, Zustand
wechseln, ...). Damit brauchst du nicht sicherstellen, dass das erste
Zeichen von deiner UART auch der Startbefehl ist.
Die Sprungtabelle ist ein Spezialfall davon.
Der Rächer der Transistormorde schrieb:> Setzt das nicht voraus das dein Empfangspuffer mit diesem Zeichen> anfängt?
Mein Vorschlag orientierte sich einfach nur an der Notation im zweiten
Post des TE.
Und nein, der Empfangspuffer muss nicht mit dieser Sequenz beginnen.
Z.B. mit:
1
char*a=...
2
if(a[0]...
kann die Sequenz irgendwo im Empfangspuffer, aber sie muss im Speicher
stehen. Mit einem Zustandsautomaten ist das nicht nötig.
Ist wie immer: kommt drauf an! ;-)
Hallo,
erstmal herzlichen Dank für alle Beiträge!
Vermutlich ist die Zustandsmaschine eine gute Lösung, aber nach kurzer
Lektüre von
http://www.embedded.com/design/embedded/4024591/State-machine-shortcuts
hab ich mich dran erinnert, dass das Hobby ist. Bin aus dem Studium noch
durch das Thema Operations Research etwas verbrannt.
Ich glaube, ich werde mich mal an einer Kombination von Falk's Routine
mit den obigen Tips versuchen. Vermutlich hilft mir auch die Idee des
Rächers der Transistormorde weiter. Der USART schreibt mir in einen
Ringpuffer, d.h. dort kann ich auch mit Konrad's Ansatz zugreifen.
Grüsse und Dank
Suchender schrieb:> Der USART schreibt mir in einen> Ringpuffer, d.h. dort kann ich auch mit Konrad's Ansatz zugreifen.
Wobei es passieren kann, dass z.B. das erste Byte der Startsequenz am
Ende des Ringpuffers steht, das zweite Byte am Anfang des Ringpuffers.
Du könntest die Befehle (samt Funktionszeiger) in einer Tabelle ablegen
und zwar geordnet. Während dann die Zeichen nach für nach reinkommen,
kannst du sozusagen mit jedem Zeichen die Auswahl an noch verbleibenden
Befehlen eingrenzen.
Wenn das Array alphabetisch geordnet ist, dann heißt das, dass du den
Zeiger immer weiterschieben kannst, bis du einen Treffer an der
aktuellen Stelle gefunden hast. Alle danachfolgenden Befehle im Array
sind dann nur noch möglich.
So oder so ähnlich würde ich das lösen. Damit sollte sich der
Suchaufwand ziemlich linear verhalten.
Also ich habe mir für "lesbare" Kommandos mal eine "Debug-Konsole"
ausgedacht... das lässt sich natürlich auch auf andere Protokolle
anpassen.
Ich weiß nicht ob das so völlig bugfrei war, hat mir aber bisher ganz
gute Dienste geleistet. getch, putchar, printf muss man ggf. noch
anpassen, jenachdem was man so verwendet für die uart Ein/Ausgaben.
console.c
1
#include<stdio.h>
2
#include<stdlib.h>
3
#include<conio.h>
4
5
#include"console.h"
6
7
#define MAX_ARGS 10
8
/* Kommando-Struktur, einfach nach Belieben erweitern
Konrad S. schrieb:> Wobei es passieren kann, dass z.B. das erste Byte der Startsequenz am> Ende des Ringpuffers steht, das zweite Byte am Anfang des Ringpuffers.
Noch mal eine Frage vom C Novizen.
Wozu braucht man einen Ringpuffer wenn das Protokoll bekannt ist?
Sind nicht zwei (oder mehr) Protokoll Buffer besser? Die Empfangsroutine
muss doch so oder so jedes Zeichen vom Uart in einen Buffer schreiben.
Da kann Sie doch gleich auf ETX (oder was auch auch immer) prüfen. Das
empfangene Byte steht schon im CPU Register und sie muss nur ein
Bufferflag toggeln (oder hochzählen).
Dann ist die Protokollsequenz vorsortiert im eigenen Buffer mit
definiertem Anfang.
Hallo, Rächer,
das läuft darauf hinaus, einen Teil des Zustandsautomaten in die
Empfangsprozedur zu verlagern. Wenn der UART-Empfang interruptgesteuert
ist, dann muss man aufpassen, ob das zeitlich hinhaut. Ansonsten kann
man das auch so machen, wie Du vorschlägst.
Allerdings hat man dann mehreren Zustandsautomaten am Hals: Einen, der
die Zeichen direkt nach dem Empfang auswertet und entscheidet, in
welchem Puffer sie landen, und dann noch welche, die nur die Zeichen in
den diversen Puffern verarbeiten. Alle diese Automaten müssen
gegenseitig ihre Zustände beeinflussen. Z.B. um zu verhindern, dass ein
Kommando, das noch nicht vollständig empfangen wurde, ausgewertet wird,
weil Teile davon schon im Kommandopuffer stehen (die Kommandos können ja
mehr als ein Byte lang sein).
Mehrere miteinander verknüpfte Zustandsautomaten in verschiedenen
Programmteilen - das wird sehr schnell sehr unübersichtlich. Deshalb ist
es programmiertechnisch praktisch, alles übersichtlich in einem
Automaten zusammenzufassen. Dann hat man nur einen Programmteil, in dem
man suchen und korrigieren muss ;-).
Ciao, mare_crisium
> ... Alle diese Automaten müssen gegenseitig ihre Zustände beeinflussen.
Nein, eben nicht!
Jeder Zustandsautomat arbeitet autonom, sonst ist es keiner.
Er bekommt die (vollständigen, eventuell mit einem anderen Automaten
schon aufbereitetetn) Events nacheinander (meinetwegen aus einer Queue)
und verarbeitet sie schön der Reihe nach. Erst wenn er einen Event
vollständig abgearbeitet hat, holt er sich den nächsten.
Es können also mehrere Automaten unabhängig voneinander werkeln.
Blackbird
Nachtrag:
Ein Event kann (z.B.:) eine (vollständige) Nachricht sein oder ein
Timeout oder ein Interrupt.
Der Automat kann selbst seine Eingangsqueue pollen oder per Ereignis
aufgerufen werden.
Blackbird
Harald M. schrieb:> Allerdings hat man dann mehreren Zustandsautomaten am Hals:
Hallo Harald und alle anderen.
Schönen Dank für die Antwort, muss dazu sagen das ich mich an die
Materie herantaste. Vielleicht ist einiges was ich schreibe trivial oder
Blödsinn oder spinnert.
Sei's drum, ich sehe das so:
Das mit den Zustandsautomaten ist sicher richtig, aber nicht wenn du
verschiedene - nennen wir es mal- Hierarchieebenen hast. Dann ist der
empfangene Datenstrom die eine (eine Übertragungsschicht), das in ihm
enthaltene Protokoll aber eine ganz andere (verschiedene Bytefolgen mit
durch die Protokolldefinition intendiertem Informationsinhalt).
Wenn du nun eine state machine auf den Empfangspuffer jagst dann ist
aber genau anders herum als von dir geschrieben. Das vermischt die
Ebenen. Du musst sie gleichzeitig behandeln. 1. den Datenstrom und 2.
die intendierte Information.
Wenn du Punkt 1 in der Empfangsroutine erledigst (wo Sie meiner Meinung
nach hingehört und mit einem Bufferpointer auch im Interrupt sehr
Effizient behandelt werden kann.
z.B.
if (ETX) pBuffers++;
else schieb das blöde Byte in den Buffer;
dann hat deine state machine komplette Telegramme vor sich und du die
Separierung schon erledigt.
Stimmt das oder bin ich auf dem Holzweg?
Der Rächer der Transistormorde schrieb:> Stimmt das oder bin ich auf dem Holzweg?
Stimmt schon.
Viele Wege führen nach Rom.
Hat man UART Routinen vor sich, wie zb die vom P.Fleury so enthalten die
bereits eine Interrupt-gesteuerte Empfangsroutine samt zugehörigem
Ringbuffer. Der Ringbuffer hat den Zweck, das restliche Programm von der
Bürde des UART Überwachens zu befreien und eine kleine Speicherfläche zu
bieten, so dass das Programm auch mal etwas länger brauchen kann, ohne
dass gleich Zeichen verloren gehen.
Das ist sein Zweck. Sein einziger Zweck. Nicht mehr.
Da man diese Funktionalität praktisch gesehen in jedem Programm
gebrauchen kann, macht es durchaus Sinn, das (und nur das) als Baustein
verwendbar zu haben und alles darüber hinausgehende in darüberliegende
Software-Schichten zu legen.
Was wiederrum den Nachteil hat, dass man eventuell manche Dinge doppelt
macht. So speichert der Ringbuffer Zeichen zwischen und die darüber
liegende Schicht muss dann wieder ihren eigenen Buffer bemühen um daraus
die Nachrichten zusammenzusetzen (oder eine State-Maschine darauf
ansetzen)
Du musst jetzt für dich abwägen was dir wichtiger ist. Willst du ein
universell einsetzbares UART-Modul, dass du auch in den nächsten 10
Projekten so wie es ist einsetzen kannst, oder willst du die
ISR-Empfangsroutine so pimpen, dass sie dir das Aufdröseln des Inputs in
Nachrichten auch gleich mit macht, auch auf die Gefahr hin, dass du
damit schon im nächsten Projekt nichts mehr anfangen kannst.
Es ist deine Entscheidung, beides ist möglich.
Du wirst dich dran gewöhnen müssen, dass es in der Software mehr als nur
einen Weg bzw. Möglichkeit gibt und diese verschiedenen Möglichkeiten
unterschiedliche Vor- und Nachteile haben. Manchmal hat man auch nur die
Wahl zwischen Pest und Cholera. 'One size fits all' funktioniert halt
nur bei Baseballkappen, aber selten in Software.
Genau das ist auch der pragmatische Grund, warum ich einen Ringpuffer
verwende - code reuse vom Fachmann (Fleury).
Simon und Timmo - genau so etwas hab ich zwischenzeitlich auch gesehen,
und zwar im Webserver von Ulrich Radig. Das ist sehr strukturiert und
übersichtlich.
Ich hatte kurz überlegt, ob ich das verwenden soll, hab es dann aber für
meinen Fall verworfen, und zwar weil ich ein Binärformat auswerte, in
dem ein Protokolloverhead (mit Adressen, u.a.) von einem 4-Byte Nutzkode
zu trennen ist. Von diesen 4 Bytes (eigentlich ein KNX 32bit Wert) ist
dann das erste Byte ein Kommando, und die 3 nächsten jeweils Parameter.
Da ist ein Switch einfacher, da nur ein uint8_t ausgewertet werden muss.
Ich finde das ist ein recht interessanter Thread geworden, der durchaus
auch allgemein von Interesse ist. Ergänzend wären jetzt vielleicht
Hinweise zu Implementierungen dieser "Zustandsautomaten" noch
interessant - insbesondere, wenn es da möglich sein sollte, die ähnlich
universell wie die Tabellen von Simon/Timmo aufzubauen.
Danke und Grüsse
Karl Heinz Buchegger schrieb:> Viele Wege führen nach Rom.
Schon Ok, ist ja nicht nur bei Software so.
Danke auch für die Erklärung mit dem Ringbuffer und Wiederverwendbarkeit
was man sich eventuell mit dem Nachteil mehrere Buffer erkauft.
Dazu hab ich noch eine (vielleicht etwas akademische) Frage. Was ist mit
"Endpoints".
Fast jede Übertragung läuft doch auf einen Datensatz mit Anfang und Ende
hinaus.
Statt in den Ringpuffer schiebt die Empfangsroutine die Zeichen in den
Endpoint (oder Datensatz Puffer).
Sie prüft bei jedem Zeichen ob die EndeOfDatensatz Bedingung erfüllt
ist.
Wenn ja nimmt Sie das nächste Buffer das der Parser freigegeben hat.
Wäre das nicht effizienter (weil der Ringzähler quasi in buffer
aufgeteilt wird) und ebenso universell verwendbar?
> Dazu hab ich noch eine (vielleicht etwas akademische) Frage. Was ist mit> "Endpoints".
Gibt es. Sind dann sinnvoll, wenn man mehrere unabhängige (virtuelle)
Kanäle über eine Leitung multiplexen möchte. Dann könnte man auch
mehrere kleine Ringpuffer anlegen.
> Sie prüft bei jedem Zeichen ob die EndeOfDatensatz Bedingung erfüllt> ist.
Verschiedene Protokolle haben verschiedene Datensatztrenner. Wenn du
nachrichtenbasiert auswerten möchtest, muss dein Puffer (für jeden
Endpoint!) mindestens so groß sein, wie die größte erlaubte Nachricht,
während der Ringpuffer nur der Entlastung dient und daher auch klein
bleiben kann (ein/zweimal 8 Byte plus zwei Indizes reicht oft schon).
Mit einem Zustandsautomat kannst du Pakete bereits verarbeiten, während
du sie empfängst, verteilst im Optimalfall also den Rechenaufwand über
die Empfangszeit und kannst bereits verarbeitete Daten direkt wegwerfen.
Wenn dein Protokoll das ermöglicht (und du eine Strategie für
Empfangsfehler hast), bekommst du eine gleichmäßigere CPU-Auslastung und
damit geringe Latenzen hin.