c++: bit field
wergor 23.09.2020 - 18:08 5512 9
wergor
connoisseur de mimi
|
ich hab folgende union: 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: 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 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: 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
AdministratorLegends never die
|
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
|
endianness habe ich schon berücksichtigt, das habe ich im post vergessen zu erwähnen. (ein paar checks sind zwecks lesbarkeit entfernt) 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.: uint16_t cfg = 0;
readConfigRegister(cfg);
config_.reg = cfg;
bool ready = config_.bits.data_ready;
|
Vinci
hatin' on summer
|
static_assert(sizeof(ConfigRegister::bits) == 2);
Ups. ![;)](/images/smilies/wink.gif) 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](/images/smilies/tongue.gif) deshalb auch hier die "Lösung": #pragma pack(1)
Bearbeitet von Vinci am 23.09.2020, 19:13
|
wergor
connoisseur de mimi
|
static_assert(sizeof(ConfigRegister::bits) == 2);
Ups. ![;)](/images/smilies/wink.gif)
äääh.. jo. fürst nächste mal weis ichs https://en.cppreference.com/w/c/language/bit_field also zurück zu & und >> :'( /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](/images/smilies/tongue.gif) aber wenn type punning UB ist, warum gibts dann union, das ist ja genau nix anderes? /edit2 So wie ich die kenn hörst du sowieso nicht auf mich ![:p](/images/smilies/tongue.gif) deshalb auch hier die "Lösung": #pragma pack(1) stimmt gar ned, ich hör meistens auf dich ![:D](/images/smilies/biggrin.gif) 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
|
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: #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. 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. die lib kommt schon von mir 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
|
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. 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. /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
|
wergor
connoisseur de mimi
|
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
|
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
|
danke
|