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
Suchender schrieb: > If-Schleifen wenn schon: if-Abfrage if-Abfragen kann man u.U. durch switch-case ersetzen
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 kannst für den Start die if-Bedingungen zusammenfassen:
1 | if ( a[0]==0x68 && a[1]==0xE0 && a[2]==0xE0 ) { ... |
Wie es weitergeht kann ich dir nicht sagen, da ich das KNX-Protokoll nicht kenne.
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 :-)
Suchender schrieb: > ein KNX FT1.2 Protokollmitschnitt vllt. mla hier stöbern: http://ask.aboutknx.com/questions/1217/list-of-knx-open-source-or-free-software
Danke, Konrad und Lutz. Super naheliegend, da schämt man sich ja fast. Dirk, interessante Liste, werd ich mal checken. Merci auch!
Am besten ist eine Zustandsmaschine, die aufgrund der Konstruktion nicht wartet, und daher jedes empfangene byte nur einmal auswertet.
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?
wenn es 1-byte-Befehle sind, wie bei vielen einfachen Protokollen, dann reicht eine Sprungtabelle (oder halt ein switch/case in C), z.B. so hier:
1 | command_loop: |
2 | |
3 | ; ... |
4 | ; befehl einlesen |
5 | ; ... |
6 | |
7 | ldi ZH, high(command_jump_table) |
8 | ldi ZL, low(command_jump_table) |
9 | add ZL, command |
10 | adc ZH, zero |
11 | icall |
12 | |
13 | rjmp command_loop |
14 | |
15 | command_jump_table: |
16 | rjmp command_flush ; 0x00 |
17 | rjmp command_try_set_sid_count ; 0x01 |
18 | rjmp command_mute ; 0x02 |
19 | rjmp command_try_reset ; 0x03 |
20 | rjmp command_try_delay ; 0x04 |
21 | rjmp command_try_write ; 0x05 |
22 | rjmp command_try_read ; 0x06 |
23 | rjmp command_get_version ; 0x07 |
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.
Ja, aber dafür hat Peter Dannegger den ROLLOVER erfunden :-).
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
|
9 | *
|
10 | * {"BEFEHL ... Kurzbeschreibung\n", funktionsname, "Syntaxhilfe\n"},
|
11 | * ^Leerzeichen zwischen BEFEHL und "..." zwingend notwendig!!!
|
12 | * Prototypen der Funktionen in console.h eintragen!
|
13 | */
|
14 | const debug_command_t f[] = { |
15 | {"add ... add two numbers\n", cmd_add, "add <number> <number>\n"}, |
16 | {"mul ... multiply two numbers\n", cmd_mul, "mul <number> <number>\n"}, |
17 | {"help .. Command overview/help\n", cmd_help,"help [command]\n"}, |
18 | {"? .... Command overview/help\n", cmd_help,"? [command]\n"}, |
19 | {"exit .. exit\n", cmd_exit, "x\n"} |
20 | };
|
21 | |
22 | #define NO_COMMANDS (sizeof(f)/sizeof(debug_command_t))
|
23 | #define COMMAND_BUFFER 80 /* Eingabepuffer */ |
24 | |
25 | int exit_debug = 0; /* Wird durch cmd_exit auf 1 gesetzt */ |
26 | |
27 | /* Hauptfunktion, wird verlassen wenn exit_debug == 1
|
28 | *
|
29 | */
|
30 | void debug(){ |
31 | char debug[COMMAND_BUFFER]; /* Buffer für Debug-Kommando */ |
32 | unsigned char i; |
33 | |
34 | printf("\n========= Console ==========\n"); |
35 | printf("Enter '?' for command overview\n"); |
36 | while(1){ |
37 | i = 0; |
38 | |
39 | printf("\n# "); |
40 | |
41 | do{ |
42 | debug[i] = getch(); |
43 | putchar(debug[i]); /* Echo */ |
44 | if(debug[i] == '\b' && i){ /* Backspace? */ |
45 | printf(" \b"); /* Leerzeichen ausgeben + Cursor zurück*/ |
46 | i--; /* letztes Zeichen löschen */ |
47 | }
|
48 | else{ |
49 | i++; |
50 | }
|
51 | if(i >= COMMAND_BUFFER -1){ /* Kommando zu lang */ |
52 | i = 0; |
53 | printf("\nCommand too long\n"); |
54 | continue; |
55 | }
|
56 | /* Schleife bei ENTER verlassen */
|
57 | }while(debug[i-1] != '\r' && debug[i-1] != '\n'); |
58 | |
59 | if(i == 1) /* Kein Kommando eingegeben */ |
60 | continue; |
61 | debug[i-1] = '\0'; /* Stringterminierung */ |
62 | putchar('\n'); |
63 | |
64 | if(i > 0) |
65 | parse_and_call_cmd(debug); /* Kommando suchen und ausführen */ |
66 | if(exit_debug) /* Wenn exit eingegeben */ |
67 | break; |
68 | }
|
69 | }
|
70 | |
71 | /* Addiert zwei Zahlen */
|
72 | void cmd_add(char argc, char **argv){ |
73 | int no1, no2; |
74 | if(argc == 3){ |
75 | no1 = atoi(argv[1]); |
76 | no2 = atoi(argv[2]); |
77 | printf("%d + %d = %d", no1, no2, no1 + no2); |
78 | }
|
79 | }
|
80 | |
81 | /* Multipliziert zwei Zahlen */
|
82 | void cmd_mul(char argc, char **argv){ |
83 | int no1, no2; |
84 | if(argc == 3){ |
85 | no1 = atoi(argv[1]); |
86 | no2 = atoi(argv[2]); |
87 | printf("%d * %d = %d", no1, no2, no1 * no2); |
88 | }
|
89 | else
|
90 | cmd_help(2,argv-1); /* Syntax-Ausgabe */ |
91 | }
|
92 | |
93 | /* beendet die Debug-Konsole */
|
94 | void cmd_exit(char argc, char **argv){ |
95 | exit_debug = 1; |
96 | }
|
97 | |
98 | void cmd_help(char argc, char **argv){ |
99 | unsigned char i; |
100 | unsigned char cmd_pos; |
101 | |
102 | if(argc == 2){ /* Kommandospez. Hilfe? */ |
103 | cmd_pos = is_dbg_command(argv[1]); |
104 | /* Debug-Kommando? Ja => Syntax ausgeben */
|
105 | if(cmd_pos != NO_COMMANDS + 1 ){ |
106 | printf("\n[..] - optional argument\n<..> - required argument\n"); |
107 | printf("\n%sSyntax: %s", f[cmd_pos].nptr, f[cmd_pos].syntax); |
108 | }
|
109 | else /* Kommando nicht gefunden */ |
110 | printf("Command not found\n"); |
111 | }
|
112 | else{ /* Befehlsübersicht ausgeben */ |
113 | for(i = 0; i < NO_COMMANDS ; i++) |
114 | printf(f[i].nptr); |
115 | }
|
116 | }
|
117 | |
118 | /* Extrahiert und zählt die Argumente, sucht das Kommando über
|
119 | * is_dbg_command und führt es dann aus.
|
120 | *
|
121 | */
|
122 | void parse_and_call_cmd(char *cmdptr){ |
123 | unsigned char argc=0, cmd_pos; |
124 | |
125 | #ifndef DYN_ARGS
|
126 | unsigned char *argv[MAX_ARGS]; |
127 | #else
|
128 | unsigned char **argv = NULL; |
129 | argv = (unsigned char **)malloc(sizeof(char **)); |
130 | #endif
|
131 | |
132 | argv[argc++] = cmdptr; /* Kommando selbst in argv[0] */ |
133 | while(1){ |
134 | cmdptr++; |
135 | if(*cmdptr == ' '){ |
136 | *cmdptr = '\0'; |
137 | while(*++cmdptr == ' '); /* Leerzeichen überspringen */ |
138 | #ifdef DYN_ARGS
|
139 | argv = realloc(argv, (argc+1)*sizeof(char **)); |
140 | #else
|
141 | if(argc > 9){ |
142 | printf("\nToo many arguments!\n"); |
143 | return; |
144 | }
|
145 | #endif
|
146 | argv[argc++] = cmdptr; |
147 | }
|
148 | else if(*cmdptr == '\0'){ |
149 | break; |
150 | }
|
151 | }
|
152 | cmd_pos = is_dbg_command(argv[0]); |
153 | if(cmd_pos == NO_COMMANDS + 1) /* Befehl nicht gefunden */ |
154 | printf("\nUnknown command\n"); |
155 | else{ |
156 | (f[cmd_pos].fptr)(argc, (char **)argv); /* Funktion aufrufen */ |
157 | }
|
158 | #ifdef DYN_ARGS
|
159 | free(argv); |
160 | #endif
|
161 | }
|
162 | |
163 | /* Sucht das Kommando in dem debug_commands-Struct und liefert
|
164 | * die Position im Struct zurück
|
165 | */
|
166 | char is_dbg_command(char *cmdptr){ |
167 | unsigned char i=0, j=0, error; |
168 | |
169 | while(i < NO_COMMANDS ){ /* Alle Kommandos durchsuchen */ |
170 | error = 0; |
171 | while(f[i].nptr[j] != ' '){ |
172 | if(f[i].nptr[j] != cmdptr[j]){ |
173 | error = 1; |
174 | break; /* Abweichung => nächsten Befehl testen */ |
175 | }
|
176 | j++; |
177 | }
|
178 | if(!(error == 0 && (cmdptr[j] == ' ' || cmdptr[j] == '\0') )){ |
179 | error = 1; |
180 | i++; |
181 | j=0; |
182 | }
|
183 | else /* Kommando gefunden, error = 0 */ |
184 | break; |
185 | }
|
186 | if(error==0) |
187 | return i; |
188 | else
|
189 | return NO_COMMANDS+1; |
190 | }
|
console.h
1 | /* Prototypen für die Kommandos */
|
2 | void cmd_add(char argc, char **argv); |
3 | void cmd_mul(char argc, char **argv); |
4 | void cmd_help(char argc, char **argv); |
5 | void cmd_exit(char argc, char **argv); |
6 | |
7 | void debug(); |
8 | void parse_and_call_cmd(char *cmdptr); |
9 | char is_dbg_command(char *cmdptr); |
10 | |
11 | typedef void generic_f (char argc, char **argv); |
12 | |
13 | typedef struct{ |
14 | char *nptr; |
15 | generic_f *fptr; |
16 | char *syntax; |
17 | }debug_command_t; |
:
Bearbeitet durch User
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.
:
Bearbeitet durch User
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.
Svenska schrieb: > Mit einem Zustandsautomat kannst du Pakete bereits verarbeiten, während > du sie empfängst, ... Verstanden, danke für die Erklärung.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.