Forum: Mikrocontroller und Digitale Elektronik Struct ausgeben


von Walter T. (nicolas)


Lesenswert?

Guten Abend,

meine im EEPROM hinterlegten Daten (Einstellungen) sind ein großes 
globales struct mit dem Namen "Settings". Da ich zu 
Dokumentationszwecken ohnehin Daten über den UART sende, will ich jetzt 
auch die Einlstellungen zum Programmstart senden.

Der erste naive Ansatz:
1
/** Sendet die Einstellungsdaten */
2
void settings_send(void)
3
{
4
    char buffer[81];
5
    snprintf(buffer, 80, "Settings.StepmotorDriver.currentRun_mA=%f\n", Settings.StepmotorDriver.currentRun_mA);
6
    usart_putstr(buffer);
7
    snprintf(buffer, 80, "Settings.StepmotorDriver.currentHold_mA=%f\n", Settings.StepmotorDriver.currentHold_mA);
8
    usart_putstr(buffer);
9
    snprintf(buffer, 80, "Settings.Stepcontroller.a_max_spss=%u\n", Settings.Stepcontroller.a_max_spss);
10
    usart_putstr(buffer);
11
    snprintf(buffer, 80, "Settings.Stepcontroller.v_max_sps=%u\n", Settings.Stepcontroller.v_max_sps);
12
    usart_putstr(buffer);
13
14
 ...
15
}

Bis ich diese Funktion implementiert habe, habe ich ein großes Stück 
Schreibarbeit vor mir. Außerdem ist das fehlerträchtig.

Wenn ich mir die Arbeit vereinfachen will- welche Möglichkeiten habe 
ich? Muß ich meinen eigenen Parser in Python oder Matlab schreiben, oder 
gibt es schon Werkzeuge, die ein struct mundgerecht vorzerlegen können?

: Bearbeitet durch User
von Pragmatiker (Gast)


Lesenswert?

Da dürfte wohl die einfachste Lösung sein - Du klopft Quick&Dirty ein 
Script zusammen, das sich auf die Zeilenstruktur verlässt und mit 
Regular Expressions arbeitet.

Wahrscheinlich wirst du das Script sowieso noch ein paar mal umbauen. 
Weil dir dein erster Ansatz nicht mehr gefällt, oder weil du andere 
Felder haben willst, oder eine mehrspaltige Tabelle...

Selbst wenn es ein Tool gibt - entweder macht es nicht das, was du haben 
willst, oder konfigurieren wird aufwendiger als selbst zusammen 
schustern.

von Tom (Gast)


Lesenswert?

Man kann  die Struct-Deklaration und Zugriffe auf jedes Feld im struct 
auch vom preprocessor generieren lassen. Das ist maximal hässlich, aber 
manchmal nützlich. Mit deinen verschachtelten structs wird es 
wahrscheinlich schwierig.

fields.inc
1
STRUCT_ENTRY(price, float, "%f", 0.234)
2
STRUCT_ENTRY(weight, float, "%f", 5.0)
3
STRUCT_ENTRY(count, int, "%d", 0)


example.c
1
#include <stdio.h>
2
3
typedef struct {
4
#define STRUCT_ENTRY(n, t, foo, fooo) t n;
5
#include "fields.inc"
6
#undef STRUCT_ENTRY
7
} tConfig;
8
9
void disp(tConfig bla)
10
{
11
#define STRUCT_ENTRY(n, foo, u, fooo) printf("\t" #n "=" u "\n", bla.n );
12
#include "fields.inc"
13
#undef STRUCT_ENTRY
14
}
15
16
void init(tConfig* bla)
17
{
18
#define STRUCT_ENTRY(n, foo, fooo, val) bla->n = val;
19
#include "fields.inc"
20
#undef STRUCT_ENTRY
21
}
22
23
int main(void)
24
{
25
    tConfig foo;
26
    init(&foo);
27
    disp(foo);
28
}


Ergebnis
1
typedef struct {
2
    float price;
3
    float weight;
4
    int count;
5
6
} tConfig;
7
8
void disp(tConfig bla)
9
{
10
    printf("\t" "price" "=" "%f" "\n", bla.price );
11
    printf("\t" "weight" "=" "%f" "\n", bla.weight );
12
    printf("\t" "count" "=" "%d" "\n", bla.count );
13
14
}
15
16
void init(tConfig* bla)
17
{
18
    bla->price = 0.234;
19
    bla->weight = 5.0;
20
    bla->count = 0;
21
}

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Nicht wirklich toll; vermindert die Schreibarbeit auf ca. die Hälfte:
1
#define Out(fmt, var)                               \
2
    do {                                            \
3
        snprintf (buffer, 80, #var "=" fmt, var);   \
4
        usart_putstr (buffer);                      \
5
    } while (0)
6
    
7
void settings_send (void)
8
{
9
    char buffer[81];
10
    Out ("%f", Settings.StepmotorDriver.currentRun_mA);
11
    ...
12
}

Ansonsten sehe ich da i.W. 2 Ansätze:

1) Einen Parser (z.B: aus cer C/C++ Quelle oder aus der Debug-Info).

2) Beschreibung der Datenstruktur in einer konfortablen Darstellung und 
daraus dann Typdefinition und Output-Code generieren.

Bei beiden Ansätzen braucht's Host-Interaktion, d.h. auto-generierten 
Code, was den Build-Prozess dann komplizierter macht.

Man käme auch ohne auto-generierten Code (für's Target) aus, wenn man 
einfach Settings ein einem Stück binär rüberschiebt und der Host dann 
die Meta-Info wie Komponentennamen beisteuert; was dann aber eine App 
auf dem Host erfordert statt die Infos einfach auf einem Terminal 
darzustellen.

Ganz ohne Host-Code bekäme man das auch über XMacros hin, also über eine 
Datei, die man im Gegensatz zu einem Header mehrfach in 
unterschiedlichen Kontexten includiert, siehe Beispiel von Tom.  Aber 
schön ist anders...

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


Lesenswert?

Die X-Macro-Methode hat den Vorteil, dass man bei Erweiterungen der
Struktur die Änderungen nur an einer einzigen Stelle vornehmen muss, was
nicht nur weniger mühsam, sondern auch weniger fehleranfällig ist.

Die Hässlichkeit dieser Methode liegt im Auge des Betrachters, ich
persönlich finde haufenweise Boilerplate ich noch hässlicher. Selbst
wenn es andersherum wäre, würde ich hier dennoch den Pragmatismus über
die Ästhetik siegen lassen, d.h. die X-Macros verwenden.

von A. S. (rava)


Lesenswert?

hab viel Erfahrung mit XMACROS.
Das lohnt sich nur, wenn sich deine Settings regelmäßig ändern und die 
Struktur flach ist. Verschachtelte Structs machen Kopfschmerzen.

Und wenn deine Settings fix sind, dann sende auch mit statischem Code.

Alternative: Jedes Setting bekommt denselben Datentyp und du überträgst 
die Rohdaten als Vektor. Vielleicht hast du am PC ja bessere 
Möglichkeiten, die Struktur zu parsen und anzuzeigen?

von Walter T. (nicolas)


Lesenswert?

Johann L. schrieb:
> Nicht wirklich toll; vermindert die Schreibarbeit auf ca. die Hälfte:

#facepalm#

Danke Johann! Ich hatte ganz vergessen, daß man Strings einfach 
hintereinanderschreiben kann. Und daß ein Argument gleichzeitig als 
String und als Variable nutzen kann.

Das ist schon deutlich angenehmer, als separat ein Skript zu pflegen.

Eigentlich bin ich mit dem Makro auch schon ganz glücklich. Im Vergleich 
zu vorher ist es ein Traum.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Yalu X. schrieb:
> Die Hässlichkeit dieser Methode liegt im Auge des Betrachters, ich
> persönlich finde haufenweise Boilerplate noch hässlicher. Selbst
> wenn es andersherum wäre, würde ich hier dennoch den Pragmatismus über
> die Ästhetik siegen lassen, d.h. die X-Macros verwenden.

An sich finde ich XMacros nicht so schlecht, werden z.B. auch im GCC 
verwendet für Devices, Built-ins etc:

http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/avr/avr-mcus.def?revision=267494&view=markup#l36
http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/avr/builtins.def?revision=267494&view=markup#l18

Im vorliegenden Fall dürfte es aber nicht einfach sein, die 
Ausgabe(strings) so wie von Walter gewünscht zu erzeugen, und schon die 
Typdefinition von Settings und statische Initializer etc. zu erzeugen 
dürfte hässlich werden — das hässlich bezog sich also i.W. auf die im 
konkreten Fall umzusetzenden Anforderungen.

von c++ (Gast)


Lesenswert?

C++ to the rescue
https://github.com/cbeck88/visit_struct
1
struct my_type {
2
  int a;
3
  float b;
4
  std::string c;
5
};
6
7
VISITABLE_STRUCT(my_type, a, b, c);
8
9
10
11
struct debug_printer {
12
  template <typename T>
13
  void operator()(const char * name, const T & value) {
14
    std::cerr << name << ": " << value << std::endl;
15
  }
16
};
17
18
void debug_print(const my_type & my_struct) {
19
  visit_struct::for_each(my_struct, debug_printer{});
20
}

Statt cerr machst du eine Unterscheidung nach dem typen und leitest auf 
snprintf um mit entsprechendem specifier.

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.