Forum: Compiler & IDEs Pfiffiges Makro gesucht für Variablen-Deklaration / - Initialisierung


von Joachim (Gast)


Lesenswert?

Hallo,

ich muß Telegramme über die serielle Schnittstelle verschicken.

Die Telegramme müssen folgenden Aufbau haben (geschrieben als struct:

struct
{
   uint8_t cmd;    // Das Kommando
   uint8_t data[]  // Abhängig vom Kommando eine bestimmte Anzahl Bytes
   uint8_t ck1;    // Byte 1 der Fletcher_Prüfsumme der Daten
   uint8_t ck2;    // Byte 2 der Fletcher_Prüfsumme der Daten
} meinTelegramm;

Die Reihenfolge muß natürlich eingehalten werden.

Die Telegramme würde ich gerne im Flash ablegen und um die Bytes
in einer Schleife aus dem Flash lesen zu können folglich noch gerne
die Gesamtlänge als Byte voran auch im Flash ablegen, also
ungefähr so:

struct
{
   uint8_t len;    // Länge im Flash
   uint8_t cmd;    // Das Kommando
   uint8_t data[]  // Abhängig vom Kommando eine bestimmte Anzahl Bytes
   uint8_t ck1;    // Byte 1 der Fletcher_Prüfsumme der Daten
   uint8_t ck2;    // Byte 2 der Fletcher_Prüfsumme der Daten
} meinTelegramm;

Da ich viele Telegramme habe, würde ich mir gerne ein Makro bauen,
am liebsten natürlich so:

#define TELEGRAMM( telegramm_name, kommando, ... )

Problem 1: Der C-Präprozessor kann mir die Fletcher-Prüfsumme nicht 
ausrechnen.
Also dann halt so:

#define TELEGRAMM( telegramm_name, kommando, ck1, ck2, daten ) \
  struct\
  { \
    const uint8_t  len; \
    const uint8_t  cmd; \
    const uint8_t   data[]; \
    const uint8_t   ck1; \
    const uint8_t   ck2; \
  }   telegramm_name PROGMEM = \
  { \
    .len = sizeof(telegramm_name), \
    .cmd = kommando, \
    .data = daten, \
    .ck1 = ck1, \
    .ck2 = ck2, \
  }

Ein Aufruf sollte dann z.B. so aussehen:

TELEGRAMM( Telegramm, 0x22, 0xAB, 0xCD, { 0,1,2,3,4,5 } );

Problem 2.1: Warum schafft es GCC nicht die Größe des Structs
zu ermitteln und in len einzutragen?

Problem 2.2: Warum nimmt mir der Präprozessor { 0,1,2,3,4,5 }
auseinander, statt es als ein Argument ans Makro zu verwenden?

Problem 2.3: Warum hat GCC ein Problem damit, da0ß data[]
nicht als letztes Element im struct steht?


Oder bin ich völlig auf dem Holzweg?

Im Kern wie gesagt brauche ich bestimmte Bytes in einer bestimmten
Reihenfolge und muß deren Länge kennen.


Viele Grüße
Joachim

von Patrick D. (oldbug) Benutzerseite


Lesenswert?

Denk' mal nach: die Länge von data[] ist unbestimmt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Joachim wrote:

> Problem 2.2: Warum nimmt mir der Präprozessor { 0,1,2,3,4,5 }
> auseinander, statt es als ein Argument ans Makro zu verwenden?

Weil das in der C-Syntax so festgelegt ist, dass er sie trennen
muss.

> Problem 2.3: Warum hat GCC ein Problem damit, da0ß data[]
> nicht als letztes Element im struct steht?

Wie soll er denn sonst die Offsets für die Zugriffe auf ck1 und
ck2 berechnen?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Jörg Wunsch wrote:

>> Problem 2.2: Warum nimmt mir der Präprozessor { 0,1,2,3,4,5 }
>> auseinander, statt es als ein Argument ans Makro zu verwenden?
>
> Weil das in der C-Syntax so festgelegt ist, dass er sie trennen
> muss.

Geklammerte Argumente nimmt er übrigens nicht auseinander.  Sie
müssen aber in runden Klammern stehen, das nützt dir hier nicht
viel.

Was dir aber was nützen könnte ist, wenn du deine data-Liste ans
Ende nimmst und als ... deklarierst.  Zugreifen kannst du über
1
__VA_ARGS__
 (das ist ein C99-Feature).

von Joachim (Gast)


Lesenswert?

Hallo,

@Jörg: Danke, wenn das andere nicht geht, dann wenigstens das.

So ganz kann ich aber nicht nachvollziehen, warum die Größe
bei einem Array unbestimmt ist / sein soll.

Bei einem struct kann der Comipler ja z.B.:

struct
  {
    const uint8_t  len;
    const uint8_t  cmd;
  }   telegramm_name PROGMEM =
  {
    .len = sizeof(telegramm_name), \
  };

schluckt er anstandslos.

Warum kann er es dann nicht bei einem Array?

Einfach erstmal das Array aufbauen, dann die Größe ermitteln
und dann initialisieren.


Aber ok, Wehklagen hilft nicht. ;-)

von Joachim (Gast)


Lesenswert?

Nachtrag:

Warum kann der Comiler sowas denn auch nicht?

struct
  {
    const uint8_t  len;
    const uint8_t  cmd;
    const uint8_t  data[sizeof({0,1,2,3})];
  }   telegramm_name;

von yalu (Gast)


Lesenswert?

So etwas könnte deinen Vorstellungen nahe kommen:
1
#define TELEGRAMM(telegramm_name, kommando, check1, check2, ...) \
2
struct {                                                         \
3
  const uint8_t len;                                             \
4
  const uint8_t cmd;                                             \
5
  const uint8_t data[sizeof (char []){__VA_ARGS__}];             \
6
  const uint8_t ck1;                                             \
7
  const uint8_t ck2;                                             \
8
} telegramm_name = {                                             \
9
  .len  = sizeof telegramm_name,                                 \
10
  .cmd  = kommando,                                              \
11
  .data = {__VA_ARGS__},                                         \
12
  .ck1  = check1,                                                \
13
  .ck2  = check2                                                 \
14
}
15
16
TELEGRAMM(Telegramm, 0x22, 0xAB, 0xCD, 0, 1, 2, 3, 4, 5);

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Joachim wrote:

> Warum kann der Comiler sowas denn auch nicht?

>     const uint8_t  data[sizeof({0,1,2,3})];

Weil {0,1,2,3} weder ein C-regelgerechter Ausdruck noch ein
regelgerechter Datentyp ist.  Diese beiden sind aber die
einzigen zulässigen Argumente für den sizeof-Operator.

Dein variable-length array member ist ja am Ende der Struktur
gut aufgehoben, und dort funktioniert es auch prima.  (Ist ein
weiteres C99-Feature.)  Der Compiler kann trotzdem alle Offsets
berechnen, die Längeninformation für das Array am Ende musst
du halt anderweitig mitgeben.

Dein Versuch, das Array mit einer festen Größe in der Mitte zu
platzieren, würde ja lauter verschiedene zueinander inkompatible
Datentypen erzeugen.  Das variable-length array am Ende hingegen
erzeugt nur einen einzigen Datentyp.

von Joachim (Gast)


Lesenswert?

@Jörg: Das mit {1,2,3} sehe ich ein. Sorry.
Am Ende des structs nützt mir das data[] Element nichts.
Es muß halt mittendrin sein.
Ob es der Compiler mit vielen Datentypen (Pro Telegram einer)zu tun 
bekäme
könnte mir doch egal sein.
Der Zugriff muß ja ehe immer auf PGM_P gecastet werden.

@yalu:  Vielen Dank!  Ich nehme Deinen Vorschlag.

Oder weiss doch noch jemand, wie man dem Präprozessor
die Berechnung der Fletcher-Prüfsummen beibringen kann? ;-)

Viele Grüße
Joachim

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Joachim wrote:

> Am Ende des structs nützt mir das data[] Element nichts.
> Es muß halt mittendrin sein.

Warum denn das?  Warum muss deine Struktur im Speicher denn mit
der physischen Übertragung übereinstimmen?  Du wirst die Struktur
ja wohl (bzw. deren Adresse) an eine Routine übergeben, die sie
dann sendet.  Die kann ja ck1 und ck2 nach den Daten senden,
obwohl sie physisch im Flash-ROM vor den Daten liegen.

> Ob es der Compiler mit vielen Datentypen (Pro Telegram einer)zu tun
> bekäme
> könnte mir doch egal sein.

Nein.  Der Compiler hat sonst keine Chance, auf die Elemente ck1 und
ck2 der verschiedenen Strukturen zuzugreifen.

Das sinnvollste erscheint es mir, dass du ck1 und ck2 gar nicht
erst gesondert behandelst, sondern einfach ans Ende des Array mit
als Datenbytes dranklebst.  Du musst sie ja ohnehin vorberechnen.

> Oder weiss doch noch jemand, wie man dem Präprozessor
> die Berechnung der Fletcher-Prüfsummen beibringen kann? ;-)

Das scheitert schon allein daran, dass der C-Präprozessor keine
Schleifen ausführen kann.  Du könntest möglicherweise den m4-
Präprozessor benutzen.

Eine komplett andere alternative ist aber, dass du deine Prüfsumme
während der Übertragung zur Laufzeit einfach ausrechnen lässt.  Die
CPU wird ja wohl dazu in der Lage sein.  (Keine Ahnung, was eine
Fletcher-Prüfsumme sein soll, hab' auch grad keine Lust zu gugeln.)
Dann braucht deine Struktur im Speicher nur die eigentlichen Daten
enthalten, und die Routine, die die Daten sendet, weiß, dass sie
noch zwei Prüfsummenbytes berechnen und danach übertragen muss.

von Joachim (Gast)


Lesenswert?

Hallo Jörg,

natürlich gibt's verschiedene Wege zum Ziel zu kommen, jeweils
mit Vor- und Nachteilen.
Aber der verfolgte Ansatz war halt so wie beschrieben:
"Einfach mit bzw. in einer(!) Schleife die Bytes rausschicken.
und sich dazu die Daten vorher passend ins Flash legen."

Danke
Joachim

von yalu (Gast)


Lesenswert?

> "Einfach mit bzw. in einer(!) Schleife die Bytes rausschicken.
> und sich dazu die Daten vorher passend ins Flash legen."

Wenn du die Daten "nur" rausschicken möchtest und nirgends direkt auf
einzelne Elemente der Strukturen zugreifen möchtest, könntest du die
einzelnen Bytes des Telegramms auch einfach in einem uint8_t-Array
ablegen. Du würdest die Startadressen der Strukturen zum Senden sowieso
in einen uint8_t-Pointer casten, warum die Daten also nicht gleich im
benötigten Format ablegen?

Die Telegrammlänge steht dann entweder im ersten Element des Arrays oder
wird als zusätzliches Argument an die Telegrammsendefunktion übergeben.
Damit deren Aufruf einfacher wird, kannst du ihn zusätzlich noch in ein
Makro verpacken, das nur ein Argument, nämlich das Array, entgegen
nimmt und das Längenargument für die Funktion automatisch erzeugt.

von Joachim (Gast)


Lesenswert?

@yalu: Klar, aber dann müßte ich ja die Länge des jeweiligen .data auch
vorher ermitteln. Bei Deiner Version muß ich das nicht.

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.