Dubbio su programma molto lento (Arduino)

Ciao a tutti! :smiley:

Di recente ho provato a fare delle prove di prestazione e velocità programmando un Arduino Nano e un STM32f103c8t6 (Blue Pill).

Ho notato che la latenza è veramente elevata.

Codice fatto apposta per rallentare il codice:

void Program_Loop() {

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

    Out[0] = (In[0] or Out[0]) and !In[1];
    
  }
  

}

Il codice in se è molto più ampio ma non ci sono istruzioni nel loop apparte il codice che ho pubblicato...

Questo è il codice che fa lampeggiare il led 13 in maniera ciclica a 500ms per capire se ci sono rallentamenti:

void loop() {

  delay(1);

  Read_Input();

  Program_Loop();

  Write_Output();

  if (Counter_Led < Counter_Led_Value) {
    Counter_Led++;
  } else {
    Counter_Led = 0;
    Program_Debug();
    digitalWrite(Debug_Led, !digitalRead(Debug_Led));
  }

}

Tutto il ciclo si basa un delay di un millisecondo che fa incrementare i vari contatori ecc ecc

Però il led 13 che dovrebbe lampeggiare ogni 500ms lampeggia ogni 3000ms...

Quindi quel ciclo for da 10000 cicli rallenta tantissimo :frowning:
Pensavo fosse più veloce lo spostamento di dati nelle variabili...

è normale che sia così lento?

ciao

non so definire quanto "pesante" possa essere quel for()...penso si debba contare quanti "click" (si dice così?) genera e la velocità del micro in Hz... comunque vedo anche una write_output()...che fa?
ad ogni modo se lo fosse, e ti serve un programma più fuido, va trovato un altro modo di "scrivere" l'istruzione...è ovvio che una istruzione "pesante" possa avvicinarsi al "bloccante"...ma è perchè gli chiedi tu di fare così...si deve sempre passare per una ottimizzazione del codice.

spero di essere stato chiaro

Perdonami ma non ho capito lo scopo di questa cosa.

Intanto come sono definiti Out[] e In[]? Se poi questo è solo un benchmark per testare quanto impiega ad effettuare 10.000 cicli di quella operazione, perché non metti quel codice nel solo setup () e usi millis() prima e dopo, e da lì confronti?

Ad esempio:

// qui definisci Out[] e In[] che non so cosa siano... Esempio:
bool In[2] = { false, true };
bool Out[2] = { true, true };
...
void setup() {
  Serial.begin(115200);
  Serial.println("START");
  unsigned long t0 = millis();
  for (int i = 0; i < 10000; i++) {
    Out[0] = (In[0] or Out[0]) and !In[1];   
  }
  unsigned long t1 = millis();
  Serial.print("Tempo="); Serial.println(t1-t0);
}

void loop() {

}

millis sbaglia anche di 2 ms, per queste misure bisogna usare micros (che sbaglia di 4 µs).

Per lo stesso motivo non va messo delay(1) ma delayMicroseconds(1000)

Mi correggo, vedo che delay al suo interno usa micros per contare i millisecondi. Quindi delay(1) come precisione equivale a delayMicroseconds(1000).

Ti allego tutto il progetto:

Praticamente è un programma con funzioni complete come se fosse un PLC.
Dove tutto viene aggiornato e ragioni con logica "LADDER".

Ho usato questa "specie di struttura" per fare il flipper che ho casa: dove tutto è sempre in aggiornamento e le temporizzazioni devono essere dinamiche e non bloccanti...

File principale:

#include "Wire.h"
#define EEPROM_I2C_ADDRESS 0x50

//---------------------------------------------------------------------------------------
#define Num_In 6
#define Num_Out 8
#define Debug_Led 13
byte Pin_In[] = {2, 3, 4, 5, 6, 7};
byte Pin_Out[] = {9, 10, 11, 12, A0, A1, A2, A3};
bool In[Num_In], Out[Num_Out];

//---------------------------------------------------------------------------------------
#define Num_Mem 32
bool Mem[Num_Mem];

//---------------------------------------------------------------------------------------
#define Num_Timer 16
bool Timer_Out[Num_Timer];
int Timer_Data[Num_Timer];

//---------------------------------------------------------------------------------------
#define Num_Counter 16
bool Counter_Out[Num_Counter], Counter_Mem_Up[Num_Counter], Counter_Mem_Down[Num_Counter];
int Counter_Data[Num_Counter], Counter_Value[Num_Counter];

//---------------------------------------------------------------------------------------
#define Num_Edge 16
bool Edge_Out[Num_Edge], Edge_Mem[Num_Edge];

//---------------------------------------------------------------------------------------
#define Num_Data 16
int Data[Num_Data];

//---------------------------------------------------------------------------------------
#define Num_Compare 8
int Compare_A[Num_Compare], Compare_B[Num_Compare];
bool Compare_Out[Num_Compare];

//---------------------------------------------------------------------------------------
#define Num_Math 8
int Math_Data[Num_Math];

//---------------------------------------------------------------------------------------
#define Counter_Led_Value 500
int Counter_Led;

void setup() {

  delay(10);

  for (byte i = 0; i < Num_In; i++) {
    pinMode(Pin_In[i], INPUT);
  }

  for (byte i = 0; i < Num_Out; i++) {
    digitalWrite(Pin_Out[i], 0);
    pinMode(Pin_Out[i], OUTPUT);
  }

  pinMode(Debug_Led, OUTPUT);

  Wire.begin();

  Serial.begin(115200);

  delay(500);

  Program_Setup();

}

void loop() {

  delay(1);

  Read_Input();

  Program_Loop();

  Write_Output();

  if (Counter_Led < Counter_Led_Value) {
    Counter_Led++;
  } else {
    Counter_Led = 0;
    Program_Debug();
    digitalWrite(Debug_Led, !digitalRead(Debug_Led));
  }

}

File Hardware:

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Read_Input() {

  for (byte i = 0; i < Num_In; i++) {
    In[i] = digitalRead(Pin_In[i]);
  }
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Write_Output() {

  for (byte i = 0; i < Num_Out; i++) {
    digitalWrite(Pin_Out[i], Out[i]);
  }

}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
byte EEPROM_Read(int ADR) {

  byte rData = 0xFF;

  Wire.beginTransmission(EEPROM_I2C_ADDRESS);

  Wire.write((int)(ADR >> 8));    // MSB
  Wire.write((int)(ADR & 0xFF));  // LSB

  Wire.endTransmission();

  Wire.requestFrom(EEPROM_I2C_ADDRESS, 1);

  rData = Wire.read();

  return rData;
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void EEPROM_Write(byte DATA, unsigned int ADR) {

  Wire.beginTransmission(EEPROM_I2C_ADDRESS);

  Wire.write((int)(ADR >> 8));   // MSB
  Wire.write((int)(ADR & 0xFF)); // LSB

  Wire.write(DATA);

  Wire.endTransmission();

  delay(10);

}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void EEPROM_Write_Page(byte DATA[], byte OFFSET, byte LENGHT, unsigned int ADR) {

  Wire.beginTransmission(EEPROM_I2C_ADDRESS);

  Wire.write((int)(ADR >> 8));   // MSB
  Wire.write((int)(ADR & 0xFF)); // LSB

  for (byte i = 0; i < LENGHT; i++) {
    Wire.write(DATA[i + OFFSET]);
  }

  Wire.endTransmission();

  delay(10);
}

File delle funzioni:

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Timer(bool SET, int TIME, byte ADR) {

  if (SET == 1) {

    if (Timer_Data[ADR] < TIME) {
      Timer_Data[ADR]++;
    } else {
      Timer_Out[ADR] = 1;
    }

  } else {

    Timer_Out[ADR] = 0;
    Timer_Data[ADR] = 0;
  }
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Counter_Up(bool SET, byte ADR) {

  if (SET != Counter_Mem_Up[ADR]) {

    if (SET == 1) {

      Counter_Data[ADR]++;

      Counter_Process(ADR);

    }

    Counter_Mem_Up[ADR] = SET;
  }
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Counter_Down(bool SET, byte ADR) {

  if (SET != Counter_Mem_Down[ADR]) {

    if (SET == 1) {

      Counter_Data[ADR]--;

      Counter_Process(ADR);

    }

    Counter_Mem_Down[ADR] = SET;
  }
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Counter_Process(byte ADR) {

  if (Counter_Data[ADR] >= Counter_Value[ADR]) {
    Counter_Out[ADR] = 1;
  } else {
    Counter_Out[ADR] = 0;
  }

}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Counter_Reset(bool RESET, byte ADR) {

  if (RESET == 1) {
    Counter_Out[ADR] = 0;
    Counter_Data[ADR] = 0;
  }
}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Edge(bool IN_EDGE, byte FUC, byte ADR) {

  bool Rising_Edge = 0, Falling_Edge = 0;

  if (FUC == 1) {
    Rising_Edge = 1;
    Falling_Edge = 0;
  } else if (FUC == 2) {
    Rising_Edge = 0;
    Falling_Edge = 1;
  } else if (FUC == 3) {
    Rising_Edge = 1;
    Falling_Edge = 1;
  }

  Edge_Out[ADR] = (IN_EDGE and !Edge_Mem[ADR] and Rising_Edge) or (!IN_EDGE and Edge_Mem[ADR] and Falling_Edge);
  Edge_Mem[ADR] = IN_EDGE;

}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Compare(byte TYPE, byte ADR) {

  if (TYPE == 1) {

    if (Compare_A[ADR] == Compare_B[ADR]) {
      Compare_Out[ADR] = 1;
    } else {
      Compare_Out[ADR] = 0;
    }

  } else if (TYPE == 2) {

    if (Compare_A[ADR] > Compare_B[ADR]) {
      Compare_Out[ADR] = 1;
    } else {
      Compare_Out[ADR] = 0;
    }

  } else if (TYPE == 3) {

    if (Compare_A[ADR] < Compare_B[ADR]) {
      Compare_Out[ADR] = 1;
    } else {
      Compare_Out[ADR] = 0;
    }

  } else if (TYPE == 4) {

    if (Compare_A[ADR] >= Compare_B[ADR]) {
      Compare_Out[ADR] = 1;
    } else {
      Compare_Out[ADR] = 0;
    }

  } else if (TYPE == 5) {

    if (Compare_A[ADR] <= Compare_B[ADR]) {
      Compare_Out[ADR] = 1;
    } else {
      Compare_Out[ADR] = 0;
    }

  }

}

//-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void Math(int DATA_A, int DATA_B, byte TYPE, byte ADR) {

  if (TYPE == 1) {

    Math_Data[ADR] = DATA_A + DATA_B;

  } else if (TYPE == 2) {

    Math_Data[ADR] =  DATA_A - DATA_B;

  } else if (TYPE == 3) {

    Math_Data[ADR] =  DATA_A * DATA_B;

  } else if (TYPE == 4) {

    Math_Data[ADR] = DATA_A / DATA_B;

  }

}

File Main:

void Program_Setup() {

}

void Program_Loop() {

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

    Out[0] = (In[0] or Out[0]) and !In[1];
    
  }

}

void Program_Debug(){
  
}

Ciao andreaber, allora ho allegato tutto il progetto in risposta ad un altro utente.

Il sistema usato in maniera normale funziona bene, perché arrivare ad avere in maniera simulata 10000 istruzioni di codice è esagerato.

Ho provato a fare molti programmi che automatizzano macchine industriali con tale sistema e giustamente bisogna fare in modo che il tempo di ciclo sia abbastanza veloce da aggiornare tutto senza troppi ritardi...

Usato in maniera normale va benissimo, ma volevo testare la velocità della manipolazione dei dati e noto che 10000 cicli rallentano molto il led 13 che dovrebbe lampeggiare ogni 500ms... (circa)

Ho provato a calcolare il tempo della istruzione incriminata e dalle prove ci mette: 6ms
Se raddoppio il numero dei cicli (20000) va a 11ms...

Tutto questo è normale che sia così lento?

A me 10 mila cicli danno (in µs):

START
Tempo=9472

È meno di 1 µs ad espressione. Lento??? A confronto una singola digitalRead o Write (su Nano) dura 5 µs.

Come fa a darti il valore in micro secondi?

E poi 9472us sarebbero 9,472 millisecondi...

Io ho usato millis() e ottengo 6ms di tempo per il ciclo for da 10000 cicli :frowning:

Io ho provato il test quanto segue:

void Program_Setup() {

  Serial.println("START");

  unsigned long t0 = micros();

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

    Out[0] = (In[0] or Out[0]) and !In[1];

  }

  unsigned long t1 = micros();

  Serial.print("Tempo = ");
  Serial.println(t1 - t0);

}

E mi da:

START
Tempo = 5072 (microsecondi)

I conti sembrano tornare. Abbiamo un tempo di ciclo costante di 6ms moltiplicato 500 = 3000ms.

Quanto il tempo non è costante si calcola il tempo di ciclo medio, ad esempio quanti cicli di loop in 1 secondo.

Per le applicazioni crital time l'oscilloscopio e un pin toggle ci da delle indicazioni più precise, ma non ci dice quale parte del codice consuma più risorse. Più pin toggle ci possono dare ulteriori info su chi consuma più risorse. Ma in genere è bene fare anche i conti a manina, analizzando il disassemblato.

Considera che un display.display() si mangia 40ms, che sono una eternità in certi casi. Anche usando un RTOS lo switch tra task ha un costo fisso e sotto 1ms è difficile stare.

In mancanza di oscilloscopio su piattaforma arm (lo stm è arm) micros() ha risoluzione di 1us ecco che quello che hai usato è buon sistema per ricavare le metriche. Cioè puoi benissimo misurare quanto tempo cpu costa una chiamata a digitalRead, digitalWrite ecc, poi ovviamente moltiplichi per il numero di iterazioni del for.

Ciao.

Prova a dichiarare volatile gli array In e Out.


Poi, vedendo la funzione Timer, deduco che vuoi misurare il tempo che passa contando i cicli di loop, il cui tempo di esecuzione però dipende di volta in volta dalle istruzioni eseguite, e sicuramente non si può regolare con precisione con delay.
void setup() {
    ...
    t0 = micros();
}

void loop() {
    ...
    while (micros()-t0 < TCICLO) { }
    t0 += TCICLO;
}

In questo modo le istruzioni nel loop possono durare a caso da 0 a TCICLO µs. Anche sforare un po' saltuariamente, ma mediamente l'intero loop durerà sempre TCICLO (il while durerà ad ogni giro TCICLO meno il tempo consumato dalle altre istruzioni, e meno eventuali sforamenti del giro precedente).

Infatti!
La struttura di programma che ho progettato funziona così:

Semplicemente il ciclo macchina viene eseguito ogni 1 millisecondo più il tempo di lettura degli ingressi più il tempo per le istruzioni del programma utente e più per la scrittura delle uscite.

Ovviamente la funzione Timer() incrementa la variabile fino al valore prestabilito ad ogni ciclo macchina che è più o meno veloce in base alla pesantezza (in tempo) del programma.

Quello che intendi tu è fare una funzione che calcola il tempo del Timer() non con il tempo di ciclo ma con i microsecondi trascorsi?

Così a prescindere da quanto sia lento il ciclo lui calcola sempre lo stesso tempo? :smiley:

Non ho capito però come applicare tale metodo.

Cosa ci va dentro il while ?

Cioè non capsico come la funzione Timer() diventi indipendente nonostante il loop sia più o meno veloce... bho :frowning:

Ragazzi ho risolto in questo modo:

Ho fatto una gestione della funzione Timer() tramite interrupt dei timer interni del microcontrollore, precisamente il TIMER2 a 8bit (ad ogni suo overflow richiamo l'interrupt)

Ho trovato circa il tempo corretto per far si che il Timer[x] funziona con intervallo in millisecondi.

Il codice presenta 8 timer in array eseguiti in maniera costante senza ritardi a prescindere da quanto sia lento il void loop()... (ovviamente il tempo di ciclo dev'essere sempre tenuto gestibile e non troppo lento a priori):

#define Debug_Led 13

int Cycle;

#define Num_Timer 8
volatile bool Timer_In[Num_Timer], Timer_Out[Num_Timer];
volatile int Timer_Data[Num_Timer], Timer_Data_Value[Num_Timer];

bool Input[8], Output[8], Mem[8];

void setup() {

  delay(10);

  pinMode(Debug_Led, OUTPUT);

  Serial.begin(115200);

  TCCR2A = 0;
  TCCR2B = 0b00000001;

  TCNT2 = 0;

  TIMSK2 = 0b00000001;

  delay(100);

  //Settaggio del tempo di 500 millisecondi
  Timer_Data_Value[0] = 500;
  Timer_Data_Value[1] = 500;
  
}

ISR(TIMER2_OVF_vect) {

  //Ciclo d'interrupt per gli 8 Timer

  if (Cycle < 60) {
    Cycle++;
  } else {

    //digitalWrite(Debug_Led, !digitalRead(Debug_Led));

    for (byte i = 0; i < Num_Timer; i++) {

      if (Timer_In[i] == 1) {

        if (Timer_Data[i] < Timer_Data_Value[i]) {
          Timer_Data[i]++;
        } else {
          Timer_Out[i] = 1;
        }

      } else {
        Timer_Out[i] = 0;
        Timer_Data[i] = 0;
      }

    }

    Cycle = 0;
  }
  
}

void loop() {

  delay(1);

  //Metodo con due timer per il lampeggio del led (pin 13)
  Timer_In[0] = !Timer_Out[1];
  Timer_In[1] = Timer_Out[0];

  digitalWrite(Debug_Led, Timer_Out[0]);


  //Codice che rallenta il ciclo ma non i Timer
  for (int i = 0; i < 30000; i++) {

    Mem[0] = (Input[0] or Mem[0]) and !Output[2];
    
  }

}

MODIFICA:

MOLTO IMPORTANTE è rendere le variabili usate all'interno dell'ISR che vengono condivise con il ciclo "loop()" in tipo "volatile" che mi sono studiato che serve per dire al compilatore di non ottimizzare come vuole lui tale variabili e il fatto che possano cambiare in qualsiasi momento rispetto al di fuori del ciclo "loop()". :smiley:

Codice semplificato:

#define Debug_Led 13

#define Num_Timer 8
volatile bool Timer_In[Num_Timer], Timer_Out[Num_Timer];
volatile int Timer_Data[Num_Timer], Timer_Data_Value[Num_Timer];

bool Input[8], Output[8], Mem[8];

void setup() {

  delay(10);

  pinMode(Debug_Led, OUTPUT);

  Serial.begin(115200);

  TCCR2A = 0;
  TCCR2B = 0b00000100;

  TCNT2 = 0;

  TIMSK2 = 0b00000001;

  delay(100);

  //Settaggio del tempo di 500 millisecondi
  Timer_Data_Value[0] = 500;
  Timer_Data_Value[1] = 500;

}

ISR(TIMER2_OVF_vect) {

  //Ciclo d'interrupt per gli 8 Timer
  
  //digitalWrite(Debug_Led, !digitalRead(Debug_Led));

  for (byte i = 0; i < Num_Timer; i++) {

    if (Timer_In[i] == 1) {

      if (Timer_Data[i] < Timer_Data_Value[i]) {
        Timer_Data[i]++;
      } else {
        Timer_Out[i] = 1;
      }

    } else {
      Timer_Out[i] = 0;
      Timer_Data[i] = 0;
    }

  }
  
}

void loop() {

  delay(1);

  //Metodo con due timer per il lampeggio del led (pin 13)
  Timer_In[0] = !Timer_Out[1];
  Timer_In[1] = Timer_Out[0];

  digitalWrite(Debug_Led, Timer_Out[0]);


  //Codice che rallenta il ciclo ma non i Timer
  for (int i = 0; i < 30000; i++) {

    Mem[0] = (Input[0] or Mem[0]) and !Output[2];

  }

}

Cosa ci va dentro il while ?

Niente

nonostante il loop sia più o meno veloce

Il loop dura sempre TCICLO, a quello serve il while: ad aspettare che scada il tempo di loop prestabilito, poi la bandierina t0 viene spostata avanti di un altro TCICLO.

La versione asincrona tramite interrupt introduce un grave problema di "coerenza". All'interno di un singolo ciclo elaborativo, un timer non deve poter essere scaduto in alcuni rung e non scaduto in altri, e usando gli interrupt questo può sicuramente accadere.

Se serve un timer slegato dal tempo di esecuzione delle istruzioni, basta e avanza usare millis.

Il tuo timer potrebbe essere:

void Timer(bool SET, unsigned long TIME, byte ADR) {

  if (SET) {

    if (millis()-Timer_Data[ADR] >= TIME) {
      Timer_Out[ADR] = 1;
    }

  } else {

    Timer_Out[ADR] = 0;
    Timer_Data[ADR] = millis();
  }
}

Il mio è:

class TON_timer {
    bool     s = 0;
    uint32_t t;
public:
    bool q = 0;
    void run(bool in, uint32_t pt) {
        if (!s && in) {
            t = millis();
            s = 1;
        }
        else if (s && !in)
            s = q = 0;
        else if (s && !q && (millis()-t >= pt))
            q = 1;
    }
};

...che tiene conto anche della possibilità che l'ingresso sia alto già al primo ciclo.

Intendi che il metodo ad interrupt ha il problema che non è preciso poiché, in base a quanti timer dinamici sono attivi o disattivi, il peso del codice all'interno degli interrupt può variare e quindi rendere il tempo dell'interrupt non sempre uguale?

Cioè non ho capito bene... perché se io gestisco l'interrupt ogni millisecondo, e il codice all'interno varia di peso in tempo minore rispetto a quando chiamo la funzione ISR() non dovrebbe essere sbagliato...

per ora ho fatto varie prove e con tanto di oscilloscopio e non perdono un colpo, sono precisi precisi :smiley:

Non capisco cosa intendi che i timer sono scaduti oppure no... in che senso "coerenza" ?

Cioè a prescindere da com'è sono gli ingressi dei timer i loro relativi contatori non dovrebbe influenzare la loro funzione "tempo" :frowning:

Poi il metodo col mills() non va bene per applicazioni che stanno sempre accese...
Quando millis() si resetta e un Timer è attivo succede che millis() sarà minore di Timer_Data[ADR] e fa pasticci... :frowning:

oltre al fatto che finché lo metto dentro il void loop() il codice del Timer() verrà eseguito a tempo di loop quindi se il loop si rallenta si rallenta anche il numero di volte che la funzione viene eseguita... con il sistema ad interrupt il problema non si pone... ;(

Mi è già successo di utilizzare mills() con il solito programmino dove calcolo la differenza con una variabile "long" ma infatti, come pensavo, dopo 50 giorni non andava più :frowning:

in che senso che la tua funzione TON tiene conto se l'ingresso è abilitato al primo ciclo?
non mi serve a nulla sapere se attivo o no l'ingresso... bisogna tenere in considerazione che tali timer vengono utilizzati come in un sistema a PLC dove possono essere anche attivi all'avvio del sistema che poi si attivano per fare cose... non mi serve captare un fronte di salita o di discesa nel contesto Timer()... (sempre se ho capito cosa intendevi :frowning: )

Se Timer_Data[ADR] è int, per forza fa pasticci!

ma che centra è scontato che se usi millis() devi adeguare anche la variabile ad "unsigned long" :smiley:
fa pasticci comunque :smiley:

Secondo il mio parere fai bene a farti tutte queste domande a cui poco per volta troverai risposta. Ci sono buone probabilità che dopo tutte le risposte tu arrivi alle stesse nostre conclusioni, ma non è detto, magari trovi una tua soluzione che per noi è una novità (speriamo).

Qui c'è uno sketch su wokwi che dovresti provare sulla tua board, oltre che sul simulatore.

#define HOOO (300UL - 4294967096UL)

Ho chiamato HOOO come una esclamazione di stupore poiché 300 - 4294967096 = 500.

Bene lo sketch dovrebbe aiutarti a sperimentare con millis() e con il simulatore eviti anche di consumare la flash della tua MCU.

Ciao.