Forum: Mikrocontroller und Digitale Elektronik 20 Variablen unterschiedlichen Typs seriell einlesen


von Ich B. (ichbin)


Lesenswert?

Ok, wieder ein Problem, das einer Lösung bedarf:

Ich möchte nacheinander etwa 20 Variablen füllen, über die serielle
Schnittstelle. Ich verwende Peter Fleurys UART-Lib, und stehe bei 82%
von insgesamt 2K Flash.

Falls möglich, ziehe ich ein lesbares Format vor, d.h. ASCII
Klartextwerte kommen an.

Dann muß ich mir also eine Funktion schreiben, die einzelne Zeichen in
einen Puffer schreibt, bis z.B. ein "\r" kommt.
Anschließend das Ganze mit atoi() in einen Integerwert umwandeln und in
die Variable schreiben (kommt das eigentlich mit signed/unsigned und
bytes/words gleichermaßen klar?).

Was mir nicht klar ist: wie kann ich das für 20 unterschiedliche
Variablen (Typ, Name) effizient gestalten?

Oder mache ich das Naheliegende:

switch()

case 1:
    diesevariable = einlesen();
...
case 20:
    neganzandere = einlesen()

In Peter Danneggers 'Command Interpreter' für C51 ist mir nicht ganz
klar, wie dann die Funktionen aufgerufen werden. .. aber das ist schon
fast ein anderes Thema..

von Gast ein Anderer (Gast)


Lesenswert?

Struct ?? Damit könntest du deine Variablen in einer zusammenfassen und
als Block verschicken. Ist aber nicht ascii.

von Ich B. (ichbin)


Lesenswert?

Nee, das soll einzeln erfolgen..

Also z.B. so:

for i=1 to 20
   variable(i) = einlesen();

Und dann noch ein Array, so das in irgendeiner Form auf meine 20
Variablen verwiesen werden kann.
Aber ob das effizienter ist, falls es denn irgendwie geht?
In PHP kann man Code in Stringform ausführen, da sind dann auch
Variablennamen kein Problem.

Aber wie gesagt, das sind alles unterschiedliche Typen und Namen, kein
Array..

von Rahul (Gast)


Lesenswert?

Ne Union...
Da kannst du ein Array "reintun" und deine 20 Variablen.
Allerdings wäre das kein ASCII mehr...
ASCII bzw. lesbare Zeichen benutze ich meistens nur zu Anfang, um es
beim Debuggen einfacher zu haben, oder ich mir das Protkoll selber
ausdenken darf...

von Ich B. (ichbin)


Lesenswert?

Ich möchte eigentlich nicht die ganze Union auf einen Schlag einlesen,
sondern jede Variable einzeln, um verschiedene Parameter zu setzen.
Aber da das ganze 20 mal geschehen soll, hoffe ich noch immer, daß es
irgendeinen Trick gibt.

Parameter wird ausgegeben.
Wenn nur ein /r hereinkommt, soll sich nichts ändern.
Wenn jetzt 1-3 Bytes hereinkommen, und ein /r soll der Wert gesetzt
werden.

Wenn man Platz hätte, und verschiedene Typen in ein Array einbauen
könnte, wär's ja kein Problem. Dann könnte man einfach durch das Array
zählen.
Aber ich weiß nicht, ob es in C die Möglichkeit zu soetwas gibt..

von peter dannegger (Gast)


Lesenswert?

Ich würde es als Text machen:

SET 0 12345\n
SET 1 20000\n
SET 2 0\n

usw.

Das 1. Argument ist dann der Speicherplatz und das 2. dann der Wert.


Peter

von Karl H. (kbuchegg)


Lesenswert?

> Was mir nicht klar ist: wie kann ich das für 20 unterschiedliche
> Variablen (Typ, Name) effizient gestalten?

Nun ja. In der Praxis hast Du ja nicht sooo viele verschiedene
Datentypen.
Ich wuerde mal damit anfangen, Funktionen zu schreiben die
einen int, char, float und was du sonst noch so brauchst empfangen
koennen (jeweils in Textform mit einem geeignetem Ende-Zeichen)
und diesen String dann in das gewuenschte Datenformat umsetzen.
Wahrscheinlich wirst du eine zentrale 'Empfange als String' Funktion
haben und dieser String wird dann in weitere Funktionen zur
Umwandlung uebergeben.

Soweit, so gut.
Was natuerlich ebenfalls mit ueber die Schnittstelle kommen muss,
ist ein Kennzeichen welche Variable du setzen moechtest. Da wuerde
ich pragmatisch vorgehen und jeder Variable einfach einen Buchstaben
verpassen. Ein 'Telegram' vom Host sieht dann zb so aus:

A200

und das bedeutet: setzte den Wert fuer Timer-Tick-Count auf 200.

Wichtig: Die Buchstaben sequentiall benutzen, nach 'A' also 'B'.
Danach 'C' usw. Damit kannst Du eine Tabelle aufbauen und den
Buchstaben nach geeigneter Transformation ('A' abziehen) als
Index in die Tabelle nehmen.

In der Tabelle gibt es dann einen Funktionszeiger auf die,
fuer diese Variable zustaendige Umwandlungsfunktion + einen
Pointer auf die tatsaechliche Variable, deren Wert von
der Umwandlungsfunktion gesetzt werden soll:

   0:   &KonvertToInt     &TimerTickCount
   1:   &KonvertToInt     &PwmHigh
   2:   &KonvertToInt     &PwmLow
   3:   &KonvertToDouble  &UmrechnungsFaktor

Du wirst ein bischen casten muessen, denn letztendlich bleibt
Dir nichts anderes uebrig als alles als void Pointer in dieser
Tabelle abzulegen (keine gemeinsame Datentypen)

OK. Sagen wir mal Du erhaelts als Telegram
D345.8

Der erste Buchstabe wird abgesplittet: 'D'. Davon 'A' abgezogen
ergibt 3. Also ist die Zeile 3: zustaendig.
Du rufst also indirekt, ueber den Pointer, die Funktion
KonvertToDouble auf. Dieser Funktion (wie allen anderen
Konvertierfunktionen auch), uebergibst Du den restlichen Text
aus dem Telegram, sowie den void Pointer aus der Tabelle.

Effektiv muendet das darin, dass

    KonvertToDouble( "345.8", &Umrechnungsfaktor )

aufgerufen wird. Nun in der Funktion KonvertToDouble
wandelst Du den String nach double (zb. mit atof()), castest
dir den void Pointer zu einem double Pointer und machst
die Zuweisung.

   void KonvertToDouble( char* String, void* pWhere )
   {
     (double*)pWhere = atof( String );
   }

Fuer die Tabelle empfehle ich mal einen struct:

typedef void (*pFnct)( char*, void* );

struct ConvertEntry
{
  pFnct  Function;
  void*  pVariable;
};

Daraus dann ein Array aufbauen

ConvertEntry Conversions[] =
   { { KonvertToInt,    (void*)&TimerTickCount },
     { KonvertToInt,    (void*)&PwmHigh },
     { KonvertToInt,    (void*)&PwmLow },
     { KonvertToDouble, (viod*)&PemHigh } };

Die Auswertung geht dann so:

   char* Telegram = UART_Receive(); /* oder wie auch immer */
   char FirstChar = *Telegram;
   char* Message = Telegram + 1;

   int Which = FirstChar - 'A';
   /* Hier sollte noch Fehlerabfrage rein */
   Conversions[Which].Function( Message,
                                Conversions[Which].pVariable );

und das sollte es eigentlich sein.

Du kannst das ganze natuerlich noch etwas komplizierter machen,
indem Du anstatt eines Kennbuchstabens einen laengeren String
benutzt um anzugeben welche Variable gesetzt werden soll, aber
das sind dann schon Details, das Prinzip ist dasselbe.

von Ich B. (ichbin)


Lesenswert?

Vielen Dank! Mit Funktionszeigern habe ich noch keine Erfahrung. Das
wird sich ja jetzt ändern :-)

von Karl H. (kbuchegg)


Lesenswert?

Ist nicht so schlimm.
Die Syntax ist am Anfang gewöhnungsbedürftig.
Ansonsten sind die Dinger simpel.

von Ich B. (ichbin)


Lesenswert?

Die Übertragung als Klartext kann ich vergessen. mit ltoa() und atol()
bin ich bei 137 Prozent Flash .. :-)

von Ich B. (ichbin)


Lesenswert?

Nochmals danke für Deine tolle Erklärung!

Ist es auch möglich, den Typ einer über einen void-pointer
referenzierten (?) Variablen zu ermitteln, und in Abhängigkeit davon zu
casten?

Denn dann wäre es doch möglich, die pointer auf meine n Variablen
unterschiedlichen Typs in einem Array abzulegen, und durch dieses Array
durchzulaufen

Hier mal meine Funktion, die momentan nur Müll liefert (das ist nicht
weiter verwunderlich, wenn man meinen Kenntnisstand bedenkt)..
Ich rufe sie mit

parameters((unsigned int *)stepper_speed);

periodisch auf, stepper_speed ist volatile uint8_t und hat den Wert
170.
Nach einem Durchlauf gibt sie ständig dezimal 170 aus, anstatt einmal
den eingegebenen Wert auszugeben und dann wieder auf eine Eingabe zu
warten.

void parameters(void *ptr) {
  static uint8_t status;
  // get current value once
  if(!status) {
    // read varible referred to by pointer and convert
    x.u32 = *((unsigned long *) &ptr);
    // write single bytes of x.u32 to string
    for(i=0; i < 2; i++)
      buffer[i]=x.u8[i];
    buffer[i+1]=0;
    // send string to uart
    uart_puts(buffer);
    // next time, check incoming data
    status=1;
  }
  else {
    // read bytes from uart until CR
    i=0;

    if((temp=uart_getc()) && temp != CR) {
      buffer[i]=temp;
      i++;
    }
    else if(temp == CR) {

      buffer[i+1]=0;
      *(int *)ptr = *((unsigned long *)&buffer[0]);

      uart_putc(0);
      uart_putc(CR);
      LED_PORT ^= 1<<LEDg | 1<<LEDr;
      status=0;
    }
  } // else
} // function


Obwohl ich nicht ganz genau weiß, was ich hier mache, würde ich mich
über weitere lehrreiche Kommentare sehr freuen!

von Ich B. (ichbin)


Lesenswert?

Ach so: ich benutze

  union {
    uint32_t u32;
    uint8_t u8[4];
  } x;

um auf die einzelnen Bytes meiner Werte zugreifen zu können..

von Karl H. (kbuchegg)


Lesenswert?

> Ist es auch möglich, den Typ einer über einen void-pointer
> referenzierten (?) Variablen zu ermitteln, und in Abhängigkeit
> davon zu casten?

Nein. Das geht in C nicht.

Ich hab den Code nur mal schnell ueberflogen

>   if((temp=uart_getc()) && temp != CR) {
>      buffer[i]=temp;
>      i++;
>    }
>    else if(temp == CR) {
>
>      buffer[i+1]=0;

Da ist ein Fehler. i enthaelt ja bereits den Index an dem
der naechste Character abzulegen ist.

       buffer[i] = 0;

Obwohl, das ist momentan nicht dein Problem.

>    x.u32 = *((unsigned long *) &ptr);
>    // write single bytes of x.u32 to string
>    for(i=0; i < 2; i++)
>      buffer[i]=x.u8[i];
>    buffer[i+1]=0;
>    // send string to uart
>    uart_puts(buffer);

Das das nur Müll ueber die Serielle schickt, wundert mich
nicht. Du kannst nicht die einzelnen Bytes eines unsigned
long einfach so textuell auf die Reise schicken. (Ich geh
mal davon aus, dass Du immer noch textuell uebertragen moechtest).
Du musst schon aus den Bytes schoene Strings machen, damit das
geht.

von Ich B. (ichbin)


Lesenswert?

Danke für Deine Hilfe .. ich sende nicht Ascii, sondern 'raw'
byteweise. Die Endlosschleife existiert nicht mehr, und es wird der
richtige Wert ausgegeben.
Ich habe der Einfachkeit halber die Funktion auf einen Datentypen
beschränkt, aber es funktioniert noch nicht, den Wert neu zu setzen.
Ich verstehe leider nicht, wieso das nicht funktioniert.

Die betreffende Zeile:
  *(unsigned char *)ptr = (unsigned char )buffer[0];
buffer[0] enthält das zuletzt empfangene Zeichen, und der Void-Pointer
ptr die Adresse von stepper_speed. Ich dereferenziere, damit sollte das
Byte aus buffer[0] in stepper_speed geschrieben werden. Fast richtig?

volatile uint8_t stepper_speed;


Aufruf:
            parameters(&stepper_speed);

Funktion:

void parameters(void *ptr) {
  static uint8_t status;
  // get current value once
  if(!status) {

    uart_putc(*(unsigned char *)ptr);
    // read varible referred to by pointer and convert
    x.u32 = *(unsigned long *)ptr;
    // write single bytes of x.u32 to string
    for(i=0; i < 2; i++)
      buffer[i]=x.u8[i];
    buffer[i+1]=0;
    // send string to uart
    uart_puts(buffer);
    // next time, check incoming data
    status=1;
  }
  else {
    // read bytes from uart until CR
    i=0;

    if((temp=uart_getc()) && temp != CR) {
      buffer[i]=temp;
      i++;
    }
    else if(temp == CR) {

      buffer[i]=0;
      *(unsigned char *)ptr = (unsigned char )buffer[0];

      uart_putc(0);
      uart_putc(CR);
      LED_PORT ^= 1<<LEDg | 1<<LEDr;
      status=0;
    }
  } // else
} // function

von Karl H. (kbuchegg)


Lesenswert?

> *(unsigned char *)ptr = (unsigned char )buffer[0];

Das ist schon OK.

Aber, sag mal. Was ist mit den restlichen Bytes?

Hier:
>    x.u32 = *(unsigned long *)ptr;
>    // write single bytes of x.u32 to string
>    for(i=0; i < 2; i++)
>      buffer[i]=x.u8[i];
>    buffer[i+1]=0;

Wieso 2?
Ich denke ein unsigned long hat 4 Bytes auf Deiner
Maschine?

und hier:

>    else if(temp == CR) {
>
>     buffer[i]=0;
>      *(unsigned char *)ptr = (unsigned char )buffer[0];

Da wird ueberhaupt nur 1 Byte aus dem Buffer abgeholt.
Wenn die Bytes in buffer schon in der richtigen Reihenfolge
sind, dann kannst Du das zb. so machen:

       *(unsigned long*)Ptr = *(unsigned long*)buffer;

und die Schleife da oben, schreibst Du besser so:

    for(i=0; i < sizeof( unsigned long); i++)
      buffer[i]=x.u8[i];

(Also: Anstatt der fest kodierten 2 oder 4 einen sizeof Ausdruck
benutzen. Dann brauchst Du Dir nicht den Kopf zerbrechen, ueber
wieviele Bytes denn hier die Rede ist.

Das hier sieht auch seltsam aus:

    uart_putc(*(unsigned char *)ptr);
    // read varible referred to by pointer and convert
    x.u32 = *(unsigned long *)ptr;

Zuerst schickst Du das erste Byte auf das ptr zeigt auf die
Reise, und dann schickst Du das ganze nochmal wobei Du dann
alle 4 (2) Bytes schickst auf die ptr zeigt.

Worauf zeigt den ptr eigentlich?

Die Schleife kannst Du auch ersetzen durch:

    *(unsigned long*)buffer = *(unsigned long *)ptr;
    buffer[ sizeof( unsigned long) ] = 0;
    uart_puts( buffer );

dann erledigt Dir der Compiler die ganze Kopiererei, ganz
analog zum Code im empfangen.


Generell: Wenn man solche Uebertragungsfunktionen schreibt, dann
ist es wichtig entweder:
  * daneben einen Zettel liegen haben und mal im Kopf das Programm
    Zeile fuer Zeile durchgehen und exakt mitschreiben wie sich
    Variablen veraendern und in welcher Reihenfolge welches Byte
    auf die Leitung geschickt wird.

  * oder man kann sich das Leben etwas leichter machen, indem man
    den Code im Debugger durchsteppt und jedes einzelne Byte
    untersucht ob es in der richtigen Reihenfolge geschickt/empfangen
    wird und richtig verarbeitet wird.

Normalerweise braucht man da mehrere Anlauefe. Ganz wichtig: Immer
mit denselben Zahlen arbeiten. Nicht einfach Zufallszahlen in
die Routine schicken, sondern immer denselben Zahlenwert. Nur so
hast Du beim Debuggen eine Chance. Erst wenn das geht, nimmt man
andere Zahlen. Auch wichtig: Wenn Du denkst, dass es geht nimm
auch mal grosse Zahlen (in einen unsigned long geht ne Menge rein).

Bevor Du mit einer Zahl durch die Funktion durchsteppst, solltest
Du eine genaue Vorstellung davon haben, was eigentlich passieren
muesste. Also wenn Du die Zahl nimmst:
1744320800

dann solltest Du vorher schon wissen, dass da, in dieser Reihenfolge,
die Bytes 67 F8 39 20 ueber die Leitung gehen. Dann steppt man
die Funktion durch und verfolgt, ob die Funktion das auch wirklich
so macht. Wenn da in der Uebertragung ganz andere Bytes auftauchen,
dann muss man dem nachgehen wo die herkommen. Wenn die Zahlenwerte
zwar stimmen, aber die Reihenfolge eine andere ist, dann ist
wahrscheinlich Ending (Bytereihenfolge in int oder long) anders
als du angenommen hast. Dann muss man auch auf der Gegenstelle
pruefen, ob das so in Ordnung ist.
Summa, summarum:
* Bei binaerer Uebertragung gibt es eine Menge Stolpersteine.
* Nie davon ausgehen, dass Du schon ein korrektes Pgm schreibst,
  sondern pruefen, pruefen, pruefen. Das Hilfsmittel dazu ist
  der Debugger
* Schon vorher eine Hypothese haben, was eigentlich in der Funktion
  in welcher Reihenfolge geschehen sollte und im Debugger checken
  ob das auch so ist. Wenn nicht gibt es 2 Moeglichkeiten: Entweder
  ist Deine Hypothese falsch oder das Program implementiert was
  anderes als Du denkst. Hinsetzen und Nachdenken was denn der
  Fall ist. Danach: Nicht zuviel auf einmal aendern und naechster
  Test.

von Ich B. (ichbin)


Lesenswert?

Hallo Karl Heinz,

es ist unheimlich motivierend, so detaillierte Hilfe zu bekommen.
Vielen Dank für Deine Zeit.

Ich hätte mein aktuelles Vorhaben mit der Funktion noch genauer
beschreiben sollen; weil ich klein anfangen wollte, bearbeite ich zur
Zeit nur eine unsigned int, nämlich stepper_speed.
diese wird anfangs auch korrekt ausgegeben (170, AA) [zweimal, um zu
prüfen ob die Rückwandlung funktioniert], nur konnte ich sie im zweiten
Teil der Funktion anscheinend nicht setzen - zumindest sah es so aus.

Ich habe mir noch ein paar Testzeichen ausgeben lassen, und
festgestellt, daß 'buffer' im zweiten Teil der Funktion nicht
ausgegeben wird. Kein Wunder, habe ich doch bei jedem Aufruf den Index
i zurückgesetzt, und meistens eine 0 reingeschrieben...
Die Ausgabe bestätigt mir jetzt, daß empfangene Wert in die Variable
übernommen wird.

Es lag also gar nicht an den Zeigern, ich habe die Schuld auf das
Unbekannte geschoben :-)

Aber ich denke, dank Deines Beispiels werde ich bald meine
Vorstellungen umsetzen können, und nebenbei auch noch Zeiger* &Co
verstehen. Ich hatte mal ein schlechtes C-Buch (spontankauf, "Jetzt
lerne ich C"), das ausgesprochen fehlerhaft ist; seitdem plane ich
regelmäßig, Ersatz dafür zu finden - ich sollte das mal in die Tat
umsetzen!

von Karl H. (kbuchegg)


Lesenswert?

Solltest Du.

Ich will ja nicht prejudizieren. Aber lass die Finger
von so Buechern wie:
  C in 20 Stunden
  C ganz einfach
  C fuer Dummies

und wie sie alle heissen. Die sind zwar duenn aber
auch meist ziemlich fehlerhaft bzw. da steht dann immer
nur die Haelfte drinnen.

Viele schwoeren auf 'den Klassiker': K&R

K&R steht fuer 'Kernighan und Ritchie' den Autoren des Ur-C
und dessen Dokumentation. Meiner ist schon 18 Jahre alt und sieht
auch dementsprechend aus. Keine Angst: Heutzutage gibt es eine
andere Auflage und das dort beschriebene C ist auch nicht
mehr K&R - C sondern modernes ANSI-C

von Ich B. (ichbin)


Lesenswert?

Daß es nicht das Buch sein konnte, habe ich mir schon damals gedacht,
aber daß dann auch noch Falsches und Widersprüchliches drinstehen
könnte kann man als Anfänger-Käufer nicht berücksichtigen.. Im Prinzip
sollte man es als 'defekt' zurückgeben können..

von Karl H. (kbuchegg)


Lesenswert?

Sei auch vorsichtig mit diversen 'Online-Tuorials'
Selbes Spielchen: widerspruechlich, falsch, unvollstaendig.

Es hat schon seinen Grund, warum ein gutes C-Buch nun mal
eine entsprechende Seitenanzahl hat, waehrend die meisten
Online-Tutorials grade mal die zweistelligen Seiten-Anzahl
so lala erreichen.

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.