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
(Tips !!!)
(Differenze fondamentali)
 
Line 11: Line 11:
 
* Non c'è modo di usare librerie come <code>'''std::vector'''</code> , <code>'''std::string'''</code> e comunque sono sconsigliati perché usati in un microcontrollore con memoria limitata. Articolo in cui si spega limitazioni e come installare [https://roboticsbackend.com/arduino-stl-library/ STL Library] che include , la libreria su GitHub è [https://github.com/maniacbug/StandardCplusplus qui].
 
* Non c'è modo di usare librerie come <code>'''std::vector'''</code> , <code>'''std::string'''</code> e comunque sono sconsigliati perché usati in un microcontrollore con memoria limitata. Articolo in cui si spega limitazioni e come installare [https://roboticsbackend.com/arduino-stl-library/ STL Library] che include , la libreria su GitHub è [https://github.com/maniacbug/StandardCplusplus qui].
 
* La "[https://www.microchip.com/en-us/tools-resources/develop/microchip-studio/gcc-compilers AVR toolchain]"(strumenti di sviluppo) per Arduino <span style="color: #ff0000;">'''non supporta le librerie C++ standard''', vanno aggiunte manualmente</span>. Il motivo è semplice, da Arduno Uno R3 compreso in giù dispongono di <span style="color: #ff0000;">poca memoria</span> e fisicamente non potrebbero usare queste librerie.
 
* La "[https://www.microchip.com/en-us/tools-resources/develop/microchip-studio/gcc-compilers AVR toolchain]"(strumenti di sviluppo) per Arduino <span style="color: #ff0000;">'''non supporta le librerie C++ standard''', vanno aggiunte manualmente</span>. Il motivo è semplice, da Arduno Uno R3 compreso in giù dispongono di <span style="color: #ff0000;">poca memoria</span> e fisicamente non potrebbero usare queste librerie.
 +
* Rispetto a C#, non si può usare la <code>'''switch''' case</code> su un '''char*''' ovvero su un ''array di caratteri'' o volgarmente una stringa.
  
 
NOTARE
 
NOTARE

Latest revision as of 19:24, 19 January 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" che mette a disposizione tutta una serie di funzioni già pronte che ti nascondono la complessità del funzionamento delle MCU (Microcontrollore).
Test script C++ on-line con aiuto dell'AI: onecompiler.com

Differenze fondamentali

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

  • 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.

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

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
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";

Libreria string

Relativamente al tool PlatformIO, questa libreria afferisce all'header string.h che è incluso di default in ogni sketch.

Funzione Descrizione Esempio
int strcmp(const char *, const char *) returns an integer less than, equal to, or greater than zero if s1 is found, respectively, to be less than, to match, or be greater than s2. A consequence of the ordering used by strcmp() is that if s1 is an initial substring of s2, then s1 is considered to be "less than" s2.
if (strcmp(m_ArrDBVisualObj[i].PicSubType, m_ArrFilterSubTypes[j])) {
    //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.
 
.
 
.
 

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 dare 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.

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 [3]. La parola chiave PROGMEM è un modificatore di variabile che indica al compilatore di "mantenere queste informazioni solo nella memoria flash", invece di copiarle nella SRAM all'avvio, come farebbe normalmente.
L'utilizzo di PROGMEM è 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.

// save some unsigned ints
    const PROGMEM uint16_t charSet[] = { 65000, 32796, 16843, 10, 11234};
 
    // save some chars
    const char signMessage[] PROGMEM = {"I AM PREDATOR,  UNSEEN COMBATANT. CREATED BY THE UNITED STATES DEPART"};
 
    unsigned int displayInt;
    char myChar;
 
 
    void setup() {
      Serial.begin(9600);
      while (!Serial);  // wait for serial port to connect. Needed for native USB
 
      // put your setup code here, to run once:
      // read back a 2-byte int
      for (byte k = 0; k < 5; k++) {
        displayInt = pgm_read_word_near(charSet + k);
        Serial.println(displayInt);
      }
      Serial.println();
 
      // read back a char
      int signMessageLength = strlen_P(signMessage);
      for (byte k = 0; k < signMessageLength; k++) {
        myChar = pgm_read_byte_near(signMessage + k);
        Serial.print(myChar);
      }
 
      Serial.println();
    }
 
    void loop() {
      // put your main code here, to run repeatedly:
    }

Tips

Output x Debug

Serial Monitor

A titolo di esempio con Arduino non funziona il "Console.WriteLn()" di C# ma per stampare dei testi che poi comunque finiranno a "video" si usa il "Serial Monitor" che è una applicazione fruibile dall'IDE di Arduino e che dallo scketc si può usare per inviare del testo, esempio messaggi per debuggare.
Per usarlo occorrerà inserire nella funzione setup() l'inializzazione, attendere che sia disponibile e poi nella funzione loop() si potrà produrre output. A scopo dei esempio ecco come suarlo:
void setup() {
   Serial.begin(9600); // Inizializzazione con 9600 baud
   while (Serial);  // Da Arduino 4 in poi occorre attendere che l'inizializzazione seriale sia davvero operativa
}
 
void loop() {
    Serial.println("Testo con accapo!");
    Serial.print("Solo testo (NON con accapo)!");
}

Leggere da seriale

//??? String stringaLetta = Serial.readStringUntil('\n');
String readString;
 
while(Serial.available()) //all the time there is something to read
{
	//read the input
    delay(50);  //waste some time to allow characters to arrive
    char c=Serial.read(); //read a character
    readString+=c;  //add it to a String
} 	//keep reading until there is nothing more to read
 
if(readString.length()>0){  //if something was actually read and added to the String
    Serial.println(readString); //print the String
	if (readString =="ON"){ //if the String is "ON"
     digitalWrite(led, HIGH); //make the led pin HIGH
    }
 
    readString="";  //reset the value of the String to nothing
 }

Stringhe

Ecco come concatenare un numero ad una stringa:
String displayMsg = "";
int stationCount = 0;
//Concatenazione
dispMsg = "Trovate " + (String)stationCount + " staz. ";
 
Serial.println(dispMsg);

Soluzioni

Alternative ai DELAY()

I delay(milliseconds) sono semplici e funzionali ma INTERROMPONO l'elaborazione che sequenzia il vostro algoritmo pertanto 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*</code> 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 <vector>, 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

Variabili puntatore

(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.


(Mappa e Link)


Arduino


C++ info fondamentali | C# | Dizionario Elettronica | Visual Studio | Dizionario


Parole chiave: