PROGETTI ARDUINO

  • HOME
  • 200 Progetti
  • Progetti e Tutorial
  • Reti
    • Impara le reti
    • internet fibra
    • wifi mesh
    • modem router
    • diagnostica reti
    • ethernet lan
    • sicurezza configurazione
  • Elettronica
  • Recensioni
  • Manuale
  • Contatti
  • HOME
  • 200 Progetti
  • Progetti e Tutorial
  • Reti
    • Impara le reti
    • internet fibra
    • wifi mesh
    • modem router
    • diagnostica reti
    • ethernet lan
    • sicurezza configurazione
  • Elettronica
  • Recensioni
  • Manuale
  • Contatti

Progetto 133
​Navigazione menu con encoder

🚀 Introduzione

Vuoi creare un vero menu Arduino controllato con una sola manopola? 🎛️📋

In questo progetto utilizzerai un encoder rotativo con pulsante integrato per navigare tra diverse voci visualizzate su un display OLED.

Ruotando l’encoder potrai spostare il cursore. Premendo la manopola entrerai nella voce selezionata e potrai modificare i relativi valori.
​
👉 È la stessa logica utilizzata in stampanti 3D, termostati, amplificatori, centraline e dispositivi elettronici professionali.

🎯 Cosa realizzerai
​

Con Arduino creerai un’interfaccia che:
  • 🎛️ utilizza un encoder rotativo;
  • ⬆️ scorre le voci del menu;
  • ✅ seleziona una funzione premendo la manopola;
  • 🌡️ modifica una temperatura impostata;
  • 💡 modifica il livello di luminosità;
  • 🔔 attiva o disattiva un buzzer;
  • ℹ️ mostra una schermata informativa;
  • 🔙 permette di tornare al menu principale;
  • 📟 visualizza tutto su display OLED;
  • ⏱️ gestisce rotazione e pressione senza bloccare Arduino.

🧰 Materiale necessario
​
  • 1x Arduino UNO;
  • 1x encoder rotativo KY-040 o compatibile;
  • 1x display OLED 0,96 pollici I2C SSD1306;
  • 1x buzzer passivo;
  • cavetti jumper;
  • breadboard;
  • cavo USB.
Il pulsante integrato nell’encoder utilizza la resistenza interna INPUT_PULLUP di Arduino.

🛒 Non hai ancora i componenti?

Molti dei progetti presenti su progettiarduino.com utilizzano componenti elettronici di base come LED, resistenze, sensori e breadboard.
Se stai iniziando con Arduino, il modo più semplice è utilizzare un kit completo per principianti, che include quasi tutti i componenti necessari per realizzare molti dei progetti presenti sul sito.

👉 Kit Arduino consigliato:
ℹ️ Link di affiliazione Amazon: in qualità di Affiliato Amazon ricevo un guadagno dagli acquisti idonei, senza alcun costo aggiuntivo per te.
https://amzn.to/4b3dxjW

📘 Vuoi imparare Arduino passo dopo passo?
Se vuoi approfondire e realizzare progetti più completi, puoi dare un'occhiata anche ai miei libri dedicati ad Arduino.
All'interno troverai spiegazioni semplici, schemi chiari e molti progetti pratici pensati per chi parte da zero.

👉 Scopri i miei libri su Arduino:
ℹ️ Link di affiliazione Amazon: in qualità di Affiliato Amazon ricevo un guadagno dagli acquisti idonei, senza alcun costo aggiuntivo per te.
https://amzn.to/4rfzjXE
​

📚 Librerie necessarie
​

Per utilizzare il display OLED devi installare:
  • Adafruit SSD1306;
  • Adafruit GFX Library.
La libreria Wire è già inclusa nell’Arduino IDE.
Per la gestione dell’encoder non è richiesta alcuna libreria esterna.

🔌 Schema di collegamento
​

Display OLED SSD1306
  • VCC → 5V;
  • GND → GND;
  • SDA → A4;
  • SCL → A5.
L’indirizzo I2C utilizzato nel codice è 0x3C.

Encoder rotativo KY-040
  • + → 5V;
  • GND → GND;
  • CLK → pin digitale 2;
  • DT → pin digitale 3;
  • SW → pin digitale 4.

Buzzer passivo
  • terminale positivo → pin digitale 8;
  • terminale negativo → GND.
Il buzzer emette un breve suono quando viene premuta la manopola, se l’opzione audio è attiva.
Foto

💻 Codice Arduino

Il menu principale contiene quattro voci:
  • Temperatura;
  • Luminosità;
  • Suono;
  • Informazioni.
Ruotando l’encoder, il cursore si sposta tra le voci.
​
Premendo la manopola:
  • si entra nella schermata selezionata;
  • si conferma un valore;
  • si ritorna al menu principale.
​/*
  ============================================================
  Progetto 133 - Navigazione menu con encoder
  Menu OLED controllato tramite encoder rotativo KY-040
  ============================================================
*/

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Dimensioni e indirizzo del display OLED
#define LARGHEZZA_DISPLAY 128
#define ALTEZZA_DISPLAY 64
#define RESET_OLED -1
#define INDIRIZZO_OLED 0x3C

// Collegamenti dell'encoder e del buzzer
#define PIN_CLK 2
#define PIN_DT 3
#define PIN_SW 4
#define PIN_BUZZER 8

// Intervalli utilizzati per ridurre
// rimbalzi e letture multiple
const unsigned long TEMPO_DEBOUNCE_PULSANTE = 180;
const unsigned long TEMPO_MINIMO_ROTAZIONE = 3;

// Numero delle voci presenti nel menu
const byte NUMERO_VOCI = 4;

// Oggetto utilizzato per controllare il display OLED
Adafruit_SSD1306 display(
  LARGHEZZA_DISPLAY,
  ALTEZZA_DISPLAY,
  &Wire,
  RESET_OLED
);

// Nomi delle voci del menu principale
const char* vociMenu[NUMERO_VOCI] = {
  "Temperatura",
  "Luminosita",
  "Suono",
  "Informazioni"
};

// Schermate disponibili nel programma
enum Schermata {
  MENU_PRINCIPALE,
  MODIFICA_TEMPERATURA,
  MODIFICA_LUMINOSITA,
  MODIFICA_SUONO,
  SCHERMATA_INFO
};

Schermata schermataAttuale = MENU_PRINCIPALE;

// Voce attualmente selezionata nel menu
byte voceSelezionata = 0;

// Parametri modificabili
int temperaturaImpostata = 22;
int luminositaImpostata = 50;
bool suonoAttivo = true;

// Stato precedente del segnale CLK
int statoPrecedenteClk = HIGH;

// Variabili temporali utilizzate
// per encoder e pulsante
unsigned long ultimaRotazione = 0;
unsigned long ultimaPressione = 0;

// Indica quando il display deve essere ridisegnato
bool aggiornamentoDisplayRichiesto = true;

void setup() {
  // I pin dell'encoder utilizzano
  // le resistenze interne di Arduino
  pinMode(PIN_CLK, INPUT_PULLUP);
  pinMode(PIN_DT, INPUT_PULLUP);
  pinMode(PIN_SW, INPUT_PULLUP);

  // Configura il buzzer come uscita
  pinMode(PIN_BUZZER, OUTPUT);
  noTone(PIN_BUZZER);

  // Memorizza lo stato iniziale del segnale CLK
  statoPrecedenteClk = digitalRead(PIN_CLK);

  // Inizializza il display OLED
  if (!display.begin(
        SSD1306_SWITCHCAPVCC,
        INDIRIZZO_OLED
      )) {
    // Se il display non viene rilevato,
    // il programma rimane bloccato
    while (true) {
    }
  }

  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextWrap(false);

  mostraAvvio();
  mostraSchermata();

  aggiornamentoDisplayRichiesto = false;
}

void loop() {
  // Controlla continuamente la rotazione
  gestisciEncoder();

  // Controlla il pulsante dell'encoder
  gestisciPulsante();

  // Ridisegna il display soltanto
  // quando qualcosa è cambiato
  if (aggiornamentoDisplayRichiesto) {
    mostraSchermata();
    aggiornamentoDisplayRichiesto = false;
  }
}

void gestisciEncoder() {
  // Legge lo stato attuale del segnale CLK
  int statoAttualeClk = digitalRead(PIN_CLK);

  // Rileva soltanto il fronte di discesa
  // per contare una volta ogni scatto
  if (statoPrecedenteClk == HIGH &&
      statoAttualeClk == LOW &&
      millis() - ultimaRotazione >=
      TEMPO_MINIMO_ROTAZIONE) {
    ultimaRotazione = millis();

    // Confronta DT con CLK per stabilire
    // la direzione della rotazione
    if (digitalRead(PIN_DT) != statoAttualeClk) {
      ruotaDestra();
    } else {
      ruotaSinistra();
    }

    aggiornamentoDisplayRichiesto = true;
  }

  // Salva lo stato per il ciclo successivo
  statoPrecedenteClk = statoAttualeClk;
}

void gestisciPulsante() {
  // Con INPUT_PULLUP il pulsante premuto
  // viene letto come livello LOW
  if (digitalRead(PIN_SW) == LOW &&
      millis() - ultimaPressione >=
      TEMPO_DEBOUNCE_PULSANTE) {
    ultimaPressione = millis();

    // Emette un breve feedback acustico
    emettiClick();

    // Esegue l'azione associata
    // alla schermata attualmente visibile
    confermaSelezione();

    aggiornamentoDisplayRichiesto = true;
  }
}

void ruotaDestra() {
  // Nel menu principale sposta
  // il cursore alla voce successiva
  if (schermataAttuale == MENU_PRINCIPALE) {
    voceSelezionata++;

    if (voceSelezionata >= NUMERO_VOCI) {
      voceSelezionata = 0;
    }

    return;
  }

  // Nella schermata temperatura
  // aumenta il valore di un grado
  if (schermataAttuale ==
      MODIFICA_TEMPERATURA) {
    temperaturaImpostata++;

    temperaturaImpostata = constrain(
      temperaturaImpostata,
      10,
      35
    );

    return;
  }

  // Nella schermata luminosità
  // aumenta il valore di cinque punti
  if (schermataAttuale ==
      MODIFICA_LUMINOSITA) {
    luminositaImpostata += 5;

    luminositaImpostata = constrain(
      luminositaImpostata,
      0,
      100
    );

    return;
  }

  // Nella schermata suono
  // attiva il feedback acustico
  if (schermataAttuale == MODIFICA_SUONO) {
    suonoAttivo = true;
  }
}

void ruotaSinistra() {
  // Nel menu principale sposta
  // il cursore alla voce precedente
  if (schermataAttuale == MENU_PRINCIPALE) {
    if (voceSelezionata == 0) {
      voceSelezionata = NUMERO_VOCI - 1;
    } else {
      voceSelezionata--;
    }

    return;
  }

  // Diminuisce la temperatura impostata
  if (schermataAttuale ==
      MODIFICA_TEMPERATURA) {
    temperaturaImpostata--;

    temperaturaImpostata = constrain(
      temperaturaImpostata,
      10,
      35
    );

    return;
  }

  // Diminuisce la luminosità impostata
  if (schermataAttuale ==
      MODIFICA_LUMINOSITA) {
    luminositaImpostata -= 5;

    luminositaImpostata = constrain(
      luminositaImpostata,
      0,
      100
    );

    return;
  }

  // Nella schermata suono
  // disattiva il feedback acustico
  if (schermataAttuale == MODIFICA_SUONO) {
    suonoAttivo = false;
  }
}

void confermaSelezione() {
  // Dal menu principale entra
  // nella voce selezionata
  if (schermataAttuale == MENU_PRINCIPALE) {
    switch (voceSelezionata) {
      case 0:
        schermataAttuale =
          MODIFICA_TEMPERATURA;
        break;

      case 1:
        schermataAttuale =
          MODIFICA_LUMINOSITA;
        break;

      case 2:
        schermataAttuale =
          MODIFICA_SUONO;
        break;

      case 3:
        schermataAttuale =
          SCHERMATA_INFO;
        break;
    }

    return;
  }

  // Da qualsiasi schermata secondaria,
  // una nuova pressione torna al menu principale
  schermataAttuale = MENU_PRINCIPALE;
}

void mostraSchermata() {
  // Pulisce completamente il display
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);

  switch (schermataAttuale) {
    case MENU_PRINCIPALE:
      mostraMenu();
      break;

    case MODIFICA_TEMPERATURA:
      mostraTemperatura();
      break;

    case MODIFICA_LUMINOSITA:
      mostraLuminosita();
      break;

    case MODIFICA_SUONO:
      mostraSuono();
      break;

    case SCHERMATA_INFO:
      mostraInformazioni();
      break;
  }

  // Invia al display tutti gli elementi disegnati
  display.display();
}

void mostraMenu() {
  display.setTextSize(1);

  // Titolo del menu
  display.setCursor(0, 0);
  display.print("MENU PRINCIPALE");

  display.drawLine(
    0,
    10,
    127,
    10,
    SSD1306_WHITE
  );

  // Calcola la prima voce da mostrare.
  // Il display visualizza tre voci alla volta.
  int primaVoce;

  if (voceSelezionata <= 1) {
    primaVoce = 0;
  } else if (voceSelezionata >=
             NUMERO_VOCI - 1) {
    primaVoce = NUMERO_VOCI - 3;
  } else {
    primaVoce = voceSelezionata - 1;
  }

  // Disegna le tre voci visibili
  for (byte riga = 0; riga < 3; riga++) {
    byte indiceVoce = primaVoce + riga;
    int coordinataY = 16 + riga * 15;

    // Evidenzia la voce selezionata
    if (indiceVoce == voceSelezionata) {
      display.fillRect(
        0,
        coordinataY - 2,
        128,
        12,
        SSD1306_WHITE
      );

      display.setTextColor(SSD1306_BLACK);
    } else {
      display.setTextColor(SSD1306_WHITE);
    }

    display.setCursor(4, coordinataY);
    display.print(vociMenu[indiceVoce]);
  }

  // Ripristina il colore normale
  display.setTextColor(SSD1306_WHITE);

  display.setCursor(0, 61);
  display.print("Ruota e premi");
}

void mostraTemperatura() {
  display.setTextSize(1);

  display.setCursor(0, 0);
  display.print("IMPOSTA TEMPERATURA");

  display.drawLine(
    0,
    10,
    127,
    10,
    SSD1306_WHITE
  );

  // Mostra il valore con caratteri grandi
  display.setTextSize(3);
  display.setCursor(25, 20);
  display.print(temperaturaImpostata);
  display.print((char)247);
  display.print("C");

  display.setTextSize(1);
  display.setCursor(13, 53);
  display.print("Ruota per modificare");

  display.setCursor(18, 62);
  display.print("Premi per salvare");
}

void mostraLuminosita() {
  display.setTextSize(1);

  display.setCursor(0, 0);
  display.print("IMPOSTA LUMINOSITA");

  display.drawLine(
    0,
    10,
    127,
    10,
    SSD1306_WHITE
  );

  display.setTextSize(2);
  display.setCursor(44, 17);
  display.print(luminositaImpostata);
  display.print("%");

  // Disegna la barra del valore impostato
  display.drawRect(
    8,
    39,
    112,
    10,
    SSD1306_WHITE
  );

  int larghezzaBarra = map(
    luminositaImpostata,
    0,
    100,
    0,
    108
  );

  display.fillRect(
    10,
    41,
    larghezzaBarra,
    6,
    SSD1306_WHITE
  );

  display.setTextSize(1);
  display.setCursor(18, 56);
  display.print("Premi per salvare");
}

void mostraSuono() {
  display.setTextSize(1);

  display.setCursor(0, 0);
  display.print("FEEDBACK ACUSTICO");

  display.drawLine(
    0,
    10,
    127,
    10,
    SSD1306_WHITE
  );

  display.setTextSize(2);
  display.setCursor(30, 23);

  if (suonoAttivo) {
    display.print("ATTIVO");
  } else {
    display.print("SPENTO");
  }

  display.setTextSize(1);
  display.setCursor(9, 49);
  display.print("Ruota per cambiare");

  display.setCursor(18, 60);
  display.print("Premi per salvare");
}

void mostraInformazioni() {
  display.setTextSize(1);

  display.setCursor(0, 0);
  display.print("INFORMAZIONI");

  display.drawLine(
    0,
    10,
    127,
    10,
    SSD1306_WHITE
  );

  display.setCursor(0, 18);
  display.print("Progetto: 133");

  display.setCursor(0, 31);
  display.print("Display: SSD1306");

  display.setCursor(0, 44);
  display.print("Comando: KY-040");

  display.setCursor(12, 58);
  display.print("Premi per tornare");
}

void emettiClick() {
  // Non produce alcun suono
  // se il feedback è disattivato
  if (!suonoAttivo) {
    return;
  }

  // Emette un breve tono senza utilizzare delay()
  tone(
    PIN_BUZZER,
    1800,
    40
  );
}

void mostraAvvio() {
  display.clearDisplay();
  display.setTextSize(1);

  display.setCursor(22, 14);
  display.print("Menu con encoder");

  display.setCursor(24, 30);
  display.print("Navigazione OLED");

  display.setCursor(28, 47);
  display.print("Progetto 133");

  display.display();

  delay(1800);
}

⚙️ Come funziona
​

Il programma utilizza due modalità principali:
  • navigazione nel menu;
  • modifica di una voce.
Nel menu principale, la rotazione cambia la voce selezionata.
​
Quando entri in una schermata di modifica, la rotazione agisce invece sul valore.
Per esempio:
  • nella voce Temperatura modifica i gradi;
  • nella voce Luminosità modifica la percentuale;
  • nella voce Suono attiva o disattiva il buzzer.
​
👉 La funzione della manopola dipende quindi dalla schermata attiva.

📋 Menu principale
​

Il display OLED mostra tre voci contemporaneamente.
La voce selezionata viene evidenziata con:
  • un rettangolo bianco;
  • testo nero su sfondo bianco.
​
Quando il cursore supera l’ultima voce, torna automaticamente alla prima.
​
Ruotando nella direzione opposta dalla prima voce, si passa invece all’ultima.
Questa tecnica viene chiamata navigazione circolare.

🌡️ Modifica della temperatura
​

La temperatura può essere regolata tra:
  • minimo: 10 °C;
  • massimo: 35 °C.
Ogni scatto dell’encoder modifica il valore di un grado.
Premendo la manopola, il nuovo valore viene confermato e Arduino torna al menu principale.

💡 Modifica della luminosità
​

La luminosità può essere regolata tra 0% e 100%.
Ogni scatto dell’encoder modifica il valore di 5 punti percentuali.
Il display mostra anche una barra grafica proporzionale al valore impostato.

🔔 Attivazione del suono
​

La voce Suono permette di attivare o disattivare il feedback acustico.
Quando il suono è attivo, il buzzer emette un breve tono:
  • quando viene premuto l’encoder;
  • quando viene confermata una voce.
Quando il suono è disattivato, il buzzer rimane silenzioso.

🧭 Gestione delle schermate
​

Il programma utilizza un’enumerazione per identificare la schermata attiva:
enum Schermata {
MENU_PRINCIPALE,
MODIFICA_TEMPERATURA,
MODIFICA_LUMINOSITA,
MODIFICA_SUONO,
SCHERMATA_INFO
};

Questa organizzazione rende il codice più leggibile e permette di aggiungere facilmente nuove funzioni.

🎛️ Lettura dell’encoder
​

L’encoder produce due segnali digitali:
  • CLK;
  • DT.
Il programma controlla il fronte di discesa del segnale CLK e confronta lo stato del pin DT.
​
In questo modo stabilisce se la manopola è stata ruotata:
  • verso destra;
  • verso sinistra.

🧠 Perché è un progetto importante
​

Con questo progetto impari a:
  • creare un menu navigabile;
  • utilizzare un encoder rotativo;
  • gestire più schermate;
  • cambiare il comportamento dei comandi;
  • evidenziare graficamente una selezione;
  • modificare parametri numerici;
  • attivare impostazioni booleane;
  • utilizzare un buzzer come feedback;
  • organizzare il codice tramite enumerazioni;
  • costruire un’interfaccia riutilizzabile.
​
👉 Questa struttura può diventare la base di termostati, timer, controlli motore e centraline domotiche.

🔄 Varianti possibili
​

Vuoi migliorare il progetto?
  • 💾 salva le impostazioni nella EEPROM;
  • 🌡️ collega un sensore reale di temperatura;
  • 💡 controlla un LED tramite PWM;
  • ⚡ aggiungi il controllo di un relè;
  • 📋 crea sottomenu con più livelli;
  • ⏰ aggiungi un modulo RTC DS3231;
  • 🔒 inserisci una schermata di blocco;
  • 🎵 aggiungi suoni differenti;
  • 📊 mostra grafici e barre;
  • 🔘 riconosci una pressione lunga;
  • 📟 utilizza un display OLED più grande;
  • 🏠 trasforma il menu in una centralina domotica.

Foto
Foto

© ProgettiArduino.com 2026 - Fancello Salvatore
​ Layout, immagini e testi sono di esclusiva proprietà di www.progettiarduino.com 
Privacy Policy