www.mikrocontroller.net

Forum: Codesammlung Einfacher Interpreter für Komandozeilen/Befehlszeilen


Autor: G. B. (garyb)
Datum:
Angehängte Dateien:

Hallo,

anbei ein kleiner aber feiner C-Code für einen einfachen Interpreter von
Komandozeilen. Vielleicht kann jemand so etwas ja brauchen.

Komandozeilen folgen dabei folgender Syntax...
Befehl Operand1, Operand2, ... Operand5<CR>

Kurz zur Funktion/den Eigenschaften:
Dem Interpreter werden über einen String die Kommandozeile oder deren
Bruchstücke übergeben. Bruchstücke? Nun ja bei mir war das Problem, dass
ich über die serielle Kopplung nicht immer alles auf einmal bekomme.

Die Bruchstücke werden bis zum Erkennen eines Zeilenendes, anhand des
Zeilenende-Zeichens, in einem internen Puffer zusammengesetzt. Das
Zeichen für Zeilenende ist einstellbar in cmd_interface.h über den
define _CMD_INTERFACE_EOL. Wird ein Zeilenende erkannt wird die Zeile an
die "Analyse-Abteilung" übergeben.

Erkennt die "Analyse-Abteilung" einen Befehl, so ruft der Code die dem
Befehl zugeordnete Funktion auf. Diese musste vorher nebst dem
Befehl-String über die Funktion
  cmd_interface_add_cmd("help", &command_0);
in den variablen Befehlsvorat eingetragen werden.

Der Rest ist hoffentlich selbsterklärend... der Code ist in einem
kleinen Beispiel für den AVR-Mega32 verpackt, aber grundsätlich sollte
es dem Code egal sein auf welcher Plattform er zum laufen kommt.

Wenns einem hilft ist schön... wenns einer besser kann... dann darf er
mich auch gerne belehren.

Viele Grüße, Gary
PS: Keine Haftung, Keine Garantie... das ist open-source.
Autor: GaryB (Gast)
Datum:

PS:
Backspaces als Zeichencode im String, werden auch als solche behandelt.
Trennzeichen für Operanden und Commando sind ebenfalls über defines
einstellbar.
Autor: doc (Gast)
Datum:

Grade mal überflogen.. was mir aufgefallen ist:

>register uint8_t ui_cnt=0;

"register" war vor gefühlten 100 Jahren mal wichtig..inzwischen machen
die Compiler das nach Gutdünken..und legen eh alles machbare in Register
:-)
Autor: G. B. (garyb)
Datum:

naja... Compiler-Magie ;O) das ist mal ne Ecke in die ich mich nur
begebe wenn ich wirklich muss... Bestaune immer die Werke von so manchen
"Freaks" die hier PreCompiler-Kunst vom Feinsten vom Stapel lassen.
Aber danke für den Hinweis... :)

Anmerkung: ich hab da aus Versehen noch eine "Testversion" des Beispiels
reingehangen, bei der die Befehle über direkt in ein Array gelegt
werden.
Der Schönheit wegen sollte man aber die obige Funktion
                 cmd_interface_add_cmd("help", &command_0);
nehmen.

Frohe Weihnachten

Gruss, Gary
Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

Ich hoffe du bist mir nicht böse, wenn ich ein wenig 'meckere':

Vermeide in String-Funktionen die Array-Sicht eines Strings, wie der
Teufel das Weihwasser.
In den meisten Fällen benötigt man die Array Sicht nicht und durch die
Indizierungsvariable handelt man sich eine Beschränkung auf irgendeine
Stringlänge ein, die man nicht haben will oder muss.

Sieh einen String so an:
Du hast einen Pointer auf den Anfang des Strings. Ist das Zeichen an der
Stelle des Pointers ein '\0' Zeichen, dann ist dort der String zu Ende.
In allen anderen Fällen kannst du den Pointer inkrementieren und kommst
so zum nächsten Zeichen des Strings.

Mit diesem Kochrezept kannst du die meisten String-Funktionen einfacher
formulieren und das bei gleichzeitig mehr Universialität, weil die
Indizierungsvariable gar nicht mehr auftaucht.

Deine Funktion
void l_trim(char *string, char sign) {
  register uint8_t   a_cnt=0;
  /* Search for the end of the string */
  while(string[a_cnt]!=0)  a_cnt++;
  /* search at the end for char which is not the same like the one in char sign */
  while (string[--a_cnt]==sign);
  string[++a_cnt]=0;  
}/* void l_trim(char *string, char sign) */
kann man in einer Pointer-Version zb so formulieren
void l_trim(char *string, char sign) {
  /* Search for the end of the string */
  while( *string++ )
    ;
  /* search at the end for char which is not the same like the one in char sign */
  while( *(--string) == sign )
    ;
  string++;
  *string = '\0';  
}/* void l_trim(char *string, char sign) */

-> Kein a_cnt mehr und damit auch keine Beschränkung auf eine maximale
Stringlänge von 255

(Ach ja. Das ist mir auch schon vor einiger Zeit aufgefallen.
Ein einzelnes Zeichen ist im Englischen ein character. Ein sign ist ein
Hinweisschild (zb beim Autofahren) bzw. in der Programmierung ist es das
Vorzeichen einer Zahl. Aber keinesfalls bezeichnet das englische 'sign'
in der Programmierung ein Zeichen aus einem String)


Hmm.
Du möchtest vielleicht auch noch darüber nachdenken, wo in einem String
'links' und 'rechts' ist und wie hier der Zusammenhang zwischen
'Beschneiden am Anfang' bzw. 'Beschneiden am Ende' lautet.
r_trimm/l_trimm macht das genau Gegenteil von dem was man erwarten
würde.


Und dann solltest du dir noch den Vorrat an Standardfunktionen ansehen.
Deine string_copy Funktion macht IMHO dasselbe wie strncpy. Auch bin ich
mir nicht sicher, ob man die String Splitterei nicht mittels strtok
eleganter und vor allen Dingen ohne notwendigen Zwischenspeicher
cmd_line_split_parts lösen könnte.
(Soll heißen: Ich bin mir ziemlich sicher, dass das mit strtok eleganter
geht, bin aber jetzt zu faul, das entsprechend auszuformulieren ohne dir
die Freude an der Sache zu nehmen)
Autor: Sven P. (haku) Benutzerseite
Datum:

Die Trim-Funktion könnte man recht einfach aus ltrim und rtrim
zusammenstecken.
Autor: G. B. (garyb)
Datum:

Hey warum soll ich sauer sein - ich meine dafür sind solche Foren da.
Man programmiert im stillen Kämmerlein, aber wenn man sich nich der
Kritik stellt... wird man nicht besser.

@Karl-Heinz
Das ist ein gutes Beispiel... diese Funktionen haben sich "ergeben" ich
hab die gebraucht, aber auf die Feinheit mit der Indizierungsvariable
nicht geachtet. Super "Kritik"

zum Rest... naja das mit dem links und rechts schreibe ich mal dem
Rotwein zu den ich beim Programieren getrunken habe ;O)
Das mit dem Englischen...  wie du an den restlichen Kommentaren siehst
bin ich mit eigentlich schon bewusst was ein character ist, aber
irgendwie muss man das Kind nennen.... dennoch vielen Dank.

strtok - kannte ich bisher noch nicht.. werd ich mir mal ansehen.

@Sven
Ist klar, aber ich hab es so gemacht, weil ich die Aufrufzeit sparen
wollte. Platz ist noch kein Problem. Ob es was bringt hab ich noch nicht
gemessen.

Ich verbessere das Ganze mal und stell ne neue Version rein.
Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

G. B. schrieb:

> @Sven
> Ist klar, aber ich hab es so gemacht, weil ich die Aufrufzeit sparen
> wollte.

Du bist hier ganz dicht am UI. Das heist an dieser Stelle bestimmt die
Übertragungszeit des Strings die Laufzeit. Die beiden Aufrufe fallen
absolut nicht ins Gewicht. Und selbst wenn der String irgendwo anders
herkommt: Alleine die Suchzeit des Schlüsselwortes in deiner
Commando-Tabelle ist höher als das bischen Funktions-Aufruf.
Nichts gegen Optimieren. Aber Optimieren dort, wo es sinnvoll ist.
Optimierungen, die darauf abzielen, im Maschinencode ein paar wenige
Befehle einzusparen, können zwar in manchen Situationen der Lebensretter
sein, sind es aber meistens nicht. Optimierungen auf algorithmischer
Ebene bringen da deutlich mehr. Zb. würde eine alphabetische Sortierung
der Schlüsselworte dir den Einsatz eines binären Suchens erlauben,
wodurch du bei genügender Anzahl an Schlüsselworten die Suchzeit
drastisch senken könntest. Anstelle O(n) hättest du dann O(log2(n)).
(qsort und bsearch aus der Standlibrary sind deine Freunde :-)

Und immer daran denken:
Premature optimization is the root of all evil


Ach da ist noch was und ich gebe zu: das ist auch ein wenig
Geschmackssache.
return ist kein Funktionsaufruf!

     return(ret_val);

Lass die Klammern weg. Sie bringen keine Klarheit und sind nur optischer
Ballast. Wenn beim return ein komplizierter arithmetischer Ausdruck
steht, können Klammern, wie bei jedem komplizierteren arithmetischen
Ausdruck, für Klarheit beim Verstehen der Berechnung sorgen, aber diesen
Fall hast du ja nicht.

  return ret_val;
Autor: G. B. (garyb)
Datum:
Angehängte Dateien:

Hallo Karl-Heinz,

so die Feiertage habe ich genutzt, um deine Anregungen einzuarbeiten und
das Beispielprogramm aufzupeppen.

Die Anregungen mit Ergebniss ....

a) Vermeidung von Index-Zugriffen auf String bei Trim-Funktionen.
   -> eingebaut und bringt sogar einen wesentliche Verbesserung der
      Laufzeit.
            neue Version mit Pointer  alte Version mit Index
     trim        19,13 µs            30,88 µs
     l_trim      17,75 µs            19,13 µs
     r_trim      14,94 µs            31,25 µs
     Testbedingung findest du als Kommentar am Ende der Datei
     string_addon.c
     Mit dieser Messung hat sich aber auch gezeigt das eine aus
     l_trim und r_trim zusammen gesetzte Funktion langsamer sein muss.
     Aber korrekter Weise nicht wegen der Aufrufzeiten, sondern wegen
     der separaten Bearbeitung ohne "Synergien".
     Aber deine Hinweise in Richtung Algorithmus ist sicher richtig,
     nur wenn die Basis nicht stimmt... macht auch ein Algorithmus
     nicht alles aus.

b) Verwendung von strtok bzw. Standard-Funktionen für die
String-Zerlegung
   -> Ich habe die Funktion split_command_line() umgeschrieben und das
      Ergebniss war wirklich "schöner", aber leider auch wesentlich
      langsamer [57 µs] als die Variante zu Fuss. Beides wurde schon mit
      der neuen Trim-Funktion gemessen.
      strtok version  218,75 µs
      alte version  160,94 µs
      Neue Funktion (mit define herausgenommen) und Ergenissen sind
      wieder am Ende der Datei command_interface.c zu finden.
      Den Code mit strtok hab ich der Einfachheit wegen unten angefügt.

Anhang - neue Versionen der split_command_line mit strtok
int8_t split_command_line(){
  int8_t  op_cnt    =0;  /* operand counter which shall not exceed cmd_interface_max_no_operands*/
  char  *c_str=NULL;
  char  c_separator[2];

  /* whipe previous results */
  for (op_cnt=0; op_cnt < _CMD_INTERFACE_MAX_NO_OPERANDS; op_cnt++) cmd_line_split_parts[op_cnt][0]=0; /* Set EOS */

  /* check for command */
  op_cnt=0;                       /* index for command */
  c_separator[0]=_CMD_INTERFACE_COMMAND_SPLIT_SIGN;  /* character used to split commands from operands */
  c_separator[1]=0;                  /* ÊOS */
  trim(c_ptr_cmd_line,_ASCII_SPACE);
  c_str= strtok(c_ptr_cmd_line, c_separator);

  /* check for operands and store command.. if one has been found */
  c_separator[0]=_CMD_INTERFACE_OPERAND_SPLIT_SIGN;  /* character used to split operands */
  while ( (NULL!=c_str) && (_CMD_INTERFACE_MAX_NO_OPERANDS>op_cnt) ){
    trim(c_str,_ASCII_SPACE);
    if (_CMD_INTERFACE_MAX_OPERAND_LENGTH>strlen(c_str)){
      strcpy(&cmd_line_split_parts[op_cnt++][0],c_str);
    }else  return -1; /* error - too many char per operand/command*/
    c_str= strtok(NULL, c_separator); /* strtok uses the last used string and position if NULL is forwarded */
  }/* while */

  /* check whether further operands would be available */
  if (NULL!=c_str)  {
    op_cnt = -2;    /* error - too many operands */
  }

  return op_cnt;
}/*inline int8_t execute_command()*/
Autor: G. B. (garyb)
Datum:

PS:
Deine Anregungen zum Suchen über Sortierung werde ich mir auch noch
einmal ansehen und schauen was es bringt.
Ich hoffe nun umgekehrt das meine Antworten nicht zu "peniebel"
(schreibt man das so ;O) erscheinen, aber ich war wirklich froh über
deine Anregungen. Ich versuch nur ein Gefühl für die Laufzeiten zu
bekommen, da ich derzeit mit der Ansteuerung von Motoren spiele
(Stepper/ BLDC).
Leider habe ich da noch wenig Gefühl für und deswegen meine Messungen.
Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

G. B. schrieb:

> b) Verwendung von strtok bzw. Standard-Funktionen für die
> String-Zerlegung
>    -> Ich habe die Funktion split_command_line() umgeschrieben und das
>       Ergebniss war wirklich "schöner", aber leider auch wesentlich
>       langsamer [57 µs] als die Variante zu Fuss. Beides wurde schon mit
>       der neuen Trim-Funktion gemessen.
>       strtok version  218,75 µs
>       alte version  160,94 µs

Du hast hier immer noch einen wesentlichen Fehler:
Du brauchst die Teilstrings nicht umkopieren!
Alles was du brauchst ist ein Array von char Pointern, das du dir von
strtok füllen lässt.

Im Prinzip so (ungetesteter Code)
char* cmd_line_split_parts[ _CMD_INTERFACE_MAX_NO_OPERANDS ];


int8_t split_command_line()
{
  int8_t op_cnt;
  char* c_str=NULL;
  char  c_separator[2] = { CMD_INTERFACE_COMMAND_SPLIT_SIGN, '\0' };

  for( op_cnt=0; op_cnt < _CMD_INTERFACE_MAX_NO_OPERANDS; op_cnt++ )
    cmd_line_split_parts[op_cnt] = NULL;

  op_cnt = 0;
  c_str= strtok( c_ptr_cmd_line, c_separator );

  while( c_str && op_cnt < _CMD_INTERFACE_MAX_NO_OPERANDS )
  {
    trim( c_str, _ASCII_SPACE );
    cmd_line_split_parts[op_cnt] = c_str;

    c_str = strtok( NULL, c_separator );
  }

  if( c_str )
    op_cnt = -2;    /* error - too many operands */

  return op_cnt;
}


Noch was
    if (_CMD_INTERFACE_MAX_OPERAND_LENGTH>strlen(c_str)){
      strcpy(&cmd_line_split_parts[op_cnt++][0],c_str);

das ist suboptimal. Sowohl strlen als auch strcpy durchlaufen den
String. Hier könntest du besser strncpy einsetzen. Aber wie gesagt: Wozu
eigentlich Teilstrings bilden. Braucht kein Mensch.
Autor: G. B. (garyb)
Datum:

Hallo Karl Heinz,

ich hatte das schon verstanden, aber mein Gedanke war das ich der
Anwender-Funktion diese Strings mit übergebe. Wenn ich das nicht durch
umkopieren mache, dann müsste ich in die String-Ende-Kennungen für die
einzelnen Sequenzen in die cmd_line direkt eintragen.
Im Grunde solte das aber gehen, weil der String mit der Kommandozeile
erhalten bleibt bis die Anwender-Funktion für ein Kommando abgearbeitet
ist.
Ich machs mal ... mal schauen ob es klappt.

Im Übrigen hat mir das Prinzip des Codes mit strtok so gut gefallen...
das ich weiter geschaut habe und meinen eigene strtok-funktion
geschrieben habe... damit wurde der Code sogar doppelt so schnell wie
die ursprünglich Version. Der Unterschied... weis ich nicht so genau...
meine Vermutung war
das die strtok-funktion aus dem Standard Speicher allokiert für das
Ergebnis und das dies die Funktion langsam macht... deswegen hab ich das
weg gelassen und 89µs anstatt 160µs erreicht.

Danke .. und melde mich wenn ich was Neues habe.

Gruss Gary
Autor: G. B. (garyb)
Datum:

PS: das
    if (_CMD_INTERFACE_MAX_OPERAND_LENGTH>strlen(c_str)){
      strcpy(&cmd_line_split_parts[op_cnt++][0],c_str);
diente alleine dem Zweck einen Indikator für die Fehlermeldung zu haben.
strncpy gibt mir ja keine Rückmeldung und schliest de kopierten String
auch nicht mit /0 ab.
Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

G. B. schrieb:

> ich hatte das schon verstanden, aber mein Gedanke war das ich der
> Anwender-Funktion diese Strings mit übergebe.

Kannst du ja.
Für die Funktion ist es doch völlig unerheblich, ob sie 5 Pointer in 5
Variablen bekommt, oder einen Pointer auf ein Array von Pointern. Und
wenn man möchte kann man den Funktionen immer noch die 5 Pointer aus dem
Array einzeln übergeben.

Schau dir doch einmal an, wie die Schnittstelle zu main() es macht, eine
beliebige Anzahl an String-Argumenten an main() zu übergeben. genau das
gleiche Prinzip

int main( int argc, char* argv[] )

argv ist ein Array von Pointern, wobei jeder Pointer auf einen String
zeigt.

Wird ein Program test.exe im Verzeichnis C:\tmp mit den Argumenten
"juhu.txt" "-o" "out.dat" gestartet, lautete die Commandline also

C:\tmp> test -o out.dat

dann sieht das argv, welches das Programm erhält, so aus

   argv
    +------+
    | o-------------> "C:\tmp\test.exe"
    +------+
    | o----------------------> "-o"
    +------+
    | o----------> "out.dat"
    +------+
    | NULL |
    +------+

Ob beim Programmnamen der Pfad dabei ist oder nicht, ist BS abhängig,
aber darum geht es nicht. Es geht um den Mechanismus, wie man eine
beliebige Anzahl an Argumenten geordnet an eine Funktion übergeben kann,
ohne dass die Funktion geändert werden muss, wenn sich diese Anzahl
ändert.

> Wenn ich das nicht durch
> umkopieren mache, dann müsste ich in die String-Ende-Kennungen für die
> einzelnen Sequenzen in die cmd_line direkt eintragen.

Genau das macht doch strtok sowieso.
Ansonsten könntest du ja auch nicht mit strlen auf die Einzelteile
losgehen.

> Im Grunde solte das aber gehen, weil der String mit der Kommandozeile
> erhalten bleibt bis die Anwender-Funktion für ein Kommando abgearbeitet
> ist.

Das ist ein Argument.
Aber auch das kann man leicht umgehen, indem man mit 2 Buffern arbeitet,
die wechselweise befüllt werden. Der jeweils gerade von der UART
befüllte wird mittels strtok in Einzelteile zerlegt, während der andere
die nächsten UART Zeichen aufnimmt. Ist die nächste Zeile komplett
empfangen, tauschen die beiden Buffer die Plätze.

> geschrieben habe... damit wurde der Code sogar doppelt so schnell wie
> die ursprünglich Version. Der Unterschied... weis ich nicht so genau...
> meine Vermutung war
> das die strtok-funktion aus dem Standard Speicher allokiert für das
> Ergebnis und das dies die Funktion langsam macht...

Nein.
strtok allokiert nichts.
strtok manipuliert den Ursprungsstring.
Es tauscht das Zeichen an der Trennstelle gegen ein '\0' Zeichen aus.

Aus dem String

 char buffer[] = "Hallo world";
 char* tmp = buffer;

 tmp
 +-----+
 | o   |
 +-|---+
   |
   |
   v
   +---+---+---+---+---+---+---+---+---+---+---+---+
   | H | a | l | l | o |   | w | o | r | l | d | \0|
   +---+---+---+---+---+---+---+---+---+---+---+---+

und einem Trennstring mit einem Leerzeichen, macht strtok

 tmp
 +-----+
 | o   |
 +-|---+
   |
   |
   v
   +---+---+---+---+---+---+---+---+---+---+---+---+
   | H | a | l | l | o | \0| w | o | r | l | d | \0|
   +---+---+---+---+---+---+---+---+---+---+---+---+
   ^                       ^
   |                       |
   |                       |

wobei du nacheinander die beiden unteren Pointer zurückbekommst.

Der komplette String wird also 'in-Place' zerlegt, und strtok gibt dir
Pointer auf die Einzelteile. Die Einzelteile sind (durch das \0) bereits
in einer Form, dass sie als gültige C-Strings benutzt werden können.
Nach wie vor stehen aber die einzelnen Strings an der selben Stelle, an
der sie auch vorher schon waren. Kein String wird umkopiert. Die Pointer
werden in einem Array von Pointern gesammelt

 tmp
 +-----+
 | o   |
 +-|---+
   |
   |
   v
   +---+---+---+---+---+---+---+---+---+---+---+---+
   | H | a | l | l | o | \0| w | o | r | l | d | \0|
   +---+---+---+---+---+---+---+---+---+---+---+---+
   ^                       ^
   |                       |
   |                       |
   +--------------+        |
                  |        |
                  |        |
   args           |        |
   +------+       |        |
   | o------------+        |
   +------+                |
   | o---------------------+
   +------+

und zusammen mit der Anzahl an die Funktion übergeben

   function( args, op_cnt )

Die Funktion selbst kann nicht mehr ohne weiteres feststellen, ob die
Pointer jetzt Zeiger in eigentlich nur einen einzigen String sind, oder
ob die Pointer auf jeweils einzeln allokierte Speicherbereiche zeigen.
Für die Funktion sind das einfach nur 2 Pointer, die jeweils auf den
Anfang eines C-Strings zeigen.

Der Unterschied in der Zeit wird sich wohl eher so erklären, dass das
originale strtok eine Sammlung von Auftrennzeichen behandeln kann (daher
auch der String als 2.tes Argument) während deine Funktion nur ein
einzelnes Trennzeichen benutzt.
Autor: Patrick Dohmen (oldbug) (Moderator) Benutzerseite
Datum:

Btw:
  _CMD_INTERFACE_MAX_OPERAND_LENGTH

Da Du gerade den Anschein machst, dass Du erst anfängst zu
programmieren:
Spar Dir führende Unterstriche!
Das ist den Entwicklern des Compilers und der lib vorbehalten.
Autor: G. B. (garyb)
Datum:

Hallo Karl-Heinz,

also Anregung aufgegriffen und umgesetzt....

Der Code übergibt nun nur noch Pointer -> nochmal bischen Zeit gespart.
Der Doppelpuffer ist bereits durch einen Ringpuffer gegeben, welchen ich
beim Empfangen der Daten benutze. Der ist aber nicht Bestandteil des
Teils des Kommando-Interfaces.

Bin ehrlich gesagt verblüfft von deiner umfangreichen Antwort... vielen
Dank das du dir soviel Mühe gibst, aber das Prinzip hatte ich schon
verstanden bzw. gekannt.

Hallo Patrick,

wuups... warum erweckt das den Anschein? Ich habe vor ca. 10-15 Jahren
das letzte mal ernsthaft in C programmiert. Mit Mikrocontrollern
"spiele" ich erst sein einem Jahr als Hobby und da erst über Assembler.
Mein C ist wirklich eingerostet, aber was ich mit Karl-Heinz diskutiert
habe ware ja der Algorithmus und nicht C.
Genau genommen kommt aus der "alten Zeit" auch dieser Umgang mit den
Defines.. sprich ich hab schon damals den Underscore benutzt... Wo gibt
es den solche "bestehenden" Festlegungen nachzulesen?

Gruss, Gary
Autor: G. B. (garyb)
Datum:
Angehängte Dateien:

so und nun die neue Variante als Anhang....

macht es eurer Meinung nach Sinn diese noch etwas weiter zu
dokumentieren mit Beispielen am Terminal - oder ist das zu trivial?

Ich hab hier halt viele Fragen dazu gelesen aber keine Umsetzung
gefunden.
Aus diesem Grund dachte ich ... vielleicht ist es von Interesse für den
einen oder anderen Forums-Besucher.

Gruss,
Gary
Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

G. B. schrieb:

> Genau genommen kommt aus der "alten Zeit" auch dieser Umgang mit den
> Defines.. sprich ich hab schon damals den Underscore benutzt... Wo gibt
> es den solche "bestehenden" Festlegungen nachzulesen?

Genau genommen: Im Dokument, welches die Sprachdefinition beschreibt.
Aber der ist sehr trocken geschrieben und beschriebt nur die normierte
Sprache. Einiges aus dieser Sprachdefinition hat Auswirkungen, die man
zwischen den Zeilen heruaslesen muss.
So zb, dass es für Makros eine Konvention gibt, nach der Makros die nur
aus Grossbuchstaben bestehen und mit einem _ anfangen, für die
'Implementierung' reserviert sind.
Hä? Was will uns der Dichter sagen?

Ganz einfach:
In C gibt es keine Namensräume, mit denen man Dinge verbergen könnte.
Wann immer du irgendein System-Header-File inkludierst, und das tut man
praktisch in jedem Programm, so kommen damit eine Menge #define durch
dieses Header File in deinen Source Code hinein. Was man also braucht,
ist eine Regelung, damit es zu keinen Namenskonflikten zwischen den
Makros aus dem System-Header-File und deinen eigenen #define kommt.
Nichts ist ärgerlicher, als wenn du ein #define machst und der Compiler
akzeptiert es nicht, weil es schon ein #define mit dem gleichen Namen in
irgendeinem System-Header-File gibt. Danach sucht man nämlich lange.
Daher diese Regelung: Alle in den System-Header-Files vorkommenden
Makros sind immer noch diesem Schema aufgebaut: zuerst ein _ und dann
alles andere in Grossbuchstaben (es gibt noch eine 2-te Konvention, aber
die ist nicht ganz so gebräuchlich, daher lasse ich sie hier der
Einfachheit halber weg).
Benutzt du daher selbst niemals Makronamen, die nicht mit einem _
anfangen, dann bist du auf der sicheren Seite: Es kann per Definition
keine Namenskollision mit Makros in System-Header-Files geben.
Sieh den _ einfach als eine Art Präfix an, der deinen 'Namensraum' vom
Namensraum der Implementierung trennt.
Autor: shell (Gast)
Datum:

Karl heinz Buchegger schrieb:
> Aus diesem Grund dachte ich ... vielleicht ist es von Interesse für den
> einen oder anderen Forums-Besucher.

Für mich wäre das sehr von Interesse.
Autor: G. B. (garyb)
Datum:

shell schrieb:
> Für mich wäre das sehr von Interesse.

Hallo shell, was fehlt dir in dem Beispielprojekt.
Wenn du mir sagst was du wissen möchtest... denn kann ich gerne darauf
eingehen.

Gruss,
Gary
Autor: Axel R. (axelr) Flattr this
Datum:

Hallo Gary,

bin dabei, mir Deinen Code anzusehen und zu verstehen.
Erstmal Danke für die Mühe, die sicher darin steckt.

ich möchte in diesem Thread keine Fragen zu Funktion an sich stellen,
nur auf eine Sache hinweisen, die mir eben beim compilieren auffiehl:

Den Ringpuffer kann man per defines in alle Einzelheiten konfiguriert
zerdröseln.
Warum hast Du das bei der USART nicht auch gemacht?
Ich habe einen Controller mit zwei USART und da geht der Code dann
leider nicht.
../AVR_CMD_INTERFACE.c:128: warning: 'USART_RXC_vect' appears to be a misspelled signal handler
../AVR_CMD_INTERFACE.c: In function 'avr_init_usart':
../AVR_CMD_INTERFACE.c:155: error: 'UCSRB' undeclared (first use in this function)
../AVR_CMD_INTERFACE.c:155: error: (Each undeclared identifier is reported only once
../AVR_CMD_INTERFACE.c:155: error: for each function it appears in.)
../AVR_CMD_INTERFACE.c:156: error: 'UCSRC' undeclared (first use in this function)
../AVR_CMD_INTERFACE.c:158: error: 'UBRRH' undeclared (first use in this function)
../AVR_CMD_INTERFACE.c:158: error: 'UBRRL' undeclared (first use in this function)
make: *** [AVR_CMD_INTERFACE.o] Error 1
Build failed with 6 errors and 1 warnings...

Es muss wohl irgentwie zwischen USART0 und USART1 unetrschieden
werden...

Ich bin interessierter, unerfahrener C-Hobbyist und komme aus der
Hardware-Ecke.
Magst Du die Defines in der usart.h entsprechend anpassen/erweitern,
damit man auch andere Controller verwenden kann? Hier ein Atmega128A -
dankeschön.
BTW:

ich bin hier
Beitrag "AT Kommandos / Befehlsliste verarbeiten"
nochmals allegemein auf die Problematik Kommandozeile und
Befelsauswertung eingegangen.

Frohe Pfingsten und

VIELEN DANK an Dich nochmal für die wirklich umfangreiche Vorlage
und an Karl Heinz für die geleistete Unterstützung.

Axelr.
Autor: thisamplifierisloud (Gast)
Datum:

Ohne in den DeTAILs abzutauchen sehe ich einen SEHR ordentlich
dokumentierten Code.

Kann man lesen wie in einem Buch !

Muß man doch auch mal lobenswert erwähnen.
Autor: Axel R. (axelr) Flattr this
Datum:

Das der Quelltext gut kommentiert ist, gefällt mir auch gut.

@Gary
mir fiel im Zuge der Durcharbeitung noch etwas auf:

im Ersten Ansatz hast Du "_AVR_USART_UBRR_CALC" als Baudratenkonstante
errechnen lassen, verwendest aber zur eigentlichen Initialisierung der
USART gar nicht den Code aus der USART.c, sondern einen eigenen
Startup-Code, in dem die errechnete Konstante "_AVR_USART_UBRR_CALC"
keine Rolle mehr spielt.

Fiel mir auf, als ich auf 115200 umstellen wollte.
/* Internal Defines
****************************************************************************/
  #ifndef F_CPU
    #error " _AVR_USART_H: F_CPU has not been defined -> please check your project-settings"
  #endif
  #ifndef _XTAL
    #define _XTAL    F_CPU+'UL'
    /* Taktfrequenz des Prozessors in Hz
       >> Vorsicht mus unsigned long sein sonst kommt es zu fehlerhaften Baudraten Berechnung */
  #endif
...
...
  /**  @details  Makro calculating required content of the USART Baud Rate Register (UBRR) in "SINGLE SPEED MODE"
  */
  #define _AVR_USART_UBRR_CALC(_BAUD) (((_XTAL)/((_BAUD)*16UL))-1)

  void avr_usart_init_9600_8_1_N_Asyn( void )  {
    /* Set UART-Control-Registers 
                    \todo this code is a little bit smaller without those macros 
    */
      _AVR_USART_ENABLE_TRANSMIT();
      _AVR_USART_ENABLE_RECEIVE();
      _AVR_USART_DISABLE_RECV_IRQ();
      _AVR_USART_DISABLE_TRANS_IRQ();
      _AVR_USART_MODE_SYNCHRONOUS();
      _AVR_USART_SET_PARITY_NONE();
      _AVR_USART_SET_DATABITS_TO_8();
      _AVR_USART_SET_STOPPBITS_1();
      _AVR_USART_SAMPLE_AT_POS_EDGE();      

    /* Set Baudrate  */
      UBRRH = (uint8_t)(_AVR_USART_UBRR_CALC(9600)>>8); /*9600UL 19200UL 57600UL 115200UL*/
      UBRRL = (uint8_t) _AVR_USART_UBRR_CALC(9600);
  } /* void init_usart( void ) */


hier Deine eigne Routine, in der UBBR mit einem festen Wert beschrieben
wird. Welchen Hintergrund hatte diese Entscheidung?
void avr_init_usart( void )  {
  /* Set UART-Control-Registers - 9600,8,1,none  */
    UCSRB = ( 1 << TXEN )|( 1 << RXEN )|( 1 << RXCIE )|( 0 << TXCIE )|(0<<UCSZ2);
    UCSRC = ( 1<<UMSEL )|( 1<<UCSZ1 )|( 1<<UCSZ0 );
  /* Set Baudrate  */
    UBRRH = 0x00; UBRRL = 0x67;
} /* void init_usart( void ) */



Danke und viele Grüße
Axelr.
Autor: G. B. (garyb)
Datum:

@ thisamplifierisloud : danke ;)
Autor: G. B. (garyb)
Datum:

Hallo Axel

> Den Ringpuffer kann man per defines in alle Einzelheiten konfiguriert
> zerdröseln.
> Warum hast Du das bei der USART nicht auch gemacht?
> Ich habe einen Controller mit zwei USART und da geht der Code dann
> leider nicht.
Genau wegen sowas - ich hab das am Anfang probiert, aber wenn du das
versuchst wirklich "plattform-neutral" zu halten ist das mehr Aufwand
als ab und an ins Handbuch zu schauen. Bei meinen ersten Versuchen hat
das ein Gewitter von defines gegeben, das nicht mehr lesbar war.

> Magst Du die Defines in der usart.h entsprechend anpassen/erweitern,
> damit man auch andere Controller verwenden kann? Hier ein Atmega128A -
> dankeschön.
Welchen der beiden USARTs willst du benutzen?
Autor: G. B. (garyb)
Datum:

> im Ersten Ansatz hast Du "_AVR_USART_UBRR_CALC" als Baudratenkonstante
> errechnen lassen, verwendest aber zur eigentlichen Initialisierung der
> USART gar nicht den Code aus der USART.c, sondern einen eigenen
> Startup-Code, in dem die errechnete Konstante "_AVR_USART_UBRR_CALC"
> keine Rolle mehr spielt.

Ja ich weis ;) wie so oft im Leben - es hat keinen .. ich hab hier
gespielt und das zieht sich nun schon seit einiger Zeit via Copy und
Paste mit.
Sorry das dies nun in dem Beispiel gelandet ist.
Wie ich oben schon geschrieben habe ursprünglich war das mal eine
USART-Datei die auch via Defines mehr oder weniger einstellbar war, nach
einer Aufräumaktion ist das übrig geblieben. Ich konnte mich nie
aufraffen das Energie zu investieren.


Gruss,
Gary
Autor: G. B. (garyb)
Datum:

Auf deine Punkte zu meinem Code aus dem anderen Beitrag antworte ich
auch mal lieber hier um alles beieinander zu halten
Axel Rühl schrieb im Beitrag #1719684:

> Leider sind hier verschiedene Punkte für mich unverständlich, da hier
> ganz viel Pointerrechnerei betrieben wurde und viele vorhandene
> String-Funktionen selbst geschrieben wurde und ich bei denen leider mit
> meinen bescheidenen C-Kenntnissen nicht wirklich durchsteige.

String-Funktionen wurde nur eine neu geschrieben strtok - Grund war
einfach weil meine Funktion etwas schneller ist. Alle anderen wie die
TRIM Funktionen gibt es so nicht - zumindes nicht das ich es wüsste...
lasse mich gerne eine Besseren belehren.

> Ein ganz simples Beispiel aus dem Thread:
 int8_t execute_cmd_interface(char *string){
   /* local variables */
   register uint8_t  ui8_cnt  =0;
   register char    act_char  =1;
   register int8_t    cmd_ret_val  =0;
   register uint8_t  cmd_count  =0;
   int8_t         ret_val    =0;
   /* start reading string */
   act_char=string[ui8_cnt++];  /* Read next sign */
 
 
> Speziell /* Read next sign */
>
> um welches Vorzeichen handelt es sich? Ich dachte char ist deshalb
> Vorzeichen behaftet, weil die Druckbaren Zeich im 7Bit Bereich liegen,
> also bis 127 gehen.
>
Das ist genau die Stelle an der nicht mit Pointern gearbeitet wird ;)
Hier wird nur das erste Zeichen aus dem Übergebenen String in die
Variable act_char übergeben. Ich erlaube mir nun zu vermuten das du aber
die Operation string[ui8_cnt++] nicht verstehst. Deswegen kurz erklärt
.. und verzeih mir wenn ich falsch gegelegen haben sollte.
string ist ein Pointer = Adresse auf den STring im Speicher
C erlaubt es dazu einen Offset anzugeben in Form eines Index in eckigen
Klammern. Der Compiler berechnet daraus die Adresse des indizierten
Elementes.... Adresse aus dem Pointer + Index * Byte des Datentyps (hier
char = 1 Byte)

Deswegen ist diese Zeile auch ein bischen "hoch gegriffen" - wieder mal
Copy und Paste - weil eigentlich hätte man es auch so - einfach -
schreiben können.
   act_char=*string; /* Zuweisung erstes Zeichen über Indirektion * */
   ui8_cnt=1;  
 
 

> So wird auch viel hin-und herkopiert -k.A. ;)
Nicht das ich wüsste ;) es wird mit Pointern gearbeitet.

Gruss,
Gary
Autor: Axel R. (axelr) Flattr this
Datum:

Hallo Gary,

ich lass dein Beispiel erstmal so in einem meiner Unterverzeichnisse
stehen.

Mir ist das schlicht zu kompliziert und viel zu viel - sry.

Ich dachte, man kann einen String einfacher in Teilstrings zerlegen und
dann über ein Array auf die einzelnen gewonnenen Teilstrings zugreifen
und...

Nocheinmal herzichen Dank für die geleistete Unterstützung!

Viele Grüße
Axelr.
Autor: G. B. (garyb)
Datum:

Hallo Axel,

klar kannst du den String einfach nur zerlegen, wenn du ihn den einmal
vollständig hast. Letztlich macht mein Beispiel nichts anderes. Der Rest
ist im Prinzip nur Verwaltung und Zusammenstellen des Strings bis eine
gültige Endekennung (CR) gelesen wird.

Schau dir einfach die Funktion split_command_line() bzw.
strtrok_single_char im string_addon.c an. Die erlauben dir genau das.

Also viel Erfolg.

Gary
Autor: frist (Gast)
Datum:

Hallo Gerhard,
ich werde mir Deinen Interpreter mal anschauen, ich glaube ich kann
genau so was gerade brauchen. Vielen Dank fürs Veröffentlichen. Nur eine
Frage: Gibt's eine aktualisierte Version?

Viele Grüße
Flo
Autor: Rene B. (themason) Benutzerseite
Datum:

Wird zwar nicht so gern gesehen, aber ich entführe den Thread mal eben
um Werbung für meinen Kommandozeilen Interpreter zu machen.

Zu finden unter hier :

Beitrag "uParse - ein kompakter und vielseitiger Parser"

Bei meinem Parser ist die Syntax frei wählbar (also nicht beschränkt af
Befehl Op1, Op2, Op3, Op4, Op5), ebenso die Anzahl der maximal möglichen
übergebenen Parameter ist einstellbar.

Ansonsten : Gute Arbeit Gary !
Autor: G. B. (garyb)
Datum:

frist schrieb:
> Hallo Gerhard,
> ich werde mir Deinen Interpreter mal anschauen, ich glaube ich kann
> genau so was gerade brauchen. Vielen Dank fürs Veröffentlichen. Nur eine
> Frage: Gibt's eine aktualisierte Version?
>
> Viele Grüße
> Flo

Hallo Flo - die hier eingestellte Version vom 01.01.2010 - ist auch noch
die aktuelle.

Rene Böllhoff schrieb:
> Bei meinem Parser ist die Syntax frei wählbar (also nicht beschränkt af
> Befehl Op1, Op2, Op3, Op4, Op5), ebenso die Anzahl der maximal möglichen
> übergebenen Parameter ist einstellbar.

Hallo Rene - bei meinem auch ;O) - Danke.

Gruss,
Gary
Autor: frist (Gast)
Datum:

Hallo  G. B.

> die hier eingestellte Version vom 01.01.2010 - ist auch noch
> die aktuelle.

OK. Danke. Ich hatte gehofft, Du hättest vielleicht die UART
Initialisierung geändert, aber das ist eh nur eine Kleinigkeit.

@Rene

Ich werd' Deinen auch probieren. :-)

Grüße
Flo
Autor: G. B. (garyb)
Datum:

Hallo Flo,

komisch das mit der UART-Initialisierung scheint jetzt wohl mehr Leute
zu stören, dabei ist der UART ja nur eine mögliche Quelle für Zeichen.

Aber wie ich es schon geschrieben hatte... die ist "geworden", wenn du
eine variablere Variante hast..  her damit.. oder noch besser bau Sie
gleich ein.

Möge das bessere Progi gewinnen ;O)

Grüße,
Gerhard
Autor: frist (Gast)
Datum:

Hallo

> komisch das mit der UART-Initialisierung scheint jetzt wohl mehr
> Leute zu stören,

Ich hab Deine Interpreter schon vor einiger Zeit mal kurz ausprobiert
und da hat mich die UART Initialisierung etwas Nerven gekostet. Ich hab
nämlich den Makefile an meine Taktfrequenz angepasst und dann nur kurz
in den Code geschaut und gedacht UBRR wird automatisch neu berechnet und
richtig gesetzt - war aber nicht so, falsche Baudrate. Dann hab ich die
Makros, die das vermeintlich machen angeschaut und konnte da keinen
grundsätzlichen  Fehler finden und erst später bin ich drauf gekommen,
dass die gar nicht verwendet werden sondern UBRR hart codiert auf einen
Wert gesetzt wird. Ich denke anderen wird's beim Lesen ähnlich gegangen
sein. das hat ja alles nicht wirklich was mit dem Kern der Geschichte,
dem Interpreter zu tun.

Grüße
Flo
Autor: garyb (Gast)
Datum:

Hallo Flo,
danke für das Feedback - das verstehe ich und nix ist ärgerlicher
als ein Beispiel was nicht sofort funktioniert.
Ich passe das an in den nächsten Tagen an.

Gruss,
Gary
Autor: Rene B. (themason) Benutzerseite
Datum:

@frist

Viel Spaß beim probieren. Falls es Probleme gibt, du weißt ja wo der
Thread zu finden ist :))

@gary

>und nix ist ärgerlicher als ein Beispiel was nicht sofort funktioniert.

Ist mir bei meinem Parser auch passiert. Der wollte keine 0 mehr
verstehen :-)
Autor: G. B. (garyb)
Datum:
Angehängte Dateien:

Hallo - wie gewünscht das Update mit kleiner Verbesserung bei
AVR_UART.h.

Gruss,
Gary
Autor: Sebastian M. (sebastian_m)
Datum:

habs mir mal angeschaut, es tut was es soll

vielen Dank!!

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel




Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder GIF-Format hochladen.
Siehe Bildformate
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken erkennst du die Nutzungsbedingungen an.

webmaster@mikrocontroller.netImpressumNutzungsbedingungenWerbung auf Mikrocontroller.net