Hallo :)
Ich entwickle gerade die Firmware fuer ein Messgeraet, was in der
Fertigungsmesstechnik eingesetzt werden soll. An das Geraet sollen
Kommandos von einem Anwender-PC über die RS 232 Schnittstelle gesendet
werden.
Als µC-Basis dient mir im Moment noch ein ATMega32 (noch BreadBoard
Entwicklungsstatus). Auf der fertigen Platine wird es dann ein ATMega128
sein.
Ueber die Implementierung des UARTS auf dem AVR hab ich mir schon
Gedanken gemacht. Ich hab die UART-Library von
http://homepage.hispeed.ch/peterfleury/avr-software.html
fuer meine Umgebung genommen und entsprechend angepasst. Jetzt stehe ich
an dem Punkt wo es darum geht einen String, der einen Befehl vom
Terminal-Programm zum Gerät representiert, auszuwerten und die
Befehlsparameter fehlerfrei abzufangen.
Da ich ein Zeitgesteuertes Betriebssystem nutze
(http://www.le.ac.uk/engineering/mjp9/pttes.html) habe ich eine
UART-Handler-Funktion konstruiert, die zyklisch aufgerufen wird und
immer den Ring-Buffer auswertet, sofern was neues drinne steht. Wenn ein
Endzeichen ('\n') gefunden wird gilt der String als komplett uebertragen
und die "Befehlsauswertung" beginnt. An dieser Stelle bin ich mir nicht
sicher, ob ich wirklich eine vernünftige Lösung gefunden habe.
Hier mal mein Quellcode für die Befehlsauswertung:
1
/* Wird nur betreten wenn ein String uebertragen wurde */
Flag_string_complete=false;/* es kann neuer String uebertragen werden */
81
}
82
83
else
84
{
85
/* Syntax-Fehlercode ausgeben */
86
}
87
break;
88
}
89
break;
90
}
91
}
Das ist natuerlich nur ein Ausschnitt aus der gesamten
uart_rx_handler-Funktion. Auch die explizite Zuweisung des Inhhalts des
char-arrays "rx_string" ist "symbolisch" zu verstehen. So kann der
String aussehen, wenn er aus dem Ring-Buffer ausgelesen wurde. Außerdem
wird im Moment auch nur dieser eine Befehl verarbeitet. Aber es geht ja
ums Prinzip.
Ihr seht, dass die Paramter von Kommas getrennt sind. Meine
"Befehlsauswertung" soll diese Paramter finden, in Integer-Zahlen
umwandeln und dann an die passende Funktion, welche zu dem Befehl
gehört, weitergeben.
Im Moment mache ich das auf die ziemlich "strohdoofe" Variante:
Einfach den String "rx_string" von links nach rechts durchgehen und
eventuell sinnvolle Sachen in einem tempraeren array namens "tmp_string"
ablegen.
Tja, meine Implementierung um die Parameter fuer den Befehl aus dem
String "rx_string" zu "fischen" funktioniert zwar. Aber irgendwie
gefällt mir das alles nicht. Das ist so richtiger
"Tipel-Tapel-Tour"-Code:
- zu viele while-Schleifen (gefällt mir nicht in meiner zeit-gesteuerten
Betriebssystemumgebung)
- zwei Indexvariablen (unuebersichtlich)
- fuer jeden Paramteter eine neue while-Schleife (ineffizient)
Ich muss zugeben, dass ich von Strings in C nicht viel verstehe. Was ich
weiß ist, dass ein String immer mit "\0" terminiert werden muss. Wie ihr
steht hab ich das in meiner Implementierung auch umgesetzt ;).
Darum frage ich euch, ob es für mein Problem auch eine bessere
Implementierung gibt. Vielleicht ist ja mein ganzer Ansatz unguenstig
gewaehlt. War einfach das Erste, was mir eingefallen ist.
Die Schwerpunkte, nach denen ich immer suche sind:
- bessere Lesbarkeit des Codes, damit ich ihn selber noch nach nem Monat
verstehe
- hohe Abarbeitungsgeschwindigkeit (ist ja ein Zeitkritischer Prozess)
Ich bedanke mich schonmal im Voraus fuer eure Anregungen und
Vorschlaege!
LG, Armin
Mal ein paar Tips:
- Deine Eingabe wird durch Kommas getrennt. Nutze es.
Zerlege deinen empfangenen String in
"Befehl Komma Wert Komma Wert Komma Wert Ende" (ich geh mal davon aus,
dass die Sequenz immer so ist).
- Den Befehl auswerten, falls dieser immer kleiner gleich 2 oder 3 chars
ist, lässt sich das sehr gut mit vergleichen erledigen:
1
uint32_ttemp=befehl[0];
2
temp=temp<<8+befehl[1];
3
temp=temp<<8+befehl[2];
4
//jetzt ist in temp der Ascii-Wert aller 3 Zeichen drin.
5
6
//Vergleichswert:
7
constuint32_tsetzen='s'<<16+'e'<<8+'t';
8
9
if(temp==setzen)
10
//tu was
- Nach dem Befehlauswerten weist du, was du mit wert 1 bis 3 machen
musst.
:-)
Ui, echter Spaghetticode :-)
sscanf() regelt. Ist vielleicht weniger performant, erleichtert die
Wart- und Lesbarkeit aber um Größenordnungen. Ach was sag ich, um
Dimensionen.
HTH
g457 schrieb:> Ui, echter Spaghetticode :-)>> sscanf() regelt. Ist vielleicht weniger performant, erleichtert die> Wart- und Lesbarkeit aber um Größenordnungen. Ach was sag ich, um> Dimensionen.>
Jup danke! sscanf() ist das, wonach ich gesucht ab. Aber so ganz klappt
das Auslesen der einzelnen Werte immer noch nicht. Ich hab jetzt mal nen
neuen Bsp.-Code ausprobiert. Die Trennung der einzelnen Abschnitte
erfolgt jetzt ueber Leerzeichen. Fuer ein erstes Versuchsstadium ist das
vollkommen ausreichend.
Blöd nur, es funktioniert nicht. Wenn ich mir das Array "befehl" und die
Variablen "befehl_1" - "befehl_3" debuggen lasse (darum die
volatile-Anweisungen) haben sie nach dem Aufruf von sscanf() folgende
Werte:
befehl[0] = '';
befehl[1] = 'v';
befehl_1 = 14;
befehl_2 = 0;
befehl_3 = 135;
sscanf_debug liefert aber korrekt = 4. Es wurden also 4 unterschiedliche
formatierte Werte eingelesen. Aber warum stimmt dann die Zuweisung an
meine Variablen nicht? Was mache ich im Umgang von sscanf() noch falsch?
uint8_t befehl[2] ist zu kurz, es müssen mindestens 3 Zeichen darin
Platz haben ("mv\0"). Die letzen 3 Argumente von sscanf müssen vom Typ
(int *) sein, damit sie mit %d eingelesen werden können. Das '&' vor
befehl in sscanf muss weg.
Wenn du mit -Wall die Compiler-Warnungen aktivierst, wirst du übrigens
auf einige dieser Fehler hingewiesen.
Servus :)
Yalu X. schrieb:> uint8_t befehl[2] ist zu kurz, es müssen mindestens 3 Zeichen darin> Platz haben ("mv\0").
Heißen Dank für den Hinweis. Klar muss noch Platz fuer die
0-Terminierung sein. Hab ich im Eifer des Gefechts vergessen. Jetzt geht
alles ^^.
> Die letzen 3 Argumente von sscanf müssen vom Typ (int *) sein, damit sie> mit %d eingelesen werden können. Das '&' vor befehl in sscanf muss weg.
Normalerweise nutze ich zur Variablen-Deklaration immer die absoluten
Angaben in (u)int($BIT_GROESSE)_t. Gut, gewoehne ich mir jetzt einfach
an, vorher in den Headern nachzuschauen, was die Funktionen als
Eingangsvariablen-Typ erwarten.
> Wenn du mit -Wall die Compiler-Warnungen aktivierst, wirst du übrigens> auf einige dieser Fehler hingewiesen.
Ich benutze AVR-GCC/AVR-Libc/AVR-Studio 4.18 als Entwicklungsumgebung.
Da ist das "-Wall" Flag fuer den GCC ja per default gesetzt. Bei meinem
Kauderwelsch hat der GCC auch ordentlich mukiert. Aber so richtig schlau
bin ich aus den Fehlermeldungen leider nicht geworden. Na ja, aber jetzt
scheint alles io zu sein :).
Dann habt mal ne schöne Arbeitswoche. Ich hab meinen Soll fürs WE dank
euch jedenfalls geschafft.
Armin S. schrieb:> Normalerweise nutze ich zur Variablen-Deklaration immer die absoluten> Angaben in (u)int($BIT_GROESSE)_t. Gut, gewoehne ich mir jetzt einfach> an, vorher in den Headern nachzuschauen, was die Funktionen als> Eingangsvariablen-Typ erwarten.
Du kannst mit *scanf theoretisch auch uint8_t und dergleichen einlesen.
Der offizielle Weg nach dem C99-Standard geht so:
Die SCNu8-Makros expandieren zu "hhu" (Format für unsigned char), oder
sollten es zumindest.
Leider wird das Makro in der AVR-Libc nicht definiert, da es dort in ein
1
#ifdef __avr_libc_does_not_implement_hh_in_scanf
2
...
3
#endif
eingeschlossen ist. __avr_libc_does_not_implement_hh_in_scanf ist
zurecht nicht definiert, da das *scanf der AVR-Libc das "hh"-Format laut
Dokumentation und Sourcecode sehr wohl implementiert.
Vielleicht schaut ja der Jörg Wunsch ¹ zufälligerweise vorbei und kann
uns sagen, was sich die AVR-Libc-Entwickler dabei gedacht haben.
Möglicherweise ist es ja ein Bug, und das #ifdef sollte durch ein
#ifndef ersetzt werden (zweimal).
¹) etwas lauter, damit er es im Vorbeigehen vielleicht hört :)