Maskieren modern?

OP #7379512
Lesenswert?

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 ???

#7379519
Lesenswert?

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)

#7379521
Lesenswert?

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.

OP #7379523
Lesenswert?

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

#7379524
Lesenswert?

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.

1
DDRA = (1<<PA5) | (1<<PA3);
2
DDRA = 32 | 8;
3
DDRA = 40

Welche Version ist besser verständlich?

Siehe Bitmanipulation

https://www.mikrocontroller.net/articles/Bitmanipulation#Bitmanipulation_beim_MSP430

An sich ist die Version ala MSP430 besser, man spart sich Tiparbeit. Ist aber historisch bedingt beim avr gcc nicht so, es sei denn, einer konvertiert ALLE Headerfiles!

#7379527
Lesenswert?

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.

#7379530
Lesenswert?

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

OP #7379531
Lesenswert?

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 !!!)

#7379550
Lesenswert?

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.

OP #7379555
Lesenswert?

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 ;-)

#7379563
Lesenswert?

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.

#7379576
Lesenswert?

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!

Moderator (Firma: Titel) Persönliche Seite #7379577
Lesenswert?

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.

#7379580
Lesenswert?

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!

#7379581
Lesenswert?

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

#7379586
Lesenswert?

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"

#7379597
Lesenswert?

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.

Moderator (Firma: Titel) Persönliche Seite #7379604
Lesenswert?

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:

1
ADMUX = (1<<ADMUX0)|(1<<ADMUX2);
2
ADMUX = (1<<ADMUX0)|(0<<ADMUX1)|(1<<ADMUX2)|(0<<ADMUX3);

Da kehrt sich dieser "selbstdokumentierende" Vorteil ins Negative, wo es doch dezimal ganz leicht zu lesen wäre: ADMUX = 5;

#7379607
Lesenswert?

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

#7379609
Lesenswert?

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.

(Firma: Gast) #7379610
Lesenswert?

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!

#7379633
Lesenswert?

:

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.

#7379636
Lesenswert?

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?!

#7379653
Lesenswert?

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.

#7379655
Lesenswert?

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

#7379670
Lesenswert?

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:

1
struct R1 {
2
    uint8_t a:1;
3
    uint8_t b:2;
4
    uint8_t c:3;
5
    uint8_t d:4;
6
};
#7379677
Lesenswert?

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.

#7379682
Lesenswert?

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.

#7379690
Lesenswert?

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?

1
#include <stdint.h>
2

3
enum E1 {a, b, c, d};
4
enum E2 {e, f, g, h};
5

6
struct R1 {
7
    uint8_t a:1;
8
    enum E1 b:2;
9
    uint8_t c:3;
10
    uint8_t d:4;
11
};
12
struct R2 {
13
    uint8_t w:4;
14
    enum E2 x:3;
15
    uint8_t y:2;
16
    uint8_t z:1;
17
};
18

19
struct R1 r1;
20
struct R2 r2;
21

22
int main(void) {
23
    r1.b = e; // Ups
24
    r2.x = a;
25
}
#7379701
Lesenswert?

Beitrag "Re: XC8 compiler - Register schreiben wie bei Atmel" https://www.mikrocontroller.net/attachment/289053/spraa85d_Programming_TMS320xx280xx_C_C__.pdf

Seite 11

1
/********************************************************************
2
* User's source file
3
********************************************************************/
4
// Access registers without a bit field definition (.all, .bit not used)
5
SciaRegs.SCIHBAUD = 0;
6
SciaRegs.SCILBAUD = 1;
7
// Write to bit fields in SCI-A SCICTL1
8
SciaRegs.SCICTL1.bit.SWRESET = 0;
9
SciaRegs.SCICTL1.bit.SWRESET = 1;
10
SciaRegs.SCIFFCT.bit.ABDCLR = 1;
11
SciaRegs.SCIFFCT.bit.CDC = 1;
12
// Poll (i.e., read) a bit
13
while(SciaRegs.SCIFFCT.bit.CDC == 1) { }
14
// Write to the whole SCI-B SCICTL1/2 registers (use .all)
15
ScibRegs.SCICTL1.all = 0x0003;
16
ScibRegs.SCICTL2.all = 0x0000;
#7379708
Lesenswert?

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."

#7379709
Lesenswert?

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.

Gast #7379713
Lesenswert?

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

Gast #7379734
Lesenswert?

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.

#7379736
Lesenswert?

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)

#7379751
Lesenswert?

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.

1
#define ENDSCHALTER (PINB & (1<<3))
2
#define LED_WARNING_ON  PORTC |= (1<<2)
3

4
if (ENDSCHALTER) foo(); else LED_WARNING_ON;
#7379765
Lesenswert?

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)

#7379773
Lesenswert?

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;

#7379794
Lesenswert?

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.

#7379796
Lesenswert?

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.

#7379805
Lesenswert?

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 ;-)

#7379811
Lesenswert?

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.

Moderator (Firma: Titel) Persönliche Seite #7379819
Lesenswert?

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.

Gast #7379851
Lesenswert?

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.

#7379857
Lesenswert?

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.

Gast #7379858
Lesenswert?

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.

#7379862
Lesenswert?

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.

Gast #7379865
Lesenswert?

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.

Angehängte Dateien:
#7379873
Lesenswert?

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 ...

#7379878
Lesenswert?

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.

Moderator Persönliche Seite #7379889
Lesenswert?

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. :-)

#7379917
Lesenswert?

Ü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.

Moderator Persönliche Seite #7379939
Lesenswert?

(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).

#7379940
Lesenswert?

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.

Moderator #7379949
Lesenswert?

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.

Moderator #7379965
Lesenswert?

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

#7380106
Lesenswert?

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.

#7380144
Lesenswert?

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...

#7380189
Lesenswert?

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.

#7380212
Lesenswert?

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.

#7380220
Lesenswert?

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
> #define UTC_2098_12_31_23_59_59 4070908799
2
>

Würde dafür eine constexpr-Funktion nehmen: das kann der Compiler ganz gut zur Compilezeit selbst ausrechnen.

Moderator Persönliche Seite #7380223
Lesenswert?

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 ;-).

Gast #7380247
Lesenswert?

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

Moderator #7380258
Lesenswert?

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.

OP #7380264
Lesenswert?

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 ;-)

#7380330
Lesenswert?

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?

1
constexpr auto secondsSinceEpoch(const std::chrono::year_month_day& d) {
2
    const auto t =  sys_days{d}.time_since_epoch();
3
    return std::chrono::duration_cast<std::chrono::seconds>(t).count();    
4
}

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:

1
constexpr uint32_t julianDay(const uint16_t year, const uint8_t month, const uint8_t day) {
2
    const size_t a = (14 - month)/12;
3
    const size_t y = year+4800-a;
4
    const size_t m = month + 12*a - 3;
5
    return day + (153*m+2)/5 + uint32_t{y}*365 + y/4 - y/100 + y/400 - 32045;
6
}
7

8
constexpr auto secondsSinceEpoch(const uint16_t year, const uint8_t month, const uint8_t day) {
9
    return (julianDay(year, month, day) - julianDay(1970, 1, 1)) * 24u * 3600u;
10
}

Benutzung:

1
int main() {
2
    constexpr auto v1 = secondsSinceEpoch(2098y/12/31) - 1 ;
3
    constexpr auto v2 = secondsSinceEpoch(2098, 12, 31) - 1;
4
}

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

Moderator #7380434
Lesenswert?

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 + 12a - 3; return day + (153m+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
constexpr auto v1 = 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.

#7380447
Lesenswert?

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
> constexpr auto v1 = 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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren