Accensione elettronica motore termico

Ciao a tutti! Mi chiamo Marco. Sono nuovo del forum e nel mondo di Arduino. Sono appassionato di motori e elettronica. Sto realizzando (ci sto provando più che altro) una accensione ad anticipo variabile per un motore due tempi. Ho già letto altri post esistenti da cui ho preso spunto per realizzare il mio codice, quindi dovrebbe essere tutta roba già vista.
Un sensore hall dovrà rilevare la posizione del volano (tramite un magnete incollato ad esso) e inviare il segnale ad Arduino, il quale calcolerà l'anticipo (al variare dei giri motore) e, tramite un mosfet, piloterà la bobina alta tensione.
Per ora sto semplicemente accendendo un led quando il magnete passa sul sensore. Allego il codice:

#include <StopWatch.h>

const byte led = 12;    // bobina
const byte pin = 2;     // sensore hall

volatile int hallstate = HIGH;       // stato magnete

StopWatch sw_micros(StopWatch::MICROS);    // contatore periodo

long time = 0;           //periodo 
long rpm = 0;            //numero giri

void setup() {
  Serial.begin(9600);                     
  pinMode(led, OUTPUT);                    //bobina 
  pinMode(pin, INPUT_PULLUP);              //pullup sensore
  attachInterrupt(0, magnete, RISING);     //interrupt passaggio magnete
  sw_micros.start();                       //inizia a contare periodo
}

void loop(){
  if(hallstate == LOW){
    time=sw_micros.elapsed();     //memorizza periodo
    sw_micros.reset();            //azzera timer periodo
    sw_micros.start();            //riavvia il contatore
    hallstate = HIGH;             //reimposta hallstate
    
    scintilla();                  //void scintilla
    Serial.println(time);         //stampa durata periodo
    Serial.print("\t");
    rpm=60000000/time;            //calcolo rpm
    Serial.println(rpm);          //stampo rpm
   }
  
} 

void scintilla()
{
  digitalWrite(led, HIGH);     //accendi bobina
  delayMicroseconds(1000);
  digitalWrite(led, LOW);      //spegni bobina
}

void magnete() 
{
  hallstate = !hallstate ;    //passaggio magnete
}

Il problema è che nel monitor seriale io vedo valori assurdi. Ad un singolo passaggio del magnete compaiono almeno tre letture diverse, delle quali solo una è più o meno precisa. Ho pensato che il problema possa essere il sensore hall (che dovrebbe essere un KY-024) che all'avvicinarsi/allontanarsi del magnete fa delle false letture. Esiste un modo per "pulire" il segnale digitale? Sempre che non sia un errore nel codice.
Grazie mille a tutti.

Ciao, non sono sicurissimo che il problema si questo ma nella definizione dell'interrupt hai messo 0 e non 2 (pin al quale hai collegato il sensore hall)

Dipende da che board usa ... in realtà, con quel parametro, andrebbe proprio passato il numero di interrupt utilizzato e quindi, nel caso di Arduino UNO, il pin 2 è proprio equivalente all'interrupt 0.

Dato che però non sempre la cosa è così semplice, la cosa migliore è affidarsi ad un'apposita funzione che ritorna il numero di interrupt in funzione del piedino ed usare nella attachInterrupt() ovvero fare:

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);

Quindi, se sta usando una UNO, va bene (anche se è il metodo meno raccomandabile), in caso contrario non è detto ...

Guglielmo

fai un esempio, per cortesia?
indicando anche le condizioni di contorno (numero di giri)

comunque propendo anch'io per false letture, magari provare a mettere un gruppo RC all'ingresso...

Grazie per le risposte. La scheda è una uno. Anche modificando il codice come dice Guglielmo il problema persiste.
A circa 900 giri mi appaiono questi valori: (a sinistra il periodo e a destra gli rpm)

66380    903
8536     7029
64692    927
10044    5973
1556     38560
65736    912
7044     8517
66600    900
2388     20080  

Inoltre il periodo mi esce in millisecondi, ma nel codice io sto contando in microsecondi. Forse sbaglio a dichiarare la variabile come long?

Per quanto riguarda il gruppo rc, in teoria non vado a ritardare di alcuni microsecondi la lettura? Dovendo gestire differenze di anticipo anche nell'ordine dei 2/3 gradi ho bisogno della massima precisione.
Il sensore che sto utilizzando ha un trimmer, ho provato a regolarlo ma la situazione non cambia.
Grazie

Ovvio, sei su una UNO, non cambia nulla ... è con le SAMD che è meglio usare quella forma per evitare di sbagliare.

Guglielmo

micro o millis, comunque la variabile deve essere "unsigned long" o "uint32_t" (32 bit non segnati).

    rpm=60000000/time;            //calcolo rpm

hai provato ad aggiungere L dopo 60000000 qui ? Una volta impazzì per una svista del genere.

Inoltre la variabile hallstate come mai è int e non bool ?

8500 e fischia millisecondi?
Sono 8 secondi e mezzo
No, il periodo non ti esce in millisecondi

Inoltre devi considerare anche il tempo di trasmissione...

A 9600 bps sono 1 ms a carattere

Facile che passi più tempo a trasmettere che altro...

Grazie per le risposte! Provvederò ad aggiornare le variabili.

Hai ragione, non sono millisecondi. La lettura è giusta, avevo sbagliato io il calcolo.

Cercando su internet ho scoperto che il sensore che uso fornisce un segnale digitale “penoso”. All’apertura e chiusura genera un’oscillazione (Lo spiego da ignorante, correggetemi se sbaglio) quindi arduino legge centinaia di passaggi. Con un condensatore e una resistenza dovrei risolvere? E nel caso mi sapete indicare che condensatore e resistenza comprare.

non e' che quel sensore fa schifo, è che si tratta di un sensore lineare e quindi non adatto a quello che vuoi farci tu.
su quale piedino lo hai collegato A0 o D0 ?

comunque ti conviene usare un sensore digitale.... ad esempio l' A3144

L’ho collegato a D0. Il venditore lo vendeva come un sensore adatto per il mio scopo. Proverò con un A3144.
Grazie

Per le prove con tutti quei Serial.print credo che ti convenga disattivare l'interrupt in magnete() e riattivarlo dopo l'ultimo Serial.print. In questo modo perderai alcuni impulsi, quindi puoi farlo solo per queste prove.

Aggiornamenti: ho cambiato il sensore con un KY-003, il problema delle false letture è stato risolto. Ho corretto il codice con i vostri consigli:

#include <StopWatch.h>

const byte led = 12;    // bobina
const byte hall = 2;     // sensore hall

volatile bool hallstate = HIGH;       // stato magnete

StopWatch sw_micros(StopWatch::MICROS);    // contatore periodo

uint32_t time = 0;           //periodo 
uint32_t rpm = 0;            //numero giri

void setup() {
  Serial.begin(9600);                     
  pinMode(led, OUTPUT);                                               //bobina 
  pinMode(hall, INPUT_PULLUP);                                        //pullup sensore
  attachInterrupt(digitalPinToInterrupt(hall), magnete, RISING);      //interrupt passaggio magnete
  sw_micros.start();                                                  //inizia a contare periodo
}

void loop(){
  if(hallstate == LOW){
    time=sw_micros.elapsed();     //memorizza periodo
    sw_micros.reset();            //azzera timer periodo
    sw_micros.start();            //riavvia il contatore
    hallstate = HIGH;             //reimposta hallstate
    
    scintilla();                  //void scintilla
    Serial.println(time);         //stampa durata periodo
    Serial.print("\t");
    rpm=60000000/time;            //calcolo rpm
    Serial.println(rpm);          //stampo rpm
   }
  
} 

void scintilla()
{
  digitalWrite(led, HIGH);     //accendi bobina
  delayMicroseconds(1000);
  digitalWrite(led, LOW);      //spegni bobina
}

void magnete() 
{
  hallstate = !hallstate ;    //passaggio magnete
}

Anziché legarti a una libreria, però, con poco sforzo puoi usare micros(), semplificando anche il codice. A volte è più facile scrivere il codice direttamente che usare una libreria! :slight_smile:

uint32_t t0=micros(); // t all'inizio del conteggio

// - loop -
time=micros()-t0; // In realtà non si dovrebbe chiamare time, ma periodo!
t0=micros();

Avevo pensato anch’io all’inizio di usare micros(), ma mi era venuto il dubbio su cosa potesse succedere se il programma superasse i 70 minuti (circa mi pare) di esecuzione, ossia quando micros si azzera. In teoria avrei un valore di t0 estremamente grande e micros che è praticamente zero, quindi facendo micros()-t0 verrebbe un numero negativo. Ma sempre in teoria uint32_t non dovrebbe avere segno, quindi 0-(qualsiasi numero) farebbe sempre zero. Giusto? Il periodo però non può essere zero.

Sbagliato ... leggi questo tutorial (è riferito a millis(), ma è la stessa cosa) :wink:

Guglielmo

micros() è internamente usata dalla libreria, forse per questo ti suggerivano di usare micros() direttamente. Per il discorso degli interi senza segno, cioè uint e per esempio uint8_t.
Bene uint8_t è grande 8-bit e con tutti i bit accesi il valore decimale vale 255 (0xff esadecimale).
Se a 255 ci aggiungi una unità a causa dell'overflow ritorni a 0. L'overflow al contrario lo possiamo chiamare underflow e cioè 0 - 1 = 255. Sperimentali con Serial.println(). Fai attenzione anche alla promozione al tipo int quando si eseguono operazioni su due o più variabili di tipo uint8_t.
Occhio che la promozione è legata alla architettura e quindi ad esempio su esp32 la promozione è ad int32_t. Insomma il C/C++ ti può sorprendere fornendoti risultati non aspettati e quindi sarebbe tra le prime cose da sperimentare.

Ricorda che micros() ha risoluzione di 4us, quindi non otterrai mai 6us. Questo tipo di applicazioni
dovrebbero essere sviluppate facendo uso dei timer hardware e su arduino UNO ne trovi 3 di cui 2 a 8-bit (timer0 e timer2) e 1 a 16-bit (timer1). Purtroppo usare questi timer richiede la manipolazione dei registi interni mappati in memoria ram dall'indirizzo 0 a 256. Il framework di arduino non è adatto per questo tipo di applicazioni, basti solo pensare che per accendere un pin di una porta bastano 62.5n impiegando i registri, mentre con digitalWrite non ricordo con precisione ma è almeno 20 volte più lento. Classifico questa applicazione tra quelle dove il tempo è critico e infatti si chiamano time-critical. Si può arrivare al punto in cui aggiungi una istruzione e le tempistiche non vengono rispettate e l'applicazione fallisce.

Ad esempio la tua funzione scintilla impegna la cpu (impedendo di fatto di eseguire altro codice nel loop) per 1ms. C'è modo di evitare ciò impiegando i timer che possono essere configurati per generare segnali PWM su alcuni pin.

Scusa il post lungo, ma dovevo avvertirti, con ciò non ti sto dicendo di desistere ma considera
i limiti delle funzioni facilitate come digitalWrite, read ecc.

Ciao.

Grazie, proverò a sperimentare con micros().

Prima di iniziare il progetto mi sono informato sulla velocità di lettura/scrittura di Arduino Uno. Ipotizzando che il mio motore riesca a raggiungere 20000rpm (e non li raggiunge, si ferma attorno ai 13/14000) il periodo è di 3ms, quindi per l’esecuzione del programma dovrei ancora rientrarci alla grande. (Mi sbaglio?) Con una risoluzione di 4us avrei (sempre a 20000rpm) una precisione di mezzo grado, che è più che sufficiente. Inoltre se conosco il ritardo tra il passaggio del magnete e lo scoccare della scintilla dovuto all’esecuzione del programma mi basta spostare il sensore hall in modo da compensare il ritardo. Questo lo verificherò sperimentalmente.