Nov 182017
 

Vediamo oggi come interfacciare ad Arduino un piccolo joystick similare a quelli usati sulla PS2 per creare delle semplici interfacce utente.

Introduzione

La scorsa volta abbiamo visto come collegare un modulo RTC ad Arduino per “tenere a mente” data ed ora anche in caso di mancanza di alimentazione. Abbiamo visto che uno dei problemi principali è l’interfacciamento con Arduino per effettuare le regolazioni dell’ora. Oggi vediamo come aggiornare il precedente progetto aggiungendo un piccolo joystick che ci permetterà  di interfacciare l’utente con Arduino. In questo articolo useremo l’interfaccia seriale per visualizzare l’output di Arduino e solo dal prossimo articolo aggiungeremo un display ed andremo effettivamente a modificare l’ora del modulo RTC.

Il  joystick

Se non li avete mai visti, ci sono dei piccoli moduli già pronti all’uso simili a quelli che vi riporto nella foto qui accanto. Il vantaggio nell’usare uno di questi è che in un solo dispositivo fisico avete ben 5 diversi “ingressi”, infatti il joystick può essere mosso nelle 4 direzioni ed in più può essere premuto per cui è in grado di sostituire ben 5 pulsanti separati. 

Se volete acquistarli vi lascio due link qui sotto, il primo per un singolo modulo, il secondo a mio parere più conveniente, per l’acquisto di 5 moduli (attenzione che se non acquistate almeno 29€ di prodotti vi caricano 3.99€ di spedizione).

In alternativa potete andare sul mercato cinese e prenderne 5 al prezzo di 3.84€ spedizione inclusa, aspetterete di più ma se non avete fretta è sicuramente l’opzione più conveniente. Se non lo sapete, per ordini al di fuori dell’UE, per importi dichiarati inferiori a 22 euro non si pagano dazi / spese doganali per cui attenzione a non esagerare con la merce acquistata magari nella frenesia di prendere un sacco di cose a basso prezzo, perchè rischiate poi di perdere tutto il vantaggio dell’acquisto “cinese” a fronte di un’attesa molto più lunga (20-30 giorni). Se vi sembra che stia facendo della pubblicità avete perfettamente ragione, infatti acquistando attraverso uno di quei link farete in modo che una piccola quota venga versata al blog aiutandomi a non restare costantemente con i conti in rosso visto che comunque resto in costante perdita.

Collegamenti

Dopo queste divagazioni vediamo come collegare il nostro joystick ad Arduino. Massa e VCC andranno chiaramente collegati a GND e 5V di Arduino (inteso su Arduino UNO e famiglia, altrimenti potrà essere usata la 3.3V). Poi abbiamo altri 3 pin, uno per l’asse X, uno per l’asse Y ed uno per rilevare la pressione del pulsante. Quest’ultimo lo collegheremo ad uno degli ingressi digitali mentre gli altri due andranno connessi a due ingressi analogici. Come? Analogici? Ebbene si, perchè ciò che vi ho detto prima sulla possibilità di rilevare le 4 direzioni è solo una mezza verità. Questi moduli rilevano in analogico il movimento sui 2 assi per cui tramite la lettura di due tensioni rilevano la posizione nello spazio sugli assi X ed Y. Potremmo perciò rilevare anche movimenti obliqui o movimenti solo parziali sugli assi X e Y. Ma per il lavoro che dobbiamo fare, ossia la semplice interazione con l’utente, non ci servono tutte queste funzionalità, ci basterà “simulare” l’utilizzo delle 4 direzioni.

Quando andremo a leggere i valori restituiti dal joystick, perciò, da una parte ci sarà il valore LOW/ HIGH per il pulsante premuto o meno, dall’altra ci saranno due valori numerici che rappresentano una tensione fra 0 e 5V (o 0 – 3.3V a seconda del tipo di Arduino) a seconda del grado di “inclinazione” lungo i due assi. Chiaramente quando il joystick è rilasciato si troverà a metà scala su entrambi gli assi.

Per quanto riguarda il pulsante, questo è connesso a massa quando il pulsante è premuto, in caso contrario il pin è flottante per cui dobbiamo provvedere a collegarlo tramite resistore ai 5V. Per farlo mi adeguerò però alla stragrande maggioranza degli esempi che trovate in rete per cui per una volta anzichè usare un resistore esterno,  sfrutterò il resistore di pullup interno dell’Atmega il che mi semplifica la vita (certo che potevano sprecarsi ad aggiungere un resistore in più).

Un semplice esempio

#define asseX A0 
#define asseY A1 
#define PressPin 8

void setup ()
{
  Serial.begin (9600); 
  pinMode (PressPin, INPUT_PULLUP);
}
void loop ()
{
 int xVal, yVal;
 bool buttonVal;
 
 xVal = analogRead (asseX);
 yVal = analogRead (asseY);
 buttonVal = digitalRead (PressPin);
 Serial.print("X = ");
 Serial.println (xVal, DEC);
 Serial.print ("Y = ");
 Serial.println (yVal, DEC);
 Serial.print("Button is ");
 if (buttonVal == LOW) Serial.println ("PRESS");
 else Serial.println ("Not Press");
 delay (500);
}

Il listato è estremamente semplice e non fa altro che leggere le due tensioni X ed Y rispettivamente sugli ingressi A0 ed A1, mentre sul pin 8 andrà a leggere la pressione o meno del pulsante. Ora diventa molto semplice convertire il segnale analogico in qualcosa di più simile ad un’input digitale. Ad esempio potremmo dire ad Arduino che un valore sull’asse X inferiore a 200 corrisponde allo spostamento a sinistra, mentre un valore superiore a 800 corrisponde ad uno spostamento verso destra. La stessa cosa possiamo farla sull’asse Y.

Fate però attenzione che se spostiamo il jostick in direzioni oblique, è possibile modificare in contemporanea il valore dei due assi cosa che potrebbe essere desiderata o da evitarsi. Se proviamo il listato ci accorgiamo però di alcuni dettagli che non influiscono sul nostro progetto ma che è bene cooscere. Se usiamo Arduino UNO i valori restituiti sui due assi dovrebbero variare da 0 a 1023 per cui lasciando il joystick fermo dovremmo cadere a metà scala, quindi intorno ai 512. Sarà realmente così? No, non proprio, ad esempio nel mio caso appena aperto il monitor seriale con il joystick in posizione neutra ho letto X=493 ed Y=521. Per il nostro scopo la cosa è del tutto ininfluente, ma se usaste questo approccio per muovere un braccio meccanico o pilotare un drone, allora questi valori corrisponderebbero ad un leggero movimento che andrebbe corretto via software. Allo stesso modo il fondo scala dei due assi è stato intorno a 1021 per cui non potete usare qualcosa del tipo if(asseX==1023) in quanto è una condizione che, almeno sul mio campione di jostick, non viene mai soddisfatta. Attenzione che i numeri che vi ho passato sono del tutto variabili, ad esempio rifacendo le stesse prove la sera della medesima giornata, trovo una posizione centrale a 480, 508 ed un fondo scala che non raggiunge neanche il 1000.

Menu contestuale

Lo scopo di questo articolo era creare un semplice menu per far interagire Arduino con l’essere umano, vediamo come possiamo creare da zero un sistema di menu attrraverso il quale possiamo “navigare” con il nostro joystick. Abbiamo una miriade di modi per creare ciò, ma dobbiamo ricordarci che stiamo lavorando con un processore che ha caratteristiche computazionali e di memoria molto limitate per cui dobbiamo pensare ad un sistema che permetta di risparmiare al massimo operazioni e memoria non indispensabili. Ciò si rifletterà in un sistema non molto flessibile, ma è anche vero che non abbiamo bisogno di un sistema che cambi dinamicamente, a noi serve unicamente un menu fisso per “gestire” alcune operazioni elementari. Ho pensato di usare una semplice struttura composta da 5 bytes più la stringa con il testo da contenere per la specifica voce di menu. A parte i 5 bytes da noi dedicati, abbiamo anche l’indice 0-254 dell’array che fungerà anche da identificativo della singola voce di menu. Il  successivi 5 bytes indicano rispettivamente l’id del menu che precede, del seguente, l’eventuale figlio (sottomenu) o l’eventuale padre che permette ad un sottomenu di identificare il proprio genitore. L’ultimo indica un valore numerico che viene ritornato quando la voce di menu viene selezionata. Nella foto qui vicina potete vedere l’esempio che ho utilizzato per questo articolo. Vediamo come funziona.

struct MenuItem
{
 unsigned char Previous;
 unsigned char Next;
 unsigned char Child;
 unsigned char Parent;
 unsigned char Fire;
 char testo[20];
};

Fate estrema attenzione alla lunghezza delle stringhe, nel prototipo che stiamo preparando ho usato 20 caratteri in quanto corrisponde alla dimensione di molti display, ma se ci inserite una stringa più lunga andrete a fare un piccolo grande disastro. Ora non dobbiamo far altro che inserire le voci di menu che ci interessano. Ad esempio prepariamo due menu principali per la regolazione separata di data ed ora, e poi dei sottomenu per le regolazioni da una parte di ora e minuti, dall’altra di giorno, mese ed anno. Fate due conti e vedrete che ci servono 7 voci di menu perciò aggiungeremo:

struct MenuItem ArduinoMenu[7];

poi ci prepariamo una comoda funzione per settare tutte le varie voci della struttura senza scrivere decine di linee di codice ripetitive e di ridotta leggibilità.

void SetItemMenu(struct MenuItem * AI,unsigned char P,unsigned char N,unsigned char C,unsigned char Pr,char * testo,unsigned char Fire)
{
 AI->Previous=P;
 AI->Next=N; 
 AI->Child=C; 
 AI->Parent=Pr; 
 strcpy(AI->testo,testo); 
 AI->Fire=Fire;
}

A questo punto settiamo tutte le voci del menu:

 SetItemMenu(&ArduinoMenu[0],1,1,2,0,"Settaggio Orario",100);
 SetItemMenu(&ArduinoMenu[1],0,0,1,1,"Settaggio Data",110);

 SetItemMenu(&ArduinoMenu[2],3,3,2,0,"Set Ora",120);
 SetItemMenu(&ArduinoMenu[3],2,2,3,0,"Set Min",130);

 SetItemMenu(&ArduinoMenu[4],6,5,4,1,"Set Giorno",140);
 SetItemMenu(&ArduinoMenu[5],4,6,5,1,"Set Mese",150);
 SetItemMenu(&ArduinoMenu[6],5,4,6,1,"Set Anno",160);

A questo punto dobbiamo entrare nel vivo del listato, ci servono ancora due porzioni di codice, la prima che identifica il movimento del joystick e nel momento in cui “sente” che stiamo impartendo un comando invoca una funzione che determinerà cosa fare, ossia in base al menu in cui siamo localizzati in un certo momento, andrà a spostarsi eventualmente in una diversa voce di menu ed andrà poi a stampare a video il contenuto del nuovo menu. Visto che dobbiamo identificare solamente gli estremi dello spostamento del joystick ho impostato rispettivamente le soglie di 200 e 800 per far “scattare” i relativi comandi, soglie che eventualmente potete modificare per rendere il sistema più o meno reattivo in base all’usabilità che ne deriva.

Mettiamo tutto insieme

Qui sotto vediamo il listato completo. Ho usato la porta seriale per non complicare il tutto con pezzi di codice che non interessavano direttamente l’articolo di oggi. Nel prossimo andremo a mettere insieme il modulo RTC, questo listato sul menu contestuale ed un display LCD in modo da poter settare e visualizzare l’ora corrente sul display.

#define asseX A0 
#define asseY A1 
#define PressPin 4

#define Menu_UP 1
#define Menu_DOWN 2
#define Menu_LEFT 4
#define Menu_RIGHT 8
#define Menu_PRESS 16


struct MenuItem
{
 unsigned char Previous;
 unsigned char Next;
 unsigned char Child;
 unsigned char Parent;
 unsigned char Fire;
 char testo[40];
};

 unsigned char index; 
 int xVal, yVal, KVal; // asseX, asseY e pulsante
 unsigned char buttonVal;
 struct MenuItem ArduinoMenu[7];

void setup ()
{
 Serial.begin (9600); 
 pinMode (PressPin, INPUT_PULLUP);
 //pinMode (PressPin, INPUT);
 //digitalWrite(PressPin, HIGH);
 
 //le 7 righe qui sotto preparano il menu come nello schema che vi ho allegato
 SetItemMenu(&ArduinoMenu[0],1,1,2,0,"Settaggio Orario",100);
 SetItemMenu(&ArduinoMenu[1],0,0,4,1,"Settaggio Data",110);

 SetItemMenu(&ArduinoMenu[2],3,3,2,0,"Set Ora",120);
 SetItemMenu(&ArduinoMenu[3],2,2,3,0,"Set Min",130);

 SetItemMenu(&ArduinoMenu[4],6,5,4,1,"Set Giorno",140);
 SetItemMenu(&ArduinoMenu[5],4,6,5,1,"Set Mese",150);
 SetItemMenu(&ArduinoMenu[6],5,4,6,1,"Set Anno",160);

 index = 0; // idice di partenza
 Serial.println(ArduinoMenu[index].testo); // stampo il menu iniziale
}

void loop ()
{
 xVal = analogRead (asseX); // lettura asse x
 yVal = analogRead (asseY); // lettura asse y
 buttonVal = digitalRead (PressPin); // lettura pulsante

 if(xVal<200) MenuReaction(ArduinoMenu,&index,Menu_LEFT); // hai mosso a sinistra?
 else if(xVal>800) MenuReaction(ArduinoMenu,&index,Menu_RIGHT); // a destra?
 
 if(yVal>800) MenuReaction(ArduinoMenu,&index,Menu_DOWN); //giu'?
 else if(yVal<200) MenuReaction(ArduinoMenu,&index,Menu_UP); // su?

if (buttonVal == LOW) MenuReaction(ArduinoMenu,&index,Menu_PRESS); // hai premuto il pulsante

}


void SetItemMenu(struct MenuItem * AI,unsigned char P,unsigned char N,unsigned char C,unsigned char Pr,char * testo,unsigned char Fire)
{
 AI->Previous=P;
 AI->Next=N; 
 AI->Child=C; 
 AI->Parent=Pr; 
 strcpy(AI->testo,testo); 
 AI->Fire=Fire;
 
}

void MenuReaction(struct MenuItem * MenuBase,unsigned char * index,unsigned char action)
{
 // decido cosa fare in base al movimento selezionato e alla posizione nel menu
 struct MenuItem local;
 local=MenuBase[*index];
 if(action==Menu_UP) 
 {
 *index=local.Previous;
 Serial.print("UP : ");
 }
 if(action==Menu_DOWN) 
 {
 *index=local.Next;
 Serial.print("DOWN : ");
 }
 if(action==Menu_LEFT) 
 {
 *index=local.Parent;
 Serial.print("LEFT : ");
 }
 if(action==Menu_RIGHT) 
 {
 *index=local.Child;
 Serial.print("RIGHT : ");
 }
 if(action==Menu_PRESS) 
 {
 Serial.println("FIRE : ");
 }
 local=MenuBase[*index]; 
 Serial.println(local.testo); // stampo il nuovo menu

 delay(500); // inibisco nuove letture per mezzo secondo altrimenti si scatenanodecine di eventi
}

Anche per oggi è tutto, spero che questo articolo sia utile a tutti coloro che devono creare un semplice menu contestuale. Vi lascio con un breve video che mostra il funzionamento del menu. Nel video si vede anche il modulo RTC che però non è utilizzato, è li pronto per il prossimo articolo.




Salva