Login Login
MORE

WIDGETS

Widgets

Wanted articles
Who is online?
Article tools

Difference between revisions of "Arduino C++"

From Aino Wiki

Jump to: navigation, search
(Metodo empirico)
((Mappa e Link))
 
Line 1,716: Line 1,716:
  
 
----
 
----
[[Cpp Info fondamentali | C++ info fondamentali]] | [[C Sharp | C#]] | [[Dizionario:Termini elettronica | Dizionario Elettronica]] | [[Visual Studio]] | [[Dizionario]]
+
[[Cpp Info fondamentali | C++ info fondamentali]] | [[C Sharp | C#]] | [[Dizionario:Termini elettronica | Dizionario Elettronica]] | [[Visual Studio]] | [[Dizionario:Termini tecnici informatici]] | [[Dizionario]]
  
 
----
 
----
 
'''Parole chiave''':
 
'''Parole chiave''':

Latest revision as of 18:52, 28 February 2026

Arduino utilizza una versione di C++ che è principalmente limitata a C++11, anche se i compilatori più recenti spesso supportano funzionalità più moderne (come C++14, C++17 o addirittura C++20). Il compilatore è avr-gcc (Link interno qui).
Quello che però caratterizza il mondo Arduino NON è tanto il linguaggio, ma il framework "wiring" (x DOC qui) che mette a disposizione tutta una serie di funzioni già pronte che ti nascondono la complessità del funzionamento delle MCU (Microcontrollore).
Tutte le applicazioni partono dall'esecuzione della funzione steup() e successivamente dalla loop() quest'ultima in sequenza infinita. 
Test script C++ on-line con aiuto dell'AI: onecompiler.com

Differenze fondamentali con C# e C++

Tenendo sempre presente che per Arduino ci riferiamo al compilatore C++ versione 11, ecco alcune differenze che io ho raccolto:

  • Tutte le applicazioni\sketch partono dall'esecuzione, in primo step, della funzione steup() e successivamente dalla loop() quest'ultima in sequenza infinita. Pertanto, dopo le prime impostazioni in setup() tutta la logica di un programma è nella loop().
  • L'istruzione printf non esiste ma si usa la print
  • C'è differenza tra C++ (e C++ di Arduino) e C#, per la definizione di array si usano le parentesi quadarate ma in C++ si mettono dopo il nome della variabile mentre in C# si mettono dopo il nome del tipo.
  • La gestione degli errori con try {} catch() {} è disabilitata utilizzando il compilatore C++ in arduino, questo perché è difficilmente implementabile la gestione di tutti i possibili errori con i microcontrollori.
  • Non c'è modo di usare librerie come std::vector , std::string e comunque sono sconsigliati perché usati in un microcontrollore con memoria limitata. Articolo in cui si spega limitazioni e come installare STL Library che include , la libreria su GitHub è qui.
  • La "AVR toolchain"(strumenti di sviluppo) per Arduino non supporta le librerie C++ standard, vanno aggiunte manualmente. Il motivo è semplice, da Arduno Uno R3 compreso in giù dispongono di poca memoria e fisicamente non potrebbero usare queste librerie.
  • Rispetto a C#, non si può usare la switch case su un char* ovvero su un array di caratteri o volgarmente una stringa.
  • Rispetto a C#, il passaggio di oggetti a funzioni NON è per referenza ma per valore.

NOTARE

  • Nell'IDE di Arduino lo sviluppo in C++ non richiede la definizione dei prototipi delle funzioni definite ed usate oltre le due funzioni di setup() e loop() MA.. se si usa Visual Studio Code e relative estensioni PlatformIO e quella dedicata a C++ con relativo compilatore "standard", i prototipi sono indispensabili pena l'errore in compilazione.

Tipi fondamentali

Occorrerà considerare il microcontrollore specifico dell'hardware ...
Doc ufficiale sulle variabili [1]

Tipo Descrizione etc
array Un array è un insieme di variabili a cui si accede tramite un indice. Gli array nel linguaggio di programmazione C++ in cui sono scritti gli sketch di Arduino possono essere complessi, ma utilizzare array semplici è relativamente semplice.
int myInts[6];
 
// Declare an array without explicitly choosing a size (the compiler
// counts the elements and creates an array of the appropriate size):
int myPins[] = {2, 4, 8, 3, 6, 4};
 
// Declare an array of a given length and initialize its values:
int mySensVals[5] = {2, 4, -8, 3, 2};
 
// When declaring an array of type char, you'll need to make it longer
// by one element to hold the required the null termination character:
char message[6] = "hello";
//--- es di accesso:
int myArray[10]={9, 3, 2, 4, 3, 2, 7, 8, 9, 11};
    // myArray[9]    contains 11
    // myArray[10]   is invalid and contains random info
bool Può contenere solo due valori: ture o false. Occupa 1 byte di memoria. es.:
bool running = false;
if (running) {...}
byte

Interi senza segno da 0 a 255

char Tipo usato per immagazzinare un carattere che è indicato tra apici singoli ' oppure mediante il codice ASCII. Occupa 8 bit es.:
char mioCarattere = 'A';
char mioCarattere = 65; // Carattere A usando il codice decimale ASCII
double Rappresenta numeri in virgola mobile.

Su Arduino Uno con ATmega si usano 4 byte  Su Arduino Due si usano 8 byte

enum Tipo enumerativo, molto probabilmente la struttura sottostante è un array du byte ma non è sicuro che un suo elemento occupi esattamente un byte....
enum EnPictureType    {Text, Rectangle, Square, Circle, Triangle};
//...etc...
//Ecco come assegnare un valore:
EnPictureType picType;
picType = EnPictureType::Text;
//...
switch (picType)
{
  case EnPictureType::Text:
	//...
	break;
  case EnPictureType::Rectangle:
	//...
	break;
  case EnPictureType::Square:
	//...
	break;
//....etc
}
float Per rappresentare numeri reali, numeri in virgola mobile. Rappresenta numeri da -3.4028235E+38 a 3.4028235E+38. Occupa 32 bits (4 bytes)
float sensorCalbrate = 1.117;
int x;
int y;
float z;
 
x = 1;
y = x / 2;
z = (float)x / 2.0;
//--
float x1 = 2.9;
int y1 = x1 + 0.5;  // y1 sarà: 3
//--
float x2 = 2.9;
int y2 = round(x2);  // y2 sarà: 3
int Su Arduino Uno con ATmega si usano 16 bit, con range da -32.768 a -32.767

Su Arduino Due con SAMD (per le MKR 1000 WiFi e Zero) si usano 32 bit con range da -2.147.483.648 a 2.147.483.647

doc
long<code></code> Numeri interi di 4 byte
size_t E' un tipo di dato capace di rappresentare la dimenzione di ogni tipo di oggetto in bytes. Es d'udo sono il tipo di ritorno di sizeof() e Serial.print(). es.:
int16_t m_tftWidth = 0, m_tftHeight = 0;
string Il testo delle stringhe può esser rappresentato in due modi. Si può usare il tipo String() o creare comunque una stringa come array di caratteri terminante con un null (codificato con '\0'). Per dettagli sill'oggetto String() quì

Esempi di dichiarazione:

char Str1[15];
char Str2[8] = {'a', 'r', 'd', 'u', 'i', 'n', 'o'};
char Str3[8] = {'a', 'r', 'd', 'u', 'i', 'n', 'o', '\0'};
char Str4[] = "arduino";
char Str5[8] = "arduino";
char Str6[15] = "arduino";
doc
char *myStrings[] = 
      {"This is str 1", "This is str 2", "This is str 3",
      "This is str 4", "This is str 5", "This is str 6"
      };
 
    void setup() {
      Serial.begin(9600);
    }
 
    void loop() {
      for (int i = 0; i < 6; i++) {
        Serial.println(myStrings[i]);
        delay(500);
      }
    }
String() Costruisce una istanza della classe String. Es.:
String stringOne = "Hello String";                    
// using a constant String
    String stringOne = String('a');                       
// converting a constant char into a String
    String stringTwo = String("This is a string");        
// converting a constant string into a String object
    String stringOne = String(stringTwo + " with more");  
// concatenating two strings
    String stringOne = String(13);                        
// using a constant integer
    String stringOne = String(analogRead(0), DEC);        
// using an int and a base
    String stringOne = String(45, HEX);                   
// using an int and a base (hexadecimal)
    String stringOne = String(255, BIN);                  
// using an int and a base (binary)
    String stringOne = String(millis(), DEC);             
// using a long and a base
    String stringOne = String(5.698, 3);                  
// using a float and the decimal places
word Numeri interi senza segno ed occupa 16 bits

Array

 

Array - string

Sono stringhe di testo costruite come array di caratteri che terminano col terminatore, carattere null = 0x000. conformi al C classico assolutamente da non confondere con "String" (il cui uso è sconsigliato).[[File:Cpp_Chars_array_01.jpg|none|548x133px]Sono valide le seguenti:
char Str1[15];
//Nella seguente sarà il compilatore ad aggiungere il carattere extra '\0'
char Str2[8] = {'a', 'r', 'd', 'u', 'i', 'n', 'o'}; // HA DIMENSIONE 8! Ovvero 7 + 1, l'1 è '\0'
//La seguente che ha il carattere null = '\0' ESPLICITO
char Str3[8] = {'a', 'r', 'd', 'u', 'i', 'n', 'o', '\0'};
 
//Array inizializzato con una stringa costante tra doppi apici ", il compilatore
//      aggiungerà '\0' alla fine e darà automaticamente la dimensione 8!
char Str4[] = "arduino";
//Definizione ed inizializzazione esplicita.. nota la dimensione 8!
char Str5[8] = "arduino";
//Come prima ma con extra-dimensionamento, '\0' sarà dopo la 0 di "arduino"
char Str6[15] = "arduino";

Librerie

string.h

L'IDE di arguino include automaticamente le seguenti funzioni (anche il tool PlatformIO) che afferiscono all'header string.h disponibili in ogni sketch (quindi non c'è bisogno di #include ).

Funzione Descrizione Esempio
int strcmp(const char *, const char *) Restituisce un intero minore, uguale o maggiore di zero se s1 risulta, rispettivamente, minore (si controlla il valore dei corrispondenti valori ASCII dei caratteri), uguale o maggiore (stessa logica del minore) di s2.

Una conseguenza dell'ordinamento utilizzato da strcmp() è che se s1 è una sottostringa iniziale di s2, allora s1 è considerato "minore di" s2.

if (strcmp(s1, s2)) {
    //Azioni nel caso le due stringhe fossero DIVERSE
}
if (strcmp(s1, s2) == 0) {
    //Azioni nel caso le due stringhe fossero UGUALI!
}
char *strcpy(char *dest, const char *src) The strcpy() function copies the string pointed to by src (including the terminating '\\0' character) to the array pointed to by dest. The strcpy() function returns a pointer to the destination string dest.
char arrDestin[25];
char arrSource[25] = "Stringa sorgente";
strcpy(arrDestin, arrSource);
void *memmem(const void *, size_t, const void *, size_t) The memmem() function returns a pointer to the beginning of the substring, or NULL if the substring is not found. If len2 is zero, the function returns s1
 
char *strstr(const char *, const char *) The strstr() function finds the first occurrence of the substring s2 in the string s1.

The terminating '\\0' characters are not compared.Returns a pointer to the beginning of the substring, or NULL if the substring is not found. If s2 points to a string of zero length, the function returns s1.Sostanzialmente se la stringa è stata trovata estrae la sottostringa cha inizia con quella ricercata e contisua sino alla sua fine (interrotta dal carattere \0)

    char strTesto[] = "La storia di uno di noi, uno onesto.";
    char strRicerca[] = "di noi";
 
    if (strstr(strTesto, strRicerca) != NULL) {
      cout << "Testo trovato:\r\n";
      std::cout << strstr(strTesto, strRicerca) << std::endl;
    }
    else {
      cout << "Testo NON trovato";
    }

Con il seguente output:

Testo trovato:
di noi, uno onesto.
size_t strlen(const char *src) Fornisce in output la lunghezza di una stringa src, carattere terminatore '\\0' escluso.
const char* pntStr = "Col_";
int lRoot = strlen(s);
cout << lRoot << endl; // Stamperà 4
 
char res[50] = "Col_1";
int lSource = strlen(res);
cout << lSource << endl; // Stamperà 5

stdio.h

Libreria inclusa di default negli schetch per cui non c'è bisogno di aggiungere alcun #include.

Funzione Descrizione Esempio
sprintf(char * destination, const char * format, arg1, arg2..); Copia una stringa formattata (quindi anche testo misto tra caratteri e numeri) in una variabile stringa destinazione (trattasi di array di caratteri).

La stringa destinazione avrà alla fine il carattere \0

char text[25];
	sprintf(text, "Simboli sequenza: %d/%d", nrFilterVisObj, DIM_DB_VOBJ);
	Print(text);
//..
void Print(const char* text){
 	Serial.println(text);
}
etc...

Uso in argomento di funzione

Passaggio di un array di caratteri\stringa come argomento di funzione:
L'argomento è passato per referenza mediante &, notare (&nomeArray) e poiché è un array di stringhe è come se fosse un array di array di caratteri con dimensione fissa (normalmente sarebbe orrendo ma qui parliamo di microcontrollori con memeoria limitata).
#include <iostream>
using namespace std;
 
char m_ArrDBSubTypes[3][10] = {"Number", "Letter", "Shape"};
 
void StampaArrayStr(char (&arr)[3][10]) {
  //La base dell'indice è 0 quindi per 3 elementi si cicla dino al'indice =2 (ovvero i < 3)
  for (int i = 0; i < 3; i++) {	
    cout << i << "-> " << arr[i] << "|\n";
  }
}
 
int main() 
{
    cout << "Test array:\n";
    StampaArrayStr(arrDBSubTypes);	
    return 0;
}
Output:
Test array:
0-> Number|
1-> Letter|
2-> Shape|

Uso come argomento in costruttore di classe

Passaggio di un array di caratteri\stringa come argomento di un costruttore di calsse:
Notare l'argomento al costruttore della classe "MyVisualObject ", const char* picSubType, ricordiamo che *è usato per accedere ad un oggetto a cui un puntatore si riferisce. Se anteposto ad un nome di una variabile indica che questa sarà un puntatore ovvero un indirizzo di memoria che conterrà una variabile.
Notare l'uso della libreria cstring in questo esempio ci serve per la copia da array ad array.
#include <iostream>
#include <cstring>	//Per usare la funzione di copia strcpy(destinazione, sorgente)
 
//COSTANTI
const int DIM_DB_VOBJ = 6;  const int DIM_DB_STYPE = 3;
const int DIM_STR_PicSubType = 8;
 
enum PictureType    {Text, Rectangle, Square, Circle, Triangle};
 
class MyVisualObject {
	public:
	//--------------- Attributi ----------------
	char Label;             //Opzionale nel caso l'oggetto da disegnare sia grafico e non testo		
	PictureType    PicType; //Enum.,   indica il tipo oggetto: testo, rettangolo, cerchio, triangolo
	char PicSubType[DIM_STR_PicSubType];	//Stringa sottotipo: "Number", "Letter", "Shape", "Symbol"		
	//--------------- END Attributi ------------
 
	//Costruttori:
	MyVisualObject() {}
	MyVisualObject(char label, PictureType picType, const char* picSubType) {
		Label = label; 
		PicType = picType; 
		strcpy(PicSubType, picSubType); //!! Copia da un array an un altro!
	}
};
 
MyVisualObject m_ArrDBVisualObj[DIM_DB_VOBJ];
 
void StampaArrObjOut() {
  for (int i=0; i<DIM_DB_VOBJ; i++){
    //cout << i << "\n";
    cout << i << "-> " << m_ArrDBVisualObj[i].Label << ", SubType=" << m_ArrDBVisualObj[i].PicSubType << "|\n";
  }
}
 
int main() 
{
    cout << "Test array:\n";
 
    //Assegnazione array di oggetti
    m_ArrDBVisualObj[0]  =  MyVisualObject('0', PictureType::Text, "Number");
    m_ArrDBVisualObj[1]  =  MyVisualObject('1', PictureType::Text, "Number");
	m_ArrDBVisualObj[2]  =  MyVisualObject('2', PictureType::Text, "Number");
	m_ArrDBVisualObj[3]  =  MyVisualObject('3', PictureType::Text, "Number");
	m_ArrDBVisualObj[4]  =  MyVisualObject('X', PictureType::Text, "Letter");
    m_ArrDBVisualObj[5]  =  MyVisualObject('Q', PictureType::Square, "Shape");
 
	StampaArrObjOut();
	return 0;
}
Output:
Test array:
0-> 0, SubType=Number|
1-> 1, SubType=Number|
2-> 2, SubType=Number|
3-> 3, SubType=Number|
4-> X, SubType=Letter|
5-> Q, SubType=Shape|

Custom utility

Create sempre nell'ottica di non usare pesanti librerie per quanto siano standard.

Comparazione

Per verificare semplicemente se due array di caratteri sono uguali o no:
class MyVisualObject {
public:
	char Label;             //Opzionale nel caso l'oggetto da disegnare sia grafico e non testo
	PictureType    PicType; //Enum.,   indica il tipo oggetto: testo, rettangolo, cerchio, triangolo
	char PicSubType[DIM_STR_PicSubType];//Stringa, indica il sottotipo: "Number", "Letter", "Shape"
//Costruttori:
	MyVisualObject() {}
}
MyVisualObject m_ArrDBVisualObj[14]; 
//-------------------------------------------------------
 
bool StrCmp(const char* arr1, const char* arr2) {
    int i = 0;
    while (arr1[i] != '\0' && arr2[i] != '\0') {
        if (arr1[i] != arr2[i]) return false;
        i++;
    }
    return arr1[i] == '\0' && arr2[i] == '\0';
}
int main() 
{
    // Your existing code...
    bool isEqual = StrCmp(m_ArrDBVisualObj[0].PicSubType, "Number");
    cout << "Comparison result: " << (isEqual ? "Equal" : "Not Equal") << "\n";
 
    return 0;
}

Istruzioni fondamentali

DOC ufficiale Arduino programming !

Istruzione Descrizione Esempio
pinMode(nrPin, direzione I/O) Usato nella funzione setup(), definisce il ruolo che avrà un pin di Arduino ovvero la direzione ovvero se INPUT o OUTPUT cioè se il pin sarà usato per leggere il valore assunto o per scrivere\accendere il valore in tensione (se usato per accogliere 3.3V o 5V a seconda del tipo di Arduino).
pinMode(13, INPUT);
//--Es completo
void setup() {
  pinMode(13, OUTPUT);
}
 
void loop() {
  digitalWrite(13, HIGH);
  delay(2000);
  digitalWrite(13, LOW);
  delay(1000);
}
digitalWrite(nrPin, stato); Usato nella funzione loop() su un pin digitale scrive lo stato alto o basso ovvero HIGH o LOW, 3.3V\5V oppure 0V
digitalWrite(13, !digitalRead(13));
digitalRead(numeroPin); Per leggere lo stato di un pin digitale. Potrà assumere uno di 2 valori associati alle due constanti intere HIGH o LOW.
int statoPinX = digitalRead(numeroPin);
analogWrite(numeroPin, valore_pwm); Produce uno speciale segnale a onda quadra chiamato PWM (Pulse With Modulation), è un segnale che passa continuamente da 0 a 5V in una certa percentuale di tempo. In realtà non usiamo una percentuale da 0 a 100 ma un numero compreso tra 0 e 255.
 
analogRead(numeroPinAnalogico); Così Arduino legge il valore della tensione del pin (sono i pin da A0 ad A5) e lo converte in un numero di misurazione che va da 0 a 1023. Notare che il valore di tensione più piccolo sarà 5V / 1024 ovvero 0.0048V
 
delay(500); Usato per attendere n millisecondi, in questo caso 500 ovvero mezzo secondo.
 
delayMicroseconds(500); Usato per attendere ancora meno rispetto a prima, m microsecondi, 1000 microsecondi sono 1 millisecondo.
 
map() Rimappa un range di numeri in un altro. Sintassi: map(value, fromLow, fromHigh, toLow, toHigh). Per dettagli [2]
//Se x in 1..50 rimappa all'opposto tra 50 e 1
y = map(x, 1, 50, 50, 1);
//Anche su numeri negativi:
y = map(x, 1, 50, 50, -100);
tone(numeroPin, frequenza, durata) Genera un segnale ad una certa frequenza sul pin desiderato, la nota prosegue sinché non la interrrompiamo con la funzione noTone(); Genera un onda quadra con un rapporto acceso spento (duty cycle) del 50%, per ascoltare la nota occorrerà collegare al pin un trasduttore.
Nota Equivalente anglosassone Frequenza (Hz)
Do C 261
Re D 294
Mi E 329
Fa F 349
Sol G 392
La A 440
Si B 493
Do C 523
 
pulseIn(numeroPin, valore, timeout); Serve a misurare quanto tempo dura un inpulso (ad es. può essere usato per verificare la psessione di un pulsante). Il secondo parametro può essere HIGH così che si inizierà la misura dal momento in cui il pin cambierà stato da basso a alto (max misurazione 3 minuti).
void setup() {
  Serial.begin(9600);
  pinMode(7, INPUT); 
}
 
void loop() {
  // Si misura il tempo in cui il pin 7 è in stato HIGH
 unsigned long durata = pulseIn(7, HIGH); 
if (durata > 0) {
    Serial.print("Durata del segnale HIGH: ");
    Serial.print(durata/1000.0);
    Serial.println(" millisecondi");
  } else {
    Serial.println("Timeout: nessun segnale HIGH rilevato.");
  }
}

Funzioni

Funzione Descrizione Esempio
abs(x); Valore assoluto di un numero.
int val = bit(n); Aiuta a trasformare un bit in numero decimale. La funzione equivale a moltiplicare 2 , tante volte quanto è indicato dal parametro fornito.
bit(0); // restituisce 1
bit(1); // restituisce 2
bit(3); // restituisce 8
//!!!! se si ha il numero binario 1010 calcoleremo il decimale come segue:
int val = 1 * bit(3) + 0 * bit(2) + 1 * bit(1) + 0 * bit(0);
byte val = bitRead(numero, bit_da_leggere); Per leggere un singolo bit di un numero, la posizione si legge dal meno significativo (più a dx) muovendosi verso sx
bitWrite(nr, bit_da_leggere, bit); Scrive il bit (0 o 1) nella posizione bit_da_leggere del numero nr. bitSet(nr, bit_da_impostare); è come bitWrite ma imposta il bit in posizione indicata a 1.
bitClear(nr, bit_da_azzerare); Imposta il bit indicato impostandolo a zero.
contraind(n, a, b); Contiene una variabile tra un valore minimo e massimo.
int n = analogRead(A0);
int r = constrain(n, 50, 200); //se n>200, r sarà 200
// se n > 50, r sarà comunque 50

boolean ret = isAlphaNumeric(carattere);

boolean ret = isUpperCase(carattere);

Ci sono anche altre funzioni per il trattamento dei caratteri, queste probabilmente le più utili.
 
max(a, b); Restituisce il massimo tra due numeri. Come parametri NON usare funzioni ma numeri!
unsigned long = micros();<code></code> Restituisce il numero di microsecondi trascorsi dall'avvio del programma (o reset della scheda), offrendo una misurazione del tempo estremamente precisa per operazioni come il controllo preciso di segnali, timing di eventi a breve termine, e la gestione di ISR (Interrupt Service Routines), micros si distingue da millis() che misura in millisecondi e è soggetta a overflow più frequente a causa della sua minore precisione. Overflow dopo 70 minuti! 
unsigned long = millis(); Restituisce il numero di millisecondi dal momento in cui Arduino ha iniziato l'esecuzione del programma corrente. Non può andare oltre i 50 giorni altrimenti la variabile andrebbe in overflow ! Vedere nei tips come alternativa al delay(milliseconds);
min(a, b); Restituisce il minimo tra due numeri. Come parametri NON usare funzioni ma numeri!
pow(base, esponente); Per calcolare le potenze indicando base ed esponente, i due parametri devono essere di tipo float e il risultato di tipo double.
random(max);

random(min, max);

Genera un numero casuale. Per avere numeri più attendibili conviene usare anche la funzione randomSeed(), allo scopo si può passare alla funzione un argomento preso da un pin analogico "scollegato", dove ci sarà sicuramente del rumore che aiuterà allo scopo.
void setup() {
  Serial.begin(9600);
  while (!Serial) {}
  randomSeed(analogRead(A0)); //Iniz. generatore num casuali 
}
 
void loop() {
  long n = random(100);//Numero a caso tra 0 e 99
  Serial.println("n");
  delay(1000);  
}
sin(alfa)

cos(alfa)
tan(alfa)

Funzioni trigonometriche, il parametro è l'angolo (valori da 0 a π) ed è in radianti espressi in double, anche il risultato è double.
sizeof(); Restituisce la dimensione in byte di una variabile. Utile con gli array di cui si vuole conoscere la lunghezza.
char testo[] = "Quanti caratteri?";
int nr = sizeof()testo; //Restituirà 17
sqrt(numero) Esegue la radice quadrata di un numero in qualsiasi formato producendo un risultato double.

PIN

Per leggere o scrivere
Usando pinMode in accoppiata con digitalkRead o digitalkWrite. Bisogna prima dichiarare i pin da utilizzare nella funzione setup() e successivamente in loop() si passa ad utilizzarli.
#include <Arduino.h> //<-- Usando VS code e PlatformIO
 
int PIN_x  = 13; //Definisco con una costante il PIN per una determinata azione
 
void setup() {
    pinMode(PIN_x, INPUT);
}
 
void loop() {
    if (digitalRead(PIN_x) == HIGH) {
    // Si esegue azione specifica assegnata allo stato HIGH del pin 13
    }
}

Analogici

I pin analogici vanno da A0 ad A5, servono per la lettura di segnali analogici, tensione da 0 a 5V, fa eccezione Arduino Uno R4 ed i nuovi dispositivi. Quindi su Uno R4 si può scrivere un segnale analogico sul pin A0 usato come uscita analogica (DAC, Digital Analog Converter).
Connesso ad una sorgente di tensione un qualsiasi pin analogico restituirà un valore con l'istruzione:
int valoreTensione = analogRead(numeroPosta_analogica); //Valore tensione è da normalizzare!
il valore restituito sarà un numero da 0 a 1023. Notare che il valore di tensione più piccolo sarà 5V/1024 ovvero 0.0048V, e vuo anche dire che ogni numero da 0 a 1023 vale 0.0048V
Sketch per avere i Volt e non un numero che li rappresenti
// Constants

const float V_REF = 5.0; // Analog reference voltage (e.g., 5V or 3.3V) const float R_BITS = 10.0; // ADC resolution (bits) const float ADC_STEPS = (1 << int(R_BITS)) - 1; // Number of steps (2^R_BITS - 1)   const int potentiometerPin = A3; // Potentiometer wiper connected to analog pin A3   void setup() {

 Serial.begin(9600); // Initialize serial communication
 Serial.println(ADC_STEPS);

}   void loop() {

 int rawValue = analogRead(potentiometerPin); // Read the analog input
 float voltage = (rawValue / ADC_STEPS) * V_REF; // Convert to voltage

 

 Serial.print("Voltage: ");
 Serial.print(voltage, 3); // Print voltage with 3 decimal places
 Serial.println(" V");

 

 delay(200); // Small delay to avoid flooding the serial monitor
}

Operatori

Operatore Descrizione Esempio
Modulo, numero%divisore Esegue l'operazione di calcolare il resto di una divisione, l'operazione modulo.
int i = 0;
if (i%255 == 0) {
   Serial.print("Si riprende il conteggio");
} else {
   Serial.print(i);
}
i++;

Funzioni non documentate

Funzione Descrizione Esempio
yield() Letteralmente la parola significadare la precedenza, cedere.

Funzione definita in "Arduino.h", serve a far respiare il sistema cedendo il controllo della CPU a un altro task o processo in attesa, permettendo di eseguire altre attività, specialmente utile durante i blocchi delay() o in sistemi multitasking cooperativi, per evitare che il microcontrollore si blocchi e possa gestire più funzioni contemporaneamente senza passare a un vero multitasking preemptive.

 
 

Librerie

Wire

Doc ufficiale language-reference (Se si usa VS Code con Platformio, questa libreria è "Built-in" cioè usabile direttamente).
Questa libreria consente la comunicazione con I2C devices (anche dispositivi TWI), è una funzionalità presente in TUTTE le schede Arduino. I2C è un protocollo molto comune, primariamente usato per leggere e spedire dati a/da componenti I2C esterni. Per conoscerne di più: Arduino & I2C.
Il protocollo I2C prevede l'utilizzo di due linee per inviare e ricevere dati: un pin di clock seriale (SCL), che la scheda controller Arduino invia impulsi a intervalli regolari, e un pin di dati seriali (SDA), attraverso il quale i dati vengono inviati tra i due dispositivi.
In I2C, c'è un dispositivo controller, con uno o più dispositivi periferici collegati alle linee SCL e SDA del controller.
Alcuni moduli breakout board consentono di collegarli direttamente, con fili scoperti su una breadboard. Solitamente la connessione tra Arduino e la board I2C avviene come segue:

  • VCC - pin 5V/3V3 (a seconda del modulo breakout)
  • GND - GND
  • SDA - SDA
  • SCL - SCL

Alcune istruzioni fondamentali:

Istruzione Descrizione Esempio
begin() Inizializza il bus I2C. .
end() Chiude il bus I2C. .
requestFrom() Richiede bytes da un dispositivo periferico .
beginTransmission() Inizia ad accodare una trasmissione .
endTransmission() Transmit the bytes that have been queued and end the transmission .
write() Writes data from peripheral to controller or vice versa .
available() Returns the number of bytes available for retrieval .
read() Reads a byte that was transmitted from a peripheral to a controller .
setClock() Modify the clock frequency .
onReceive() Register a function to be called when a peripheral receives a transmission .
onRequest() Register a function to be called when a controller requests data .
setWireTimeout() Sets the timeout for transmissions in controller mode .
clearWireTimeoutFlag() Clears the timeout flag .
getWireTimeoutFlag() Checks whether a timeout has occurred since the last time the flag was cleared. .

SD

Libreria per gestire l'accesso al lettore di SD cards.
 

Arduino Printf

Per usare la funzione printf() di C++ al posto di Serial.print() o Serial.println() che serve ma non aiuta nella efficacia\efficienza del codice (video di P.Aliverti qui).
Di EmbeddedArtistry qui il progetto GitHub. Nell'Arduino IDE va installata come file ZIP esterno. In VS Code con PlatformIO si aggingono i files nella cartella "lib" anche se occorrerà aggiungervi sempre in "lib" la catella "extras".

  • Andare sul sito GitHub, dal pulsante "<> Code" scaricare il .ZIP che però conterrà tutto il progetto della libreria (a noi serviranno invece solo alcuni files)
  • Scaricato lo zip, generalmente i file che ci interessano sono nella cartella "src", si copiano in "include"
Arduino Lib ZIP da GitLab 02.jpg
  • In questo caso NON sarà sufficiente copiare i due files (.h e .cpp) "solo" nella cartella "include" ma nel file principale "LibPrintf.h" si fa riferimento (lo si vede esplicitamente aprendo il file .h) ad altro file che è in altra cartella e che creeeremo appositamente.
Arduino Lib ZIP da GitLab 03 PlatformIO.jpg
  • A questo punto siam pronti ad usare la libreria medinate #include

Il seguente esempio funziona sotto VS Code ed estensione PlatformIO:

#include <LibPrintf.h>
 
void setup() {
  Serial.begin(9600);
  printf("Semplice testo\n");
  printf("x: %d \n", 100);
  printf("v: %f \n", 123.34);
 
  printf("%5d %5d %5d\n", 1, 22, 333);
  printf("%05d %05d %05d\n", 12, 23, 456);
  printf("%-5d %-5d %-5d\n", 12, 23, 456);
  printf("%+5d %+5d %+5d\n\n", 12, -23, 456);
  printf("%5.2f\n", 123.3456);
  printf("%10.2e\n\n", 123.3456);
  printf("str: %s\n", "Stringa innestata nel testo di output.");
  printf("perc: 10%%\n");
 
  printf("%*.*f\n", 10,3, 123.3456);
}
 
void loop() {
}

NOTARE i "\n" per andare accapo.

EEPROM.h

NOTA L'Arduino Nano ESP32 NON dispone di una EEPROM fisica dedicata come i classici Arduino basati su AVR. Invece, utilizza una parte della memoria Flash per emulare la EEPROM, permettendo di memorizzare dati in modo permanente. Se si salva/legge usando i metodi della libreria qui indicata, #include EPROM.h, non ci saranno errori riportati in compilazione ma alla fine non salverà nulla e si leggera sempre 0.

E' una libreria (guida) con funzioni utili ad usare la memoria non volatile del dispositivo per informazioni da conservare (ad es. di configurazione); varia in base al modello ma la EEPROM è di almeno 512 byte. Tenere presente cha questa memoria ha una vita limitata a circa 100.000 cicli di scrittura\cancellazione.
Questa libreria è automaticamente inclusa negli sketch sia in Arduino IDE che sotto VS Code con estensione PlatformIO.
Punti Chiave:

  • EEPROM.put(): utilizzato per scrivere qualsiasi tipo di dato, inclusi float, int, long o strutture complesse.
  • EEPROM.get(): utilizzato per leggere il dato scritto precedentemente.
  • Indirizzi: poiché un float occupa 4 byte, se scrivi un altro dato, il prossimo indirizzo disponibile deve essere indirizzo + sizeof(float) (cioè indirizzo + 4).
  • Durata: la EEPROM ha un numero limitato di cicli di scrittura (circa 100.000), quindi evita di usare put() nel loop() continuamente
Istruzione Descrizione Esempio
EEPROM.read(address) Legge un byte dalla EEPROM. Le posizioni che non sono mai state scritte hanno il valore 255.

la variabile address è un valore intero che va da zero a 255, la read restituisce un valore di tipo byte.

#include <EEPROM.h>
int address = 0;
int value;
 
void setup()
{
  Serial.begin(9600);
}
 
void loop()
{
  value = EEPROM.read(address);
 
  Serial.print(address); Serial.print("\t");
  Serial.print(value); Serial.println();
  address = address + 1;
 
  if (address == 512) //Riparte da zero passando dalla
              //ultima alla prima locazione di memoria.
    address = 0;
 
  delay(500);
}
EEPROM.write(address, value) address: è la locazione di memoria in cui scrivere, parte da 0 (int)

value: è la variabile valore di tipo byte da scrivere, può assumere valori da 0 a 255 (byte)Una scrittura impiega 3.3ms per esser completata.

#include <EEPROM.h>
void setup()
{
  for (int i = 0; i < 255; i++)
    EEPROM.write(i, i);
}
 
void loop()
{
}
EEPROM.update(address, value) E' come la write ma scriverà solo se il valore da scrivere è diverso da quello che già c'è.
#include <EEPROM.h>
void setup()
{
  for (int i = 0; i < 255; i++) {
    // this performs as EEPROM.write(i, i)
    EEPROM.update(i, i);
  }
  for (int i = 0; i < 255; i++) {
    // write value "12" to cell 3 only the first time
    // will not write the cell the remaining 254 times
    EEPROM.update(3, 12);
  }
}
 
void loop()
{
}
EEPROM.get(address, data) Imposta il valore della referenza con il contenuto della cella all’indirizzo specificato.

address: the location to read from, starting from 0 (int)data: the data to read, can be a primitive type (eg. float) or a custom struct

#include <EEPROM.h>
struct MyObject{
  float field1;
  byte field2;
  char name[10];
};
 
void setup(){
  float f = 0.00f;   //Variable to store data read from EEPROM.
  int eeAddress = 0; //EEPROM address to start reading from
 
  Serial.begin( 9600 );
  while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }
  Serial.print( "Read float from EEPROM: " );
 
  //Get the float data from the EEPROM at position 'eeAddress'
  EEPROM.get( eeAddress, f );
  Serial.println( f, 3 );  //This may print 'ovf, nan' if 
                           //the data inside the EEPROM is not a valid float.
 
  // get() can be used with custom structures too.
  eeAddress = sizeof(float); //Move address to the next byte after float 'f'.
  MyObject customVar; //Variable to store custom object read from EEPROM.
  EEPROM.get( eeAddress, customVar );
 
  Serial.println( "Read custom object from EEPROM: " );
  Serial.println( customVar.field1 );
  Serial.println( customVar.field2 );
  Serial.println( customVar.name );
}
 
void loop(){ /* Empty loop */ }
EEPROM.put(address, data) Imposta il contenuto della cella con il valore e tipo della referenza passata.

address: the location to write to, starting from 0 (int) data: the data to write, can be a primitive type (eg. float) or a custom struct

 
EEPROM[address] Utilizzato per accedere in lettura o in scrittura sulla EEPROM come se fosse un array. L’indice passato equivale all’indirizzo della cella.
#include <EEPROM.h>
 
void setup(){
  unsigned char val;
 
  //Read first EEPROM cell.
  val = EEPROM[ 0 ];
 
  //Write first EEPROM cell.
  EEPROM[ 0 ] = val;
 
  //Compare contents
  if( val == EEPROM[ 0 ] ){
    //Esegue quel che deve qui...
  }
}
 
void loop(){ /* Empty loop */ }
EEPROM.length() Restituisce il numero di celle della EEPROM, è un intero senza segno.

Esempio scrittura della EEPROM, anche di oggetti

Da sito personale di Andrea Lombardo: [3]
#include <EEPROM.h>
 
void setup() {
  Serial.begin(9600);
 
  while (!Serial) {
    //Attendo che venga aperto il monitor seriale
  }
 
  /*
     Definisco degli indirizzi di memoria.
     Ciascuno per ogni esempio.
  */
  int IND_0 = 0; // Per il metodo write() e read() nel successivo sketch
  int IND_1 = 10; // Per il metodo put() e get() nel successivo sketch
  int IND_2 = 20; // Per il metodo put() e get() con tipo di dato custom
  int IND_3 = 40; // Per il metodo EEPROM[] e update()
 
  //Valore di tipo float per il primo esempio put() e get()
  float tipoFloat = 0.65;
 
  //Struttura custom per il secondo esempio put() e get()
  struct CustomObj {
    byte numero;
    float conVirgola;
    char testo[10];
  };
 
  //Oggetto custom per il secondo esempio put() e get()
  CustomObj mioCustomObj = {
    2,
    9.21000f,
    "Ciaone"
  };
 
  //Il metodo write
  EEPROM.write(IND_0, 25); //salvo all'indirizzo 0 il valore 25
  Serial.println("Scrittura con metodo write eseguita.");
 
  //Il metodo put - primo esempio
  EEPROM.put(IND_1, tipoFloat); //salvo all'indirizzo 1 il valore di tipoFloat con il relativo tipo (float)
  Serial.println("Scrittura con metodo put - primo esempio eseguita.");
 
  //Il metodo put - secondo esempio
  EEPROM.put(IND_2, mioCustomObj); //salvo all'indirizzo 2 l'oggetto mioCustomObj avente struttura CustomObj
  Serial.println("Scrittura con metodo put - secondo esempio eseguita.");
 
  //EEPROM come array
  EEPROM[IND_3] = 9; //salvo all'indirizzo 3 il numero 9
  Serial.println("Scrittura con sintassi array eseguita.");
 
  Serial.println("Adesso carica il secondo sketch e controlla che ci sia tutto");
}

Esempio, lettura della EEPROM, anche di oggetti

Da sito personale di Andrea Lombardo: [4]
#include <EEPROM.h>
 
void setup() {
  Serial.begin(9600);
 
  while (!Serial) {
    //Attendo che venga aperto il monitor seriale
  }
 
  /*
     Definisco gli stessi indirizzi di memoria dello sketch precedente.
  */
  int IND_0 = 0; // Per il metodo write() e read() nel successivo sketch
  int IND_1 = 10; // Per il metodo put() e get() nel successivo sketch
  int IND_2 = 20; // Per il metodo put() e get() con tipo di dato custom
  int IND_3 = 40; // Per il metodo EEPROM[] e update()
 
  //Valore di tipo float per il primo esempio put() e get()
  float tipoFloat;
 
  //Struttura custom per il secondo esempio put() e get()
  struct CustomObj {
    byte numero;
    float conVirgola;
    char testo[10];
  };
 
  //Oggetto custom per il secondo esempio put() e get()
  CustomObj mioCustomObj;
 
  //Il metodo read
  int valPrimoEsempio = EEPROM.read(IND_0); //leggo dall'indirizzo 0
  Serial.print("Lettura con metodo read mi aspetto 25: ");
  Serial.println(valPrimoEsempio, DEC);// mi aspetto il valore 25
 
  //Il metodo get - primo esempio
  EEPROM.get(IND_1, tipoFloat); //leggo dall'indirizzo 1 il valore di tipo float e lo metto nella variabile tipoFloat
  Serial.print("Lettura con metodo get - primo esempio mi aspetto 0.65: ");
  Serial.println(tipoFloat, 2); // mi aspetto il valore 0.65
 
  //Il metodo get - secondo esempio
  EEPROM.get(IND_2, mioCustomObj); //leggo dall'indirizzo 2 e metto il contenuto nel mio oggetto custom
  Serial.println("Lettura con metodo get - secondo esempio mi aspetto (2, 9.21, 'Ciaone') ");
  Serial.println(mioCustomObj.numero, DEC); // mi aspetto 2
  Serial.println(mioCustomObj.conVirgola, 2); // mi aspetto 9.21
  Serial.println(mioCustomObj.testo);  // mi aspetto "Ciaone"
 
  //EEPROM come array
  EEPROM[IND_3] = 9; //salvo all'indirizzo 3 il numero 9
  Serial.println("Scrittura con sintassi array eseguita.");
 
  int valAsArray = EEPROM[IND_3]; //leggo dall'indirizzo 3
  Serial.print("Lettura con sintassi array mi aspetto 9: ");
  Serial.println(valAsArray); // mi aspetto 9
 
  //Aggiorno ultimo valore con metodo update()
  EEPROM.update(IND_3, 18);
  Serial.println("Aggiornamento valore con metodo update eseguito.");
 
  //leggo nuovamente dall'indirizzo 3
  int valAfterUpdate = EEPROM[IND_3];
  Serial.print("Lettura con sintassi array mi aspetto 18: ");
  Serial.println(valAfterUpdate); // mi aspetto 18
 
  Serial.println("Fatto!");
 
}
 
void loop() {
  // tutto avviene nel setup
}

Verifica indirizzo EEPROM non valorizzato

Metodo base

Per capire se un indirizzo (o una cella) in EEPROM è stato precedentemente valorizzato su Arduino, il metodo standard consiste nel verificare se il suo contenuto è 0xFF (255 in decimale).

#include <EEPROM.h>
 
int address = 0; // Indirizzo da controllare
 
void setup() {
  Serial.begin(9600);
 
  byte valore = EEPROM.read(address);
 
  if (valore == 0xFF) {
    Serial.println("EEPROM vuota, inizializzo...");
    // Scrivi qui i valori di default
    EEPROM.write(address, 1); // Esempio: scrivo 1
  } else {
    Serial.print("Valore trovato: ");
    Serial.println(valore);
  }
}
 
void loop() {}

Metodo robusto

Se devi salvare dati complessi (strutture, float, int multipli) e vuoi essere sicuro che siano stati scritti dal tuo programma, utilizza una "firma" (signature) nei primi byte.

  • Inizia il setup leggendo 2 byte da un indirizzo fisso (es. 0 e 1).
  • Se i byte corrispondono a un valore magico predefinito (es. 0xABCD), allora la EEPROM è stata inizializzata.
  • Altrimenti, scrivi i valori di default e la firma.
#include <EEPROM.h>
 
const int addrFlag = 0;
const int addrData = 2;
const unsigned int SIGNATURE = 0xABCD; // Firma magica
 
void setup() {
  unsigned int checkSign;
  EEPROM.get(addrFlag, checkSign);
 
  if (checkSign != SIGNATURE) {
    // Prima esecuzione o EEPROM corrotta
    Serial.println("Inizializzazione EEPROM...");
    EEPROM.put(addrData, 123.45f); // Salva dati di default
    EEPROM.put(addrFlag, SIGNATURE); // Scrive la firma
  } else {
    // EEPROM già in uso
    float dati;
    EEPROM.get(addrData, dati);
    Serial.print("Dati letti: ");
    Serial.println(dati);
  }
}
void loop() {}

Metodo empirico

In base al tipo della variabile si può verificare la corrispondenza al associato valore di default.
Se il tipo è numerico, ad es float, il contenuto, nonostante tutto può non essere 0xFF ma NaN (not a number). Per verificare il valore è NAN si usa la funzione matematica isnan()

Arduino Nano ESP32

A differenza della EEPROM classica, quella emulata su ESP32 richiede l'inizializzazione della dimensione e il "commit" (salvataggio) esplicito dei dati per scriverli effettivamente nella memoria flash.
Passaggi fondamentali:

  1. Inclidere comunque la libreria con: #include
  2. Inizializzazione (begin): definire la dimensione (in byte) nel setup()
  3. Lettura/Scrittura: usare put()/get() (consigliati) o write()/read()
  4. Salvataggio (commit): fondamentale! Chiamare EEPROM.commit() per salvare i dati.

Per applicazioni moderne su ESP32, si consiglia l'uso della libreria <Preferences.h>, che gestisce meglio la memoria non volatile e riduce l'usura della flash rispetto all'emulazione EEPROM.

Esempio con libreria EEPROM.h

#include <EEPROM.h>
 
// Definire la dimensione della EEPROM (es. 512 byte)
#define EEPROM_SIZE 512
 
void setup() {
  Serial.begin(115200);
 
  // 1. Inizializzare la EEPROM
  if (!EEPROM.begin(EEPROM_SIZE)) {
    Serial.println("Errore nell'inizializzazione EEPROM");
    delay(1000);
    ESP.restart();
  }
 
  // --- SCRITTURA ---
  int valoreScritto = 12345;
  int indirizzo = 0;
 
  // EEPROM.put scrive qualsiasi tipo di dato (int, float, struct)
  EEPROM.put(indirizzo, valoreScritto);
 
  // 2. Fondamentale: commit() per rendere la scrittura permanente
  EEPROM.commit(); 
  Serial.println("Dato scritto e salvato.");
 
  // --- LETTURA ---
  int valoreLetto;
  EEPROM.get(indirizzo, valoreLetto);
 
  Serial.print("Dato letto dalla EEPROM: ");
  Serial.println(valoreLetto);
}
 
void loop() {
  // Loop vuoto
}


Esempio con Preferences.h

La libreria Preferences.h è lo standard moderno per ESP32. È più efficiente, sicura e facile da usare perché:

  • Utilizza un sistema a chiave-valore (come un piccolo database) invece di indirizzi numerici.
  • Gestisce automaticamente i tipi di dati (int, float, stringhe).
  • Riduce l'usura della memoria Flash.
#include <Preferences.h>
Preferences prefs;
 
void setup() {
  prefs.begin("my-app", false); // Apre lo spazio "my-app" in lettura/scrittura
  prefs.putInt("punteggio", 42); // Salva un intero con chiave "punteggio"
  int valore = prefs.getInt("punteggio", 0); // Legge il valore (0 è il default)
  prefs.end();
}

How to

Usare librerie interne\esterne

  • Aggiunta libreria da Arduino IDE
    • libreria pre-installata. Click sulla voce di menu \Sketch\Include Library quindi scegliere la libreria tra quelle nella sezione "Arduino libraries" o in "Contributed libraries" (che sono quelle precedentemente installate manualmente). La selezione produrrà nel codice una o più istruzioni aggiunte in testa al file .ino #include
    • libreria non presente nell'IDE ma disponibile nell'archi on-line, si usa il "Library Manager". Click sulla voce di menu \Sketch\Include Library\Manage Library
    • Aggiunta libreria mediante zip file. Si scarica il .zip dal web (ad es. GitHub dopo aver cliccato su "<> Code"), tale file avrà una struttura standard riconoscibile e poi si importa dal menù principale \Sketch\Inluce Library\Add .ZIP Library....
  • Aggiunta libreria da VS Code, PatformIO: Link Interno

Creazione propria libreria

Link interno Moduli_o_Librerie.
Una libreria è fatta da due files che hanno lo stesso nome ma estensione .h (file di header) con le funzioni prototipo e risorse esportabili ed il file .cpp con le implemetazioni delle funzioni accennate nel file di header.
Per creare un file di header corretto è necessario sempre includere delle istruzioni che evitano di includere più di una volta la stessa liberia (vedere il gruppo che inizia con #ifndef nella implementazione del file di header).
Codice del file MioModulo.h
//Il seguente è un "trucco" per evitare di includere più volte una stessa libreria
//la costate Blink_h non è usata direttamente se non come flag qui
#ifndef Blink_h
#define Blink_h
 
#include "Arduino.h"
 
class Blink {
    public:
        //Costruttore:
        Blink(int pin);
        void blink(int tim)
 
    private:
        int _pin
}
#endif

Codice del file MioModulo.cpp

#include "Arduino.h"
#include "Blink.h"
 
//Implementazione costruttore!
Blink::Blink(int pin) {
    pinMode(pin, OUTPUT);
    _pin = pin;
}
 
void Blink::blink(int tim) {
    digitalWrite(_pin, HIGH);
    delay(tm);
    digitalWrite(_pin, LOW);    
    delay(tm);
}

Codice del file di client: ?.ino

#include "Blink.h"
 
Blink blink(13);
 
void setup() {}
 
void loop() {
    blink.blink(500);
}

Ottimizzazioni

Macro F()

La macro F() in Arduino serve a ottimizzare la memoria, indicando al compilatore di salvare le stringhe di testo (costanti) direttamente nella memoria Flash (a programma) invece che nella RAM (memoria dinamica), liberando preziosa RAM per altre operazioni e rendendo il codice più efficiente, specialmente su microcontrollori con poca memoria come l'Arduino UNO (Sulla versione R3 si dispone di soli 2Kbyte). Si usa principalmente nelle funzioni di stampa come Serial.print() o lcd.print(), per esempio Serial.println(F("Ciao mondo!"));.
L'utilizzo di PROGMEM è una procedura in due fasi. Una volta definita una variabile con PROGMEM, non può essere letta come una normale variabile basata su SRAM: è necessario leggerla utilizzando funzioni specifiche, anch'esse definite in pgmspace.h.

Serial.println(F("Ciao mondo!"));

PROGMEM

PROGMEM fa parte della libreria pgmspace.h [https://www.nongnu.org/avr-libc/user-manual/group__avr__, in molti casi affinché il flusso continui nel loop() per eseguire altro e non attendere si adottano stratagemmi come segue.
Ad es. per lo sketch del "Blink" si suggeriva:

void setup() {  
  pinMode(LED_BUILTIN, OUTPUT); // Il pin LED_BUILTIN è il 13 ...
}
 
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);  // Accende il LED sulla scheda
  delay(1000);                      // Attende FERMANDO L'ESECUZIONE per 1 secondo
  digitalWrite(LED_BUILTIN, LOW);   // Si spegne il LED
  delay(1000);                      // Si attende 1 secondo sempre BLOCCANDO tutto
}

L'alternativa è:

#include <Arduino.h>
 
unsigned long m_previousMillis = 0; // Intero lungo con i millisecondi dell'istante attuale DOPO l'accensione di Arduino
const long c_delayMillis = 1000;    // Millisecondi di attesa per una azione nel loop()
 
void setup() {
  pinMode(LED_BUILTIN, OUTPUT); // Imposta il pin del LED integrato come uscita  
}
 
void loop() {
  unsigned long currentMillis = millis(); // Millisecondi dell'istante attuale DOPO l'accensione di Arduino
 
  // Controlla se è passato il tempo di attesa
  if (currentMillis - m_previousMillis >= c_delayMillis) {
    m_previousMillis = currentMillis; // Aggiorna l'istante precedente
 
    // Cambia lo stato del LED
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
}

NOTA millis() va in overflow se l'esecuzione persiste dopo l'accensione di Arduino per più di 50 giorni!

Alternative a String

L'uso del tipo String è deprecabile per il consumo e l'utilizzo dannoso che fa della memoria.
"Adafruit": "Nella maggior parte degli utilizzi, molti altri piccoli oggetti String vengono temporaneamente utilizzati forzando la nuova allocazione delle stringhe in una nuova area dell'heap e lasciando un grosso buco dove si trovava quello precedente (frammentazione della memoria)."
Arduino memory heap String 01.gif

Uso SafeString

Materiali:

Tips:

  • SafeString posson esser passate come argomento di funzioni solo per referenza (usando &) altrimenti scatta un errore di compilazione.
  • Non può esser passato un argomento const SafeString & ad una funzione.
  • Una SafeString non può esser passata come riustato di una funzione, al suo posto si userà un argomento per referenza.
  • Acquisire o riversare da\in variabili char[] o code>char* si può fare attraverso l'uso di apposite funzioni: createSafeStringFromCharPtr( ) o cSFP( ) e createSafeStringFromCharPtrWithSize( ) o cSFPS( ).

Usare SafeString in calssi

 

Uso Array di char

Gli array di char contenenti stringhe per essere conformi al C classico devono terminate con il carattere null (ovvero 0 ovvero 0x000).
 

Tecniche di programmazione

Differenziare codice debug da funzionale

La seguente tecnica consente di usare un flag (chiamato DEBUG) allo scopo di inserire o meno nel codice finale pubblicato delle parti che servono solo in fase di sviluppo o troubleshooting e così alleggerire quando andrà in produzione in termini di memoria ed efficienza.
Lo si fa utilizzando dei #define e codice condizionale

#if DEBUG
...
#else
#
#endif
che implica il coninvolgimento del precompilatore C++ il quale prima di compilare effettuerà delle semplicissime sostituzioni in tutto il codice sottoposto.
#define DEBUG 0
 
#if DEBUG
#define prt(x) Serial.print(x);
#define prtn(x) Serial.println(x);
#else 
#define prt(x)
#define prtn(x)
#endif
 
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  prt("Ciaone");
  prtn(" !");
}
 
void loop() {
  // put your main code here, to run repeatedly:
 
}

Il codice di sopra ovviamente se in DEBUG utilizzerà comunque la SerialPrint ma in modo più facile per il programmatore.

Array dinamici

In Arduino, gli array dinamici non esistono nativamente come in C++, ma si posson simulare o, più comunemente e più sicuro per la memoria limitata di Arduino, usando array statici di dimensione massima e gestendo manualmente gli "elementi attivi" con un contatore.
NOTE

  • su un microcontrollore la memoria è limitata (su Arduino Uno R3, la flash RAM 32Kbyte), quindi pensare di usare strutture dinamiche e occuparsi della frammentazione della memoria heap, non ne vale la pena.
  • non c'è modo di usare librerie come std::vector , std::string. Articolo in cui si spiega limitazioni e comunque come installare STL Library che include , la libreria su GitHub è qui.
Metodo più comune e consigliato:
const int MAX_ELEMENTI = 10; // Dimensione massima dell'array
int mioArrayDinamico[MAX_ELEMENTI]; // Array statico
int numeroElementi = 0; // Contatore degli elementi attivi
 
void setup() {
  Serial.begin(9600);
}
 
void loop() {
  // Aggiungi un elemento (se c'è spazio)
  if (numeroElementi < MAX_ELEMENTI) {
    mioArrayDinamico[numeroElementi] = analogRead(A0); // Esempio: legge valore analogico
    numeroElementi++;
    Serial.print("Aggiunto elemento. Totale: ");
    Serial.println(numeroElementi);
  }
 
  // Rimuovi elemento (l'ultimo)
  if (numeroElementi > 0) {
    numeroElementi--;
    Serial.print("Rimosso elemento. Totale: ");
    Serial.println(numeroElementi);
  }
 
  // Leggi tutti gli elementi attivi
  for (int i = 0; i < numeroElementi; i++) {
    Serial.print("Elemento ");
    Serial.print(i);
    Serial.print(": ");
    Serial.println(mioArrayDinamico[i]);
  }
  delay(1000);
}

Esempio "rubato" dalla rete... usa "malloc" e "realloc"

int *mioArray;
int dimensione = 0;
 
void setup() {
  Serial.begin(9600);
  // Alloca per 5 elementi iniziali
  mioArray = (int*)malloc(5 * sizeof(int));
  if (mioArray != NULL) {
    dimensione = 5;
    Serial.println("Allocazione iniziale riuscita.");
  }
}
 
void loop() {
  // Esempio: rialloca per aggiungere altri elementi
  if (dimensione < 10) {
    int *nuovoArray = (int*)realloc(mioArray, 10 * sizeof(int));
    if (nuovoArray != NULL) {
      mioArray = nuovoArray;
      dimensione = 10;
      Serial.println("Reallocazione riuscita.");
    }
  }
  // ... usa mioArray ...
  delay(2000);
}

Cloud

 

Memo

Istruzione/Oggetto Descrizione Esempio
"thingProperties.h" Header da aggiungere negli sketch che si riferiscono al cloud. Le variabili dichiarate nelle Things sono in questo file.
#include "thingProperties.h"
ArduinoCloud Oggetto che ha i metodi per interagire col cloud
// Connect to Arduino IoT Cloud
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
ArduinoCloud.begin Inizializza l'ogetto "ArduinoCloud"

Sketch di default

Non è proprio generico ma è il mio primo, ha per nome che inizia col nome dato alle "Thinks" (ovvero oggetti elettronici o variabili pulliche sul cloud).
/* 
  Sketch generated by the Arduino IoT Cloud Thing "Untitled"
  https://create.arduino.cc/cloud/things/8cb07275-7f5c-414f-86cc-7b7e9c80389a 
 
  Arduino IoT Cloud Variables description
 
  The following variables are automatically generated and updated when changes are made to the Thing
 
  String dispositivoParlante;
  String messaggio;
 
  Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/
 
#include "thingProperties.h"
 
void setup() {
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  delay(1500); 
 
  // Defined in thingProperties.h
  initProperties();
 
  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
 
  /*
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information you’ll get.
     The default is 0 (only errors).
     Maximum is 4
 */
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}
 
void loop() {
  ArduinoCloud.update();
  // Your code here 
 
 
}
 
/*
  Since Messaggio is READ_WRITE variable, onMessaggioChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onMessaggioChange()  {
  // Add your code here to act upon Messaggio change
}
 
/*
  Since DispositivoParlante is READ_WRITE variable, onDispositivoParlanteChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onDispositivoParlanteChange()  {
  // Add your code here to act upon DispositivoParlante change
}

Riferimenti teorici

PUNTATORI

(Testo copia riportato da C++) Innanzitutto definiamo due caratteri usati nei riferimenti a puntatori e variabili puntate:

  • '*', usato per accedere ad un oggetto a cui un puntatore si riferisce. Se anteposto ad un nome di una variabile indica che questa sarà un puntatore ovvero un indirizzo di memoria che conterrà una variabile. Anche chiamato operatore di deferenziazione o indirezione.
  • '&', se anteposto ad una variabile ne restituisce l'indirizzo di memoria (il puntatore) a questa variabile. Chiamato operatore di referenziazione o "address of".
int number = 1234;
int *pointer;       //pointer è una variabile che conterrà un puntatore e sarà di tipo intero
pointer = &number;  //pointer conterrà l'indirizzo di memoria della variabile number
*pointer = 10;      //modifica inserendo 10 nella memoria puntata da pointer ==> number sarà così diventato 10

Altro esempio:

int i=0, j=0;
int *p, *q;
p=&i; //p conterrà l'indirizzo di i
*p=3; //poiché p punta ad i, l'istruzione equivale a scrivere: i=3;
j=*p; //è come scrivere assegna a j il contenuto puntato da p, equivale a j=i;
q=p; //equivale a scrivere q=&i;

Con un puntatore è possibile, in teoria, raggiungere via software ognuna delle celle di memoria esistenti sul computer ospitante (è un tipo di codice non protetto), ma in pratica non è saggio farlo (e con i Sistemi Operativi moderni non è nemmeno possibile), poiché in taluni casi si rischia un crash di sistema.
Tentare di accedere al di fuori dello spazio di indirizzi riservato al programma genera una violazione, che il sistema operativo ci segnala con un messaggio di errore e con l'interruzione forzata del codice che stiamo eseguendo.

Array

char arrCaratteri[15] = "Salve Peppino.";
char *p; //E' un PUNTATORE a variabile di tipo char (volendo anche il 1°elemento di un array di caratteri)
 
//Ora SI ASSEGNA a p l'indirizzo dell'array !!!
p = arrCaratteri; //p punterà alla testa dell'array ovvero al carattere 'S'
 
//DA QUI IN POI scrivere   *p    è come scrivere    arrCaratteri[0]  !!!

NOTARE che:

  • Dopo aver definito e assegnato l'array char arrCaratteri[15], dopo aver definito la variabile puntatore a questo array char *p; affinchè "p" punti a "arrCaratteri" è essenziale: p = arrCaratteri; !!!

Tips / Soluzioni

Inizializzazione Serial Monitor

Generalmente usato per scopi di tracciamento e debug, la sua inizializzazione nella funzione setup() è attuata in parallelo all'esecuzione dello sketch e pertanto affinché funzioni subito con le prime Serial.print() dovrà richiedere una interruzione sinché non sia completamente inizializzato.

void setup() {	
   Serial.begin(9600);		
   unsigned long previousMillis = millis();
   unsigned long currentMillis = millis();
   while (!Serial         //Attende che il Serial Monitor sia pronto
      && (currentMillis - previousMillis <= 10000)) {//timeout 10s Xevitare blocco se SerialMonitor non partisse
         currentMillis = millis();
   }
   Serial.println(F("Serial Monitor inizializzato correttamente...")); 
   //..altre istruzioni qui...
}

Identificare il microcontrollore

Il microcontrollore è identificabile in fase di compilazione utilizzando macro predefinite, ma non è possibile per uno sketch rilevare dinamicamente il nome specifico del modello di scheda (ad esempio, Uno vs. Nano) perché queste informazioni non vengono memorizzate nella scheda.

void setup() {
  Serial.begin(9600);
  Serial.print("Board: ");
 
  #if defined(ARDUINO_AVR_UNO)
    Serial.println("Arduino Uno");
  #elif defined(ARDUINO_AVR_MEGA2560)
    Serial.println("Arduino Mega 2560");
  #elif defined(ARDUINO_AVR_NANO)
    Serial.println("Arduino Nano");
  #elif defined(__AVR_ATmega32U4__)
    Serial.println("Arduino Leonardo/Micro");
  #elif defined(ESP8266)
    Serial.println("ESP8266 board");
  #elif defined(ESP32)
    Serial.println("ESP32 board");
  #else
    Serial.println("Unknown/Other board");
  #endif
}
 
void loop() {
  // Your main code here
}

(Mappa e Link)


Arduino


C++ info fondamentali | C# | Dizionario Elettronica | Visual Studio | Dizionario:Termini tecnici informatici | Dizionario


Parole chiave: