PPM Signal Generator

Guten Abend/Morgen,

mal wieder eine toller Fund in der alten Modellbaukiste fuer ein Arduinoprojekt:
Und zwar habe ich einen PPM zu USB(Flugsimulator) Adapter gefunden, welchen
ich vor einigen Jahren mit meiner alter 35MHz Multiplex Steuerung verwendet hatte.
Da ich dieses Equipment nicht mehr habe, aber dafür einen schicken Arduino mit ein paar
Joysticks, kam mir schnell der Gedanke “bau’s dir selbst ne Steuerung”. :slight_smile:

Und hier der Sketch:

/* 06.2018 PPM Signalgenerator by grillgemuese
   PPM Pulsdauer  1000-2000us
   PPM Zyklus     20000us
   PPM Kanaele    2-8
   fuer digitalRead() -> 0=LOW 1023=HIGH
*/
//Makros fuer PPM Pin 4 (Arduino UNO)
#define PD4_PPM B00010000           //oder (1 << PD4)
#define PD4_ON PORTD |= PD4_PPM     //aktiviert   Pin 4
#define PD4_OFF PORTD &= ~PD4_PPM   //deaktiviert Pin 4

#define ppm_ch 4                    //Kanalanzahl (2-8)
#if ppm_ch > 8
#error ppm_ch zu gross! (maximal 8)
#elif ppm_ch < 2
#error ppm_ch zu klein! (minimal 2)
#endif
//globale Variablen
//const uint8_t ai_pin[ppm_ch] = {A2, A3, A4, A5};
const uint32_t ai_read_takt = 5; //ms

//Prototypen
void ppm_ausgabe(const uint32_t *ch_raw);

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); //nerft auf dem Tisch
  DDRD |= PD4_PPM; //Pin 4 als Ausgang
}//void setup() ENDE

void loop()
{
  static uint16_t testwerte[ppm_ch]; //dient dem Test!
  static uint32_t ai_read_ms;
  static uint32_t ai_raw[ppm_ch];
  uint32_t akt_ms = millis(); //pro loop aktuelle ms
  //Werte einlesen
  if (akt_ms - ai_read_ms >= ai_read_takt)
  {
    ai_read_ms = akt_ms;
    for (uint8_t ch = 0; ch < ppm_ch; ch++)
    {
      if (testwerte[ch] < 1023) testwerte[ch] += 1;
      else testwerte[ch] = 0;
      ai_raw[ch] = testwerte[ch]; //dient dem Test!
      //ai_raw[ch] = analogRead(ai_pin[ch]);
    }
  }
  ppm_ausgabe(ai_raw); //jeden loop-Zyklus aufrufen!
}//void loop() ENDE

void ppm_ausgabe(const uint32_t *ch_raw)
{
  enum {START, STOP, KANAL};
  const uint32_t ppm_total = 20000; //20ms
  const uint32_t ppm_puls_min = 700;
  const uint32_t ppm_puls_max = 1700;
  const uint32_t ppm_stop = 300;

  static uint8_t akt_mode = STOP;
  static uint8_t akt_ch = ppm_ch;
  static uint32_t ppm_last_us, ppm_start_us, ppm_ch_us;
  static uint32_t ppm_pause = 10000; //wird dynamisch an ppm_total angepasst

  uint32_t akt_us = micros();
  switch (akt_mode)
  {
    case START:
      if (akt_us - ppm_start_us >= ppm_pause)
      {
        ppm_last_us = akt_us;
        akt_mode = STOP;
        PD4_OFF;
      }
      break;
    case STOP:
      if (akt_us - ppm_last_us >= ppm_stop)
      {
        ppm_last_us = akt_us;
        if (akt_ch < ppm_ch)
        {
          ppm_ch_us = map(ch_raw[akt_ch], 0, 1023, ppm_puls_min, ppm_puls_max);
          akt_mode = KANAL;
          akt_ch++;
          PD4_ON;
        }
        else
        {
          ppm_start_us = akt_us;
          ppm_pause = ppm_total - (akt_us - ppm_start_us);
          akt_mode = START;
          akt_ch = 0;
          PD4_ON;
        }
      }
      break;
    case KANAL:
      if (akt_us - ppm_last_us >= ppm_ch_us)
      {
        ppm_last_us = akt_us;
        akt_mode = STOP;
        PD4_OFF;
      }
      break;
  }
}//void ppm_ausgabe() ENDE

bitte bei Anregungen/Fehlerfung melden
Ich danke Euch
grillgemuese :slight_smile:

Guten Abend zusammen,

entspricht nicht mehr dem vorherigen Sketch, da hier die Empfaenger Seite abgearbeitet wird. Heißt hier wird jeder Kanal einzeln angesteuert.

Eine Frage haette ich an Euch und zwar erhalte ich diese Fehlermeldung/Warnung:
Servo_class\Servo_class.ino:16:5: warning: extra qualification 'Servo_class::' on member 'Servo_class' [-fpermissive]

Servo_class::Servo_class(cui8 do_pin);

^
Was mache ich falsch?
Ich dachte man kann einfach ein Objekt erstellen, welches Parameter mit nimmt und diese in der zur Klasse gleichnamigen Funktion/Methode uebergibt.. :?

typedef const uint8_t cui8;
typedef const uint16_t cui16;
typedef const uint32_t cui32;

class Servo_class
{
  private:
    bool mode;
    uint8_t ppm_pin;
    uint32_t ppm_total;    //20000us    (50Hz)
    uint32_t ppm_puls_min; //1000us (min. 750)
    uint32_t ppm_puls_max; //2000us (max.2500)
    uint32_t ppm_start_us;
    uint32_t ppm_ch_us;
  public:
    Servo_class::Servo_class(cui8 do_pin);
    void begin(cui32 puls_min, cui32 puls_max, cui32 total);
    void run(cui16 val); //0-1023
    int8_t get_pos() 
    {
      return (int8_t)map(ppm_ch_us, ppm_puls_min, ppm_puls_max, -100, 100);
    }
};
//Konstruktoren
Servo_class::Servo_class(cui8 do_pin) //richtig?
{
  pinMode(do_pin, OUTPUT);
  ppm_pin = do_pin;
}
void Servo_class::begin(cui32 puls_min, cui32 puls_max, cui32 total)
{
  ppm_puls_min = puls_min;
  ppm_puls_max = puls_max;
  ppm_total = total;
}
void Servo_class::run(cui16 val)
{
  if (!mode && micros() >= ppm_start_us + ppm_total)
  {
    ppm_start_us = micros();
    ppm_ch_us = map(val, 0, 1023, ppm_puls_min, ppm_puls_max);
    mode = true;
    digitalWrite(ppm_pin, HIGH);
  }
  else if (mode && micros() - ppm_start_us >= ppm_ch_us)
  {
    mode = false;
    digitalWrite(ppm_pin, LOW);
  }
}
cui8 poti_pin = A0;
cui8 servo_pin = 4;
//Servo_class object's
Servo_class Servo_1(servo_pin);

void setup()
{
  Serial.begin(19200);
  //ppm_puls_min, ppm_puls_max, ppm_total
  Servo_1.begin(900, 2100, 20000);
}

void loop()
{
  static int8_t old_pos;
  static uint16_t ai_read;
  static uint32_t last_ai_read;
  if (micros() - last_ai_read >= 10000) //alle 10ms/10000us
  {
    last_ai_read = micros();
    ai_read = analogRead(poti_pin); //analogRead() benoetigt ca. 104us
  }
  int8_t pos = Servo_1.get_pos();
  if (pos != old_pos)
  {
    Serial.println(pos);
    old_pos = pos;
  }
  Servo_1.run(ai_read); //jeden loop-Zyklus aufrufen!
}

Ich danke Euch
grillgemuese :slight_smile:

Hallo,

hier gehört kein Konstruktor hin, sondern Deklarationen / Bekanntmachungen

public:
    Servo_class(cui8 do_pin);

Nur was der Sinn deiner seltsamen typedef werden soll verstehe ich nicht ganz. Konstanten erkennt man im Code an Großbuchtaben des Variablennames. Muss man sich nur angewöhnen das durchzuziehen. Der Datentyp einer Variablen steht nur einmal irgendwo im Code. Der Variablenname ggf. öfters. Woran erkennst du dann ohne Datentyp was das ist ...

hier gehört kein Konstruktor hin

Sehe ich nicht so, das ist genau die richtige Stelle für die Deklaration eines Konstruktors.

Was ich allerdings anders machen würde, wäre die const Geschichte...

class Servo_class {

private:
   const uint8_t ppm_pin;  // Wird nur im c'tor gesetzt.
   ...
public:
   Servo_class(uint8_t pin) : ppm_pin(pin) {
       pinMode(ppm_pin, OUTPUT);     // dieser Code wäre sauberer in die Methode begin();
   }
   void begin();
   ... 
};

Ob man die Konstante nun PPM_PIN oder ppm_pin nennt wäre mir egal, Hauptsache der Compiler hilft einem, dass sie nicht mehr nachträglich verändert werden kann.

Hallo,

das wundert mich jetzt, denn mit der Korrektur kompiliert es bei mir. Es muss doch in dem Fall nur die Funktion öffentlich verfügbar gemacht bzw. erlaubt werden. Ansonsten wäre der Konstruktor doppelt im Sketch.
Die Übersicht wäre besser wenn man gleich eine Lib baut ohne alles in den Sketch zu kloppen.

PPM_PIN oder ppm_pin

Zeigt mir leider das du zu oberflächlich drüber geschaut hast, mein Ansinnen liegt viel tiefer. Ich muss das leider so schreiben.

Oh, Missverständnis: natürlich wird innerhalb der Klasse der c’tor so deklariert wie du es geschrieben hast, Doc. Das behebt die Fehlermeldung.

Ausserhalb der  class Servo_class {...};
kann dann die Definition wie geschehen erfolgen:

Servo_class::Servo_class(uint8_t pin) {...}; // hier voller Name (mit Klassenname)
Ob der Parameter als const oder variabel deklariert wird, macht keinen Unterschied. Interessanter ist eher, dass die member-Variable als const nur in der Initialisierungsliste gesetzt werden kann und danach fest ist.

Hallo,

dann bin ich beruhigt, hatte schon die Befürchtung mein angeeignetes Wissen über Bord werfen zu müssen. :slight_smile:

Alles Peace.

Mahlzeit zusammen,

ich bedanke mich fuer Eure Unterstuetzung.

typedef... habe ich nur verwendet, da mir const uint32_t/... zu lang ist.

typedef const uint8_t cui8;
typedef const uint16_t cui16;
typedef const uint32_t cui32;
//... (in Servo_class.h ?)
class Servo_class
{
  private:
    cui8 ppm_pin;
    bool mode;
    uint32_t ppm_total;    //20000us    (50Hz)
    uint32_t ppm_puls_min; //1000us (min. 750)
    uint32_t ppm_puls_max; //2000us (max.2500)
    uint32_t ppm_start_us;
    uint32_t ppm_ch_us;
  public:
    Servo_class(cui8 do_pin) : ppm_pin(do_pin) {}
    void begin(cui32 puls_min, cui32 puls_max, cui32 total); //Variante 1
    void begin(); //Variante 2 (Standard-Parameter)
    void run(cui16 val); //0-1023
    int8_t get_pos()
    {
      return (int8_t)map(ppm_ch_us, ppm_puls_min, ppm_puls_max, -100, 100);
    }
};
//Konstruktoren (in Servo_class.cpp ?)
void Servo_class::begin(cui32 puls_min, cui32 puls_max, cui32 total) //Variante 1
{
  pinMode(ppm_pin, OUTPUT);
  ppm_puls_min = puls_min;
  ppm_puls_max = puls_max;
  ppm_total = total;
}
void Servo_class::begin() //Variante 2
{
  pinMode(ppm_pin, OUTPUT);
  ppm_puls_min = 1000UL;
  ppm_puls_max = 2000UL;
  ppm_total = 20000UL;
}
void Servo_class::run(cui16 val)
{
  if (!mode && micros() >= ppm_start_us + ppm_total)
  {
    ppm_start_us = micros();
    ppm_ch_us = map(val, 0, 1023, ppm_puls_min, ppm_puls_max);
    mode = true;
    digitalWrite(ppm_pin, HIGH);
  }
  else if (mode && micros() - ppm_start_us >= ppm_ch_us)
  {
    mode = false;
    digitalWrite(ppm_pin, LOW);
  }
}

cui8 poti_pin = A0;
cui8 servo_pin = 4;

Servo_class Servo_1(servo_pin);

void setup()
{
  Servo_1.begin(900UL, 2100UL, 20000UL); //Variante 1
  //Servo_1.begin(); //Variante 2
}

void loop()
{
  static uint16_t ai_read;
  static uint32_t ai_read_us;
  if (micros() - ai_read_us >= 10000UL) //alle 10ms/10000us
  {
    ai_read_us = micros();
    ai_read = analogRead(poti_pin); //analogRead() benoetigt ca. 104us
  }
  //int8_t pos = Servo_1.get_pos();
  Servo_1.run(ai_read); //jeden loop-Zyklus aufrufen!
}

Gruß
grillgemuese :slight_smile: