Difference between revisions of "Arduino C++"
From Aino Wiki
(→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
Contents
- 1 Differenze fondamentali
- 2 Tipi fondamentali
- 3 Istruzioni fondamentali
- 4 Funzioni non documentate
- 5 Librerie
- 6 Ottimizzazioni
- 7 Tips
- 8 Cloud
- 9 Riferimenti teorici
- 10 (Mappa e Link)
Differenze fondamentali
Tenendo sempre presente che per Arduino ci riferiamo al compilatore C++ versione 11, ecco alcune differenze che io ho raccolto:
- L'istruzione
printfnon esiste ma si usa laprint - C'è differenza tra C++ (e C++ di Arduino) e C#, per la definizione di
arraysi 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::stringe 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 casesu 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()eloop()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.
|
||||||||||||||||||||||||||||
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 = 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);
|
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)
|
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 scrivereUsando
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!
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"
- 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.
- A questo punto siam pronti ad usare la libreria medinate
#include
#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 Libraryquindi 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....
- libreria pre-installata. Click sulla voce di menu
- 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)."
Uso SafeString
Materiali:
- Libreria su GitHub SafeString
- SafeString tutorial forward.com
- SafeString doc: forward.com.au
- Arduino forum
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( )ocSFP( )ecreateSafeStringFromCharPtrWithSize( )ocSFPS( ).
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)
C++ info fondamentali | C# | Dizionario Elettronica | Visual Studio | Dizionario
Parole chiave:
