[Risolto]Reaction Timer

Ciao a tutti, sto provando a creare un semplice giochino(basato su un LED e due pulsanti) che misura la velocità di reazione di una persona, così strutturato:
dopo x secondi dall'avvio del programma il LED viene acceso;
il giocatore deve premere un pulsante(stopBtn nel codice) il più in fretta possibile ed il ritardo tra l'accensione del LED e la pressione del pulsante viene stampato;
il processo si ripete 5 volte, viene calcolata la media dei 5 valori ed il loop si interrompe;
alla pressione di un altro pulsante(startBtn) il programma riparte.

Il codice per ora è questo:

int led = 11;
int startBtn = 7;
int stopBtn = 4;

const int readnum = 5;
int readings[readnum];
int index = 0;
int total = 0;
int average = 0;

int dly = 5000;
unsigned long time;
unsigned long prevtime = 0;

void setup() {
  pinMode(led, OUTPUT);
  pinMode(startBtn, INPUT);
  pinMode(stopBtn, INPUT);
  Serial.begin(9600); 
  
  for (int thisRead = 0; thisRead < readnum; thisRead++) {
    readings[thisRead] = 0;
  }
}

void loop() {
  time = millis();
  if (time-prevtime > dly) {
    digitalWrite(led, HIGH);
    if (digitalRead(stopBtn) == HIGH) {
      total = total-readings[index];
      readings[index] = time-prevtime-dly;
      total = total+readings[index];
      index++;
      average = total/readnum;
      
      digitalWrite(led, LOW);
      Serial.print("Reaction time: ");
      Serial.print(time-prevtime-dly);
      Serial.println(" ms");
      prevtime = time;
    }
    if (index == readnum) {
      Serial.print("Average reaction time: ");
      Serial.print(average);
      Serial.println(" ms");
      Serial.println(" ");
      index++;  //Forza il programma ad entrare nel loop while
    }   
  }
  while (index > readnum) {
    if (digitalRead(startBtn) == HIGH) {
      //Il programma viene resettato
      prevtime = time;
      index = 0;
    }
  }
}

Il problema che incontro è nel riavvio del loop:
dopo la pressione di startBtn il programma riparte ma non come vorrei, e non capisco quale sia esattamente il problema.
Se premo startBtn in un intervallo di tempo minore di dly i risultati sono ok

Reaction time: 173 ms
Reaction time: 292 ms
Reaction time: 166 ms
Reaction time: 171 ms
Reaction time: 149 ms
Average reaction time: 190 ms

Reaction time: 180 ms
Reaction time: 204 ms
Reaction time: 193 ms
Reaction time: 359 ms
Reaction time: 160 ms
Average reaction time: 219 ms

ma se aspetto un po' prima di far ripartire il loop queste sono le letture che ottengo:

Reaction time: 192 ms
Reaction time: 185 ms
Reaction time: 166 ms
Reaction time: 168 ms
Reaction time: 159 ms
Average reaction time: 174 ms

Reaction time: 21514 ms
Reaction time: 211 ms
Reaction time: 221 ms
Reaction time: 122 ms
Reaction time: 188 ms
Average reaction time: 4451 ms

Un grazie a chiunque possa darmi una mano 8)

Probabilmente il tuo pulsante non avendo un debounce il loop legge diversi 0/1 in brevissimi istanti durante la pressione del suddetto tasto, visto che non puoi mettere dei delay per fare il debounce, ti consiglio di farlo a livello hardware.

avevo valutato questa eventualità, ma l'ho scartata perché non mi sembra correlata alla risposta del programma. mi spiego:
il problema, per quel poco che ne capisco, sembra essere che al momento di verificare la condizione if (time-prevtime > dly)
il valore di time-prevtime è decisamente maggiore di quello che dovrebbe essere, come se il loop ripartisse PRIMA di ricevere il comando ed il valore di prevtime non venisse aggiornato all'ultimo valore registrato di time nel momento in cui startBtn viene premuto(ho tuttavia verificato con un Serial.println(time-prevtime) che alla pressione di startBtn i due valori risultano uguali, non mi spiego quindi perché successivamente risultino sfalsati).
Se infatti lascio passare 20 secondi prima di riavviare il loop, quando premo startBtn il led si accende immediatamente.

Comunque potrei sbagliarmi, quindi nell'eventualità correggetemi, ma credo che la struttura stessa del codice agisca come una specie di debounce: ho fatto in modo che la lettura dei pulsanti fosse sempre preceduta da un if o while le cui condizioni non risultano più verificate dopo la pressione del pulsante stesso. In questo modo, anche se Arduino rileva un grande alternarsi di 0 e 1, una volta ricevuto il primo segnale gli altri non hanno più alcuna influenza sul programma.

Assicurati di dare il giusto peso alle variabili:
Un unsigned INT conterà fino 65.535 poi si azzera, se non è unsigned va da ?32.768 a +32.767 o comunque non potrà contenere un numero superiore.
Se fai operazioni con i tempi dovresti usare i long unsigned .... nel millis l'hai usato, ma nel calcolo singolo e della media?
Verifica gli azzeramenti delle variabili a fine cicli.

Beh in quei calcoli, a meno a che a giocare non sia un bradipo, penso che un int sia molto più che sufficiente :grin:

Ho appena verificato le variabili e mi sembra tutto ok: dopo aver stampato la media dei valori index risulta uguale a 6, quindi il programma entra nel while e dopo aver premuto startBtn ottengo index = 0 e time e prevTime con lo stesso identico valore.

Probabile che ci sia un errore logico.
Comunque, per togliersi dubbi, intanto metti tutte le var che hanno a che fare con i millis di tipo unsigned long, per evitare che il compilatore autocasti qualche tipo.
Poi vai di debug sulla seriale mettendo print ad ogni rigo e controlla che le cose si svolgano come vorresti tu.
Ad esempio, io non capisco la logica dell'ultimo while. Secondo me non fa come pensi. Tu lo usi come debounce ma non è così. Il significato di quel while è: mentre l'index è maggiore di readnum, fai il resto. Il "resto" è un if che attende di vedere il pulsante su HIGH per resettare index. Io avrei invertito.

if (index > readnum) {
  while (digitalRead(startBtn) == HIGH) {} // aspetta che il pulsante venga rilasciato - debounce
  //Il programma viene resettato
  prevtime = time;
  index = 0;
}

Una volta entrato nel while il programma non può uscirne finché la condizione non risulta più confermata, e ciò avviene solo e soltanto al momento della pressione del pulsante.
Se inverto la posizione di if e while, anche se è vero che index > readnum, quando il pulsante non è premuto il programma riprende il suo loop e dopo 5 secondi il led si accende che io abbia premuto il tasto o meno.

Questo è l'ultimo debug che ho fatto

Modifiche al codice in grassetto: errore stupido, il grassetto nel codice non si vede XD

int led = 11;
int startBtn = 7;
int stopBtn = 4;

const int readnum = 5;
int readings[readnum];
int index = 0;
int total = 0;
int average = 0;
int x = 0;

int dly = 5000;
unsigned long time;
unsigned long prevtime = 0;

void setup() {
  pinMode(led, OUTPUT);
  pinMode(startBtn, INPUT);
  pinMode(stopBtn, INPUT);
  Serial.begin(9600); 
  
  for (int thisRead = 0; thisRead < readnum; thisRead++) {
    readings[thisRead] = 0;
  }
}

void loop() {
  time = millis();
  if (x > 0) {
    Serial.print("x al riavvio="); Serial.println(x);
    Serial.print("time al riavvio="); Serial.println(time);
    Serial.print("prevtime al riavvio="); Serial.println(prevtime);
  }
  if (time-prevtime > dly) {
    digitalWrite(led, HIGH);
    if (digitalRead(stopBtn) == HIGH) {
      total = total-readings[index];
      readings[index] = time-prevtime-dly;
      total = total+readings[index];
      index++;
      average = total/readnum;
      
      digitalWrite(led, LOW);
      Serial.print("Reaction time: ");
      Serial.print(time-prevtime-dly);
      Serial.println(" ms");
      Serial.print("index="); Serial.println(index);
      prevtime = time;
    }
    if (index == readnum) {
      Serial.print("Average reaction time: ");
      Serial.print(average);
      Serial.println(" ms");
      Serial.println(" ");
      index++;  //Forza il programma ad entrare nel loop while
      Serial.print("index prima del while="); Serial.println(index);
    }   
  }
  while (index > readnum) {
    if (digitalRead(startBtn) == HIGH) {
      //Il programma viene resettato
      prevtime = time;
      index = 0;
      x++;
      Serial.print("x finale=");  Serial.println(x);
      Serial.print("time finale="); Serial.println(time);
      Serial.print("prevtime finale="); Serial.println(prevtime);
      Serial.print("index finale="); Serial.println(index);
    }
  }
}

Questi i risultati:

Reaction time: 264 ms
index=1
Reaction time: 207 ms
index=2
Reaction time: 165 ms
index=3
Reaction time: 236 ms
index=4
Reaction time: 163 ms
index=5
Average reaction time: 207 ms

index prima del while=6
x finale=1
time finale=26004
prevtime finale=26004
index finale=0
x al riavvio=1
time al riavvio=40438
prevtime al riavvio=26004

i valori di time finale e al riavvio dovrebbero avere soltanto una piccola variazione, invece time al riavvio aumenta a dismisura ed apparentemente senza una particolare logica. Ho ripetuto infatti varie volte questa prova ed aumenta sempre di quantità diverse.

Per me è un overflow :smiley:

se per overflow intendi "In a computer, the condition that occurs when a calculation produces a result that is greater in magnitude than that which a given register or storage location can store or represent." sappi che ho provato a sostituire tutti gli int con unsigned long come mi avevi suggerito ma purtroppo il risultato non cambia di una virgola

Secondo me...
quando nell'ultimo while fai prevtime = time, imposti previtime al valore che aveva time L'ULTIMA VOLTA che è stato assegnato con time = millis(), che potrebbe risalire a diversi secondi prima a seconda di quanto ci metti a premere il riavvio.
Quindi hai prevtime = time risalente a qualche secondo prima, riparte il loop ed a time viene assegnato il valore di millis() che nel frattempo è andato avanti...

a questo non avevo pensato, ma ora ho verificato e confermo che è così: se stampo time dopo l'ultima lettura e di nuovo dopo aver premuto startBtn ottengo lo stesso valore.

Ho risolto aggiungendo un ulteriore time = millis() all'interno del while. Grazie a tutti :slight_smile:

Ovviamente lo hai messo subito prima di prevtime = time.

Ti suggerisco a questo punto di modificare il titolo mettendo [Risolto].

ultima domanda prima di chiudere

A questo punto vorrei randomizzare i valori di dly: ho aggiunto una variabile seed da usare come parametro per randomSeed e cambio il suo valore ogni volta che un pulsante viene premuto. Il problema è che non so come rendere random anche il valore di partenza di seed. Avevo letto da qualche parte un po' di tempo fa che effettuare un analogRead su un pin non utilizzato avrebbe generato valori casuali ma ho appurato che non funziona. Come posso rimediare?

hai provato questo?

se sto facendo uso del randomSeed mi sembra palese che stia facendo anche uso della funzione random nel programma, ma quello che ho chiesto adesso è qualcosa per generare un numero realmente random da assegnare al seed.
La funzione random() dà sempre lo stesso numero in output e a quel punto tanto vale che lo setti io il valore :stuck_out_tongue:

In caso non mi fossi spiegato bene, ecco il codice:

int led = 11;
int startBtn = 7;
int stopBtn = 4;

const int readnum = 5;
int readings[readnum];
int index = 0;
int total = 0;
int average = 0;

unsigned long dly;
unsigned long seed = random(3439);
unsigned long time;
unsigned long prevtime = 0;

void setup() {
  pinMode(led, OUTPUT);
  pinMode(startBtn, INPUT);
  pinMode(stopBtn, INPUT);
  Serial.begin(9600); 
  
  for (int thisRead = 0; thisRead < readnum; thisRead++) {
    readings[thisRead] = 0;
  }
}

void loop() {
  randomSeed(seed);
  time = millis();
  dly = random(1000, 6000);
  if (time-prevtime > dly) {
    digitalWrite(led, HIGH);
    if (digitalRead(stopBtn) == HIGH) {
      total = total-readings[index];
      readings[index] = time-prevtime-dly;
      total = total+readings[index];
      index++;
      average = total/readnum;
      
      digitalWrite(led, LOW);
      Serial.print("Reaction time: ");
      Serial.print(time-prevtime-dly);
      Serial.println(" ms");
      prevtime = time;
      seed += index;
    }
    if (index == readnum) {
      Serial.print("Average reaction time: ");
      Serial.print(average);
      Serial.println(" ms");
      Serial.println(" ");
      index++;
    }   
  }
  while (index > readnum) {
    if (digitalRead(startBtn) == HIGH) {
      time = millis();
      prevtime = time;
      index = 0;
      seed++;
    }
  }
}

quello che sto cercando è qualcosa per sostituire la funzione random in unsigned long seed = random(3439) dato che questa genera sempre lo stesso numero

Se è possibile generare un numero veramente random senza troppe complicazioni ovviamente lo userei direttamente per generare il valore del delay senza dover ricorrere al seed

Se è possibile generare un numero veramente random senza troppe complicazioni

no, non si può su questo chip avr 328, 1280 o 2560 ... su arduino DUE si

Capito, mi arrangerò come posso
grazie 8)

E' possibile generare un numero veramente random ma non usando le funzioni predefinite di Arduino. Esse si basano su un algoritmo che, all'avvio, genera sempre la stessa sequenza numerica.

Se vuoi un numero pseudo casuale, puoi fare ad esempio una lettura di un pin analogico flottante e prendere il valore letto come seme. Non è il massimo perché l'oscillazione sarà comunque sempre all'interno di un intervallo limitato.

Se vuoi un sistema più casuale, puoi usare la mia lib pRNG:
http://www.leonardomiliani.com/2013/nuova-libreria-prng/

Essa si basa sull'uso di un interrupt agganciato al watchdog dell'Atmega per generare una sequenza numerica molto random, sempre diversa ad ogni avvio dell'Arduino.

ho installato la libreria ma quando chiamo prng.getRndLong() ho sempre lo stesso numero ad ogni avvio, 4294967151 per l'esattezza, stessa cosa se sostituisco long con int(65535), o byte(255).

Ho cercato altre librerie ed ho installato TrueRandom che sembra funzionare a dovere

Non vorrei spararla grossa perché senza vedere il codice che hai scritto potrei sbagliarmi ma penso che ci sia sicuramente un problema nell'inizializzazione della libreria. Hai verificato con un esempio allegato come si usa? Hai fatto il begin() prima di estrarre i numeri?