"Christmas - the time to fix the computers of your loved ones" « Lord Wyrm

c++: bit field

wergor 23.09.2020 - 18:08 5224 9
Posts

wergor

connoisseur de mimi
Avatar
Registered: Jul 2005
Location: graz
Posts: 4019
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

Administrator
Legends never die
Avatar
Registered: Aug 2003
Location: nö
Posts: 25371
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

connoisseur de mimi
Avatar
Registered: Jul 2005
Location: graz
Posts: 4019
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

hatin' on summer
Registered: Jan 2003
Location: Wien
Posts: 5771
Code: C++
static_assert(sizeof(ConfigRegister::bits) == 2);

Ups. ;)


Zitat
The following properties of bit fields are undefined:
  • The effect of calling offsetof on a bit field
The following properties of bit fields are unspecified:
  • Alignment of the allocation unit that holds a bit field
The following properties of bit fields are implementation-defined:
  • Whether bit fields of type int are treated as signed or unsigned
  • Whether types other than int, signed int, unsigned int, and _Bool are permitted
  • Whether atomic types are permitted
  • Whether a bit field can straddle an allocation unit boundary
  • The order of bit fields within an allocation unit (on some platforms, bit fields are packed left-to-right, on others right-to-left)

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)
Bearbeitet von Vinci am 23.09.2020, 19:13

wergor

connoisseur de mimi
Avatar
Registered: Jul 2005
Location: graz
Posts: 4019
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

hatin' on summer
Registered: Jan 2003
Location: Wien
Posts: 5771
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.
Bearbeitet von Vinci am 23.09.2020, 20:06

wergor

connoisseur de mimi
Avatar
Registered: Jul 2005
Location: graz
Posts: 4019
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

connoisseur de mimi
Avatar
Registered: Jul 2005
Location: graz
Posts: 4019
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

hatin' on summer
Registered: Jan 2003
Location: Wien
Posts: 5771
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

connoisseur de mimi
Avatar
Registered: Jul 2005
Location: graz
Posts: 4019
danke :)
Kontakt | Unser Forum | Über overclockers.at | Impressum | Datenschutz