Salve ho creato un menù su LCD a 4 righe e 20 colonne da utilizzare per il mio progetto, una centralina per comandare le luci sul presepe. Sullo schermo compaiono 4 righe con riportato le diciture dei tempi di ogni fase (alba, giorno, tramonto, notte). Con 4 tasti messi su ingresso analogico ( ad ogni tasto ho dato una resistenza di valore diverso per poterli identificare) faccio scorrere una freccia per indicare in quale fase devo cambiare il valore del tempo. Funziona tutto bene. Nel proseguimento del progetto ho modificato il programma inserendo un interrupt, premendo un pulsante sull'ingresso 2, il codice del menù che prima si trovava nel Loop, l'ho inserito in un void richiamato dall'interrupt, con un semplice taglia e incolla. Quando premo il pulsante, la funzione da me creata viene aperta ma il codice del menù che prima funzionava quando stava nel loop, ora non funziona. per la verifica ho messo un Serial.print subito dopo la lettura del pin A1. Fino a quando non premo i tasti mi ritorna zero ma nel momento in cui premo i tasti del menu, la visualizzazione nella finestra Monitor si blocca e non accade nulla sul display. Cercando in rete ho visto che nelle funzioni chiamate dall'interrupt il delay non funziona, per cui penso che tutto il resto dovrebbe funzionare, non riesco a venirne a capo.
void menutempo(){
flagpremuto = !flagpremuto; // Ogni volta che il tasto viene premuto la variabile inverte il suo stato passando da 0 a 1 e viceversa.
while(flagpremuto) {
//Serial.println("dentro while");
// Serial.println(flagpremuto);
tasto = analogRead(A0); // Legge il valore restituito dalle resistenze poste in serie ai tasti
Serial.println(tasto);
stato = 0;
if (tasto!=0) {
if (tasto <892 && tasto >888) stato = 1; // Tasto decrementa
else if (tasto <696 && tasto >692) stato = 2; // Tasto incrementa
else if (tasto <992 && tasto >988) stato = 3; // Tasto SU
else if (tasto <565 && tasto >561) stato = 4; // Tasto GIU
}
// Serial.println(tasto);
switch(stato) {
case 1: // Tasto decrementa
inc = -1;
setTempo();
break;
case 2: // Tasto incrementa
inc = 1;
setTempo();
break;
case 3: // Tasto su
ve=ve-1;
if (ve<0) ve=3;
muovefreccia();
break;
case 4: // Tasto GIU
ve = ve+1;
if (ve>3) ve=0;
muovefreccia();
break;
} // Fine switch
} // Fine while
Serial.println("esce while");
} // Fine void
quando premo il tasto viene eseguito il void e flagpremuto cambia di stato diventando 1, così il ciclo while si ripete all'infinito. Nel ciclo while ho inserito il codice del menu. Per uscire dal void e tornare al loop ho pensato di usare lo stesso pulsante usato per entrare nel void, infatti all'inizio del void ho inserito la variabile flagpremuto, premendo per la seconda volta il pulsante, flagpremuto cambia di stato diventando zero e il ciclo while non viene eseguito, tornando al loop, questo in teoria, perchè nella pratica non succede niente.
setTempo() è una funzione creata da me, varia il tempo di una variabile array in base alla posizione della freccia sul display.
muovefreccia() è una funzione creata da me che muove la freccia sul display per indicare quale fase del giorno modificare.
la funzione richiamata dall'interrupt deve essere il più breve e veloce possibile...all'interno degli interrupt i vari timer non si aggiornano...quindi millis(), delay() ed anche la seriale non funzioneranno...devi cambiare approccio...
Con arduino 1 se ho capito bene si possono avere 2 interrupt, sul piedino 2 e 3, dato che il 2 dovrò utilizzarlo per rilevare lo zero di un'onda sinusoidale, il 3 lo userò per entrare ed uscire dal menù. Quindi la routine del pin 3 dovrà azionare solo un flag che mi dice quando è stato premuto il pulsante. Faccio un esempio, arduino sta eseguendo il codice, viene premuto il tasto per entrare nel menù, il flag viene posto a 1 e poi arduino torna da dove aveva lasciato. Se il controllo del flag sta all'inizio del loop, per entrare nel menù, devo aspettare che arduino finisca di eseguire tutte le righe per tornare all'inizio. Se ci sono poche righe e nel codice non ci sono tanti cicli, non ci saranno problemi di tempo, ma se il codice si complica, ci può volere del tempo per tornare all'inizio, rendendo vano l'utilizzo dell'interrupt per il pulsante. Quindi come posso sfruttare al meglio l'interrupt?
Il meccanismo degli interrupt è molto semplice e lo possiamo riassumere così:
Mentre sto leggendo un post qualcuno mi interrompe chiamandomi, io o leggo o rispondo, rapidamente mi segno dove sono arrivato a leggere e presto attenzione totale a chi mi ha interrotto (e non accetto altre interruzioni), se questi mi tiene impegnato in un loop o mi impartisce molte istruzioni dovrò eseguirle e ci impiegherò del tempo, terminate queste istruzioni torno a leggere il post.
Non accettare altre interruzioni vuole dire non accettare interruzioni nemmeno dal timer hardware TIMER0 che solleva una interruzione ogni 1 millesimi di secondo ( millis() ). Per questo e per altri motivi simili le funzioni collegate alle interruzioni sono brevi e impegnano la cpu per poco tempo. Quanto "poco tempo" dipende dalla applicazione complessiva, cioè se non ci importa perdere il conto del tempo per 10 millesimi di secondo bene.
Andiamo al pulsante che solleva una interruzione, questa cosa non si fa per più motivi, uno di questi è che i contatti elettromeccanici soffrono di rimbalzi (bounce), cioè pigio il pulsante il segnale da HIGH passa a LOW e da LOW ad HIGH decine di volte in pochi millisecondi, poi si stabilizza su LOW.
Questo vuole dire sollevare una interruzione decine volte o più nei primi 3, 4 o 5 millisecondi. Una raffica di interruzioni che dipende dalla qualità del pulsante. Il rimbalzo si cura si elettronicamente che via software. Tagliando corto, esistono delle librerie per gestire i pulsanti e queste lavorano in polling e se si riesce a scrive l'applicazione senza delay alcuno la reattività è assicurata e le librerie non falliscono nella gestione del rimbalzo.
Per esperienza diretta io ci ho impiegato diversi anni per imparare a scrivere applicazioni senza alcun delay, ora però sono ormai tanti anni che mi sono disintossicato dal delay.
Pigia sul pulsante Code in verde, si apre un dialog box e in basso c'è scritto Download ZIP. Oppure devi usare git clone per clonare il repo e poi copiare i file manualmente. Se usi la versione "portable" di arduino IDE dovresti anche sapere in quale percorso si trovano le librerie, se non lo sai specifica il sistema operativo che usi e qualcuno ti risponderà.
Grazie, ho pigiato sul pulsante verde e ho scaricato. Ho incluso la libreria ma quando premo l'icona per il controllo codice mi esce un errore. Da quello che ho capito, questa libreria usa il pin 2 per il suo interrupt, per cui io ho usato il pin 3 per il il mio interrupt, la linea è questa:
Se la tolgo non mi da errore. Se la inserisco mi dà questo errore:
WInterrupts.c.o (symbol from plugin): In function attachInterrupt': (.text+0x0): multiple definition of __vector_1'
C:\Users\cicci\AppData\Local\Temp\arduino-sketch-FC6EB80FD9114853DA11F8A0F578DEB6\libraries\RBDDimmer-master\avr\RBDmcuAVR.cpp.o (symbol from plugin):(.text+0x0): first defined here
collect2.exe: error: ld returned 1 exit status
exit status 1
Compilation error: exit status 1
Mi chiedo se si possono usare due interrupt nello stesso programma, leggendo la tua specifica sugli interrupt, mi sa che in un programma si può usare un solo interrupt alla volta perchè hai detto: Non accettare altre interruzioni vuole dire non accettare interruzioni nemmeno dal timer hardware TIMER0 che solleva una interruzione ogni 1 millesimi di secondo ( millis() ).
quindi dovrò modificare il codice relativo al menù.
#include <RBDdimmer.h> // Libreria per il funzionamento della scheda Dimmer PWM
#include <Wire.h> // Include libreria I2C
#include <LiquidCrystal_I2C.h> // Include libreria er LCD 2 o 4 righe
LiquidCrystal_I2C lcd(0x27, 20, 4); // Indirizzo 27, 20 colonne e 4 righe
byte freccia[8] = { // Crea un carattere con la figura della freccia
B00000,
B00000,
B00010,
B11111,
B00010,
B00000,
B00000,
B00000
};
volatile int flagpulsante = 0; // Questa variabile può essere cambiata solo nel void dell'interrupt
int tasto = 0;
int stato = 0;
int ve = 0; // Indica la riga del display
int inc = 1;
int tempo[4] = {30,30,30,30}; // Definisce un array di 4 posizioni, 0=Alba, 1=Giorno, 2=Tramonto, 3=Notte
int luce[4] = {0,0,0,0}; // Indica di che percentuale si deve accendere la lampadina su ogni fase. La percentuale va da 0 a 100
void setup() {
attachInterrupt(digitalPinToInterrupt(3),flagpremuto , FALLING); // Crea un interrupt quando il pin 3 cambia stato ed esegue il void menu flagpremuto
Serial.begin(9600); // Inizializza la seriale alla velocità di 9000
lcd.init(); // Inizializza LCD
lcd.backlight(); // Attiva retroilluminazione
lcd.clear(); // Cancella lo schermo
lcd.createChar(0, freccia); // Crea la freccia nella posizione zero
menu(); // Visualizza il menù sul display senza la freccia
for(int i=0;i<4;i++) { // Visualizza i secondi di fianco ad ogni opzione
lcd.setCursor(17,i);
lcd.print(tempo[i]);
} // Fine Ciclo For
} // Fine Setup
void loop() {
if (flagpulsante == 1) menutempo();
delay(500);
} // Fine loop principale
void menu() // Visualizza Menu con alba, giorno, tramonto, notte
{
lcd.setCursor(1, 0);
lcd.print("Tempo alba");
lcd.setCursor(1, 1);
lcd.print("Tempo giorno");
lcd.setCursor(1, 2);
lcd.print("Tempo tramonto");
lcd.setCursor(1, 3); // Imposta il cursore alla riga (da 0 a 19) e colonna (da 0 a 3)
lcd.print("Tempo notte");
}
void muovefreccia() // Muove la freccia su e giu
{
for(int i=0;i<4;i++) { // Pulisce la prima colonna di ogni riga, in pratica cancella la freccia
lcd.setCursor(0,i);
lcd.print(" ");
}
lcd.setCursor(0,ve); // Stampa la freccia sul LCD
lcd.write(byte(0));
}
void setTempo() // Varia il tempo delle fasi in base alla posizione della freccia
{
tempo[ve] = tempo[ve] + inc; // Incrementa o decrementa di 1 il valore delle varie fasi in base al valore di inc (1 o -1)
if (tempo[ve]<15) tempo[ve]=15; // Imposta a 15 secondi il limite minimo del tempo
else if (tempo[ve]>120) tempo[ve]=120; // Imposta a 2 minuti il tempo massimo
if (tempo[ve]==99) { // Quando il valore del tempo passa da 100 a 99 cancella il terzo numero sul display
lcd.setCursor(17,ve);
lcd.print(" ");
}
lcd.setCursor(17,ve);
lcd.print(tempo[ve]); // Visualizza il valore del tempo della relativa fase indicata dalla freccia
}
void flagpremuto(){
flagpulsante = !flagpulsante; // Ogni volta che il tasto viene premuto la variabile inverte il suo stato passando da 0 a 1 e viceversa
}
void menutempo(){
lcd.setCursor(0,ve);
lcd.write(byte(0)); // Stampa la freccia alla posizione indicata da lcd.setCursor
while(flagpulsante) {
tasto = analogRead(A0); // Legge il valore restituito dalle resistenze poste in serie ai tasti
stato = 0;
if (tasto!=0) {
if (tasto <892 && tasto >888) stato = 1; // Tasto decrementa
else if (tasto <696 && tasto >692) stato = 2; // Tasto incrementa
else if (tasto <992 && tasto >988) stato = 3; // Tasto SU
else if (tasto <565 && tasto >561) stato = 4; // Tasto GIU
}
switch(stato) {
case 1: // Tasto decrementa
inc = -1;
setTempo();
break;
case 2: // Tasto incrementa
inc = 1;
setTempo();
break;
case 3: // Tasto su freccia
ve=ve-1;
if (ve<0) ve=3;
muovefreccia();
break;
case 4: // Tasto GIU freccia
ve = ve+1;
if (ve>3) ve=0;
muovefreccia();
break;
} // Fine switch
delay(150); // Crea ritardo per rallentare il movimento della freccia
} // Fine while
for(int i=0;i<4;i++) { // Pulisce la prima colonna di ogni riga, in pratica cancella la freccia
lcd.setCursor(0,i);
lcd.print(" ");
}
} // Fine void
per ora il tasto per entrare ed uscire dal menù non l'ho fatto con l'interrupt ma lo faccio funzionare col pin analogico A0 insieme agli altri tasti. Ho preso 4 schedine, funzionano con la libreria RBDdimmer, nel sito faceva vedere che si potevano collegare anche più di una. Sono andato nello zip della libreria per vedere quali erano i comandi e ho aperto il file ReaMe.txt. Da quello che ho capito (non conoscendo l'inglese) dimmerLamp dimmer(alba) indica quale pin PWM si usa per l'uscita, dimmer.begin(NORMAL_MODE, ON); Dice come deve funzionare l'uscita PWM e l'abilita, dimmer.setPower(100) serve a scrivere il valore 100 nel suddetto pin di uscita, però se ho 4 uscite, come fa a sapere a quale uscita invio il 100? Era più logico se fosse stato dimmer.setPower(pin, 100). In più quando faccio la verifica del codice mi mette in rosso la riga dimmer.setPower(100); e poi scrive questo:
C:\Users\cicci\OneDrive\Documenti\Arduino\Dimmer PWM con scheda\Dimmer PWM con scheda.ino: In function 'void loop()':
C:\Users\cicci\OneDrive\Documenti\Arduino\Dimmer PWM con scheda\Dimmer PWM con scheda.ino:134:3: error: 'dimmer' was not declared in this scope
^
C:\Users\cicci\OneDrive\Documenti\Arduino\Dimmer PWM con scheda\Dimmer PWM con scheda.ino:134:3: note: suggested alternative: 'memmem'
^
memmem
exit status 1
Compilation error: 'dimmer' was not declared in this scope
Ho capito perchè mi dava errore, dimmerLamp dimmer(alba); lo dovevo inserire all'eserno del void setup(). Però ora mi capita un'altra cosa, se inserisco anche dimmerLamp dimmer(giorno); per abilitare un altro pin PWM, mi esce l'errore: Compilation error: redefinition of 'dimmerLamp dimmer'
"Modulo Dimmer luce ca, 4 canali, logica 3.3V/5V, ca 50/60hz, 110V ~ 400V, 10a per canale"
Ho preso questo modulo dimmer a 4 uscite, per funzionare utilizza la libreria RBDdimmer.h, però non ho ancora capito come in
dirizzare le varie uscite.