ArduinoDue Scheduler

Ciao a tutti,

ho inserito la libreria Scheduler - Arduino Reference nel mio progetto,
ma non ho capito come interagisce con il loop (main) e gli altri task.

ho notato che se eseguo un programma con un banale delay(xxx); contenuto in un task chiamato loop2(); arduino si blocca o si resetta...
inoltre non mi è chiaro cosa serva l'istruzione yield();

passa il controllo ad un altro task, ma il task corrente rimane in esecuzione o si interrompe?

la gestione dei task mi server per poter visualizzare in tempo reale su un display la posizione corrente, la posizione richiesta e la velocità di movimentazione di un motore stepper, e visto che il comando del motore per la gestione del BigEasyDriver avvieme mediante ciclo do{} non posso gestire il display nello stesso task in quanto esso è come se si "bloccasse" fino al termine del posizionamento del motore.

la struttura che vorrei realizzare è la seguente:

task loop() lanciato di default e contenente il parse gcode e la gestione del motore (questo task contiente il ciclo do{})
task display(); per la gestione del display e la selezione delle informazioni da visualizzare in base ai modi di funzionamento
task mode(); gestisce i modi di funzionamento e gli I/O verso led di segnalazione, pulsanti vari e selettori

spero che qualcuno mi possa spiegare meglio :wink:

grazie

saluti
kattivik76

Lo scheduler inserito nell'Arduino Due è di tipo cooperativo, significa che il SO non esegue nessun controllo sulla durata di un task ed il passaggio da un task all'altro è lasciato al programmatore.

Lo scheduler della Due si basa su un delay modificato, che freeza un task e passa al successivo, in modo che durante l'attesa del task il SO esegua altri task.
Se un task non deve attendere, allora deve usare il meccanismo yield() che informa lo scheduler di controllare gli altri task.

Guarda l'esempio MultiBlink:

Nel loop c'è un delay. In realtà quel delay è modificato affinché sia controllato lo scheduler per vedere se ci sono task da eseguire. Loop2 è un task, al cui interno ci sono 2 delay per alternare un led. Se delay funzionasse alla vecchia maniera, il codice rimarrebbe bloccato. Invece, mentre pensi che sia eseguito un delay, lo scheduler va al task successivo, loop3.
Questo viene eseguito di getto e siccome non deve aspettare nulla, usa yield per rendere il controllo allo scheduler, a differenze di loop e loop2 che passano inconsciamente il controllo proprio col delay.

grazie Leo,

quindi se non ho capito male:

mettiamo che io abbia 5 task,

loop(); //contiene dei delay per gli impulsi dello stepper
task02(); contiene dei delay per il display
task03(); nessun delay, solo stati logici
task04(); nessun delay, solo stati logici
task05(); contiene dei delay

in questo caso, l'istruzione yield(); va messa soltanto in task03(); e task04(); giusto?

grazie
saluti

kattivik76

Sì, dovrebbe andare così.

grazie Leo,

quindi sarebbe plausibile il fatto che mi si resetti o si blocchi arduinodue in caso di posizionamento errato di yield();?

stasera ci riprovo :slight_smile:

grazie :slight_smile:

kattivik76

kattivik76:
grazie Leo,

quindi sarebbe plausibile il fatto che mi si resetti o si blocchi arduinodue in caso di posizionamento errato di yield();?

Senza vedere il codice non so dirti.
Inoltre lo scheduler della Due, come hai visto, è ancora bollato come "sperimentale" per cui potrebbe essere anche buggato di suo.

stasera ci riprovo :slight_smile:

grazie :slight_smile:

kattivik76

Sono reperibile fino alle 2:00 :sweat_smile:

Eccomi dopo varie prove :slight_smile:

Da quello che ho potuto vedere lo scheduler funziona come il "vecchio" doevent() di visual basic :wink:

mio avviso il tutorial non è chiaro per niente!!!

Per restituire il controllo al task in un ciclo do{} l'istruzione yield() deve essere inserita al suo interno, così come per tutti gli altri task creati, il delay() non restituisce il controllo a nessun task :wink:

Grazie delle info :wink:

kattivik76:
mio avviso il tutorial non è chiaro per niente!!!

Concordo :wink:

Per restituire il controllo al task in un ciclo do{} l'istruzione yield() deve essere inserita al suo interno, così come per tutti gli altri task creati, il delay() non restituisce il controllo a nessun task :wink:

Non ho fatto tesk, avevo solo provato l'esempio Multiblink.

Grazie delle info :wink:

Prego :wink:

Eccomi di nuovo alla carica,

Utilizzo lo scheduler con buoni risultati, ma non è quello che cerco... Nel senso, ho un motore stepper ed un LCD I2C... Ed ecco il domandone che già immagini:

Come posso implementare un interrupt al mio progetto?

Ovviamente al momento se libero il codice che mi visualizza i dati sul display, il motore subisce una drastica riduzione degli impulsi... Inevitabile visto che il tutto funziona sul clock di sistema ed è subordinato a tempi di delay prestabiliti di 10microsecondi...

Grazie :wink:

Saluti kattivik76

Vorresti inserire la gestione del motore in interrupt, giusto?
In questo modo la renderesti indipendente dal pilotaggio del display e dal resto del codice.
Si può fare, dovresti usare un timer, impostarlo per un overflow ogni 10 us e dentro all'ISR mettere il codice per pilotare il motore.

Ma gestire il display con una FIFO o coda e la lettura di questa la metti sotto interrupt ogni 10ms, se non ci sono dati nella coda la routine di interrupt esce subito, diversamente butta tutto sul display e poi termina.

Però se i dati nella coda sono tanti i rischi di rimanere intrappolato troppo tempo nella ISR è alto e la conseguenza è identica a quello che lamenti ora, per ovviare potresti fare processare alla ISR, solo un numero di dati prestabilito, dopo di che anche se ci sono ancora dati nella coda esce e quelli rimanenti verranno processati la prossima volta.

La cosa si complica un po se devi anche gestire la tastiera per introdurre dati e usare il display come feedback per l'utente.

A rigor di logica non importa quanto tempo e fatica costa la gestione del display sotto coda, perchè è il task che meno risente dei rallentamenti, mentre il motore quello andrebbe sempre nel loop principale.
Però arduino 2 è molto diverso è più veloce e se l'applicazione non è altamente time critical c'è spazio per improvvisare una programmazione priva di infrastutture complesse da realizzare e gestire.

Ma il clock della 2 non dovrebbe essere sufficientemente alto da permettere l'uso di un RTOS quasi serio?

Ciao.

Difatti, per evitare latenze, consigliavo appunto di pilotare il motore nella ISR perché mettere un pin su high/low richiede un tempo molto più breve rispetto all'invio di un buffer su un display o la lettura di una tastierina, che si possono sempre fare nel loop.

Concordo comunque sul fatto che con una MCU a 32 bit con clock a 84 MHz si può usare un RTOS vero.

eccomi :slight_smile:

allora, il codice in questione è questo:

void dda_move(long micro_delay)
{
  long max_delta;
  long x_counter;

  // initialise Heartbeat
  //=====================
  InitialiseHeartBeat(true);

  // enable our steppers
  //====================
  enable_steppers();

  // figure out our deltas
  //======================
  max_delta = delta_steps.x;

  // init stuff
  //===========
  x_counter = -max_delta/2;

  // our step flags
  //===============
  x_can_step = false;

  if (micro_delay >= 16383)
    milli_delay = micro_delay / 1000;
  else
    milli_delay = 0;

  #ifdef DEBUG
    Serial.print("MaxDelta: ");
    Serial.print(max_delta, DEC);
    Serial.print(" MilliDelay: ");
    Serial.print(milli_delay, DEC);
    Serial.print(" MicroDelay: ");
    Serial.println(micro_delay, DEC);
  #endif
  
  // do our DDA line!
  do 
  {
    // see if user hit eStop
    //======================
    eSTOP();
    x_can_step = can_step(FC_HOME, FC_LIMIT, current_steps.x, target_steps.x, x_direction);

    if (x_can_step) 
    {
      x_counter += delta_steps.x;

      if (x_counter > 0) 
      {
        do_step(BIG_EASY_DRIVER_STEP);
        x_counter -= max_delta;

        if (x_direction)
          current_steps.x++;
        else
          current_steps.x--;
      }
    }
    current_units.x = to_units(unit_X_MM, float(current_steps.x));
    
    if (!x_can_step)
      return;
      
    // wait for next step
    //===================
    if (milli_delay > 0)
      delay(milli_delay);			
    else
      delayMicroseconds(micro_delay);
    Heartbeat();
  } while (x_can_step);
 	
  // set our current to target
  //==============================
  /*if (!free_run_end_exec){
    current_units.x = target_units.x;
    Serial.print("current_units.x= ");
    Serial.println(current_units.x);
  }
  else{
    target_units.x = current_units.x;
  }*/
    Serial.print("current_units.x= ");
    Serial.println(current_units.x);
    Serial.print("target_units.x= ");
    Serial.println(target_units.x);
    
  //current_units.x = target_units.x;
  calculate_deltas();

  // reset Heartbeat
  //================
  InitialiseHeartBeat(false);
}

sarebbe da gestire con interrupt o qualcosa di simile, al momento è gestito da un ciclo do{} che "blocca" il ciclo all'interno di esso..

mediante scheduler ho evitato che il codice mi si bloccasse sul serio all'interno del loop, ma non è sufficiente per poter gestire il display senza ritardi nel invio degli impulsi di step :slight_smile:
se riuscite a postarmi un esempio per una gestione ottimale di un interrupt (timer) con arduinodue vi sarei molto grato!!! :):):):):):):):):slight_smile:

saluti
kattivik76

Ma perché non deputi un task dello scheduler originale alla gestione del motore, lasciando la gestione del display al loop principale?
Se non ho capito male, la priorità deve averla il motore, altrimenti hai un avanzamento a singhiozzo, giusto?

I timer del SAM non dovrebbero essere diversi da quelli degli altri Atmega, ne imposti uno e poi ci agganci un interrupt.