PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Seriell-RF mehrere Bytes übertragen und auswerten (Gcc)



oderlachs
21.09.2013, 19:33
Hallo Kenner der Materie !

Ich habe wieder mal das, von mir oft zitierte Brett, irgendwie vorm K... ;)

Ich möchte einige Byte nacheinander übertragen, zuerst einmal 3 Byte nacheinander, aber ich weiss nicht wie ich es anstellen kann das dieses auch richtig getrennt von der Gegenseite ausgewertet wird. Das also der Wert des entsprechenden Bytes auch dahingehend ausgewertet wird , wo er auch hingehört.

Irgendwie finde ich nicht recht den Anfang, einen Frame von erst mal zBsp. 3 Byte zu senden und die einzelnen Werte auf der Empfangsseite dementsprechend auszuwerten.
Ich erwarte hier keinen fertigen Quellcode der mir in den Schoss gelegt wird, ein paar Hinweise, vielleicht auch Anschaungsbeispiele zur Realisierung meines Projektes würden mich schon sehr erfreuen :)

Gruss und Dank

Gerhard

markusj
21.09.2013, 21:09
Es gibt verschiedene Möglichkeiten, die auch ein Stück weit davon abhängen, wie komplex das Protokoll werden soll. Ich will dir Mal zwei Möglichkeiten aufzeigen:

Option 1: Zustandsautomat
Auf Empfängerseite definierst du für jedes Byte einen Zustand, zum Beispiel B1_Lesen, B2_Lesen, B3_Lesen. Wenn du ein Byte empfängst, wechselst du über eine Fallunterscheidung (switch/case) in den aktuellen Zustand und verarbeitest dort das Empfange Byte. Danach wechselst du in den nächsten Zustand (Zustandsvariable inkrementieren) und wenn die Routine dann das nächste Mal aufgerufen wird, wechselt die Fallunterscheidung so in den nächsten Zustand. B3_Lesen muss nach Abschluss der Auswertung dann aber nicht in B3_Lesen+1 sondern in Zustand B1_Lesen überleiten.

Option 2: Puffer/struct/union
Wenn du ein ausreichend strukturiertes Protokoll hast (wovon ich fast ausgehe), kannst du die Daten auch erst einmal in einen Puffer (Byte-Array) einlesen und solange den Inhaltszähler inkrementieren bis du drei Bytes empfangen hast. Für deinen Frame hast du dir ein passendes struct definiert, mit dem du bequem auf die Inhalte zugreifen kannst. Jetzt kannst du den Puffer entweder über einen cast in einen Zeiger auf den struct-Datentyp verwandeln, oder du hast dir einen union-Datentyp definiert in dem sowohl ein Byte-Array als auch der struct liegen, und kommst so komplett ohne casts aus.

Dazu ein Codebeispiel:


typedef struct {
uint8_t a;
int8_t b;
uint16_t c;
} frame_t;

typedef union {
frame_t frame;
uint8_t buffer[sizeof(frame_t)];
} frame_union_t;


void someFunction() {
frame_union_t buf;

// irgendwie mit Daten befüllen, hier nicht sehr sinnvoll ...
buf.buffer[0] = 0xaa;
buf.buffer[1] = 0xbb;
buf.buffer[2] = 0xcc;
buf.buffer[3] = 0xdd;

foo = buf.frame.c; // was das wohl gibt?!
}


mfG,
Markus

oberallgeier
22.09.2013, 08:59
... verschiedene Möglichkeiten ... abhängen, wie komplex das Protokoll ...Schöne Erklärung Markus, danke. Irgendwann muss ich mich wohl wirklich mit Sinn, Zweck, Anwendungs- und (allgemein) Möglichkeiten der struct´s und union´s beschäftigen. Ich finde, dass die einfache Reihenfolge mehrerer Bytes aber eine Stolperstelle sein könnte. Ich habe mein Protokoll dran aufgehängt, dass ich eine führende Kennung bei mehreren Bytes verwende, wobei für sehr wichtige Aktionen die Kennung als Information alleine (also NUR EIN Byte senden!) ausreicht.

Beispiele:

// ================================================== ============================ =
// == Lies den Puffer rx_buff[RX0_SIZE]
// ================================================== ============================ =
void u0st1 (void) // Schreib+Les RX-Tx-UART0(PD) z Einstellen + FUnktionswahl
{ //
#define KOMMANDO_APPS 'A' // Anwendungsprogramme fahren, diverse
#define KOMMANDO_APPS_LEN 7 // Annkkkk
// nn =: 01..99 - Programmnummer
// kkkk Parameter für Prog
//...
#define KOMMANDO_NORM 'N' // Normposition der Servos anfahren
#define KOMMANDO_NORM_LEN 1 // Kommandolänge´
//...
#define KOMMANDO_POSER 'P' // Positionierbefehl Servo
#define KOMMANDO_POSER_LEN 6 // Pn3600 {1-9, j, z} 10 Servo
// n =: 1..9, j=10, z=alle
//...
// Muss nur das laengste Kommando aufnehmen koennen.
#define KOMMANDO_BUFFER_LAENGE 14
//...
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Diese for-Schleife ist das eigentliche uart0-Stellprogramm.
for (;;)
{
// ...
/* Auch wir nutzen diese GoTo-Variante um auf Inhalt im FIFO zu warten. */
if ( ! ukbhit0 () )
continue;
/* EIN EINZIGES Zeichen aus FIFO lesen. */
zeichen_aus_fifo = ugetchar0();
// ==================================================
/* Wenn noch kein Kommando bekannt ist, muessen wir erst einen
Kommando-Buchstaben erkennen.

Randbemerkung
Hier ist auch ein switch/case-Dingsda erlaubt, da zeichen_aus_fifo und
auch der Define genau ein char-Zeichen bzw. ein numerisch auswertbarer
einfacher Datentyp sind. */
if (zeiger == 0)
{ //
if (zeichen_aus_fifo == KOMMANDO_APPS) // Fahre Anwendungsprogramme
telegrammlaenge = KOMMANDO_APPS_LEN; // mit und ohne Parameter
//...
if (zeichen_aus_fifo == KOMMANDO_NORM) // Servo Normposition
telegrammlaenge = KOMMANDO_NORM_LEN; // anfahren
//...
// Beispiel für korrekten Befehl
if (zeichen_aus_fifo == KOMMANDO_POSER) // Pn3250: n{1-9, j, z} Servo,
telegrammlaenge = KOMMANDO_POSER_LEN; // j=10, z alle Servos
//...
}
/* Und jetzt das geniale Ergebnis:
Wenn nun "zeiger" und "telegrammlaenge" gleich sind,
dann haben wir ein fertig empfangenes Kommando mit
seiner erwarteten Laenge. */
if (zeiger == telegrammlaenge)
{
//...
/* Die Funktionen koennen nun gemuetlich das
Telegramm auswerten. */

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Fahre verschiedene Anwendungsprogramme, je nach Nummer
if (mein_rx_buff [0] == KOMMANDO_APPS) // Tastatur-Eingabe "Annkkkk"
{ // Annkkkk
// nn =: 01..99 - Programm-Casenummer
// kkkk Parameter für Prog
// Dekodiere Programmkennziffer im Byte mein_rx_buff 1 und 2
for ( u8 i=0; i<=1; i++) { s = mein_rx_buff [i + 1]; }
s[2] = 0; // Markiere Stringende "0"
nmbr = atoi ( s ); //

// - - - - - - - - - - - - - - -
// case
// 10 Augen rollen k mal (erstes "k")
// 11 Alle Lider auf, full speed
// 13 Alle Lider auf, SlowMo 50
//...
switch (nmbr) // Fahre gewählte Aktionen
{ //
case 10: // Augen rollen k-mal
s[0] = mein_rx_buff [3]; s[1] = 0; // Dekodiere 1stes k
npar1 = atoi ( s ); //
AuRoll ( npar1 , 100 ); // Augen rollen mit Standard-slow
for ( u8 i=1; i<=6; i++) {mein_rx_buff [i] = '9'; }
mein_rx_buff [7] = 0; // Hilft das bei unvollständiger Eingabe ?
break;
//...
default:
break;
} // Ende switch (nmbr)
} // Ende if (mein_rx_buff [0] == KOMMANDO_APPS)

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Wurde "KOMMANDO_NORM" = "N" erkannt ? Normposition der Servos anfahren
if (mein_rx_buff [0] == KOMMANDO_NORM) // Tastatur-Eingabe "N"
{ //
uputs0 ("\r\tServonormposition,"); //
kal_0(); // Initialiswerte einstellen kal
uputs0 ("\tneue Servodaten :"); //
dat_aus (); // Servodaten ausgeben

} // Ende if (mein_rx_buff [0] == KOMMANDO_NORM)

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//...
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Wurde "KOMMANDO_POSER" erkannt ? (PO sition SER vo stellen)
if (mein_rx_buff [0] == KOMMANDO_POSER) // Pa3250 ... a-j=ServoNr, z=alle
{ //
// Mal sehen, ob ALLE zehn Servos gemeint sind:
if ( (mein_rx_buff[1] == GRZ) || (mein_rx_buff[1] == klz) )
// Alle zehn Servos ??
{ // ja, alle zehn Servos auf Wert ansteuern
for ( u8 all = 1; all <= 10; all++ )
{ //
mein_rx_buff[1] = all; // Servonummer ist 1 .. 10
do_poser (mein_rx_buff); // Alle zehn Servos ansteuern
} // Ende von for ( u8 all = 1; all..
} // weiter mit else
else // NUR ein einziger Servo ist dran
{ // Dekodiere Servonummer
nsrv = mein_rx_buff[1] - '0';
if ( mein_rx_buff[1] == 'j' ) nsrv = 10;
if ( mein_rx_buff[1] == '0' ) nsrv = 10;
mein_rx_buff[1] = nsrv; //
do_poser (mein_rx_buff); // z.B.: 3250 : neuen Position rUn
} // ENDE if ( (mein_rx_buff[1] == GRZ) ||..

} // Ende if (mein_rx_buff [0] == KOMMANDO_POSER)

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//...
// - - - - - - - - - - - - - - -
} // Ende if (zeiger == telegrammlaenge)

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
} // Ende for (;;) ...
// - - - - - - - - - - - - - - -
return; // Ende von void u0st1 (void)
} //
// ================================================== ============================ =

[I](Hoffentlich sind diese Auszüge meines funktionierenden PC-Controller-Schriftwechsels nicht zuuu stark gekürzt und unverständlich . . . .)

So ist das gesendete Byte "D" eine Aufforderung, den aktuellen Zustand bestimmter Variablen über UART zu senden (DISPLAY), ein "N" stellt den NORMAL-Zustand (sozusagen Default- bzw. Startwerte) her. Um den Servo 10 (oder drei) auf die Position 3247 zu stellen funke ich eben:
für Servo 10: P03247
für Servo 03: P33247
für alle ......: Pz3247

markusj
22.09.2013, 10:04
Ich habe mein Protokoll dran aufgehängt, dass ich eine führende Kennung bei mehreren Bytes verwende, wobei für sehr wichtige Aktionen die Kennung als Information alleine (also NUR EIN Byte senden!) ausreicht.

Und das ist ein sehr interessanter Fall, weil man hier nämlich mit einer Kombination aus beiden Techniken arbeiten kann/muss. Du hast einen Protokollkopf (bei dir ein Byte), der mitteilt was für einen Datagrammtyp eigentlich gesendet wird. Der Zustandsautomat müsste folglich das erste Mal eine Prüfung starten, wenn der Protokollkopf vollständig ist. Danach könnte der Code dann warten bis das Datagramm komplettiert wurde. Das reduziert die Anzahl der Zustände auf Datagrammanzahl + 1 anstelle der max. Datagrammlänge bei der reinen Zustandsautomatenlösung.

Übrigens: In einem union können mehrere Datenstrukturen überlagert werden. Zum Beispiel besagter Protokollkopf, Datagrammtyp1, Datagrammtyp2 etc., womit du gleich auf alles bequem zugreifen kannst.

Grüße,
Markus

oderlachs
22.09.2013, 13:31
Danke für Eure Unterstützung, versuche jetzt zu verstehen.. aber was ich dar nicht erst begreiffe ,wie bekomme ich den Datenframe in die Serielle Übertragung, da hängt bei mir der Hase im Pfeffer, da tue ich mir mit schwer...
Ich will auch offenlegen was damit geschehen soll:
ein JoyStick gibt analogX und analogY , jeweils 0...255 aus, das wären 2 Byte, dann muss nochmal 0x00 oder 0xFF als "Bool" übetragen werden. die Ersten zwei Byte sollen 2 Motore steuern(PWM) der "boolsche Wert" stopt oder startet die Motoren....aber irgendwie klemme ich da fest...
Na ja ich werde mal ein Prog anfangen und kann es dann zur Diskussion stellen..
Vorerst vielen Dank Euch allen

Gerhard

markusj
22.09.2013, 13:36
Danke für Eure Unterstützung, versuche jetzt zu verstehen.. aber was ich dar nicht erst begreiffe ,wie bekomme ich den Datenframe in die Serielle Übertragung, da hängt bei mir der Hase im Pfeffer, da tue ich mir mit schwer...

Auch da funktioniert die union/struct-Variante: Anstatt den Puffer zu beschreiben und dann den struct auszulesen befüllst du auf Senderseite den struct mit deinen Daten und iterierst dann beim Senden über den byte-Puffer.

Grüße,
Markus

oberallgeier
22.09.2013, 15:52
... In einem union ... mehrere ... überlagert ... womit du gleich auf alles bequem zugreifen kannst ...Bequem? Kannst? Bei mir liest sich das "könntest" - wenn ich verstünde was ein union ist. Ok, ein Anfang ist gemacht, ich habe gerade im Kernighan/Ritchie-Sachverzeichnis das union (struct, Struktur) gesucht, gefunden und die Seite aufgeschlagen *ggg*.


... Zustandsautomat ... Prüfung starten, wenn der Protokollkopf vollständig ist. Danach ...Genau, das ist das Schicke daran:

//...
}
/* Und jetzt das geniale Ergebnis:
Wenn nun "zeiger" und "telegrammlaenge" gleich sind,
dann haben wir ein fertig empfangenes Kommando mit
seiner erwarteten Laenge. */
if (zeiger == telegrammlaenge)
{
//...
/* Die Funktionen koennen nun gemuetlich das
Telegramm auswerten. */

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Fahre verschiedene Anwendungsprogramme, je nach Nummer
if (mein_rx_buff [0] == KOMMANDO_APPS) // Tastatur-Eingabe "Annkkkk"
{ // Annkkkk
// nn =: 01..99 - Programm-Casenummer
// kkkk Parameter für Prog
// Dekodiere Programmkennziffer im Byte mein_rx_buff 1 und 2
for ( u8 i=0; i<=1; i++) { s[i] = mein_rx_buff [i + 1]; }
s[2] = 0; // Markiere Stringende "0"
nmbr = atoi ( s ); //

// - - - - - - - - - - - - - - -
// case
// 10 Augen rollen k mal (erstes "k")