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..
Struct ?? Damit könntest du deine Variablen in einer zusammenfassen und als Block verschicken. Ist aber nicht ascii.
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..
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...
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..
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
> 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.
Vielen Dank! Mit Funktionszeigern habe ich noch keine Erfahrung. Das wird sich ja jetzt ändern :-)
Ist nicht so schlimm. Die Syntax ist am Anfang gewöhnungsbedürftig. Ansonsten sind die Dinger simpel.
Die Übertragung als Klartext kann ich vergessen. mit ltoa() und atol() bin ich bei 137 Prozent Flash .. :-)
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!
Ach so: ich benutze union { uint32_t u32; uint8_t u8[4]; } x; um auf die einzelnen Bytes meiner Werte zugreifen zu können..
> 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.
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
> *(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.
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!
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
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..
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.