se interruttore alto attiva un uscita altrimenti me ne attivi un'altra

Progetti Arduino
Avatar utente
pgv
Messaggi: 484
Iscritto il: gio 17 set 2020, 13:16
Località: Ginevra

Re: se interruttore alto attiva un uscita altrimenti me ne attivi un'altra

Messaggio da pgv »

Eccoci...

Comincio con le spiegazioni di pezzi del listato, quello completo segue in calce. Domande, richieste di chiarimenti e obiezioni sono ben accette.

Da principio le dichiarazioni delle variabili "necessarie" per il corretto funzionamento. Da notare che le variabili manipolate dalla Routine di Servizio ad un Interrupt (ISR nel seguito) devono essere dichiarate come volatile altrimenti il compilatore ha tutto il diritto di ottimizzare gli accessi alle stesse e non ricaricare il valore se gia' dovesse essere presente in un registro. Con il qualificatore volatile specifichiamo che si tratta di variabili che possono cambiare in qualsiasi momento.
Utilizzo una enum per ricordare lo stato perche' e' piu' elegante, sotto sotto e' come dichiarare un insieme di costanti numeriche... Dichiarato il tipo enum FSM poi mi dichiaro anche una variabile di quel tipo con il nome statoAttuale . In pratica, e' una variabile intera che assume solamente un certo numero di valori. Qui non ne ho bisogno (e partono automaticamente da zero), ma si puo' anche assegnare un valore specifico alle "costanti". Insomma, per programmare Arduino sono comode, le enum... Mi dichiaro anche, qui vicino alla variabile che ne fara' uso (contatore) due costanti durataUno e durataDue con il numero di millisecondi che mi devono durare HIGH le due uscite.

volatile int contatore; // il contatore per i 100 "millisecondi"
const int durataUno = 100; // Vogliamo un HIGH lungo 100 millisecondi sull'uscita Uno
const int durataDue = 100; // Vogliamo un HIGH lungo 100 millisecondi sull'uscita Due

enum FSM { // Listiamo i vari casi in cui porssiamo trovarci
inAttesaDiHigh, // All'inizio, siamo in attesa che l'ingresso passo da LOW a HIGH
uscitaUnoHigh, // L'ingresso ha fatto il suo dovere e abbiamo attivato la prima uscita
inAttesaDiLow, // Il tempo di HIGH per la prima uscita e' passato ma l'ingresso e' ancora HIGH
uscitaDueHigh // L'ingresso e' tornato LOW, e la seconda uscita e' stata attivata
}; // Da notare che una volta finito il tempo di HIGH per la seconda uscita ricominciamo daccapo
volatile enum FSM statoAttuale = inAttesaDiHigh; // Tiene conto del progresso della MSF


Per generalita' mi piace dichiarare che pin utilizzo per una certa funzione come costanti, cosi' se cambiano devo cambiare in un posto solo:

const int ingresso = 8; // pin di ingresso che andiamo a monitorare
const int uscitaUno = 13; // pin di uscita Uno guardacaso quello col LED sull'Arduino Uno
const int uscitaDue = 13; // pin di uscita Due per ora lo stesso che per uscitaUno non trovo i LED

void setup()
{
// Dichiariamo i nostri pin, e azzeriamo le uscite
pinMode(ingresso, INPUT);
pinMode(uscitaUno, OUTPUT);
pinMode(uscitaDue, OUTPUT);
digitalWrite(uscitaUno, LOW);
digitalWrite(uscitaDue, LOW);
<... continua ...>


In questo caso (siccome non trovo lo scatolino con i LED) ho utilizzato due volte l'uscita 13 che il LED ce l'ha sull'Arduino Nano, ovviamente basta cambiare il numero (a meno che non utilizzi pin Analogici e allora diventa un po' piu' complicato, devi usare "A0", "A1" etc, senza virgolette beninteso, che sono macro definite con una serie di #define). All'inizio della setup() dichiariamo gli ingressi e le uscite, e azeriamo (a LOW) le uscite controllate dal Timer esterno.

Proseguiamo... Ora arriva una istruzione rognosa, che dichiara alla CPU che vogliamo che avvenga un interrupt ogni volta che il Timer0 conta 256 impulsi. Dovrebbe essere gia' settato ma non fa male. Questo ci generera' i nostri interrupt ogni millisecondo, perche' internamente il Timer0 "sa" che deve prendere un clock a 250 kHz, e 250 kHz diviso 256 fa "quasi esattamente" un mllisecondo. Purtroppo non e' bene cambiare il conteggio da 256 a 250 per avere un millisecondo preciso perche' le uscite analogiche e forse anche altri meccanismi interni se ne servono anche loro. Nel listato (piu' in basso) c'e' anche una spiegazione di come "correggere" la lieve discrepanza se necessario.

// Timer0 e' gia' usato per la funzione millis()
// ma noi ci agganciamo da qualche parte nel mezzo
// e chiamiamo la nostra funzione
TIMSK0 |= _BV(OCIE0A); // Questa e' la maschera degli Interrupt del Timer 0, e questa strana istruzione
// _BV() e' una macro offertaci da Arduino che vuol dire "Bit Value", quindi _BV(2) = 4
// (il valore del Bit 2). E' definita come uno shift a sinistra di N posizioni:
// #define _BV(bit) (1 << (bit))
// Facciamo l'OR bita a bit del contenuto del registro con _BV(OCIE0A) per mettere
// A "1" il bit che abilita le interruzioni quando il Timer 0 raggiunge il valore di sopra


Cominciamo con la dichiarazione che la funzione che segue e' una routine di risposta a interruzioni:

// Ora dichiariamo la funzione che vogliamo sia eseguita in risposta all'interrupt generato ogni volta che il Timer0 conta 256 colpi, ossia ogni millisecondo
SIGNAL(TIMER0_COMPA_vect)
{


Che si traduce semplicemente con "Arduino per favore, ogni volta che il Timer0 raggiunge 256 e genera un interrupt, esegui anche il codice seguente".

Che cosa c'e' dentro la routine ISR? Per cominciare, il codice che "corregge" per il fatto che la ISR non viene chiamata esattamente ogni millisecondo. La correzione funziona un po' come gli anni bisestili (ma al contrario), conta di quanto ci siamo allontanati dal millisecondo intero e quando siamo troppo lontani ne aggiunge uno per compensare:

// Ahinoi, il Timer 0 dell'Arduino non va in overflow tutti i 1000 microsecondi come ci farebbe comodo, ma
// tutti i 1024 microsecondi. Siccome mettersi a "ciacciare" nel funzionamento interno del Timer 0 e' volere
// male a se' e all'Arduino, per evitare di fare cose di cui ci potremmo pentire copiamo il trucchetto che
// viene usato all'interno della funzione millis() delle librerie di Arduino.

milliSecondi += 1; // Aggiorniamo provvisoriamente il nostro contatore di millisecondi
erroreMilliSecondi += 3; // Valore empirico necessario per compensare il rapporto 1024/1000 millisecondi
if (erroreMilliSecondi >= 125) { // Altro valore empirico che ci dice quanto deve essere l'errore cumulativo
// per dover incrementare di uno il nostro computo dei millisecondi
erroreMilliSecondi -= 125; // Sottraiamo il numero magico, potrebbe avanzare qualcosina
milliSecondi += 1; // Aggiungiamo il millisecondo mancante, quasi fosse bisestile.
}


Ora viene la "ciccia" del programmino, ossia la Macchina a Stati Finiti (paroloni!) che tiene conto di che cosa stanno facendo ingressi e uscite e decide che cosa fare in seguito:

switch(statoAttuale) {
case inAttesaDiHigh : // Ancora non e' successo niente, stiamo aspettando tranquilli tranquilli
if (digitalRead(ingresso) == HIGH) { // L'ingresso di controllo e' diventato HIGH?
digitalWrite(uscitaUno, HIGH); // Si', provvediamo ad attivare l'uscita Uno
contatore = durataUno; // Carichiamo la durata dell'impulso di uscita
statoAttuale = uscitaUnoHigh; // E ricordiamoci che abbiamo cambiato stato!
}
break; // Basta qui per lo stato inAttesaDiHigh
case uscitaUnoHigh : // Abbiamo attivato l'uscita Uno, controlliamo se basta come durata
if (--contatore <= 0) { // Pre-decrementiamo il contatore, siamo arrivati a zero?
digitalWrite(uscitaUno, LOW); // Si', provvediamo a disattivare l'uscita Uno
statoAttuale = inAttesaDiLow; // E ricordiamoci che abbiamo cambiato stato!
} // Con l'istruzione --contatore abbiamo gia' decrementato il numero di cicli da attendere
break;
case inAttesaDiLow : // L'uscita Uno ha fatto il suo dovere, ora aspettiamo che l'ingresso ridiventi LOW
if (digitalRead(ingresso) == LOW) { // L'ingresso di controllo e' diventato LOW?
digitalWrite(uscitaDue, HIGH); // Si', provvediamo ad attivare l'uscita Due
contatore = durataDue; // Carichiamo la durata dell'impulso di uscita
statoAttuale = uscitaDueHigh; // E ricordiamoci che abbiamo cambiato stato!
}
break;
case uscitaDueHigh : // Abbiamo attivato l'uscita Due, controlliamo se basta come durata
if (--contatore <= 0) { // Pre-decrementiamo il contatore, siamo arrivati a zero?
digitalWrite(uscitaDue, LOW); // Si', provvediamo a disattivare l'uscita Uno
statoAttuale = inAttesaDiHigh; // E ricordiamoci che abbiamo cambiato stato!
}
break;
default : // Per qualche motivo, non siamo in uno degli stati predefiniti?
statoAttuale = inAttesaDiHigh; // Riconduciamoci ad un caso noto
digitalWrite(uscitaUno, LOW);
digitalWrite(uscitaDue, LOW);
break; // Meglio metterlo, se un giorno dovessimo allungare la lista dello switch() fa comodo!
}


Utilizzo l'istruzione switch perche' alla fin fine ci sta come il cacio sui maccheroni, "a seconda del valore di una variabile, esegui questo o quello spezzone di codice". Molto meglio che una valanga di if ... else, ma alla fin fine si tratta di quello:

- se lo stato attuale e' "in attesa di un livello HIGH sull'ingresso collegato al Timer", verifichiamo se per l'appunto non sia arrivato un livello HIGH al nostro ingresso (if (digitalRead(ingresso) == HIGH) {). In caso affermativo, facciamo partire i 100 millisecondi di HIGH all'uscita Uno (poniamo l'uscita Uno a HIGH e diamo il valore 100 al contatore, che decrementeremo ogni volta che ripassiamo dalla ISR fino a che non arriva a zero).
Cambiamo la variabile che contiene lo stato attuale per riflettere la nuova situazione, ossia che 'uscita Uno e HIGH (ovviamente a seguito di un livello HIGH all'ingresso). Basta cosi' per questo caso. L'istruzione "break" ordina esplicitamente di uscire dallo switch() senza eseguire il codice che segue (qualche volta puo' fare comodo invece eseguire anche il codice dei casi seguenti. Non qui).

- se invece lo stato attuale e' "l'uscita Uno e' HIGH a seguito dell'arrivo di un livello HIGH all'ingresso" facciamo qualcosa di diverso (ovviamente!), e cioe' decrementiamo il nostro contatore con l'istruzione --contatore (il "--" o "++" prima del nome della variabile vuol dire "decrementa/incrementa SUBITO la variabile e usa il nuovo valore nel seguito", al contrario del "--" o "++" dopo il nome della variabile che vuol dire "esegui quanto segue e solo IN SEGUITO decrementa/incrementa la variabile"). Controlliamo se siamo arrivati a zero, ossia se sono passati abbastanza "millisecondi" (non precisissimi) per i nostri gusti, e in caso affermativo possiamo riportare a LOW la nostra uscita Uno e cambiare ancora una volta stato, andando ad aspettare che il mio ingresso (che si presuppone sia ancora HIGH) torni a LOW. Sempre se il contatore e' arrivato a zero, dobbiamo ricordarci di cambiare stato della FSM (statoAttuale = inAttesaDiLow;) e ci mettiamo ad aspettare che l'ingresso ridiventi LOW.

Da notare che qui sta uno dei presupposti fondamentali di questa implementazione, ossia che l'ingresso resta HIGH e LOW piu' a lungo dei 100 millisecondi che durano i segnali in uscita. Se questo non e' vero occorre modificare il codice.

- se stiamo aspettando che il segnale in ingresso ridiventi LOW, controlliamo il suo livello (if (digitalRead(ingresso) == LOW) {), e in caso affermativo ripetiamo, con le opportune modifiche, quanto abbiamo fatto non appena e' diventato HIGH, ossia facciamo partire il segnale all'uscita Due e carichiamo il contatore con la durata. E ovviamente cambiamo lo stato della FSM ancora una volta, aspettando che siano passati i 100 millisecondi di durata di questo secondo segnale (statoAttuale = uscitaDueHigh;)

- se lo stato attuale e' con uscita Due HIGH, verifichiamo se non sia arrivato il momento di smetterla e di riportarla a LOW, e a quasto punto il prossimo stato della nostra FSM quale e'? Ma lo stato iniziale! Abbiamo completato un ciclo completo di attesa di HIGH in ingresso, 100 millisecondi di HIGH all'uscita UNO, attesa di LOW in ingresso, 100 millisecondi di HIGH all'uscita Due, e ci rimettiamo ad aspettare un livello HIGH in ingresso.

Tutto questo, senza che dal tuo loop() tu debba fare alcunche' per gestire ingressi e uscite. Puoi fare quel che ti pare (io stampo i cambiamenti di stato), e sapere ad ogni istante esattamente in che stato si trova la FSM leggendo la variabile statoAttuale.

Ed ecco il listato completo, da cut&pastare (bel neologismo!) nella IDE di Arduino...
<--- tagliare qui --->
volatile unsigned long milliSecondi; // Deve essere "volatile" per avvertire il compilatore di NON ottimizzare l'accesso alla variabile
// altrimenti rischia che se ha gia' letto il valore in un registro non lo rilegge e quello e' cambiato
volatile uint8_t erroreMilliSecondi; // Per tenere conto del fatto che il Timer 0 genera un interrupt tutti i 1024 e non 1000
// millisecondi, accumuliamo l'errore fatto fino a che non e' tanto da farci aggiungere
// un millisecondo intero.
volatile int contatore; // il contatore per i 100 "millisecondi"
const int durataUno = 100; // Vogliamo un HIGH lungo 100 millisecondi sull'uscita Uno
const int durataDue = 100; // Vogliamo un HIGH lungo 100 millisecondi sull'uscita Due

enum FSM { // Listiamo i vari casi in cui porssiamo trovarci
inAttesaDiHigh, // All'inizio, siamo in attesa che l'ingresso passo da LOW a HIGH
uscitaUnoHigh, // L'ingresso ha fatto il suo dovere e abbiamo attivato la prima uscita
inAttesaDiLow, // Il tempo di HIGH per la prima uscita e' passato ma l'ingresso e' ancora HIGH
uscitaDueHigh // L'ingresso e' tornato LOW, e la seconda uscita e' stata attivata
}; // Da notare che una volta finito il tempo di HIGH per la seconda uscita ricominciamo daccapo
volatile enum FSM statoAttuale = inAttesaDiHigh; // Tiene conto del progresso della MSF

const int ingresso = 8; // pin di ingresso che andiamo a monitorare
const int uscitaUno = 13; // pin di uscita Uno guardacaso quello col LED sull'Arduino Uno
const int uscitaDue = 13; // pin di uscita Due per ora lo stesso che per uscitaUno non trovo i LED

void setup()
{
// Dichiariamo i nostri pin, e azzeriamo le uscite
pinMode(ingresso, INPUT);
pinMode(uscitaUno, OUTPUT);
pinMode(uscitaDue, OUTPUT);
digitalWrite(uscitaUno, LOW);
digitalWrite(uscitaDue, LOW);

// Timer0 e' gia' usato per la funzione millis()
// ma noi ci agganciamo da qualche parte nel mezzo
// e chiamiamo la nostra funzione
TIMSK0 |= _BV(OCIE0A); // Questa e' la maschera degli Interrupt del Timer 0, e questa strana istruzione
// _BV() e' una macro offertaci da Arduino che vuol dire "Bit Value", quindi _BV(2) = 4
// (il valore del Bit 2). E' definita come uno shift a sinistra di N posizioni:
// #define _BV(bit) (1 << (bit))
// Facciamo l'OR bita a bit del contenuto del registro con _BV(OCIE0A) per mettere
// A "1" il bit che abilita le interruzioni quando il Timer 0 raggiunge il valore di sopra
// Azzeriamo il nostro contatore di millisecondi e il contatore di errore sui millisecondi
milliSecondi = 0L;
erroreMilliSecondi = 0;
// E predisponiamo la porta seriale per vedere che cosa succede
Serial.begin(9600);
}

// Ora dichiariamo la funzione che vogliamo sia eseguita in risposta all'interrupt, ossia ogni millisecondo
SIGNAL(TIMER0_COMPA_vect)
{
// Ahinoi, il Timer 0 dell'Arduino non va in overflow tutti i 1000 microsecondi come ci farebbe comodo, ma
// tutti i 1024 microsecondi. Siccome mettersi a "ciacciare" nel funzionamento interno del Timer 0 e' volere
// male a se' e all'Arduino, per evitare di fare cose di cui ci potremmo pentire copiamo il trucchetto che
// viene usato all'interno della funzione millis() delle librerie di Arduino.

milliSecondi += 1; // Aggiorniamo provvisoriamente il nostro contatore di millisecondi
erroreMilliSecondi += 3; // Valore empirico necessario per compensare il rapporto 1024/1000 millisecondi
if (erroreMilliSecondi >= 125) { // Altro valore empirico che ci dice quanto deve essere l'errore cumulativo
// per dover incrementare di uno il nostro computo dei millisecondi
erroreMilliSecondi -= 125; // Sottraiamo il numero magico, potrebbe avanzare qualcosina
milliSecondi += 1; // Aggiungiamo il millisecondo mancante, quasi fosse bisestile.
}
switch(statoAttuale) {
case inAttesaDiHigh : // Ancora non e' successo niente, stiamo aspettando tranquilli tranquilli
if (digitalRead(ingresso) == HIGH) { // L'ingresso di controllo e' diventato HIGH?
digitalWrite(uscitaUno, HIGH); // Si', provvediamo ad attivare l'uscita Uno
contatore = durataUno; // Carichiamo la durata dell'impulso di uscita
statoAttuale = uscitaUnoHigh; // E ricordiamoci che abbiamo cambiato stato!
}
break; // Basta qui per lo stato inAttesaDiHigh
case uscitaUnoHigh : // Abbiamo attivato l'uscita Uno, controlliamo se basta come durata
if (--contatore <= 0) { // Pre-decrementiamo il contatore, siamo arrivati a zero?
digitalWrite(uscitaUno, LOW); // Si', provvediamo a disattivare l'uscita Uno
statoAttuale = inAttesaDiLow; // E ricordiamoci che abbiamo cambiato stato!
} // Con l'istruzione --contatore abbiamo gia' decrementato il numero di cicli da attendere
break;
case inAttesaDiLow : // L'uscita Uno ha fatto il suo dovere, ora aspettiamo che l'ingresso ridiventi LOW
if (digitalRead(ingresso) == LOW) { // L'ingresso di controllo e' diventato LOW?
digitalWrite(uscitaDue, HIGH); // Si', provvediamo ad attivare l'uscita Due
contatore = durataDue; // Carichiamo la durata dell'impulso di uscita
statoAttuale = uscitaDueHigh; // E ricordiamoci che abbiamo cambiato stato!
}
break;
case uscitaDueHigh : // Abbiamo attivato l'uscita Due, controlliamo se basta come durata
if (--contatore <= 0) { // Pre-decrementiamo il contatore, siamo arrivati a zero?
digitalWrite(uscitaDue, LOW); // Si', provvediamo a disattivare l'uscita Uno
statoAttuale = inAttesaDiHigh; // E ricordiamoci che abbiamo cambiato stato!
}
break;
default : // Per qualche motivo, non siamo in uno degli stati predefiniti?
statoAttuale = inAttesaDiHigh; // Riconduciamoci ad un caso noto
digitalWrite(uscitaUno, LOW);
digitalWrite(uscitaDue, LOW);
break; // Meglio metterlo, se un giorno dovessimo allungare la lista dello switch() fa comodo!
}
}

void loop() {
unsigned long myMs;
enum FSM statoVecchio;

statoVecchio = statoAttuale;

while(1) {
// Se lo stato cambia, stampiamo lo stato, e su due colonne i millisecondi contati da noi e quelli contati dal sistema
if (statoAttuale != statoVecchio) {
statoVecchio = statoAttuale; // Ricordiamoci dove siamo arrivato per le stampe
Serial.print(F("Lo stato attuale e' "));
switch(statoAttuale) { // Esercizio per "nascondere" le stringhe in FLASH e non in RAM che e' poca
case inAttesaDiHigh :
Serial.print(F("in attesa che l'ingresso passi da LOW a HIGH"));
break;
case uscitaUnoHigh :
Serial.print(F("uscita Uno attiva"));
break;
case inAttesaDiLow :
Serial.print(F("in attesa che l'ingresso passi da HIGH a LOW"));
break;
case uscitaDueHigh :
Serial.print(F("uscita Due attiva"));
break;
default : // Non dovrebbe poter succedere ma...
Serial.print(F("sconosciuto! Il valore numerico e' "));
Serial.print(statoAttuale);
break;
}
Serial.print(F(". Sono passati "));
Serial.print(milliSecondi);
Serial.print(F(" "));
Serial.print(myMs = millis());
Serial.println(F(" millisecondi."));
}
}
}
<--- Tagliare qui --->
Guido
Messaggi: 1443
Iscritto il: dom 18 mar 2018, 20:21

Re: se interruttore alto attiva un uscita altrimenti me ne attivi un'altra

Messaggio da Guido »

Mamma mia ! Ma hai fatto una cosa ciclopica ! Appena avro' un po' di tempo voglio proprio provare questo codice. Faro' sapere.
Guido
RoccoCostruzioni
Messaggi: 51
Iscritto il: mer 9 set 2020, 21:16

Re: se interruttore alto attiva un uscita altrimenti me ne attivi un'altra

Messaggio da RoccoCostruzioni »

mio dio!!! mi inchino a PGV!!!! ma sei un programmatore professionista? domani lo carico su arduino e lo provo. mio dio grazie ancora per il tempo dedicatomi, non so come sdebitarmi
RoccoCostruzioni
Messaggi: 51
Iscritto il: mer 9 set 2020, 21:16

Re: se interruttore alto attiva un uscita altrimenti me ne attivi un'altra

Messaggio da RoccoCostruzioni »

Allora siccome morivo dalla voglia di vedere il funzionamento, ho implementato il codice gentilmente fornitomi da PGV nel mio con il risultato che uscitauno e uscitadue si comportano esattamente come ci si aspettava legati ovviamente a ingresso, però funzionano solo le due uscite e l'ingresso, tutto il resto è bloccato, mi chiedo dov'è l'errore!!

Ecco il listato con l'implementazione:

Codice: Seleziona tutto

#include <OneShotTimer.h>
#define ledPinON 2 //LED OUT ON
#define ledPinOFF 3 //LED OUT OFF
#define pulsON 4 //PULSANTE OUT ON
#define pulsOFF 5 //PULSANTE OUT OFF
#define releSS 6 //RELE SOFT START
#define ledSS 7 //LED SOFT START
#define A 8 //LED +10% 
#define B 9 //LED +5%
#define C 10 //LED 230V
#define D 11 //LED -5%
#define E 12 //LED -10%
#define pulsI 13//PULSANTE INCREMENTO PERCENTUALE INGRESSO TRAFO
#define pulsD 14//PULSANTE DECREMENTO PERCENTUALE INGRESSO TRAFO
#define ledPin230 15 //LED 230V SECONDARIO
#define ledPin110 16 //LED 110V SECONDARIO
#define puls110e230 18 //PULSANTE 110-230V SECONDARIO
#define buttonPinPROTa 22 //INGRESSO PROTEZIONE LINEA A (OVP, UVP, OCP, UCP, OPP, UPP, OTIMER)
#define buttonPinPROTb 23 //INGRESSO PROTEZIONE LINEA B (OVP, UVP, OCP, UCP, OPP, UPP, OTIMER)
#define ledPinPROTa 24 //RELE PROTEZIONE LINEA A
#define ledPinPROTb 25 //RELE PROTEZIONE LINEA B
#define buttonPinRESET 26 //PULSANTE RESET PROTEZIONI
#define buttonPinPOWER 27 //PULSANTE POWER ON/STANDBY
#define ledPinPOWERON 28 //LED POWER ON
#define ledPinPOWEROFF 29 //LED STANDBY
#define buttonPinTENSFISREG 31 //PULSANTE SELEZIONE TENSIONI FISSE O REGOLABILE
#define ledPin230F 32 //LED TENSIONE FISSA 230V
#define ledPin110F 33 //LED TENSIONE FISSA 110V
#define ledPinREG 34 //LED TENSIONE REGOLABILE


OneShotTimer timer;

int buttonState1 = 0;
int buttonState2 = 0;

int array1[] = {A, B, C, D, E};
int cont1;
int maxArray1, maxArray2;

int ledState3 = HIGH;
int ledState4 = LOW;
int buttonState3;
int lastButtonState3 = HIGH;
long lastDebounceTime3 = 0;
long debounceDelay3 = 50;

int ledState5 = HIGH;
int ledState6 = LOW;
int buttonState4;
int lastButtonState4 = HIGH;
long lastDebounceTime4 = 2;
long debounceDelay4 = 50;

int buttonStatus1 = 0;
int buttonStatus2 = 0;
int buttonStatus3 = 0;

int array2[] = {ledPin230F, ledPin110F, ledPinREG};
int cont2;
int maxArray3;

volatile unsigned long milliSecondi; // Deve essere "volatile" per avvertire il compilatore di NON ottimizzare l'accesso alla variabile
// altrimenti rischia che se ha gia' letto il valore in un registro non lo rilegge e quello e' cambiato
volatile uint8_t erroreMilliSecondi; // Per tenere conto del fatto che il Timer 0 genera un interrupt tutti i 1024 e non 1000
// millisecondi, accumuliamo l'errore fatto fino a che non e' tanto da farci aggiungere
// un millisecondo intero.
volatile int contatore; // il contatore per i 100 "millisecondi"
const int durataUno = 100; // Vogliamo un HIGH lungo 100 millisecondi sull'uscita Uno
const int durataDue = 100; // Vogliamo un HIGH lungo 100 millisecondi sull'uscita Due

enum FSM { // Listiamo i vari casi in cui porssiamo trovarci
  inAttesaDiHigh, // All'inizio, siamo in attesa che l'ingresso passo da LOW a HIGH
  uscitaUnoHigh, // L'ingresso ha fatto il suo dovere e abbiamo attivato la prima uscita
  inAttesaDiLow, // Il tempo di HIGH per la prima uscita e' passato ma l'ingresso e' ancora HIGH
  uscitaDueHigh // L'ingresso e' tornato LOW, e la seconda uscita e' stata attivata
}; // Da notare che una volta finito il tempo di HIGH per la seconda uscita ricominciamo daccapo
volatile enum FSM statoAttuale = inAttesaDiHigh; // Tiene conto del progresso della MSF

const int ingresso = 35; // pin di ingresso che andiamo a monitorare (timerPin)
const int uscitaUno = 36; // pin di uscita Uno (ledPinTIMERON)
const int uscitaDue = 37; // pin di uscita Due (ledPinTIMEROFF)

void setup () {

  pinMode (ledPinON, OUTPUT);
  pinMode (ledPinOFF, OUTPUT);
  pinMode (pulsON, INPUT);
  pinMode (pulsOFF, INPUT);
  pinMode (releSS, OUTPUT);
  pinMode (ledSS, OUTPUT);
  pinMode (A, OUTPUT);
  pinMode (B, OUTPUT);
  pinMode (C, OUTPUT);
  pinMode (D, OUTPUT);
  pinMode (E, OUTPUT);
  pinMode (pulsI, INPUT);
  pinMode (pulsD, INPUT);
  pinMode (ledPin230, OUTPUT);
  pinMode (ledPin110, OUTPUT);
  pinMode (puls110e230, INPUT);
  pinMode (buttonPinPROTa, INPUT);
  pinMode (buttonPinPROTb, INPUT);
  pinMode (ledPinPROTa, OUTPUT);
  pinMode (ledPinPROTb, OUTPUT);
  pinMode (buttonPinRESET, INPUT);
  pinMode (buttonPinPOWER, INPUT);
  pinMode (ledPinPOWERON, OUTPUT);
  pinMode (ledPinPOWEROFF, OUTPUT);
  pinMode (buttonPinTENSFISREG, INPUT);
  pinMode (ledPin230F, OUTPUT);
  pinMode (ledPin110F, OUTPUT);
  pinMode (ledPinREG, OUTPUT);


  maxArray1 = 4;
  maxArray2 = 0;
  for (int i = 0; i < 4; i++) digitalWrite (array1[i], LOW);
  cont1 = 2;

  maxArray3 = 3;
  for (int i = 0; i < 2; i++) digitalWrite (array2[i], LOW);
  cont2 = 2;

  digitalWrite (ledPinON, LOW);
  digitalWrite (ledPinOFF, HIGH);
  digitalWrite (releSS, LOW);
  digitalWrite (ledSS, HIGH);

  digitalWrite (array1[cont1], HIGH);

  digitalWrite (ledPin230, HIGH);
  digitalWrite (ledPin110, LOW);

  digitalWrite (ledPinPROTa, LOW);
  digitalWrite (ledPinPROTb, LOW);

  digitalWrite (ledPinPOWERON, HIGH);
  digitalWrite (ledPinPOWEROFF, LOW);

  digitalWrite (array2[cont2], HIGH);

  pinMode(ingresso, INPUT);
  pinMode(uscitaUno, OUTPUT);
  pinMode(uscitaDue, OUTPUT);
  digitalWrite(uscitaUno, LOW);
  digitalWrite(uscitaDue, LOW);

  // Timer0 e' gia' usato per la funzione millis()
  // ma noi ci agganciamo da qualche parte nel mezzo
  // e chiamiamo la nostra funzione
  TIMSK0 |= _BV(OCIE0A); // Questa e' la maschera degli Interrupt del Timer 0, e questa strana istruzione
  // _BV() e' una macro offertaci da Arduino che vuol dire "Bit Value", quindi _BV(2) = 4
  // (il valore del Bit 2). E' definita come uno shift a sinistra di N posizioni:
  // #define _BV(bit) (1 << (bit))
  // Facciamo l'OR bita a bit del contenuto del registro con _BV(OCIE0A) per mettere
  // A "1" il bit che abilita le interruzioni quando il Timer 0 raggiunge il valore di sopra
  // Azzeriamo il nostro contatore di millisecondi e il contatore di errore sui millisecondi
  milliSecondi = 0L;
  erroreMilliSecondi = 0;
  // E predisponiamo la porta seriale per vedere che cosa succede
  Serial.begin(9600);
}

// Ora dichiariamo la funzione che vogliamo sia eseguita in risposta all'interrupt, ossia ogni millisecondo
SIGNAL(TIMER0_COMPA_vect)
{
  // Ahinoi, il Timer 0 dell'Arduino non va in overflow tutti i 1000 microsecondi come ci farebbe comodo, ma
  // tutti i 1024 microsecondi. Siccome mettersi a "ciacciare" nel funzionamento interno del Timer 0 e' volere
  // male a se' e all'Arduino, per evitare di fare cose di cui ci potremmo pentire copiamo il trucchetto che
  // viene usato all'interno della funzione millis() delle librerie di Arduino.

  milliSecondi += 1; // Aggiorniamo provvisoriamente il nostro contatore di millisecondi
  erroreMilliSecondi += 3; // Valore empirico necessario per compensare il rapporto 1024/1000 millisecondi
  if (erroreMilliSecondi >= 125) { // Altro valore empirico che ci dice quanto deve essere l'errore cumulativo
    // per dover incrementare di uno il nostro computo dei millisecondi
    erroreMilliSecondi -= 125; // Sottraiamo il numero magico, potrebbe avanzare qualcosina
    milliSecondi += 1; // Aggiungiamo il millisecondo mancante, quasi fosse bisestile.
  }
  switch (statoAttuale) {
    case inAttesaDiHigh : // Ancora non e' successo niente, stiamo aspettando tranquilli tranquilli
      if (digitalRead(ingresso) == HIGH) { // L'ingresso di controllo e' diventato HIGH?
        digitalWrite(uscitaUno, HIGH); // Si', provvediamo ad attivare l'uscita Uno
        contatore = durataUno; // Carichiamo la durata dell'impulso di uscita
        statoAttuale = uscitaUnoHigh; // E ricordiamoci che abbiamo cambiato stato!
      }
      break; // Basta qui per lo stato inAttesaDiHigh
    case uscitaUnoHigh : // Abbiamo attivato l'uscita Uno, controlliamo se basta come durata
      if (--contatore <= 0) { // Pre-decrementiamo il contatore, siamo arrivati a zero?
        digitalWrite(uscitaUno, LOW); // Si', provvediamo a disattivare l'uscita Uno
        statoAttuale = inAttesaDiLow; // E ricordiamoci che abbiamo cambiato stato!
      } // Con l'istruzione --contatore abbiamo gia' decrementato il numero di cicli da attendere
      break;
    case inAttesaDiLow : // L'uscita Uno ha fatto il suo dovere, ora aspettiamo che l'ingresso ridiventi LOW
      if (digitalRead(ingresso) == LOW) { // L'ingresso di controllo e' diventato LOW?
        digitalWrite(uscitaDue, HIGH); // Si', provvediamo ad attivare l'uscita Due
        contatore = durataDue; // Carichiamo la durata dell'impulso di uscita
        statoAttuale = uscitaDueHigh; // E ricordiamoci che abbiamo cambiato stato!
      }
      break;
    case uscitaDueHigh : // Abbiamo attivato l'uscita Due, controlliamo se basta come durata
      if (--contatore <= 0) { // Pre-decrementiamo il contatore, siamo arrivati a zero?
        digitalWrite(uscitaDue, LOW); // Si', provvediamo a disattivare l'uscita Uno
        statoAttuale = inAttesaDiHigh; // E ricordiamoci che abbiamo cambiato stato!
      }
      break;
    default : // Per qualche motivo, non siamo in uno degli stati predefiniti?
      statoAttuale = inAttesaDiHigh; // Riconduciamoci ad un caso noto
      digitalWrite(uscitaUno, LOW);
      digitalWrite(uscitaDue, LOW);
      break; // Meglio metterlo, se un giorno dovessimo allungare la lista dello switch() fa comodo!
  }

  timer.OneShot (1000, action);

}

void loop () {

  timer.Update();

  int reading1 = digitalRead (releSS);
  if (reading1 == HIGH) {
    digitalWrite (ledSS, HIGH);
  } else {
    digitalWrite (ledSS, HIGH);
    delay (500);
    digitalWrite (ledSS, LOW);
    delay (500);
  }

  if (digitalRead (pulsON) == HIGH) {
    delay (100);
    digitalWrite (ledPinON, HIGH);
    digitalWrite (ledPinOFF, LOW);
  }

  if (digitalRead (pulsOFF) == HIGH) {
    delay (100);
    digitalWrite (ledPinOFF, HIGH);
    digitalWrite (ledPinON, LOW);
  }

  if (digitalRead (pulsI) == HIGH) {
    delay (100);
    digitalWrite (array1[cont1], LOW); //spengo led corrente
    cont1 = cont1 + 1; //incremento contatore led corrente
    if (cont1 >= maxArray1) //se il contatore è al massimo
    {
      cont1 = maxArray1;
    }
    digitalWrite (array1[cont1], HIGH); //accendi il nuovo led corrente
    while (digitalRead (pulsI) == HIGH); //attendi il rilascio di BUTTON1
    delay (100);
  }

  if (digitalRead (pulsD) == HIGH) {
    delay (100);
    digitalWrite (array1[cont1], LOW); //spengo led corrente
    cont1 = cont1 - 1; //incremento contatore led corrente
    if (cont1 <= maxArray2) //se il contatore è al massimo
    {
      cont1 = maxArray2;
    }
    digitalWrite (array1[cont1], HIGH); //accendi il nuovo led corrente
    while (digitalRead (pulsD) == HIGH); //attendi il rilascio di BUTTON1
    delay (100);
  }

  int reading3 = digitalRead(puls110e230);
  if (reading3 != lastButtonState3) {
    lastDebounceTime3 = millis();
  }
  if ((millis() - lastDebounceTime3) > debounceDelay3) {
    if (reading3 != buttonState3) {
      buttonState3 = reading3;
      if (buttonState3 == HIGH) {
        ledState3 = !ledState3;
        ledState4 = !ledState4;
      }
    }
  }
  digitalWrite (ledPin230, ledState3);
  digitalWrite (ledPin110, ledState4);
  lastButtonState3 = reading3;

  buttonStatus1 = digitalRead (buttonPinPROTa);
  buttonStatus2 = digitalRead (buttonPinPROTb);
  buttonStatus3 = digitalRead (buttonPinRESET);

  if (buttonStatus1 == HIGH && buttonStatus3 == LOW) {
    digitalWrite (ledPinPROTa, HIGH);
  }

  if (buttonStatus2 == HIGH && buttonStatus3 == LOW) {
    digitalWrite (ledPinPROTb, HIGH);
  }

  if (buttonStatus1 == LOW && buttonStatus3 == HIGH) {
    digitalWrite (ledPinPROTa, LOW);
  }

  if (buttonStatus2 == LOW && buttonStatus3 == HIGH) {
    digitalWrite (ledPinPROTb, LOW);
  }

  int reading4 = digitalRead(buttonPinPOWER);
  if (reading4 != lastButtonState4) {
    lastDebounceTime4 = millis();
  }
  if ((millis() - lastDebounceTime4) > debounceDelay4) {
    if (reading4 != buttonState4) {
      buttonState4 = reading4;
      if (buttonState4 == HIGH) {
        ledState5 = !ledState5;
        ledState6 = !ledState6;
      }
    }
  }
  digitalWrite (ledPinPOWERON, ledState5);
  digitalWrite (ledPinPOWEROFF, ledState6);
  lastButtonState4 = reading4;

  if (digitalRead (buttonPinTENSFISREG) == HIGH) {
    delay (100); //attesa antibounce
    digitalWrite (array2[cont2], LOW); //spengo led corrente
    cont2 = cont2 + 1; //incremento contatore led corrente
    if (cont2 == maxArray3) //se il contatore è al massimo
      cont2 = 0; //azzeralo
    digitalWrite (array2[cont2], HIGH); //accendi il nuovo led corrente
    while (digitalRead (buttonPinTENSFISREG) == HIGH); //attendi il rilascio di BUTTON1
    delay (100);
  }
  unsigned long myMs;
  enum FSM statoVecchio;

  statoVecchio = statoAttuale;

  while (1) {
    // Se lo stato cambia, stampiamo lo stato, e su due colonne i millisecondi contati da noi e quelli contati dal sistema
    if (statoAttuale != statoVecchio) {
      statoVecchio = statoAttuale; // Ricordiamoci dove siamo arrivato per le stampe
      Serial.print(F("Lo stato attuale e' "));
      switch (statoAttuale) { // Esercizio per "nascondere" le stringhe in FLASH e non in RAM che e' poca
        case inAttesaDiHigh :
          Serial.print(F("in attesa che l'ingresso passi da LOW a HIGH"));
          break;
        case uscitaUnoHigh :
          Serial.print(F("uscita Uno attiva"));
          break;
        case inAttesaDiLow :
          Serial.print(F("in attesa che l'ingresso passi da HIGH a LOW"));
          break;
        case uscitaDueHigh :
          Serial.print(F("uscita Due attiva"));
          break;
        default : // Non dovrebbe poter succedere ma...
          Serial.print(F("sconosciuto! Il valore numerico e' "));
          Serial.print(statoAttuale);
          break;
      }
      Serial.print(F(". Sono passati "));
      Serial.print(milliSecondi);
      Serial.print(F(" "));
      Serial.print(myMs = millis());
      Serial.println(F(" millisecondi."));
    }
  }
}

void action() {

  digitalWrite (releSS, HIGH);
}
Avatar utente
pgv
Messaggi: 484
Iscritto il: gio 17 set 2020, 13:16
Località: Ginevra

Re: se interruttore alto attiva un uscita altrimenti me ne attivi un'altra

Messaggio da pgv »

Cosi' a naso, ho come il sospetto che il congelamento sia dovuto a questa istruzione:

timer.OneShot (1000, action);

a riga 214 che si e' "infilata" nella ISR e viene eseguita ogni millisecondo invece di starsene dove sospetto voglia stare, nella setup(), per esempio subito prima o dopo la riga 162:

Serial.begin(9600);


<Correzione successiva>
MI sono accorto che c'e' un altro problemino con il codice: se il nostro OneShotTimer e' "caricato" per intervenire dopo 1000 millisecondi, e viene aggiornato solamente all'inizio della loop(), tutte le istruzioni delay() ne renderanno la risposta completamente fuori dai 1000 millisecondi di progetto. Cerco di costruirmi una descrizione del sistema per vedere se c'e' qualche modo astuto di risolvere il problema, magari in maniera elegante e didattica che non guasta.

P.S.: pur essendo un fisico di formazione, in effetti ho finito per programmare PLC e microcontrollori e progettare elettronica di acquisizione dati per mangiare... ma anche per passione.
Guido
Messaggi: 1443
Iscritto il: dom 18 mar 2018, 20:21

Re: se interruttore alto attiva un uscita altrimenti me ne attivi un'altra

Messaggio da Guido »

RoccoCostruzioni ha scritto: dom 20 set 2020, 17:30 Buongiorno a tutti ho necessità di comandare due uscite partendo da un ingresso.
l'ingresso è azionato da un relè di un modulo temporizzatore che mi funge da interruttore, se attivo il timer il relè si eccita e rimane eccitato per il tempo prestabilito diseccitandosi allo scadere del tempo, ora con questo ingresso ho bisogno di comandare due uscite distinte e cioè, all'attivazione del relè del temporizzatore mi manda alto la prima uscita per 100mS per poi andare basso, allo scadere del tempo il relè si diseccita mandandomi alto la seconda uscita per 100mS per poi andare basso.
Con la porzione di sketch che posto me lo fa ma mi blocca tutto il resto dello sketch eppure le istruzioni sono chiare!!
(nota di Guido: quoting del codice rimosso per non farla troppo lunga)
So perfettamente che la funzione delay è bloccante ma passati i 100mS si sblocca invece qua non si sblocca niente, secondo voi cosa non va?
Finalmente ho avuto il tempo di guardare con calma il codice nel primo messaggio di RoccoCostruzioni che a prima vista mi sembrava corretto, ed ho visto dove sta l'errore, in questa semplice istruzione:
while (digitalRead (timerPin) == LOW);
delay (100);
L'errore si manifesta quando il timer e' disattivo, ed una volta entrato in questa situazione non ne esce piu' perche' "while (digitalRead (timerPin) == LOW);" sara' sempre vera.
E' bastato togliere i cicli While ed aggiungere una variabile per fare in modo che il codice che viene eseguito con il timer OFF venga eseguito una sola volta. Siccome ho provato il codice su Thinkercad, simulatore di Arduino UNO, ho inserito il numero dei pin corrispondenti a questa versione di Arduino.
Bye
Guido
PS volendo si puo' fare il tutto con due transistor ed un paio di componenti passivi ...

Codice: Seleziona tutto

//#define timerPin 34 //
//#define ledPinTIMERON 35 //
//#define ledPinTIMEROFF 36 //

// VERSIONE ARDUINO UNO
#define timerPin 8
#define ledPinTIMERON 12
#define ledPinTIMEROFF 13 
boolean blnOneStep = false;

void setup() {
  
  pinMode (timerPin, INPUT);
  pinMode (ledPinTIMERON, OUTPUT);
  pinMode (ledPinTIMEROFF, OUTPUT);

  digitalWrite (ledPinTIMERON, LOW);
  digitalWrite (ledPinTIMEROFF, LOW);

}

void loop() {
 
 if (digitalRead (timerPin) == HIGH)
 {
    digitalWrite (ledPinTIMERON, HIGH);
    digitalWrite (ledPinTIMEROFF, LOW);
    delay (100);
    digitalWrite (ledPinTIMERON, LOW);
    digitalWrite (ledPinTIMEROFF, LOW);
    blnOneStep = true;
 }
 else if (blnOneStep == true)
 {
    digitalWrite (ledPinTIMEROFF, HIGH);
    digitalWrite (ledPinTIMERON, LOW);
    delay (100);
    digitalWrite (ledPinTIMEROFF, LOW);
    digitalWrite (ledPinTIMERON, LOW);
    blnOneStep = false;
  }

}
RoccoCostruzioni
Messaggi: 51
Iscritto il: mer 9 set 2020, 21:16

Re: se interruttore alto attiva un uscita altrimenti me ne attivi un'altra

Messaggio da RoccoCostruzioni »

si Guido hai ragione ma applicando il listato scritto da te con l'aggiunta della variabile, ottengo che ledPinTIMERON risulta sempre acceso quando timerPin è HIGH, quando poi passa a LOW si spegne ledPinTIMERON e si accende ledPinTIMEROFF per 100mS, se voglio variare i tempi ad esempio 500mS comunque è bloccante e non va bene, meglio il listato di PGV
Per quanto riguarda l'uso di componenti passivi preferirei che tutto si svolgesse via software per evitare di autocostruirmi schede millefori ecc tutto qui.
grazie comunque a tutti
RoccoCostruzioni
Messaggi: 51
Iscritto il: mer 9 set 2020, 21:16

Re: se interruttore alto attiva un uscita altrimenti me ne attivi un'altra

Messaggio da RoccoCostruzioni »

Guido ho corretto il tuo listato e così funziona bene

Codice: Seleziona tutto

if (digitalRead (timerPin) == HIGH) {
    delay (100);
    if (blnOneStep == false) {
    digitalWrite (ledPinTIMERON, HIGH);
    digitalWrite (ledPinTIMEROFF, LOW);
    delay (100);
    digitalWrite (ledPinTIMERON, LOW);
    digitalWrite (ledPinTIMEROFF, LOW);
    blnOneStep = true;
  }
  } else 
  if (blnOneStep == true) {
    digitalWrite (ledPinTIMEROFF, HIGH);
    digitalWrite (ledPinTIMERON, LOW);
    delay (100);
    digitalWrite (ledPinTIMEROFF, LOW);
    digitalWrite (ledPinTIMERON, LOW);
    blnOneStep = false;
  }
ovviamente per me all'accensione timerPin è LOW

Cosa ne pensa PGV di questa modifica? Comunque senza ombra di dubbio il funzionamento con la modifica di PGV va molto meglio!!
Guido
Messaggi: 1443
Iscritto il: dom 18 mar 2018, 20:21

Re: se interruttore alto attiva un uscita altrimenti me ne attivi un'altra

Messaggio da Guido »

RoccoCostruzioni ha scritto: dom 11 ott 2020, 13:01 si Guido hai ragione ma applicando il listato scritto da te con l'aggiunta della variabile, ottengo che ledPinTIMERON risulta sempre acceso quando timerPin è HIGH, quando poi passa a LOW si spegne ledPinTIMERON e si accende ledPinTIMEROFF per 100mS, se voglio variare i tempi ad esempio 500mS comunque è bloccante e non va bene, meglio il listato di PGV
Per quanto riguarda l'uso di componenti passivi preferirei che tutto si svolgesse via software per evitare di autocostruirmi schede millefori ecc tutto qui.
grazie comunque a tutti
Giusto ! : per quanto riguarda il problema che ledPinTIMERON risulta sempre acceso finche' timerPin è HIGH ho corretto aggiungendo un'altra variabile; per quanta riguarda il problema del delay bloccante per tempi lunghi bisognerebbe usare una interrupt.
Per il primo problema ho scritto il codice ed ora te lo sorbisci. Hai voluto la bicicletta ? :lol:
Bye
Guido

Codice: Seleziona tutto

//#define timerPin 34 //
//#define ledPinTIMERON 35 //
//#define ledPinTIMEROFF 36 //
// per ARDUINO UNO
#define timerPin 8
#define ledPinTIMERON 12
#define ledPinTIMEROFF 13 
boolean blnOneShotON = true;
boolean blnOneShotOFF = false;

void setup() {
  
  pinMode (timerPin, INPUT);
  pinMode (ledPinTIMERON, OUTPUT);
  pinMode (ledPinTIMEROFF, OUTPUT);

  digitalWrite (ledPinTIMERON, LOW);
  digitalWrite (ledPinTIMEROFF, LOW);

}

void loop() {
 
 if ((digitalRead (timerPin) == HIGH) && (blnOneShotON == true))
 {
    digitalWrite (ledPinTIMERON, HIGH);
    digitalWrite (ledPinTIMEROFF, LOW);
    delay (100);
    digitalWrite (ledPinTIMERON, LOW);
    digitalWrite (ledPinTIMEROFF, LOW);
    blnOneShotON = false;
    blnOneShotOFF = true;
 }
 if ((digitalRead (timerPin) == LOW) && (blnOneShotOFF == true))
 {
    digitalWrite (ledPinTIMEROFF, HIGH);
    digitalWrite (ledPinTIMERON, LOW);
    delay (100);
    digitalWrite (ledPinTIMEROFF, LOW);
    digitalWrite (ledPinTIMERON, LOW);
    blnOneShotON = true;
    blnOneShotOFF = false;

  }

}
RoccoCostruzioni
Messaggi: 51
Iscritto il: mer 9 set 2020, 21:16

Re: se interruttore alto attiva un uscita altrimenti me ne attivi un'altra

Messaggio da RoccoCostruzioni »

io mi sorbisco tutto :lol: :lol: :lol: :lol:
Rispondi