[Risolto] Eliminare ritardo su analog read in un loop con delay

Buongiorno a tutti! Sono alle prime armi con Arduino, ho provato a riprodurre qualche semplice sketch da internet, e fin lì tutto è andato bene :slight_smile: sto "lavorando" ad un piccolo sketch che accende accende e spegne uno dopo l'altro 7 LED con una velocità (che sarebbe l'intervallo tra l'accensione di uno e lo spegnimento dello stesso) con un potenziometro e intanto scrive i valori ricevuti dal potenziometro (valori da 0 a 1023, percentuale e valore in millisecondi) su porta seriale. Il codice è questo:

float COM;   //è il valore da 0 a 1023
float MS;              //è il valore in millisecondi
int del = 50;          //è il delay 
void setup()
{
  Serial.begin(9600);



  pinMode(13, OUTPUT);      //i pin da accendere
  pinMode(12, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(6, OUTPUT);
  }



void loop(){

  
 {
   del=MS;                     //imposto il valore in ms come "del" 
   COM = analogRead(A0);
   MS = 400*COM/1023+20; // MS è in funzione di COM (valore del potenziometro)
   Serial.print(analogRead(A0));
   Serial.print("\t");
   Serial.print(COM*100/1023);
   Serial.print("%");
   Serial.print("\t");
   Serial.println(MS);                //lettura e scrittura su seriale dei valori rivecuti dal potenziometro che sarà uguale da adesso in poi dopo ogni digital write high e low
   
}
  

{
digitalWrite(13, HIGH);  
delay(del);              
digitalWrite(13, LOW);    

del=MS;
   COM = analogRead(A0);
   MS = 400*COM/1023+20;
   Serial.print(analogRead(A0));
   Serial.print("\t");
   Serial.print(COM*100/1023);
   Serial.print("%");
   Serial.print("\t");
   Serial.println(MS);

digitalWrite(12, HIGH);  
delay(del);           
digitalWrite(12, LOW);   

del=MS;
   COM = analogRead(A0);
   MS = 400*COM/1023+20;
   Serial.print(analogRead(A0));
   Serial.print("\t");
   Serial.print(COM*100/1023);
   Serial.print("%");
   Serial.print("\t");
   Serial.println(MS);

digitalWrite(11, HIGH);   
delay(del);            
digitalWrite(11, LOW);    

del=MS;
   COM = analogRead(A0);
   MS = 400*COM/1023+20;
   Serial.print(analogRead(A0));
   Serial.print("\t");
   Serial.print(COM*100/1023);
   Serial.print("%");
   Serial.print("\t");
   Serial.println(MS);

digitalWrite(10, HIGH); 
delay(del);             
digitalWrite(10, LOW);    

del=MS;
   COM = analogRead(A0);
   MS = 400*COM/1023+20;
   Serial.print(analogRead(A0));
   Serial.print("\t");
   Serial.print(COM*100/1023);
   Serial.print("%");
   Serial.print("\t");
   Serial.println(MS);

digitalWrite(9, HIGH); 
delay(del);             
digitalWrite(9, LOW);   

del=MS;
   COM = analogRead(A0);
   MS = 400*COM/1023+20;
   Serial.print(analogRead(A0));
   Serial.print("\t");
   Serial.print(COM*100/1023);
   Serial.print("%");
   Serial.print("\t");
   Serial.println(MS);

digitalWrite(8, HIGH); 
delay(del);             
digitalWrite(8, LOW);    

del=MS;
   COM = analogRead(A0);
   MS = 400*COM/1023+20;
   Serial.print(analogRead(A0));
   Serial.print("\t");
   Serial.print(COM*100/1023);
   Serial.print("%");
   Serial.print("\t");
   Serial.println(MS);

digitalWrite(7, HIGH);   
delay(del);             
digitalWrite(7, LOW);    

del=MS;
   COM = analogRead(A0);
   MS = 400*COM/1023+20;
   Serial.print(analogRead(A0));
   Serial.print("\t");
   Serial.print(COM*100/1023);
   Serial.print("%");
   Serial.print("\t");
   Serial.println(MS);

digitalWrite(6, HIGH);  
delay(del);             
digitalWrite(6, LOW);   

del=MS;
   COM = analogRead(A0);
   MS = 400*COM/1023+20;
   Serial.print(analogRead(A0));
   Serial.print("\t");
   Serial.print(COM*100/1023);
   Serial.print("%");
   Serial.print("\t");
   Serial.println(MS);

}
}

Come vedete ho ripetuto la lettura del potenziometro dopo ogni digital write HIGH e LOW del LED perchè se no prima di cambiare effettivamente la velocità di intermittenza dei led bisognava aspettare che il ciclo finisse. Ma comunque la lettura non è istantanea, c'è qualche ritardo tra la rotazione del potenziometro e il cambiamento di velocità, soprattutto quando il delay tra high e low è alto. Come faccio a "separare" la lettura del potenziometro dalla funzione di accendi spegni dei led in modo che sia indipendente e quindi aggiornata ogni millisecondo?

Spero di essere stato abbastanza chiaro nella spiegazione, se non lo sono stato ditemi pure :slight_smile:
Grazie in anticipo, Tommaso.

Questo è uno di quei casi in cui devi ripensare completamente il tuo programma usando millis, che è una funzione di Arduino che restituisce il tempo trascorso dall'accensione della scheda. In questo modo potrai strutturare il codice per attendere un certo intervallo di tempo e nel contempo eseguire altre operazioni.
Guardati l'esempio BlinkWithoutDelay allegato all'IDE e poi puoi leggerti anche questo mio articolo:
http://www.leonardomiliani.com/2013/programmiamo-i-compiti-con-millis/

Svelato l'arcano! Grazie, vedrò di capire bene come funziona!

Ho messo mano al codice, spero non ti dispiaccia. :roll_eyes: 8)

#define TRIMMER A0

void setup()
{
  Serial.begin(9600);
  pinMode(13, OUTPUT);      //i pin da accendere
  pinMode(12, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(6, OUTPUT);
}

void loop(){
  SetDelay (13);
  SetDelay (12);
  SetDelay (11);
  SetDelay (10);
  SetDelay (9);
  SetDelay (8);
  SetDelay (7);
  SetDelay (6);
}

void SetDelay (byte _pin)
{
  int Read = analogRead(TRIMMER);
  unsigned long MilliSec = (400*Read)/1023UL + 20;

  Serial.print(Read);
  Serial.print("\t");
  Serial.print(Read*100/1023);
  Serial.print("%");
  Serial.print("\t");
  Serial.println(MilliSec);

  digitalWrite(_pin, HIGH);  
  delay(MilliSec);              
  digitalWrite(_pin, LOW);    
}

Questa versione utilizza il #define per stabilire il pin a cui è collegato il potenziometro.
Ho creato una funzione che viene chiamata più volte con passaggio di parametro.
All'interno della funzione si usano variabili locali.

Buono studio :grin:
Dal cap. 11 in poi --> C (linguaggio): tutorial per imparare programmare | Guida HTML.it

Comunque questa versione è ancora affetta dai problemi di cui sopra.

impressionante paolo la tua riscrittura, credo sia importante iniziare a pensare di imparare a passare argomenti per le funzioni, io ragiono ancora solo con procedure void (almeno cosi' ho imparato che si chiamano le funzioni senza argomenti).

questo UL e' un errore di battitura "1023UL" ?

  1. Le void sono funzioni che non restituiscono nulla. Una funzione può essere con o o senza argomenti.
    Quindi:
    void miaFunz(void); //funzione senza argomenti che non restituisce nulla
    void miaFunz(byte); //funzione con 1 argomento che non restituisce nulla
    void miaFunz(byte, int, char*); //funzione con 3 argomenti che non restituisce nulla
    byte miaFunz(void); //funzione senza argomenti che restituisce un valore
    int miaFunz(byte); //funzione con 1 argomento che restituisce un valore.

  2. "UL" indica al compilatore che il numero deve essere interpretato come "U"nsigned "L"ong per cui memorizzato in 4 byte anche se esso starebbe in un tipo di dati inferiore.

grazie leo, il primo punto lo avevo capito dopo aver letto la sezione Funzioni della guida C lincata,

il secondo punto invece non sapevo esistesse questa opzione,
a che serve in questa occasione memorizzare 1023 in UL ? Read e' un int, dividerlo per un altro int non avrebbe dato lo stesso risultato ?

Diciamo che si usa la specifica del formato del numero quando si vuole essere certi che il compilatore non faccia un casting automatico da un tipo ad un altro.
Nel caso specifico non ho guardato se 1023UL viene poi usato per operazioni in cui sono implicate variabili di tipo int. Magari lo ha usato Paolo perché aveva pensato al codice in una forma differente e poi l'ha cambiata, lasciando però "UL", non so.

Serve per forzare la divisione, poiche 400*1023 (massimo valore di Read) supererebbe il limite massimo per un intero che è di 32K.
In effetti si potrebbe usare anche un unsigned int, ma il valore massimo sarebbe solo di 65535, quindi inferiore a 400K.
Credo si possa anche scrivere:

unsigned long MilliSec = (unsigned long)(400*Read)/1023 + 20;

Mi devo studiare meglio queste cosette. :wink:

Ma, limitandoci a questa singola riga di codice, tu dichiari anticipatamente che MilliSec è una UL quindi perché specificarlo anche dopo ?
Cioè questo non funziona ?
unsigned long MilliSec = 400*1023/1023 + 20;

Ad esser pignoli, 1023 credo sia sbagliato. Siccome si parla di una lettura analogica, essa è a 10 bit, per cui si hanno 1024 letture, da 0 a 1023. Quando si fanno le conversioni non si deve usare il valore massima ma il numero dei possibili step.

Quindi sarebbe più giusto usare

unsigned long MilliSec = (400*Read)/1024 + 20;

Ora, siccome la divisione non è implementata via HW nell'ALU integrata nel chip, questa operazione richiede tempo e risorse inutili, visto che non viene usata la parte decimale del risultato.
Visto che siamo in presenza di una potenza di 2 (2^10=1024), conviene anche per accelerare enormemente l'operazione, fare uno shift a destra di 10 posizioni, che equivale ad una divisione per 1024. Quindi il calcolo precedente diverrebbe:

unsigned long MilliSec = ((400*1023)>>10) + 20;

Soluzione spettacolare.
La implemento subito nell'Ardutester. :grin:

grande, ma se ho capito quindi sulla domanda specificas mi stai dando ragione ? non ha nessun utilizzo pratico UL su quella riga ?
Cioe' questa che e' la posizione di massimo valore di read funziona ?

unsigned long MilliSec = 400*1023/1024 + 20;

400*1023 fa 409200, un numero superiore al max consentito di un intero (di default il compilatore usa gli interi, quindi numeri con 2 byte). Non so se il casting è esplicito in questo caso, visto che si specifica che il risultato di tutto va messo in un unsigned long: potrebbe venir fatto subito dal compilatore, visto che la moltiplicazione ha una priorità maggiore della divisione ed è quindi eseguita per prima, anche senza parentesi, ottenendo quindi un long.

Però se si vuole essere pignoli, si potrebbero esplicitare tutti i numeri:

unsigned long risultato = 400UL*1023UL/1024UL+20UL;

:sweat_smile:

quindi e' solo per una questione di compilatore, ma personalmente mi aspetto che un compilatore non abbia rpoblemi con queste cose, alemno quello che usa arduino, perche' altrimenti i listati sarebbero pieni di UL.
Alla fine per avere la risposta basta stampare il numero risultante e vedere se funziona, provo un attimo.

diavolo, questo compilatore e' deludente, funziona solo con l'ipotesi di Leo, cioe' spargere tutti i numeri con UL

non funziona ne' la mia versione unsigned long MilliSec = 400*1023/1024 + 20;

Ne' la versione di PaoloP unsigned long MilliSec = (400*1023)/1023UL + 20;

le due versioni errate riportano 35 come risultato :fearful:
ma perche' il compilatore non va a guardare come abbiamo dichiarato MilliSec ? da li puo' accorgersi cosa vogliamo

Leo a questo punto mi spieghi per bene sta cosa, da dove esce proprio il 35, se hai voglia, Grazie :slight_smile:

void setup() 
{
  Serial.begin(115200);
}

void loop() 
{
 
  unsigned long MilliSec1 = 400*1023/1024 + 20; // risultato sbagliato 35
  unsigned long MilliSec2 = (400*1023)/1023UL + 20; // risultato sbagliato 35
  unsigned long MilliSec3 = 400UL*1023UL/1024UL + 20UL; 

  Serial.println(MilliSec1);
  Serial.println(MilliSec2);
  Serial.println(MilliSec3);
  delay(1000);      
}

leggendo il reference ho trovato il modo piu' elegante per farlo

 unsigned long MilliSec4 = (unsigned long) 400*1023/1024 + 20;

questo almeno spiega perche' non avevo mai visto quelle lettere dopo i numeri :slight_smile:
resta stupido il compilatore o c'e' un motivo pratico per non farglielo fare ?

35 viene fuori proprio da quello che ti avevo detto prima, e cioè che il compilatore fa il casting su int.
400*1023 = 409200

Ma 409.200 non ci sta in un int, sono solo 16 bit. Allora il risultato viene troncato, ed i bit che stanno in 2 byte danno 15984. 15984/1024=15.xxx a cui, sommando 20, ottieni 35.

yes, grazie, ho capito, perdiamo i primi tre MSB 110 :slight_smile:
i compilatori di tutti i linguaggi si comportano in questo modo, cioe' ragionano solo con INT ?

credo che l'efficienza del compilatore però dipenda molto anche dall'hardware
sulla arduino due per esempio int equivale a 4 byte e quindi credo che questi problemi non si dovrebbero presentare...
o per lo meno si presenteranno con numeri molto più grandi...
e secondo quanto ho letto in molte guide e libri ultimamente questa "rigidità" a volte ci permette di forzare il micro a lavorare in un certo modo restituendo un certo tipo di risultato cosa che se il compilatore fosse "intelligente" non protremmo fare...
mi sbaglio??