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:
1
If enough space remains, a bit-field that immediately follows
2
another bit-field in a structure shall be packed into adjacent bits
3
of the same unit.
das ist schon mal gut; wenn's anders wäre brauchte man kein Bitfeld :)
1
If insufficient space remains, whether a bit-field that does
2
not fit is put into the next unit or overlaps adjacent units
3
is implementation-defined.
egal, das kann mit Hardware-Registern nicht passieren
1
The order of allocation of bit-fields within a unit (high-order
2
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
1
The alignment of the addressable storage unit is unspecified.
egal, die Hardware ist ja aligned
1
Each non-bit-field member of a structure or union object is aligned
2
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
1
As specified in 6.7.2 above, if the actual type specifier used
2
is int or a typedef-name defined as int, then it is
3
implementation-defined whether the bit-field is signed or unsigned.
egal, da geht's nur um "signed int" und "unsigned int" vs. "int"
1
Within a structure object, the non-bit-field members and the units
2
in which bit-fields reside have addresses that increase in the order
3
in which they are declared.
das ist ja mal sehr schön definiert
1
The value of at most one of the members can be stored in a union
2
object at any time. A pointer to a union object, suitably converted,
3
points to each of its members (or if a member is a bit- field,
4
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.
1
offsetof(type, member-designator)
2
If the specified member is a bit-field, the behavior is undefined.
das ist natürlich schade ;)
1
It is not safe to concurrently update two non-atomic bit-fields
2
in the same structure if all members declared between them are also
3
(nonzero-length) bit-fields, no matter what the sizes of those
4
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:
1
If the container of a non-volatile bit-field overlaps a volatile
2
bit-field then it is undefined whether access to the non-volatile
3
field will cause the volatile field to be accessed.
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.
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.
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 ;-)
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.
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?
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):
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.
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.
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.
1
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.
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:
1
p->bit0=1;// ggf. RMW
2
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.
1
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.
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.
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!
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?
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.
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.
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.
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.
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.
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.
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
1
0=internalTrigger0
2
1=internalTrigger1
3
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.