Schleife um Funktionsparameter zu durchlaufen

Hallo zusammen, folgende Problemstellung stellt sich mir gerade: Ich habe eine Funktion, der acht Default-Parameter mitgegeben werden, der Dummy schaut so aus:

void initPLC(uint8_t Port0 = iPORT, uint8_t Port1 = oPORT, etc, uint8_t Port7 = iPORT)
{
if {Port0 == 1} bitSet(ioType,0);
if {Port1 == 1} bitSet(ioType,1);
...
if {Port7 == 1} bitSet(ioType,7);
// Funktion geht weiter
}

Das muss doch irgendwie "eleganter" gehen, ich hab' gerade ein Brett vom Kopf. Ein Array kann und will ich nicht übergeben, weil der User beim Aufruf der Funktion die Parameter ändern können soll, es soll also flexibel bleiben.

Greetz
Cox

Für C geht das so:

https://en.cppreference.com/w/c/variadic

Sollte auch in C++ klappen.

Danke, auf "va_" bin ich auch schon gestoßen, allerdings habe ich es so verstanden dass es für eine variable Parameteranzahl gedacht ist. Hier sind's ja fix acht Stück. Zudem habe ich auch keine Lösung gefunden, wie ich mit einem solchen Konstrukt die Defaultwerte übergeben kann.

Hallo IRCox

hier kommt eine Lösung in C++.

//https://forum.arduino.cc/t/schleife-um-funktionsparameter-zu-durchlaufen/1167855
//https://europe1.discourse-cdn.com/arduino/original/4X/7/e/0/7e0ee1e51f1df32e30893550c85f0dd33244fb0e.jpeg
#define ProjectName "Schleife um Funktionsparameter zu durchlaufen"
#define NotesOnRelease "first proposal"
// make names
enum Ports {iPort, oPort};
// make structures
struct INITPLC
{
  uint8_t ports[8];
  uint8_t ioType;
  void make()
  {
    uint8_t bitNumber = 0;
    for (auto port : ports)
    {
      if (port == oPort) bitSet(ioType,bitNumber);
      bitNumber++;
    }
    Serial.println(ioType, BIN);
  }
} initPLC {iPort, oPort, iPort, oPort, iPort, oPort, iPort, oPort, 0};


// make support
void heartBeat(int LedPin, uint32_t currentMillis)
{
  static bool setUp = false;
  if (!setUp) pinMode (LedPin, OUTPUT), setUp = !setUp;
  digitalWrite(LedPin, (currentMillis / 500) % 2);
}
// make application
void setup()
{
  Serial.begin(115200);
  Serial.print("Source: "), Serial.println(__FILE__);
  Serial.print(ProjectName), Serial.print(" - "), Serial.println(NotesOnRelease);
  
  initPLC.make();
}
void loop()
{
  uint32_t currentMillis = millis();
  heartBeat(LED_BUILTIN, currentMillis);
}

Ich wünsche einen geschmeidigen Abend und viel Spass beim Programmieren in C++.

Vielen Dank für den Denkanstoss... so ganz raffe ich das noch nicht muss ich ehrlich sagen, aber ich beisse mich da durch. Schön wäre es den oben genannten Funktionsaufruf beizubehalten.

Tja....
Dass du in die Irre gelaufen bist, dürfte ja jetzt relativ klar geworden sein.
Ich vermute ein X-Y Problem.

Die Frage ist nun: Was willst du "wirklich" erreichen.

Im Idealfall möchte ich den Funktionsaufruf ...

void initPLC(uint8_t Port0 = iPORT, uint8_t Port1 = oPORT, etc, uint8_t Port7 = iPORT)

... inklusive der Defaultwerte beibehalten (oder zumindest userfreundlich gestalten) und in einer Schleife o.ä. die Werte von Port0 bis Port7 abfragen.

der ist nur anders "verpackt"

} initPLC {iPort, oPort, iPort, oPort, iPort, oPort, iPort, oPort, 0};

Ja, mittlerweile steige ich auch durch den Code durch, aber der User soll diese Defaultwerte bei Aufruf der Funktion ja ändern können.

Dann habe ich das Missverstanden.

Ich wünsche einen netten Fußballabend.

1 Like

Warum, wozu?
Warum so?
In Verbindung mit "geht nicht", werden die Fragen um so wichtiger.

Aus meiner Sicht bist du irgendwo falsch abgebogen, und steckst jetzt in einer Sackgasse.
Mein Rat: 3 Schritte zurückgehen, und beschreiben, was du "wirklich" erreichen willst.

Ich frage mich die ganze Zeit was mit "Der User soll die Daten ändern können" gemeint ist.
Zum Ändern der Daten wird eh ein Interface gebraucht. Und da ist es doch erst mal egal, in welcher Form die Parameter an eine Funktion übergeben werden. Also warum wird ein Array da von vornherein ausgeschlossen? Könnte ja auch ein Objekt sein...

Kommt doch dann aufs Interface an....

Nundenn... nochmal ganz langsam:
Das Ganze soll eine Initialisierungsroutine für vier Portexpander werden. Diese besitzen jeweils zwei Ports a 8 Bit, welche in der Software als Input oder Output definiert werden können.
Die Ansteuerung der Expander selbst übernimmt eine Library. Auf der Platine selbst können diese acht Ports durch Tausch von ICs entweder mittels Spannungsteiler als Eingang, oder via Leistungstreiber als Ausgang fungieren. Standardmäßig sind Port 0 bis 3 als Eingang bestückt, die Ports 4 bis 7 als Ausgang - wie gesagt, dies kann mittels einer Änderung an der Hardware geändert werden.... soweit die "Rahmenbedingungen".
Ich möchte nun eine Routine schreiben, in der der User die gewünschte - und auch (hoffentlich) hardwareseitig richtig konfiguierte - Funktion der Ports 0 bis 7 angeben kann, damit mittels der Expander-Library die Ports entsprechend gesetzt werden können. Auf die Defaultwerte kam ich damit die "Standardkonfiguration" bereits hinterlegt ist und die Routine auch ohne Parameter aufgerufen werden kann.
Und nein, das Ganze ist bis dato nicht in einer Klasse gekapselt. Der Krempel im ersten Post funktioniert ja, aber schön ist nunmal anders.
Ich bin für weitere Denkanstöße und Ansätze offen, weiß aber wirklich nicht wie ich es noch ausführlicher beschreiben soll.

Kann ich nicht bestätigen!
Wirft zu viele Fehler bei mir.

Ich frage dich auch nochmal:

Warum?
Was steckt hinter dem "weil" verborgen?
Der Logik kann ich auch nicht folgen!

Statt acht Byte reicht doch eins, von dem die Bits ausgewertet werden. Das geht wunderbar in einer Schleife.

0= Input, 1= Output

Ein Parameter statt acht.

Natürlich würde ein Byte reichen, aber ich möchte nicht zwingend etwas kryptisches wie "0b11110000" übergeben, sondern Bezeichner (Input = 0, Output = 1) aus einem Enum.

Könnte daran liegen dass dies oben nur ein Rumpf ist und glaube mir, das Ganze funktioniert ausprogrammiert. Und jetzt komm' mir bitte nicht mit "Aber warum ist es nicht ausprogrammiert? Fragen über Fragen?!". Solltest du dich da jetzt angesprochen fühlen kannst du das Thema für dich gerne als abgeschlossen betrachten, auf solche Diskussionen von der Seite kann ich gerne verzichten. Nichts für ungut...

overloading

klingt für mich du möchtest ein Funktion mit 8 Parameter oder 0 Parameter
Das kannst du mit den default Werten machen (wie im Eingangspost angedeutet) oder
du machst eine Funktion mit 8 Parameter (ohne Default Werte) und eine zweite (kann ja auch gleichlautend sein) mit 0 Parameter die dann erstere mit ihren 8 Parametern aufruft (overloading)

https://www.google.com/search?q=c%2B%2B+function+overloading&bshm=rimc/1

P.S.: als User würde ich das durchaus gerne auch als bitamp übergeben können...

OOP
alternativ, wenn du z.B. dem User gezieht die Möglichkeit geben willst nur z.B. Port 3 und 5 zu setzen, würde ich doch den OOP weg gehen und setter schreiben für

void setPort(byte port, byte type); 

dann kann der User geziehlt setzen:

plc.setPort(3, OUTPUT); 
plc.setPort(5, OUTPUT);

und die anderen Ports bleiben auf INPUT.

wenn beides nicht ist was du dir vorstellst - führe weiter aus, was bei den beiden Vorschlägen anders sein soll.

Warum:

ich:

constexpr bool iPort {LOW};
constexpr bool oPort {!iPort};

das enum PORTS macht - nmM - nur in Verbindung mit class Sinn.

Und is das ein Fehler, der gegen das C++ Mantra verstößt?

Hallo,

du (TO) möchtest das der User konfiguriert welcher Pin Eingang bzw. Ausgang ist? Konstrukte wie iPORT oder oPORT sind genauso fehleranfällig, unterscheidet sich nur in einem Buchstaben und wird irgendwo im Sketch angegeben. Wenn es nur 8 Bit sind schreibe eine Klasse und gib das bei der Initialisierung an.
Bspw.

/*           I2C Addr
             |     wire0/wire1
             |     |      I/O Config (0 Input, 1 Output)             
             |     |      |                           */
myPCA9538 <0x73, wire0, 0b11100111> u7;

Der Rest läuft intern in der Klasse ab. Mit dem einen Byte für die I/O Konfig kann man intern um Längen besser arbeiten als wenn man hunderte Parameter auswerten muss. Man kann mit Bitmasken zum filtern arbeiten usw. Meine Meinung. Ich kann damit einfach unterbinden das Pin Bit.4 versehentlich geschalten wird.

Wenn es allerdings zur Laufzeit änderbar sein soll, dann nimmt man eine Methode und kann den Pin mittels INPUT/OUTPUT konfigurieren. Mit letzterem kann man die Arduino defines verwenden.
Dann könnte man nach der Objekt Initialisierung mit 8 Methodenaufrufen die Pins konfigurieren.

u7.pinMode(3, OUTPUT);
u7.pinMode(7, INPUT);

oder so ähnlich. So als Ideen ...