Questa è la versione finale del programma (1.0b), con un #define che permette di usare sia un display OLED 1306 che un LCD 1602 I2C:
// Sketch for Arduino magnetometer
// Measures strength of magnetic field with SS49E hall sensor
// #define LCD /* Display LCD 1602, altrimenti OLED SSD1306 */
// E' adatto anche per display OLED con le 16 righe superiori di colore diverso (p. es. gialle), perché lì scrive nome
// e versione, mentre tutto il resto va nella parte inferiore blu.
const char *percorso=__FILE__; // Dalla macro __FILE__ prende il percorso del file in uso. Questa parte di programma,
char ver[12]; // però, si trova nel file/cartella dell'IDE c_setup, in cui non è scritta la versione
// del programma. La versione è scritta nel file principale, che ha lo stesso nome della
// cartella che contiene tutti i file del programma. Questa riga, quindi, non può stare
// nel setup.
#include <EEPROM.h>
#ifdef LCD
#include <PCF8574_HD44780_I2C.h>
// LCD 16x2 con indirizzo I2C 0x27:
PCF8574_HD44780_I2C lcd(0x27,16,2);
#else // OLED
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h> // v2.5.7 Ok
#include <Adafruit_GFX.h> // v1.0.6 Ok
Adafruit_SSD1306 display(128,64,&Wire,-1);
char txtDC_old[10]=" "; // Testo DC precedente per cancellarlo. E' inizializzata con 9 spazi.
char txtAC_old[10]=" "; // Testo AC precedente per cancellarlo. E' inizializzata con 9 spazi.
#endif
uint32_t sum=0;
uint32_t sumsq=0;
#define nmeas 2000 // stay below 2048 to avoid overflow
uint16_t nmeas_div_2 = nmeas/2;
#define cal_int 0.349*0.582 // calibration constant in mT per ADC count. nominal (5.0/1024)/0.014=0.349:
#define cal_est 0.349*0.582
float cal=cal_int;
#define per_let_bat 10000 // Periodo di lettura della tensione della batteria in ms: 10 secondi.
bool puls_prec=HIGH;
uint32_t t_press=0; // millis() alla pressione del pulsante.
float zero = 511.0; // nominal 511: halfway 0 and 1023
float temp;
bool zero_fatto=LOW;
bool G_mT=LOW; // HIGH=Gauss; LOW=mT.
bool sonda_int_est=LOW; // HIGH=sonda interna; LOW=sonda esterna.
bool sonda_int_est_prec=HIGH; // HIGH=sonda interna; LOW=sonda esterna.
uint32_t t_int_est=0; // Per la verfica della presenza della sonda esterna.
uint16_t vbat; // Tensione della batteria: lettura di A3: 1023=10V.
uint32_t t_bat=per_let_bat; // Per la lettura periodica della tensione della batteria, impostato per una prima lettura immediata.
uint32_t t_interm_bat; // Per l'intermittenza della batteria scarica.
bool corpo_bat=true; // Corpo della batteria visibile/non visibile per l'intermittenza.
void Bip() {tone(5,2000,100);}
void Biip() {tone(5,2000,400);}
void PiRiPo() {uint8_t dur=180; tone(5,2000,dur); delay(dur); tone(5,1688,dur); delay(dur); tone(5,1333,dur);}
// Do-La-Fa secondo la scala pitagorica.
void batteria()
{
analogReference(INTERNAL);
analogRead(A3); delay(10); vbat=analogRead(A3); // Serve un delay di almeno 7ms.
if(vbat>=860) display.fillRect(111,2,3,7,WHITE); // Barretta 4.
else display.fillRect(111,2,3,7,BLACK);
if(vbat>=800) display.fillRect(115,2,3,7,WHITE); // Barretta 3.
else display.fillRect(115,2,3,7,BLACK);
if(vbat>=740) display.fillRect(119,2,3,7,WHITE); // Barretta 2.
else display.fillRect(119,2,3,7,BLACK);
if(vbat>=680) display.fillRect(123,2,3,7,WHITE); // Barretta 1.
else display.fillRect(123,2,3,7,BLACK);
// display.display(); // Non serve qui solo perché lo incontrerà nel loop()!
analogReference(DEFAULT);
analogRead(A3); delay(10);
}
void test_puls()
{
if(digitalRead(6)==HIGH) // Se il pulsante non è premuto:
{
if(puls_prec==LOW && millis()-t_press<1000) // E' stato premuto brevemente:
{
G_mT=!G_mT;
mask_H_Gauss_L_mT();
EEPROM.update(8,G_mT); // Memorizza lo stato G/mT.
}
t_press=millis(); // Prende continuamente il tempo.
puls_prec=HIGH;
zero_fatto=LOW;
}
else // Se è premuto:
{
if(puls_prec==HIGH) // Se è appena stato premuto:
{
Bip();
puls_prec=LOW;
}
}
if(millis()-t_press>=1000 && !zero_fatto) // E' premuto a lungo:
{
zero=(float)sum/nmeas;
zero_fatto=HIGH;
if(sonda_int_est) EEPROM.put(0, zero); // Memorizza lo zero della sonda interna.
else EEPROM.put(4, zero); // Memorizza lo zero della sonda esterna.
Biip();
}
}
void mask_H_Gauss_L_mT()
{
#ifdef LCD
lcd.setCursor(11,0); if(G_mT) lcd.print("G "); else lcd.print("mT");
lcd.setCursor(11,1); if(G_mT) lcd.print("G "); else lcd.print("mT");
#else // OLED
display.setTextSize(1);
display.setCursor(0,29); // DC.
if(G_mT) // DC - G.
{
display.setTextColor(BLACK); display.print("mT");// Cancella.
display.setCursor(0,29);
display.setTextColor(WHITE); display.print('G'); // Scrive.
}
else // DC - mT.
{
display.setTextColor(BLACK); display.print('G'); // Cancella.
display.setCursor(0,29);
display.setTextColor(WHITE); display.print("mT");// Scrive.
}
display.setCursor(0,56); // AC.
if(G_mT) // AC - G.
{
display.setTextColor(BLACK); display.print("mT");// Cancella.
display.setCursor(0,56);
display.setTextColor(WHITE); display.print('G'); // Scrive.
}
else // AC - mT.
{
display.setTextColor(BLACK); display.print('G'); // Cancella.
display.setCursor(0,56);
display.setTextColor(WHITE); display.print("mT");// Scrive.
}
#endif
}
void setup()
{
char *ver_ext=strrchr(percorso,'v'); // Va a cercare l'ultima 'v' nel percorso.
byte n_car=strlen(ver_ext)-4; // Calcola la lunghezza escludendo .ino.
strncpy(ver, ver_ext, n_car); // Copia i primi n_car caratteri da ver_ext a ver.
ver[n_car]='\0'; // Mette il terminatore in fondo.
pinMode(A0,INPUT); // Segnale del sensore Hall interno.
pinMode(A1,INPUT); // Segnale del sensore Hall esterno.
pinMode(5, OUTPUT); // Uscita per cicalino passivo.
pinMode(6,INPUT_PULLUP); // Pulsante a GND G/mT e azzeramento.
pinMode(7,INPUT_PULLUP); // Ingresso del consenso a GND della sonda esterna.
pinMode(8, OUTPUT); // Alimentazione per sonda esterna.
analogReference(DEFAULT);
#ifdef LCD
lcd.init();
lcd.backlight(); // Backlight ON
lcd.setCursor(2,0); lcd.print("MAGNETOMETRO");
lcd.setCursor((16-strlen(ver))/2, 1); lcd.print(ver);
delay(1500);
lcd.clear();
lcd.setCursor(1,0); lcd.print("DC");
lcd.setCursor(1,1); lcd.print("AC");
if(digitalRead(7))
{
EEPROM.get(0, temp); // float: 0,1,2,3. Legge lo zero della sonda interna.
if(temp>-1000 && temp<1000) zero=temp;
}
else
{
EEPROM.get(4, temp); // float: 4,5,6,7. Legge lo zero della sonda esterna.
if(temp>-200 && temp<200) zero=temp;
}
G_mT=EEPROM.read(8); // HIGH=Gauss.
mask_H_Gauss_L_mT();
#else // OLED
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // 0x3C: Indirizzo I2C.
// display.clearDisplay(); // Cancella immediatamente il logo automatico Adafruit :)
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(28,0); display.print("MAGNETOMETRO");
display.setCursor((106-15-6*strlen(ver))/2 +20, 8); display.print(ver);
display.setCursor(0,16); display.print("Press.breve: G/mT RMS");
display.setCursor(80,28); display.print(">>");
display.setCursor(0,40); display.print("Press.lunga: Zero DC");
display.display();
display.startscrollright(3,4); // Dalla terza alla quarta riga, per come le intende la
// libreria, per spostare tutto il simbolo ">>".
delay(2500);
while(!digitalRead(6)); // Premendo il pulsante durante lo scorrimento di ">>", si prolunga
display.stopscroll(); // la visualizzazione delle informazioni iniziali.
display.setTextColor(BLACK);
display.setCursor(80,28); display.print(">>"); // Cancella >> che, non so perché, riappare
display.setTextColor(WHITE); // nella posizione iniziale dello scroll!
for(uint8_t x=0; x<17; x+=4) {display.fillRect(x,16,4,48,BLACK); display.display();}
if(digitalRead(7))
{
display.setCursor(0,0); display.print("Int"); sonda_int_est_prec=LOW;
}
else
{
display.setCursor(0,8); display.print("Est"); sonda_int_est_prec=HIGH;
}
display.setCursor(0,16); display.print("DC");
display.setCursor(0,43); display.print("AC");
if(digitalRead(7))
{
EEPROM.get(0, temp); // float: 0,1,2,3. Legge lo zero della sonda interna.
if(temp>-1000 && temp<1000) zero=temp;
}
else
{
EEPROM.get(4, temp); // float: 4,5,6,7. Legge lo zero della sonda esterna.
if(temp>-200 && temp<200) zero=temp;
}
G_mT=EEPROM.read(8); // HIGH=Gauss.
mask_H_Gauss_L_mT();
for(uint8_t x=17; x<125; x+=4) {display.fillRect(x,16,4,48,BLACK); display.display();}
display.drawRect(106,3,3,5,WHITE); // Contatto +.
display.drawRect(109,0,19,11,WHITE); // Corpo.
display.display();
#endif
}
void loop()
{
if(millis()/1000==0 || millis()-t_bat>per_let_bat)
{
t_bat=millis();
batteria();
}
if(vbat<680)
{
if(millis()-t_interm_bat>500)
{
t_interm_bat=millis();
if(corpo_bat)
{
display.drawRect(109,0,19,11,BLACK); // Cancella il corpo.
display.drawRect(106,3,3,5,BLACK); // Cancella il contatto +.
}
else
{
display.drawRect(109,0,19,11,WHITE); // Disegna il corpo.
display.drawRect(106,3,3,5,WHITE); // Disegna il contatto +.
}
corpo_bat=!corpo_bat;
}
}
if(millis()-t_int_est>=500) // Ogni mezzo secondo controlla se è stata inserita la sonda esterna.
{
t_int_est=millis();
sonda_int_est=digitalRead(7);
if(sonda_int_est!=sonda_int_est_prec)
{
display.setTextSize(1);
if(sonda_int_est) // Sonda interna.
{
digitalWrite(8, HIGH); // Alimenta la sonda interna.
EEPROM.get(0, temp); // float: 0,1,2,3. Legge lo zero della sonda interna.
if(temp>-1000 && temp<1000) zero=temp;
cal=cal_int; // Carica la calibrazione della sonda interna.
display.setCursor(0,8); display.setTextColor(BLACK); display.print("Est"); // Cancella.
display.setCursor(0,0); display.setTextColor(WHITE); display.print("Int"); // Scrive.
display.display();
Bip();
}
else // Sonda esterna.
{
digitalWrite(8, LOW); // Toglie tensione alla sonda interna per risparmiare energia.
EEPROM.get(4, temp); // float: 4,5,6,7. Legge lo zero della sonda esterna.
if(temp>-1000 && temp<1000) zero=temp;
cal=cal_est; // Carica la calibrazione della sonda esterna.
display.setCursor(0,0); display.setTextColor(BLACK); display.print("Int"); // Cancella.
display.setCursor(0,8); display.setTextColor(WHITE); display.print("Est"); // Scrive.
display.display();
PiRiPo();
}
delay(200);
}
sonda_int_est_prec=sonda_int_est;
}
sum=0;
sumsq=0;
for(uint16_t n=0; n<nmeas; n++) // Impiega 224ms (112us a lettura), pari a 11 cicli a 50Hz.
{
uint32_t val;
if(sonda_int_est) val=analogRead(A0); // Sonda interna.
else val=analogRead(A1); // Sonda esterna.
sum+=val;
sumsq+=val*val;
}
test_puls();
// Calculate DC and AC field strength. AC corresponds to RMS of the fluctuations
float B_DC = ((float)sum/nmeas - zero)*cal;
float B_ACsq = ((float)sumsq/nmeas - pow((float)sum/nmeas, 2))*pow(cal, 2);
float B_AC = 0.0;
if (B_ACsq>0.000025) B_AC=pow(B_ACsq, 0.5);
B_AC=pow(B_ACsq, 0.5);
char txt[10]; // Text buffer needed to print formatted float
#ifdef LCD
lcd.setCursor( 3, 0);
#else
display.setTextSize(3);
#endif
if(!G_mT) // DC in mT.
{
dtostrf(B_DC,7,2,txt);
}
else // DC in G.
{
dtostrf(B_DC*10,7,1,txt);
}
#ifdef LCD
lcd.print(txt);
#else // OLED
display.setCursor(0,16);
display.setTextColor(BLACK); display.print(txtDC_old); // Cancella.
display.setCursor(0,16);
display.setTextColor(WHITE); display.print(txt); // Scrive.
#endif
#ifdef LCD
lcd.setCursor( 3, 1);
#else // OLED
strcpy(txtDC_old, txt);
#endif
if(!G_mT) // AC in mT.
{
dtostrf(B_AC,7,2,txt);
}
else // AC in G.
{
dtostrf(B_AC*10,7,1,txt);
}
#ifdef LCD
lcd.print(txt);
#else // OLED
display.setCursor(0,43);
display.setTextColor(BLACK); display.print(txtAC_old); // Cancella.
display.setCursor(0,43);
display.setTextColor(WHITE); display.print(txt); // Scrive.
display.display();
strcpy(txtAC_old, txt);
#endif
}
/*
Fis.I/OPorta
1 - RS
2 0 PD0
3 1 PD1
4 2 PD2
5 3 PD3
6 4 PD4 Consenso sonda esterna (a GND).
--
11 5 PD5 Uscita per cicalino passivo.
12 6 PD6 Pulsante di azzeramento DC (con INPUT_PULLUP) verso GND con 100nF in parallelo.
13 7 PD7
14 8 PB0 Alimentazione per sonda interna.
----------
15 9 PB1
16 10 PB2
17 11 PB3
18 12 PB4
19 13 PB5
--
23 14 PC0 A0 SS49E int.
24 15 PC1 A1 SS49E est.
25 16 PC2 A2
26 17 PC3 A3 Vbat/2
27 18 PC4 A4 SDA | Al display
28 19 PC5 A5 SCL | SSD1306.
29 20 A6 (solo in Arduino Nano)
Fis
9 PB6 XTAL 16MHz In : compensatore a GND (ho messo 22+10pF).
10 PB7 XTAL 16MHz Out: 22pF a GND.
21 ARef
7,20: Vcc
8,22: GND
EEPROM
0-3: float zero_int.
4-7: float zero_est.
8: bool Gauss/mT.
*/
/*
v0.4 16/3/23 Introduco la commutazione milliTesla/Gauss con pressione breve e zero con pressione lunga.
Introduco la memorizzazione Gauss/milliTesla.
v0.5 16/3/23 Divido il programma in schede (file).
v0.6 20/3/23 Introduco degli #ifdef LCD per selezionare un display LCD 1602 oppure un OLED SSD1306.
Aggiungo la scheda (file) a_funzioni.
v0.7 21/3/23 Ho corretto la funzione mask_H_Gauss_L_mT(), dove avevo scambiato i G con i mT.
Ho corretto le scritte sul display OLED.
Metto il controllo del pulsante in una funzione e lo richiamo anche a metà misura: in questo
sto modo, viene controllato ogni 112ms anziché ogni 224ms e risponde anche a brevi pressioni.
v0.8 22/3/23 Sposto tutte le scritte un po' più in basso per farle entrare nell'area blu dei display che ho
acquistato da Amazon, che hanno le prime 16 righe gialle e le successive 48 blu.
Aggiungo nell'area gialla il nome e la versione (con centramento automatico).
Aggiungo nome e versione anche nell'LCD.
v0.9 26/3/23 Introduco l'icona della batteria e la lettura della batteria.
Introduco le due sonde: interna ed esterna, ognuna con il proprio zero.
v0.10 29/3/23 Aggiungo un #if per avere la lettura del pulsante ogni 112ms anche se uso un clock a 8MHz.
v0.11 3/4/23 Riparto dalla v0.9, avendo deciso di andare a 16MHz con il quarzo.
Metto la commutazione Gauss/milliTesla tenendo premuto il pulsante all'accensione. La cancel-
lazione delle scritte sull'OLED, quindi, non è più necessaria.
La pressione breve commuterà AC RMS, min, MAX.
v0.12 3/4/23 Comincio a introdurre le misure RMS, min, MAX.
Scrivo Int e Est sulla stessa riga, mentre prima Int appariva sopra e Est appariva sotto.
Adesso, nella riga gialla inferiore appare un'unica scritta Gauss o mT.
v0.14 3/4/23 Ho completato RMS, min e MAX.
Ho spostato il cicalino su PD5 (pin 11) e modificato il programma per uno attivo a 5V.
Mi sa che min e MAX sono proprio inutili!
v0.15 3/4/23 Ritorno alla v0.9, 16MHz con il quarzo.
Correggo la posizione verticale di G e mT da 55 a 56.
Correggo anche la posizione verticale di AC DA 42 A 43.
v0.16 4/4/23 Aggiungo il Bip breve per la pressione del pulsante, che nella v0.9 era assente.
Sposto il cicalino passivo sull'I/O 5, PD5 (pin 11) per liberare i pin 17, 18 e 19 dell'ICSP.
Sposto l'alim. per la sonda interna sull'I/O 8, PB0 (pin 14) per liberare i pin dell'ICSP.
Aggiungo un delay(200) dopo la commutazione della sonda (ma forse è inutile).
v0.17 5/4/23 Nel setup() metto sonda_int_est_prec uguale all'opposto delvalore in EEPROM per fare in modo
che al controllo nel loop (riga 13) sia diversa dal valore corrente e faccia l'impostazione del-
la sonda interna/esterna.
v0.18 6/4/23 Con la tensione di riferimento DEFAULT, pari alla tensione di alimentazione del 328P (5V nom.),
l'ultima barretta della batteria rimaneva sempre accesa, perché con la batteria a meno di 6V co-
minciava a calare anche la tensione dei 5V! Perciò sono passato alla tensione di riferimento a
1,1V nom. (ho misurato 1,0774V), facendo il partitore con 82k (100k//470k) e 10k.
Poiché, a causa del 100nF su A3, la tensione su A3 impiega qualche istante a salire, a volte
l'indicatore della batteria restava indietro di una barretta e non poteva più risalire, perché
non avevo inserito le righe per la riscrittura delle barrette. Ore le ho inserite con degli else.
Nell'if della temporizzazione della lettura della tensione della batteria ho aggiunto
millis()/1000==0 per avere un aggiornamento continuo finché millis() non arriva a 1000.
v0.19 6/4/23 Ho aggiunto l'intermittenza del corpo della batteria quando tutte le barrette sono spente.
Nella v0.18 non mi ero reso conto che per i sensori mi serve il riferimento a 5V! Perciò, per
leggere la tensione della batteria devo commutare il riferimento da DEFAULT (5V) a INTERNAL
(1,1V) e poi ricommmutare alla fine della funzione. NOTA: Già avevo messo un analogRead() a vuoto
prima di quello effettivo, ma senza almeno 7ms di pausa dal primo NON FUNZIONA! Ho messo 10ms.
v0.20 8/4/23 Ho aggiunto le informazioni iniziali sul pulsante e le animazioni.
v0.21 9/4/23 All'accensione ho anticipato la scrittura di AC/DC e G/mT appena passata la cancellazione, an-
ziché dopo aver cancellato tutto.
v0.22 10/4/23 Aggiungo "RMS" nelle informazioni iniziali dopo "Press.breve: G/mT".
v0.23 12/4/23 Ho tolto all'inizio la nota "Per ATmega328P con clock interno a 8 DIV8=1MHz", poiché lo faccio
girare a 16MHz quarzato.
Per eliminare il logo Adafruit all'accensione, eliminando anche l'ingombro dei dati del logo in
memoria e il rallentamento della programmazione, ho dovuto inserire #define SSD1306_NO_SPLASH
all'interno del file Adafruit_SSD1306.cpp della libreria, perché messo nel programma non funziona.
v0.24 13/4/23 Ho aggiunto Bip() e PiRiPo() all'inserimento e al disinserimento della sonda esterna. Il suono
viene riprodotto anche all'accensione, secondo la sonda attiva.
Ho aggiunto display.display(); nel rilevamento della sonda interna/esterna perché, altrimenti,
le scritte Int/Est venivano aggiornate con un attimo di ritardo, cioè solo quando veniva incon-
trato un display.display();.
v1.0 15/4/23 Ora, premendo il pulsante durante lo scorrimento di ">>", si prolunga la visualizzazione delle
informazioni iniziali.
v1.0a 27/4/23 Per sicurezza, dopo le esperienze con il Fuse Rescue, porto ver a [12].
Faccio una funzione per il rilevamento della pressione breve o lunga del pulsante.
Ora rilevo la pressione breve al rilascio del pulsante, quindi non cambia più per un attimo le
unità di misura, dovendole poi ripristinare.
Tolgo il rilevamento della pressione del pulsante a metà ciclo di calcolo, perché ora, non so
per quale ragione, a volte sbaglia lo zero e lo mette a 513. Forse, in realtà, ci sarebbe da chi-
edersi perché prima funzionasse correttamente...
v1.0b 27/4/23 Metto il Bip immediato alla pressione del pulsante, senza attendere che venga lasciato.
*/
/* ISTRUZIONI
Premendo brevemente il pulsante, si cambia unità di misura da milliTesla a Gauss e viceversa;
tenendolo premuto per un secondo, si azzera la componente continua del campo (DC).
La componente alternata è RMS, detratto il campo magnetico continuo eventualmente presente.
*/