Salve a tutti
Vi spiego lo sketch al quale sto lavorando ormai da qualche giorno.
Dovrei far aprire un ingresso al comando di un telecomando IR.
Se però all'apertura della barra un auto rimane bloccata sotto ,un sensore di ostacolo IR deve rilevarla e far rimanere bloccata la barra fino al transito dell'auto.
Cosa semplice almeno a dirsi per me.
Dopo tanti esperimenti sono riuscito a far funzionare il comando con gli IR, ed il servo funziona perfettamente.
Il problema mi si presenta nel comandare il blocco e lo sblocco della barra secondo il segnale del sensore di ostacolo.
Praticamente vorrei che venisse sempre controllato lo stato del pin 5 del sensore ad ogni ciclo per leggerne lo stato e uscire o entrare in un ciclo piuttosto che un altro, o compiere una determinata azione o un altra.
Un po come Servo.resume() per la libreria IRremote, alla fine di ogni ciclo.
Ho provato a fare una funzione "ricorsiva" , non so se sia fatta bene però perchè è la prima volta che tento!
Potreste aiutarmi a capire dove sbaglio?
grazie mille
Allego lo sketch:
#include <Servo.h>
#include <IRremote.h>
#define BarUp 97 //rotazione servo barra in salita
#define BarLow 177 // rotazione servo barra in discesa
bool valOst ;
const int ostPin =5;
int IRpin = 11; // pin output sensore IR
int LED = 2; // LED pin
IRrecv irrecv(IRpin); //crea oggetto IRrecv di nome irrecv,e riceve come argomento IRpin
decode_results risultati; // crea il contenitore per il risultato decodificato di nome risultati
Servo RCservo;
bool LEDon = true; // inizializzazione di LEDon come true (led di Stato)
void setup ()
{
Serial.begin(9600);
irrecv.enableIRIn(); // Avvia il ricevitore
pinMode(LED, OUTPUT);
pinMode (ostPin,INPUT);
RCservo.attach (9); // pin del Servo
RCservo.write(BarLow); //azzera barra
delay(1000);
}
void loop()
{
if (irrecv.decode(&risultati))
{
irrecv.resume(); // Riceve il valore successivo
}
if (risultati.value == 3622325019) // codice del telecomando IR
{
Barra();
if (LEDon == true) // LEDon è uguale a true?
{
LEDon = false;
digitalWrite (LED, HIGH);
delay(100); // mantiene fluida la transizione
}
else
{
LEDon = true;
digitalWrite(LED, LOW);
delay(100);
}
}
}
void Barra(){
Serial.println(risultati.value);
delay(50);
valOst = digitalRead (ostPin);
Serial.println(valOst);
if (digitalRead (ostPin)){
RCservo.write (BarUp);
delay(3000);
ControlloOstacolo();
RCservo.write(BarLow);
}
}
void ControlloOstacolo(){
if (valOst==false) {
delay(200);
Barra();
}
}
praticamente il mio obiettivo è che se il controllo ostacolo restituisce false, esce dal ciclo e torna alla prima riga della funzione Barra(), un po come le istruzioni di salto in Assembly.
In C ancora sono poco pratico!
Ho letto la tua presentazione dove menzioni il linguaggio assembly. Le difficoltà che incontri sono principalmente legate alle pregresse conoscenze di programmazione. Stessa cosa avviene quando il programmatore C passa ad un linguaggio di alto livello (tipo python) dove il legame con l'hardware scompare.
I termini stack, loop e ricorsione dovrebbero esserti familiari, per cui li uso. Con Arduino (il C/C++ embedded) l'architettura software è quello conosciuta come superloop. C'è un solo loop principale appunto la funzione loop(). Attenzione questa termina e una funzione nascosta nel core lib di ardunio chiama nuovamente la funzione loop(). Questo fa si che le variabili dentro la funzione loop sono locali e vivono nello stack, quando la funzione (tutte si comportano così) termina anche le variabili locali cessano di esistere.
Con l'assembly il programmatore ha tutte le responsabilità e un controllo totale della CPU. In C le responsabilità sono minori poiché non serve usare i registri della CPU, salvarli dopo un jump ecc, ci pensa il C a chiamare la funzione e quando questa termina la cpu torna ad eseguire l'istruzione seguente alla chiamata.
La ricorsione in C non può essere infinita, in assembly il controllo dello stack (ed in genere di tutta la ram) ricade sul programmatore. In C chiamare il chiamante consuma lo stack, già la ram è poca, non abbiamo un sistema operativo che ci può impedire di mettere in crisi il sistema e quindi la ricorsione può facilmente portare a comportamenti del programma imprevedibili.
Nel tuo programma la sequenza di chiamate è la seguente:
loop -> barra -> ControlloOstacolo .> barra
C'è ricorsione non controllata, ed è via senza uscita.
Una soluzione la si trova impiegando gli interrupt e una macchina a stati finiti che per semplicità è composta da uno switch case. Se cerchi sul forum trovi buone cose sulle state machine.
Rimanderei per il momento di tentare di sperimentare con gli interrupt ma documentati e tra l'altro il termine di dovrebbe fare ricordare qualcosa.
Io mi sono dilungato parecchio e mi fermo qui dicendoti che questa problematica è conosciuta e tutti (incluso io all'inizio) ci hanno dovuto sbattere il muso.
...ma non si usa, non serve, come in ogni linguaggio strutturato se ne può fare a meno, e tra l'altro è usabile solo in salti interni alla stessa funzione.
L'indicazione corretta è quella della macchina a stati: strutturare l'intero programma sotto forma di "frasi":
Se sono nella situazione X:
SE rilevo evento Y:
azione Z
Ad esempio descrivere la logica in un modo simile a questo:
SE situazione==1
SE pulsantePremuto:
....azioni....
ALTRIMENTI SE situazione==2
SE timeout:
....azioni....
ecc...
...e non usare lunghi delay per generare i tempi, la funzione loop deve girare liberamente per essere sempre reattiva a quello che avviene.
Ogni pausa va trasformata nella permanenza in una certa situazione X finché non avviene un evento Y che la fa terminare, cioè la fa passare ad una nuova nuova situazione X (la situazione X è un valore a piacere mantenuto in una variabile di stato).
Ad ogni ciclo di esecuzione va elaborato solo lo stato attivo.
Si parte dal disegnare un diagramma degli stati possibili e degli archi che portano da uno stato all'altro quando avviene un evento. Quando il diagramma su carta funziona, il passaggio in istruzioni C è praticamente una traduzione 1:1
Questo modo di procedere permette di risolvere tutti i problemi di automazione presenti passati e futuri, compreso il portare avanti in parallelo più compiti contemporaneamente.
Grazie mille a entrambi
Terrò a mente i vostri consigli
In effetti come dice Maurotec mi è rimasta un pò l'abitudine di vedere le cose secondo un linguaggio che non centra nulla o quasi con questo
Devo imparare a guardare le cose in altro modo
Proseguiamo con la logica di controllo_barra: int ostPin; è un refuso, li non serve a nulla.
Poi alziamo la barra, controlliamo se c'è un ostacolo (nel qual caso attendiamo che non ci sia più), attendiamo tre secondi, e riabbassiamo la barra.
Ma che succede se l'ostacolo arriva durante quei tre secondi?
in effetti a volte incappo in situazioni ridondanti!
forse devo prenderci la mano, nel frattempo ho provveduto.
Io per accorgermi dell'errore in effetti ho dovuto fare varie prove pratiche, poi mi spieghi come sei riuscito a capirlo dallo sketch?
Capire gli errori anche dalla carta è veramente utile!
Per questo ho risolto con un doppio controllo prima della chiusura barra:
void controllo_barra(){
int ostPin;
Serial.println(risultati.value);
delay(50);
RCservo.write (BarUp);
controllo_ostacolo(); // effettua il controllo della barra
delay (3000);
controllo_ostacolo(); //viene eseguito un doppio controllo nel caso entri un auto mentre la barra è aperta
delay(2000); // se il passaggio è libero attende 2s e chiude la barra
RCservo.write(BarLow);
Si certo, ti riferisci a uint32_t che tradotto unigned int 32 bit _t solo per indicare che si tratta di un tipo di dato. In sostanza è un unsigned long. Grazie alle typedef predefinite ci sono degli alias, vedi qui
Sotto coperta c'è la standard C library ed è proprio quella usata da arduino con architettura AVR 8-bit.
La trovi in fondo allo scketch, era inclusa nell'esempio di uso della IRremote su wokwi, io l'ho riadattato e non ho ripulito.
Saranno 2 mesi che lo uso, lo trovo comodo per mostrare porzioni di sketch, anziché postare sul forum. Così l'utente può verificare il funzionamento online senza dovere scrivere in flash.
Mi ci sono avvicinato timidamente è l'ho trovato molto complesso, ma mi pare di capire che ci puoi fare di tutto
virtualmente. Però non mi sono iscritto, e poi poco dopo ho scoperto sempre qui sul forum wokwi e la sua immediatezza mi ha colpito.