RC-Steuerung - PPM-Signal auslesen

Wer nicht weiss, worum es geht, aber trotzdem interessiert ist, kann mal hier nachlesen:

http://wiki.rc-network.de/PPM
http://www.mftech.de/ppm.htm

Eines der wichtigsten Dinge an meinem "HELUINO" (fiel mir grad so ein :grin:) ist ja u.a. auch das Einlesen des PPM-Steuersignal von meiner Fernbedienung (ne alte Robbe FX-18), weil ich die sehr handlich finde und ich mechanische Dinge nicht neu basteln muss.
Einen interessanten Artikel mit Source dazu hatte ich neulich hier gefunden:

.... allerdings funktioniert das bei mir hinten und vorne nicht - also "eben mal schnell" was eigenes gebastelt.
PPM-Signal aus der "Lehrer-Buchse" am Sender zur Anpassung und Invertierung über einen Transistor an Pin 2 für INT0
Das sieht dann im Sketch elementar so aus:

#define NUM_OF_CHL 8          // we are working with an 8-ch-Transmitter
#define NUM_OF_AVG 3          // 3 added values for average

volatile int valuesInt[9] = {0};
volatile int valuesUse[9] = {0};
volatile byte counter = NUM_OF_CHL;
volatile byte average  = NUM_OF_AVG;
volatile boolean ready = false;
volatile long timelast;
long timelastloop;

void EvalPPM()
{
  long timenew = micros();
  long timediff = timenew - timelast;
  timelast = timenew;
  if (timediff > 2500)   // this must be the gap between set of pulses to synchronize
  { valuesInt[8] = valuesInt[8] + timediff;
    counter = 0; 
    if (average == NUM_OF_AVG)
    { for (int i = 0; i < NUM_OF_CHL + 1; i++)
      { valuesUse[i] = (valuesInt[i] + 0.5) / average;
        valuesInt[i] = 0;
      }
      average = 0;
      ready = true;
    }  
    average++;
  }
  else
  { if (counter < NUM_OF_CHL)
    { valuesInt[counter] = valuesInt[counter] + timediff;
      counter++;
    }
  }
}

void setup()
{ Serial.begin(115200);
  Serial.println(F("Start reading PPM-Signal from Remote-Control"));
  pinMode(2, INPUT_PULLUP); 
  attachInterrupt(0, EvalPPM, RISING);  
  timelast = micros();
  timelastloop = timelast;
}

void loop()
{ if (ready)
  { long timenew = micros();
    Serial.print(timenew - timelastloop); Serial.print(" | ");
    for (int i = 0; i < NUM_OF_CHL + 1; i++)
    { Serial.print(valuesUse[i]);
      if (i < 8) Serial.print(" - ");
    }
    Serial.println();
    ready = false;
    timelastloop = timenew;
  }
}

Im Monitor kommt dann folgendes dabei raus:

Start reading PPM-Signal from Remote-Control
16784 | 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 3742
67472 | 1401 - 1402 - 1521 - 1408 - 952 - 1522 - 1520 - 1517 - -10617
67424 | 1402 - 1401 - 1522 - 1408 - 952 - 1521 - 1520 - 1518 - -10617
67424 | 1401 - 1402 - 1522 - 1406 - 952 - 1522 - 1520 - 1517 - -10617
67428 | 1402 - 1401 - 1522 - 1408 - 952 - 1522 - 1518 - 1518 - -10617
67416 | 1401 - 1402 - 1522 - 1406 - 952 - 1522 - 1520 - 1517 - -10617
67428 | 1402 - 1401 - 1522 - 1408 - 952 - 1522 - 1518 - 1518 - -10617
67408 | 1401 - 1404 - 1520 - 1408 - 952 - 1524 - 1518 - 1517 - -10617
67428 | 1402 - 1401 - 1524 - 1406 - 952 - 1521 - 1520 - 1518 - -10617
67416 | 1401 - 1402 - 1522 - 1406 - 952 - 1524 - 1518 - 1517 - -10617
67428 | 1402 - 1401 - 1522 - 1408 - 952 - 1525 - 1516 - 1518 - -10617
67424 | 1401 - 1402 - 1525 - 1404 - 952 - 1524 - 1518 - 1517 - -10617
67424 | 1402 - 1401 - 1524 - 1406 - 952 - 1524 - 1517 - 1518 - -10617
67412 | 1401 - 1402 - 1521 - 1408 - 952 - 1524 - 1518 - 1517 - -10617
67424 | 1402 - 1401 - 1522 - 1408 - 952 - 1524 - 1517 - 1518 - -10617
67424 | 1401 - 1402 - 1521 - 1408 - 952 - 1524 - 1518 - 1517 - -10617
67424 | 1402 - 1401 - 1522 - 1408 - 952 - 1525 - 1516 - 1518 - -10617
67428 | 1401 - 1402 - 1522 - 1406 - 952 - 1524 - 1518 - 1517 - -10617
67416 | 1402 - 1401 - 1522 - 1408 - 952 - 1524 - 1517 - 1518 - -10617
67420 | 1401 - 1402 - 1522 - 1406 - 952 - 1524 - 1518 - 1517 - -10617
67416 | 1402 - 1401 - 1522 - 1409 - 950 - 1524 - 1517 - 1518 - -10617
67428 | 1401 - 1402 - 1522 - 1406 - 952 - 1524 - 1518 - 1517 - -10617
67424 | 1402 - 1401 - 1522 - 1408 - 952 - 1524 - 1517 - 1518 - -10617
67424 | 1401 - 1402 - 1522 - 1406 - 953 - 1525 - 1516 - 1517 - -10617
67428 | 1402 - 1401 - 1522 - 1408 - 952 - 1524 - 1517 - 1518 - -10617
67412 | 1401 - 1402 - 1521 - 1408 - 952 - 1524 - 1518 - 1517 - -10617
67420 | 1402 - 1401 - 1524 - 1408 - 950 - 1524 - 1517 - 1518 - -10617
67424 | 1401 - 1402 - 1521 - 1408 - 953 - 1522 - 1518 - 1517 - -10617
67424 | 1402 - 1401 - 1522 - 1408 - 952 - 1524 - 1517 - 1518 - -10617
67428 | 1401 - 1402 - 1522 - 1406 - 952 - 1524 - 1518 - 1517 - -10617
67416 | 1402 - 1402 - 1521 - 1408 - 953 - 1522 - 1517 - 1518 - -10617
67428 | 1401 - 1402 - 1522 - 1406 - 952 - 1524 - 1518 - 1517 - -10617
67408 | 1402 - 1401 - 1522 - 1408 - 952 - 1524 - 1517 - 1518 - -10617
usw.

Als erstes die Zeit zwischen den zwischen den "ready's", dann die Werte für die 8 Kanäle und zum Schluss die Dauer des Sync-Impulses.
Zusammengerechnet etwa 22ms je Sequenz und 3x gesampelt eben halt die 67ms für jeden Aufruf in der Loop.

So weit, so gut. Einfacher & funktioneller als beim code vom o.g. rcarduino.blogspot.
Nur wer mal genau hinsieht:
Ich habe beim messen nichts bedient - die Werte "zappeln" ein wenig trotz 3-fach-Sampling.
Wenn ich NUM_OF_AVG auf 5 setze, wird das zwar besser, aber die Wiederholzeit in der Loop ist dann mit ca. 110ms doch schon etwas lang.
Sprich: Die Reaktion Live am Modell könnte dann ein wenig zu träge sein ....

Meine (eigentlich sichere) Vermutung geht dahin, dass es an der min. Auflösung von micros() mit 4µs liegt.
"Dann lies doch die Timerregister" wird wahrscheinlich kommen (und so hatten die sich das bei rcarduino wohl auch gedacht).
... wie ? Dafür bin ich leider immer noch zu viel Dummy ....
Wer kann helfen oder hat noch ne andere Idee ?

Meine Eigenbau-Fernsteuerung wird gerade lackiert. Das Gehäuse, Elektronik und Code ist fertig. Fast 45kb sinds geworden....

Das PPM-Signal hatte ich so mit einem Leonardo ausgelesen:

#define channumber 9 //Kanalanzahl
int value[channumber];

void setup()
{
  Serial.begin(57600); 
  pinMode(3, INPUT); //PPM-Signal
}
void loop()
{
  while(pulseIn(3, HIGH) < 10000){} 
  for(int x=0; x<=channumber-1; x++) 
  {
    value[x]=pulseIn(3, HIGH);
  }
  for(int x=0; x<=channumber-1; x++)
  {
    Serial.print(value[x]); 
    Serial.print(" ");
    value[x]=0; 
  }
Serial.println(""); 
}

Grüße

Edit: Je nachdem was die Ursprungsfunke sendet, ob positives oder negatives PPM, musst du die beiden HIGH in LOW tauschen.

Vorweg:
So kann man es logo auch machen, aber ....
Meiner bescheidenen Meinung nach ebenso "ungenau", weil auch hier mit micros() gearbeitet wird und die minimale Auflösung weiterhin bei 4µs liegt.
Wenn man bedenkt, das der Bereich im PPM/PWM-Signal zwischen normalerweise +400/-400µs um 1,5ms herum liegt, sind das zwar nur 0,5% - aber immerhin.
Zum anderen finde es es nicht so geschickt, wenn man wie in deiner Art die Loop blockiert - auch dein Dino hat doch sicherlich noch anderes zu tun ?!

Zum Thema:
Wenn ich ne grundlegende Idee habe und ich weiss, andere können das auch, lässt mir das ja keine Ruhe ....
HEUREKA ! Hab ich doch heute Mittag endlich mal was gefunden, wo diese Geschichte mit Timern und dessen externen Interrupts ein bischen deutlicher für Dummies erklärt wird:
https://www.inkling.com/read/arduino-cookbook-michael-margolis-2nd/chapter-18/recipe-18-8
.... nun ist (auch) mir endlich zu diesem Thema mal ne Lampe angegangen.
Mein Auslesecode sieht nun so aus:

// Arduino CookBook 18.0
// https://www.inkling.com/read/arduino-cookbook-michael-margolis-2nd/chapter-18/recipe-18-8
// ICP1-Pin für UNO & Timer1: Pin 8
// ICP5-Pin für Mega & Timer5: Pin 48

#define NUM_OF_CHL 8         // we are working with an 8-ch-Transmitter
#define NUM_OF_AVG 3         // 3 added values for average

const int    inputCapturePin = 48;    // input pin fixed to internal Timer
const int    prescale = 8;            // prescale factor (each tick 0.5 us @16MHz)
const byte   prescaleBits = B010;     // pecision scale factor 8
const long   precision = (1000000/(F_CPU/1000)) * prescale; // time per counter tick in ns
volatile int overflows = 0;

volatile long valuesInt[9] = {0};
volatile long valuesUse[9] = {0};
volatile byte counter = NUM_OF_CHL;
volatile byte average  = NUM_OF_AVG;
volatile boolean ready = false;
long timelastloop;


/* Overflow interrupt vector */
ISR(TIMER5_OVF_vect)                 // here if no input pulse detected
{
  overflows++;                       // increment overflow count
}

/* ICR interrupt vector */
ISR(TIMER5_CAPT_vect)
{
  TCNT5 = 0;                            // reset the counter
  if( bitRead(TCCR5B ,ICES5) == true)   // wait for rising edge
  {                                     
    long time = ICR5 + (overflows * 65536);  // save the input capture value
    overflows = 0;
    time = time * precision / 1000;          // time in ms 
    if (time > 2500)   // this is a gap between set of pulses
    {
      valuesInt[8] = valuesInt[8] + time;
      counter = 0; 
      if (average == NUM_OF_AVG)
      { for (int i = 0; i < NUM_OF_CHL + 1; i++)
        {
          valuesUse[i] = (valuesInt[i] + 0.5) / average;
          valuesInt[i] = 0;
        }
        average = 0;
        ready = true;
      }  
      average++;
    }
    else
    { if (counter < NUM_OF_CHL)
      {
        valuesInt[counter] = valuesInt[counter] + time;
        counter++;
      }
    }
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Start reading PPM-Signal from Remote-Control"));
  Serial.print( precision);     // report duration of each tick in nanoseconds
  Serial.println(F(" nanoseconds per tick"));

  pinMode(inputCapturePin, INPUT_PULLUP); // ICP pin (digital pin 8 on Arduino/48 on Mega) as input
  TCCR5A = 0 ;               // Normal counting mode
  TCCR5B = prescaleBits ;    // set prescale bits
  TCCR5B |= _BV(ICES1);      // enable input capture
  TIMSK5 =  _BV(ICIE1);      // enable input capture interrupt for timer 1
  TIMSK5 |= _BV(TOIE1);      // enable overflow interrupt

  timelastloop = micros();   // measure loop-time with "standard" micros()
}

void loop()
{
  if (ready)
  {
    long timenew = micros();
    Serial.print(timenew - timelastloop); Serial.print(F(" | "));
    for (int i = 0; i < NUM_OF_CHL + 1; i++)
    {
      Serial.print(valuesUse[i]);
      if (i < 8) Serial.print(F(" - "));
    }
    Serial.println();
    ready = false;
    timelastloop = timenew;
  }
}

Das sind jetzt die ausgelesenen Timings:

Start reading PPM-Signal from Remote-Control
496 nanoseconds per tick
1356 | 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 11127
67464 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67412 | 1384 - 1388 - 1505 - 1402 - 940 - 1505 - 1500 - 1501 - 11120
67416 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67424 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67424 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67432 | 1384 - 1390 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11119
67416 | 1384 - 1388 - 1505 - 1402 - 940 - 1505 - 1501 - 1501 - 11120
67424 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67416 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67424 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67424 | 1384 - 1389 - 1505 - 1402 - 940 - 1505 - 1500 - 1501 - 11120
67424 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11118
67420 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67424 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67420 | 1384 - 1390 - 1505 - 1401 - 940 - 1505 - 1500 - 1501 - 11120
67424 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67412 | 1384 - 1388 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67432 | 1383 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67424 | 1384 - 1390 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67424 | 1384 - 1388 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67416 | 1384 - 1386 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67416 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
67424 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1500 - 1501 - 11120
67424 | 1384 - 1389 - 1505 - 1401 - 940 - 1505 - 1501 - 1501 - 11120
usw.

Hin und wieder "zappelt" mal ein Wert +/- 1, aber das schiebe ich einfach mal auf das eigentliche Signal selbst.
Man beachte mal die 1. Spalte und deren Wertschwankungen:
Diese Werte lese ich weiterhin per micros() - der Rest stammt aus der ISR vom Timer, bzw. entspricht dem Registerwert.

Alles läuft nun brav und artig über den Timer 5 und Pin 48 des Mega (geht auch mit Timer 1 und Pin 8 auf dem UNO) per Hardware.
Wenn man sich die Demo auf o.g. Seite mal ansieht und das einfach mal zum Test kopiert und so laufen lässt, fällt auf, das die Messung sogar weiterläuft, wenn die Loop in einem Delay hängt. Das Delay bestimmt sogar dabei die Messzeit. Erstaunlich ....

Wer's wie ich nicht kapiert hat, wie das hier vor sich geht, frage einfach mal an - dann erklär ich das mal mit meinen Worten.

Hey,
Ich versuche das selbe.
Nur ist das einzige Ergebniss ein

"Start reading PPM-Signal?"

Hat irgendjemand ein Idee woher der Fehler kommen kann?
Vielen Dank im Vorraus :slight_smile:

Moin,

macht euch das Leben nicht so schwer!

Das gibt's schon komplett fertig, und ihr müsst nur noch das aus dem Code rauslöschen was ihr nicht braucht:

Lieben Gruß,
Chris

Moin Chris, sag mal was hast mit diesem Sketch vor? Mir ist noch nicht klar wozu diese Lösung sein soll.
MfG
Lutz

Das ist eine bewährtes funktionierendes Sketch. Da muss man die Welt nicht neu erfinden.

Lieben Gruß,
Chris

Chris das habe ich voraus gesetzt.Mir geht es aber um die Anwendung.Den ich verwnde da einen etwas anderen Sketch.
MfG
Lutz

Hi!

Vielleicht wir es jemand brauchen:
ich habe eine kleine Bibliothek zum Auslesen von PPM Signal geschrieben. Es ist für alle Arduinos mit ATMega328 on board gedacht. Die Kanäle werden mittels interrupts und eigenen micros() mit einer Auflösung von +-0,5us ausgelesen

PPMDecoder