Leggere da sensore PWM

Ciao a tutti! Ho bisogno di leggere un treno d'onda da un sensore che mi spara la rilevazione in PWM (sono 3 letture separate da un segnale LOW). Questo è il codice riadattato preso da uno sketch trovato sul web (RCArduino: How To Read an RC Receiver With A Microcontroller - Part 1):

// First Example in a series of posts illustrating reading an RC Receiver with
// micro controller interrupts.
//
// Subsequent posts will provide enhancements required for real world operation
// in high speed applications with multiple inputs.
//
// http://rcarduino.blogspot.com/ 
//
// Posts in the series will be titled - How To Read an RC Receiver With A Microcontroller

// See also http://rcarduino.blogspot.co.uk/2012/04/how-to-read-multiple-rc-channels-draft.html  

#define THROTTLE_SIGNAL_IN 0 // INTERRUPT 0 = DIGITAL PIN 2 - use the interrupt number in attachInterrupt
#define THROTTLE_SIGNAL_IN_PIN 2 // INTERRUPT 0 = DIGITAL PIN 2 - use the PIN number in digitalRead

#define NEUTRAL_THROTTLE 1500 // this is the duration in microseconds of neutral throttle on an electric RC Car

volatile int nThrottleIn = NEUTRAL_THROTTLE; // volatile, we set this in the Interrupt and read it in loop so it must be declared volatile
volatile unsigned long ulStartPeriod = 0; // set in the interrupt
volatile boolean bNewThrottleSignal = false; // set in the interrupt and read in the loop
// we could use nThrottleIn = 0 in loop instead of a separate variable, but using bNewThrottleSignal to indicate we have a new signal 
// is clearer for this first example

void setup()
{
  // tell the Arduino we want the function calcInput to be called whenever INT0 (digital pin 2) changes from HIGH to LOW or LOW to HIGH
  // catching these changes will allow us to calculate how long the input pulse is
  attachInterrupt(THROTTLE_SIGNAL_IN,calcInput,CHANGE);

  Serial.begin(9600); 
}

void loop()
{
 // if a new throttle signal has been measured, lets print the value to serial, if not our code could carry on with some other processing
 if(bNewThrottleSignal)
 {

   Serial.println(nThrottleIn);  

   // set this back to false when we have finished
   // with nThrottleIn, while true, calcInput will not update
   // nThrottleIn
   bNewThrottleSignal = false;
 }

 // other processing ... 
}

void calcInput()
{
  // if the pin is high, its the start of an interrupt
  if(digitalRead(THROTTLE_SIGNAL_IN_PIN) == HIGH)
  { 
    // get the time using micros - when our code gets really busy this will become inaccurate, but for the current application its 
    // easy to understand and works very well
    ulStartPeriod = micros();
  }
  else
  {
    // if the pin is low, its the falling edge of the pulse so now we can calculate the pulse duration by subtracting the 
    // start time ulStartPeriod from the current time returned by micros()
    if(ulStartPeriod && (bNewThrottleSignal == false))
    {
      nThrottleIn = (int)(micros() - ulStartPeriod);
      ulStartPeriod = 0;

      // tell loop we have a new signal on the throttle channel
      // we will not update nThrottleIn until loop sets
      // bNewThrottleSignal back to false
      bNewThrottleSignal = true;
    }
  }
}

Ottengo effettivamente 3 letture ma non ho capito dove posso memorizzare i singoli valori delle singole letture. Avrei bisogno di qualcosa del tipo

Serial.print("Valore1: ");
Serial.println(lettura1);
Serial.print("Valore2: ");
Serial.println(lettura2);
Serial.print("Valore3: ");
Serial.println(lettura3);

ma non riesco a capire come fare.
Qualche idea?

Grazie!

Niente di più facile.
Invece di stampare il dato come fai ora:

Serial.println(nThrottleIn);

Assegnalo ad una variabile di tipo opportuno (nThrottleIn è un signed int).

int a = nThrottleIn;

leo72:
Niente di più facile.
Invece di stampare il dato come fai ora:

Serial.println(nThrottleIn);

Assegnalo ad una variabile di tipo opportuno (nThrottleIn è un signed int).

int a = nThrottleIn;

Purtroppo non è così semplice: essendo tre valori, sono tre risultati distinti. Il risultato che ottengo dal Serial monitor è il seguente:

3880
4224
580

e a me serve trattarli in tre modi distinti. Forse sbaglio qualcosa?

Usi un array o vettore di dimensione 3 contenente int.

int lettura[3];

Hai così tre variabili che si chiamano

lettura[0], lettura[1] e lettura[2]

Usando un'altra variabile come indice puoi accedere ai vari elementi. Questa variabile, ad esempio x, fa da indice e la gestisci tu.

for(int x=0;x<3;x++)        // faccio tre letture 
{  lettura[x]=analogRead(A0);
}

Avevo già pensato ad un array. Il problema non è scorrere per leggerlo, quanto riempirlo! Non riesco a capire dove piazzare il codice che potrebbe riempire l'array.

Ma qui allora ti serve una guida per il C, non un aiuto per Arduino :wink:

No, forse avete frainteso. Non è che non so come riempire un array. In questo caso specifico non saprei DOVE piazzare il codice per riempire l'array. Idee in merito?

Qui:

if(bNewThrottleSignal)
 {
   Serial.println(nThrottleIn);

nella loop() è dove hai un singolo tempo, perciò qui devi scrivere nell'array. Usando una variabile globale che fa da indice.
Esempio byte ndx inializzata a zero e vettore di nome arr.

int arr[3];         // tra le variabili globali a inizio
byte ndx=0;
...
... nella loop
if(bNewThrottleSignal)
{ arr[ndx]=nThrottleIn;
  ndx++;
  if(ndx==3)   
  { // fatte tre letture perciò faccio quel che devo con i tre valori
     ....
    // per ultima cosa azzero ndx, altrimenti vado oltre agli elementi del vettore
     ndx=0;
  }
   Serial.println(nThrottleIn);
...

Il "dove" metterlo nel tuo programma lo devi decidere tu. Nell'esempio che hai postato, c'era un loop con una sola lettura del sensore.
A questo punto non dovresti fare altro che ampliare quel codice e fare 3 letture, ovviamente quando il dato è disponibile (controlla bNewThrottleSignal).

nid69ita:
Qui:

if(bNewThrottleSignal)

{
  Serial.println(nThrottleIn);



nella loop() è dove hai un singolo tempo, perciò qui devi scrivere nell'array. Usando una variabile globale che fa da indice.
Esempio byte ndx inializzata a zero e vettore di nome arr.


int arr[3];         // tra le variabili globali a inizio
byte ndx=0;
...
... nella loop
if(bNewThrottleSignal)
{ arr[ndx]=nThrottleIn;
 ndx++;
 if(ndx==3)  
 { // fatte tre letture perciò faccio quel che devo con i tre valori
     ....
   // per ultima cosa azzero ndx, altrimenti vado oltre agli elementi del vettore
    ndx=0;
 }
  Serial.println(nThrottleIn);
...

Grazie mille, le letture sembrano avvenire correttamente. Ora devo fare altri conti su quei segnali, bisogna condizionarli in un certo modo. Sicuramente avrò bisogno di altro aiuto, scriverò.

Grazie!

Rieccomi (come sospettavo :slight_smile: )

Il sensore funziona a inverted PWM. Come dovrei riadattare quel codice affinché legga correttamente il treno d'onda? Triggerando l'interrupt quando arriva un segnale LOW ottengo letture inconcludenti... la prima potrebbe anche significare qualcosa ma le altre due sono 0. Suggerimenti?

Lasciando il controllo dell'IF su HIGH, scambia il codice dell'IF con l'ELSE e viceversa.
Cosi invece degli intervalli alti calcoli la durata di quelli bassi.
Oppure se conosci la durata fissa del segnale fai una semplice sottrazione:

arr[ndx]=Total - nThrottleIn

Purtroppo non è una durata fissa... anzi, proprio al variare del duty cycle (ovviamente) varia la lettura.
Ho modificato il codice in questo modo:

// First Example in a series of posts illustrating reading an RC Receiver with
// micro controller interrupts.
//
// Subsequent posts will provide enhancements required for real world operation
// in high speed applications with multiple inputs.
//
// http://rcarduino.blogspot.com/ 
//
// Posts in the series will be titled - How To Read an RC Receiver With A Microcontroller

// See also http://rcarduino.blogspot.co.uk/2012/04/how-to-read-multiple-rc-channels-draft.html  

#define THROTTLE_SIGNAL_IN 0 // INTERRUPT 0 = DIGITAL PIN 2 - use the interrupt number in attachInterrupt
#define THROTTLE_SIGNAL_IN_PIN 2 // INTERRUPT 0 = DIGITAL PIN 2 - use the PIN number in digitalRead

#define NEUTRAL_THROTTLE 0


// this is the duration in microseconds of neutral throttle on an electric RC Car

volatile int nThrottleIn = NEUTRAL_THROTTLE; // volatile, we set this in the Interrupt and read it in loop so it must be declared volatile
volatile unsigned long ulStartPeriod = 0; // set in the interrupt
volatile boolean bNewThrottleSignal = false; // set in the interrupt and read in the loop
// we could use nThrottleIn = 0 in loop instead of a separate variable, but using bNewThrottleSignal to indicate we have a new signal 
// is clearer for this first example
double arr[3];         // tra le variabili globali a inizio
byte ndx=0;
double lettura1;
double lettura2;
double lettura3;
void setup()
{
  // tell the Arduino we want the function calcInput to be called whenever INT0 (digital pin 2) changes from HIGH to LOW or LOW to HIGH
  // catching these changes will allow us to calculate how long the input pulse is
  attachInterrupt(THROTTLE_SIGNAL_IN,calcInput,CHANGE);

  Serial.begin(9600); 
}

void loop()
{
 // if a new throttle signal has been measured, lets print the value to serial, if not our code could carry on with some other processing
 if(bNewThrottleSignal)
 {
  arr[ndx]=nThrottleIn;
  ndx++;
  if(ndx==3)   
  { // fatte tre letture perciò faccio quel che devo con i tre valori
  lettura1 = arr[0];
  lettura2 = arr[1];
  lettura3 = arr[2];
  Serial.print("Lettura 1: ");
  Serial.println(lettura1);
  Serial.print("Lettura 2: ");
  Serial.println(lettura2);
  Serial.print("Lettura 3: ");
  Serial.println(lettura3);
    // per ultima cosa azzero ndx, altrimenti vado oltre agli elementi del vettore
     ndx=0;
  }
  
   // set this back to false when we have finished
   // with nThrottleIn, while true, calcInput will not update
   // nThrottleIn
   bNewThrottleSignal = false;
 }

 // other processing ... 
}

void calcInput()
{
  // if the pin is high, its the start of an interrupt
  if(digitalRead(THROTTLE_SIGNAL_IN_PIN) == HIGH)
  { 
     // if the pin is low, its the falling edge of the pulse so now we can calculate the pulse duration by subtracting the 
    // start time ulStartPeriod from the current time returned by micros()
    if(ulStartPeriod && (bNewThrottleSignal == false))
    {
      nThrottleIn = (int)(micros() - ulStartPeriod);
      ulStartPeriod = 0;

      // tell loop we have a new signal on the throttle channel
      // we will not update nThrottleIn until loop sets
      // bNewThrottleSignal back to false
      bNewThrottleSignal = true;
    }
   
  }
  else
  {
    // get the time using micros - when our code gets really busy this will become inaccurate, but for the current application its 
    // easy to understand and works very well
    ulStartPeriod = micros();
  }
}

e ottengo le prime due letture negative... potrebbe essere relativo comunque. Il problema è che avrei bisogno di eseguire il seguente calcolo. Speriamo di riuscire a spiegarmi bene...

Allora. Io ho da calcolare 3 valori, rispettivamente
PWM1
PWM2
PWM3

che sono da calcolarsi in questo modo:

PWM1 = T1/T
PWM2 = T2/T
PWM3 = T3/T

dove, supponendo la seguente onda:

H   -----------                            -----------                             -----------                   --------------------------
                           |                           |                      |                           |                      |                  |
L                         |_________|                      |_________|                      |______|
 
<--------->    <------------->                      <------------->                     <-------------> <------------------>
     pausa                     t1                                                   t2                                             t3                   pausa
                          <------------------------><------------------------><------------------------>
                                              T                                                      T                                                  T

(ammazza che fatica disegnare l'onda quadra :P)

Dunque...

PWM1 dovrebbe essere il T in cui il segnale rimane LOW la prima volta / t1
PWM2 dovrebbe essere il T in cui il segnale rimane LOW la seconda volta / t2
PWM3 dovrebbe essere il T in cui il segnale rimane LOW la terza volta / t3 (notare che il duty cycle di t3 rimane uguale, anche se
il segnale ha un rising ad un certo punto - qui dovrei capire bene come fare).

E qui sono fermo...

Ma Arduino deve fare solo questo oppure gli devi far fare anche altre operazioni?
Se deve far solo questo potresti usare il metodo pulsein(), con tre conteggi di seguito.
Il problema è che blocca qualsiasi altra operazione quindi non puoi fare altro con il micro.

Rimanendo con gli interrupt... ci rifletto un attimo...

Diciamo che dovrebbe fare anche altro. Ho usato gli interrupt perché dovrebbero essere anche più precisi in termini di timing. pulseIn() potrebbe anche andare, forse è anche molto più semplice, ma non l'ho mai usato per letture simultanee e non saprei come fare. Sicuramente sarebbe molto più comodo. Il fatto che sia blocking non è un grosso problema... la lettura dal sensore avviene velocissimamente e potrei postporre le altre operazioni. Con pulseIn come potrei fare?

altagest:
ottengo le prime due letture negative...

Se inizializzi le variabili a 0 e false nel setup, cosa succede?

EDIT:
Se la durata è variabile più che PWM mi pare un PPM.
--> http://forum.arduino.cc/index.php/topic,7167.0.html
--> Arduino Code to read PPM timings - RC Groups
oppure
--> http://forum.arduino.cc/index.php/topic,22140.0.html
--> Decoding PPM from RC Receiver - Syntax & Programs - Arduino Forum
--> Decoding PPM From RC Receiver Revisited - Syntax & Programs - Arduino Forum

Inizializzando a 0 ottengo sempre letture negative. E' PWM, non PPM, questo è sicuro...
Non ho a priori il dato sulla durata, ma prove empiriche oscilloscopiche mi danno 1200 ms di durata. Sapendo questo, con pulseIn() come potrei fare?

Se il segnale è così lungo la pulsein ti blocca il micro per troppo tempo: sono pur sempre 1,2 secondi; una eternità.
Meglio ragionare con l'interrupt.
Purtroppo non puoi impostare 2 condizioni sullo stesso interrupt: rising e falling quindi ci si deve arrangiare con change.
Il codice si attiva ad ogni transizione alto-basso e viceversa.
Il tuo deve iniziare a contare sulla alto-basso e terminare su basso-alto.
Giusto?

Giusto. Per rendere più chiaro il discorso, guarda questa immagine:

sempre considerando che il segnale va calcolato come già detto, ovvero

PWM1 = T1/T
PWM2 = T2/T
PWM3 = T3/T

Io dico che pulseIn() potrebbe andare... so che 1.2 sec è tantissimo ma gli altri calcoli non sono così importanti. Certo, se riuscissimo a fare meglio tanto meglio :stuck_out_tongue:

Allora

void calcInput()
{
  if(digitalRead(THROTTLE_SIGNAL_IN_PIN) == LOW)
  { 
    ulStartPeriod = micros();
  }
  else
  {
    if(bNewThrottleSignal == false)
    {
      nThrottleIn = (int)(micros() - ulStartPeriod);
      ulStartPeriod = 0;
      bNewThrottleSignal = true;
    }
  }
}

Così dovresti ottenere i tre segnali distinti.
Quando l'impulso è in LOW, transizione alto-basso, parte il conteggio.
Viceversa quando è in HIGH calcola il periodo trascorso.

Il problema è che il loop deve essere più veloce di un singolo ciclo altrimenti perdi un dato poiche non metti a false la variabile e ti restituisce la somma di 2 letture consecutive.

EDIT:
Altrimenti sposti l'acquisizione dentro l'interrupt insieme all'indice e all'array.