Archivi tag: serial monitor

Arduino – lettura di caratteri da Serial Monitor

La Serial Monitor è la finestra di chat tra noi e la scheda Arduino. Da un lato c’è il computer (con l’IDE o l’editor web), dall’altro c’è il microcontrollore. Quando apriamo la Serial Monitor, stiamo aprendo un canale in cui possiamo leggere messaggi che Arduino ci invia (testi, numeri, stati dei sensori) e scrivergli comandi (caratteri, parole, numeri) per fargli fare cose. È il modo più semplice e immediato per “vedere dentro” un programma che sta funzionando su una scheda senza schermo.

Saper usare la Serial Monitor è utile perché in elettronica e automazione non basta “caricare lo sketch e sperare che funzioni tutto”, bisogna capire cosa succede: quanto misura un sensore, se un calcolo è corretto, se un evento si verifica davvero. Con Serial.print() e Serial.println() facciamo in modo che Arduino ci spieghi passo passo cosa sta facendo. Questo è fondamentale per il debug: quando qualcosa non funziona, far “parlare” il codice è spesso la via più rapida per scoprire l’errore.

Concetti chiave

  • Velocità (baud rate): indica il numero di caratteri al secondo che vengono trasmessi. Questa velocità deve coincidere tra sketch e Serial Monitor, altrimenti ricevete  caratteri non corretti. Usare Serial.begin(9600) o altre velocità tipiche (9600/115200).
  • Buffer di ricezione: i byte arrivano e restano nel buffer finché non li leggete. Serial.available() ti dice quanti.
  • Terminatori di riga nel Serial Monitor: potete inviare No line ending (Nessun fine riga), Newline (\n) (A capo (NL)), Carriage return (\r) (Ritorno carrello (CR)), Both NL & CR (Entrambi NL & CR). Scegliendo A campo (NL) potete leggere “una riga per volta”.

Durante le precedenti lezioni di ripasso abbiamo visto in più occasioni come usare la Serial Monitor, quindi questa che state leggendo è una lezione di ripasso, di seguito i link alle lezioni precedenti:

Riporto di seguito una serie di programmi standard che possono essere riutilizzati in più occasioni:

Esempio 01: echo con conteggio: leggo caratteri subito (non bloccante)

// Prof. Maffucci Michele
// data: 11.11.25
// Fare "echo" dei caratteri ricevuti e contare quanti ne vengono letti.
// Impostazioni Serial Monitor: qualsiasi line ending (anche nessuno va bene).

unsigned long totale_byte = 0;

void setup() {
  Serial.begin(115200); // per Arduino R4, per R3 impostare 9600
  while (!Serial) {}    // necessario su alcune schede USB native
  Serial.println("Echo pronto: digita qualcosa...");
}

void loop() {
  while (Serial.available() > 0) {     // ciclo finché c'è input
    int c = Serial.read();             // leggo un byte
    totale_byte++;
    Serial.write(c);                   // rimando lo stesso byte (echo)
  }

  // Ogni 2 secondi stampo lo stato sulla Serial Monitor
  static unsigned long t0 = 0;
  if (millis() - t0 > 2000) {
    Serial.print("\nByte letti finora: ");
    Serial.println(totale_byte);
    t0 = millis();
  }
}

Dovreste notare che se avete impostato “Entrambi NL &CR” e digitate ad esempio A e premete invio il conteggio dei byte viene fatto in questo modo:

  • A > 1 byte
  • \r > Carriage Return > 1 byte
  • \n > Newline > 1 byte

totale = 3 byte.

Se impostate “A capo (NL)” otterrete un totale di 2 byte (A + \n).
Con “Nessuna fine riga” ottenete 1 byte (solo A).

Impostazione Monitor Cosa viene inviato dopo il carattere Byte totali per tasto
No line ending (nulla) 1
Newline \n 2
Carriage return \r 2
Both NL & CR \r\n 3

Esempio 02: lettura “riga per riga” con \n (line ending = Newline – “A capo(NL))

// Prof. Maffucci Michele
// data: 11.11.25
// Leggere comandi testuali una riga alla volta (terminatore '\n').
// Impostazioni Serial Monitor: "Newline" come line ending.

// DIM = dimensione massima del buffer di input, inclusa la chiusura '\0'.
// Sceglietela abbastanza grande da contenere la riga più lunga che prevedete.
const byte DIM = 64;

// riga[] è un array di char (buffer) dove accumuliamo i caratteri ricevuti
// fino all'invio '\n'. Essendo "C string", va CHIUSA con '\0' quando completa.
char riga[DIM];

// i è l'indice di scrittura corrente dentro riga[] (prossima posizione libera).
// Parte da 0 e cresce a ogni carattere valido letto; non deve mai superare DIM-1.
byte i = 0;

void setup() {
  Serial.begin(115200); // per Arduino Uno R4, per R3 impostare 9600
  while (!Serial) {}
  Serial.println("Inserisci un comando (LED_ON / LED_OFF / HELP) e premi Invio");
}

void loop() {
  while (Serial.available() > 0) {
    char c = (char)Serial.read();

    // Se il Monitor è su "Both NL & CR", scarto il ritorno carrello '\r'
    if (c == '\r') continue;

    if (c == '\n') {            // RIGA COMPLETA
      riga[i] = '\0';           // Chiudo la stringa C (terminatore)
      eseguiComando(riga);      // Passo l'intera riga alla funzione di parsing
      i = 0;                    // Reset per la prossima riga
    } else if (i < DIM - 1) { // Protezione da overflow: lascio 1 posto per '\0' riga[i++] = c; // Accumulo il carattere e avanzo l'indice } // Se il buffer è pieno, i caratteri extra vengono ignorati } } void eseguiComando(char* cmd) { // strcmp(a,b) confronta due stringhe C (char*) e restituisce: // 0 --> uguali
  //  <0 --> a < b // >0  --> a > b
  // ricordare: è case-sensitive ("LED_ON" != "led_on").

  if (strcmp(cmd, "LED_ON") == 0) {   // esattamente "LED_ON"?
    Serial.println("Accendo (simulato) LED");
  } else if (strcmp(cmd, "LED_OFF") == 0) {
    Serial.println("Spengo (simulato) LED");
  } else if (strcmp(cmd, "HELP") == 0) {
    Serial.println("Comandi validi: LED_ON, LED_OFF, HELP");
  } else {
    Serial.print("Comando sconosciuto: ");
    Serial.println(cmd);
  }
}

ricordo che strcmp(...)

  • confronta contenuti delle C-string fino al primo ‘\0’
  • ritorna 0 quando le stringhe sono identiche (è il caso che usiamo negli if)
  • è case-sensitive: “LED_ON” è diverso da “led_on”
  • per confronti su prefisso: strncmp(cmd, “PWM:”, 4) == 0
  • ricorda di passargli stringhe chiuse con ‘\0’ (come facciamo con riga[i] = ‘\0’;)

Ho parlato su queste pagine in più occasioni degli array (seguite il link) un ripasso veloce:

  • un array è una sequenza contigua di celle tutte dello stesso tipo, indicizzate da 0 a N-1;
  • un char buf[N] è spesso usato come stringa C: la parte “valida” termina con '\0' (byte zero);
  • mai uscire dai limiti: per una stringa lunga al massimo N-1, lascia sempre l’ultimo posto per '\0';
  • accesso in scrittura: mantenere un indice (es. i) e verifica i <N-1 prima di scrivere e poi chiudere con buf[i]='\0';
  • per evitare “sporcizia” residua, non serve azzerare tutto il buffer ogni volta: basta chiudere correttamente la stringa;
  • per copiare in sicurezza: strncpy(dst, src, N-1); dst[N-1] = '\0';
  • se preferite evitare la gestione manuale di '\0', valutate String di Arduino; però per prestazioni/memoria prevedibili (e in contesti embedded reali) gli array di char restano la scelta più affidabile.

Esempio 03: lettura “fino a terminatore” con blocco controllato

// Prof. Maffucci Michele
// data: 11.11.25
// Lettura "fino a terminatore" con blocco controllato
// Leggere una sequenza di caratteri dall’utente fino a un terminatore (es. '#')
// usando funzioni bloccanti ma con un timeout breve, così la loop resta reattiva.

const byte DIM = 32;    // dimensione massima del buffer, compreso lo '\0' finale
const char TERM = '#';  // carattere terminatore scelto per “chiudere” l’input

char buf[DIM];  // buffer dove accumulo i caratteri ricevuti

void setup() {
  Serial.begin(115200);
  while (!Serial) {}       // su schede con USB nativa: attendo l’apertura della porta
  Serial.setTimeout(100);  // timeout per tutte le funzioni bloccanti Serial (100 ms)
                           // -> se l’utente NON digita nulla, la readBytesUntil attenderà
                           //    al massimo 100 ms e poi restituirà 0 byte letti
  Serial.println("Digita testo e termina con '#' (terminatore).");
  Serial.println("Suggerimento: imposta il line ending su 'No line ending'.");
}

void loop() {
  // readBytesUntil BLOCCA fino a quando:
  //  - incontra il terminatore (TERM), oppure
  //  - raggiunge (DIM-1) caratteri (lasciamo 1 posto per '\0'), oppure
  //  - scade il timeout impostato con setTimeout(...)
  //
  // Ritorna quanti byte REALMENTE copiati in buf (senza includere il terminatore).
  size_t n = Serial.readBytesUntil(TERM, buf, DIM - 1);

  if (n > 0) {      // ho letto qualcosa (terminatore trovato o buffer quasi pieno)
    buf[n] = '\0';  // chiudo la C-string

    // Se nel Serial Monitor usate "Both NL & CR" ed inviate per errore \r o \n
    // dentro al testo, potreste voler ripulire la stringa:
    // stripCRLF(buf);

    Serial.print("Ricevuto (");
    Serial.print(n);
    Serial.print(" char): ");
    Serial.println(buf);

    // Esempio: gestisco un comando speciale
    if (strcmp(buf, "RESET") == 0) {  // strcmp ritorna 0 se le stringhe sono identiche
      Serial.println("-> Comando RESET eseguito (simulato).");
    } else if (strncmp(buf, "PWM:", 4) == 0) {  // prefisso "PWM:" seguito da valore

      // "buf" contiene l'intera riga ricevuta, es. "PWM:128"
      // Confrontato il prefisso "PWM:", vogliamo estrarre solo la parte numerica ("128").
      // "buf + 4" significa: punta al 5° carattere della stringa (salta 'P','W','M',':').
      // atoi(...) converte la sottostringa numerica in int (qui: 128).

      int v = atoi(buf + 4);
      if (v >= 0 && v <= 255) { Serial.print("-> Imposto PWM a ");
        Serial.println(v);
      } else {
        Serial.println("-> Valore PWM fuori range (0..255).");
      }
    } else {
      Serial.println("-> Testo generico ricevuto.");
    }

    // readBytesUntil CONSUMA il terminatore dal flusso,
    // quindi non dovete "buttarlo" voi. Se invece usate readBytes (senza Until),
    // il terminatore resterebbe nel buffer e dovreste occuparvene manualmente.
  }

  // Se n == 0, non ho letto nulla: o non è arrivato niente entro 100 ms,
  // o l'utente ha inviato meno caratteri del previsto e non ha terminato con '#'.
  // In ogni caso il loop rimane libero di fare altro.
}

setTimeout() controlla l’attesa massima delle funzioni bloccanti come readBytesUntil(), readString(), parseInt() ecc.

Esempio 04: Lettura di numeri con parseInt() e validazioni

// Prof. Maffucci Michele
// data: 11.11.25
// Acquisire un numero intero inserito dall'utente, con controllo di validità.
// Impostazioni: line ending opzionale; consigliato "A capo(NL)".

void setup() {
  Serial.begin(115200); // per Arduino Uno R4, per R3 impostare 9600
  while (!Serial) {}
  Serial.setTimeout(3000);  // 3 s per digitare il numero
  Serial.println("Inserisci un numero intero tra 0 e 255 e premi Invio:");
}

void loop() {
  if (Serial.available() > 0) {
    long val = Serial.parseInt();  // blocca finché trova cifre o va in timeout
    if (val >= 0 && val <= 255) { Serial.print("OK, hai scritto: "); Serial.println(val); } else { Serial.println("Valore non valido o fuori range (0..255). Riprova."); } // Svuoto eventuali residui sulla linea while (Serial.available() > 0) {
      Serial.read();
    }
    Serial.println("Inserisci un nuovo numero:");
  }
}

parseInt() e parseFloat() saltano caratteri non numerici, leggono il numero e rispettano setTimeout().

Esempio 05: parser a stati non bloccante

// Prof. Maffucci Michele
// data: 11.11.25
// Parser “a stato” senza blocchi
// Leggere comandi testuali dalla Serial senza usare funzioni bloccanti.
// Formati accettati (esempi):
//   - "S1\n"   -> LED ON
//   - "S0\n"   -> LED OFF
//   - "PWM:123\n" -> imposta duty a 123
//
// Strategia:
//  - Usiamo una MACCHINA A STATI: IN_ATTESA -> IN_TOKEN -> (':' visto?) IN_VALORE -> (newline) ESEGUI
//  - Leggiamo byte con available()/read() (mai bloccare).
//  - Accumuliamo in due buffer separati: token (comando) e valore (parametro).
//  - Al newline '\n' consideriamo il comando “completo” e lo eseguiamo.
//  - Ignoriamo '\r' (utile se il Monitor invia "Both NL & CR").
//
// Vantaggi: loop reattiva (puoi fare sensori/attuatori mentre leggi), nessun timeout da gestire.

enum StatoInput {      // Enum = elenco di costanti intere leggibili che rappresentano gli stati possibili
  IN_ATTESA,           // Non sto leggendo nulla (skip spazi, attendo primo char utile del token)
  IN_TOKEN,            // Sto leggendo il nome del comando (es. "S1" o "PWM")
  IN_VALORE            // Ho visto ':' e sto leggendo il valore (es. "123")
};
// Nota: di default, IN_ATTESA=0, IN_TOKEN=1, IN_VALORE=2 (interi consecutivi).

StatoInput stato = IN_ATTESA;   // stato corrente della macchina a stati

// Dimensioni dei buffer: lascia sempre 1 char per il terminatore '\0'
const byte DIM_TOKEN = 8;
const byte DIM_VAL   = 8;

char token[DIM_TOKEN];          // buffer per il comando (senza parametri)
char valore[DIM_VAL];           // buffer per il parametro (se presente)
byte iT = 0, iV = 0;            // indici di scrittura nei buffer

// (Opzionale) Normalizza in minuscolo: utile se vuoi comandi case-insensitive
char toLowerChar(char c) {
  if (c >= 'A' && c <= 'Z') return c + ('a' - 'A');
  return c;
}

void setup() {
  Serial.begin(115200);
  while (!Serial) {}
  Serial.println("Comandi: S0, S1, PWM:<0..255>  (termina con Invio)");
  Serial.println("Esempi: S1\\n   S0\\n   PWM:128\\n");
}

void loop() {
  // ---- QUI PUOI FARE ALTRO (letture sensori, logica, attuatori) ----
  // NIENTE nel parser sotto blocca l'esecuzione.

  while (Serial.available() > 0) {
    char c = (char)Serial.read();     // prendo 1 byte dal buffer RX
    if (c == '\r') continue;          // ignoro CR (se Monitor manda Both NL & CR)

    if (c == '\n') {                  // newline = riga/ comando completo
      token[iT]  = '\0';              // chiudi stringhe C
      valore[iV] = '\0';

      esegui(token, valore);          // interpreta ed esegui

      // reset parser per prossima riga
      stato = IN_ATTESA;
      iT = iV = 0;
      continue;
    }

    // Se non è newline, gestisco in base allo stato
    switch (stato) {

      case IN_ATTESA:
        // Salto eventuali spazi iniziali, poi inizio a costruire il token
        if (c == ' ' || c == '\t') {
          // rimango in attesa
        } else {
          stato = IN_TOKEN;           // ho trovato il primo char "utile"
          if (iT < DIM_TOKEN - 1) {
            token[iT++] = c;          // salvo nel token
          }
          // se pieno, i char extra verranno scartati (vedi controllo nei case seguenti)
        }
        break;

      case IN_TOKEN:
        if (c == ':') {
          // Due casi:
          // - Comandi con parametro (es. "PWM:123"): passo allo stato IN_VALORE
          // - Comandi senza parametro ma con ':' accidentale: comunque vado in IN_VALORE (sarà vuoto)
          stato = IN_VALORE;
        } else if (c == ' ' || c == '\t') {
          // Ignoro spazi in mezzo al token (scelta di design: potresti anche “chiudere” il token qui)
        } else {
          // Accumulo altri caratteri del token se c'è spazio
          if (iT < DIM_TOKEN - 1) token[iT++] = c;
          // Se il token supera la capienza, i char extra vengono silenziosamente scartati
        }
        break;

      case IN_VALORE:
        // Tutto ciò che arriva fino al newline è il valore (spazi inclusi)
        if (iV < DIM_VAL - 1) valore[iV++] = c;
        // Se supera la capienza, scarto il resto del valore
        break;
    }
  }
}

// Esegue il comando interpretando token e valore
void esegui(const char* tk, const char* val) {
  // --- Esempio: comandi "S1" e "S0" (LED simulato) ---
  // strcmp ritorna 0 quando le stringhe sono identiche (case-sensitive).
  if (strcmp(tk, "S1") == 0) {
    Serial.println("LED = ON (simulato)");
    return;
  }
  if (strcmp(tk, "S0") == 0) {
    Serial.println("LED = OFF (simulato)");
    return;
  }

  // --- Esempio: comando con parametro "PWM:<0..255>" ---
  // Confronto solo il prefisso "PWM" (3 caratteri). Se uguale, mi aspetto il valore in 'val'.
  if (strncmp(tk, "PWM", 3) == 0) {
    // Converte il valore da stringa a intero.
    // Nota: atoi non segnala errori; in casi reali preferisci controllare che 'val' contenga solo cifre.
    int duty = atoi(val);
    if (duty >= 0 && duty <= 255) {
      Serial.print("Imposto PWM a ");
      Serial.println(duty);
    } else {
      Serial.println("PWM fuori range (0..255).");
    }
    return;
  }

  // --- Default: comando sconosciuto ---
  Serial.print("Comando sconosciuto: '");
  Serial.print(tk);
  Serial.print("'");
  if (val[0] != '\0') {
    Serial.print(" con valore '");
    Serial.print(val);
    Serial.print("'");
  }
  Serial.println();
}

Funzionamento:

  1. Accumulo byte finché ce n’è (available()>0).
  2. Transizioni di stato:
    • IN_ATTESA > ignora spazi > primo carattere utile > IN_TOKEN
    • IN_TOKEN > caratteri “nome comando”; se arriva ':' > IN_VALORE
    • IN_VALORE > accumulo fino al newline
  3. Newline \n = “fine comando”: chiudo le stringhe con '\0' e chiamo esegui(...)
  4. protezione buffer: sempre DIM-1 per lasciare spazio al '\0'. Char extra > scartati
  5. niente blocchi: nessun readString/parse*; la loop() resta fluida.

Non ricordo se avevo già parlato di enum ma vi scrivo qualcosina.

Un enum elenca costanti intere con nomi leggibili, esempio:

enum StatoInput { IN_ATTESA, IN_TOKEN, IN_VALORE };

equivale (di default) a: IN_ATTESA=0, IN_TOKEN=1, IN_VALORE=2.

Usarlo in una macchina a stati è utile perché:

  • leggibilità: stato = IN_TOKEN; è auto-esplicativo
  • sicurezza: eviti “numeri di significato ambiguo” (0,1,2) sparsi nel codice
  • aiuta con switch (stato) { … }

Tipo sottostante: in C/C++ classico l’enum usa un intero (di solito int); su AVR è tipicamente int.
In C++11 esiste anche enum class (scoped enum), con scope ristretto e nessuna conversione implicita a int:

enum class StatoInput { InAttesa, InToken, InValore };
StatoInput s = StatoInput::InAttesa;
// switch(s) { case StatoInput::InToken: ... }

Qualche NOTA importante a questa lezione, dedicato ai:

  1. “lamentosi”
  2. studenti e non, bravi o svogliati
  3. a chi si dimentica che ciò che scrivo è gratuito ed in primis è rivolto ai miei studenti che hanno la priorità
  4. alle persone che fanno parte della categoria dei “flamer” o dei “troll” quindi per me identificabili come “ciucci” e “perdi tempo a tradimento” (come diceva un mio insegnante)

dico:

  1. è stato abbastanza faticoso scrivere questa lezione, pertanto non troverete le consuete dimostrazioni animate alla fine di ogni sketch
  2. copiate ed incollate e verificate se non ci sono errori, non dovrebbero essercene, ma la stanchezza può averne aggiunto qualcuno… consiglio: fate dell’errore un motivo di miglioramento
  3. se notate qualche formattazione del codice di esempio (codice tutto su un riga) ciò è imputabile a qualche carattere precedente nello sketch che aggiunge un errore di formattazione, poiché ancora non ho trovato soluzione, fate “copia ed incolla” nell’IDE Arduino ed una successiva “Formattazione automatica” dal menù strumenti questo aggiusta tutto
  4. non assegnerò come di consueto gli esercizi finali per due motivi:
    • credo che sia già un esercizio capire il funzionamento degli sketch (per me è stato un bel ripasso) 🙂
    • vedi punto 2 (stanchezza)

Buon studio a tutti 🙂

Visualizzare i dati inviati da un micro:bit ad un computer via seriale con un emulatore di terminale

Durante le attività di sperimentazione del mio prossimo corso sui laboratori green mostrerò l’uso di RTC per la rilevazione automatica dei dati provenienti da sensori, sarà quindi necessario leggere i dati ricevuti dal micro:bit ed inviati al micro:bit attraverso un PC via seriale USB. Per leggere le informazioni che transitano sulla seriale è possibile usare qualsiasi emulatore di terminale. Seguendo il link: Outputing serial data from the micro:bit to a computer trovate la documentazione dell’help online di microbit.org che mostra l’utilizzo di alcuni emulatori terminali per i diversi sistemi operativi.

Una modalità estremamente semplice e che vi tornerà utile anche in altre occasioni, è quella che fa uso della Serial Monitor di Arduino, vediamo di seguito come fare questa operazione.

Configurazione

Passo 1
Qualche tempo fa avevo indicato come usare l’IDE di Arduino per programmare micro:bit, seguite la procedura indicata nel link

Passo 2
Impostare la scheda

Passo 3
Impostare la porta a cui è connesso il micro:bit

Passo 4
Aprire uno sketch vuoto e fare click sull’icona Monitor Seriale

Passo 5
Impostate la velocità di comunicazione a 115200 baud

Il risultato che vedete nell’immagine è quello che deriva dalla realizzazione di un programma scritto in Blocks con l’IDE grafico di micro:bit in cui viene inviato ogni minuto sulla seriale:

  • la data della misurazione
  • l’ora della misurazione
  • la temperatura rilevata dal sensore di temperatura del micro:bit e la temperatura rilevata dal sensore disposto sull’RTC DS3231.

Ovviamente questi dati potranno essere inviati ad altri dispositivi con modalità di trasmissione diverse, tutto questo sarà approfondito durante il corso.

Buon Making a tutti 🙂

CoolTerm – Alternative alla Serial Monitor di Arduino

La comunicazione seriale viene usata in moltissimi dispositivi elettronici che utilizziamo ogni giorno. Rispetto alla comunicazione parallela (4, 8 o 64-bit), la comunicazione seriale è utilizzata spesso nei sistemi embedded a causa del basso costo e della trasmissione efficace nelle lunghe distanze.
Per chi opera con Arduino, sa che l’uso della Serial Monitor è necessario per la lettura di dati inviati serialemente tra Arduino ed il computer con altri dispositivi elettronici, ad esempio l’invio sulla Serial Monitor dei valori di temperatura rilevata da un sensore di temperatura connesso ad Arduino.

La comunicazione seriale è inoltre uno strumento utilissimo per effettuare il debug, ovvero inviare dei messaggi sulla seriale per l’utente al fine di rilevare errori o mettere in evidenza i passi di esecuzione di un programma o lo stato di un sistema.

I dati inviati da Arduino al Computer o viceversa possono essere visualizzati sul monitor del computer (attraverso la finestra della Serial Monitor) o su altri dispositivi come ad esempio un display LCD connesso ad Arduino.

Ho mostrato più volte in precedenti lezioni l’uso dei comandi seriali: Serial.begin(), Serial.print(), Serial.read() e l’utilizzo della Serial Plotter che permette di visualizzare il grafico su piano cartesiano dei dati seriali trasmessi da Arduino.

Sicuramente anche voi, nell’uso costante della Serial Monitor, avrete notato i limiti dello strumento, come esempio l’impossibilità del salvataggio dei dati inviati ad un file di log.
E’ utile quindi considerare l’uso di un terminale alternativo che offra maggiori funzionalità rispetto alla Serial Monitor, come ad esempio visualizzare:

  • i caratteri di controllo;
  • i dati in formato testo o binario o entrambi;
  • salvare i dati rilevati in un file di log.

Tra i molti software disponibili il mio consiglio va a CoolTerM, un terminale user-friendly estremamente utile.
CoolTerM viene distribuito con licenza freeware / donationware sviluppato da Roger Meier ed è disponibile per dispositivi Windows, Mac, Linux.

Come si usa CoolTerM per rilevare dati che provengono da Arduino.

A titolo di esempio sfruttiamo quanto già svolto in passato, visualizzimo su CoolTerM la distanza di un ostacolo rilevato da un sensore ad ultrasuoni.
Prendiamo come riferimento lo sketch che segue, tratto da una mia lezione sull’uso del sensore ad ultrasuoni per un kit robotico, nello stesso post trovate la teoria di funzionamento del sensore HC-SR04.

/* Prof. Maffucci Michele

   Utilizzo del sensore ultrasuoni HC-SR04
   Misura della distanza di un ostacolo

*/

long durata;          // durata dell'impulso
long distanza;        // distanza dell'oggetto
int pin_echo = 7;     // pin Arduino a cui è collegato il sensore SR04
int pin_trig = 9;     // pin Arduino a cui è collegato il sensore SR04

void setup()
{
    Serial.begin(9600);
	pinMode(pin_trig, OUTPUT);
  	pinMode(pin_echo, INPUT);
  	Serial.println("Sensore ad ultrasuini");
}

void loop()
{
  Serial.print("Distanza ostacolo: ");
  Serial.println(distanzaOstacolo());
  delay(100);
}

// rilevazione distanza ostacolo

// misura la distanza dell'ostacolo
long distanzaOstacolo()
{
  digitalWrite(pin_trig, LOW);
  delayMicroseconds(2);
  digitalWrite(pin_trig, HIGH);
  delayMicroseconds(10);
  digitalWrite(pin_trig, LOW);
  durata = pulseIn(pin_echo, HIGH);
  distanza = (durata / 2.0) / 29.1;
  delay(100);
  return distanza;
}

Con Arduino connesso al computer aprire CoolTerM e selezionare “Options”, da quì impostare la porta a cui è connessa la scheda e la velocità di connessione a 9600, una velocità diversa non permetterà una lettura corretta dei dati.

Torna alla schermata principale fate clic su “Connect”, ciò permetterà di avviare la trasmissione dei dati tra Arduino e il computer e i dati inizieranno ad apparire.
Quando desiderate interrompere la trasmissione ovviamente farete clic su “Disconnetti”.

E’ spesso utile analizzare i dati raccolti, in tal caso selezionate: Connection > Capture to Text/Binary File > Start ciò permetterà di salvare i vostri dati in un file TXT che potrà poi essere analizzato ad esempio con un foglio di calcolo.

CoolTerM offre ulteriori funzionalità molto utili:

  • se disponibile è possibile connettere più porte seriali concorrenti;
  • visualizzazione dei dati in formato semplice o esadecimale (HEX);
  • salvataggio e caricamento delle opzioni di connessione;
  • gestione dei caratteri speciali;
  • scripting;
  • eco locale di dati trasmessi / ricevuti;

Buon Making a tutti 🙂

Arduino: controllo sequenziale uscite digitali

Durante la progettazione di un sistema di automazione accade frequentemente di avere la necessità di ripetere, sequenzialmente e in modo continuo, l’attivazione di apparati (ad es. motori) oppure la lettura continua dei dati provenienti da più sensori. Come attività di ripasso per i miei studenti ho deciso di riprendere alcuni argomenti affrontati nelle scorse settimane con specifiche esperienze di laboratorio:

  • automi a stati finiti;
  • utilizzo degli array;
  • input valori interi da serial monitor;
  • marcia, arresto, pausa di sequenze;
  • controllo uscite digitali mediante ingressi analogici;
  • realizzazione di commutatori con pulsanti con uno o più pulsanti;
  • utilizzo corretto dei tipi di dati per risparmiare memoria;
  • e molto altro

Di seguito 9 sketch in cui vengono ripresi gli argomenti sopra elencati e che potranno essere utilizzati nei prossimi mesi per sviluppare ulteriori sperimentazioni.
Come sempre all’interno degli sketch proposti trovate le spiegazioni di ogni parte del codice ed in alcuni casi trovate link di approfondimento che rimandano a questo sito.

Per ripercorrere gli argomenti svolti partirò dal classico sketch che permette di realizza l’accensione sequenziale di LED, come quello che potete trovare nelle mie slice: Alfabeto Arduino – Lezione 2 a pagina 66.
I LED nel circuito identificano gli apparati da attivare sequenzialmente, realizzando così il classico effetto “super car” (i diversamente giovani 🙂 sanno perché si chiama così).
Circuito e sketch verranno poi modificati per rispondere alle specifiche indicate ad inizio di ogni esempio.

Sketch 01

Sequenza di accensione e spegnimento da destra e sinistra e viceversa di 8 LED con tempo di accensione di 100 millisecondi.

 

/* Prof. Michele Maffucci
   30.12.2020
   Lezione di riferimento: https://wp.me/p4kwmk-4D3

   Versione 01
   Sequenza di accensione e spegnimento alternato
   da destra e sinistra e viceversa di 8 LED con
   tempo di accensione di 100 millisecondi.

   Questo codice è di dominio pubblico
 */

// creazione di un array di 8 pin a cui vanno collegati i LED
// per ulteriori informazioni sull'uso degli array si consulti il seguente link:
// http://wp.me/p4kwmk-26e

byte ledPin[] = {3, 4, 5, 6, 7, 8, 9, 10};

// per approfondimenti sull'uso dei tipi di dati
// si consultino i link:
// https://wp.me/p4kwmk-4As
// https://wp.me/p4kwmk-1zF

// intervallo di accensione/spegnimento
byte ritardoLed = 100;

// indicatore di direzione di accensione
byte direzione = 1;

// indice dell'array per l'accensione del LED
byte ledCorrente = 0;

// variabile in cui memorizzare il tempo di accensione di Arduino
// per ulteriori informazioni sui tipi unsigned long si consulti il seguente link:
// http://wp.me/p4kwmk-1zF

unsigned long tempoTrascorso;

void setup() {
  // impostiamo tutti i pin ad output
  for (byte x=0; x<8; x++) {
    pinMode(ledPin[x], OUTPUT);
  }

  // Memorizzazione del tempo trascorso
  // dal momento in cui avviamo Arduino
  // Per ulteriori informazioni sull'uso di millis() si consulti il seguente link:
  // http://wp.me/p4kwmk-1QG

  tempoTrascorso = millis();
}

void loop() {
  // Se sono passati "ritardoLed" millisecondi dall'ultimo cambiamento
  if ((millis() - tempoTrascorso) > ritardoLed) {
    cambiaStatoLed();
    tempoTrascorso = millis();
  }
}

// la funzione cambiaStatoLed() permette di controllare
// la sequenza di accensione dei LED

void cambiaStatoLed() {
  // spegne tutti i LED
  for (byte x=0; x<8; x++) {
    digitalWrite(ledPin[x], LOW);
  }
  // accende il LED corrente
  digitalWrite(ledPin[ledCorrente], HIGH);
  // incrementa la variabile direzione
  ledCorrente += direzione;
  // cambia la direzione se si arriva alla fine
  if (ledCorrente == 7) {
    direzione = -1;
  }
  if (ledCorrente == 0) {
    direzione = 1;
  }
}

Sketch 02

Sequenza di accensione e spegnimento da destra e sinistra e viceversa di 8 LED. Con un trimmer è possibile variare il tempo di accensione nell’intervallo da 50 millisecondi a 1000 millisecondi (1 secondo).

/* Prof. Michele Maffucci
   30.12.2020
   Lezione di riferimento: https://wp.me/p4kwmk-4D3

   Versione 02
   Sequenza di accensione e spegnimento alternato
   da destra e sinistra e viceversa di 8 LED controllato
   da un trimmer che permetterà di variare il tempo di accensione
   da 50 millisecondi a 1000 millisecondi (1 secondo).

   Questo codice è di dominio pubblico
 */

// creazione di un array di 8 pin a cui vanno collegati i LED
// per ulteriori informazioni sull'uso degli array si consulti il seguente link:
// http://wp.me/p4kwmk-26e

byte ledPin[] = {3, 4, 5, 6, 7, 8, 9, 10};

// per approfondimenti sull'uso dei tipi di dati
// si consultino i link:
// https://wp.me/p4kwmk-4As
// https://wp.me/p4kwmk-1zF

// intervallo di accensione/spegnimento
int ritardoLed;

// variabile in cui memorizzare il valore restituito dall'analogRead
int val = 0;

// indicatore di direzione di accensione
byte direzione = 1;

// indice dell'array per l'accensione del LED
byte ledCorrente = 0;

// variabile in cui memorizzare il tempo di accensione di Arduino
// per ulteriori informazioni sui tipi unsigned long si consulti il seguente link:
// http://wp.me/p4kwmk-1zF

unsigned long tempoTrascorso;

void setup() {
  Serial.begin(9600);
  // impostiamo tutti i pin ad output
  for (byte x=0; x<8; x++) {
    pinMode(ledPin[x], OUTPUT);
  }

  // Memorizzazione del tempo trascorso
  // dal momento in cui avviamo Arduino
  // Per ulteriori informazioni sull'uso di millis() si consulti il seguente link:
  // http://wp.me/p4kwmk-1QG

  tempoTrascorso = millis();
}

void loop() {

  // valore analogico letto su A0 inserito con il trimmer
  val = analogRead(A0);

// Togliere il commento per valutare
// valore massimo/minimo del valore restituito
// dall'analogRead in questo modo si potranno
// inserire nella map i valori massimi e minimi
// dell'intervallo di partenza

// Serial.println(val);
// delay(1000);

  // ValMax = 285, ValMin = 719
  // riconvertiti nell'intervallo 50, 1000

  ritardoLed = map(val, 285, 719, 50, 1000);

  // Se sono passati "ritardoLed" millisecondi dall'ultimo cambiamento
  if ((millis() - tempoTrascorso) > ritardoLed) {
    cambiaStatoLed();
    tempoTrascorso = millis();
  }
}

// la funzione cambiaStatoLed() permette di controllare
// la sequenza di accensione dei LED

void cambiaStatoLed() {
  // spegne tutti i LED
  for (byte x=0; x<8; x++) {
    digitalWrite(ledPin[x], LOW);
  }
  // accende il LED corrente
  digitalWrite(ledPin[ledCorrente], HIGH);
  // incrementa la variabile direzione
  ledCorrente += direzione;
  // cambia la direzione se si arriva alla fine
  if (ledCorrente == 7) {
    direzione = -1;
  }
  if (ledCorrente == 0) {
    direzione = 1;
  }
}

Sketch 03

Sequenza di accensione e spegnimento alternato da destra e sinistra e viceversa di 8 LED. L’accensione di ogni LED è fissato in partenza a 100 millisecondi. Con un messaggio sulle Serial Monitor viene richiesto di inserire un nuovo tempo di accensione e spegnimento di ogni LED (delay), tempo che può essere scelto a piacimento.

Lo schema di collegamento è analogo a quello utilizzato per lo sketch 01.

/* Prof. Michele Maffucci
   30.12.2020
   Lezione di riferimento: https://wp.me/p4kwmk-4D3

   Versione 03
   Sequenza di accensione e spegnimento alternato
   da destra e sinistra e viceversa di 8 LED.

   Partenza sequenza con 100 millisecondi e messaggio sulla
   Serial Monitor per modificare il tempo di
   accensione e spegnimento del singolo LED (delay)

   Questo codice è di dominio pubblico
 */

// creazione di un array di 8 pin a cui vanno collegati i LED
// per ulteriori informazioni sull'uso degli array si consulti il seguente link:
// http://wp.me/p4kwmk-26e

byte ledPin[] = {3, 4, 5, 6, 7, 8, 9, 10};

// per approfondimenti sull'uso dei tipi di dati
// si consultino i link:
// https://wp.me/p4kwmk-4As
// https://wp.me/p4kwmk-1zF

// intervallo di accensione/spegnimento
int ritardoLed = 100;

// indicatore di direzione di accensione
byte direzione = 1;

// indice dell'array per l'accensione del LED
byte ledCorrente = 0;

// variabile in cui memorizzare il tempo di accensione di Arduino
// per ulteriori informazioni sui tipi unsigned long si consulti il seguente link:
// http://wp.me/p4kwmk-1zF

unsigned long tempoTrascorso;

// per stampare una sola volta il messaggio sulla Serial Monitor
bool abilitaMessaggio = 0;

void setup() {
  // inizializzazione della serial monitor
  Serial.begin(9600);

  // impostiamo tutti i pin ad output
  for (byte x=0; x<8; x++) {
    pinMode(ledPin[x], OUTPUT);
  }

  // Memorizzazione del tempo trascorso
  // dal momento in cui avviamo Arduino
  // Per ulteriori informazioni sull'uso di millis() si consulti il seguente link:
  // http://wp.me/p4kwmk-1QG
  tempoTrascorso = millis();
}

void loop() {
  // consente di visualizzare sulla Serial Monitor
  // una sola stampa delle stringa
  if (abilitaMessaggio == 0) {
    // ritardo che evita la doppia stampa del messaggio
    delay(200);
    Serial.print("Inserisci il ritardo in millisecondi: ");
    abilitaMessaggio = 1;
  }

  // Controlla se è disponibile almeno un carattere sulla seriale
  // La Serial.available() restituisce
  // 1 se presente un cattere,
  // 0 se non è presente un carattere

  // per maggior informazioni sull'uso di parseInt() consultare il link:
  // https://wp.me/p4kwmk-4Ah

  if (Serial.available())
  {
    // in r viene memorizzato il valore inserito
    // attraverso la Serial Monitor
    int r = Serial.parseInt();
    if (r != 0) {
      ritardoLed = r;
      Serial.println(ritardoLed);

      // abilita alla stampa di una nuova stringa:
      // "Inserisci il ritardo in millisecondi: "
      abilitaMessaggio = 0;
    }
  }

  // funzione che fa lampeggiare il LED su Arduino
  lampeggio();
}

void lampeggio() {
  // Se sono passati "ritardoLed" millisecondi dall'ultimo cambiamento
  if ((millis() - tempoTrascorso) > ritardoLed) {
    cambiaStatoLed();
    tempoTrascorso = millis();
  }
}

// la funzione cambiaStatoLed() permette di controllare
// la sequenza di accensione dei LED

void cambiaStatoLed() {
  // spegne tutti i LED
  for (byte x = 0; x < 8; x++) {
    digitalWrite(ledPin[x], LOW);
  }
  // accende il LED corrente
  digitalWrite(ledPin[ledCorrente], HIGH);
  // incrementa la variabile direzione
  ledCorrente += direzione;
  // cambia la direzione se si arriva alla fine
  if (ledCorrente == 7) {
    direzione = -1;
  }
  if (ledCorrente == 0) {
    direzione = 1;
  }
}

Continua a leggere

Arduino: utilizzo del metodo parseInt() per la conversione di un stringa di testo che rappresenta un numero in un numero

Nel primo esercizio della lezione: Arduino: Stepper 28BYJ-48 – AccelStepper library veniva chiesto di impostare i parametri di azionamento del motore passo paso da Serial Monitor, questa operazione può essere svolta usando la classe toInt(), ma in modo più efficace e semplice possiamo usare la classe parseInt(). In questa breve lezione un esempio di controllo del lampeggio di un LED da Serial Monitor mediante l’uso di parseInt().

È possibile ricevere numeri con più di una cifra utilizzando i metodi parseInt e parseFloat che semplificano l’estrazione di valori numerici da seriale. (Funziona anche con Ethernet e altri oggetti derivati dalla classe Stream)

Serial.parseInt() e Serial.parseFloat() leggono i caratteri seriali e restituiscono la loro rappresentazione numerica.

I caratteri non numerici prima del numero vengono ignorati e il numero termina con il primo carattere che non è una cifra numerica (o “.” Se si utilizza parseFloat). Se non ci sono caratteri numerici nell’input, le funzioni restituiscono 0, quindi bisogna controllare i valori zero e gestirli in modo appropriato.

Nel dettaglio

  • I caratteri iniziali che non sono cifre o sono numeri negativi vengono ignorati;
  • L’analisi si interrompe quando non sono stati letti caratteri per un valore di tempo di timeout che può essere configurato oppure viene letta una non cifra;
  • Se non sono state lette cifre valide quando si verifica il timeout (vedere Serial.setTimeout ()), viene restituito 0; Serial.parseInt () eredita dalla classe Stream.

Se avete la Serial Monitor configurata per inviare una nuova riga o un ritorno a capo (o entrambi) quando fate clic su invia, parseInt o parseFloat proveranno ad interpretare il return come numero, ma poiché il ritorno a capo non è un numero il valore restituito da parseInt o parseFloat sarà zero.

Nell’esempio che segue un invio imposta blinkRitardo a zero il che implica che il LED non lampeggia.

// Prof. Maffucci Michele
// 10.11.2020
// Impostazione del delay del Blink da tastiera

int lampeggioRitardo = 0;
int chiave = 0;
void setup()
{
  Serial.begin(9600); // inizializzazione della serial monitor
  pinMode(LED_BUILTIN, OUTPUT); // imposta il pin come output
}
void loop()
{
  // ritardo per evitare una doppia scrittura
  // della prima stampa a monitor
  delay(1000);

  // consente di visualizzare sulla Serial Monitor
  // una sola stampa delle stringa
  if (chiave == 0) {
    Serial.print("Inserisci il ritardo in millisecondi: ");
    chiave = 1;
  }

  // Controlla se è disponibile almeno un carattere sulla seriale
  // La Serial.available() restituisce
  // 1 se presente un cattere,
  // 0 se non è presente un carattere

  if (Serial.available())
  {
    int r = Serial.parseInt(); // in r viene memorizzato il valore inserito in numero
    if (r != 0) {
      lampeggioRitardo = r;
      Serial.println(lampeggioRitardo);

      // abilita alla stampa di una nuova stringa:
      // "Inserisci il ritardo in millisecondi: "
      chiave = 0;
    }
  }
  lampeggio(); // funzione che fa lampeggiare il LED su Arduino
}

// il LED lampeggia con i tempi di
// accensione e spegnimento determinati da lampeggioRitardo
void lampeggio()
{
  digitalWrite(LED_BUILTIN, HIGH);
  delay(lampeggioRitardo); // il delay dipende dal valore in lampeggioRitardo
  digitalWrite(LED_BUILTIN, LOW);
  delay(lampeggioRitardo);
}

Esercizio 1
Dato un LED RGB connesso ad Arduino, realizzare un selettore che da Serial Monitor consente di controllare l’accensione e lo spegnimento del rosso, del verde e del blu

Esercizio 2
Svolgere l’esercizio 1 della lezione Stepper 28BYJ-48 – AccelStepper library usando la parseInt()