mikrocontroller.net

Forum: Compiler & IDEs Bitfelder, Portabilität und STM32


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Bauform B. (bauformb)


Bewertung
0 lesenswert
nicht lesenswert
guten Morgen,

für einen STM32L451 wollte ich Funktionen aus einem STM32F205-Programm 
recyclen. Alles ganz primitiv. Die Bausteine der STM32-Familie sollen ja 
leicht austauschbar sein. Nur zwei Beispiele von den UARTs: Das Bit für 
die Wortbreite heißt jetzt M0 statt M, sie hängen an einem anderen Bus 
wodurch sich alle Bits und Register im RCC ändern. Wie funktioniert hier 
die Portabilität wenn ich in jeder Funktion eine Kleinigkeit ändern 
muss?

Die 12505 #define (5300 beim F205) aus dem stm32l451xx.h helfen da nur 
bedingt :) Also wüsste ich jetzt gerne, was an Bitfeldern wirklich so 
schlecht ist. Ich finde eigentlich nur Aussagen wie zum Beispiel
Wilhelm M. schrieb:
> Es gibt unzählige Versuche, das mit bit-fields zu lösen - auch hier im
> Forum.
>
> Die sind alle falsch bis zweifelhaft, weil sie ausnahmslos mindestens
> implementation-defined behaviour dabei haben, manche im Kontext von C++
> mit union as type-punning UB.

Das ist so pauschal sicher berechtigt, aber in der ganz konkreten 
Anwendung für STM32-Hardware-Register? Die meisten Versuche hat man wohl 
für AVR gemacht, aber die sind doch nicht so viel komplizierter? Oder 
sind das alles theoretische Einwände von C++ Programmierern?

Ich hab' mal bei MISRA und in den Drafts für C1x und C2x nach Bitfeldern 
gesucht. Als erstes fiel auf, dass ein Preprozessor nur 4095 Macros 
können muss, also, wenn's portabel sein soll scheiden die STM-Header 
schon mal aus ;) MISRA mag keine unions, aber Bitfelder sind erlaubt. 
Sie geben dazu nützliche Tipps mit Erklärung, z.B. warum ein Bitfeld aus 
1 Bit nicht signed sein sollte. Lesenswert! Also Argumente aus dem 
C-Standard:
If enough space remains, a bit-field that immediately follows
another bit-field in a structure shall be packed into adjacent bits
of the same unit.
das ist schon mal gut; wenn's anders wäre brauchte man kein Bitfeld :)
If insufficient space remains, whether a bit-field that does
not fit is put into the next unit or overlaps adjacent units
is implementation-defined.
egal, das kann mit Hardware-Registern nicht passieren
The order of allocation of bit-fields within a unit (high-order
to low-order or low-order to high-order) is implementation-defined.
o.k.! ein neuer Compiler kostet ggf. etwas Fleissarbeit (oder nicht, 
siehe unten). Und selbst wenn: ich müsste die Schleife, in der die 
struct erzeugt wird, rückwärts laufen lassen
The alignment of the addressable storage unit is unspecified.
egal, die Hardware ist ja aligned
Each non-bit-field member of a structure or union object is aligned
in an implementation-defined manner appropriate to its type.
o.k., aber dann nimmt man statt (u)int16_t und (u)int8_t auch Bitfelder
As specified in 6.7.2 above, if the actual type specifier used
is int or a typedef-name defined as int, then it is
implementation-defined whether the bit-field is signed or unsigned.
egal, da geht's nur um "signed int" und "unsigned int" vs. "int"
Within a structure object, the non-bit-field members and the units
in which bit-fields reside have addresses that increase in the order
in which they are declared.
das ist ja mal sehr schön definiert
The value of at most one of the members can be stored in a union
object at any time. A pointer to a union object, suitably converted,
points to each of its members (or if a member is a bit- field,
then to the unit in which it resides), and vice versa.
kein Bitfeld-Problem; man braucht ja, wenn überhaupt, nur eine union aus 
genau einem uint32_t und einer struct mit den Bits.
offsetof(type, member-designator)
If the specified member is a bit-field, the behavior is undefined.
das ist natürlich schade ;)
It is not safe to concurrently update two non-atomic bit-fields
in the same structure if all members declared between them are also
(nonzero-length) bit-fields, no matter what the sizes of those
intervening bit-fields happen to be.
o.k., aber das geht ja nie (ja, Spezial-Register oder einzelne Bits mit 
bit-banding)

mehr undefiniertes aus dem Internet¹
- the endianess of bit fields larger than one byte
- how bit fields are promoted implicitly by the integer promotions
- whether signed bit fields are one's compliment or two's compliment
alles egal solange man nur unsigned benutzt, aber "integer promotion" 
mit signed könnte, wie immer, überraschend sein.

TLDR: im aapcs, Procedure Call Standard for the Arm Architecture, 
IHI0042, ist das alles ziemlich genau definiert. Der GCC kennt 
-mabi=aapcs und Compiler, das nicht kennen, sind einfach das falsche 
Werkzeug.

Noch eine nette Warnung aus dem aapcs, aber das ist auch kein 
Bitfeld-Problem:
If the container of a non-volatile bit-field overlaps a volatile
bit-field then it is undefined whether access to the non-volatile
field will cause the volatile field to be accessed.

[1] 
https://embeddedgurus.com/stack-overflow/2009/10/effective-c-tip-6-creating-a-flags-variable/#comment-2390

von Walter T. (nicolas)


Bewertung
4 lesenswert
nicht lesenswert
Bauform B. schrieb:
> Wie funktioniert hier
> die Portabilität wenn ich in jeder Funktion eine Kleinigkeit ändern
> muss?

Wenn jede Deiner Funktionen direkt auf die Hardware zugreift, solltest 
Du Dein Abstraktionskonzept überdenken.

Bei einem halbwegs sinnvollen Abstraktionskonzept tauscht man für jede 
Prozessorfamilie für jede unterschützte Peripherie 1 bis 5 Funktionen 
aus und alles, was auf einem "höheren Layer" ist, bleibt gleich.

von Bauform B. (bauformb)


Bewertung
0 lesenswert
nicht lesenswert
Walter T. schrieb:
> Bei einem halbwegs sinnvollen Abstraktionskonzept tauscht man für jede
> Prozessorfamilie für jede unterschützte Peripherie 1 bis 5 Funktionen
> aus und alles, was auf einem "höheren Layer" ist, bleibt gleich.

Einverstanden, aber die kleinen unnötigen Änderungen sind trotzdem 
ärgerlich - besonders, wenn man damit wirbt, wie austauschbar alles ist.

Ein kleiner Trost: bei der Hardware ist es viel krasser. Der 
STM32L412RBT6P und der ohne ...P sind identisch bis auf einen Pin für 
VDD12, aber 15 Pins sind um einen versetzt.

von Wilhelm M. (wimalopaan)


Bewertung
2 lesenswert
nicht lesenswert
Bauform B. schrieb:
> Das ist so pauschal sicher berechtigt, aber in der ganz konkreten
> Anwendung für STM32-Hardware-Register? Die meisten Versuche hat man wohl
> für AVR gemacht, aber die sind doch nicht so viel komplizierter? Oder
> sind das alles theoretische Einwände von C++ Programmierern?

Wenn Du mit IB leben kannst, ist doch alles in Ordnung. Sprich, wenn Du 
bit-fields nur für eine Architektur und mit einem definierten ABI 
(Compiler) einsetzt, und sinnvollerweise die IB-Eigenschaften der 
bit-fields zur Compile-Zeit checkst. Ggf. spielen auch RMW-Zyklen eine 
Rolle.

Allerdings bleibt eben der Unterschied zwischen C und C++, das ein 
type-punning mit unions (egal, ob bit-fields oder nicht) in C++ eben 
nicht geht, das ist dann UB.

Den oben genannten Check der IB-Eigenschaften (mindestens der 
bit-Positionen im underlying-type) zur Compilezeit (etwa als 
static_assert(...)) auszuführen ist m.E. derzeit in C++ noch nicht 
möglich, da müssen wir auf C++20 und constexpr std::bit_cast warten. 
Dann kann man das aber sicher prüfen.

Insofern steht bit-fields also ggf. eine schöne Renaissance bevor ;-)

von W.S. (Gast)


Bewertung
1 lesenswert
nicht lesenswert
Bauform B. schrieb:
> Also wüsste ich jetzt gerne, was an Bitfeldern wirklich so
> schlecht ist.

Was haben Bitfelder bei der Bedienung von HW-Registern in 
Peripherie-Cores zu suchen?

Dein Denkfehler besteht darin, daß du Hardware generalisieren willst - 
und das ist kein Abstrahieren von Hardware.

generalisieren: SetzePin(int Port,int Pin, bool Zustand);
abstrahieren:   SchalteMotor(bool ein);

Mache es also anders als bisher. Schreibe dir Lowlevel-Treiber - und 
zwar jeweils einen dedizierten für jede Plattform - aber all diese mit 
einem gemeinsamen Headerfile, das keine Hardware-bezüge mehr aufweist. 
Klassisches Beispiel ist der serielle Port (UART oder VCP).

Sicherlich gibt es da diverse subtile Differenzen zwischen den µC-Typen, 
aber die beherrschst du garantiert nicht mit irgendwie konstruierten 
Bitfeldern, sondern nur mit dem jeweils an den µC-Typ angepaßten 
LL-Treiber.

Aber wenn all deine Treiber nach oben hin das selbe abstrahierte 
Interface haben, dann hast du damit die jeweilige HW-Abstraktionsstufe 
erreicht.

Beispiel: Grafik-Display per I2C an den µC.
Das macht:
- I2C-Handler: baut auf direktem HW-Zugriff auf
- Displayhandler: baut auf Funktionen des I2C-Handlers auf
- GDI: Baut auf dem Display-RAM des Display-Handlers auf
- Grafik-Komponenten: bauen auf Funktionen des GDI auf

Bei anderem µC: I2C-Handler wechseln
Bei anderem Display: Display-Handler wechseln
usw.

W.S.

von Bauform B. (bauformb)


Bewertung
0 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Wenn Du mit IB leben kannst, ist doch alles in Ordnung.

Naja, ich sehe dabei eben (leider?) kein Problem. Ein neuerer/anderer 
Compiler bringt immer Überraschungen und Änderungen mit, bei diesen 
Bitfeldern bin ja jetzt besser vorbereitet als bei allem anderen.

> Sprich, wenn Du bit-fields nur für eine Architektur und mit einem
> definierten ABI (Compiler) einsetzt, und sinnvollerweise die
> IB-Eigenschaften der bit-fields zur Compile-Zeit checkst.

Wie würde denn der Check funktionieren? Ein Laufzeit-Check struct 
gegen Hardware wäre einfach, z.B. gegen ARM CPUID oder die Option Bytes. 
Oder ich lasse den Flash Loader ein statisch initialisiertes Bitfeld 
prüfen, aber Compiler oder Preprozessor?


Walter T. schrieb:
> Bei einem halbwegs sinnvollen Abstraktionskonzept tauscht man für jede
> Prozessorfamilie für jede unterschützte Peripherie 1 bis 5 Funktionen
> aus und alles, was auf einem "höheren Layer" ist, bleibt gleich.

W.S. schrieb:
praktisch das gleiche, nur mit mehr Worten, aber die Frage war 
eigentlich
> Bauform B. schrieb:
>> Also wüsste ich jetzt gerne, was an Bitfeldern wirklich so
>> schlecht ist.

W.S. schrieb:
> Was haben Bitfelder bei der Bedienung von HW-Registern in
> Peripherie-Cores zu suchen?

Alles, weil, es geht hier nur um die Low Level Treiber ganz innen drin 
und nur dafür will ich Bitfelder verwenden. Du meinst also, mein Plan 
ist grundfalsch, völlig unbrauchbar und nicht der Rede wert? Soll ich 
lieber (23 << 17) schreiben?

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
Bauform B. schrieb:
> Naja, ich sehe dabei eben (leider?) kein Problem.

Wenn Du also sichergestellt hast, dass Deine Bit-Positions korrekt sind, 
und Du alle alignments korrekt hast, und etwa folgendes schreibst (hier: 
Beispiel 8-Bit Register):
struct Bits {
    uint8_t bit0:1;
    uint8_t bit1:1;
    uint8_t bit2:1;
    uint8_t bit3:1;
    uint8_t bit4:1;
    uint8_t bit5:1;
    uint8_t bit6:1;
    uint8_t bit7:1;
};

int main() {
    auto p = reinterpret_cast<volatile Bits*>(&FLAG_Register);
    p->bit0 = 1; // ggf. RMW

    FLAG_Register = 1; // kein RMW
}

Jetzt ist die spannende Frage weiterhin, ob der Compiler einen 
RMW-Zyklus (Load, OR, Store) daraus macht, oder nicht. Bei einem 
Flag_Register, bei dem die Flags zurückgesetzt werden, wenn man eine "1" 
rein schreibt, ist das RMW - neben der nicht-Atomarität - semantisch 
falsch, weil dann alle gesetzten Flags und nicht nur das eine bestimmte 
zurückgesetzt werden. Dies ist schon ein Fallstrick, dem Du auch mit 
Bit-Shift/Mask begegnen musst. Dann ist die Frage, ob man nicht generell 
dabei bleibt.

von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
Bauform B. schrieb:
> Wie würde denn der Check funktionieren?

Nun ja, wie ein Laufzeitcheck per memcpy(): Bits setzen, memcpy nach 
uint32_t oder uint8_t, und schauen, ob es passt. std::bit_cast ist im 
gegensatz zu memcpy constexpr, d.h. Du kannst es in einem 
constexpr-Kontext bspw. in einem static_cast() zur Compilezeit machen.

von W.S. (Gast)


Bewertung
-3 lesenswert
nicht lesenswert
Bauform B. schrieb:
> Alles, weil, es geht hier nur um die Low Level Treiber ganz innen drin
> und nur dafür will ich Bitfelder verwenden. Du meinst also, mein Plan
> ist grundfalsch, völlig unbrauchbar und nicht der Rede wert? Soll ich
> lieber (23 << 17) schreiben?

Also: Wenn du in derselben Quelldatei schreibst

#define ottokar (23<<17)

dann mach das und verwende ottokar in der Folge. Aber wenn du dieses 
#define ottokar... in irgend einer anderen Datei herumstehen hast und 
folglich dafür eine oder mehrere andere Dateien inkludieren mußt, dann 
schreib lieber (23<<17) und einen Kommentar dahinter, was das bezwecken 
soll.

In einem Lowlevel-Treiber sollte man keine unnötigen Abhängigkeiten von 
irgendwelchen anderen Dateien haben.

Und wenn du Bitfelder zu definieren gedenkst, bist du auf die 
Eigenheiten des jeweiligen Compilers angewiesen.

Und nocheins:
In einem LL-Treiber behandelt man regelmäßig nur einen bestimmten 
Peripherie-Core und entsprechend eng ist der Blickwinkel auf die 
Hardware - zumeist betrifft das nur ein einziges Kapitel im RefManual 
und die dort erklärten Bits der zum Core gehörigen Register. Deshalb ist 
es völlig OK, tatsächlich nur (23<<17) und so zu schreiben.
  if (USART1_ISR & ((1<<5)|(1<<3))) // RX: Zeichen empfangen oder Overrun
Sowas ist völlig OK, da man es hier ausdrücklich NUR mit einem 
speziellen Teil der Peripherie zu tun hat, jedoch NICHT mit 
irgendwelchen (höheren) Algorithmen, die die betreffende Firmware 
ausführen soll. Und das Nachsehen, was ein High oder ein Low bei Bit 3 
oder 5 in USART1_ISR zu bedeuten hat, geht auf diese Weise am 
schnellsten und sichersten - und wenn der Treiber fertig und ausgetestet 
ist, brauchst du dort nicht mehr hineinzuschauen - schon garnicht aus 
anderen Programmteilen.

Und selbst, wenn du das 1<<5 durch einen Bezeichner ersetzt hast, weißt 
du noch immer nicht, was dort ein high oder low bewirkt oder zu sagen 
hat und du mußt deshalb trotz Benennung ins Manual schauen.

Und es hat mit Bitfeldern garnix zu tun.

W.S.

von Bauform B. (bauformb)


Bewertung
0 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Nun ja, wie ein Laufzeitcheck per memcpy(): Bits setzen, memcpy nach
> uint32_t oder uint8_t, und schauen, ob es passt. std::bit_cast ist im
> gegensatz zu memcpy constexpr, d.h. Du kannst es in einem
> constexpr-Kontext bspw. in einem static_cast() zur Compilezeit machen.

Cool, was es alles gibt.

Wilhelm M. schrieb:
  p->bit0 = 1; // ggf. RMW
  FLAG_Register = 1; // kein RMW

> Jetzt ist die spannende Frage weiterhin, ob der Compiler einen
> RMW-Zyklus (Load, OR, Store) daraus macht, oder nicht.

Im ersten Fall würde ich nicht "ggf" schreiben, man muss RMW annehmen. 
Aber wenn er es im zweiten Fall macht, hätte er kein Compiler werden 
sollen.

> Dies ist schon ein Fallstrick, dem Du auch mit Bit-Shift/Mask
> begegnen musst.

Ja, klar, deshalb wäre mir das auch garnicht aufgefallen. So ein 
Register belegt genau ein Wort in der struct und dazu gibt es die 
passenden Masken und gut ist, wie immer. Welche Hardware sich dahinter 
verbirgt, muss man sowieso wissen.

> Dann ist die Frage, ob man nicht generell dabei bleibt.

Das hat mich jetzt direkt ins Grübeln gebracht. Ein Vorteil wäre, dass 
RMW im Quelltext besser auffällt, weil man es selbst hinschreiben muss 
;)

Den Vorteil, dass sich jeder sofort auskennt, verspiele ich sowieso. Die 
Original-ST-Definitionen sind mir viel zu sperrig und trotzdem zu 
lückenhaft.

Für Bitgruppen wie Alternate Functions oder Timer-Betriebsart mag ich 
enums. Ohne Bitfelder muss ich dazu noch die Masken definieren. Der 
Aufwand spielt in den Headern keine große Rolle, aber das Programm wird 
unleserlich.

Manchmal muss man mehrere einzelne Bits gleichzeitig setzen. Das kostet 
mit Bitfeldern viel Platz oder geht garnicht, wenn die wirklich 
gleichzeitig geschrieben werden müssen. Also baut man eine union, damit 
bekommt man eine union und Bits werden doppelt definiert, oder man 
verzichtet bei so einem Register auf die Bitfelder.

Solche Sachen sind mir bisher garnicht aufgefallen und ich hab's einfach 
passend gemacht. Weil ich die Header genau dann aus dem Manual abgetippt 
habe, wenn ich sie gebraucht habe. Jetzt wollte ich vorher möglichst 
einheitliche Header möglichst automatisch erzeugen. Naja.


W.S. schrieb:

> In einem Lowlevel-Treiber sollte man keine unnötigen Abhängigkeiten von
> irgendwelchen anderen Dateien haben.
> In einem LL-Treiber behandelt man regelmäßig nur einen bestimmten
> Peripherie-Core und entsprechend eng ist der Blickwinkel auf die
> Hardware - zumeist betrifft das nur ein einziges Kapitel im RefManual
> und die dort erklärten Bits der zum Core gehörigen Register. Deshalb ist
> es völlig OK, tatsächlich nur (23<<17) und so zu schreiben.
 if (USART1_ISR & ((1<<5)|(1<<3))) // RX: Zeichen empfangen oder Overrun
Ziemlich extrem und genau das Gegenteil von meinem Plan. Aber 
interessant, und natürlich funktioniert es auch so.

> Und selbst, wenn du das 1<<5 durch einen Bezeichner ersetzt hast, weißt
> du noch immer nicht, was dort ein high oder low bewirkt oder zu sagen
> hat und du mußt deshalb trotz Benennung ins Manual schauen.

Nein, bei weitem nicht so oft. Ja, ein guter Kommentar hilft genauso, 
aber Kommentare neigen zum verfaulen. Merke: kein Kommentar ist besser 
als ein falscher.

Also, vielen Dank für alle Argumente.

von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
Bauform B. schrieb:
> Im ersten Fall würde ich nicht "ggf" schreiben, man muss RMW annehmen.

Auch da muss ich Dich enttäuschen. Beispiel AVR: hier kann der avr-core 
für Adressen < 0x20 tatsächlich atomar Bits setzen mit sbi/cbi und der 
Compiler setzt das auch so ein, für Adressen >= 0x20 nicht mehr und da 
wirds dann ein lds/or/sts durch den Compiler.

Natürlich "weiß" man das, wenn man eine Abstraktion für eine int. 
Peripherie schreibt und auf deren Register zugreift. Man muss die 
Adressen der Register ja kennen, um darauf zuzugreifen. Aber dann hier 
eine Fallunterscheidung einzubauen - mal bit bit-fields, mal mit 
set/shift/mask - finde ich unschön. Dann besser immer set/shift/mask und 
explizite RMW.

Im übrigen sind Prozessorregister auch als volatile zu kennzeichnen, um 
Optimierungen des Zugriffs abzuschalten und ein reorder-barrier 
einzuführen. C++ hat genau aus diesem Grund die compund-operators wie 
etwa += für volatile ge-deprecated, damit das auffällt, dass hier ggf. 
RMW im Spiel ist.

von Wilhelm M. (wimalopaan)


Bewertung
1 lesenswert
nicht lesenswert
Bauform B. schrieb:
> Nein, bei weitem nicht so oft. Ja, ein guter Kommentar hilft genauso,
> aber Kommentare neigen zum verfaulen. Merke: kein Kommentar ist besser
> als ein falscher.

Scott Meyers: "Kommentiere nur, was der Code nicht sagen kann, und nicht 
das, was er nicht sagt." Und das ist dann eine sehr entscheidende 
Handlungsanweisung für den Programmierer!

von Bernd K. (prof7bit)


Bewertung
0 lesenswert
nicht lesenswert
Kannst Du mal kurz erklären inwiefern Bitfelder helfen würden wenn das 
eigentliche Problem das ist daß in den neuen Headern die selben Bits 
alle subtil unterschiedliche Namen haben? Die würden doch dann wenn man 
die Header stattdessen auf Bitfeldern aufgebaut hätte ebenfalls leicht 
andere Namen bekommen und du müsstest immer noch Deinen Code anpassen!?

Oder was genau ist jetzt überhaupt das Problem?

: Bearbeitet durch User
von W.S. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Bauform B. schrieb:
> Nein, bei weitem nicht so oft.

Oh doch. Guck dir mal die diversen Register in den PLL- und 
Takt-Zentralen bei verschiedenen Cortexen an. Was du da an Bits findest 
und was davon lowaktiv oder sonstwie codiert ist und was nicht, das 
kannst du dir an keinem Bitnamen merken - oder dein Name für's Bit 
wird länger als ne ungarische Eisenbahnstation und du kannst ihn nicht 
mehr fehlerfrei hinschreiben ohne zu kopieren.

Selbst bei simpleren Dingen wie z.B. den Einstellregister für die GPIO's 
mancher µC wandeln sich da die Bedeutungen diverser Bits in Abhängigkeit 
vom Zustand anderer Bits (wenn input dann ob Hysterese, wenn output dann 
Treiberstärke und sowas eben).

Bei kleinen µC wie PIC16 oder AVR ist das alles viel simpler, da gibt es 
auch keine verschiedenen Arten des Zugriffs, sondern nur eine Art - und 
wenn man von dieser Denke ausgeht, dann kommt man eben zu dem Ansatz, 
den ich bei deinem Eröffnungspost gesehen habe. Aber der ist bei 
genauerer Betrachtung eben nicht zielführend, sondern landet irgendwann 
in einer Kombination aus uneffizientem Code und einer Flut von #ifdef's 
in den Quellen.

W.S.

von Bernd K. (prof7bit)


Bewertung
1 lesenswert
nicht lesenswert
W.S. schrieb:
> länger als ne ungarische Eisenbahnstation und du kannst ihn nicht
> mehr fehlerfrei hinschreiben ohne zu kopieren

Ctrl + Space

von Bauform B. (bauformb)


Bewertung
0 lesenswert
nicht lesenswert
Wilhelm M. schrieb:
> Natürlich "weiß" man das, wenn man eine Abstraktion für eine int.
> Peripherie schreibt und auf deren Register zugreift. Man muss die
> Adressen der Register ja kennen, um darauf zuzugreifen. Aber dann hier
> eine Fallunterscheidung einzubauen - mal bit bit-fields, mal mit
> set/shift/mask - finde ich unschön.

Ich auch, aber für die GPIO von ST braucht man Masken mit 1,2 und 4 Bit 
pro Pin. Beim ADC gibt's die Kanalwahl in 6er-Gruppen und die Samplezeit 
in 3er-Gruppen, also mal 5 und mal 10 Kanäle pro Register. Zum Ausgleich 
sind die 6er-Gruppen in zwei Registern nicht an der gleichen Position.


Bernd K. schrieb:
> Kannst Du mal kurz erklären inwiefern Bitfelder helfen würden wenn das
> eigentliche Problem das ist daß in den neuen Headern die selben Bits
> alle subtil unterschiedliche Namen haben?

Die subtil unterschiedlichen Namen waren nur der Auslöser nochmal drüber 
nachzudenken. Wenn ich sowieso alles anfassen muss, könnte ich es auch 
gleich ganz anders machen. Früher hab' ich Bitfelder einfach so benutzt, 
es hat ja funktioniert. Nachdem aber alle davon abraten, war das die 
Gelegenheit.

Außerdem hatte ich gehofft, dass ich die Monster-Header von ST zu etwas 
handlichem konvertieren kann. Das geht so leidlich, ich spare viel 
Tipparbeit, aber es gibt viel zu viele Sonderfälle und Ungereimtheiten. 
Das vernebelt den Blick auf die richtige Lösung - äh, Moment, die gibt's 
ja garnicht.

von Bernd K. (prof7bit)


Bewertung
0 lesenswert
nicht lesenswert
W.S. schrieb:
> Bei kleinen µC wie PIC16 oder AVR ist das alles viel simpler, da gibt es
> auch keine verschiedenen Arten des Zugriffs, sondern nur eine Art

Ich sag nur Pin-Toggeln.

von Bernd K. (prof7bit)


Bewertung
4 lesenswert
nicht lesenswert
Bauform B. schrieb:
> Außerdem hatte ich gehofft, dass ich die Monster-Header von ST zu etwas
> handlichem konvertieren kann. Das geht so leidlich, ich spare viel
> Tipparbeit, aber es gibt viel zu viele Sonderfälle und Ungereimtheiten.

Es ist die Organisation der Register und Bits die beim STM32 
grundsätzlich maximal unfreundlich ist. Dort ist alles in erster Ordnung 
nach der Funktion und in zweiter Ordnung (innerhalb der Register!) nach 
Kanal sortiert. Als Beispiele wie man das auch anders machen kann nenn 
ich mal Kinetis, da ist alles immer in erster Ordnung nach dem Kanal und 
in zweiter Ordnung nach der Funktion sortiert. Dort haben zwei 
Timerkanäle das exakt identische Struct und die Bits heißen alle gleich 
und es gibt auch nur ein einziges define für jedes Bit und das kann man 
dann in jedem Kanal nutzen!

Du nimmst also unterschwellig (ohne es genau benennen zu können) das 
selbe Problem wahr wie ich auch als man mich zum Umstieg von Kinetis auf 
GD(STM)32 gezwungen hat, ich empfand/empfinde es als ein einziges Chaos 
und der Hund liegt in der Anordnung der Register und Sortierung der Bits 
begraben, die Header können nichts dafür, die müssen so chaotisch und 
aufgeblasen sein um das Chaos in der Hardware abzubilden. Der einzige 
Ausweg ist eine HAL-Schicht die das wieder wegabstrahiert.

Sowas war beim Kinetis nie wirklich nötig. Dessen Register kann man 
blind spielen wie ein Klavier und wenn man das ganze Klavier um eine 
Oktave nach links verschiebt gehts immer noch. Beim STM32 liegen alle C 
nebeneinander, dann alle D, usw. Und die schwarzen Tasten sind alle ganz 
rechts, mal vier stück auf einer Taste, mal zwei, so ungefähr kann man 
sich das bildlich vorstellen.

: Bearbeitet durch User
von Bauform B. (bauformb)


Bewertung
-1 lesenswert
nicht lesenswert
Bernd K. schrieb:
> Kinetis

Oha, die Umstellung ist hart. Bei Freescale hatte ich immer den 
Eindruck, dass die Chip-Entwickler den auch selbst programmieren - ganz 
im Gegensatz zu STM. Bei mir war es vorher nur der S12, da konnte ich 
mir einbilden, dass 32 Bit eben so kompliziert sind ;)

> der Hund liegt in der Anordnung der Register und Sortierung der Bits
> begraben, die Header können nichts dafür, die müssen so chaotisch und
> aufgeblasen sein um das Chaos in der Hardware abzubilden.

Da war doch was ... Überbringer der schlechten Nachricht...

> Dessen [Kinetis] Register kann man blind spielen wie ein Klavier und
> wenn man das ganze Klavier um eine Oktave nach links verschiebt gehts
> immer noch. Beim STM32 liegen alle C nebeneinander, dann alle D, usw.
> Und die schwarzen Tasten sind alle ganz rechts, mal vier stück auf einer
> Taste, mal zwei, so ungefähr kann man sich das bildlich vorstellen.

Sehr schön! Man sollte das als Vorwort in den großen STM32-Artikel 
einbauen.

von W.S. (Gast)


Bewertung
-3 lesenswert
nicht lesenswert
Bernd K. schrieb:
> und der Hund liegt in der Anordnung der Register und Sortierung der Bits
> begraben

Ach nö - der wahre Hund liegt noch ganz wo anders begraben. Nämlich 
darin, daß ST mit allen Mitteln versucht, seine Kunden an sich zu 
binden. Auch mit dem Mittel der aktiven Kundenverblödung.

Deswegen sind die .h so aufgebläht und zugleich wird den Leuten 
eingetrichtert, daß sowas wie (1<<7) weitaus bösartiger sei als goto und 
man ausschließlich die von ST kreierten Namen zu benutzen hat.

Ebenso, daß man die ST-Lib, dann HAL, Cube und dergleichen zu benutzen 
hat. Unter dem Strich lautet die Botschaft "ihr sollt nichts anderes 
tun, als das zu verwenden, was euch ST dargereicht hat und wer das 
anders macht, ist ein Ketzer. Steinigt ihn" oder so ähnlich.

Das ist der wahre Begräbnisort des besagten Hundes.

Ich mach's anders und ich lasse mich nicht auf irgend einen Hersteller 
festnageln, auch nicht auf irgendwelche Vorurteile gegenüber 
Formulierungen - und das hat sich bislang bestens bewährt.

W.S.

von Bauform B. (bauformb)


Bewertung
1 lesenswert
nicht lesenswert
W.S. schrieb:
> zugleich wird den Leuten
> eingetrichtert, daß sowas wie (1<<7) weitaus bösartiger sei als goto

ist es ja auch. (1 << 7) ist ein Herdentier und wird am Besten in Header 
Files gehalten.

> und man ausschließlich die von ST kreierten Namen zu benutzen hat.

Wie bitte? Man will sich doch keine eigenen ausdenken? Ich wäre froh, 
wenn ST noch mehr Namen kreieren würde, also im Reference Manual. Bei 
den Timern gibt's so Sachen wie
0 = internal Trigger 0
1 = internal Trigger 1
2 = ...
ja, und wo bitte sind die angeschlossen?
Was sie in die Header schreiben sollte natürlich dazu passen, das muss 
auch noch besser werden.

> Ebenso, daß man die ST-Lib, dann HAL, Cube und dergleichen zu
> benutzen hat.

Wo steht das (außer in diesem Forum)? Ich glaube eher, die Programmierer 
mögen das so, immerhin gehört schon fast kriminelle Energie dazu, CubeMX 
und Zubehör überhaupt zu bekommen. Außerdem, ST muss sowas liefern, weil 
NXP und Freunde das auch tun.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.