Arduino Macro Pad
Arduino Macro Pad è un dispositivo che ha in ingresso una tastiera formata da 12 pulsanti, dove – a ogni tasto – viene associato comando il quale viene inviato al computer tramite comunicazione seriale.
Il punto fondamentale quest'interfaccia è che i comandi (da taglia/copia/incolla in su) possono interagire con qualsiasi applicazione.
Il progetto si ispira a un altro prodotto già in commercio, ma dai costi notevolmente più elevati (oltre 150 €).
Premesse
[modifica]Nel considerare la realizzazione di un simile progetto bisogna considerare diversi fattori:
- il fatto che Arduino debba comunicare con il dispositivo al quale è collegato, genera un vincolo riguarda alla scelta della scheda da utilizzare tra Arduino Micro; Pro Micro o Arduino Leonardo. Volendo realizzare qualcosa che sia il più facilmente trasportabile possibile, la scelta ricade sulla scheda Arduino Pro Micro;
- dopo la scelta della scheda è bene tenere a mente la disponibilità dei pin da poter utilizzare per il progetto, di conseguenza stabilire il principio di funzionamento del dispositivo;
- se si desidera realizzare un prodotto finale ben fatto, occorre trovare una soluzione: in questo caso, tramite l'utilizzo di un CAD di modellazione 3D, modelleremo i pezzi necessari per il progetto, argomento che verrà approfondito in un paragrafo dedicato
Principio di funzionamento
[modifica]In primo luogo per aiutare l'utente a ricordare facilmente i vari comandi Macro utilizzeremo un display OLED di risoluzione 64 x 128, che risulta ideale in quanto non eccessivamente ingombrante e abbastanza visibile e leggibile. tramite 12 pulsanti collegati in una matrice, utilizziamo 7 pin digitali della scheda Arduino e questo ci dà la possibilità di implementare lo scorrimento tra diversi profili tramite l'utilizzo di un encoder rotativo.
Modelli 3D
[modifica]Per poter realizzare in maniera esteticamente piu accattivante questo progetto, sono stati realizzati attraverso l'utilizzo di un software di progettazione per modelli 3D (il CAD in questione è FreeCAD).
I file sono scaricabili in modo da poterli realizzare tramite l'utilizzo di una stampante 3D.
Da notare che spesso nel processo di stampa 3D potrebbero presentarsi degli errori di stampa che possono essere dovuti all'ambiente nel quale si stampa oppure anche ad impostazioni del programma di slicer (programma esterno al CAD che 'traduce il file STL in istruzioni per la stampante 3D, memorizzate in un file .gcode)
Progettazione della PCB
[modifica]Dopo aver realizzato un prototipo del progetto in maniera 'rudimentale', ho deciso di voler rendere il tutto riproducibile su una PCB (Printed Circuit Board). La realizzazione della PCB è stata fatta tramite il programma Fritzing, in seguito ordinate su internet da JLC PCB (azienda situata in cina che realizza PCB e altro su ordinazione). per effettuare l'ordine è sufficiente caricare il file gerber della PCB compresso in un file .zip (scaricabile su questo link di Google Drive)
Codice
[modifica]Programma principale
[modifica]Come prima cosa includiamo le librerie necessarie per il funzionamento dello sketch
#include <Keyboard.h> //Libreria utilizzata per 'simulare' la pressione di tasti su una tastiera fisica
#include <Keypad.h> // Libreria utilizzata per ridurre il numero di pin digitali utilizzati dalla scheda
#include <Wire.h> //Libreria per la comuncizaione seriale installata nativamente nell'IDE
#include <Adafruit_GFX.h> // le due librerie di Adafruit sono entrambe necessarie per il funzionamento del display OLED
#include <Adafruit_SSD1306.h>
#include <Define.h>
Dopodiché proseguiamo con la scrittura del blocco setup
:
void setup() {
Serial.begin(9600); //Inizializzazione della comunicazione seriale
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { //inizializzazione del display OLED [righe da 11-15]
while (true);
}
display.display();
display.clearDisplay();
pinMode(CLK, INPUT); //Pin di ingresso dell'encoder
pinMode(DT, INPUT);
pinMode(SW, INPUT_PULLUP);
lastStateCLK = digitalRead(CLK); //Lettura dello stato iniziale di CLK
displaySet(); //Istruzioni per mostrare sul Diplay OLED la sritta "Macro Pad" nella prima riga
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 0);
display.print("Macro Pad");
display.display();
}
Nel blocco loop
va inserito il seguente codice che rappresenta l'intero funzionamento del progetto. Anche se apparentemente può sembrare estremamente complicato, in realtà alla base di tutto c'è la semplice struttura di codice della funzione switch case
. In ordine di scrittura i 3 blocchi switch case si occupano di:
- Avviare l'esecuzione del profilo selezionato / accedere al menu dei profili;
- Eseguire il profilo selezionato e le rispettive macro;
- Mostrare sul display OLED informazioni per le macro del profilo selezionato
void loop() {
encoder(); //Funzione definita al di fuori del loop per acquisire informazioni dall'encoder
switch (buttonpress) { //in base al valore della variabile buttonpress, che assume solo valore 0 e 1 si puo accedere al 'menu' di selezione del profilo e selezionarlo
case 0: //quando vale 0 si accede alle macro del profilo selezionato
switch (ProfileSelect) { //In base al valore della variabile ProfileSelect si definisce cosa fanno i due profili delle macro
case 0: //Quando la variabile vale 0 si riporta al profilo 1
ProfileSelect = 1;
break;
case 1: //------------------------------------------------------------ | Macro Profile 1 | -------------------------------------------------------------------------------
encoder();
if(counter < 1) counter = 1; if(counter > 3) counter = 3; //Con i due if si limita lintervallo di valori della variabile counter da 1 a 3
switch (counter) {
case 1:
displaySet(); //Le istruzioni delle righe da 45 a 62 mostrano del display informazione per le macro da 1-4
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 0);
display.print("Profile 1");
display.setTextSize(1);
display.setCursor(0, 18);
display.print("M1: Copy (Ctrl+C)");
display.setCursor(0, 30);
display.print("M2: Paste (Ctrl+V)");
display.setCursor(0, 42);
display.print("M3: Cut (Ctrl+X)");
display.setCursor(0, 54);
display.print("M4: Undo (Ctrl+Z)");
display.display();
pressedKey = keypad.getKey(); //Lettura del pulsante premuto sul keypad
switch (pressedKey) { //In base al carattere di pressedKey vengono riprodotte le rispettive macro del profilo 1
case '1':
Serial.println("Macro 1");
Macro1(1);
break;
case '2':
Serial.println("Macro 2");
Macro2(1);
break;
case '3':
Serial.println("Macro 3");
Macro3(1);
break;
case '4':
Serial.println("Macro 4");
Macro4(1);
break;
case '5':
Serial.println("Macro 5");
Macro5(1);
break;
case '6':
Serial.println("Macro 6");
Macro6(1);
break;
case '7':
Serial.println("Macro 7");
Macro7(1);
break;
case '8':
Serial.println("Macro 8");
Macro8(1);
break;
case '9':
Serial.println("Macro 9");
Macro9(1);
break;
case '0':
Serial.println("Macro 10");
Macro10(1);
break;
case 'A':
Serial.println("Macro 11");
Macro11(1);
break;
case 'B':
Serial.println("Macro 12");
Macro12(1);
break;
}
break;
case 2:
displaySet(); //Le istruzioni delle righe da 131 a 148 mostrano del display informazione per le macro da 5-8
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 0);
display.print("Profile 1");
display.setTextSize(1);
display.setCursor(0, 18);
display.print("M5: Sel All (Ctrl+A)");
display.setCursor(0, 30);
display.print("M6:");
display.setCursor(0, 42);
display.print("M7:");
display.setCursor(0, 54);
display.print("M8:");
display.display();
pressedKey = keypad.getKey(); //Lettura del pulsante premuto sul keypad
switch (pressedKey) { //In base al carattere di pressedKey vengono riprodotte le rispettive macro del profilo 1
case '1':
Serial.println("Macro 1");
Macro1(1);
break;
case '2':
Serial.println("Macro 2");
Macro2(1);
break;
case '3':
Serial.println("Macro 3");
Macro3(1);
break;
case '4':
Serial.println("Macro 4");
Macro4(1);
break;
case '5':
Serial.println("Macro 5");
Macro5(1);
break;
case '6':
Serial.println("Macro 6");
Macro6(1);
break;
case '7':
Serial.println("Macro 7");
Macro7(1);
break;
case '8':
Serial.println("Macro 8");
Macro8(1);
break;
case '9':
Serial.println("Macro 9");
Macro9(1);
break;
case '0':
Serial.println("Macro 10");
Macro10(1);
break;
case 'A':
Serial.println("Macro 11");
Macro11(1);
break;
case 'B':
Serial.println("Macro 12");
Macro12(1);
break;
}
break;
case 3:
displaySet(); //Le istruzioni delle righe da 216 a 233 mostrano del display informazione per le macro da 9-12
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 0);
display.print("Profile 1");
display.setTextSize(1);
display.setCursor(0, 18);
display.print("M9:");
display.setCursor(0, 30);
display.print("M10:");
display.setCursor(0, 42);
display.print("M11:");
display.setCursor(0, 54);
display.print("M12:");
display.display();
pressedKey = keypad.getKey(); //Lettura del pulsante premuto sul keypad
switch (pressedKey) { //In base al carattere di pressedKey vengono riprodotte le rispettive macro del profilo 1
case '1':
Serial.println("Macro 1");
Macro1(1);
break;
case '2':
Serial.println("Macro 2");
Macro2(1);
break;
case '3':
Serial.println("Macro 3");
Macro3(1);
break;
case '4':
Serial.println("Macro 4");
Macro4(1);
break;
case '5':
Serial.println("Macro 5");
Macro5(1);
break;
case '6':
Serial.println("Macro 6");
Macro6(1);
break;
case '7':
Serial.println("Macro 7");
Macro7(1);
break;
case '8':
Serial.println("Macro 8");
Macro8(1);
break;
case '9':
Serial.println("Macro 9");
Macro9(1);
break;
case '0':
Serial.println("Macro 10");
Macro10(1);
break;
case 'A':
Serial.println("Macro 11");
Macro11(1);
break;
case 'B':
Serial.println("Macro 12");
Macro12(1);
break;
}
break;
default:
counter = 1;
break;
}
break;
case 2: //------------------------------------------------------------ | Macro Profile 2 | -------------------------------------------------------------------------------
pressedKey = keypad.getKey();
encoder();
if(counter == 0) counter = 1; if(counter == 4) counter = 3; //Con i due if si limita lintervallo di valori della variabile counter da 1 a 3
switch (counter) {
case 1:
displaySet(); //Le istruzioni delle righe da 288 a 305 mostrano del display informazione per le macro da 1-4
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 0);
display.print("Profile 2");
display.setTextSize(1);
display.setCursor(0, 18);
display.print("M1:");
display.setCursor(0, 30);
display.print("M2:");
display.setCursor(0, 42);
display.print("M3:");
display.setCursor(0, 54);
display.print("M4:");
display.display();
pressedKey = keypad.getKey(); //Lettura del pulsante premuto sul keypad
switch (pressedKey) { //In base al carattere di pressedKey vengono riprodotte le rispettive macro del profilo 2
case '1':
Serial.println("Macro 1");
Macro1(2);
break;
case '2':
Serial.println("Macro 2");
Macro2(2);
break;
case '3':
Serial.println("Macro 3");
Macro3(2);
break;
case '4':
Serial.println("Macro 4");
Macro4(2);
break;
case '5':
Serial.println("Macro 5");
Macro5(2);
break;
case '6':
Serial.println("Macro 6");
Macro6(2);
break;
case '7':
Serial.println("Macro 7");
Macro7(2);
break;
case '8':
Serial.println("Macro 8");
Macro8(2);
break;
case '9':
Serial.println("Macro 9");
Macro9(2);
break;
case '0':
Serial.println("Macro 10");
Macro10(2);
break;
case 'A':
Serial.println("Macro 11");
Macro11(2);
break;
case 'B':
Serial.println("Macro 12");
Macro12(2);
break;
}
break;
case 2:
displaySet(); //Le istruzioni delle righe da 374 a 391 mostrano del display informazione per le macro da 5-8
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 0);
display.print("Profile 2");
display.setTextSize(1);
display.setCursor(0, 18);
display.print("M5:");
display.setCursor(0, 30);
display.print("M6:");
display.setCursor(0, 42);
display.print("M7:");
display.setCursor(0, 54);
display.print("M8:");
display.display();
pressedKey = keypad.getKey(); //Lettura del pulsante premuto sul keypad
switch (pressedKey) { //In base al carattere di pressedKey vengono riprodotte le rispettive macro del profilo 2
case '1':
Serial.println("Macro 1");
Macro1(2);
break;
case '2':
Serial.println("Macro 2");
Macro2(2);
break;
case '3':
Serial.println("Macro 3");
Macro3(2);
break;
case '4':
Serial.println("Macro 4");
Macro4(2);
break;
case '5':
Serial.println("Macro 5");
Macro5(2);
break;
case '6':
Serial.println("Macro 6");
Macro6(2);
break;
case '7':
Serial.println("Macro 7");
Macro7(2);
break;
case '8':
Serial.println("Macro 8");
Macro8(2);
break;
case '9':
Serial.println("Macro 9");
Macro9(2);
break;
case '0':
Serial.println("Macro 10");
Macro10(2);
break;
case 'A':
Serial.println("Macro 11");
Macro11(2);
break;
case 'B':
Serial.println("Macro 12");
Macro12(2);
break;
}
break;
case 3:
displaySet(); //Le istruzioni delle righe da 459 a 476 mostrano del display informazione per le macro da 9-12
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 0);
display.print("Profile 2");
display.setTextSize(1);
display.setCursor(0, 18);
display.print("M9:");
display.setCursor(0, 30);
display.print("M10:");
display.setCursor(0, 42);
display.print("M11:");
display.setCursor(0, 54);
display.print("M12:");
display.display();
pressedKey = keypad.getKey(); //Lettura del pulsante premuto sul keypad
switch (pressedKey) { //In base al carattere di pressedKey vengono riprodotte le rispettive macro del profilo 2
case '1':
Serial.println("Macro 1");
Macro1(2);
break;
case '2':
Serial.println("Macro 2");
Macro2(2);
break;
case '3':
Serial.println("Macro 3");
Macro3(2);
break;
case '4':
Serial.println("Macro 4");
Macro4(2);
break;
case '5':
Serial.println("Macro 5");
Macro5(2);
break;
case '6':
Serial.println("Macro 6");
Macro6(2);
break;
case '7':
Serial.println("Macro 7");
Macro7(2);
break;
case '8':
Serial.println("Macro 8");
Macro8(2);
break;
case '9':
Serial.println("Macro 9");
Macro9(2);
break;
case '0':
Serial.println("Macro 10");
Macro10(2);
break;
case 'A':
Serial.println("Macro 11");
Macro11(2);
break;
case 'B':
Serial.println("Macro 12");
Macro12(2);
break;
}
break;
default:
counter = 1;
break;
}
break;
case 3: //Quando la variabile vale 3 si riporta al profilo 2 non andando oltre il valore 3
ProfileSelect = 2;
break;
}
break;
case 1: //Quando vale 1 si accede al menu di selezione del profilo
displaySet();
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 0);
display.print("Select:");
display.setTextSize(1);
display.setCursor(0, 18);
display.print("Profile: ");
display.print(ProfileSelect);
display.display();
encoder();
ProfileSelect = counter;
if (counter == 0) counter = 1;
if (counter == 3) counter = 2;
break;
}
}
All'esterno del blocco del loop
vanno definite alcune funzioni particolari che si occupano di definire le dodici macro in entrambi i profili. Per semplificare il codice, ad esempio, per la Macro 1 viene definita una sola funzione con all'interno un blocco switch case
, con numero di casi pari al numero dei profili, contenente le istruzioni per la stessa macro nei vari profili.
//----------------------------------------------------------------------|Profile Macro Functions|------------------------------------------------------------------------
/*
in questo blocco di commento viene descritta la sintassi e il funzionamento delle diverse istruzioni della libreria che invia comandi di tastiera al dispositivo collegato
Keyboard.write(); Invia una pressione di un tasto ed è simile a premere e rilasciare un tasto fisico sulla tasteira
Sono anche accettati caratteri in codice ASCII ad esempio se si volesse premere il tasto del menu di Windows
Keyboard.press(); Ideale per le combinazioni dove bisogna tenere premuti piu tasti contemporaneamente
infatti dopo questa istruzione bisogna inserire Keyboard.release(); oppure Keyboard.releaseAll(); per rilasciare il tasto o tutti i tasti
precedentemente premuti
Keyboard.print(); Invia un inserimento di piu tasti in successione, come ad esempio una frase spesso utilizzata
Keyboard.println(); E' Simile al precedente con la differenza che dopo la successione di tasti viene inviato la pressione del tasto 'Invio?'
*/
void Macro1(int profile) { //Funzione per la macro 1, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('c');
Keyboard.releaseAll();
break;
case 2:
break;
}
}
void Macro2(int profile) { //Funzione per la macro 2, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('v');
Keyboard.releaseAll();
break;
case 2:
break;
}
}
void Macro3(int profile) { //Funzione per la macro 3, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('x');
Keyboard.releaseAll();
break;
case 2:
break;
}
}
void Macro4(int profile) { //Funzione per la macro 4, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('z');
Keyboard.releaseAll();
break;
case 2:
break;
}
}
void Macro5(int profile) { //Funzione per la macro 5, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('a');
Keyboard.releaseAll();
break;
case 2:
break;
}
}
void Macro6(int profile) { //Funzione per la macro 6, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
break;
case 2:
break;
}
}
void Macro7(int profile) { //Funzione per la macro 7, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
break;
case 2:
break;
}
}
void Macro8(int profile) { //Funzione per la macro 8, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
break;
case 2:
break;
}
}
void Macro9(int profile) { //Funzione per la macro 9, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
break;
case 2:
break;
}
}
void Macro10(int profile) { //Funzione per la macro 10, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
Keyboard.press(KEY_LEFT_ALT);
Keyboard.press('m'); delay(100);
Keyboard.releaseAll();
Keyboard.press('t');
Keyboard.releaseAll();
break;
case 2:
Keyboard.println("Youtube.com");
break;
}
}
void Macro11(int profile) { //Funzione per la macro 11, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
break;
case 2:
break;
}
}
void Macro12(int profile) { //Funzione per la macro 12, dove tra parentesi viene inserito il profile della macro da eseguire
switch (profile) {
case 1:
break;
case 2:
break;
}
}
//-----------------------------------------------------------------------------|Encoder Function|--------------------------------------------------------------------------------------
void encoder() { //Funzione che semplifica il codice quando necessario ricevere informazioni dall'encoder
// Read the current state of CLK
currentStateCLK = digitalRead(CLK);
//se l'ultimo e l'atuale valore della variabile CLK sono diversi allora è avvenuto una rotazione
if (currentStateCLK != lastStateCLK && currentStateCLK == 1) {
// se il valore di DT è diverso dall'attuale valore di CLk allora si sta ruotando in senso orario quindi si incrementa
if (digitalRead(DT) != currentStateCLK) {
counter ++;
//delay(800); //Buffer per rallentare il conteggio
} else {
//Altrimenti si sta ruotanto in senso antiorario
counter --;
//delay(800); //Buffer per rallentare il conteggio
}
Serial.print("Counter: ");
Serial.println(counter);
}
//Memorizzail precedente valore di CLK
lastStateCLK = currentStateCLK;
//Lettura dello stato del pulsante
int btnState = digitalRead(SW);
//Se viene rilevato il segnale LOW allora si è effettuata una pressione
if (btnState == LOW) {
//Se sono passati 50ms dall'ultimo valore LOW
//allora il pulsante è stato premuto, rilasciato e ripremuto una seconda volta
if (millis() - lastButtonPress > 50) {
Serial.println("Button pressed!");
delay(50);
buttonpress++;
if (buttonpress > 1) buttonpress = 0;
}
//Memorizza l'ultimo evento di pressione del tasto
lastButtonPress = millis();
}
delay(1);
}
//---------------------------------------------|Istruzioni che vengono effettuate ogni volta prima di mostrare informazioni sul display OLED|----------------------------------------
void displaySet() {
display.setTextColor(WHITE);
display.cp437(true);
}
Definizione delle variabili globali
[modifica]
Nel file Define.ino
sono riportate tutte le variabili globali utilizzate dal display, dalla tastiera, dall'encoder, oltre ad alcune variabili di sistema richiamate nel programma principale.
//Variabili e costanti per il display OLED
#define SCREEN_WIDTH 128 //larghezza del display OLED
#define SCREEN_HEIGHT 64 //altezza del display OLED
#define OLED_RESET 4
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
//Costanti per la libreria Keypad.h
const byte ROWS = 3;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1', '2', '3', '4'},
{'5', '6', '7', '8'},
{'9', '0', 'A', 'B'},
};
byte rowPins[ROWS] = {7, 8, 9 };
byte colPins[COLS] = {10, 16, 14, 15};
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
//Varibili per la selezione dei profili Macro
unsigned int ProfileSelect = 1;
unsigned int profiles = 2;
unsigned int buttonpress = 0;
//Variabili e costanti per il funzionamento dell'encoder
#define CLK 4 //Pin di collegamento di CLK
#define DT 5 //Pin di collegamento di DT
#define SW 6 //Pin di collegamento di SW
int counter = 1; //Variabile counter
int currentStateCLK; //Variabili per il monitoraggio di CLK
int lastStateCLK;
unsigned long lastButtonPress = 0; //Variabile per il monitoraggio della presisone del pulsante
int pressedKey = ' '; //Variabile per il carattere premuto sul keypad