Neues RC Projekt - 16 Kanal Schalter für Lichtsteuerung

Hallo und guten Morgen zusammen,

wie in meinem anderen Projekt bereits angekündigt möchte ich hier ein neues Projekt starten. Ziel ist es, die Signale die über 2 Kanäle von einer Flysky Funke kommen, aufzuarbeiten und damit bis zu 16 Schaltkanäle unabhängig zu steuern. Verwendet wird ein Arduino Nano. Dies wird mein erstes Projekt geben das mit Strukturen aufgebaut werden soll. Bisher absolutes Neuland für mich, aber nur so lernt man dazu. Ich würde mich sehr freuen, auch hier eine so tatkräftige Unterstützung wie bei dem vorherigen Projekt zu bekommen. Bisher habe ich so angefangen:

// 16 Kanal Schalter für Lichtsteuerung
// Fernsteuerung: Flysky i6X
// CPU: Arduino Nano


struct Daten {
  const byte pin;
  const byte mem;
  bool stat;
};

  
Daten Kanal [16] ={
//                      Pin  mem   stat
/*  Daten Kanal01 = */{ 4,   1,   false, },
/*  Daten Kanal02 = */{ 5,   0,   false, },
/*  Daten Kanal03 = */{ 6,   1,   false, },
/*  Daten Kanal04 = */{ 7,   1,   false, },
/*  Daten Kanal05 = */{ 8,   0,   false, },
/*  Daten Kanal06 = */{ 9,   0,   false, },
/*  Daten Kanal07 = */{10,   0,   false, },
/*  Daten Kanal08 = */{11,   0,   false, },
/*  Daten Kanal09 = */{12,   0,   false, },
/*  Daten Kanal10 = */{13,   0,   false, },
/*  Daten Kanal11 = */{A0,   0,   false, },
/*  Daten Kanal12 = */{A1    0,   false, },
/*  Daten Kanal13 = */{A2,   0,   false, },
/*  Daten Kanal14 = */{A3,   0,   false, },
/*  Daten Kanal15 = */{A4,   0,   false, },
/*  Daten Kanal16 = */{A5,   0,   false, },
};

int RCIN1 = 2;
int RCIN2 = 3;

void setup() {
  Serial.begin(9600);

  pinMode (RCIN1, INPUT);
  pinMode (RCIN2, INPUT);

  for (int OutPin = 4; OutPin < 19; OutPin++) 
  {
    pinMode(OutPin, OUTPUT);
  }
}

void loop() {

for (auto &a : Kanal) {
    Serial.println("PIN");
    Serial.println(a.pin);
    Serial.println("mem");
    Serial.println(a.mem);
   
    digitalWrite (10, HIGH);   
}
}

Die Sachen in der loop dienen im Moment nur dazu, zu sehen ob das soweit funktioniert. Und das erste Problem ist auch schon da :wink: Der Code funktioniert nur bis Kanal 10. Sobald ein pin mit A0 usw deklariert ist nicht mehr. Kann man die Pins A0 bis A5 irgendwie umbenennen in Pin 14 bis 19?

Gruß Thomas

Warum nimmst du nicht die Daten aus deinem struct-Array ?

Warum wird digitalWrite (10, HIGH); für jeden Kanal gemacht?

Ich dachte eigentlich, das ich genau das mit der for Schleife tue?

das digitalwrite war nur um zu sehen ob er die Outputs auch setzt.

Wo?

Welche?

die outputs für alle im Daten Kanal angegebenen Pins.

OK. Also in der for Schleife greift er nicht auf das struct zu. Verstanden. Ich habe noch nie mit struct usw gearbeitet, deswegen tue ich mir da gerade schwer und versuche aus Beispielen mir was zusammen zu reimen.

vielleicht so (waren ein paar kommas usw. noch nicht passend :

#define ANZ_KANAELE 16
Daten Kanal [ANZ_KANAELE] ={
//                      Pin  mem   stat
/*  Daten Kanal01 = */{ 4,   1,   false },
/*  Daten Kanal02 = */{ 5,   0,   false },
/*  Daten Kanal03 = */{ 6,   1,   false },
/*  Daten Kanal04 = */{ 7,   1,   false },
/*  Daten Kanal05 = */{ 8,   0,   false },
/*  Daten Kanal06 = */{ 9,   0,   false },
/*  Daten Kanal07 = */{10,   0,   false },
/*  Daten Kanal08 = */{11,   0,   false },
/*  Daten Kanal09 = */{12,   0,   false },
/*  Daten Kanal10 = */{13,   0,   false },
/*  Daten Kanal11 = */{A0,   0,   false },
/*  Daten Kanal12 = */{A1,   0,   false },
/*  Daten Kanal13 = */{A2,   0,   false },
/*  Daten Kanal14 = */{A3,   0,   false },
/*  Daten Kanal15 = */{A4,   0,   false },
/*  Daten Kanal16 = */{A5,   0,   false }
};

int RCIN1 = 2;
int RCIN2 = 3;

void setup() {
  Serial.begin(9600);

  pinMode (RCIN1, INPUT);
  pinMode (RCIN2, INPUT);

  for (int i = 0; i < ANZ_KANAELE; i++) 
  {
    pinMode(Kanal[i].pin, OUTPUT);
  }
}

Ich finde das lesbarer und kürzer.

for (auto &kanal : Kanal)  {
   pinMode(kanal.pin, OUTPUT);
}

gewöhn' dir an:

Die Struktur beginnt mit einem großen Buchstaben: Daten
die konkrete Instanz (deine Variable) beginnt mit kleinem Buchstaben: kanal.

Nach dieser Änderung musst du den Tipp von @Whandall leicht anpassen. denn kanal heißt schon kanal.

z.B.:

for (auto &i: Kanal)  {
   pinMode(i.pin, OUTPUT);
}

kannst natürlich auch a nennen. Aber ich fang in for Schleifen halt mit i an.

Also funktionieren tut beides.

for (int i = 0; i < ANZ_KANAELE; i++) 
  {
    pinMode(Kanal[i].pin, OUTPUT);
  }

diese Schreibweise kann ich gedanklich nachvollziehen.

for (auto &kanal : Kanal)  {
   pinMode(kanal.pin, OUTPUT);
}

da verstehe ich das mit dem Auto nicht. Könntest du mir das kurz erklären? Ich hab nichts darüber in der Referenz gefunden.

Entschuldigt bitte wenn ich nicht immer sofort antworte, ich versuche dann meistens nachzulesen.

Alternativ kannst du auch deine struct mit Funktionalität füllen,
nicht nur mit Dataen.

struct Daten {
  const byte pin;
  const byte mem;
  bool stat;
  void begin() {
    pinMode(pin, OUTPUT);
  }
  void display() {
    Serial.print(F("PIN "));
    Serial.print(pin);
    Serial.print(F(", mem "));
    Serial.print(mem);
    Serial.print(F(", stat "));
    Serial.println(stat);
  }
};

Dann kannst du in setup

for (auto &a : Kanal) {
  a.begin();
}

und in loop

for (auto &a : Kanal) {
  a.display();
}

benutzen.

so ist es. selbst wenn es kürzer und eleganter geht reicht es eigentlich wenn du einen Weg findest der funktioniert und den du dir gut merken kannst.
Die erwähnten Tipps bezüglich der Benamung von Strukturen sind natürlich auch alle richtig.
Habe in nem Buch mal die Reihenfolge gelesen:

  1. make it work
  2. make it right
  3. make it fast

Also ich würde mit dem schön machen anfangen nachdem es funktioniert was ja meistens gerade am anfang schon eine nicht gerade kleine hürde ist und schritt 1 und 2 auf einmal auch gerade am anfang überfordernd sein kann meiner Meinung nach.

Auto erspart dir den korrekten Typ anzugeben und überlässt das dem Compiler.

Also statt

for (Daten& kanal : Kanal)  {

kannst du

for (auto& kanal : Kanal)  {

schreiben.

Also wenn ich das jetzt richtig verstanden habe, dann beziehen sich die Funktionalitäten die ich direkt ins struct schreibe, auch nur auf dieses struct. Hätte ich mehrere, dann könnte ich diese dort deklarieren und im setup bzw in der loop einfach ansprechen.

Ja gerade am Anfang und mit neuer Materie ist es gar nicht so einfach das alles im Kopf zu sortieren. Das mag für fortgeschrittene alles easy sein, aber für Anfänger teilweise erschlagend. Noch dazu wenn man das wirklich verstehen will.

Ich muss leider kurz meine Kids wegbringen, dann gehts weiter.

Vielen Dank erstmal :+1:

struct und class haben nur Unterschiede in der Sichtbarkeit von Elementen,
und nur, wenn man nicht explizit public:, protected: oder private: benutzt.

In einer struct ist der Grundzustand public:, in einer class private:.

Ach wat, einfach mal ein bißchen mit rumspielen, bis es "KLICK" gemacht hat.

UDT - User defined datatypes - sind ein mächtiges Werkzeug, das einen viel Tipparbeit abnimmt und somit auch das debugging extremst erleichtert.

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

1 Like

So da bin ich wieder :wink:

Mein tatsächlich größtes Problem bei solchen Projekten ist es zu verstehen wie ich es am besten strukturiere. Das ist übrigens der Link zu dem besagten vorherigen Prokjekt:

In diesem Projekt habe ich die PWM Werte vom Empfänger abgefragt und mit case ein Unterprogramm aufgerufen das den entsprechenden Kanal schaltet. Dieses Unterprogramm gab es entsprechend oft den Kanälen. Von der gewünschten Funktion hat sich nichts geändert. Die Variable mem steht für memory. Wird ein Taster an der Funke gedrückt, weiß er über den Wertebereich im case welcher das ist. Bei mem=0 , wird der entsprechende Pin High und nach einer Wartezeit X wieder low. Bei mem=1 schaltet er den Pin High, wartet eine Zeit X und setzt stat auf true (quasi ist eingeschaltet). Bei nochmaligen betätigen wird der Pin Low und stat wird wieder false.

Jetzt soll das ganze ja quasi "global" funktionieren, ohne 16 Unterprogramme. In dem unten stehenden Code ist noch die "alte" Abfrage über case und das entsprechende Unterprogramm drin, damit man sieht was ich meine.

Meine erste Überlegung war in den cases eine weitere Variable auf true zu setzen und diese dann in einer for Schleife abzufragen.

Hier mal der aktuelle Code:

// 16 Kanal Schalter für Lichtsteuerung
// Fernsteuerung: Flysky i6X
// CPU: Arduino Nano

struct Daten {
  const byte channel;
  const byte pin;
  const byte mem;
  bool stat;
};


#define ANZ_KANAELE 16
Daten kanal [ANZ_KANAELE] = {
  //                channel, Pin,  mem,   stat
  /*  Daten Kanal01 = */{1,   4,   1,   false },
  /*  Daten Kanal02 = */{2,   5,   0,   false },
  /*  Daten Kanal03 = */{3,   6,   1,   false },
  /*  Daten Kanal04 = */{4,   7,   1,   false },
  /*  Daten Kanal05 = */{5,   8,   0,   false },
  /*  Daten Kanal06 = */{6,   9,   0,   false },
  /*  Daten Kanal07 = */{7,  10,   0,   false },
  /*  Daten Kanal08 = */{8,  11,   0,   false },
  /*  Daten Kanal09 = */{9,  12,   0,   false },
  /*  Daten Kanal10 = */{10, 13,   0,   false },
  /*  Daten Kanal11 = */{11, A0,   0,   false },
  /*  Daten Kanal12 = */{12, A1,   0,   false },
  /*  Daten Kanal13 = */{13, A2,   0,   false },
  /*  Daten Kanal14 = */{14, A3,   0,   false },
  /*  Daten Kanal15 = */{15, A4,   0,   false },
  /*  Daten Kanal16 = */{16, A5,   0,   false }
};

constexpr byte PWMPin1 {2};
constexpr byte PWMPin2 {3};

uint16_t PWMEingang1;
uint16_t PWMEingang2;

constexpr uint32_t Wartezeit1 {400};
constexpr uint32_t Wartezeit2 {400};
constexpr uint32_t Wartezeit3 {400};

void setup() {
  Serial.begin(9600);

  pinMode (PWMPin1, INPUT);
  pinMode (PWMPin2, INPUT);

  for (auto &kanal : kanal)  {
    pinMode(kanal.pin, OUTPUT);
  }
}

void loop() {

  readRCinput();
  checkRCSwitch1();









//************ nur zum visualisieren ***************
  for (auto &kanal : kanal) {
    Serial.print(F("Kanal "));
    Serial.print(kanal.channel);
    Serial.print(F(", PIN "));
    Serial.print(kanal.pin);
    Serial.print(F(", mem "));
    Serial.print(kanal.mem);
    Serial.print(F(", stat "));
    Serial.println(kanal.stat);
  }
    Serial.println("PWM CH1: ");
    Serial.println(PWMEingang1);
    Serial.println("PWM CH2: ");
    Serial.println(PWMEingang2);
//**************************************************
}
void Channel01()
{
  stat1 = digitalRead(Kanal01);

  if (Memory[0] == 0)
  {
    if (stat1 == LOW) 
    {
    digitalWrite (Kanal01, HIGH);
    WartezeitStartTime1 = millis();
    StatusWarte1 = Warteein1;
    }
  }
  
  else
  {
    if ((stat1 == LOW) && (busy == 0))
    {
      busy = 1;
      digitalWrite (Kanal01, HIGH);
      WartezeitStartTime2 = millis();
      StatusWarte2 = Warteein2;
    }

    if ((stat1 == HIGH) && (busy == 0))
    {
      busy = 1;
      digitalWrite (Kanal01, LOW);
      WartezeitStartTime3 = millis();
      StatusWarte3 = Warteein3;
    }
  }
}


void checkRCSwitch1()
{
  switch (PWMEingang1)
  {
    case 950 ... 1050:
    Channel01();
      break;
    case 1950 ... 2050:
    
      break;
    case 1100 ... 1200:
    
      break;
    case 1800 ... 1900:
    
      break;
    case 1240 ... 1340:
    
      break;
    case 1350 ... 1450:
    
      break;
    case 1530 ... 1630:
      
      break;
    case 1660 ... 1760:
    
      break;

    default:

      uint32_t myTime4 = millis();
      if ((myTime4 > (WartezeitStartTime1 + Wartezeit1)) && (StatusWarte1 == Warteein1))
      {
        digitalWrite (Kanal01, LOW);
        StatusWarte1 = Warteaus1;
      }

      uint32_t myTime5 = millis();
      if ((myTime5 > (WartezeitStartTime2 + Wartezeit2)) && (StatusWarte2 == Warteein2))
      {
        StatusWarte2 = Warteaus2;
        busy = 0;
      }

      uint32_t myTime6 = millis();
      if ((myTime6 > (WartezeitStartTime3 + Wartezeit3)) && (StatusWarte3 == Warteein3))
      {
        StatusWarte3 = Warteaus3;
        busy = 0;
      }
      break;
  }
}

void readRCinput()
{
  int PWM1IN = pulseIn(PWMPin1, HIGH, 20000); //Read PWM Pulse
  if (PWM1IN > 900 && PWM1IN < 2100)
  {
    PWMEingang1 = PWM1IN;
  }
  int PWM2IN = pulseIn(PWMPin2, HIGH, 20000); //Read PWM Pulse
  if (PWM2IN > 900 && PWM2IN < 2100)
  {
    PWMEingang2 = PWM2IN;
  }
}

Gruß Thomas

Bekommst du für Leerzeilen Geld?

Strg-T (automatische Formatierung) würde die Lesbarkeit erhöhen.

Warum eine Baudrate von 9600?

Variablen die mit millis arbeiten, sollten (von Ausnahmefällen abgesehen) nur subtrahiert
und nicht addiert werden. Der obige Kode ist falsch.

Ich würde die RC-Eingänge als Objekte auslegen und so den Kode nur einmal debuggen müssen.

Die Auswertung des Signals lässt sich sicher berechnen und in eine Kanalnummer umrechnen.

Zusätzlich wäre es mir wichtig, festzuhalten:

  • wann wurde das letzte gültige Signal empfangen
  • wie oft traten Probleme mit dem Empfang auf (zu groß oder zu klein)
  • wie oft traten Probleme mit der Kanalzuordnung auf

Ich würde die cases im switch sortieren, so ist das schwer zu lesen und zu verstehen.

ich tu mir mit Fließtext in einer Wurst schwer. Nicht viel besser:

  • Bei mem=0 , wird der entsprechende Pin High und nach einer Wartezeit X wieder low.
  • Bei mem=1 ..................schaltet er den Pin High, wartet eine ................Zeit X und
    setzt stat auf true (quasi ist eingeschaltet).
    Bei nochmaligen betätigen wird der Pin Low und stat wird wieder false.

ich würde mir da an deiner Stelle eine Finite State Machine aufzeichnen. Zeichnen geht einfach. Man erkennt klar ob der Ablauf rund ist und man erkennt alle Nebenbedingungen.

Bezüglich der Strukturierung der Daten: versuch es einfach: Du hast irgendwelche Ausgänge, Status, Memory-Einstellungen. Evtl. auch noch eine Funktionalität der Wartezeit.

Und dann hast die Funkauswertung die eben deinen Pins ansteuert. Und jetzt ist halt die Frage, welche Daten bekommst du aus deiner Funkauswertung (offenbar einen Wert aus der PWM Auswertung den du dann in 16 Bereiche gliederst) . Und wie steuerst du damit deine Pins ans.

Und es bleibt die Frage ... wie wird ein member mem von 0 auf 1 gesetzt? Welches Interface gibt es dafür oder geschieht das nur in der Initialisierung dass du diese Eigenschaft festlegst (geraten, weil mem ja const ist)?

[OT!]

Das STRG-T macht die Leerzeilen nicht automatisch weg.
Kann man aber einstellen. :wink:
In der formatter.conf

# Kill all lines within Braces and without Code 
delete-empty-lines

Da ich da ganz viel mit gespielt habe und der IDE das aktuelle AStyle beigebracht habe verweise ich mal auf:

Ich lasse Leerzeilen nicht automatisch entfernen,
weil ich die nur benutze, wenn ich meine es sähe damit besser aus.