C Bitfelder, Structs und Datentypen

Hemi

Aktives Mitglied
Premium Benutzer
30. Nov. 2008
1.103
19
38
Korntal-Münchingen, Germany
Sprachen
  1. ANSI C
  2. C++
  3. PHP
  4. Java
Hallo zusammen,

ich nutze bei meinem aktuellen Projekt für die Konfiguration das TunerStudio. Das ist ein Tool, mit dem die Steuergeräte konfiguriert und appliziert werden. Das ist richtig cool gemacht. Für die Konfiguration wird eine INI-Datei angelegt, in der unter anderen die eigentliche Konfiguration des Steuergerätes beschrieben wird, bei mir sieht sie zum Beispiel so aus:



CodeBox C
    isCANEnabled                = bits, U08, 0, [0:0], "false", "true"
    canSpeed                    = bits, U08, 0, [1:2], "125kbit", "250kbit", "500kbit", "1mbit"
    interfaceActivation         = bits, U08, 0, [3:3], "Input", "CAN"
    isCANDebugEnabled           = bits, U08, 0, [4:4], "false", "true"
    unused1                     = bits, U08, 0, [5:7], "1", "2", "3", "4", "5", "6", "7", "8"
   
    isChannel1Enabled           = bits, U08, 1, [0:0], "false", "true"
    isChannel2Enabled           = bits, U08, 1, [1:1], "false", "true"
    channel1OutputMode          = bits, U08, 1, [2:3], "Analog", "CAN", "Analog & CAN", INVALID
    channel2OutputMode          = bits, U08, 1, [4:5], "Analog", "CAN", "Analog & CAN", INVALID
    channel1AnalogOutputMode    = bits, U08, 1, [6:6], "Wideband", "Narrowband emulation"
    channel2AnalogOutputMode    = bits, U08, 1, [7:7], "Wideband", "Narrowband emulation"

    stoich_ratio                = scalar, U08, 2, ":1", 0.1, 0.0, 0.0, 25.5, 1

    channel1CANid               = bits, U16, 3, [0:10], $CAN_ADDRESS_HEX
    channel2CANid               = bits, U16, 5, [0:10], $CAN_ADDRESS_HEX
    debugMessageID              = bits, U16, 7, [0:10], $CAN_ADDRESS_HEX


Wie man sieht, besteht die Konfig aus drei U08 Bytes (entspricht uint8_t) und zwei U16 (was uint16_t entspricht) Bytes, also insgesamt 9 Byte lang.

Das ist zum Beispiel das Menü um den Byte 0 und 2 zu konfigurieren:

1652378418020.png

Und hier werden Byte 1 und der Rest konfiguriert:

1652378473805.png

Insgesamt umfasst die Konfig 9 Byte im EEPROM der MCU. So weit, so gut. Wenn man jedoch Byte 0 und 1 anschaut, sieht man eigentlich eine Mischung als Bitfelder ( mit der Größe) und ENUM (mit den möglichen Werten, INVALID bedeutet, dass dieser Wert nicht angezeigt wird).

In der MCU ist es wie folgt angelegt:

1. Initialie Konfig:


CodeBox C
uint8_t eeprom_config[] EEMEM = {0x03, 0x03, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

2. Wird beim Start gelesen und in den RAM gepackt:


CodeBox C
uint8_t controllerSettings[sizeof(eeprom_config)];
eeprom_read_block((void*)controllerSettings, (const void*) eeprom_config, sizeof(eeprom_config));

Das funktioniert alles sehr gut.

Nun wird das ganze in einen Struct gepackt:


CodeBox C
typedef struct {
    // first byte in the config:
    uint8_t isCanEnabled : 1;
    uint8_t canSpeed : 2;
    uint8_t interfaceActivation : 1;
    uint8_t isCANDebugEnabled : 1;
    uint8_t unused1 : 3;

    // second byte in the config:
    uint8_t isChannel1Enabled : 1;
    uint8_t isChannel2Enabled : 1;
    uint8_t channel1OutputMode : 2;
    uint8_t channel2OutputMode : 2;
    uint8_t channel1AnalogOutputMode : 1;
    uint8_t channel2AnalogOutputMode : 1;

    // third by in the config:
    uint8_t stoich_ratio;

    uint16_t channel1CANid;
    uint16_t channel2CANid;
    uint16_t debugMessageID;

} tConfig;

tConfig* configuration = (tConfig*)controllerSettings;


Nun kann ich über die Struct-Member auf die Daten zugreifen:


CodeBox C
if (configuration->isCanEnabled == 1)
    {
        uart2_sendS((uint8_t*) "CAN ist enabled\n\r", 17);
    }

Auch das geht.

Wenn man sich jetzt zum Beispiel die Zeile
canSpeed = bits, U08, 0, [1:2], "125kbit", "250kbit", "500kbit", "1mbit"
anschaut, sieht man, dass es eigentlich ein enum mit vier Member ist und im Bitfeld steht dann entsprechend 0x00, 0x01, 0x10 und 0x11 diese vier Werte eben. Wenn ich es jetzt in was "Brauchbares" übersetzen müsste, müsste ich einen enum mit den vier Werten anlegen und das Bitfeld drauf casten:


CodeBox C
typedef enum
{
    can_125
    , can_250
    , can_500
    , can_1000
} tCANSpeed;

.....

    switch ((tCANSpeed) configuration->canSpeed)
    {
        case can_125:
            // mach was
            break;
        case can_250:
            // mach was
            break;
        case can_500:
            // mach was
            break;
        case can_1000:
            // mach was
            break;
    }


Finde ich jetzt ehrlich gesagt nicht so prickelnd....

Gibt es eine elegantere Möglichkeit sowas zu lösen?

Vielen Dank Euch!
 
Ist doch gut so wie du es gelöst hast. Im einfachsten Fall setzt du ja einfach nur eine Variable. Wenn es größerer sich wiederholender Code wird dann pack es halt in eine Funktion mit Übergabe der variablen Argumenten.
 
Bitfields sind böse... ...weil es keinen Standard dafür gibt. Bitfields wurden im C-Standard nicht wirklich eindeutig definiert (Bitreihenfolge etc. Portable Code sollte NIE Bitfield verwenden. Ich hatte mal ein Projekt, das verwendete eine Little Endian Architektur (war ein PIC µC) und der sollte zu einem Big Endian Rechner (m68k) über SPI Daten austauschen. Das Übertragungsprotokoll verwendete auch Bitfields in seinen Structs. PIC Firmware mit dem Microchip PIC C-Compiler gebaut. Dann auf der m68 Seite einen GCC verwendet. Was soll ich sagen? Der GCC, der den Code für den PIC (Little Endian) baute, ordnete die Bitfelder in genau entgegengesetzter Reihenfolge (die Fields NICHT die Bits als Solches) an, wie es der GCC für den m68 k (Big Endian) tat! Fazit war dann, dass der C-Header für das Übetragungsprotokoll *zweimal* geschrieben werden musste. Enmal mit der Bitfeld-Order für den PIC und dann noch einmal für dem m68k Compiler! Das Problem ist eben, dass im C-Standard nicht wirklich definiert ist, wie Bitfields innerhalb eines Wortes angeordnet werden sollen/müssen und so hat jeder Compilerhersteller so seine eigenen Vorstellungen... :-(

Für mich gibt es in meinem Code keine Bitfields mehr. Da wird ganz klar geschoben und maskiert. Und das passt dann IMMER! - Egal ob Big oder Little Endian!:) Nur bei der Bytereihenfolge muss man halt diesbezüglich aufpassen.

Siehe auch https://www.opensourceforu.com/2015/03/be-cautious-while-using-bit-fields-for-programming/#:~:text=Bit fields are used to,as well as time complexities.
 
  • Like
Reaktionen: alecxs

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)