Come strutturare un programma? (stroboscopio a LED da 100W) - I timer di Arduino

Ciao a tutti
Questa è una domanda che avrei dovuto fare molto tempo fa ma, d'altra parte, nemmeno è mai stato affrontato bene l'argomento per farsi venire il dubbio... :slight_smile:

Vorrei realizzare uno stroboscopio con un leddone da 100W che alimenterò con un alimentatore per switch di rete da 48V 3A che ho trovato a 22€ da Amazon, un regolatore buck V/I e un mosfet on/off. Non uso un alimentatore 230V per LED perché credo che non gradiscano un carico intermittente e la tensione possa salire molto quando è senza carico, mentre il regolatore buck è limitato in tensione e corrente.

Concettualmente è molto semplice e con valori fissi già funziona, ma devo aggiungere le regolazioni di periodo e shutter (durata del lampo, impostando le frazioni di secondo come i tempi dell'otturatore delle macchine fotografiche).

Solitamente, quando premo il pulsante dell'encoder l'esecuzione del programma entra nel menu e si instrada verso le diverse impostazioni, bloccando tutto fino all'uscita dalle impostazioni stesse. Questo, di solito, l'ho ritenuto accettabile; quando è stato necessario che l'esecuzione del programma principale continuasse, in tutte le impostazioni ho richiamato continuamente la funzione che fa ciò che serve.

Se invece questa volta, approfittando della semplicità del programma (almeno finché non aggiungerò funzioni fino a occupare tutta la memoria disponibile, come il sincronismo esterno per cominciare... :slight_smile: ), volessi impostarlo in maniera non bloccante, come dovrei fare?
L'idea è questa:

  • Premo il pulsante:
  • Mi fa regolare il periodo fra 2,0 secondi e 10ms (100Hz) con passi pari alla seconda delle due cifre significative (es.: 10-11-12...100-110-120ms, 1.0, 1.1, 1,2s...) o qualcosa di simile. Un potenziometro su un ingresso analogico, quando attivato, permetterà di fare una regolazione fine.
  • Premo di nuovo il pulsante:
  • Mi fa regolare la durata del lampo: 1/10s, ..., 1/100s, 1/125, 1/160, ..., 1/1000, 1/1250, ...).
  • Premo di nuovo il pulsante:
  • Esce dalle impostazioni.

Per ora ho cominciato così:

const char *percorso=__FILE__; // Dalla macro __FILE__  prende il percorso del file in uso. Questa parte  di programma,
      char ver[10];            // però, si trova  nel file/cartella  dell'IDE c_setup, in cui non è scritta la versione
                               // del programma. La versione è scritta nel file principale, che ha lo stesso nome della
                               // cartella che contiene tutti i file del programma. Questa  riga, quindi, non può stare
                               // nel setup.
#include <LiquidCrystal.h>
LiquidCrystal lcd(8,9,10,11,12,13); // RS,EN,D4,D5,D6,D7
uint16_t frequenza=100; // Frequenza in Hz. Va da 2 a 1000.
uint16_t shutter=1000; // Ton=1/shutter; p.es.: 1/1000s=1ms=1000us. Va da 100 a 10.000.
         // Shutter deve essere maggiore  di 10*frequenza: per esempio, con f=10Hz (periodo=1/10s) shutter  deve essere 
         // maggiore di 100 (1/100s) per garantire che il duty cycle non superi il 10% e il LED non si surriscaldi.
uint32_t periodo; // Periodo in us.
uint32_t t_periodo; // Per prendere il tempo con micros() (con millis c'era jitter, sia perchè va a passi di 1ms,
                    // sia perché è interrotto dagli interrupt, mentre micros() non lo è).
uint16_t Ton; // Tempo di accensione in us (1/shutter, ma in us).

int E; // Risultato della routine encoder(): 1, -1, 0.
byte S; // Lettura dei due valori dell'encoder.
byte So;// Lettura precedente dell'encoder.
int X; // Usato in encoder() per evitare letture multiple.

uint32_t t_lettura_pulsante=0; // millis() per la lettura del pulsante ogni 200ms.
uint8_t stato=0;
// stato:
#define norm 0
#define setTon 1
#define setshutter 2


void setup()
{
char *ver_ext=strrchr(percorso,'v'); // Va a cercare l'ultima 'v' nel percorso.
byte n_car=strlen(ver_ext)-4; // Calcola la lunghezza escludendo .ino.
strncpy(ver, ver_ext, n_car); // Copia i primi n_car caratteri da ver_ext a ver.
ver[n_car]='\0'; // Mette il terminatore in fondo.

pinMode (4,INPUT_PULLUP); // Encoder: A.
pinMode (5,INPUT_PULLUP); // Encoder: B.
pinMode (6,INPUT_PULLUP); // Encoder: Pulsante.
pinMode (7,OUTPUT);
lcd.begin(16,2);

periodo=1000000.0/frequenza+0.5; // In us.
if(shutter>=10*frequenza)
  {
  Ton=1000000.0/shutter+0.5; // Ton in us; +0.5 è per l'arrotondamento.
  }
lcd.print("  Stroboscopio"); 
lcd.setCursor(6, 1); lcd.print(ver);
delay(2000);
lcd.clear();
}


void loop()
{
// ----- QUANDO E' TRASCORSO IL PERIODO FA IL LAMPO -----
if(micros()-t_periodo >= periodo)
  {
  t_periodo=micros();
  lampo();
  }

// ----- OGNI 200ms CONTROLLA IL PULSANTE; SE E' PREMUTO, INCREMENTA stato. -----
if(millis()-t_lett_pulsante >= 200) // Ogni 200ms legge il pulsante.
  {
  t_lett_pulsante=millis();
  if(!(PIND||0b01000000)) // Se il pulsante dell'encoder su PD6 è premuto (livello basso)
    {
    stato+=1;
    if(stato==3) stato=norm;
    }
  }
}


void lampo()
{
//   I/O 76543210
PORTD|=0b10000000;
delayMicroseconds(Ton);
PORTD&=0b01111111;
}


void encoder()
{
//              PD 76543210
// S=3-      PIND&B00000011 ; // Uso PD1 e PD0, perciò non devo scorrere a destra.
// S=3-( PIND>>3)&B00000011 ; // Uso PD4 e PD3, quindi scorro a destra di 3 bit per farli diventare 1 e 0. 
// S=3-((PIND>>2)&B00000011); // Uso PD3 e PD2, quindi scorro a destra di 2 bit per farli diventare 1 e 0. 
// S=3-((PINC>>1)&B00000011); // Uso PC2 e PC1 (A2 e A1), quindi scorro a destra di 1 bit per farli diventare 1 e 0.
   S=3-((PIND>>4)&B00000011); // Uso PD5 e PD4, quindi scorro a destra di 4 bit per farli diventare 1 e 0.
// Il valore binario di S rappresenta A e B. Il centrale dell'encoder è a massa, quindi faccio complemento a 3 (11).  
S^=S>>1; // Faccio XOR (^) fra S (gray) e il suo bit 1, facendolo scorrere a Dx: AB XOR A,
         // ottenendo un binario che per ogni scatto fa 0-1-2-3-0 oppure 0-3-2-1-0.
E=0;
if (S!=So && S==0) X=0;
if (X==0)
  {
  if (So==1&&S==2)
    {E=1; X=1;
    // Bip();
    }
  if (So==3&&S==2)
    {E=-1; X=1;
    // Bip();
    }
  if (S==0)
    {E=0; X=0;}
  So=S;  
  }
}


/* 
    
Fis.I/O   
 2   0 PD0
 3   1 PD1
 4   2 PD2 
 5   3 PD3 
 6   4 PD4 Encoder: A (con INPUT_PULLUP).
11   5 PD5 Encoder: B (con INPUT_PULLUP).
12   6 PD6 Encoder: Pulsante (con INPUT_PULLUP).
13   7 PD7 Uscita verso il gate dell'IRLZ44N via 100 Ohm.
14   8 PB0 
15   9 PB1 
16  10 PB2 
17  11 PB3 
18  12 PB4 
19  13 PB5 
         ---------------
Fis.I/OPorta
23  14 PC0 A0 Pulsante dell'encoder a GND con 100nF in parallelo.
24  15 PC1 A1 Encoder A a GND.
25  16 PC2 A2 Encoder B a GND.
26  17 PC3 A3 
27  18 PC4 A4 SDA 
28  19 PC5 A5 SCL 
29  20     A6 (solo in Arduino Nano)
          
Fis
 9  In  XTAL 16MHz; compensatore a GND. 
10  Out XTAL 16MHz; 22pF a GND.
7,20: Vcc
8,22: GND

EEPROM(1):

*/

Nelle impostazioni (per ora nel setup) è presente una verifica affinchè il duty cycle non superi il 10%, permettendo un modesto sistema di raffreddmento del LED.

Nota: qui ho riunito tutte le schede dell'IDE (cioè i file) di cui è composto ill programma.

Grazie
Gianluca

    if (micros() - t_periodo >= periodo)
    {
        t_periodo += periodo;
        lampo();
    }

E togliere l'aggiornamento di 't_periodo' da 'lampo'.
Altrimenti il periodo sarà falsato in modo random dal tempo di esecuzione delle altre istruzioni.

Temo però per l'LCD: la scrittura porta via tempo e questo influirà sulla periodicità strobo. Come fa sulla mia sveglia displayLED multiplex + LCD quando aggiorna l'LCD (in fondo il display multiplexato ogni 2 ms è come uno strobo molto veloce).

Sì, hai ragione! E' rimasto da quando avevo messo tutto nella funzione. Poi ho tolto la verifica del tempo perché non mi piaceva che a ogni giro di loop dovesse saltare inutilmente alla funzione e riuscire, salvo una volta ogni 200ms.
L'ho corretto anche nel codice che ho pubblicato.

Temo però per l'LCD: la scrittura porta via tempo e questo influirà sulla periodicità strobo.

Beh... Meglio rischiare che càpiti che fermarlo del tutto... Comunque aggiornerò solo il numero durante la rotazione dell'encoder, quindi potrà solo capitare che oltre una certa velocità perda qualche lampo durante la rotazione dell'encoder. Aggiorno sempre il display quando E!=0, cioè quando è stato mosso l'encoder.

Sembra che nessuno sappia rispondere... :frowning:
Eppure mi sembrava una cosa basilare...

Beh... Sto aspettando i connettori a pettine e le colonnine con vite per montare il display sulla millefori; andrò avanti con il programma bloccante, chiamando la funzione (in cui dovrò rimettere l'if) in ogni punto di blocco e poi, quando arriverà il materiale, monterò il microcontrollore e l'encoder e farò verifiche e ritocchi.

L'unica cosa che mi viene in mente, ma probabilmente troppo ovvia perchè tu non la abbia già valutata e scartata per qualche motivo, è usare un timer per la generazione dei lampi, una volta impostato prosegue per conto suo indipendentemente da quello che fai nel programma.

Quale? Con gli STM32 ad esempio grazie alla libreria Hardware Timer è molto facile gestire il tutto.

Ciao, Ale.

Ciao, Ale
In realtà non l'ho neanche considerata, perché con i timer non ci ho mai combattuto... :slight_smile:

Anch'io non ho mai avuto grande simpatia, ma ti ripeto, dipende da che hardware usi e può diventare molto semplice, es.:

HardwareTimer *MyTim = new HardwareTimer(TIM3);  // TIM3 is MCU hardware peripheral instance
MyTim->setPWM(channel, pin, 5, 10); // 5 Hertz, 10% dutycycle
MyTim->resume();

Ciao, Ale.

Ormai se ne trovano di ottime per tutte le famiglie di MCU ... librerie arduino, categoria "Timing" ... ce ne è un'infinità :slight_smile:

Guglielmo

Buongiorno, Guglielmo

Userò il solito ATMega328p, che entra comodamente sotto all'LCD montato su colonnine da 12mm.
Mi puoi spiegare la giusta filosofia generale per scrivere un programma non bloccante? Sì, so usare millis() per le temporizzazioni, ma mi chiedo come si possa, per esempio, gestire un menu... Ho cercato in rete, ma non ho trovato nulla in proposito. Non mi dire "con una libreria", perché allora posso anche comprare uno stroboscopio già fatto! Credo che potrei permettermelo... :smile:

In questo caso si può usare un timer (posto che si riesca a impostarlo con precisione, perché vorrei mettere una regolazione fine, per esempio +/-10%, con un potenziometro su un ingresso "analogico"), ma più in generale?

Se non devi bloccare il programma, è ovvio che, ogni cosa deve essere gestita a temporizzazioni con millis() ed a flag che metti a vero/falso e poi, nel corso del programma, controlli per eseguire o meno un qualche cosa ...

Ora, il menu ... perché dovrebbe essere bloccante? Usi una variabile che ti dice lo "stato" del menu, continui il tuo programma che include la continua lettura dei bottoni di gestione del menu, e, a seconda delle scelte, cambi lo "stato" ed agisci di consegunenza, senza dover bloccare nulla.

Quando devi generare dei segnali su un pin, la cosa migliore, se possibile, è usare delle temporizzazioni hardware che sono indipendenti da quello che sta facendo il tuo programma ... un timer è esattamente questo, una hardware indipendente, la cui precisione dipende dalla precisione del clock, che puoi programmare per generare un'onda quadra di una certa frequenza ed un certo duty-cycle su dei pin prefissati (sono quelli collegati all'hardware del timer scelto).

Come la solito ricorda che, se usi ATmega328P (quindi equivalmente ad usare un Arduino UNO) hai le seguenti possibilità:

Timer0 (8 bit)
Usato da millis() e delay() e per il PWM sui pin 5 e 6
fast hardware pwm
(default 976.5625 Hz)
Pin 5 è anche usato per il “pulse counting”
Pin 8 è usato per il “input capture”

Timer1 (16 bit)
Usato dalla libreria Servo e per il PWM sui pin 9 e 10. Libreria e PWM mutuamente esclusivi.
8-bit phase correct pwm mode
(default 488.28125 Hz)

Timer2 (8 bit)
Usato per il PWM sui pin 3 e 11
8-bit phase correct pwm mode
(default 488.28125 Hz)

Questo vecchio documento di leo72 (ex Admin del forum) magari ti può essere utile ... Timer.pdf (747.4 KB)

Guglielmo

Ora la domanda è più chiara e circoscritta. Torno alla mia sveglia, sul cui LCD normalmente appaiono le informazioni principali (ora / data / sveglie o timer impostati). Questa "pagina principale" è uno stato di funzionamento tra i diversi stati possibili (visualizza menu, regola ora, regola timer ecc). Nel loop semplicemente chiamo una funzione per leggere l'encoder, e una per gestire la macchina a con i diversi stati:

void appLoop(void)
{
    readEncoder();
    gestBacklight();
    appMachine();
    ......altre cose
}

Nella fase "maschera principale" in particolare c'è una cattura evento click sull'encoder:

if (onClic) { fase = MENU;  beepClic();  break; }

Riporto qui l'intera fase di gestione menu:

      case MENU:  // MENU
            if (primo) { s = 0;  printMenu(s); }

            if (onLpress) { beepLpress();  fase = PRINCIPALE; }
            
            if (onClic) { beepClic(); }

            if      (onClic &&  0 == s) { fase = REGORA;      break; }
            else if (onClic &&  1 == s) { fase = REGDATA;     break; }
            else if (onClic &&  2 == s) { fase = REGSVEGLIA;  break; }
            else if (onClic &&  3 == s) { fase = REGSVEGLIA2; break; }
            else if (onClic &&  4 == s) { fase = REGLUM;      break; }
            else if (onClic &&  5 == s) { fase = REGTIMER;    break; }
            else if (onClic &&  6 == s) { fase = VISTIMER;    break; }

            if (onUp  ||  onDn) { t = gmillis; }

            if      (onUp  &&  s < 6) { s += 1;  beepUp();  printMenu(s); }
            else if (onDn  &&  s > 0) { s -= 1;  beepDn();  printMenu(s); }

            if (elapsed > 20000) { fase = PRINCIPALE; }
        break;

Semplicemente ricevo i comandi nelle variabili 'onClic' 'onUp' 'onDn' generati dalle azioni sull'encoder letto nella funzione 'readEncoder'.

Grazie a entrambi.
Penso che per fare un menu così dovrei iniziare con qualche esercizio semplice. Credo che per ora proverò con i timer.
Vedo che Maurotec sta scrivendo... Dimmi! :slight_smile:

Confermo, non si trova nulla in rete. Il problema "non bloccante" è una nostra definizione. Io ho trovato qualcosa sviluppando applicazioni sul PC, visto che un processo gira su un solo thread, quando l'applicazione è impegnata ad esempio nel leggere da file xml e creare gli oggetti l'interfaccia grafica non risponde più, cioè l'aggiornamento risulta rallentato perché sto monopolizzando l'uso delle cpu senza lasciare il giusto tempo per elaborare le altre istruzioni. La cosa si risolve creando un thread parallelo che legge dal disco i file xml li decodifica e aggiorna gli ogetti, grazie alla gestione dei thread la GUI torna reattiva e il contenuto dei file decodificato viene mostrato nei widget. Quindi il problema è conosciuto ed è generico e non specifico di arduino o delle applicazioni embedded. Potenzialmente tutto il codice è "bloccante", cioè se un ciclo for si conclude in 10 ms e il resto del codice non considera bloccante i 10ms impiegati dal for tutto fila liscio. Il for o while è un ciclo innestato nel ciclo principale, cioè dentro la funzione loop(). Quando il ciclo innestato diventa un problema si deve trovare il modo di sfruttare il ciclo principale.

Io ancora non sono stato capace di creare un pattern non bloccante e descriverlo per guidare un programmatore arduino.

Ti hanno suggerito di usare i timer hardware e quindi mi dedicherei a questi in modo da non usare micros() ecc. Poi per il menu come vedi @Claudio_FF lo ha fatto così:

if (primo) { s = 0;  printMenu(s); }

Anche non vedendo il resto del codice si intuisce che se la variabile primo è vera: azzera s e visualizza il menu passandogli il valore s. Quindi printMenu(s) fa qualcosa in relazione all'argomento s. Osservando il codice di claudio dovresti notare che le azioni sono tutte condizionate. Per finire nel suo caso ha una schermata principale a cui ritorna in automatico

e di ciò sembra si occupi questa condizione, se prima che elapsed superi 20000 c'è del codice da eseguire alla fine della esecuzione aggiorna il conteggio in modo che se non tocchi encoder e pulsanti in genere se non interagisci con l'applicazione il timer scade e torna alla schermata principale.

Ci sono solo 3 chiamate alla stessa funzione printMenu(s), se durante la scrittura del codice ti dovessi ritrovare a chiamare 5 o più volte le stessa funzione a tiro corto. allora forse sei sulla strada sbagliata e c'è un altro modo per fare ciò che vuoi. Oppure giudichi che 5 chiamate non sono un problema e non ne introdurrai in seguito a tiro corto.

Mi sono dilungato troppo e in fondo non ho scritto nulla di utile, termino dicendo che c'è una certa predisposizione naturale a scrivere codice bloccante, serve esercizio per invertire la rotta.

Ciao.

Questa è la differenza che c'è tra tempo di ciclo e tempo di processo. Se il tempo di ciclo è abbastanza piccolo da essere irrilevante per il tempo di processo, non ci sono problemi.

Io ancora non sono stato capace di creare un pattern non bloccante e descriverlo per guidare un programmatore arduino.

Più che cercare di dire di scomporre sempre il problema in termini di: se sono in questa situazione e avviene questo faccio questo, non saprei.

Anche non vedendo il resto del codice si intuisce che se la variabile primo è vera: azzera s e visualizza il menu passandogli il valore s. Quindi printMenu(s) fa qualcosa in relazione all'argomento s.

E per completezza la variabile 'primo' è calcolata appena prima della macchina a stati, per indicare allo stato da eseguire se siamo alla sua prima esecuzione (utile ad esempio per pulire il display e visualizzare le nuove informazioni relative alla fase corrente). La variabile 'elapsed' indica invece quanti ms sono trascorsi dall'attivazione dello stato (a meno che la variabile temporale 't' non venga aggiornata all'interno dello stato stesso).

    bool primo = (fase != fasePrec);  // primo ==1 se fase cambiata
    if (primo)
    {
        fasePrec = fase;
        t = gmillis; 
    }
    uint16_t elapsed = (uint16_t)gmillis - t;
    switch (fase)
    {
        .....

Nota: ho usato uint16_t perché mi bastano tempi entro un minuto e risparmio un paio di byte (moltiplicati per tutte le altre variabili temporali usate in giro per il programma fanno un discreto numero).

Osservando il codice di claudio dovresti notare che le azioni sono tutte condizionate.

Questo è i punto fondamentale, i vari case esprimono il: "se sono in questa situazione", mentre tutti gli if interni ai case esprimono i possibili "se succede questo".

Per finire nel suo caso ha una schermata principale a cui ritorna in automatico

Si la logica è: se ruoto l'encoder modifico i valori, con click confermo, se premo a lungo abortisco l'operazione, se non agisco per tot secondi idem. Questo in qualsiasi punto delle regolazioni.

Ci sono solo 3 chiamate alla stessa funzione printMenu(s),

Ogni volta che un valore viene modificato si aggiorna il display. In particolare per il menu:

void printMenu(uint8_t s)
{
    lcd.clear();
    switch (s)
    {
        case 0:  lcd.print(F("REGOLA ORA"));        break;
        case 1:  lcd.print(F("REGOLA DATA"));       break;
        case 2:  lcd.print(F("REGOLA SVEGLIA1"));   break;
        case 3:  lcd.print(F("REGOLA SVEGLIA2"));   break;
        case 4:  lcd.print(F("REG. LUMINOSITA`"));  break;
        case 5:  lcd.print(F("REGOLA TIMER"));      break;
        case 6:  lcd.print(F("VISUALIZZA TIMER"));  break;
    }
}

in fondo non ho scritto nulla di utile

Hai fatto quella chiarezza che magari io davo per scontata.

termino dicendo che c'è una certa predisposizione naturale a scrivere codice bloccante, serve esercizio per invertire la rotta.

Sicuramente TUTTI i tutorial ed esempi non aiutano in questo senso. Per chi programma i PLC ad esempio, il codice non bloccante è l'unico modo sensato di procedere fin dall'inizio. Il concetto di "ciclo di scansione" (il nostro ciclo di loop) e di elaborazione che avanza un passo dopo l'altro ad ogni intero ciclo, è fondamentale e viene prima di ogni altra considerazione .

Questa porzione di codice è esplicita, ciò in fase di codifica può aiutare, o meglio la si può desiderare perché ancora non abbiamo chiaro tutti i passaggi. Dopo si può decidere diversamente, ad esempio potrei:

if ( onClic ) {
     fase = fasi[s];
     break;
}

dove fasi è un array.

Ovviamente non conoscendo il resto del codice non posso dire se il comportamento è identico. Però sembrerebbe di si e nel caso il valore di s potesse essere minore di zero o maggiore di 6 il controllo lo possiamo aggiungere nella if (onClic), comunque ipotizziamo che il comportamento sia identico, cosa comporta la modifica e ciò che comporta lo desideriamo e torna a nostro pieno vantaggio?

Il codice esplicito non usa memoria stack, ma impegna memoria flash.

Mentre il codice che ho ipotizzato usa meno flash, ma richiede un array in ram, se per altri motivi mi torna utile avere un array globale delle fasi allora non sto usando più memoria ram. Vantaggio apparente è che uso meno flash. Ma è questo che voglio? sono pronto a perdere la caratteristica esplicita. Posso fare qualcosa per rendere esplicita quella sola if? Sarà il caso di usare una funzione? che nome darò a quella funzione. Queste sono alcune delle valutazioni che si fanno durante la codifica e per assurdo non c'è codice sbagliato se questo ti aiuta nella codifica e comunque si deve sempre essere pronti a modificare.

Lo già scritto e dovrebbe essere ovvio, appena modifico codice testato so che la probabilità di introdurre un bug è alta, quindi modificare significa invalidare il tempo speso per i test e doverne spendere altro per testare la modifica, per cui va valutata ed è facile cadere in tentazione e scoprire che era meglio non apportare modifiche.

Dimenticavo di dire qualcosa circa l'uso delle librerie, già pronte. Nel caso specifico per la lettura dei pulsanti io preferisco usare una libreria che però deve fare al caso mio, cioè non userò una libreria orientata agli eventi se devo gestire tre pulsanti perché significa avere una infrastruttura sovradimensionata rispetto alle necessità. L'uso di librerie già belle e pronte potrebbe creare dei problemi solo nel caso in cui la libreria non la si è studiata e testata in modo isolato, ad esempio tutte quelle basate su millis() falliscono se nel programma uso un delay oppure scrivo codice "bloccante". Il problema delle versioni di libreria che nel tempo diventano incompatibili è risolvibile spostando l'header e il .cpp della libreria nella cartella dello sketch, così congeliamo il tutto. In seguito potremmo valutare di apportare le modifiche alla nostra versione della libreria congelata.

Ciao.

Grazie!
Devo meditare,,, Anche perché, purtroppo, mi sta riprendendo l'insonnia (apparentemente senza alcuna ragione) e di conseguenza fatico a ragionare... :frowning:

Devo dire che io non dormo più in modo spontaneo, devo ricorrere alle pillole, purtroppo oramai sono anni che vado avanti così.

Ricordo le panzate di sonno che mi facevo, forse ho esagerato. :smiley:

Non so se suoni ancora uno strumento, ad esempio il piano ti può tornare utile questo programma:

Che offre anche un servizio a pagamento.

Sono su Ubuntu 20.04 è devo dire che musescore lavora davvero bene. Il programma è scritto in C++ con il toolkit Qt.

PS: Grazie al programma sto esercitandomi con "Per Elisa" alla chitarra classica ed è davvero difficile suonare con il metronomo.

PS: io qualcosa la devo suonare fino a che ho vita, pure fosse il campanello. :smile:

Ciao.

potrebbe diventare

if (onclic) {fase=s;}

purchè le costanti numeriche siano ottenute da una

enum FASI {REGORA,REGDATA,REGSVEGLIA....

che trovi una volta sola nel preambolo e poi lasci stare

:smile: :smile: :smile:
Ho il sospetto che sulla mia insonnia influiscano anche esigenze fisiologiche arretrate, di cui però non sento necessità...
Stanotte ho dormito oltre 10 ore(!). Moglie, presepio e albero permettendo, vorrei provare i timer.
Il pdf citato da Guglielmo lo avevo già stampato in un bel libricino A5 tempo fa... Eccolo qui! :slight_smile:

Questo è molto interessante:

L'ho copiato in Word e stampato, sempre a libretto (4 pagine su un solo foglio A4):
Generare impulsi con i timer di Arduino.zip (94.7 KB)
Non potendo allegare un docx, ho dovuto zipparlo.