Forum: Mikrocontroller und Digitale Elektronik Cast "verstecken" wenn möglich?


von Veit D. (devil-elec)


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.

von Arduino F. (Firma: Gast) (arduinof)


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.

von Εrnst B. (ernst)


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.

von Klaus K. (Gast)


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.

von MaWin O. (mawin_original)


Lesenswert?

Veit D. schrieb:
> Parameter eines Funktionsaufrufes etc., immer und überall einen Cast
> machen.

Da liegt dein Fehler.
Der Parametertyp der Funktion muss den enum-Typ haben. Nicht integer.

von Martin S. (mmaddin)


Lesenswert?

MaWin O. schrieb:
> Nicht integer.

ein uint8_t wird dann wohl eher mal ein byte oder ein unsigned char, 
oder?

MaWin O. schrieb:
> Funktion muss den enum-Typ haben

Dann wandert der Typecast in die Klasse, aber da dann ja nur ein 
mal...ist sicher einfacher...

von MaWin O. (mawin_original)


Lesenswert?

Martin S. schrieb:
>> Nicht integer.
>
> ein uint8_t wird dann wohl eher mal ein byte oder ein unsigned char,
> oder?

Ich verstehe die Frage nicht.
uint8_t ist ein integer.

von Veit D. (devil-elec)


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.

: Bearbeitet durch User
von Rolf M. (rmagnus)


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.

von Veit D. (devil-elec)


Angehängte Dateien:

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

: Bearbeitet durch User
von Rolf M. (rmagnus)


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.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Veit D. schrieb:
> hBridge.gleisOn((uint8_t)Gleis::GC);

Ist die H-Brücke für mehr Geleise zuständig, als nur das eine?
Wie macht die das? (meine H-Brücken könnten sowas nicht)

: Bearbeitet durch User
von Martin S. (mmaddin)


Lesenswert?

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

Aber vielleicht ist das ja in C++ auch alles anders...

: Bearbeitet durch User
von Rolf M. (rmagnus)


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.

von Bruno V. (bruno_v)


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.

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


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)

: Bearbeitet durch Moderator
von Oliver S. (oliverso)


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

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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.

von Martin S. (mmaddin)


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.

: Bearbeitet durch User
von MaWin O. (mawin_original)


Lesenswert?

Martin S. schrieb:
> Der Datentyp eines uint8_t ist kein integer im C-Standard

falsch.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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

: Bearbeitet durch Moderator
von Rolf M. (rmagnus)


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.

von Bruno V. (bruno_v)


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.

von Some O. (some_one)


Lesenswert?

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

Hier paßts aber ziemlich gut, Herr Fortschrittsverweigerer.

von Oliver S. (oliverso)


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

: Bearbeitet durch User
von Yalu X. (yalu) (Moderator)


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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


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.

von Mark B. (markbrandis)


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.

: Bearbeitet durch User
von Mark B. (markbrandis)


Lesenswert?

Veit D. schrieb:
> Ich hätte es gern ohne Cast in der Verwendung an allen Stellen im
> Programm.
>
1
> Gleis::GA
2
>
>
> Ist das ohne casten möglich in C++?

Wo ist da bitte ein Cast? Du verwendest den "scope resolution operator".
https://en.wikipedia.org/wiki/Scope_resolution_operator

von Oliver S. (oliverso)


Lesenswert?

Jörg W. schrieb:
> Wobei es natürlich nichtsdestotrotz hilfreich ist, wenn man sie gleich
> so definiert, dass ihre Zahlenwerte „zufällig“ der Hardware entsprechen,

Kein Plan überlebt die erste Feindberührung...

Oliver

von Hans-Georg L. (h-g-l)


Lesenswert?

Reine c enums haben den Nachteil das sie global sichtbar sind.
Weil enums zur Kompilezeit konstant sind kannst du sie in C++ anonym in 
eine struct verpacken.
1
  struct Gleis {
2
      enum {GA, GB, GC, GD, GE, GF, GZ, NumberOfGleise};
3
  };
4
  uint8_t x = GLeis::GA  // ohne cast

von Veit D. (devil-elec)


Angehängte Dateien:

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?

von MaWin O. (mawin_original)


Lesenswert?

Veit D. schrieb:
> Inkludiert ihr direkt in der Klasse die diese benötigt

Selbstverständlich. So und nicht anders.

von Some O. (some_one)


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.

von Wilhelm M. (wimalopaan)


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!

von Veit D. (devil-elec)


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

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Wenn das struct "Gleis" heisst, ist es unlogisch für Gleis A dann das 
enum GA zu nennen. Besser wäre Gleis.A. Das "G" in "GA" ist redundant. 
Gleiches bei den Weichen.

von Veit D. (devil-elec)


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.

: Bearbeitet durch User
von Ein T. (ein_typ)


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.

von Rolf M. (rmagnus)


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.

von Oliver S. (oliverso)


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

: Bearbeitet durch User
von Bruno V. (bruno_v)


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.

von Peter D. (peda)


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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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
}

von Ein T. (ein_typ)


Lesenswert?

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

von Mark B. (markbrandis)


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.

von Nicht Joachim B. (roehrmond)


Lesenswert?

Veit D. schrieb:
> Inkludiert ihr direkt in der Klasse die diese benötigt oder schreibt ihr
> alle #includes ins Hauptprogramm?

Immer dort inkludieren, wo es benötigt wird.

von Bruno V. (bruno_v)


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.

von Veit D. (devil-elec)


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

von Peter D. (peda)


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:
>         case GA: return 123;
>         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.

von Veit D. (devil-elec)


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
}

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.