Come si fa per non bloccare? Una opzione e' di attaccarsi ad un interrupt (e NON al tram...). Fondamentalmente, si scrive una funzione che gestisce i cambiamenti di un dato pin (non si puo' fare con un pin qualsiasi, dipende dal modello di Arduino, per esempio sul Nano (che e' il mio cavallo di battaglia ogni volta che comincio un progetto, salvo derapare se le risorse sono inadeguate al compito) sono gestiti solamente i pin (digitali) 2 e 3. Il Mega e il Mega2560 gestiscono interrupt dai pin (digitali) 2, 3, 18, 19, 20, 21. I nuovi Arduino (carissimi!) anche da tutti i pin. La funzione NON la chiami tu da dentro loop(), ma la "dichiari" al sistema con la chiamata
attachInterrupt(digitalPinToInterrupt(pinCheVuoiUsare), nomeDellaFunzione, rispondiA);
dove
- digitalPinToInterrupt(pinCheVuoiUsare) e' il modo raccomandato di passare il numero del pin che vuoi usare per il segnale di ingresso;
- nomeDellaFunzione e' il nome della funzione che deve essere eseguita in risposta ad un interrupt;
- rispondiA permette di selezionare a quale tipo di "evento" deve rispondere la tua funzione, e puo' assumere i valori seguenti:
- LOW se la funzione deve essere eseguita ogniqualvolta il pin e' LOW (occhio...);
- CHANGE se la funzione deve essere eseguita ogniqualvolta il pin cambia livello, ossia se passa da LOW a HIGH o da HIGH a LOW;
- RISING se la funzione deve essere eseguita ogniqualvolta il pin passa da LOW a HIGH;
- FALLING se la funzione deve essere eseguita ogniqualvolta il pin passa da HIGH a LOW.
La chiamata a attachInterrupt restituisce un valore int che corrisponde al numero del vettore di interrupt se tutto va bene, oppure -1 se la richiesta si riferiva a un interrupt non esistente.
A questo punto sappiamo come fare perche' una certa funzione venga eseguita allorche' avviene "qualcosa" in ingresso ad un determinato pin. Ora nasce il (rognoso) problema di come fare per generare un impulso di una lunghezza precisa in risposta al nostro "evento". Chiaramente, mettere una bella sequenza
digitalWrite(pinDiUscita, HIGH);
delay(100);
digitalWrite(pinDiUscita, LOW);
purtroppo semplicemente NON funziona, perche' delay() fa affidamento su un'altra routine di interrupt e durante l'esecuzione di una le altre sono disabilitate. Se l'Arduino puo' permettersi di sprecare 100 millisecondi senza fare niente altro mentre genera i segnali, in teoria si potrebbe usare ripetutamente delayMicroseconds() con un ritardo in microsecondi inferiore a 500 (altrimenti possono succedere cose fastidiose). PERO', ed e' un grosso pero', durante questo tempo l'Arduino diventa "sordo" a mondo esterno e si perde eventuali caratteri o pacchetti in arrivo su porte seriali, WiFi o Ethernet (a meno che l'hardware non abbia dei bufferoni belli grossi). Come rimediare?
Soluzione 1. "throw money at the problem" - gettare soldi al problema, ossia aggiungere un pochino di hardware esterno che generi un impulso della lunghezza desiderata a partire dal momento in cui l'Arduino invia il comando, per esempio con un 74LS123 che ha pure due canali...(
https://www.ti.com/lit/an/sdla006a/sdla006a.pdf). In questo caso, un breve impulso all'uscita fara' partire il monostabile per un tempo fissato da due componenti esterne. Ma a questo punto, a meno che per l'Arduino non sia importante sapere che cosa sta facendo o ha fatto il Timer esterno, perche' non pilotare direttamente il 74LS123 con l'uscita del Timer esterno stesso? Volendo proseguire nella stessa direzione, perche' non utilizzare addirittura un ATTINY85 come "coprocessore" per l'Arduino principale? Un pin per il segnale del Timer esterno, due pin per le uscite e altri due per comunicare all'Arduino principale lo "stato" del sistema, codificato su un massimo di 4 valori... L'ATTINY davvero eseguirebbe solo il tuo doppio loop (con in aggiunta il pilotaggio delle due uscite di comunicazione con l'Arduino master). Lo ho visto fare. Non e' elegantissima la soluzione, ma risolve.
Soluzione 2. "elbow oil" ossia olio di gomito... Niente compoenti extra, piu' lavoro, Occorre utilizzare un altro interrupt dell'Arduino, e qui ci gettiamo in un vespaio perche' gli interrupt da Timer sono delicatissimi (in quanto servono anche all'Arduino per cosucce da nulla come tenere conto del tempo o gestire le uscite pseudo-analogiche in PWM).
L'ottimo Nick Gammon ha una pagina molto completa (ma in inglese) sugli interrupt all'indirizzo
http://gammon.com.au/interrupts che spiega che cosa si puo' e che cosa non si puo' fare con e in un routine di servizio di un interrupt.
Credo di aver presentato (senza entrare nel dettaglio implementativo) due opzioni possibili, di cui la prima comprende due sotto-opzioni. Se vuoi, possiamo sceglierne una ed entrare nel dettaglio.