c++: bit field

Seite 1 von 1 - Forum: Coding Stuff auf overclockers.at

URL: https://www.overclockers.at/coding-stuff/c-bit-field_256379/page_1 - zur Vollversion wechseln!


wergor schrieb am 23.09.2020 um 18:08

ich hab folgende union:

Code:
    typedef union ConfigRegister {
        struct {
            bool unused : 1;
            bool soft_reset : 1;
            bool alert_pin_select : 1;
            bool pin_pol : 1;
            bool therm_alert_mode : 1;
            uint8_t avg : 2;
            uint8_t conv : 3;
            uint8_t mode : 2;
            bool EEPROM_busy : 1;
            bool data_ready : 1;
            bool alert_low : 1;
            bool alert_high : 1;
        } bits;
        uint16_t reg;

        ConfigRegister() {
            reg = 0;
        };
    } ConfigRegister;

mein code liest das config register von einem I2C sensor und schreibt den wert 8736 ( = 0010001000100000b) in reg. ich würde mir also folgende werte im bit field erwarten:
Code:
bool unused  = 0b;
bool soft_reset = 0b;
bool alert_pin_select = 0b;
bool pin_pol = 0b;
bool therm_alert_mode = 0b;
uint8_t avg = 01b;
uint8_t conv : 100b;
uint8_t mode : 00b;
bool EEPROM_busy : 0b;
bool data_ready : 1b;
bool alert_low : 0b;
bool alert_high : 0b;

tatsächlich sehe ich aber
Code:
bool unused  = 0b;
bool soft_reset = 0b;
bool alert_pin_select = 0b;
bool pin_pol = 0b;
bool therm_alert_mode = 0b;
uint8_t avg = 01b;
uint8_t conv : 010b;
uint8_t mode : 00b;
bool EEPROM_busy : 1b;
bool data_ready : 0b;
bool alert_low : 0b;
bool alert_high : 0b;
also das höhere byte scheint um genau 1 bit verschoben zu sein bzw. conv würde am ersten bit des höheren byte starten statt am letzten bit des niedrigen. ich glaube nicht dass es straddling ist, ich habe eine ähnliche union für ein anderes I2C gerät, das problemlos funktioniert:
Code:
    typedef union DataWrite {
        struct {
            uint8_t unused0 : 4;
            uint16_t value : 12;

            bool unused1 : 1;
            uint8_t pd_mode : 2;

            uint8_t unused2 : 2;
            uint8_t write_mode : 3;
        } bits;
        uint8_t content[3];

        DataWrite() {
            memset(content, 0, 3);
        };
    } DataWrite;

compiler ist arm gcc 9.2.1
weis jemand was das problem sein könnte?


mat schrieb am 23.09.2020 um 18:43

Anhand des vorhandenen Codes kann ich jetzt nichts sehen. Meistens liegt es in so einem Bereich an unterschiedlicher Endian/Byte Order.

Wie schaut der restliche Code aus? Wie überprüfst du die Werte?


wergor schrieb am 23.09.2020 um 18:53

endianness habe ich schon berücksichtigt, das habe ich im post vergessen zu erwähnen. (ein paar checks sind zwecks lesbarkeit entfernt)

Code:
ConfigRegister config_;

bool readRegister(uint8_t reg, char *content, unsigned int length) 
{
    char buffer[3] = {reg, 0, 0};

    if (i2c_->write(address_ << 1, buffer, 1, false) != 0)
        return false;

    if (i2c_->read(address_ << 1, buffer+1, 2, false) != 0)
        return false;

    // convert to little endian
    content[0] = buffer[2];
    content[1] = buffer[1];

    return true;
}

template <class T> bool readRegister(uint8_t reg, T &content)
{
	char buffer[sizeof(T)];

	if (!readRegister(reg, buffer, sizeof(T))) 
		return false;

	memcpy(&content, buffer, sizeof(T));

	return true;
}

die checks sind recht simpel, z.b.:
Code:
uint16_t cfg = 0;
readConfigRegister(cfg);
config_.reg = cfg;

bool ready = config_.bits.data_ready;


Vinci schrieb am 23.09.2020 um 19:10

Code: C++
static_assert(sizeof(ConfigRegister::bits) == 2);

Ups. ;)


Zitat
The following properties of bit fields are undefined:
The following properties of bit fields are unspecified:
The following properties of bit fields are implementation-defined:

https://en.cppreference.com/w/c/language/bit_field


/edit
Hardware-Entwickler können leider auch im Jahr 2020 nicht richtig programmieren. Ich weiß dass so gut wie jeder Hersteller Bitfelder und Unions für Register benutzt. Das ist aber halt alles nicht Standard konform... und das type-punning is übrigens auch UB.

/edit2
So wie ich die kenn hörst du sowieso nicht auf mich :p
deshalb auch hier die "Lösung":
#pragma pack(1)


wergor schrieb am 23.09.2020 um 19:35

Zitat aus einem Post von Vinci
Code: C++
static_assert(sizeof(ConfigRegister::bits) == 2);

Ups. ;)
äääh.. jo. fürst nächste mal weis ichs :p

Zitat aus einem Post von Vinci
https://en.cppreference.com/w/c/language/bit_field
also zurück zu & und >> :'(

Zitat aus einem Post von Vinci
/edit
Hardware-Entwickler können leider auch im Jahr 2020 nicht richtig programmieren. Ich weiß dass so gut wie jeder Hersteller Bitfelder und Unions für Register benutzt. Das ist aber halt alles nicht Standard konform... und das type-punning is übrigens auch UB.
die lib kommt schon von mir :p aber wenn type punning UB ist, warum gibts dann union, das ist ja genau nix anderes?

Zitat aus einem Post von Vinci
/edit2
So wie ich die kenn hörst du sowieso nicht auf mich :p
deshalb auch hier die "Lösung":
#pragma pack(1)
stimmt gar ned, ich hör meistens auf dich :D danke, aber ich glaub ich machs doch lieber mit bitwise operations, dann muss i mi ned drauf verlassen dass der compiler die #pragma statements so versteht wie ich sie verstehe ;)


Vinci schrieb am 23.09.2020 um 20:01

Zitat aus einem Post von wergor
also zurück zu & und >> :'(

Die meisten Hardware-Hersteller definieren meist Bitmasken für alle Bits der Register. Also um bei deinem Beispiel zu bleiben gibts dann z.B. sowas wie ein:

Code: C++
#define USART_CR_AVG (0b11 << 8)

Mit Hilfe dieser defines lassen sich Funktionen schreiben die einem zur Compilezeit die Werte für die entsprechenden Bitstellen richtig zusammenschieben.

Code: C++
template<uint32_t Mask, uint32_t Value>
void set(uint32_t volatile* reg) {
  reg = ... /* Zur Compilezeit kalkulierter Werte aus Mask & Value */
}

int main() {
  set<USART1_CR_AVG, 2>(USART1->CR);
}


Im Optimalfall abstrahiert ma selbst Register-Zugriffe in C++, um zum Beispiel read-only Register und ähnliches abzubilden... und gibt der Klasse ein Interface in dem setzen/löschen usw. gut abgekapselt ist.


Zitat aus einem Post von wergor
die lib kommt schon von mir :p aber wenn type punning UB ist, warum gibts dann union, das ist ja genau nix anderes?

Um verschiedene Typen an ein und der selben Speicherstelle zu haben. Von einem nicht aktiven (sprich nicht-zuletzt-geschriebenen) Union-Member lesen ist UB.
Das ist auch der Grund weshalb z.B. std::variant (ein fancy Union) in so einem Fall eine Exception wirft.


/edit
Mein Tip bezüglich Bitfelder. Tu so als gäbs die nicht. Der einzig valide Anwendungsfall ist echt jener wo man einen Haufen boolscher Werte braucht und nicht für jedes ein ganzes Byte verschwenden will. Dabei sind dann aber auch Alignment, Padding, sämtliche Offsets und sonstige Späße schlichtweg egal.


wergor schrieb am 24.09.2020 um 17:53

Zitat aus einem Post von Vinci
Code: C++
template<uint32_t Mask, uint32_t Value>
void set(uint32_t volatile* reg) {
  reg = ... /* Zur Compilezeit kalkulierter Werte aus Mask & Value */
}

int main() {
  set<USART1_CR_AVG, 2>(USART1->CR);
}
sowas werde ich versuchen - die library war eine der ersten wo ich es mit bit fields probiert habe, davor hatte ich immer funktionen die basierend auf der maske die bits gesetzt / gelesen haben.

Zitat aus einem Post von Vinci
Von einem nicht aktiven (sprich nicht-zuletzt-geschriebenen) Union-Member lesen ist UB.
Das ist auch der Grund weshalb z.B. std::variant (ein fancy Union) in so einem Fall eine Exception wirft.
danke, das wusste ich nicht.

Zitat aus einem Post von Vinci
/edit
Mein Tip bezüglich Bitfelder. Tu so als gäbs die nicht.
mache ich, nach dem thread hab ich eh nicht mehr viel lust die zu benutzen :D


wergor schrieb am 30.09.2020 um 10:15

Zitat aus einem Post von Vinci
Code: C++
template<uint32_t Mask, uint32_t Value>
void set(uint32_t volatile* reg) {
  reg = ... /* Zur Compilezeit kalkulierter Werte aus Mask & Value */
}
was ist der vorteil der non-type parameter in dem fall? wird damit nur sichergestellt dass mask und value zur compilezeit bekannt sind oder bringt das noch mehr?


Vinci schrieb am 30.09.2020 um 20:45

Zitat aus einem Post von wergor
was ist der vorteil der non-type parameter in dem fall? wird damit nur sichergestellt dass mask und value zur compilezeit bekannt sind oder bringt das noch mehr?

Richtig erkannt, solang C++ keine constexpr Parameter kennt kann man sonst nicht sicherstellen dass die Berechnung zur Compilezeit erfolgt.


wergor schrieb am 30.09.2020 um 22:27

danke :)




overclockers.at v4.thecommunity
© all rights reserved by overclockers.at 2000-2025