Schon hundertmal besprochen wahrscheinlich, aber ich finde keine
überzeugende Antwort.
Warum schreiben die Meisten in AVR-C :
testbyte = PINB & (1 << PINB3)
statt wie früher üblich einfach zu maskieren :
testbyte = PINB & 8
Einser dreimal links weil PINB3 mit 3 vorbelegt,
ist exakt dasselbe wie 2^3=8 ... was soll's also ???
Reiner D. schrieb:> testbyte = PINB & (1 << PINB3)
ah, in PINB wird das Bit PINB3 gesetzt
>> statt wie früher üblich einfach zu maskieren :>> testbyte = PINB & 8
ah verdammt, welcher Pin wird mit 8 gesetzt? ist das jetzt die
überarbeitete Generation oder noch die alte?
außerdem kümmert sich der Präprozessor darum, dass (1 << PINB3) zu 8
wird (oder auch zu 1 oder 2 oder 4 oder was auch immer da mit einer
neuen µC Familie daher kommen könnte)
Reiner D. schrieb:> testbyte = PINB & (1 << PINB3)>> statt wie früher üblich einfach zu maskieren :>> testbyte = PINB & 8
In C hat sich die Shift-Schreibweise durchgesetzt.
Andere Dialekte als AVR-C verwenden die Schreibweise ja auch.
Aber: Mach wie du willst. Das stößt höchtens jemandem sauer auf, der an
die Shift-Schreibweise gewöhnt ist (als ca. 90% aller C-Programmierer).
Auf jeden Fall besser als magic numbers.
Daniel F. schrieb:> ah verdammt, welcher Pin wird mit 8 gesetzt? ist das jetzt die> überarbeitete Generation oder noch die alte?
Meinst du mit "Generation" die Menschen ?
Haa ! Da kommen wir zusammen. (Wobei ich alt bin aber auch ständig
überarbeitet war ...)
Das wäre dann also ein Ratespiel, 2 hoch was gibt denn 8 ??
Erzähl mit doch nicht, daß es irgendjemand gibt, der C programmiert,
aber nicht die 2-er Potenzen wenigstens bis 256 auswendig kennt ....
Daß der Compiler 8 draus macht ist ja klar, sonst könnte er auf Maschine
ja nicht maskieren :-)
Ich schlage für die Zukunft vor :
testbyte = PINB & (1 << (PINB3+1))/2 | 0 ; //wahrscheinlich ein fehler
drin
Reiner D. schrieb:> Schon hundertmal besprochen wahrscheinlich, aber ich finde keine> überzeugende Antwort.>> Warum schreiben die Meisten in AVR-C :>> testbyte = PINB & (1 << PINB3)>> statt wie früher üblich einfach zu maskieren :>> testbyte = PINB & 8>> Einser dreimal links weil PINB3 mit 3 vorbelegt,> ist exakt dasselbe wie 2^3=8 ... was soll's also ???
Weil es direkt lesbar ist, welches Bit (NAME!) gemeint ist, vor allem
bei den Spezialregistern! Da muss man icht wissen, welcher Bitnummer
das ist, das weil der Compiler durch die Headerfiles.
Rahul D. schrieb:> In C hat sich die Shift-Schreibweise durchgesetzt.
Nein. Auf dem avr gcc vielleicht, bei anderen Compilern bzw. CPUs nicht
unbedingt.
> Auf jeden Fall besser als magic numbers.
Stimmt. Das ist der Hauptgrund. Der Compiler soll schließlich was für
sein geld machen. Quelltext soll so einfach, eindeutig und
selbsterklärend wie möglich sein, fast so, wie ein normaler Text
geschrieben ist.
setLED(GRUEN, ON);
ist deutlich besser als
PORTA |= 16; // gruene LED einschalten.
Reiner D. schrieb:> Das wäre dann also ein Ratespiel, 2 hoch was gibt denn 8 ??> Erzähl mit doch nicht, daß es irgendjemand gibt, der C programmiert,> aber nicht die 2-er Potenzen wenigstens bis 256 auswendig kennt ....
Ja ganz toll, vor allem für andere Register zB. Timer, hilft sicher
extrem beim Debuggen irgendeines komplexen Problems...
testbyte = PINB & 8
ist ja maximal bescheuert, es geht um Pin B3 aber man hat 8 als magic
number im Code, wirklich super.
testbyte = PINB & (1 << 3)
ist ok
testbyte = PINB & (1 << PINB3)
ist besser
außerdem macht es alleine von der "Nachdenklogik" absolut Sinn, man
nimmt ein Bit, und schiebt es an den richtigen Platz, versteht jeder.
Besser ist es noch bei den neuen AVRs zB. Atmega 0 series:
PORTB.OUTSET = PIN3_bm;
Jetzt ist es absolut verständlich:
Aha PORTB
aha OUTSET (also Ausgang setzen statt früher DDRB)
aha PIN Nr. 3
Mit Verlaub, bitte nicht falsch verstehen :
Ist das nicht ein wenig Kindergarten (oder Arduino ;-) ?
Motiv meiner Frage war : ich werde selbst manchmal gefragt, warum dieser
komische Shift benutzt wird, und weil mir keine sinnvolle Antwort
einfällt, hab ich jetzt mal hier gefragt.
Also Antwort : damit auch absolute Anfänger den Code besser lesen
können.
Ist halt heutzutage so. Früher haben die Leute erst Digitaltechnik
gelernt, und dann mit dem Programmieren angefangen.
(Wichtig : wie gesagt bitte nicht falsch verstehen. Ich bezeichne damit
ausdrücklich keinen der hier Antwortenden als Anfänger, Kindergarten
oder ähnlich !!!)
Falk B. schrieb:> Nein. Auf dem avr gcc vielleicht, bei anderen Compilern bzw. CPUs nicht> unbedingt.
Dann schränke ich meine Aussage ein: Die C-Libraries, mit denen ich bis
jetzt gearbeitet habe, verwenden die Shift-Schreibweise.
(Und damit gilt natürlich auch meine Verallgemeinerung bzgl 90% nicht
mehr).
Reiner D. schrieb:> Ist das nicht ein wenig Kindergarten (oder Arduino ;-) ?
Die Gründe wurden dir genannt.
Wenn du sie nicht einsehen möchtest, ist das dein Problem.
Mit Arduino hat das nichts zu tun, dort wird meist ein anderes Pin
Benennungsschemata genutzt.
Reiner D. schrieb:> Also Antwort : damit auch absolute Anfänger den Code besser lesen> können.
Nein. Damit jeder, vom Anfänger bis zum Profi, den Code schneller und
besser lesen kann.
Im Falle der Port-Register mag das auch noch ohne gehen, aber bei allen
anderen Registern kann niemand mit Magic Numbers etwas anfangen, dagegen
mit den Bitnamen sehr wohl.
Oliver
Reiner D. schrieb:> Ist das nicht ein wenig Kindergarten (oder Arduino ;-) ?
Nö, das funktioniert ohne Shift nur deshalb so gut weil die meisten AVR
Registerbits die Bitnummer im Namen haben.
Jetzt willst Du aber mal z.B. ein Bit maskieren, das nicht die Bitnummer
im Namen hat. Dann fängst Du erstmal mit dem Datenblatt an....
Also machen wir es doch lieber einheitlich.
Reiner D. schrieb:> Ist das nicht ein wenig Kindergarten (oder Arduino ;-) ?
Absolut! Weil die Leute nicht in der Lage sind, geeignete Abstraktionen
einzuführen, wie etwa
Das nehm' ich euch nicht ab, daß ihr :
test = 40
nicht auf den ersten Blick lesen könnt. Ihr seid doch Profis hier.
Und wenn ich das Datenblatt z.B. von einem AVR lese, stehen die
Bitpositionen aller Register ja sogar grafisch dargestellt drin, also
"so what" ?
btw : ich habs mal so oder ähnlich gelernt (lesbarkeit..) :
test = test | 40 ; // test = test ODER 2^5 ODER 2^3
Ok, ich danke allen für die Beiträge, mitgenommen habe ich, daß die
Shiftschreibweise eine Konvention ist, die vermutlich eine bessere
Lesbarkeit erzeugen soll. Meinem "Publikum" gebe ich ggf. dann noch mit,
daß 2-er Potenzen und Bitmaskierung unverzichtbare Grundlagen
darstellen, wenn ich merke daß irgend jemand test = 40 nicht lesen kann
;-)
Reiner D. schrieb:> btw : ich habs mal so oder ähnlich gelernt (lesbarkeit..) :>> test = test | 40 ; // test = test ODER 2^5 ODER 2^3
Und dazu schreibst Du dann einen Kommentar?
Was macht denn
Reiner D. schrieb:> Daniel F. schrieb:>> ah verdammt, welcher Pin wird mit 8 gesetzt? ist das jetzt die>> überarbeitete Generation oder noch die alte?>> Meinst du mit "Generation" die Menschen ?
na, eine hypothetische überarbeitete Generation des Prozessors, bei dem
halt aus irgend einem Grund die Register anders belegt sind als bei der
alten. Gleiche Eigenschaften, aber halt an einem anderen Bit (z.B. 4)
wegen irgendeinem Padding oder so.
Ist ja wurscht, es geht ums Prinzip - PINB3 ist ein definierte Offset,
den man ggf. an einer Stelle ändert und alle Vorkommen im Code passen
sich automatisch an. Ohne dass man nach Vorkommen von "8" suchen muss
und diese dann einzeln prüfen ob da jetzt was anderes hin kommt oder
nicht.
F. M. schrieb:> PORTB.OUTSET = PIN3_bm;
Ah ich kenn das nur so! Aber ich meide auch dieses bescheuerte C. Jetzt
kommen wieder die Negativer...
Gruss Chregu
Arduino F. schrieb:> Mit Arduino hat das nichts zu tun, dort wird meist ein anderes Pin> Benennungsschemata genutzt.
Um noch n bischn Kindergarten nachzureichen :D
Ein Schema, viele Schemata. Genau wie ein Praktikum, mehrere Praktika.
/trolloff
Reiner D. schrieb:> Das nehm' ich euch nicht ab, daß ihr :>> test = 40>> nicht auf den ersten Blick lesen könnt.
Jain. Ich könnte, WILL es aber nicht!
> Ihr seid doch Profis hier.
Aber keine Binärfreaks oder Hex-Freaks wie der gute Josef G.
> Und wenn ich das Datenblatt z.B. von einem AVR lese, stehen die> Bitpositionen aller Register ja sogar grafisch dargestellt drin, also> "so what" ?
Und die willst du ALLE im Kopf behalten? Viel Spaß!
Diese Information ist nur INDIREKT wichtig, so wie ein Telefonbuch!
DIREKT interessiert der BitNAME und dessen Funktion!
Du kannst auch gern die Namen der Register weglassen und direkt die
Adressen ansprechen. Viel Spaß!
> Ok, ich danke allen für die Beiträge, mitgenommen habe ich, daß die> Shiftschreibweise eine Konvention ist, die vermutlich eine bessere> Lesbarkeit erzeugen soll.
Das tut sie auch!
> Meinem "Publikum" gebe ich ggf. dann noch mit,> daß 2-er Potenzen und Bitmaskierung unverzichtbare Grundlagen> darstellen, wenn ich merke daß irgend jemand test = 40 nicht lesen kann> ;-)
Programierer wollen sich, nicht erst heute, nicht mit so einem
Krümelkram befassen, der DEUTLICH besser vom Compiler verwaltet wird!
Reiner D. schrieb:> Ist das nicht ein wenig Kindergarten
Das, was du da veranstaltest, erinnert mich schon ein wenig daran. Wenn
andere das anders schreiben wollen als du, was ist denn daran
grundsätzlich schlecht?
Reiner D. schrieb:> Meinem "Publikum" gebe ich ggf. dann noch mit, daß 2-er Potenzen und> Bitmaskierung unverzichtbare Grundlagen darstellen, wenn ich merke daß> irgend jemand test = 40 nicht lesen kann ;-)
Wenn schon einzelne Bits gemeint sind, dann gibt man den Wert nicht
dezimal mit 40 an, sondern hexadezumal mit 0x28. Da sieht man das
Bitmuster sofort. Man muss aber trotzdem im Datenblatt nachschaeun,
welche Funktionen mit den Bits denn nun tatsächlich gesetzt werden. Und
mit
test = (1<<ABC) | (1<<XYZ); ist softort erkennbar, dass die Funktionen
ABC und XYZ gemeint sind.
> ich habs mal so oder ähnlich gelernt (lesbarkeit..) :> test = test | 40 ; // test = test ODER 2^5 ODER 2^3
Wofür soll das gut sein? Da steht einfach nur die Codezeile im Kommentar
in anderen Worten nochmal da. Und sobald etwas geändert wird, ist der
Kommentar automatisch falsch oder er muss überarbeitet werden. Wenn ich
aber
test = (1<<ABC) | (1<<XYZ);
abändere in
test = (1<<ABC) | (1<<DEF);
dann muss ich keinen Kommentar ändern und der Code ist immer noch
leserlich und quasi selbstkommentierend.
Lothar M. schrieb:> Reiner D. schrieb:>> Meinem "Publikum" gebe ich ggf. dann noch mit, daß 2-er Potenzen und>> Bitmaskierung unverzichtbare Grundlagen darstellen, wenn ich merke daß>> irgend jemand test = 40 nicht lesen kann ;-)> Wenn schon, dann nicht dezimal, sondern hex 0x28. Da sieht man das> Bitmuster sofort.
Aber auch nur, wenn es ein Bit pro Nibble ist. Was ist 0xCA? Jaja, man
kann auch die 16 Nibble auswendig lernen und lesen, ist aber nerviger
Unsinn.
> dann muss ich keinen Kommentar ändern und der Code ist immer noch> leserlich und quasi selbstkommentierend.
BINGO!
Reiner D. schrieb:> Ok, ich danke allen für die Beiträge, mitgenommen habe ich, daß die> Shiftschreibweise eine Konvention ist, die vermutlich eine bessere> Lesbarkeit erzeugen soll. Meinem "Publikum" gebe ich ggf. dann noch mit,> daß 2-er Potenzen und Bitmaskierung unverzichtbare Grundlagen> darstellen, wenn ich merke daß irgend jemand test = 40 nicht lesen kann> ;-)
Ich hoffe mal stark, das dein Publikum keine Schüler sind.
Um es mal für dich etwas deutlicher darzustellen:
UCSR0B |= (1<<TXEN0);
UCSR0B |= 8;
Macht beides das gleiche. Der absolute Anfänger kann mit beiden Zeilen
nichts anfangen. Jemand mit etwas Erfahrung sieht in der ersten Zeile
sofort, was passiert, in der zweiten aber nicht.
Das hat überhaupt nichts damit zu tun, ob man Zweierpotenzen im Kopf
ausrechnen kann oder nicht.
Oliver
Oliver S. schrieb:> Das hat überhaupt nichts damit zu tun, ob man Zweierpotenzen im Kopf> ausrechnen kann oder nicht.
Eben. Man will die Codierung der Bits dort lassen wo sie hingehört. Im
Compiler. Menschen können mit Namen deutlich mehr anfangen als Nummern.
TXEN sagt mehr als 8, erst recht wenn es von diesen Codes tausende gibt!
Reiner D. schrieb:> Also Antwort : damit auch absolute Anfänger den Code besser lesen> können.
Nöö.
Weil es jeder besser lesen kann.
Weil sofort klar ist, was man beabsichtigt.
Weil man kein Masochist ist.
Weil es völlig egal ist, auf welchem Bit der Pin oder z.B. beim
TIMSK-Register die Bits TOIE0 und TOIE1 gemappt sind. Die beiden
letzteren liegen z.B. beim Tinyx5 auf Bit 1 und Bit 2. Das interessiert
einfach nicht und kann beim nächsten Prozessortyp anders festgelegt
sein. Und schon muss dein Code intensiv durchforstet und mit dem
Datenblatt abgeglichen werden, und meiner nicht.
Selbst bei den Ports könnte das sein. Mich interessiert doch, was TOIE0
und TOIE1 bewirken und nicht auf welchem Bit sie festgelegt sind. Dafür
hat jemand einmal die Headerfiles erstellt ...
Und wenn man schon direkt Zahlen angeben will, dann wenigstens im
binären oder gerade noch im hexadezimalen Format, aber ganz bestimmt
nicht in DEZ!
Klar, man kann schon mal schreiben
1
DDRB=0;
2
PORTB=0xFF;
wenn man alles auf Eingang schalten und die Pullups am Port aktivieren
will. Aber sonst?
Und wenn man es noch einfacher haben will, dann schaut man die sbit.h
von Peter D. an. Beitrag "Re: Frage zu Struktur für IO-Port bei AVRs"
Falk B. schrieb:>> Meinem "Publikum" gebe ich ggf. dann noch mit,>> daß 2-er Potenzen und Bitmaskierung unverzichtbare Grundlagen>> darstellen, wenn ich merke daß irgend jemand test = 40 nicht lesen kann>> ;-)>> Programierer wollen sich, nicht erst heute, nicht mit so einem> Krümelkram befassen, der DEUTLICH besser vom Compiler verwaltet wird!
Nicht nur das, es ist vor allem eine Fehlerquelle weniger!
Wenn ich magic numbers verwende, und etwas ändere, z.B. die LED in einer
neuen Platinenversion an einem anderen Portbit anschließe, dann muß ich
mühsam alles durchsuchen und anpassen. Mit der ständigen Gefahr eine
Stelle zu übersehen oder eine falsche mitzuändern
Mit Portnamen und Pinbezeichnern muß man bei so einer Änderung nur eine
Stelle in einem Headerfile anpassen.
Außerdem vermeidet man auch gleich im Ansatz die Fehlerquelle, daß
Kommentar und magic number auseinanderlaufen könnten, was dann umso
verwirrender wäre.
Lothar M. schrieb:>> Was ist 0xCA?> 1100 1010
Schon klar. Hast du das im Kopf konvertiert oder mit dem
Windows-Rechner?
> Und 0xCA ist garantiert leichter in einzelne Bits umzurechnen als 204.
Sicher, aber immer noch deutlich schlechter als die direkten Bitnamen.
Falk B. schrieb:> aber immer noch deutlich schlechter als die direkten Bitnamen.
Ja nun, es gibt darüber hinaus auch noch Situationen, wo mehrere Bits im
Verband angesprochen werden und Bitmuster wieder sinnvoll sind (z.B.
wenn am Port ein Lauflicht dargestellt werden soll).
Ganz ungünstig ist dann, wenn der Programmierer nur diese
"Bitschieberei" kennt und die Bitreihenfolge z.B. bei einem Multiplexer
nur partiell oder sogar "verkehrt herum" auscodiert:
J. T. schrieb:> Um noch n bischn Kindergarten nachzureichen :D
Geht auch anders herum. Einzahl von Spaghetti ...
Ich würde die genutzten Pins und Register eh immer alle mit defines
ersetzen:
#define HC595_PIN_CLK PIN3
#define HC595_PIN_CLK_PORT PORTB
#define HC595_PIN_CLK_DIR DDRB
#define HC595_PIN_CLK_INIT HC595_PIN_CLK_DIR |= (1<<HC595_PIN_CLK)
#define HC595_PIN_CLK_HIGH HC595_PIN_CLK_PORT |= (1<<HC595_PIN_CLK)
#define HC595_PIN_CLK_LOW HC595_PIN_CLK_PORT &=~ (1<<HC595_PIN_CLK)
So hat man immer schnell alle Möglichkeiten den Code für alles
anzupassen.
Man kann das natürlich noch durch Makros ausbauen.
Klar hat man viele Defines, muss man mögen
#define PIN_SET(pinnr, port) port|=(1<<pinnr)
#define PIN_RESET(pinnr, port) port&= ~(1<<pinnr)
#define HC595_PIN_CLK_HIGH PIN_SET(HC595_PIN_CLK,HC595_PIN_CLK_PORT)
#define HC595_PIN_CLK_LOW PIN_RESET(HC595_PIN_CLK,HC595_PIN_CLK_PORT)
Man kann das ja wirklich bis ins unendliche treiben:
#define ATM_PIN_ON |=
#define ATM_PIN_ON &=~
#define PIN_SET(pinnr, port) port ATM_PIN_ON (1<<pinnr)
#define PIN_RESET(pinnr, port) port ATM_PIN_OFF (1<<pinnr)
#define HC595_PIN_CLK_HIGH PIN_SET(HC595_PIN_CLK,HC595_PIN_CLK_PORT)
#define HC595_PIN_CLK_LOW PIN_RESET(HC595_PIN_CLK,HC595_PIN_CLK_PORT)
Gibt ja so viele Möglichkeiten. Ich finde es immer ganz gut wenn jeder
Pin eindeutig zu finden ist, und zu jeden Pin gehören die entsprechenden
Register...
DS
Die 4-Bit Binär vs. Hex-Darstellung eines Nibbles dürften die meisten
Entwickler genauso einfach im Kopf parat haben, wie sie z.B.
Widerstands-Beschriftungen, seien es Farbringe oder Ziffern auf SMDs,
nicht mehr "buchstabieren", sondern auf einen Blick als Wort lesen.
Solche Fähigkeit hilft ungemein beim Debuggen, wenn man auf die Schnelle
Registerinhalte aufschlüsseln möchte.
Dennoch käme ich nie auf die Idee magic numbers in meinen Programmen zu
verwenden, ganz gleich in welcher Programmiersprache auch immer.
Lothar M. schrieb:> dann muss ich keinen Kommentar ändern und der Code ist immer noch> leserlich und quasi selbstkommentierend.
Selbst ein:
test = (1<<ABC) | (1<<XYZ) | (0<<DEF);
macht unter dem Aspekt Sinn.
Wenn es auch auf den ersten Blick überflüssig erscheint.
Reiner D. schrieb:> test = test | 40 ; // test = test ODER 2^5 ODER 2^3
Grundregel:
Der Code sagt klar und deutlich "was getan wird".
Der Kommentar sagt "warum es so getan wird"
Dein Beispiel ist ein klarer Verstoß dagegen.
Thorsten S. schrieb:> Dennoch käme ich nie auf die Idee magic numbers in meinen Programmen zu> verwenden, ganz gleich in welcher Programmiersprache auch immer.
Grundsätzlich: Zustimmung!
:
> Warum schreiben die Meisten in AVR-C :>> testbyte = PINB & (1 << PINB3)>
Du kannst ja stattdessen auch
1
testbyte=PINB&(1<<PIND3)
oder
1
testbyte=PINB&(1<<PCINT0_vect_num)
schreiben.
Das ist zwar semantisch Blödsinn, funktioniert aber trotzdem.
Oder
1
testbyte=PINB*3;
Auch das ist Blödsinn, der Compiler hindert Dich aber nicht daran.
Das dieser Mist möglich ist (sprich: nicht zur Compilezeit aufgedeckt
werden kann), liegt daran, dass hier Typsicherheit fehlt.
PINB ist leider als `volatile uint8_t` definiert. Und `(1<<PINB3)`
hat den Typ `int`. Und ist damit implizit konvertierbar. Weiterhin sind
sowohl `uint8_t` als auch `int` arithmetische Typen. Damit ist also
etwa auch `*` oder `/` dafür definiert, was bei der hier angesprochenen
Verwendung natürlich keinen Sinn macht.
PINB oder DDRB oder PORTB.OUTSET ist erstmal nur eine Ansammlung
von Bits, sonst nichts. Solche Bitmuster möchte man gerne mal schieben,
aber eher selten als Ganzzahl interpretieren. Und wenn doch, dann sollte
der Code das klar aussagen.
AUßerdem ist natürlich auch
1
TCCR0A=(1<<CS00)
falsch und sollte deswegen gar nicht erst compilieren.
Leider kannst Du das mit C nicht erreichen.
Wobei natürlich
das von Dir oben angesprochene "Arduino"-Kindergartenzeug C++ ist. Und
in C++ ist das recht elegant möglich.
Falk B. schrieb:> Verallgemeinerungen sind immer falsch! ;-)
Das ist mein Spruch! ;)
Klaus H. schrieb:> Und wenn man schon direkt Zahlen angeben will, dann wenigstens im> binären oder gerade noch im hexadezimalen Format, aber ganz bestimmt> nicht in DEZ!
Bei Timer-Registern, die eine Anzahl angeben, ist eine Dezimalangabe
schon sinnvoll.
Lothar M. schrieb:> Da kehrt sich dieser "selbstdokumentierende" Vorteil ins Negative, wo es> doch dezimal ganz leicht zu lesen wäre:> ADMUX = 5;
Für jede Ausgangseinstellung ein Makro definieren?!
Wilhelm M. schrieb:> AUßerdem ist natürlich auch> TCCR0A = (1 << CS00)>> falsch und sollte deswegen gar nicht erst compilieren.>> Leider kannst Du das mit C nicht erreichen.
Das ist falsch. Wenn man die Register als struct/union mit Bitfeldern
anlegt, geht das sehr wohl.
Beitrag "Re: XC8 compiler - Register schreiben wie bei Atmel"> das von Dir oben angesprochene "Arduino"-Kindergartenzeug C++ ist. Und> in C++ ist das recht elegant möglich.
Geht auch im ollen C.
Rahul D. schrieb:> Klaus H. schrieb:>> Und wenn man schon direkt Zahlen angeben will, dann wenigstens im>> binären oder gerade noch im hexadezimalen Format, aber ganz bestimmt>> nicht in DEZ!>> Bei Timer-Registern, die eine Anzahl angeben, ist eine Dezimalangabe> schon sinnvoll.
Sicher, das ist aber eine Minderheit.
> Lothar M. schrieb:>> Da kehrt sich dieser "selbstdokumentierende" Vorteil ins Negative, wo es>> doch dezimal ganz leicht zu lesen wäre:>> ADMUX = 5;>> Für jede Ausgangseinstellung ein Makro definieren?!
Nein
Falk B. schrieb:> Wilhelm M. schrieb:>> AUßerdem ist natürlich auch>> TCCR0A = (1 << CS00)>>>> falsch und sollte deswegen gar nicht erst compilieren.>>>> Leider kannst Du das mit C nicht erreichen.>> Das ist falsch. Wenn man die Register als struct/union mit Bitfeldern> anlegt, geht das sehr wohl.
Abgesehen davon, dass bitfields stark implementation-defined sind,
mach mal bitte ein Beispiel mit folgendem fiktivem Register:
Wilhelm M. schrieb:> Abgesehen davon, dass bitfields stark implementation-defined sind,
Sind sie, kann man aber trotzdem machen. Wir auch so gemacht.
> mach mal bitte ein Beispiel mit folgendem fiktivem Register:
Hab ich dir schon gezeigt. Folge meinem Link, dort ist ein Dokument von
TI, wie sie es im Code Composer Studio machen, zumindest für die C2000er
Serie.
Das ist wieder mal ein sehr schönes triviales Thema, zu dem jeder seine
eigene ganz feste und ganz richtige Meinung haben kann.
Wenn du lieber 8 statt 1<<3 schreibst und das für genau so lesbar
hältst, dann mache es halt so.
Ich mache es nicht so, denn ich halte es für schwer verständlich das
dritte Bit mit 8 zu beschreiben, obwohl es natürlich vollkommen richtig
ist.
Vieeeeel viel wichtiger als Neulingen einen ganz bestimmten "richtigen"
Stil beizubringen, ist zu vermitteln, dass ein konsistenter Stil viel
wichtiger ist!
Wenn ich eine Änderung in einem Stück Code mache, wo Masken alle als
Zahl ohne Shift dargestellt sind, dann halte ich mich daran und füge
neue Masken auch als Zahl ohne Shift ein. Konsistenz ist viel wichtiger
als irgendeine Stil-Religion, weil Konsistenz die mentale Last absenkt.
Darunter fallen dann auch so Dinge wie Formatierungsstil.
Falk B. schrieb:> Wilhelm M. schrieb:>> Abgesehen davon, dass bitfields stark implementation-defined sind,>> Sind sie, kann man aber trotzdem machen. Wir auch so gemacht.>>> mach mal bitte ein Beispiel mit folgendem fiktivem Register:>> Hab ich dir schon gezeigt. Folge meinem Link, dort ist ein Dokument von> TI, wie sie es im Code Composer Studio machen, zumindest für die C2000er> Serie.
Was Du meinst ist folgendes, oder?
Wilhelm M. schrieb:> Was Du meinst ist folgendes, oder?
Enums werden dabei nicht verwendet. Die Bits bzw. Bitgruppen werden per
Name angesprochen. Damit kann man nicht die falschen Bits im falschen
Register ansprechen.
Falk B. schrieb:> Wilhelm M. schrieb:>> Was Du meinst ist folgendes, oder?>> Enums werden dabei nicht verwendet. Die Bits bzw. Bitgruppen werden per> Name angesprochen. Damit kann man nicht die falschen Bits im falschen> Register ansprechen.
Ok,geht also nicht in C
Falk B. schrieb:> J. T. schrieb:>> Ein Schema, viele Schemata. Genau wie ein Praktikum, mehrere Praktika.>> Spaghetto, Spaghetti> Visum, Visa
Basilikum, Basilika
scnr,
WK
Wilhelm M. schrieb:>> Enums werden dabei nicht verwendet. Die Bits bzw. Bitgruppen werden per>> Name angesprochen. Damit kann man nicht die falschen Bits im falschen>> Register ansprechen.>> Ok,geht also nicht in C
Das war gar nicht dein Argument! Sondern daß man falsche Bits in
falschen Registern ansprechen kann!
"AUßerdem ist natürlich auch
TCCR0A = (1 << CS00)
falsch und sollte deswegen gar nicht erst compilieren.
Leider kannst Du das mit C nicht erreichen."
Reiner D. schrieb:> Also Antwort : damit auch absolute Anfänger den Code besser lesen> können.> Ist halt heutzutage so. Früher haben die Leute erst Digitaltechnik> gelernt, und dann mit dem Programmieren angefangen.
Eigentlich ist es genau andersrum. Mit einem
PORTB = PORTB & (1 << PORTB3);
kann ein absoluter Anfänger nichts anfangen. er versteht das Shiften
nicht und weiß auch nicht, was PORTB3 ist. Er weiß nichtmal ob es eine
Variable, ein Define oder sonst etwas ist. Da ist die Version mit der 8
einfacher für das Grundverständnis, da weiß man sofort, dass es um eine
Zahl geht, in dem Beispiel dem Port also ein Wert zugewiesen wird. Das
Rückrechnen auf den Pin ist relativ intuitiv. Für den Profi ist aber das
schnelle Lesen und Vermeiden von unnötigem Nachdenken und Verringerung
der Gefahr etwas falsch zu verstehen so wertvoll, dass es sich für ihn
lohnt, das 1er-Shiften als Konvention zu lernen und er weiß auch sofort,
was das PORTB3 wohl beinhaltet.
>>> Ein Schema, viele Schemata. Genau wie ein Praktikum, mehrere Praktika.>>>> Spaghetto, Spaghetti>> Visum, Visa>> Basilikum, Basilika
Atlas - Atlanten
Thomas - Thomaten
viele Bauteile von murata - ein Bauteil von mura
ein K - viele Kata
ein bla - viel blabata
SCNR
nur sind typsichere C++ Registerdefinitionen nicht Standard bei den
Hersteller SDK, für einen STM32H7 möchte ich das nicht selber schreiben.
STM hat ja schon ewig gebraucht den 'register' storage specifier zu
entfernen.
Wilhelm M. schrieb:> TCCR0A = (1 << CS00)>> falsch und sollteFalk B. schrieb:> Geht auch im ollen C.
Nö, abgesehen davon dass es doppelt gemoppelt ist, fehlt da das
Semikolon.
DS
Thorsten M. schrieb:> er versteht das Shiften nicht
Muss er ja auch gar nicht sofort verstehen. Oder verstehst du immer
sofort alles?
> und weiß auch nicht, was PORTB3 ist.
Was soll das denn anderes sein als Pin 3 auf PORTB?
Reiner D. schrieb:> testbyte = PINB & (1 << PINB3)
Ich weiß nicht wo du das her hast. Solche Zeilen würdest du bei mir nur
in temporärem Code für Experimentierbretter finden, wo z.B. ein Taster
mit B3 beschriftet ist.
In sinnvollen Programmen würde eher so etwas stehen:
> if PINB & (1<<ENDSCHALTER) {...}
Oder
> UCSR3B = (1<<RXEN3) | (1<<TXEN3) | (1 << RXCIE3);
Da sieht man an den Namen der Bits ungefähr, worum es in der Zeile geht.
Reiner D. schrieb:> Wenn ich schon weiß, daß das ein shift-Befehl ist, dann werd ich wohl> auch wissen, was 2^3 ist, oder ?
Wetten, dass du das mit 32 Bit Registern nicht so einfach im Kopf
hinbekommst? Ich vermute stark, dass du dir die Kopf-Rechnerei bei der
Programmierung von ARM Controllern ganz schnell abgewöhnst.
Reiner D. schrieb:> Warum schreiben die Meisten in AVR-C :> testbyte = PINB & (1 << PINB3)
Bisher hatte ich mir immer eingebildet, das käme der Portabilität
zugute. Bei Pin3 von PortB ists wohl nicht so notwendig, aber bei
UART0CONTROL = UART0CONTROL | (1<<TXEnable)
erlaubt es dem Hersteller, das Bit mal an einer anderen Stelle zu
plazieren und in einem geänderten Headerfile anders anzugeben. Der
Programmierer muß dann nichts ändern, das Headerfile richtets.
Aber nachdem hier über 50 Posts nur über Schönheit diskutieren, ist
meine Sicht der Dinge wohl ziemlich abartig. Beauty rules!
Gruß Klaus (der soundsovielte)
Stefan F. schrieb:>> testbyte = PINB & (1 << PINB3)>> Ich weiß nicht wo du das her hast. Solche Zeilen würdest du bei mir nur> in temporärem Code für Experimentierbretter finden, wo z.B. ein Taster> mit B3 beschriftet ist.>> In sinnvollen Programmen würde eher so etwas stehen:>>> if PINB & (1<<ENDSCHALTER) {...}
Besser so. Mehr Kapselung, mehr Übersicht.
Reiner D. schrieb:> Wenn ich schon weiß, daß das ein shift-Befehl ist, dann werd ich wohl> auch wissen, was 2^3 ist, oder ?
Ja, aber woher weißt du was die 8 bedeutet? Mit (1 << PB3) weißt du,
dass das 3. Bit hier gesetzt werden soll. Und das ganze ganz ohne in das
Datenblatt zu schaun. OK, bei simplen Port-Routinen gehts vielleicht
auch noch mit den Magic Numbers aber was ist z.B. wenn du denn ADC eines
Atmegas starten willst? Meinst du nicht, dass ein "ADCSRA |= (1 <<
ADSC);" einfacher zu verstehen ist als ein "ADCSRA |= 64;"? (und ja, die
64 musste ich jetzt echt im Datenblatt nachlesen)
Falk B. schrieb:> Doch!
Keine Ahnung, wie du darauf kommst, dass mehr Kapselung grundsätzlich zu
mehr Übersicht führt. Aber das ist doch offensichtlicher Unsinn. Schaue
dir den Grenzfall an: Wenn ich alles immer weiter rekursiv kapsele, dann
soll das der Übersicht helfen? Das Gegenteil ist der Fall.
Bei
>if (ENDSCHALTER) foo(); else LED_WARNING_ON;
sehe ich überhaupt nicht mehr, was passiert. Das kann natürlich in
einigen Fällen hilfreich sein. In anderen aber auch wieder nicht.
Und warum nicht so, wenn Kapselung doch die Übersicht verbessert?
#define REAKTION_AUF_ENDSCHALTER if (ENDSCHALTER) foo(); else
LED_WARNING_ON
REAKTION_AUF_ENDSCHALTER;
Falk B. schrieb:> Wilhelm M. schrieb:>> Ok,geht also nicht in C>> Das war gar nicht dein Argument! Sondern daß man falsche Bits in> falschen Registern ansprechen kann!>> "AUßerdem ist natürlich auch>> TCCR0A = (1 << CS00)>> falsch und sollte deswegen gar nicht erst compilieren.>> Leider kannst Du das mit C nicht erreichen."
Dieses Beispiel und das mit dem fiktiven Register zeigt genau, was ich
meine. Und diese Art von Typ-Sicherheit kann man in C eben nicht
erreichen.
J. S. schrieb:> nur sind typsichere C++ Registerdefinitionen nicht Standard bei den> Hersteller SDK, für einen STM32H7 möchte ich das nicht selber schreiben.> STM hat ja schon ewig gebraucht den 'register' storage specifier zu> entfernen.
Tja, das ist zwar traurig, aber nicht mein Problem.
Sowas macht man mit einem Skript, was die ganzen templates aus den
XML-Beschreibungen der µC automatisch erzeugt.
Stefan F. schrieb:> Wetten, dass du das mit 32 Bit Registern nicht so einfach im Kopf> hinbekommst? Ich vermute stark, dass du dir die Kopf-Rechnerei bei der> Programmierung von ARM Controllern ganz schnell abgewöhnst.
s.a. hier:
Wilhelm M. schrieb:> Was macht denn> test &= 39653678;>> Ok, Zeit abgelaufen ...
Stefan F. schrieb:> Oder>>> UCSR3B = (1<<RXEN3) | (1<<TXEN3) | (1 << RXCIE3);>> Da sieht man an den Namen der Bits ungefähr, worum es in der Zeile geht.
Es geht dabei zwar auch um Lesbarkeit, aber auch um Sicherheit. Ich
möchte einfach, dass ein Unsinn wie
1
UCSR3A=(1<<RXEN3)|(1<<TXEN3)|(1<<RXCIE3);
oder
1
TCCR0A=(1<<CS00);
nicht kompiliert.
Denn Programme, die deswegen nicht kompilieren, sind gute Programme ;-)
Dergute W. schrieb:> Falk B. schrieb:>> MaWin O. schrieb:>>>> Mehr Kapselung, mehr Übersicht.>>>>>> nö.>>>> Doch!>> Ohhh!
Leicht abgewandelt, aber auch nett.
MaWin O. schrieb:> Falk B. schrieb:>> Doch!>> Keine Ahnung, wie du darauf kommst, dass mehr Kapselung grundsätzlich zu> mehr Übersicht führt. Aber das ist doch offensichtlicher Unsinn. Schaue> dir den Grenzfall an: Wenn ich alles immer weiter rekursiv kapsele,
Wo soll das Rekursion sein? Das sind einfahe Abstraktionsebenen, auch
HAL genannt (Hardware Abstraction Layer).
> dann> soll das der Übersicht helfen? Das Gegenteil ist der Fall.>> Bei>>>if (ENDSCHALTER) foo(); else LED_WARNING_ON;>> sehe ich überhaupt nicht mehr, was passiert.
Das WILL ich in den meisten Fällen auch gar nicht! Bei der Anwendung von
Sensoren und Aktoren will ich mich um die Hardwaredetails NICHT mehr
kümmern. Das mache ich einmalig, wenn ich die HAL aufschreibe. Ebenso in
Funktionen. Wie die INTERN arbeiten, intertessiert beim AUFRUF/Anwendung
NICHT! Im Gegenteil.
> Das kann natürlich in> einigen Fällen hilfreich sein. In anderen aber auch wieder nicht.>> Und warum nicht so, wenn Kapselung doch die Übersicht verbessert?>> #define REAKTION_AUF_ENDSCHALTER if (ENDSCHALTER) foo(); else> LED_WARNING_ON>> REAKTION_AUF_ENDSCHALTER;
Man kann alles übertreiben. Du bist ein Meister darin.
Wasser ist lebensnotwendig. 10l auf einmal "getrunken" tödlich, aka
Ertrinken (oder Wasservergiftung).
Ich bin ja kein Softwerker, aber soviel hab selbst ich Hardwaredepp mal
vor vielen Jahren im Studium gelernt.
Wilhelm M. schrieb:> Dieses Beispiel und das mit dem fiktiven Register zeigt genau, was ich> meine. Und diese Art von Typ-Sicherheit kann man in C eben nicht> erreichen.
Man kommt nahe genug ran, auch wenn es nicht der reinen Lehre
entspricht. Sieg der Praxis über die Theorie.
Falk B. schrieb:> Wilhelm M. schrieb:>> Dieses Beispiel und das mit dem fiktiven Register zeigt genau, was ich>> meine. Und diese Art von Typ-Sicherheit kann man in C eben nicht>> erreichen.>> Man kommt nahe genug ran, auch wenn es nicht der reinen Lehre> entspricht.
Dann stimmst Du mir ja endlich zu, dass es nicht wirklich geht, sondern
nur so ungefähr ;-)
Wilhelm M. schrieb:>> Ok, Zeit abgelaufen ...
Da habe ich auch noch einen kleine "Test" versteckt, den aber wegen der
aufwendigen Rechnerei schon bei 8 Bit ebenfalls keiner gesehen hat, als
ich schrieb:> Und 0xCA ist garantiert leichter in einzelne Bits umzurechnen als 204.
204 ist nämlich nicht 0xCA, sondern 0xCC. Das sieht man aber eben ohne
viel Umrechnerei auf keinen Fall. Deshalb nimmt man für Bitmuster (wenn
sie schon als "Magic Number" hingeschrieben werden sollen) immer
Hex-Zahlen.
Wilhelm M. schrieb:> Dann stimmst Du mir ja endlich zu, dass es nicht wirklich geht, sondern> nur so ungefähr ;-)
JA, drucks dir aus und häng es an die Wand! ;-)
Lothar M. schrieb:> Deshalb nimmt man für Bitmuster (wenn> sie schon als "Magic Number" hingeschrieben werden sollen) immer> Hex-Zahlen.
Oder man nimmt Bitmuster, wenn man Bitmuster verwenden will!
0b0100111
Wilhelm M. schrieb:> Ich möchte einfach, dass ein Unsinn wie> ... nicht kompiliert.
Geht leider nicht. In den Headern von STM32 werden Fehler allerdings
recht wirksam vermieden, indem die Namen der Bit-Konstanten mit dem
Namen des Register beginnen.
Stefan F. schrieb:> indem die Namen der Bit-Konstanten mit dem> Namen des Register beginnen.
Was den Quältext schon arg aufbläht und geschwätzig macht. Naja.
Stefan F. schrieb:> Wilhelm M. schrieb:>> Ich möchte einfach, dass ein Unsinn wie>> ... nicht kompiliert.> Geht leider nicht.
Das gilt eben nur für C, in C++ geht es.
Wilhelm M. schrieb:> Das gilt eben nur für C, in C++ geht es.
Die Leute mögen aus irgendwelchen nicht nachvollziehbaren Gründen gerne
die Schmerzen dieser antiquierten Sprache.
Lothar M. schrieb:> 204 ist nämlich nicht 0xCA, sondern 0xCC. Das sieht man aber eben ohne> viel Umrechnerei auf keinen Fall. Deshalb nimmt man für Bitmuster (wenn> sie schon als "Magic Number" hingeschrieben werden sollen) immer> Hex-Zahlen.
Wenn du Binärzahlen gefordert hättest, ergäbe der Satz sehr viel mehr
Sinn.
MaWin O. schrieb:> REG |= 0b00000000000010000000000000000000;>> Gibt kaum was Lesbareres! Man sieht sofort auf einen Blick, welches Bit> gesetzt wird!
Hier wäre der pöhse Shift Operator hilfreich.
Stefan F. schrieb:> Hier wäre der pöhse Shift Operator hilfreich.
Teufelszeug! Wer soll das denn bitte verstehen? Was, wenn der Anfänger
nicht weiß, was shiften ist?
MaWin O. schrieb:> Thorsten M. schrieb:>> er versteht das Shiften nicht>> Muss er ja auch gar nicht sofort verstehen. Oder verstehst du immer> sofort alles?
Als Anfänger sieht man den Wald vor lauter Bäumen nicht, da ist soetwas
nicht hilfreich. Mir scheint die Meisten hier haben überhaupt keine
Ahnung mehr, wieviel "Beiwissen" für das Verständnis von Programmcode
erforderlich ist.
Siehe:
>> und weiß auch nicht, was PORTB3 ist.>> Was soll das denn anderes sein als Pin 3 auf PORTB?
Ein Anfänger versteht vielleicht, dass mit Pin 3 irgendwas gemacht
werden soll, aber nicht, dass der Mechanismus dahinter einfach ein
zugewiesener Zahlenwert ist.
Ist eigentlich ganz einfach. Schreibe die Zahl in einem gut lesbaren
Format hin, wenn es um die Zahl geht.
Aber wenn eine bestimmte Funktion gemeint ist, dann nenne sie beim
Namen.
3 ist keine gute Bezeichnung für den Endschalter. Und 4 ist keine gute
Bezeichnung für den Status des Empfangspuffers. Ich will nicht wissen
was mit der 4 los ist, sondern was mit dem Empfangspuffer ist. Also
schreibe ich das auch so im menschen-lesbarer Form hin. Das ist
schließlich der Sinn jeder Programmiersprache.
Thorsten M. schrieb:> Mir scheint die Meisten hier haben überhaupt keine> Ahnung mehr, wieviel "Beiwissen" für das Verständnis von Programmcode> erforderlich ist.
Das ist mir sehr wohl bewusst.
Genau so könntest du aber argumentieren, dass das Konzept von Zahlen
ansich viel zu kompliziert ist. Woher soll der Anfänger das denn bitte
alles wissen? Es könnte sein, dass er noch in den Kindergarten geht.
Irgendwo muss man halt die Messlatte anlegen.
Und irgendwelche Dinge werden immer gelernt werden müssen.
Ich sehe da auch überhaupt kein Problem, wenn ein Anfänger erst einmal
eine Woche zu 5 Programmzeilen recherchiert, bis er sie wirklich
verstanden hat.
> Ein Anfänger versteht vielleicht, dass mit Pin 3 irgendwas gemacht> werden soll, aber nicht, dass der Mechanismus dahinter einfach ein> zugewiesener Zahlenwert ist.
Was soll es denn sonst sein? Ein Mettbrötchen?
Ein Rechner kennt nur Zahlen. Deshalb heißt er so. Das sollte doch
Allgemeinwissen sein.
Stefan F. schrieb:> MaWin O. schrieb:>> REG |= 0b00000000000010000000000000000000;>>>> Gibt kaum was Lesbareres! Man sieht sofort auf einen Blick, welches Bit>> gesetzt wird!>> Hier wäre der pöhse Shift Operator hilfreich.
oder man kommentiert dieses machwerk wie es jeder angehender
Programmierer in der Grundschule gelernt hat.
Alternativ kann man sich mit einem gescheiten editor behelfen der
mitzählt in welcher Spalte grad der Cursor steht.
DSGV-Violator schrieb:> oder man kommentiert dieses machwerk wie es jeder angehender> Programmierer in der Grundschule gelernt hat.>> Alternativ kann man sich mit einem gescheiten editor behelfen der> mitzählt in welcher Spalte grad der Cursor steht.
lol, nein.
Einfach nur nein.
nein.
Arduino F. schrieb:> Oder man nimmt Bitmuster, wenn man Bitmuster verwenden will!> 0b0100111MaWin O. schrieb:> Arduino F. schrieb:>> Oder man nimmt Bitmuster, wenn man Bitmuster verwenden will!>> Ja. Super praktisch. Besonders für 32-Bit-Maschinen!> REG |= 0b00000000000010000000000000000000;>> Gibt kaum was Lesbareres! Man sieht sofort auf einen Blick, welches Bit> gesetzt wird!
Also auf seiner 7bit-Maschine geht das schon noch ...
Jens G. schrieb:> Arduino F. schrieb:>> Oder man nimmt Bitmuster, wenn man Bitmuster verwenden will!>> 0b0100111>> MaWin O. schrieb:>> Arduino F. schrieb:>>> Oder man nimmt Bitmuster, wenn man Bitmuster verwenden will!>>>> Ja. Super praktisch. Besonders für 32-Bit-Maschinen!>> REG |= 0b00000000000010000000000000000000;>>>> Gibt kaum was Lesbareres! Man sieht sofort auf einen Blick, welches Bit>> gesetzt wird!>> Also auf seiner 7bit-Maschine geht das schon noch ...
Da würde ich aber trotzdem HEX vorziehen. Da hat man einen
Gruppenanhaltspunkt. 0x00080000 ist da leichter als die vielen Nullen zu
klassifizieren.
Falk B. schrieb:> Auf dem avr gcc vielleicht, bei anderen Compilern bzw. CPUs nicht> unbedingt.
Meine Vermutung ist, dass das damit zusammenhängt, wie die SBI- und
CBI-Befehle beim AVR gestrickt worden sind: sie benutzen eine Bitnummer.
Wenn man nun daraus eine Bitmaske machen muss, braucht man den
Schiebebefehl, auch im Assemblercode.
AVR-C hat das von da geerbt, zumal SBI und CBI anfangs noch nicht vom
Compiler selbst generiert werden konnten, sondern über inline-Makros
produziert worden sind.
Der Rest der Welt benutzt schon immer vorzugsweise gleich Bitmasken für
das Benennen von Bits (kann man prima sehen, wenn man sich irgendwelche
UNIX-Quellen ansieht).
Hätte man beim AVR natürlich auch haben können: es gibt keinen Grund,
warum der Assembler nicht hätte in der Lage sein sollen, ein
1
SBI PORTC, 8
selbst so umzurechnen, dass er im Opcode die Bitnummer 3 generiert (und
sich über einen Semantikfehler erbricht, wenn jemand versucht, mehr als
ein Bit im Operanden zu setzen).
Hat man aber nicht. Also schieben wir beim AVR fröhlich die Bitnummern
herum. :-)
Daniel F. schrieb:> außerdem kümmert sich der Präprozessor darum, dass (1 << PINB3) zu 8> wird
Eine nicht ausrottbare Fehlinformation. Der Präprozessor rechnet nur in
#if Statements und macht hier aus (1 << PINB3) lediglich (1 << 3). Die
Rechnung erfolgt im Compiler selbst.
Über Geschmacksfragen kann man trefflich streiten. Ich auch. ;-)
Ein Charme von sowas wie
(1 << REGx_CB1)
und auch dem von manchen als absurd angesehenen
(0 << REGx_CB1)
ist, dass man im Quelltext danach suchen kann.
Ersetzt man die Bitangabe durch eine benannte Maske, spart man zwar
Schreibarbeit, aber die zweite Variante fällt durch ersatzloses Fehlen
der Bitbezeichnung auf. Und etwas, was fehlt, widersetzt sich jeder
Suche im Quelltext. Man findet also jene Stellen, in denen es auf 1
gesetzt wird, nicht aber jene, in denen es auf 0 gesetzt wird.
Für die Maske empfiehlt sich deshalb also sowas wie
REGx_CB1_ENABLE
REGx_CB1_DISABLE
und hat dann halt jedes Bit zweimal im Header.
Bei der Variante, alle Bits eines Registers im Kopf zusammenzurechnen
und als Ergebnis in Hex, Oktal oder Dezimal in den Quelltext zu werfen,
empfehle ich ersatzweise Programmierung in INTERCAL. Das ist
konsequenter. Ist dir das zu doof, nimm APL. Damit habe ich angefangen,
weiss also, was Write-Only-Code ist.
MaWin O. schrieb:> REG |= 0b00000000000010000000000000000000;
Bei C++14 hatte man den Geistesblitz, das so schreiben zu dürfen:
REG |= 0b0000'0000'0000'1000'0000'0000'0000'0000;
Stefan F. schrieb:> MaWin O. schrieb:>> REG |= 0b00000000000010000000000000000000;>>>> Gibt kaum was Lesbareres! Man sieht sofort auf einen Blick, welches Bit>> gesetzt wird!>> Hier wäre der pöhse Shift Operator hilfreich.
ACK. Oder durch Gruppierung (ab C++14, leider nicht mit der
GCC-Extension):
(prx) A. K. schrieb:> Bei C++14 hatte man den Geistesblitz, das so schreiben zu dürfen:> REG |= 0b0000'0000'0000'1000'0000'0000'0000'0000;
Bei C23 wurde das übernommen.
Der Unterstrich wurde nicht benutzt, weil er ein gültiges
Präprozessorsymbol ist und ja auch tatsächlich in der Form benutzt wird
(beispielsweise in Systemen zur Internationalisierung von Texten).
Jens G. schrieb:> Das waren bestimmt Deutsche, die den Geistesblitz hatten ...
Mit der von mir früher verwendeten US ASCII Tastatur wäre mir das nicht
passiert. ;-)
Um das andere Ende ebenso zu würdigen: Vor Jahrzehnten verwendete ich
eine Tastatur, die auf eigene (private!) Anfrage in kundenspezifischem
Layout produziert wurde. Gibts sowas heute noch, und bezahlbar? Ich
würde liebend gerne einen halben Meter Abstand zwischen Shift|CAPS Lock
und A einbauen. Besser noch, Shift Lock ganz weglassen. Die Kappe
rauszureissen ist leider nur eine Teillösung, weil bei Notebooks nicht
anwendbar.
J. S. schrieb:> nur sind typsichere C++ Registerdefinitionen nicht Standard bei den> Hersteller SDK, für einen STM32H7 möchte ich das nicht selber schreiben.
Das sehe ich auch so.
Wenn der Toolchain/SDK-Hersteller eine C++-Template-Bibliothek mit allen
Schikanen anbietet, nehme ich die natürlich gerne. Gibt es für jedes
Register ein C-Struct mit Bitfeldern, ist das ebenso in Ordnung, auch
wenn es vielleicht nicht ganz so idiotensicher ist. Beschränken sich die
Header-Files – wie in der AVR-Libc – auf Makros für die Registeradressen
und Bitnummern, kann ich auch damit sehr gut leben.
Wichtig ist lediglich, dass diese Header-Files von anderen bereits
ausgiebig getestet und gedebugt sind, alles andere ist nice-to-have.
Wenn mein Programm nicht läuft, möchte ich die Fehler schließlich nur in
meinem eigenen Code und nicht in den kilometerlangen, unausgereiften
Header-Files suchen müssen.
Es mag zwar nett sein, wenn der Compiler für das Setzen des CS00-Bits in
TCCR0A eine Fehlermeldung ausgibt, es ist aber auch kein nennenswerter
Nachteil, wenn er es nicht tut. Mir ist ein solcher Fehler noch nie
passiert, was aber überhaupt nicht daran liegt, dass ich ein perfekter
Programmierer bin. Der Grund dafür liegt vielmehr darin, dass ich bei
der Initialisierung bspw. eines Timers das Datenblatt aufschlage und
darin schrittweise alle betroffenen Register mit den jeweiligen Bits
durchgehe. Da jedem Register ein eigener Abschnitt gewidmet ist, kommt
da überhaupt nichts durcheinander.
In dem sehr unwahrscheinlichen Fall, dass mir dieser Fehler dennoch
unterläuft, merke ich das recht schnell, da der Timer nicht so läuft,
wie ich es gerne hätte. Dann muss ich eben die paar Code-Zeilen, in
denen er initialisiert wird, noch einmal genau unter die Lupe nehmen. Da
ist eine Sache von maximal 10 Minuten, dann ist der Fehler behoben.
Würde ich stattdessen versuchen, das genannte Problem (das eigentlich
gar keines ist) durch das Schreiben einer C++-Template-Bibliothek zu
lösen, würde ich erst einmal viele Tage an Aufwand darin versenken, und
die Wahrscheinlichkeit, dabei einen Fehler zu machen, wäre wegen der
vielen Codezeilen um mehrere Größenordnungen höher. Wenn mein Programm
dann nicht läuft, muss ich den Fehler nicht nur in meinem Anwendungscode
suchen, sondern auch in den selbstgeschriebenen Header-Files, was um ein
Vielfaches aufwendiger ist.
Dieser ganze Zusatzaufwand würde sich für mich nie auch nur ansatzweise
amortisieren. Er lohnt sich – wenn überhaupt – nur dann, wenn (bspw. in
einer größeren Entwicklergruppe) einer die Arbeit übernimmt und viele
andere davon profitieren.
Wenn ich dann für das nächste Projekt keinen AVR, sondern einen ARM,
einen ESP32 oder irgendeinen anderen Mikrocontroller einsetze möchte,
geht das ganze Gefrickel wieder von vorne los.
Die in der Praxis tatsächlich auftretenden Fehler und Probleme wie bspw.
Missverständnisse beim Lesen des Datenblatts oder Vergessen, ein
bestimmtes Bit in einem Register zu setzen, werden auch mit noch so
vielen C++-Templates nicht vermieden.
Deswegen lasse ich mir das Verfahren, mit dem Register mit Werten belegt
werden, einfach vom Hersteller der Toolchain vorgeben. Damit bin ich
noch nie schlecht gefahren.
Zum eigentlichen Thema:
Statt
1
testbyte=PINB&8;
würde ich (wie schon einige andere in diesem Thread)
1
testbyte=PINB&1<<FUNKTION_DES_PINS;
scheiben (FUNKTION_DES_PINS durch einen zur Anwendung passenden Namen
ersetzen) .
Magic Numbers sind fast immer schlecht. Eine Magic Number entsteht meist
durch eine Berechnung oder durch die Nummerierung einer bestimmten, mit
der Nummer verbundenen Funktion oder Eigenschaft (bspw. bei Bitnummern
in I/O-Registern, Kommando-IDs oder Errorcodes).
Die Berechnung einer Konstanten sollte man, wenn möglich, im Quellcode
zu Dokumentationszwecken stehen lassen. Der Compiler übernimmt die
Auswertung gerne. Deswegen ist in obigem Fall 1<<3 schon einmal besser
als einfach nur 8. Erst recht ist 1<<31 besser als 2147483648 (in diesem
Fall sogar weniger Tipparbeit).
Nummern, mit denen eine bestimmte Funktion oder Eigenschaft verbunden
ist, sollten einen entsprechenden Namen bekommen, üblicherweise mittels
#define, enum oder der Initialisierung einer const-Variable.
Yalu X. schrieb:> Würde ich stattdessen versuchen, das genannte Problem (das eigentlich> gar keines ist)
Anscheinend doch: sonst würden wir hier nicht drüber reden bzw. ich
finde es toll, dass das für Dich nie ein Problem ist.
Wilhelm M. schrieb:> Yalu X. schrieb:>> Würde ich stattdessen versuchen, das genannte Problem (das eigentlich>> gar keines ist)>> Anscheinend doch: sonst würden wir hier nicht drüber reden bzw. ich> finde es toll, dass das für Dich nie ein Problem ist.
Naja, das Thread-Thema paßt in die Kategorie "Nennen Sie mir Ihre Lösung
- ich mache ein Problem daraus" ...
Wilhelm M. schrieb:> Yalu X. schrieb:>> Würde ich stattdessen versuchen, das genannte Problem (das eigentlich>> gar keines ist)>> Anscheinend doch: sonst würden wir hier nicht drüber reden
Wer außer dir sieht darin noch ein Problem?
Ich glaube nicht einmal, dass du selber darin ein ernsthaftes Problem
siehst. Oder ist dir ein Fehler der Art
Wilhelm M. schrieb:> TCCR0A = (1 << CS00)
tatsächlich schon einmal unterlaufen?
Ist es nicht eher so, dass du rein aus Interesse, die diesbezüglichen
Möglichkeiten von C++ zu erkunden, viel Zeit für eine Lösung investiert
hast, die du jetzt durch die übertriebenen Darstellung der Schwere des
Problems aufzuwerten versuchst?
Nicht, dass du mich falsch verstehst:
Ich finde deine Lösung gut¹ und bin prinzipiell für alles dankbar, was
Fehler (auch wenn es nur seltene sind) bereits zur Compilezeit
aufdecken. Nur scheint bei diesem konkreten Problem der Aufwand für
dessen Lösung den Nutzen bei Weitem zu übersteigen, sonst würde sie von
den kommerziellen Toolchain-Entwicklern standardmäßig angeboten. Auch
bei den Open-Source-Entwicklern scheint so etwas keine Priorität zu
genießen, sonst hätte sich sicher längst eine Gruppe zusammengefunden,
die das in einer AVR-Libc++ umsetzt.
──────────────
¹) auch wenn ich sie nicht im Detail kenne, weil du ja nicht viel
darüber veröffentlichst
Naja, Bitfield-structs für IO-Register gibt es ja durchaus (und die
lösen das potenzielle Setzen von Bits in falschen Registern genauso),
beispielsweise bei vielen Cortex-M. Beim AVR hatten wir das mal beim
ATmega128RFA1 (und Nachfolgern) gemacht, aber dort hat das seitens Atmel
und dann Microchip keiner aufgegriffen.
Yalu X. schrieb:> Oder ist dir ein Fehler der Art>> Wilhelm M. schrieb:>> TCCR0A = (1 << CS00)>> tatsächlich schon einmal unterlaufen?
Bei Verwendung des Shiftoperators hätte man vermutlich die Möglichkeit,
dem Compiler eine Fehlererkennung beizubringen. Auch bei aufmerksamen
Kontrollieren seines Codes hat man die Chance, zu erkenne, dass CS00
eben nicht im TCCR0A-Register vorhanden ist. Mit
1
TCCR0A=1;
wird dasselbe bewirkt und da wäre die Chance nicht vorhanden, weder für
den Programmierer (oder steht im Kommentar, dass man CS00 meinte?) noch
für den Compiler.
Und es ging ja ursprünglich um das Thema Shiftoperator oder Magic
Number.
Thorsten M. schrieb:> Ein Anfänger versteht vielleicht, dass mit Pin 3 irgendwas gemacht> werden soll, aber nicht, dass der Mechanismus dahinter einfach ein> zugewiesener Zahlenwert ist.
Wie an vielen Stellen: das muss man eben lernen! Wer ein richtiger
Anfänger ist, hat noch viele andere Baustellen. Und wer nur Anfänger im
Programmieren von µCs ist, der hat in Minuten verstanden, warum man das
hier übersichtlicher so schreibt. Es sind doch prinzipiell nur die
beiden Schreibweisen zu verstehen:
1
PORTB|=(1<<PB0);// B0 setzen
2
// und
3
PORTB&=~(1<<PB0);// B0 löschen
Etwas gefährlicher ist dies (bin auch schon selber auf mich
reingefallen):
1
WDTCR|=(1<<WDP2)|(0<<WDP1)|(1<<WDP0);
Man führt alle Bits auf um bei Überarbeitung schnell mal auf eine andere
Kombination ändern zu können, sieht aber die '0' bei WDP1 nicht auf
Anhieb ...
Es wäre auch vom Compiler erkennbar, dass in der Zeile (0<<WDP1) nichts
bewirkt.
Namen statt magic numbers sollte man auch nicht übertreiben. Gerade will
ich eine RTC von Hand stellen und die Eingabe irgendwo begrenzen; RTCs
kennen ja nur Jahre von 0 bis 99.
1
utc=mktime(&temp);
2
if(utc>4070908799){// 2098-12-31 23:59:59
3
utc=4070908799;
4
}else{
5
if(utc<source_date_epoch()){
6
utc=source_date_epoch();
7
}
8
}
Würde irgendwer diese magic number per Präprozessor ausrechnen lassen?
Nachdem dies die einzige Verwendung im ganzen Programm ist, lohnt sich
doch nicht einmal ein einziges #define? Eher würde ich sie zur Laufzeit
ausrechnen...
Klaus H. schrieb:> Wie an vielen Stellen: das muss man eben lernen! Wer ein richtiger> Anfänger ist, hat noch viele andere Baustellen.
Das sag ich ja grad. Es ist ja wirklich nicht schwierig das zu lernen.
Aber es ist eben nicht wie der TE meinte eine Hilfe für Anfänger,
sondern eine Vereinfachung für Fortgeschrittene. Und die Frage kam ja
scheinbar aus dem Kontext der Ausbildung.
MaWin O. schrieb:> Genau so könntest du aber argumentieren, dass das Konzept von Zahlen> ansich viel zu kompliziert ist. Woher soll der Anfänger das denn bitte> alles wissen?
Deswegen fängt man in der Mathematik nicht mit Differentialgleichungen
an, sondern mit dem Zählen. Bei 0 oder 1 gehts los, weiß ich nicht mehr.
Aber du hast mich wohl sowieso falsch verstanden. Ich argumentiere ja
gar nicht gegen das Konstrukt, siehe oben, sondern möchte hier nur
verdeutlichen, dass das auch etwas ist, das man erst lernen muss und im
Sinne der Lehre an geeigneter Stelle einbauen sollte und nicht einfach
sagt, das versteht sich ja von selbst.
>> Ein Anfänger versteht vielleicht, dass mit Pin 3 irgendwas gemacht>> werden soll, aber nicht, dass der Mechanismus dahinter einfach ein>> zugewiesener Zahlenwert ist.>> Was soll es denn sonst sein? Ein Mettbrötchen?> Ein Rechner kennt nur Zahlen. Deshalb heißt er so. Das sollte doch> Allgemeinwissen sein.
Holla. PortA ist schonmal nicht einfach eine Zahl im Compilersinn.
Sondern eine Adresse, die dann dank gemapptem Speicher gleich noch ein
Hardwareregister ist. Der Compiler dereferenziert automatisch, das
Beschreiben hat dann auch noch eine Auswirkung auf die Umwelt. Je nach
Anwendung sogar auf ein Mettbrötchen. Für die Profis sind die
Implikationen alle klar. Um sofort zu sehen, was ein Hardwareregister,
was eine Variable, was eine Funktion, was eine Konstante ist, muss das
Hirn erst auf Programmiersyntax eingestellt werden. Das geht nicht in
Minuten und nicht in Tagen.
Bauform B. schrieb:> Namen statt magic numbers sollte man auch nicht übertreiben.>> if (utc > 4070908799) { // 2098-12-31 23:59:59>> Würde irgendwer diese magic number per Präprozessor ausrechnen lassen?
Nein, aber einen Namen kannst du ihr ja trotzdem geben, bspw. mit
Thorsten M. schrieb:> Holla. PortA ist schonmal nicht einfach eine Zahl im Compilersinn.> Sondern eine Adresse, die dann dank gemapptem Speicher gleich noch ein> Hardwareregister ist.
1<<PA3 sollte man also deiner Meinung nach 8 schreiben, weil das weniger
abstrakt ist.
Wie müsste man dann PORTA genau schreiben?
Adresse hartcodieren?
So?
*((volatile uint8_t *)0x1234)
Schließlich muss das ja verständlicher sein, deiner Argumentation nach.
Bizarr.
Thorsten M. schrieb:> Ja, man sollte nie mit einem falschen Mawin diskutieren.
Außer Ad-hominem keine weiteren Argumente mehr?
Case closed, würde ich sagen.
Yalu X. schrieb:> Bauform B. schrieb:>> Namen statt magic numbers sollte man auch nicht übertreiben.>>>> if (utc > 4070908799) { // 2098-12-31 23:59:59>>>> Würde irgendwer diese magic number per Präprozessor ausrechnen lassen?>> Nein, aber einen Namen kannst du ihr ja trotzdem geben, bspw. mit>>
1
>#defineUTC_2098_12_31_23_59_594070908799
2
>
Würde dafür eine constexpr-Funktion nehmen: das kann der Compiler ganz
gut zur Compilezeit selbst ausrechnen.
Bauform B. schrieb:> Würde irgendwer diese magic number per Präprozessor ausrechnen lassen?
Das ist unmöglich!
Der Präprozessor kann zwar rechnen, aber man bekommt die berechneten
Zahlen nicht aus dem Präprozessor in den Code.
Wilhelm M. schrieb:> Würde dafür eine constexpr-Funktion nehmen: das kann der Compiler ganz> gut zur Compilezeit selbst ausrechnen.
Das geht auch mit jeder beliebigen "static" Funktion. Der Compiler
sieht, dass sie nur einmal benutzt wird und inlinet sie, auch ganz ohne
"constexpr". Aber es spricht absolut nichts dagegen, dass per
Präprozessor-Makro zu machen (außer das C++ den Präprozessor nicht mag
;-).
>> Würde irgendwer diese magic number per Präprozessor ausrechnen lassen?Yalu X. schrieb:> Nein, aber einen Namen kannst du ihr ja trotzdem geben, bspw. mit> #define UTC_2098_12_31_23_59_59 4070908799
Ich würde bei der Benamung möglichst noch einen Schritt weiter gehen, um
anzuzeigen, was es mit diesem Datum auf sich hat. Etwa so:
#define UTC_GERMAN_UNITY_DAY 654951600
Wilhelm M. schrieb:> Würde dafür eine constexpr-Funktion nehmen: das kann der Compiler ganz> gut zur Compilezeit selbst ausrechnen.
Diese Funktion wird man aber wohl selber schreiben müssen, oder geht das
auch irgendwie mit Funktionen aus der Standardbibliothek?
Für den AVR, um den es hier wohl primär geht und für den es keine
C++-Standardbibliothek gibt, müsste die Funktion auf jeden Fall selber
geschrieben und natürlich wie jede anderen Funktion erst einmal
ausgiebig getestet werden.
Ich glaube, in diesem konkreten Fall würde ich die Regel, Berechnungen
möglichst durch den Compiler erledigen zu lassen, ausnahmsweise brechen.
Nochmal der Versuch einer Zusammenfassung.
Ich denke, die jeweiligen programmiersprachlichen Ausdrücke werden je
nach persönlicher Historie so oder so benutzt und bewertet.
Ihr hier seid mehrheitlich junge Leute denke ich, die in moderner Zeit
mit ausgereiften Tools, IDEs und komplexen Compilern digital
sozialisiert wurden.
Ich hab das Controller-Programmieren auf dem 8048 gelernt, in Maschine,
das Entwicklungssystem und der Emulator so groß wie ein Küchenschrank.
Damals gabs nichts anderes ;-)
Daraus resultieren verschiedene Vorgehensweisen. Es würde mir nicht
einfallen, irgend etwas mit einem Controller zu tun, ohne vorher den
Absatz im Datenblatt gelesen zu haben.
(Da bin ich sehr bei yalu : "dass ich bei der Initialisierung bspw.
eines Timers das Datenblatt aufschlage und darin schrittweise alle
betroffenen Register mit den jeweiligen Bits durchgehe. ").
Libs sind mir ein Gruselding, ich will verstehen was da läuft.
--------
btw. Was mir auffällt, sind einige Beiträge mit sehr unsachlichen
Inhalten.
Was soll das in einem technischen Forum ? Vorschlag : Beiträge mit
persönlichen Schmähungen werden nur akzeptiert, wenn sie in korrekter
Syntax geschrieben sind ;-)
Reiner D. schrieb:> Ihr hier seid mehrheitlich junge Leute denke ich
Ja, selbstverständlich. Wir sind alle sehr jugendlich, dynamisch,
äußerst attraktiv und unsere volle Haarpracht bestätigt das nur noch.
räusper
Reiner D. schrieb:> Ihr hier seid mehrheitlich junge Leute denke ich, die in moderner Zeit> mit ausgereiften Tools, IDEs und komplexen Compilern digital> sozialisiert wurden.
Manchmal umschleicht mich das Gefühl, dass die Tools vor 4 Jahrzehnten
ausgereifter waren. Allerdings waren die Compiler nicht so komplex.
Yalu X. schrieb:> Wilhelm M. schrieb:>> Würde dafür eine constexpr-Funktion nehmen: das kann der Compiler ganz>> gut zur Compilezeit selbst ausrechnen.>> Diese Funktion wird man aber wohl selber schreiben müssen, oder geht das> auch irgendwie mit Funktionen aus der Standardbibliothek?
> Für den AVR, um den es hier wohl primär geht und für den es keine> C++-Standardbibliothek gibt, müsste die Funktion auf jeden Fall selber> geschrieben und natürlich wie jede anderen Funktion erst einmal> ausgiebig getestet werden.
Aus praktischen Gründen macht man Datumsarithmetik auf Basis des
julianischen Datums. Dann ist es auch für den AVR schnell geschrieben:
Dies ist natürlich nur so dahingerotzt. Im echten Leben gehen natürlich
die 3-stelligen Parameterliste mit identischen Datentypen gar nicht.
Sondern man muss hier sinnvollerweise eine Klasse Date einführen so
ähnlich wie in der stdlib für std::year_month_date. So kann man nicht
lassen.
Hier sei es gestattet um das Prinzip zu zeigen.
Die Umrechnungen findet man unter:
http://www.tondering.dk/main/index.php/calendar-information
Wilhelm M. schrieb:> constexpr uint32_t julianDay(const uint16_t year, const uint8_t month, const
uint8_t day) {
> const size_t a = (14 - month)/12;> const size_t y = year+4800-a;> const size_t m = month + 12*a - 3;> return day + (153*m+2)/5 + uint32_t{y}*365 + y/4 - y/100 + y/400 - 32045;> }
Wenn ich jetzt gehässig wäre, würde ich sagen: Du hast zwar die magische
Konstante 4070908799 erfolgreich eliminiert, dafür aber drei neue (4800,
153 und 32045) eingeführt ;-)
> constexpr auto v1 = secondsSinceEpoch(2098y/12/31) - 1 ;
Du hast (warum auch immer) die Uhrzeit (23:59:59) weggelassen und
wolltest das mit dem -1 am Ende kompensieren. Dann hättest du das Datum
aber auf den 1.1. des Folgejahres setzen müssen:
1
constexprautov1=secondsSinceEpoch(2099y/1/1)-1;
Hättest du den Timestamp bspw. hier
https://www.epochconverter.com/
aus der Eingabe von Datum und Uhrzeit berechnen lassen und mit dem
Ergebnis (4070908799) im Programm eine Konstante mit einem
aussagekräftigen Namen initialisiert, wäre das nicht passiert.
Yalu X. schrieb:>> constexpr auto v1 = secondsSinceEpoch(2098y/12/31) - 1 ;>> Du hast (warum auch immer) die Uhrzeit (23:59:59) weggelassen und> wolltest das mit dem -1 am Ende kompensieren. Dann hättest du das Datum> aber auf den 1.1. des Folgejahres setzen müssen:>>
1
>constexprautov1=secondsSinceEpoch(2099y/1/1)-1;
2
>
Wunderbar!
Du hast also sofort erfasst, was diese Zeile sollte. Und den
offensichtlichen Fehler direkt erkannt. Es ist ja kein Fehler der
Funktion, sondern seiner Anwendung.
Die RTC kann auch sicher bis Jahr 99, Monat 12, Tag 31 zählen, oder?
Dann wäre ja sogar Deine Korrektur falsch gewesen.
Ein Fehler in dem Literal 4070908799 wäre wohl länger verborgen
geblieben.