Cast "verstecken" wenn möglich?

OP #7549872
Lesenswert?

Hallo,

ich arbeite gern mit sprechenden Variablen. In dem Fall mit enums.

1
enum class Gleis {GA, GB, GC, GD, GE, GF, GZ, NumberOfGleise};

Jetzt muss ich aber bei Verwendung der eigentlichen Nummer dahinter, Parameter eines Funktionsaufrufes etc., immer und überall einen Cast machen. Irgendwie nervt mich das langsam, weil es die Lesbarkeit beeinträchtigt. Auf der einen Seite wichtig und auf der anderen bläht das die Zeilenlänge auf.

1
(uint8_t)Gleis::GA

Ich hätte es gern ohne Cast in der Verwendung an allen Stellen im Programm.

1
Gleis::GA

Ist das ohne casten möglich in C++? Irgendwo einmalig verstecken? Ansonsten fiele mir nur ein eine Struktur anzulegen und alles von Hand durch zu nummerieren.

(Firma: Gast) #7549875
Lesenswert?

Veit D. schrieb:

Hallo,

Was ist das Ziel der Daten? Also, was willst du damit anstellen? Wozu die Nummern?

Veit D. schrieb:

NumberOfGleise

Ich befürchte, dass du einem alten C Hack aufgesessen bist. Also könntest du auch die alte C Notation nutzen, welche zwar keinen Schutz gegen Böcke implementiert, aber du willst ja gerade Zweckentfremden, also genau die Böcke schießen.

#7549881
Lesenswert?

Die Scoped Enums aus C++11 sind extra eingeführt worden, damit diese NICHT mehr nach int gecastet werden. Du willst das Gegenteil, also verwende die einfach nicht.

1
namespace Gleis { enum {GA, GB, GC, GD, GE, GF, GZ, NumberOfGleise}; }

erreicht in etwa dasselbe, ist aber ein alter, regulärer enum, einer der noch nach int gecastet werden darf und auch automatisch gecastet wird.

Gast #7549888
Lesenswert?

Da steht viel, sicher auch das passende: https://en.cppreference.com/w/cpp/language/enum

Jetzt muss ich aber bei Verwendung der eigentlichen Nummer dahinter

Ja, warum verwendest du auch die integer/numeriusche reüräsentation das das Element des enums repräsentierst und nicht das Element selbst ?! versuchst Du lediglich "magic numbers" einzusparen aber willst dafür keine constant verwenden?

https://dev. to/10xlearner/magic-numbers-and-how-to-deal-with-them-in-c-2jbn

https://stackoverflow.com/questions/47882/what-are-magic-numbers-and-why-do-some-consider-them-bad

Vielleicht hilft ja der der letzte Abschnitt aus https://learn.microsoft.com/de-de/cpp/cpp/enumerations-cpp?view=msvc-170

(bescheutereweise als "Emumerationen ohne Enumerationen" betitelt, weiter.

OP #7549958
Lesenswert?

Hallo,

ob ich die Verwendung in Kurzform erklären kann, ich versuche es einmal.

Selbstgebaute Platine mit H-Brücke und 8Bit I/O Portexpander usw. Damit werden Gleisabschnitte auf der Modelleisenbahn ein- und ausgeschalten. Sieht dann bspw. so aus.

1
hBridge.gleisOn((uint8_t)Gleis::GA); hBridge.gleisOn((uint8_t)Gleis::GC);
2
oder ohne dem einfach so
3
hBridge.gleisOff(0); hBridge.gleisOff(2);

Die Nummern sind letztlich die Ausgänge 0...7 des verbauten Portexpanders PCA8574. Der Rest wird in der Klasse vom Portexpander gemacht.

Typangabe hilft nicht. Casten ist weiterhin erforderlich.

1
enum class Gleis : uint8_t {GA, GB, GC, GD, GE, GF, GZ, NumberOfGleise};

Also aktuell entweder den Cast in die Klasse verschieben. Nur dann muss ich bei Datentypänderung die Klasse anpassen. Ob das passieren kann weiß ich aktuell nicht. Außer vielleicht "auto" funktioniert. Oder ich erstelle eine Struktur für die "Magic Numbers". Jedenfalls benötige ich letztlich Integer für die Funktionsparameter hBridge.gleisOn(n) bzw. hBridge.gleisOff(n). In allen Fällen habe ich noch den Cast denn man aktuell bei Cpp nicht mehr machen soll. Ich möchte das jetzt möglichst sauber haben wenn ich jetzt nochmal daran rumfummel. Wenn ich einzelne Funktionen schreibe mit sprechenden Namen, dann wären darin als Parameter Magic Numbers drin. Gut, die könnte man kommentieren. Bspw. spontan

1
gleisA_einschalten() { hBridge.gleisOn(0);  }
2
gleisA_auschalten()  { hBridge.gleisOff(0); }

Weiß jetzt nicht ob das eine gute Idee ist. Was tun? Das mit dem namespace Gleis von Ernst ginge eigentlich auch.

#7549968
Lesenswert?

Veit D. schrieb:

Sieht dann bspw. so aus.hBridge.gleisOn((uint8_t)Gleis::GA); hBridge.gleisOn((uint8_t)Gleis::GC);

Dann hat deine Funktion gleisOn() aber als Parametertyp nicht Gleis, sondern wohl uint8_t. Warum, wenn sie doch ein Gleis haben will? Wenn es unbedingt uint8_t sein soll, warum verwendest du dann enum class? Das ist ja gerade dazu da, dass es sich nicht ohne Cast in einen Integer konvertieren lässt.

OP #7549975
Lesenswert?

Rolf M. schrieb:

Veit D. schrieb:

Sieht dann bspw. so aus.hBridge.gleisOn((uint8_t)Gleis::GA); hBridge.gleisOn((uint8_t)Gleis::GC);

Dann hat deine Funktion gleisOn() aber als Parametertyp nicht Gleis, sondern wohl uint8_t.

Genau, sie hat uint8_t als Parametertyp.

Warum, wenn sie doch ein Gleis haben will?

Dann wandert der Cast in die Klasse. Dann ist es aus den Augen aus dem Sinn. Ist das eine gute Idee? Irgendwo und irgendwann muss ich aktuell casten wenn ich dafür enum verwenden.

Edit: Klasse angehangen, hoffe das hilft, wobei da noch mehr dran hängt

Angehängte Dateien:
#7549980
Lesenswert?

Veit D. schrieb:

Dann wandert der Cast in die Klasse. Dann ist es aus den Augen aus dem Sinn. Ist das eine gute Idee?

Ja. Wenn die Funktion ein Gleis haben will und dies durch den gleichnamigen enum repräsentiert wird, dann sollte sie diesen auch als Parametertyp haben.

Irgendwo und irgendwann muss ich aktuell casten wenn ich dafür enum verwenden.

… wenn du enum class verwendest. Wenn du nur enum verwendest, geht es ohne Cast.

#7549992
Lesenswert?

Martin S. schrieb:

MaWin O. schrieb:

uint8_t ist ein integer.

Ich wollte darauf hinaus das uint8_t kein Integer ist, also nicht im C-Standard - uint8_t ist ein Alias für einen unsigned char...

Zumal ein "integer" minimal 16 Bit hätte und vorzeichenbehaftet wäre im Gegensatz zu einem unsigned int...

Was du meinst, ist ein int, nicht ein "integer". unsigned char ist kein int, aber ein integer-Typ.

#7550004
Lesenswert?

Veit D. schrieb:

Dann wandert der Cast in die Klasse. Dann ist es aus den Augen aus dem Sinn. Ist das eine gute Idee? Irgendwo und irgendwann muss ich aktuell casten wenn ich dafür enum verwenden.

Ja, die Transformation Gleis->int gehört in gleisOn.

Gleis ist ein abstraktes Objekt (enum), setPin ein konkreter HW-Pin.

Es gibt 3 Möglichkeiten:

  • casten
  • enum (ohne class)
  • Umwandlungsfunktion, z.B. uint8_t GleisToPin(Gleis g)

Sobald Du mehrere H-Brücken hast oder Lücken, beginnen die Trixereien. Eigentlich ist eine Umwandlungsfunktion sauberer, da die Reihenfolge der Enums dann nicht der HW entsprechen müssen und der Code auf andere Setups passt. Aber natürlich macht das in der Praxis jeder so.

Moderator #7550007
Lesenswert?

Derzeit sieht dein Code so aus:

1
    void gleisOn  (const uint8_t g) { icGleis.setPin(g);   } 
2
    void gleisOff (const uint8_t g) { icGleis.clearPin(g); }

Das Argument von gleisOn und gleisOff ist ein Gleis und sollte damit logischerweise vom Type Gleis (enum) sein. Das Argument von setPin und clearPin ist eine Pinnummer, also eine Integer-Zahl. Deswegen sollte der Cast meiner Meinung nach genau dazwischen stattfinden, also so:

1
    void gleisOn  (const Gleis g) { icGleis.setPin((uint8_t)g);   } 
2
    void gleisOff (const Gleis g) { icGleis.clearPin((uint8_t)g); }

Das hat auch den Vorteil, dass der Cast nur an zwei Stellen und nicht bei jedem Aufruf von gleisOn oder gleisOff benötigt wird.

Edit: Bruno war schneller und hat im Wesentlichen dasselbe geschrieben.

Bruno V. schrieb:

Ja, die Transformation Gleis->int gehört in gleisOn.

Gleis ist ein abstraktes Objekt (enum), setPin ein konkreter HW-Pin.

Edit 2:

Das hier finde ich auch gut, vor allem dann, wenn die Konvertierung an mehr als zwei Stellen im Code erfolgen soll:

Bruno V. schrieb:

  • Umwandlungsfunktion, z.B. uint8_t GleisToPin(Gleis g)
#7550013
Lesenswert?

Veit D. schrieb:

Ist das ohne casten möglich in C++? Irgendwo einmalig verstecken? Ansonsten fiele mir nur ein eine Struktur anzulegen und alles von Hand durch zu nummerieren.

Nur weil es plötzlich in C++ sehr viele neue Dinge gibt, muß man die nicht krampfhaft nutzen, wenn die nicht passen.

Bleib beim enum ohne class, oder ändere die Logik so, daß die ohne deren Integerwerte funktioniert. Beides zusammen ist Krampf.

Oliver

Moderator Persönliche Seite #7550020
Lesenswert?

Yalu X. schrieb:

Das hier finde ich auch gut, vor allem dann, wenn die Konvertierung an mehr als zwei Stellen im Code erfolgen soll:

Insbesondere kann man die Funktion ja inline realisieren, dann hat man erstmal keinerlei Overhead. Falls, wie oben angedeutet, später doch mal Gleisnummer != Pinnummer wird aus irgendeinem Grund, muss man nur noch genau diese Funktion anpassen. Ist eine saubere logische Trennung der einzelnen Funktionen.

#7550049
Lesenswert?

Rolf M. schrieb:

Was du meinst

Was ich meine:

Der Datentyp eines uint8_t ist kein integer im C-Standard sondern ein unsigned char, mehr wollte ich nicht sagen.

Ein integer Datentyp (int type) ist im C-Standard mindestens 16 Bit lang!

Das ist zumindest das was ich mal gelernt habe. Es ist aber lange her und vielleicht habe ich das ja auch falsch gelernt, alles denkbar.

Moderator Persönliche Seite #7550058
Lesenswert?

Martin S. schrieb:

uint8_t ist kein integer im C-Standard

Es ist ein "integer type".

sondern ein unsigned char

Nein. Das steht so nirgends, auch wenn beide häufig äquivalent sein dürften.

Davon abgesehen, auch ein "unsigned char" ist ein "integer type":

"There are five standard signed integer types, designated as signed char, short int, int, long int, and long long int." (Die "unsigned" pendants gibt es natürlich auch alle.)

#7550060
Lesenswert?

Martin S. schrieb:

Der Datentyp eines uint8_t ist kein integer im C-Standard sondern ein unsigned char, mehr wollte ich nicht sagen.

Ein unsigned char ist ein integer.

Ein integer Datentyp (int type) ist im C-Standard mindestens 16 Bit lang!

Nochmal, du verwechselt da den Typ int mit einem integer allgemein. Nicht jeder integer ist vom Typ int, denn es gibt mehrere integer-Typen. int ist einer davon, unsigned char ein anderer. Für int gilt deine Aussage, aber sie gilt nicht für jeden integer-Typ.

#7550062
Lesenswert?

Martin S. schrieb:

Ein integer Datentyp (int type) ist im C-Standard mindestens 16 Bit lang!

Was Rolf meint: "integer" ist einfach nur eine "Ganzzahl". Egal ob 1 oder 7 Byte, 3 oder 17 Bit, signed oder unsigned.

Prinzipiell sind alle integer Datentypen "gleich", nur der Wertebereich verschieden.

"int" ist (wie Du schreibst) ein konkreter Typ, wenn auch nicht immer gleich.

#7550238
Lesenswert?

Some O. schrieb:

Hier paßts aber ziemlich gut, Herr Fortschrittsverweigerer.

Nein, es passt nicht, wenn die enums nur etwas aufgemotzte Integer-Werte sind, und die ganze Anwendung eigentlich auf Integer angewiesen ist. Dafür ist enum class halt nicht so richtig passend. Die Alternative mit einem enum in einem namespace wurde ja oben auch schon genannt.

Die eigentliche Frage ist aber doch, warum überhaupt die Integerwerte benötigt werden.

Sauber wäre eine Implementierung, in der die Anwendung nur und ausschließlich mit den enum-Werten arbeitet. Solange die Gleise keine Reihenfolge haben müssen, oder eine Sortierung notwendig ist, braucht es da überhaupt keine Integer (und selbst wenn, geht das). Da klappt das dann auch problemlos mit class enum.

Wenn doch irgendwo nahe an der Hardware Integer benötigt werden, dann gehört die Umwandlung an eine einzige Stelle in den "Treiber", der dann direkt an den I/Os rumfummelt.

Oliver

Moderator #7550390
Lesenswert?

Oliver S. schrieb:

Die eigentliche Frage ist aber doch, warum überhaupt die Integerwerte benötigt werden.

Für die Adressierung der einzelnen Pins des Port-Expanders.

Wenn doch irgendwo nahe an der Hardware Integer benötigt werden,

... was tatsächlich der Fall ist (s.o.) ...

dann gehört die Umwandlung an eine einzige Stelle in den "Treiber", der dann direkt an den I/Os rumfummelt.

... was bereits mehrfach vorgeschlagen wurde.

Moderator Persönliche Seite #7550405
Lesenswert?

Yalu X. schrieb:

dann gehört die Umwandlung an eine einzige Stelle in den "Treiber", der dann direkt an den I/Os rumfummelt.

... was bereits mehrfach vorgeschlagen wurde.

Wobei es natürlich nichtsdestotrotz hilfreich ist, wenn man sie gleich so definiert, dass ihre Zahlenwerte „zufällig“ der Hardware entsprechen, solange das möglich ist – das reduziert die Abstraktionsschicht rein physisch dann auf Null.

#7550414
Lesenswert?

Veit D. schrieb:

Hallo,

ich arbeite gern mit sprechenden Variablen. In dem Fall mit enums.

1
> enum class Gleis {GA, GB, GC, GD, GE, GF, GZ, NumberOfGleise};
2
>

Genau mein Humor: Jemand redet von sprechenden Variablen und bringt dann ein Beispiel dafür, wie man es genau nicht macht.

Woher soll der Leser deines Codes denn wissen, wofür GA, GB, GC usw. steht?

Dazu noch ein Mischmasch aus deutsch und englisch: NumberOfGleise

Das soll wohl "NumberOfTracks" heißen oder "AnzahlGleise" oder sowas in der Art.

OP #7550775
Lesenswert?

Hallo,

wie Einige gelesen haben, ist die Zuordnung der Gleisabschnitte zur Hardware (Portexpander) passend gemacht. Sprich die Verdrahtung entspricht der enum Reihenfolge bzw. umgekehrt. Auf dem Bild unten links ist die besagte Platine. Schaltplan wegen der H-Brückenfragen vs. Gleise hängt auch dran.

Ich tendiere aktuell zur Lösung mit namespace enum oder struct enum. Sodass ich auf den Cast, egal wo er stehen würde ganz verzichten kann. Dennoch habe ich damit eine Kapselung, sodass man die enum Namen nicht völlig wild verwenden kann. Betrifft sowieso nur mich der das schreibt und lesen muss und der sich selbst vor groben Dummheiten schützen möchte. ;-)

So eine Modelleisenbahn zu programmieren ist schon eine andere Nummer. Es gibt immer wieder neue Ideen und Änderungen.

Dazu gleich noch eine Frage zum inkludieren. Auf die Gefahr hin das es wohl keine eindeutige Antwort geben könnte. Wie macht ihr das gewöhnlich, wenn eine Klasse eine andere Klasse benötigt und inkludiert werden muss. Inkludiert ihr direkt in der Klasse die diese benötigt oder schreibt ihr alle #includes ins Hauptprogramm? Wobei man hier wiederum das Problem haben kann das man tunlichts auf die Reihenfolge achten muss. Die Frage dreht sich eher darum ob man allen Lesern im Hauptprogramm zeigt, welche Klassen benötigt werden oder ob der Leser sich diese Information bei Bedarf in den Header etc. selbst zusammensuchen soll?

Angehängte Dateien:
#7550802
Lesenswert?

Oliver S. schrieb:

Some O. schrieb:

Hier paßts aber ziemlich gut, Herr Fortschrittsverweigerer.

Nein, es passt nicht, wenn die enums nur etwas aufgemotzte Integer-Werte sind, und die ganze Anwendung eigentlich auf Integer angewiesen ist.

Du kannst das gerne andes sehen, aber ich halte Typsicherheit und Lesbarkeit für wichtig. Und eine Enum an dieser Stelle zu nutzen, hilft beiden Aspekten. Sonst kann man auch Magic Numbers nehmen und mit ein bissel Unkonzentriertheit bekommt man Magic Smoke.

#7550813
Lesenswert?

Veit D. schrieb:

Inkludiert ihr direkt in der Klasse die diese benötigt oder schreibt ihr alle #includes ins Hauptprogramm?

Natürlich in der Header-Datei der Klasse bzw. der Implementierungsdatei der Klasse, je nachdem wo der vollständige Typ gebraucht wird. Um Zyklen aufzulösen, kann man Header mit nur unvollständigen Deklarationen machen (s.a. <iosfwd> statt <iostream>).

An anderer Stelle (im "Hauptprogramm") darf der Anwender der Klasse nicht über eine Reihenfolge nachdenken müssen!

OP #7550924
Lesenswert?

Hallo,

okay, Danke euch. Habe nochmal inne gehalten und mich für struct enum entschieden. Ich denke das ist der passende Weg für mich. Damit kann ich solche Konstrukte wie bspw.

1
enum class Sensorboard {SA1, SB1, SB2, SC1, SC2, SC3, SD1, SD2, SD3, SE1, SF1, NumberOfSensors};
2
enum class Servoname {WBCL, WCDL, WCDR, WDER, SCL, SCR, SDL, SDR, NumberOfServos};
3
enum class Gleis : uint8_t {GA, GB, GC, GD, GE, GF, GZ, NumberOfGleise};
4

5
struct Lok {
6
  enum class Name {BR110, BR118, BR130, BR83, BR86, NumberOfLoks};
7
  uint8_t selected {(uint8_t)Name::BR86};
8
  Lokomotive typ [(uint8_t)Name::NumberOfLoks] {
9
    {33, 72, 200, 200, 200, 200},  // BR110  
10
    {28, 52, 200, 200, 200, 200},  // BR118
11
    {24, 66, 200, 200, 200, 200},  // BR130
12
    {20, 65, 200, 200, 200, 200},  // BR83.10
13
    {24, 45, 320, 200, 200, 200},  // BR86
14
  };
15
} lok;
16

17
ControlWeiche weiche [] {
18
  { 1, (uint8_t)Servoname::WBCL, (uint8_t)Sensorboard::SD1, (uint8_t)Sensorboard::SC1, (uint8_t)Sensorboard::SB1},
19
  { 1, (uint8_t)Servoname::WCDL, (uint8_t)Sensorboard::SD1, (uint8_t)Sensorboard::SC1},
20
  {-1, (uint8_t)Servoname::WCDR, (uint8_t)Sensorboard::SC3, (uint8_t)Sensorboard::SD3},
21
  {-1, (uint8_t)Servoname::WDER, (uint8_t)Sensorboard::SC3, (uint8_t)Sensorboard::SD3, (uint8_t)Sensorboard::SE1}
22
};
23

24
ControlSignal signal [] {
25
  (uint8_t)Servoname::SCL,
26
  (uint8_t)Servoname::SCR,
27
  (uint8_t)Servoname::SDL,
28
  (uint8_t)Servoname::SDR
29
};
30

31
ControlGleis gleis [] {
32
  { (uint8_t)Gleis::GA, (uint8_t)Sensorboard::SA1 },
33
  { (uint8_t)Gleis::GB, (uint8_t)Sensorboard::SB1 },   // nicht echt, Gleis ohne Sensor, derzeit Hallsensor F1 gesteckt
34
  { (uint8_t)Gleis::GC, (uint8_t)Sensorboard::SC1,(uint8_t)Sensorboard::SC2, (uint8_t)Sensorboard::SC3 },
35
  { (uint8_t)Gleis::GD, (uint8_t)Sensorboard::SD1,(uint8_t)Sensorboard::SD2, (uint8_t)Sensorboard::SD3 },   
36
};

ändern in

1
struct Sensoren {enum {SA1, SB1, SB2, SC1, SC2, SC3, SD1, SD2, SD3, SE1, SF1, Anzahl}; } const sensor;
2
struct Servos  {enum {WBCL, WCDL, WCDR, WDER, SCL, SCR, SDL, SDR, Anzahl}; } const servo;
3
struct Gleise  {enum {GA, GB, GC, GD, GE, GF, GZ, Anzahl}; } const gleis;
4

5
struct Lok {
6
  enum  {BR110, BR118, BR130, BR83, BR86, Anzahl};
7
  uint8_t selected {BR86};
8
  Lokomotive typ [Anzahl] {
9
    {33, 72, 200, 200, 200, 200},  // BR110  
10
    {28, 52, 200, 200, 200, 200},  // BR118
11
    {24, 66, 200, 200, 200, 200},  // BR130
12
    {20, 65, 200, 200, 200, 200},  // BR83.10
13
    {24, 45, 320, 200, 200, 200},  // BR86
14
  };
15
} lok;
16

17
ControlWeiche controlWeiche [4] {
18
  { 1, servo.WBCL, sensor.SD1, sensor.SC1, sensor.SB1},
19
  { 1, servo.WCDL, sensor.SD1, sensor.SC1},
20
  {-1, servo.WCDR, sensor.SC3, sensor.SD3},
21
  {-1, servo.WDER, sensor.SC3, sensor.SD3, sensor.SE1}
22
};
23

24
ControlSignal controlSignal [4] {
25
  servo.SCL,
26
  servo.SCR,
27
  servo.SDL,
28
  servo.SDR
29
};
30

31
ControlGleis controlGleis [4] {
32
  { gleis.GA, sensor.SA1 },
33
  { gleis.GB, sensor.SB1 },  
34
  { gleis.GC, sensor.SC1, sensor.SC2, sensor.SC3 },
35
  { gleis.GD, sensor.SD1, sensor.SD2, sensor.SD3 },   
36
};

Das hilft mir aufzuräumen, Typsicherheiten beizubehalten und es lesbarer zu bekommen. Zog sich ja noch weiter durch das Programm. include Frage ist auch beantwortet. Danke an alle. Bevor der Nächste meckert, die Magic Number 1 bzw. -1 kommt auch noch weg für CW bzw. CCW. Ist ja alles noch "under construction" inkl. Spieltrieb zwischendurch. :-)

OP #7550961
Lesenswert?

Hallo,

du hast völlig recht Wilhelm. Nun kommt jedoch das aber. Kannst du dich erinnern an meinen Thread zu den #defines? Das Problem ist das im DxCore Namen wie A1 für Pins schon mittels #define existieren und dann kollidiert das alles mit meinen Namen, wegen Präprozessor. Deswegen bin ich den verschmerzbaren Umweg gegangen einen Anfangsbuchstaben zu wählen. Ich habe mich daran schnell gewöhnt.

#7550995
Lesenswert?

Veit D. schrieb:

Hallo,

du hast völlig recht Wilhelm. Nun kommt jedoch das aber. Kannst du dich erinnern an meinen Thread zu den #defines? Das Problem ist das im DxCore Namen wie A1 für Pins schon mittels #define existieren und dann kollidiert das alles mit meinen Namen, wegen Präprozessor. Deswegen bin ich den verschmerzbaren Umweg gegangen einen Anfangsbuchstaben zu wählen. Ich habe mich daran schnell gewöhnt.

Naja, das gehört ja ohnehin zu Deiner enum und ist durch diese Zugehörigkeit doch schon absolut eindeutig bezeichnet?! Das ist schon was anderes als die eher stumpfen Textersetzungen eines Präprozessors, methinks.

#7551202
Lesenswert?

Ein T. schrieb:

Naja, das gehört ja ohnehin zu Deiner enum und ist durch diese Zugehörigkeit doch schon absolut eindeutig bezeichnet?! Das ist schon was anderes als die eher stumpfen Textersetzungen eines Präprozessors, methinks.

Nun, wenn irgendwo ein #define mit dem Namen existiert, dann kann dieser Name nirgendwo mehr für was anderes verwendet werden, auch nicht als Enum-Wert.

#7551216
Lesenswert?

Veit D. schrieb:

Damit kann ich solche Konstrukte wie bspw. ... ControlWeiche weiche [] { { 1, (uint8_t)Servoname::WBCL, (uint8_t)Sensorboard::SD1, (uint8_t)Sensorboard::SC1, (uint8_t)Sensorboard::SB1}, ... ändern in ... ControlWeiche controlWeiche [4] { { 1, servo.WBCL, sensor.SD1, sensor.SC1, sensor.SB1},

Das kannst du auch, wenn du enum class Typen verwendest. ControlWeiche darf dann keine Integer, sondern deine enum class Typen enthalten. Nur der Treiber, der dann mit den Daten aus CotrolWeiche die I/Os bedient, muß wissen, welche Pins angesteuert werden müssen. Der macht dann die Umsetzung von der enum class Typen in Integer. Alle Ebenen da drüber brauchen das nicht.

Oliver

#7551396
Lesenswert?

Veit D. schrieb:

Habe nochmal inne gehalten und mich für struct enum entschieden.

Finde ich gut. In C erzwinge ich die strenge Typprüfung oft so (auch wenn enums selber global sind)

1
enum eGleis {GA, GB, GC, GD, GE, GF, GZ, NumberOfGleise};
2

3
typedef struct
4
{
5
   enum eGleis nr;
6
}GleisType;
7

8
void gleisOn  (GleisType g) { icGleis.setPin(g.nr);   } 
9
void gleisOff (GleisType g) { icGleis.clearPin(g.nr); }
10

11
int main(void)
12
{
13
GleisType GleisWest   = {GC}; 
14
SensorType SensorOben = {GC}; /* <-- nur Warnung */
15

16
   gleisOn(GleisWet);
17
   gleisOn(SensorOben); /* <-- Compilerfehler, selbst mit Warnungen aus */
18
}

Bei Gleisen mag das wie Overkill aussehen, verhindert aber das ein Sensor übergeben wird. Vielleicht wird es anschaulicher, wenn man im Programm Auftrags- und Kundennummer hat und beide u32 sind. Irgendwann hat jemand in einem Sub-Block nur noch "Nr" und der nächste Maintainer ruft damit "CheckOrder(Nr)" statt "CheckCustom(Nr)" auf.

#7551454
Lesenswert?

Veit D. schrieb:

wie Einige gelesen haben, ist die Zuordnung der Gleisabschnitte zur Hardware (Portexpander) passend gemacht. Sprich die Verdrahtung entspricht der enum Reihenfolge bzw. umgekehrt.

Hardware hat die Eigenschaft, sich während der Entwicklung mehrfach zu ändern oder später erweitert zu werden, da kann einem eine feste Zuordnung schnell auf die Füße fallen. Ich versuche daher, die Pinzuweisung variabel zu halten und nur in einem Header zuzuweisen. Dann kann man auch beim Routen der Platine noch Pins vertauschen, um Vias zu sparen. Weniger Vias machen das Layout übersichtlicher bei der Fehlersuche und beim Patchen. Für Pins, die über Expander bedient werden, lege ich daher Schattenvariablen im RAM als Bit-Struct an, die ausschließlich verwendet werden. Hier mal ein Beispiel: Definition der Bitstruct:

1
struct bits {
2
  uint8_t b0:1;
3
  uint8_t b1:1;
4
  uint8_t b2:1;
5
  uint8_t b3:1;
6
  uint8_t b4:1;
7
  uint8_t b5:1;
8
  uint8_t b6:1;
9
  uint8_t b7:1;
10
} __attribute__((__packed__));
11

12
#define SBIT(port,pin) ((*(struct bits*)&port).b##pin)

Definition der Pinzuordnung:

1
uint8_t output_ram[2] = { 0x00, 0x01 };  // REL_DBM on !
2
uint8_t input_ram[2];
3

4
/******************************  Inputs *********************************/
5

6
#define PLC_REM    SBIT( input_ram[0], 0)
7
#define PLC_CONNECT  SBIT( input_ram[0], 1)
8
#define HFC_HFON  SBIT( input_ram[0], 2)
9
#define PLC_DBM    SBIT( input_ram[0], 3)
10
#define STC_REQ    SBIT( input_ram[0], 4)
11
#define HFC_PWR    SBIT( input_ram[0], 6)
12
#define PLC_PWR    SBIT( input_ram[0], 7)
13

14
#define  TARGET    SBIT( input_ram[1], 3)  // 0 = stop
15
#define  MCU_ACK    SBIT( input_ram[1], 4)  // MCU ready
16
#define  POSITION  SBIT( input_ram[1], 5)  // Position sensor
17

18
/******************************  Outputs *********************************/
19

20
#define  MCU_REQ    SBIT( output_ram[0], 7)  // MCU request
21
#define  POS_PWR    SBIT( output_ram[0], 6)  // Sensor Power
22
#define STC_DAT0  SBIT( output_ram[0], 4)  // data to STC
23
#define  STC_DAT1  SBIT( output_ram[0], 5)  // data to STC
24
#define HFC_ST0    SBIT( output_ram[0], 0)
25
#define HFC_ST1    SBIT( output_ram[0], 1)
26
#define  HFC_REM    SBIT( output_ram[0], 2)
27

28
#define REL_DBM    SBIT( output_ram[1], 0)  // DBM (default)
29
#define REL_STC    SBIT( output_ram[1], 1)  // STC request
30
#define REL_SIMS  SBIT( output_ram[1], 2)  // SIMS (OPC)

Statemachines:

1
static void stc_actions( void )
2
{
3
  LED_STC = 0;
4
  MCU_REQ = 0;
5
  STC_DAT0 = 0;
6
  STC_DAT1 = 0;
7
  POS_PWR = 1;        // power for optical sensor
8

9
  if( STC_REQ ){
10
    LED_STC = 1;
11
    LED_SHC = 0;      // SHC off
12
    MCU_REQ = 1;
13
    if( MCU_ACK ){           // MCU not ready:   0-0
14
      STC_DAT0 = 1;                     // not in position: 0-1
15
      if( POSITION ){        // in position:     1-1
16
  STC_DAT1 = 1;
17
      }
18
      if( TARGET == 0 ){    // contakt closed:  1-0
19
  STC_DAT1 = 1;
20
  STC_DAT0 = 0;
21
      }
22
    }
23
  }
24
}
25

26
void control( void )
27
{
28
  key_scan();    // handle key press
29
  stc_actions();  // stc functions
30
  plc_actions();  // plc functions
31
  hfc_actions();  // hfc functions
32
  calib_select();  // select ADC for calibration
33
  adc_select();    // select ADC input channel
34
  hfc_relays();    // hfc relay control
35
  tlc_relays();    // tlc relay control
36
}

Mainloop:

1
  for(;;){        // gemessen: 355µs je Durchlauf
2
    rx_poll();        // UART receive
3
    command();
4
    control();
5
    update_all();
6
    if( tick_10ms ){
7
      tick_10ms = 0;
8
      SWtimer_tick();      // do delayed tasks
9
    }
10
  }

Der AVR-GCC kann die Bitzugriffe sehr gut optimieren, d.h. hält die Bytes in Registern, wenn sie mehrfach benötigt werden. Mit Enums dürfte das aber ein riesen Codemonster werden (bezüglich Schreibarbeit als auch Binärcode).

update_all(); schreibt und liest die Expander, d.h. man muß nicht für jedes einzelne IO-Bit Zeit verschwenden. Eingänge bleiben dadurch während des gesamten Zyklus konsistent. Obendrein dürfen Outputs mehrfach geändert werden, ohne daß es zu Glitches kommt.

#7551517
Lesenswert?

Johann L. schrieb:

Eigentlich brauchst du doch eine Abbildung, die Gleise auf H-Brücken abbildet? Also sowas wie

1
> constexpr uint8_t GleisToHBridge (enum eGleis gleis)
2
> {
3
>     switch (gleis)
4
>     {
5
>         case GA: return 123;
6
>         case GB: return 42;
7
>         ...
8
>     }
9
> }
10
>

Würde ich auch so machen. Eine Funktion, die die abstrakte Nummerierung in die reale Welt übersetzt und gut is.

#7551581
Lesenswert?

Veit D. schrieb:

Inkludiert ihr direkt in der Klasse die diese benötigt oder schreibt ihr alle #includes ins Hauptprogramm?

Jede Header (.h-Datei) enthält alle #includes, die es braucht. Auch wenn das nicht zwingend ist, sollte es so sein. Natürlich mit #Include-Guard in welcher Form auch immer.

Jede Klasse (.c-Datei) enthält alle #includes, die es braucht. Direkt oder Indirekt. (Zwingend, logisch)

Wenn ich irgendwo die Klasse "Gleis" verwenden will, dann muss "Gleis.h" reichen. Wenn auch "Schiene.h" oder "Gleisbett.h" benötigt werden, muss Gleis.h die mitbringen.

Ausnahme sind manchmal projektspezifische Konfigurationsdateien ("config.h") am Anfang jeder Applikations-.c.

Bei Instanzen (GleisA in "GleisA.h", GleisB in "GleisB.h") kann es sein, dass die nur in einer Datei (main.c) benötigt werden und alle anderen mit der Oberklasse "Gleis.h" zufrieden sind. Dann ist Gleis.h in GleisA.h und nicht umgekehrt. Und beide parallel in main.c.

OP #7551696
Lesenswert?

Hallo,

Ein T. schrieb:

Rolf M. schrieb:

Nun, wenn irgendwo ein #define mit dem Namen existiert, dann kann dieser Name nirgendwo mehr für was anderes verwendet werden, auch nicht als Enum-Wert.

True, aber wer würde denn sowas machen? :-)

Es ist ist wie es ist. An dem DxCore fummel ich nicht dran rum. Der wird nur angewendet. Mehr möchte ich dazu gar nicht schreiben. Das Thema ist durch.

// -----------------------------------------------------------------

Johann L. schrieb:

Eigentlich brauchst du doch eine Abbildung, die Gleise auf H-Brücken abbildet? Also sowas wie

1
> constexpr uint8_t GleisToHBridge (enum eGleis gleis)
2
> {
3
>     switch (gleis)
4
>     {
5
>         case GA: return 123;
6
>         case GB: return 42;
7
>         ...
8
>     }
9
> }
10
>

Muss ich mir überlegen. Hier prasseln gefühlt Hunderte weitere Ideen ein. ;-)

// -----------------------------------------------------------------

Oliver S. schrieb:

Veit D. schrieb:

Damit kann ich solche Konstrukte wie bspw. ... ControlWeiche weiche [] { { 1, (uint8_t)Servoname::WBCL, (uint8_t)Sensorboard::SD1, (uint8_t)Sensorboard::SC1, (uint8_t)Sensorboard::SB1}, ... ändern in ... ControlWeiche controlWeiche [4] { { 1, servo.WBCL, sensor.SD1, sensor.SC1, sensor.SB1},

Das kannst du auch, wenn du enum class Typen verwendest. ControlWeiche darf dann keine Integer, sondern deine enum class Typen enthalten. Nur der Treiber, der dann mit den Daten aus CotrolWeiche die I/Os bedient, muß wissen, welche Pins angesteuert werden müssen. Der macht dann die Umsetzung von der enum class Typen in Integer. Alle Ebenen da drüber brauchen das nicht.

und auch Bruno V. schrieb:

Den Datentyp strenger zu setzen muss ich mir auch überlegen. Die Idee ist erstmal nicht falsch. Schützt vor Dummheiten. ;-)

// -----------------------------------------------------------------

Peter D. schrieb:

Veit D. schrieb:

wie Einige gelesen haben, ist die Zuordnung der Gleisabschnitte zur Hardware (Portexpander) passend gemacht. Sprich die Verdrahtung entspricht der enum Reihenfolge bzw. umgekehrt.

Hardware hat die Eigenschaft, sich während der Entwicklung mehrfach zu ändern oder später erweitert zu werden, da kann einem eine feste Zuordnung schnell auf die Füße fallen. Ich versuche daher, die Pinzuweisung variabel zu halten und nur in einem Header zuzuweisen.

Eigentlich bin ich absolut flexibel. Bin jetzt ganz verunsichert. Falls ich die Verdrahtung ändere, was wohl nicht passieren wird, muss ich nur die Namen im enum umsortieren. Denn die Reihenfolge entspricht der Bitnummer 0...7 vom I/O des PortExpanders. Das hier meine ich, kann ich jederzeit umsortieren wenn es denn sein müßte. Bei den Sensoren 0...15, sind noch paar frei.

1
struct Sensoren {enum {SA1, SB1, SB2, SC1, SC2, SC3, SD1, SD2, SD3, SE1, SF1, Anzahl}; } const sensor;
2
struct Servos  {enum {WBCL, WCDL, WCDR, WDER, SCL, SCR, SDL, SDR, Anzahl}; } const servo;
3
struct Gleise  {enum {GA, GB, GC, GD, GE, GF, GZ, Anzahl}; } const gleis;

Das mit den Bit-Strukturen schau ich mir an. Danke.

// -----------------------------------------------------------------

Inkludierung ist klar. :-)

#7552705
Lesenswert?

Johann L. schrieb:

Eigentlich brauchst du doch eine Abbildung, die Gleise auf H-Brücken abbildet?

Da stellt sich mir die Frage, warum überhaupt erstmal Enums anlegen, wenn dann doch wieder alles umständlich umgemappt werden muß. Das kostet nur unnütz Schreibarbeit und Code und verschlechtert die Lesbarkeit rapide. Man kann doch gleich sprechende Symbole anlegen, denen sofort die konkrete Byteadresse und Bitnummer mitgeteilt wird.

Johann L. schrieb:

1
    case GA: return 123;
2
    case GB: return 42;

Sowas mag ich nun überhaupt nicht, magische Nummern, da geht doch der Überblick komplett verloren. Es sollte schon leicht zu erkennen sein, welcher IO-Port und welches Bit da angesprochen wird. Ansonsten sucht man sich dumm und dämlich nach Zuordnungsfehlern.

OP #7552742
Lesenswert?

Hallo,

ich habe das Gefühl aktuell dreht man sich im Kreis. Nicht böse gemeint.

Enums, weil das für mich die kürzeste Form ist sprechende Symbole abzubilden. Die werden automatisch durchnummeriert in der Reihenfolge wie ich diese benötige.

1
struct Gleise {enum {GA, GB, GC, GD, GE, GF, GZ, Anzahl}; };
2
Ermöglicht
3
Gleise::GA
4

5
oder
6

7
struct Gleise {enum {GA, GB, GC, GD, GE, GF, GZ, Anzahl}; } const gleis;
8
Ermöglicht
9
gleis.GA

Alles andere wie bspw.

1
struct Gleis {
2
  const uint8_t GA {0};
3
  const uint8_t GB {1};
4
  const uint8_t GC {2};
5
  const uint8_t GD {3};
6
  const uint8_t GE {4};
7
  const uint8_t GF {5};
8
  const uint8_t GZ {6};
9
};

oder

1
constexpr uint8_t GleisToHBridge (enum eGleis gleis)
2
{
3
    switch (gleis)
4
    {
5
        case GA: return 0;
6
        case GB: return 1;
7
        case GC: return 2;
8
        case GD: return 3;
9
        case GE: return 4;
10
        case GF: return 5;
11
        case GZ: return 6;
12
        default: break;
13
    }
14
}

ist mehr Tipparbeit und Fehlerträchtig, empfinde ich.

Auch die Bitstruct Idee habe ich mir überlegt. Jedoch ist es auch mehr Schreibaufwand ähnlich dem obigen struct Gleis. Ich sehe da leider keinen Vorteil drin. Wenn man sich hierbei vertippt sucht man sicherlich auch lange Zeit den Fehler. Ich wüßte jetzt ehrlich gesagt nicht was diese eine Zeile

1
struct Gleise {enum {GA, GB, GC, GD, GE, GF, GZ, Anzahl}; };

toppen könnte. Hier steckt alles drin was benötigt wird. Der "Wert" wird dem Objekt übergeben was den Portexpander verwaltet und der weiß was zu tun ist. Hier muss nichts umgemappt werden. Ist genauso direkt verwendbar wie Bitstrukturen. Vielleicht habe ich einen Tunnelblick, vielleicht haben andere einen Tunnelblick. Kann ich nicht beurteilen. Nur eins weiß ich. Mir gefallen die geänderten struct-Enums ohne Cast und dabei werde ich bleiben. Einfach zu schreiben und sicher anzuwenden.

Nach aller vorheriger Konfigurationsorgie ist die Hauptschleife dafür allein gesehen praktisch wie leer.

1
hallSensor.update();
2

3
for (auto &g : controlGleis) { 
4
  g.update(hallSensor);
5
}
6

7
for (auto &w : controlWeiche) { 
8
  w.checkPosition();
9
  if (w.isChangedPosition()) { 
10
    framControl.update(w.getObjIndex(), structOffset.last, servoObj[w.getObjIndex()].last);
11
  }
12
}
13

14
for (auto &s : controlSignal) { 
15
  s.checkPosition();
16
  if (s.isChangedPosition()) {
17
    framControl.update(s.getObjIndex(), structOffset.last, servoObj[s.getObjIndex()].last);
18
  }
19
}

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