Progetto pendolo

Ciao a tutti, sono nuovo del forum e mi sono presentato nell apposita sezione.
Vi presento il mio problema, sono un insegnante di scienze e sto utilizzando arduino per costruire degli esperimenti per i miei allievi.
Ora sto costruendo un pendolo, e mi serve un sistema che possa cronometrare i passaggi del pendolo, il sistema da me costruito prevede una fotocellula a laser e una foto resistenza, al passaggio del peso la resistenza varia e uso questa variazione come start per il cronometraggio fino al passaggio successivo, e poi l’ultimo passaggio sarà anche il nuovo start per il prossimo tempo.
L’idea da me utilizzata nello sketch è di utilizzare un array e salvare questi 10 di questi valori per poi farne una media.

Voi come fareste?
Io sono totalmente in fosso con questo problema. :o

Per il passaggio del pendolo potresti usare un HY810H, che comprende già led e fototransistor. Dipende ovviamente dalla grandezza del pendolo. Se il pendolo fosse troppo grande potresti attaccare al suo estremo un piccolo pezzo di plastica che passi nella forcella.

Cerca HY810H su ebay o su amazon.

Il tutto ha il vantaggio di poter essere alimentato direttamente dall'Arduino, dato il basso assorbimento.

Ciao,
P.

Lavitediarchimede:
Ora sto costruendo un pendolo, e mi serve un sistema che possa cronometrare i passaggi del pendolo, il sistema da me costruito prevede una fotocellula a laser e una foto resistenza, al passaggio del peso la resistenza varia e uso questa variazione come start per il cronometraggio fino al passaggio successivo, e poi l’ultimo passaggio sarà anche il nuovo start per il prossimo tempo.

Uhm, ma hai già scritto qualcosa e quindi vuoi estendere il discorso per tenere traccia di 10 passaggi consecutivi per farne la media, o qualche altro problema? Magari inizia a postare il tuo codice, anche se molto "grezzo", così vediamo meglio.

Come sensore però la farei più semplice, usando un sensore di prossimità ad infrarossi, vedi QUESTO progetto che forse ti può dare degli spunti, e ne parliamo.

Ciao e grazie mille per le risposte:
il codice semplice semplice per misurare il tempo e quindi inviare la misura alla seriale è:
float tempo;

float tempo;
int Foto = 0;
int val = 0;
int Laser = 8;

void setup()
{
  pinMode(Laser, OUTPUT);
  pinMode(Foto, INPUT);

  Serial.begin(9600);
}
void loop() {
  digitalWrite(Laser, HIGH);


  if (analogRead(Foto) < 100)
  {
    long start = millis();
    while (analogRead(Foto) < 100)
      tempo = (millis() - start);


  }
  Serial.println(tempo);
  delay (100);//prints time since program started
  // wait a second so as not to send massive amounts of data
}

Poi cercando di complicare il discorso con una media dei 4 valori, il codice molto malfunzionante è:

int Foto = 0;
int val = 0;
int Laser = 8;

int valfr [4] ; // Array di 4 valori dedicato ai valori della fotoresistenza
int somma = 0;

void setup() {

  pinMode(Laser, OUTPUT);
  pinMode(Foto, INPUT);

  Serial.begin(9600);

}

void loop() {
  digitalWrite(Laser, HIGH);

  if (analogRead(Foto) < 100)
  {
    long start = millis();

    while (analogRead(Foto) < 100) // finchè il sensore legge un valore inferiore a 100
      // fa scorrere il cronometro e calcola il tempo
      tempo = (millis() - start);

    for (int i = 0; i < 3; i++) {

      valfr[i] = tempo;
      delay(1000); // frequenza di campionamento
    }
    somma = 0;
    for (int i = 0; i < 3; i++) {

      somma = somma + valfr[i];
    }
    float media = somma / 4;

    Serial.println(tempo);


  }
}

Vi allego anche le foto del pendolo e del circuito.![](dav hosted at ImgBB — ImgBB
dav hosted at ImgBB — ImgBB
dav hosted at ImgBB — ImgBB
dav hosted at ImgBB — ImgBB)

Lavitediarchimede:
il codice semplice semplice per misurare il tempo e quindi inviare la misura alla seriale è:

Ok, gli darò un'occhiata più tardi, ma prima che te lo dica un moderatore, da regolamento devi racchiudere il codice tra i tag appositi "code"! Inoltre ti consiglio di iniziare ad imparare ad indentare bene il codice, l'IDE ti può aiutare perché premendo Ctrl-T lo fa lui per te. E poi posta il codice così indentato.

Per ognuno di questi due messaggi clicca sul pulsantino "More..." poi "Modify" e nell'editor evidenzia tutto e solo il codice, sostituiscilo con il codice indentato, e premi il pulsantino in alto a sinistra "[/]", quindi salva.

Ciao,
Controllando il materiale a cui mi avete detto di dare un occhiata... ho riassemblato il tutto utilizzando un sensore di prossimità ad IR, un ki032.
il codice che ho unpò copiato e un po' elaborato è il seguente:

int Led = 13; // voglio che al passaggio dell'ostacolo si acccenda il pin di arduino
int Sensore = 3;
unsigned long pretime;
unsigned long currtime;
unsigned long timePeriod;
boolean val = true;

void setup() {
  Serial.begin(9600);
  pinMode(Led, OUTPUT);
  pinMode(Sensore, INPUT);
  pretime = micros();
}
void loop() {
  if (digitalRead(Sensore) == 0)
  {
    if (val == true)
    {
      currtime = micros();
      timePeriod = currtime - pretime;
      pretime = currtime;
      val = !val;
      digitalWrite(Led, HIGH);

      Serial.println(timePeriod / 1000);
      delay(100);
    }
    else {
      val = !val;
      delay(100);
      digitalWrite(Led, LOW);
    }
  }
}

il discorso che facevo della media mi servirebbe per inviare ad un LCD il tempo non ad ogni passaggio ma dopo un tot di passaggi in modo da dare il tempo agli studenti di poter prendersi giù i dati.
Secondo voi è un buon metodo?? ma soprattutto come posso calcolare la media?? i metodi precedentemente postati sono validi??

Grazie mille ancora per il supporto

Allora, sempre nello spirito di darti le giuste "dritte" di programmazione, prima di passare al codice che ti suggerirò, iniziamo da qualche altra nota generale e specifica, per spiegarti le modifiche che ho apportato al tuo codice. Quando QUESTO funziona come atteso, allora passiamo all'array.
Infine si potrà estendere all'LCD ma se necessario, perché comunque sul PC potresti accumulare tutte le letture leggendole dalla seriale con un programmino seriale tipo putty, e salvarle su file e poi analizzarle con Excel. Ma vedremo in seguito.

if (digitalRead(Sensore) == 0)

Se il sensore dà un segnale normalmente alto (HIGH) e quando passa il pendolo diventa basso (LOW), per evidenziare meglio la cosa ti consiglio di usare proprio le costanti HIGH e LOW, per cui
if (digitalRead(Sensore) == LOW)

int Led = 13; // voglio che al passaggio dell'ostacolo si acccenda il pin di arduino
int Sensore = 3;

Per ottimizzazione, i numeri dei pin impostali sempre byteo, meglio, come #define (e per convenzione i simboli costanti si indicano con tutte maiuscole). Dato che parli del led integrato, sappi che esiste un simbolo LED_BUILTIN già definito (e vale 13 su UNO), quindi non c'è neanche bisogno di quello:

#define SENSORE 3
...
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(SENSORE, INPUT);
...

Inoltre dici che vuoi accendere il LED al passaggio del pendolo, ma nel codice sembra che tu faccia il contrario, nel setup() mettilo LOW e poi HIGH quando rilevi il passaggio.

boolean val = true;

Questa variabile di fatto dovrebbe indicare quando iniziare a contare il tempo (primo passaggio), non dal setup() perché altrimenti la prima lettura partirebbe non da quando passa il pendolo ma da quando accendi Arduino (visto che leggi il tempo anche nel setup). Poi evita le variabili con un nome non significativo, se indica un primo passaggio chiamalo "primo" ad esempio, e che va inizializzato a true (attendo il primo passaggio)...

currtime = micros();

Tu leggi l'intervallo di tempo in microsecondi, ma dato che fai dei delay(100) nel loop() non avrai mai una precisione maggiore di un decimo di secondo!
Credo che tu abbia messo il delay() per evitare doppie letture dello stesso passaggio del sensore, quindi ti converrebbe invece attendere che il sensore torni HIGH. Tra l'altro con quei delay(100) non hai risoluzione utile per fare caalcoli un poco più precisi perché saranno sempre sul decimo di secondo, una risoluzione in millisecondi è più che sufficiente, quindi usare millis() e non micros().

Ok, ecco il codice modificato, analizza le modifiche che ti ho fatto (ovviamente se hai dubbi, chiedi) poi provalo (non provarlo senza prima averlo analizzato e capito!), e se ti soddisfa proseguiamo:

#define SENSORE 3

unsigned long pretime;
unsigned long currtime;
unsigned long timePeriod;

boolean primo = false;

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(SENSORE, INPUT);
  digitalWrite(LED_BUILTIN, LOW);
}
void loop() {
  int sensore = digitalRead(SENSORE);
  if (sensore == LOW)
  { // Rilevata la presenza del pendolo
    // Accendo il LED
    digitalWrite(LED_BUILTIN, HIGH);
    if (primo)
    { // Se è il primo passaggio memorizzo l'istante iniziale
      pretime = millis();
    }
    else
    { // Passaggio successivo
      currtime = millis();
      timePeriod = currtime - pretime;
      pretime = currtime;
      Serial.println((float)timePeriod / 1000.0, 3);
    }
    // Attendo che il pendolo esca dalla portata del sensore
    while (digitalRead(SENSORE) == LOW)
    {
      delay(10);
    }
  }
  digitalWrite(LED_BUILTIN, LOW);
  delay(10); // Risoluzione 1/100 di secondo
}

Ciao,
Per quanto riguarda il codice intendevo proprio accendere il Led di arduino quindi siamo d’accordo :slight_smile:

Per quanto riguarda la precisione del calcolo in Micros(), so che non ne viene fuori una misura precisissima ma quel che mi basta per spiegare il comportamento di paese di un oscillatore (comunque la tua modifica sarà utilizzata per altri tipi di esperienze che necessitano di maggior precisione... grazie mille).

Unica cosa concettuale che non mi torna è il passaggio..

while (digitalRead(SENSORE) == LOW)
    {
      delay(10);
    }

Esattamente cosa dovrebbe aiutarmi a fare?

Per tutto il resto mi sembra di esserci.

Grazie ancora

Lavitediarchimede:
Per quanto riguarda la precisione del calcolo in Micros(), so che non ne viene fuori una misura precisissima ma quel che mi basta per spiegare il comportamento di paese di un oscillatore.. grazie mille).

Prego, comunque sia a meno che tu non abbia un pendolo con periodo di meno diciamo di 40-50 ms non penso che micros() ti dia nessuna maggiore precisione, anche perché ti scontri con la conversione da unsigned long a float in quanto il float ha circa 5 digit di precisione, se uno è per il valore intero (non penso tu abbia periodi maggiori di 9 secondi) ne hai massimo 4, ossia decimi di millisecondo, niente micro.

comunque la tua modifica sarà utilizzata per altri tipi di esperienze che necessitano di maggior precisione.

Beh, ok, ma la mia non ha maggiore precisione, è "solo" più affidabile. Ed è la base per estenderla poi alla lettura di 10 oscillazioni per cui non ho capito bene se vuoi usarla per il tuo scopo iniziale (e quindi proseguiamo da quella, che dovresti prima provare un poco e vedere quindi se corrisponde alla tua esigenza), oppure no (e perché?).

Unica cosa concettuale che non mi torna è il passaggio..
while (digitalRead(SENSORE) == LOW)
{
delay(10);
}
Esattamente cosa dovrebbe aiutarmi a fare?

Beh l'ho scritto nel commento che vedi sopra al while:
// Attendo che il pendolo esca dalla portata del sensore
Senza quel while() dato che la funzione loop() viene eseguita migliaia di volte al secondo, interpreteresti come evento "pendolo entrato davanti al sensore" più volte. Quindi aspetto che la lettura dal sensore mi dia HIGH ossia, come ho scritto, il pendolo sia uscito dalla portata del sensore. Questo per evitare quell'inutile e dannoso delay(100) che c'era prima.

Ok,
si avevo letto il commento ma non ho capito come il codice dovrebbe aiutarmi allo scopo ecco...

comunque chiedo una delucidazione... non manca la condizione per IF?? in questa parte??

    if (primo)
    { // Se è il primo passaggio memorizzo l'istante iniziale

detto questo... ho provato il codice e funziona... (come il mio sembra ma certamente con più reti di salvataggio). unica differenza con il mio è che il mio segna il tempo solo al passaggio da una parte (Quindi effettivamente mi da un periodo) mentre il tuo mi da una lettura in andata e una al ritorno(quindi il semiperiodo)... non capisco come mai... :astonished:

Lavitediarchimede:
comunque chiedo una delucidazione... non manca la condizione per IF?? in questa parte??
if (primo)
{ // Se è il primo passaggio memorizzo l'istante iniziale

No, quello che va dentro la parentesi della if() è una espressione booleana (vero/falso, in realtà qualsiasi valore numerico diverso da zero è "vero" mentre zero è "falso") e dato che quella variabile è booleana, se vale "true" entra nella if(). Quindi equivale a dire:
if (primo == true)

detto questo... ho provato il codice e funziona... (come il mio sembra ma certamente con più reti di salvataggio).

Bene!

unica differenza con il mio è che il mio segna il tempo solo al passaggio da una parte (Quindi effettivamente mi da un periodo) mentre il tuo mi da una lettura in andata e una al ritorno(quindi il semiperiodo)... non capisco come mai... :astonished:

Si, il periodo effettivo è il doppio di quello del semiperiodo, quindi teoricamente potresti anche solo moltiplcare per 2, ma se vuoi contare un periodo completo basta un ulteriore flag, prova questo:

#define SENSORE 3

unsigned long pretime;
unsigned long currtime;
unsigned long timePeriod;

boolean primo = false;
boolean andata = true;

void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(SENSORE, INPUT);
  digitalWrite(LED_BUILTIN, LOW);
}
void loop() {
  int sensore = digitalRead(SENSORE);
  if (sensore == LOW)
  { // Rilevata la presenza del pendolo
    // Accendo il LED
    digitalWrite(LED_BUILTIN, HIGH);
    if (primo)
    { // Se è il primo passaggio memorizzo l'istante iniziale
      pretime = millis();
    }
    else
    { // Passaggio successivo, calcolo solo al ritorno
      if ( !andata )
      {
        currtime = millis();
        timePeriod = currtime - pretime;
        pretime = currtime;
        Serial.println((float)timePeriod / 1000.0, 3);
      }
    }
    // Attendo che il pendolo esca dalla portata del sensore
    while (digitalRead(SENSORE) == LOW)
    {
      delay(10);
    }
    andata = !andata;
  }
  digitalWrite(LED_BUILTIN, LOW);
  delay(10); // Risoluzione 1/100 di secondo
}

Ciao,
Scusate il ritardo nella risposta ma il tempo è sempre quello che è...
Lo sketch funziona.. ora tornando al discorso dello schermo LCD mi serve poiché conto di non avere bisogno di PC in lab ma di far misurare i periodi in accordo con le leggi teoriche... non mi serve un trattamento dati diciamo...
detto questo si ritorna al discorso media... così potrei permettere ai ragazzi di avere una misura mediata su una decina di oscillazioni e quindi minor numero di dati da prelevare(il tempo in laboratorio ahimè è poco e va sfruttato il più possibile).

secondo voi implementando la parte sulla media postata nel secondo sketch potrebbe andare??

Ok, va bene sempre andare per gradi quindi prima si implementa il calcolo della media, e quando funziona si usa l'LCD al posto della seriale.

Se hai usato il mio ultimo listato (nel post #11, hai usato quello? In caso contrario, posta la tua ultima versione funzionante) per estenderlo per fare una media bisogna far diventare un array la variabile "timePeriod".

Confermami questa cosa, e ti spiego meglio come fare, anche con qualche modifica al programma (credo sia importante che anche tu sappia esattamente cosa fa il programma e come lo fa, per cui cerco di spiegare sempre). O intendi provarci tu?

L'idea è quella di provarci come avevo fatto nell'esempio:

void loop() {
  digitalWrite(Laser, HIGH);

  if (analogRead(Foto) < 100)
  {
    long start = millis();

    while (analogRead(Foto) < 100) // finchè il sensore legge un valore inferiore a 100
      // fa scorrere il cronometro e calcola il tempo
      tempo = (millis() - start);

    for (int i = 0; i < 3; i++) {

      valfr[i] = tempo;
      delay(1000); // frequenza di campionamento
    }
    somma = 0;
    for (int i = 0; i < 3; i++) {

      somma = somma + valfr[i];
    }
    float media = somma / 4;

    Serial.println(tempo);


  }
}

immettendo la variabile time period come dicevi tu...
ma dev'esserci qualcosa che mi sfugge... ci sto sbattendo la testa... se entro domani non mi riesce magari chiedo ulteriore aiuto..
nel frattempo.. grazie mille

Certo che non funziona visto che calcoli la media, dividendo per 4, già dalla prima misura.

Prima devi raccogliere le misure, incrementando somma, e poi calcoli la media al di fuori del ciclo di misura.

In alternativa puoi calcolare la media ad ogni ciclo ma la divisione deve essere fatta sul numero di misure, quindi
somma / 4 deve diventare somma / NumeroMisure o qualcosa del genere. Devi incrementare NumeroMisure ad ogni incremento di somma.

Edit: se non ho scritto scemenze. Ne stavo scrivendo una sul periodo del pendolo che si allunga quando nel cervello addormentato mi è suonato l'allarme sul fatto che il periodo del pendolo ideale dipende solo dalla lunghezza.

Lavitediarchimede:
immettendo la variabile time period come dicevi tu...
ma dev'esserci qualcosa che mi sfugge... ci sto sbattendo la testa...

Ok, allora per iniziare devi definire quanti periodi vuoi usare per la media, ad esempio:

#define NUM_PERIODI 10

a quel punto ti devi tenere un contatore di periodi già letti ed una variabile dove accumuli il totale dei tempi:

unsigned long timeTotal;
byte tp;

Fatto questo, ad ogni ciclo completo sommi il periodo calcolato all'interno della variabile "timeTotal" ed incrementi di 1 il contatore "tp" e quando il contatore raggiunge il numero di periodi calcolo la media dividendo per il numero di campioni e per 1000 per ottenere il valore in secondi:

timeTotal += timePeriod;
if ( ++tp == NUM_PERIODI )
{

  • timeTotal = (timeTotal / NUM_PERIODI) / 1000.0;*
    ...

In sostanza, questo (non l'ho ovviamente eseguito quindi mi raccomando, controlla le modifiche che ho fatto e dimmi se ti torna tutto):

#define SENSORE 3
// Numero di periodi da considerare per la media
#define NUM_PERIODI 10

unsigned long pretime;
unsigned long currtime;
unsigned long timePeriod;

// Somma dei tempi dei periodi acquisiti
unsigned long timeTotal;
// Contatore dei periodi acquisiti
byte tp; 

boolean primo = false;
boolean andata = true;

void setup() 
{
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(SENSORE, INPUT);
  digitalWrite(LED_BUILTIN, LOW);
  // Inizializzo le variabili per la media
  timeTotal = 0;
  tp = 0;
}

void loop() {
  int sensore = digitalRead(SENSORE);
  if (sensore == LOW)
  { // Rilevata la presenza del pendolo
    // Accendo il LED
    digitalWrite(LED_BUILTIN, HIGH);
    if (primo)
    { // Se è il primo passaggio memorizzo l'istante iniziale
      pretime = millis();
    }
    else
    { // Passaggio successivo, calcolo solo al ritorno
      if ( !andata )
      {
        currtime = millis();
        timePeriod = currtime - pretime;
        pretime = currtime;
        //Serial.println((float)timePeriod / 1000.0, 3);
        // Accumulo il valore nella variabile timeTotal
        timeTotal += timePeriod;
        // Se ho raggiunto il numero di campioni,
        if ( ++tp == NUM_PERIODI )
        {
          // calcolo la media
          timeTotal = (timeTotal / NUM_PERIODI) / 1000.0;
          // Questo verrà sostituito dal display LCD
          Serial.println((float)timeTotal, 3);
          // resetto contatore e variabile di accumulo
          timeTotal = 0;
          tp = 0;
        }
      }
    }
    // Attendo che il pendolo esca dalla portata del sensore
    while (digitalRead(SENSORE) == LOW)
      delay(10);
    andata = !andata;
  }
  digitalWrite(LED_BUILTIN, LOW);
  delay(10); // Risoluzione 1/100 di secondo
}

@ docdoc
Ciao,
sto cercando di capire il tuo ultimo codice.. mi sembrano tutti passaggi chiari ma caricandolo non capisco come mai mi faccia uscire sempre 1,000 s.

giusto per capire.. il metodo dell'array che proponevo è del tutto sbagliato??

@zoomx non capisco bene cosa intendi... sto dividendo dalla prima misura perché??

Lavitediarchimede:
sto cercando di capire il tuo ultimo codice.. mi sembrano tutti passaggi chiari ma caricandolo non capisco come mai mi faccia uscire sempre 1,000 s.

Scusa, come detto non ho provato il codice, il problema è che quando divido per NUM_PERIODI e per 1000 il compilatore interpreta tutto come intero visto che il risultato deve finire in un long, invece va messo in un float in quanto vogliamo i decimali.
Correggi così:

...
float periodoMedio;
...
  // calcolo la media
  periodoMedio = ((float)timeTotal / NUM_PERIODI) / 1000.0;
  // Questo verrà sostituito dal display LCD
  Serial.println(periodoMedio, 3);
...

giusto per capire.. il metodo dell'array che proponevo è del tutto sbagliato??

No, ma se ti serve solo calcolare la media e non le singole letture, non serve alcun array, ccumuli dentro una variabile sommando le letture e poi dividi per il numero di letture.

Mentre segui quanto scritto da docdoc ti spiego quanto credo di aver capito del tuo sketch precedente.

Qui parti con la misura
if (analogRead(Foto) < 100)
e prendi il tempo iniziale
long start = millis();
Quindi prendi il tempo continuamente nel mentre che analogRead(Foto) < 100). Ecco, qui invece potresti aspettare che analogRead(Foto) > 100) e quindi prendere il tempo.

Fino a qui hai preso UN tempo

Nel ciclo successivo copi questo tempo (sempre lo stesso) nei 4 elementi dell'array con il ciclo for

Nel passo successivo esegui la media dei 4 elementi, che, ricordo, sono tutti e 4 gli stessi. docdoc ti ha già scritto che questo non è necessario a meno che tu poi voglia aggiungere la deviazione standard.

Calcoli la media

Stampi il primo e unico tempo che hai preso, la media non viene stampata.