Millis e tempo di esecuzione del loop

Salve ragazzi, ho uno sketch abbastanza lungo e mi sto scontrando con un fastidioso problema.
Premetto che non sto usando i delay ma per fare delle pause sto usando la funzione millis.
Devo muovere dei servomotori quindi mando impulsi ai servi ogni 15 millisecondi usando la funzione millis per non bloccare l’esecuzione del resto del codice.
Noto però che nel loop il resto del codice impiega più di 15 millisecondi per tornare all’inizio… questo comporta che i servi si muovono a scatti perchè la posizione dei servi si aggiorna più tardi del tempo impostato.
Lo sketch è giusto e se lo posto farei solo casino visto che ho suddiviso le varie funzioni; ho fatto molte prove per riuscire a capire che la cosa dipende dalla velocità di esecuzione del codice e non da qualche errore visto che le varie funzioni non interferiscono tra di loro e se eseguite singolarmente tutto fila liscio.
Riassumendo: come si potrebbe ottenere una pausa del codice ogni 15 millisecondi se ogni loop impiega più tempo?
Se metto dei for e faccio muovere lì dentro i servi il resto del codice si blocca fino a quando non si esce dal ciclo, se uso i delay idem…
Grazie a tutti

Ho pensato ad una soluzione, non so se può essere valida: ripetere più volte la funzione dei servi nello sketch in mezzo alle altre, una cosa così:

void loop()
{
funzione1();
servi();
funzione2();
servi();
funzione3();
servi();
funzione4();
servi();
.....
}

Secondo voi funzia?

Richiamare piú volte la funzione servo nella loop puó aiutare ma devi assicurarti che viene intervallato da altro codice che ha un tempo di esecuzione non troppo elevato. Puó anche aiutare controllare tutto il codice per farlo piú snello e piú velocemente eseguibile.

Ciao Uwe

Grazie Uwe, le altre funzioni le ho ridotte all'osso, fanno giusto l'essenziale.
Sto cercando nel reference se c'è una pagina che mostra i tempi di esecuzione delle varie funzioni tipo if, digitalWrite, analogRead, map che sono quelle che sto usando nel resto del codice ma non l'ho trovata, sapete se esiste tale documentazione?
Intanto faccio la prova delle funzioni ripetute, speriamo basta questo trucchetto

questo è esattamente il genere di problema per cui si usano gli interrupt sul timer. metti il codice della funzione nell’interrupt sul timer (se è sufficientemente “leggero”) e poi puoi fare quello che vuoi nel loop.
Che poi è quello che fa la Servo.

cmq il seganle alto/basso deve avere una precisione di 100>micro<sec per avere dei servi stabili… altro che milli!

Pelletta:
Riassumendo: come si potrebbe ottenere una pausa del codice ogni 15 millisecondi se ogni loop impiega più tempo?
Se metto dei for e faccio muovere lì dentro i servi il resto del codice si blocca fino a quando non si esce dal ciclo, se uso i delay idem...
Grazie a tutti

Sono tardivo. Mi potresti spiegare per bene cos'è che devi fare?
Vuoi fare un intervallo tra una funzione ed un'altra con 15 ms all'interno di un loop che dura più di 15 ms?

Allora Leo, vedo se riesco a spiegarmi meglio. Nel loop ho diverse funzioni tutte indipendenti l'una dall'altra; su nessuna di queste uso la funzione delay. C'è una funzione che aziona dei servomotori che in breve è così:

      void servi()
      {
        if(variabile == true)
        {
          if(millis() - timerServi > 15)
          {
            timerServi = millis();
            servo1.write(memoriaServi[2]); 
            servo2.write(memoriaServi[5]);
            servo3.write(memoriaServi[8]);
            servo4.write(memoriaServi[11]);
            SoftwareServo::refresh();
          }
        }
      }

Questa funzione l'ho inserita nel loop in mezzo alle altre funzioni indipendenti (fanno altre cose non legate ai servi) quindi:

void loop()
{
  funzione1();
  servi();
  funzione2();
  funzione3();
  funzione4();
  .....
}

Succede che per quando il codice mi ritorna alla funzione dei servi sono passati più di 15 millisecondi perciò i servi si muovono a scatti; supponendo ad esempio che tutte le altre istruzioni (if, digitalWrite, digitalRead, map, etc) inserite nelle altre funzioni indipendenti richiedono 60 millisecondi per far temninare un giro di loop mi ritrovo i servi che si muovono dopo 60 millisecondi anzichè 15 come impostato nella funzione servi().

uhm
quindi il problema di base è che l'ARRAY memoriaServi è aggiornato > 15ms.

ora, il codice riguardante l'aggiornamento di questo array, a quanto capisco, è indipendente dal resto del codice, o quasi (quli sono le cose che lo tengono "legato"?)

perchè questo aggiornameto deve avvenire ogni ciclo ppm, tanto vale modificare la servo che a ogni inizio ciclo esegue lei l'aggiornamento dell'array di valori, che tra l'altro al suo interno dovrebbe già possedere.

Se tutte le altre funzioni sono necessarie e non è possibile aumentarne la velocità, puoi sempre considerare la possibilità di comandare i servi con un secondo ATmega collegato col primo.

EDIT: Rilancio l'idea.
Potresti costruire una schield per la gestione autonoma dei servi che comunica tramite I2C o SPI con l'Arduino. Una cosa tipo questo (http://www.robot-italy.com/product_info.php?cPath=83_31&products_id=2400) ma con a bordo un Atmel.
Non so se possa bastare un tiny2313, un tiny84 o serva un atmega168/328.

L'array viene popolato nel setup, prendo i valori nella eeprom dell'atmega.
Nell'esempio che ho postato ho messo solo una delle tante eventuali posizioni che i servi devono assumere.
Quando devo far muovere i servi cambio l'indice dell'array (questo contiene diverse posizioni).

bhe a questo punto potresti semplicemente chiamare la funzione di aggiornamento oltre che cambiare l'indice

Potresti anche chiamare la funzione più volte. Ma per ottimizzare i tempi un if lo metterei fuori.

void loop()
{
  funzione1();
  if(variabile == true) servi();
  funzione2();
  funzione3();
  if(variabile == true) servi();  
  funzione4();
  .....
}

con

void servi()
      {
         if(millis() - timerServi > 15)
          {
            timerServi = millis();
            servo1.write(memoriaServi[2]); 
            servo2.write(memoriaServi[5]);
            servo3.write(memoriaServi[8]);
            servo4.write(memoriaServi[11]);
            SoftwareServo::refresh();
          }
       }

cosi eviti la chiamata alla funzione solo per controllare una variabile.

L’unica sensata soluzione è quella sugerita da lesto, cioè attaccare una funzione utente ad un timer, magari usando qualche classe, che ho già visto in rete ma che adesso non ricordo quale provai, anche perchè c’è più di una di queste classi.

A livello di codice basta fare quello che fa leo nella sua lib RTC, e fare eseguire la tua funzione ogni 1 millesimi di secondo. Questo già ti risolve il problema alla base e ti lascia libero di fare quello che vuoi nel loop. Però devi considerare la velocità di esecuzione della tua funzione e tenerne conto così: ogni 1 ms la funzione viene eseguita e dura più di 1 ms allora siamo a mare, quindi conviene impostare il timer per triggerare ogni 15 ms direttamente risparmiando la if time > 15.

Ciao.

Ho visto che la Servo utilizza il timer 1 sull’Atmega328. Seguendo l’idea di lesto potresti provare ad agganciare la tua routine Servi() sul timer 2 e farla eseguire ogni 15 ms, sperando che il rallentamento introdotto non disturbi troppo il lavoro del microcontrollore.

Prova così:
volatile unsigned long contatore=0;

void setup() {
  ....
  impostaTimer2();
  ...
}

void impostaTimer2() {
  TIMSK2 &= ~(1<<TOIE2); 
  TIMSK2 &= ~((1<<OCIE2A) | (1<<OCIE2B));
  ASSR &= ~(1<<AS2);
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20)); 
  TCCR2B &= ~(1<<WGM22);
  TCCR2B |= (1<<CS22);
  TCCR2B &= ~((1<<CS21) | (1<<CS20));
  TCNT2 = 6;
  TIMSK2 |= (1<<TOIE2);
}

ISR(TIMER2_OVF_vect) {
  TCNT2 = 6;
  contatore++;
  if (contatore > 15) { //sono passati 15 ms
    contatore = 0;
    servi();
  }
}

Imposta il timer 2 per andare in overflow 1 volta ogni 1ms. Ne conta 15. Al 15° chiama la tua routine.
Devi però provare.

Leo non mi picchiare ti prego... sto usando un 644PA :slight_smile:
Posso provarlo ugualmente quel codice che mi hai postato o va riadattato?
Comunque per precisare specifico che sto usando la libreria softwareServo, non la servo.
Intanto vado a vedere se usa il timer1

Mi dai il link da cui l'hai presa, che controllo anch'io?

PS:
il codice va bene anche per il 644P

Subito, l'ho presa nel playground qui

Ho ricordo che il 328 e 644 hanno gli stessi timer, mentre il 1284 ha un timer in più.

prova al limite deve dare errore di tipo non dichiarato prima volta usato qui:..

Ciao.

leo72:
PS:
il codice va bene anche per il 644P

@Pelletta:
a me pare che la SoftwareServo non usi interrupt. Prova inserendo quel codice nel tuo sketch. Ricordati solo che all'interno della routine Servi() devi togliere tutto quello che dipende da timer esterni tipo le funzioni delay e millis.
Secondo me va.

EDIT:
uhm... ad un'occhiata più approfondita ho visto che refresh() controlla il trascorrere del tempo leggendo il contatore del timer 0. Mi sa che ti si blocca ogni cosa, chiamando quel metodo da dentro un'altra ISR.

Come non detto :slight_smile:
Dai non fa niente, metto un ciclo for dentro la funzione servi e passa la paura...
Anche se il resto del codice si blocca per un paio di secondi non credo mi possa dare problemi insormontabili, ci starò attento.