Cosa è un knobster? Questo:

Sostanzialmente è costituito da 2 rotelline sovrapposte ed un pushbutton in cima; collegato al PC tramite USB viene visto come un joystick / button box; si utilizza con i simulatori di volo ed emula l'analogo comando presente in molti strumenti avionici (GPS, autopilota, selettore frequenze radio, eccetera).
La versione commerciale di questo oggettino viene venduta all'esorbitante cifra di diconsi euro 90 ( :o ); considerato che bastano due encoder rotativi (o un singolo encoder a doppio asse) con pushbutton, pochi grammi di plastica e un Arduino per costruirselo fai-da-te, il vantaggio rispetto alla versione commerciale è innegabile.
Per la parte hardware, avendo già a disposizione due encoder rotativi con pushbutton (questi), ho modificato questo ingegnoso progetto che utilizza due ingranaggi stampati in 3d per trasferire sullo stesso asse il movimento di due encoder rotativi affiancati. Poiché l'autore di questo sistema usa un Raspberry Pi per far funzionare il tutto, sto cercando di scrivere uno sketch per collegarlo all'Arduino Leonardo che già possiedo.
L'obiettivo è fare in modo che il dispositivo sia visto come un button box con 5 pulsanti: rotazione a destra encoder 1, rotazione a sinistra encoder 1, rotazione a destra encoder 2, rotazione a sinistra encoder 2, pushbutton.
Il mio sketch è questo:
#include <Joystick.h>
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD,
5, 0, // Button Count, Hat Switch Count
false, false, false, // X, Y and Z Axis
false, false, false, // Rx, Ry, Rz
false, false, // rudder, throttle
false, false, false); // accelerator, brake, steering
const int pinA = 0;
const int pinB = 1;
const int pinC = 2;
const int pinD = 3;
const int puls = 4;
volatile int previous1 = 0;
volatile int counter1 = 0;
volatile int previous2 = 0;
volatile int counter2 = 0;
void setup() {
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
pinMode(pinC, INPUT_PULLUP);
pinMode(pinD, INPUT_PULLUP);
pinMode(puls, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(pinA), changed, CHANGE);
attachInterrupt(digitalPinToInterrupt(pinB), changed, CHANGE);
attachInterrupt(digitalPinToInterrupt(pinC), changed, CHANGE);
attachInterrupt(digitalPinToInterrupt(pinD), changed, CHANGE);
Joystick.begin();
}
int pinToButtonMap = 0;
void changed() {
int A = digitalRead(pinA);
int B = digitalRead(pinB);
int C = digitalRead(pinC);
int D = digitalRead(pinD);
int current1 = (A << 1) | B;
int combined1 = (previous1 << 2) | current1;
int current2 = (C << 1) | D;
int combined2 = (previous2 << 2) | current2;
if(combined1 == 0b0010 ||
combined1 == 0b1011 ||
combined1 == 0b1101 ||
combined1 == 0b0100) {
counter1++;
}
if(combined1 == 0b0001 ||
combined1 == 0b0111 ||
combined1 == 0b1110 ||
combined1 == 0b1000) {
counter1--;
}
previous1 = current1;
if(combined2 == 0b0010 ||
combined2 == 0b1011 ||
combined2 == 0b1101 ||
combined2 == 0b0100) {
counter2++;
}
if(combined2 == 0b0001 ||
combined2 == 0b0111 ||
combined2 == 0b1110 ||
combined2 == 0b1000) {
counter2--;
}
previous2 = current2;
}
int lastButtonState = 0;
void loop(){
if(counter1 >= 4) {
Joystick.setButton(0, 1);
delay(50);
Joystick.setButton(0, 0);
counter1 -= 4;
} else if(counter1 <= -4) {
Joystick.setButton(1, 1);
delay(50);
Joystick.setButton(1, 0);
counter1 += 4;
}
if(counter2 >= 4) {
Joystick.setButton(2, 1);
delay(50);
Joystick.setButton(2, 0);
counter2 -= 4;
} else if(counter2 <= -4) {
Joystick.setButton(3, 1);
delay(50);
Joystick.setButton(3, 0);
counter2 += 4;
}
int currentButtonState = !digitalRead(puls);
if (currentButtonState != lastButtonState)
{
Joystick.setButton(4, currentButtonState);
lastButtonState = currentButtonState;
}
delay(50);
}
Il pushbutton funziona perfettamente: il pulsante si attiva quando premo il bottone, resta attivo se lo tengo premuto e si disattiva se lo mollo.
Per gli encoder però un comportamento del genere non è adatto, perché vorrei che le due rotelline funzionassero come una rotella del mouse: ad ogni click degli encoder a destra o a sinistra deve corrispondere un click del relativo pulsante.
Con lo sketch qui sopra viene letta bene la direzione degli encoder: si attiva il pulsante giusto a seconda del verso in cui lo ruoto. Inizialmente avevo usato un metodo diverso, senza l'attachinterrupt, in modo da avere più libertà sui PIN da usare, ma la direzione della rotazione non veniva letta in maniera precisa e a volte si attivava il pulsante relativo al verso opposto di rotazione; ho letto che per evitare il problema sarebbe stato necessario collegare l'encoder ad un circuito di debounce usando resistenze e condensatori, che avrebbero reso più complicato il progetto; usando questo sketch invece la direzione della rotazione viene letta sempre in maniera precisa.
Il problema attuale tuttavia è che col mio sketch la corrispondenza tra click dell'encoder e click del pulsante non è precisa, nel senso che a volte alcuni click dell'encoder vanno a vuoto (il pulsante non si attiva) e ciò avviene in maniera totalmente random, senza un rapporto fisso o costante tra click dell'encoder e click del pulsante.
Come potrei modificare il mio sketch in modo da ottenere una corrispondenza 1:1 tra i click dell'encoder e i click del pulsante? E' fattibile via software? Potrebbe altrimenti aiutare quel famoso circuito di debounce con resistenze e condensatori o in questo caso sarebbe ininfluente?
Grazie a chi mi vorrà aiutare.

