Conversione DelayMicroseconds() in Micros() con AttachInterrupts()

Salve a tutti,
sono nuovo nel forum e finalmente ci scrivo, in quanto ho un problema a cui non riesco a venirne a capo.
Sto realizzando un circuito per dimmerare una o più lampade 220V utilizzando Arduino. Questo è il programma:


void setup() {
attachInterrupt(0, ZeroCrossing, CHANGE);
pinMode(10, OUTPUT);
Serial.begin(9600);
}

void loop() {

}

void ZeroCrossing() {

delayMicroseconds(4000); //1000/1500 - 4500 240V - 15V AC
tone(10, 500, 1);
}

Come si può vedere è abbastanza semplice, ed utilizza un circuito ZeroCrossDetector collegato al PinDigitale 2 per rintracciare quando l'onda sinusoidale attraversa lo zero e mantenere la lampada accesa per un tempo t, in modo da regolarne l'intensità.
Il problema è che il delayMicroseconds blocca il programma, non permettendomi quindi di controllare più di una lampada. Come sfrutto il Micros() sapendo che la funzione ZeroCrossing() viene richiamata dalla funzione attachInterrupt() all'interno del Setup()?

Ti invitiamo a presentarti (dicci quali conoscenze hai di elettronica e di programmazione) qui: Presentazioni
e a leggere il regolamento se non lo hai già fatto: Regolamento
Qui una serie di link utili, non inerenti al tuo problema:

Il codice devi racchiuderlo nei tag code, vedi sezione 7 del regolamento, spiega bene come fare ( pulsante </> ).
Altrimenti parte del codice può essere visualizzata male o mancare perchè interpretato come attributo del testo stesso.

Presentazione effettuata :slight_smile:
Correzione del codice, scusate l'errore.

void setup() {
  attachInterrupt(0, ZeroCrossing, CHANGE); 
  pinMode(10, OUTPUT);    
  Serial.begin(9600);  
}

void loop() {

}

void ZeroCrossing() {
  
  delayMicroseconds(4000);   //1000/1500 - 4500 240V - 15V AC
        tone(10, 500, 1);
}

Credo che il concetto sia lo stesso di chi al posto di delay() usa la millis() per cui ... ti conisglio le stesse letture che probabilmente potranno chiarirti un po le cose ...

... prova a legge prima QUI, poi QUI ed infine leggi anche QUI e QUI.

Ripeto, li si parla della millis(), ma i concetti ed il modo d'uso è lo stesso per la micros()

Guglielmo

Buonasera e grazie per la risposta,
avevo già visto quei link e molti altri qui sul forum, ho anche utilizzato già questo metodo e funziona perfettamente, nel seguente caso, invece, non fa il suo lavoro e credo che il problema sia legato alla funzione Setup() e Interrupt() ma potrei anche sbagliarmi.

Aspetto ulteriore risposta.

Io partirei da un approccio diverso ...
... su queste piccole e lente MCU la ISR deve durare il minor tempo possibile. Quindi, meno si fa e prima si esce e meglio è.

Premesso questo, quello che quindi farei, è utilizzare la ISR solo per settare una flag o più flag e poi valutare la/le flag all'interno del ciclo loop() con le tecniche gia descritte.

Guglielmo

Se ho ben capito ciò che mi hai suggerito, il codice dovrebbe trasformarsi in questo modo. Solo che in questo modo non funziona.

boolean Z=0;
unsigned long preMicros = 0;  
const long tempo = 2000;

void setup() {
  attachInterrupt(0, ZeroCrossing, CHANGE); 
  pinMode(10, OUTPUT);      
}

void loop() {
  while(Z==1){
      unsigned long count = micros();
      if(count - preMicros >= tempo) {
        preMicros = count;
        tone(10, 500, 1);
}}}

void ZeroCrossing() {
  Z=1;
  
  //delayMicroseconds(4000);   //1000/1500 - 4500 240V - 15V AC
   //     tone(10, 500, 1);
}

La loop() alla fin fine è già un while, quindi usa if(Z==1) e non while

Avevo già provato ma non cambia nulla, non riesco a farla funzionare nel loop().

Allora ...

  1. evita la CLASSE boolean e usa semplicemente un byte, molto più semplice e leggero

  2. le variabili che si toccano nelle ISR DEVONO essere dichiarate volatili

volatile byte Z = 0;

  1. il loop puoi scriverlo così :
void loop() {
 if ( (Z == 1) && (micros() - preMicros >= tempo) ) { 
   preMicros = micros();
   ...
   ...
 }
}
  1. ovviamente, da qualche parte, devi rimettere a 0 la flag Z ... vedi tu quando in base alle tue esigenze.

Guglielmo

Innanzitutto, grazie per le delucidazioni, ho usato poche volte l'ISR ed alcune cose mi sfuggono.
Comunque, ho modificato il codice come hai descritto, ma purtroppo la lampada rimane, anche modificando il tempo, alla massima intensità.

volatile byte Z = 0;
unsigned long preMicros = 0;  
const long tempo = 4000;

void setup() {
  attachInterrupt(0, ZeroCrossing, CHANGE); 
  pinMode(10, OUTPUT); 
}

void loop() {
  if((Z == 1) && (micros() - preMicros >= tempo)) { 
     preMicros = micros();
        tone(10, 500, 1);
        Z=0;
}}

void ZeroCrossing() {
  Z=1;
}

marci_oo:
Comunque, ho modificato il codice come hai descritto, ma purtroppo la lampada rimane, anche modificando il tempo, alla massima intensità.

Su questo non so che dirti ...
... bisognerebbe esaminare tutto lo schema, vedere con che logica la piloti, verificare il tutto, ecc. ecc.

Quello che posso dirti è che il codice ora sembra corretto e che potresti collegare un oscilloscopio sul pin 2 e sul pin 10 per verificare che faccia quello che credi debba fare.

Guglielmo

Lo schema circuitale è quello in allegato, costituito da un circuito ZeroCrossing ed il circuito per il pilotaggio della lampada. Quindi, con il programma che ho postato all’inizio l’ISR va a controllare quando l’ingresso va da 0 a 1, e quando questa condizione succede richiama la funzione ZeroCrossing che scegliendo il tempo prima che la lampadina si accenda mi permette di regolarne l’intensità, prima che l’onda sinusoidale ripassi attraverso lo 0. Tutto ciò in quel modo è molto semplice, ma mi ferma il programma non permettendomi quindi di controllare più di una lampada.

Credo, ma potrei sbagliarmi, che tutte le operazioni che facciamo con il nuovo programma aggiungano oltre che il ritardo stabilito da , anche un ritardo dovuto ai vari passaggi e quindi, mi trovo arrivare a con un ritardo maggiore di quello prefissato, noto quindi che se imposto

if((Z == 1) && (micros() - preMicros <= tempo))

la lampada si accende solo quando è uguale a 8 ms e quando riporto sulla seriale preMicros, il risultato è superiore a 8 ms.
Avete consigli su come posso ridurre in modo significativo i passaggi e quindi il tempo utilizzato, magari lavorando sui registri? Che tra l’altro non uso da tempo. :’(

Per questo tipo di applicazione il debug si fa con l'oscilloscopio, ma purtroppo non è uno strumento a buon mercato.

Avete consigli su come posso ridurre in modo significativo i passaggi e quindi il tempo utilizzato, magari lavorando sui registri? Che tra l'altro non uso da tempo. :cry:

Con i registri si può fare di meglio, però occorre usare un timer in CTC modo per contare in us.
Modificare OCR0A che viene comparato con TCNT0, quando c'è corrispondenza scatta un interrupt e li dentro attivi l'uscita OC0A con togle che genera un onda quadra con cui piloti il triac e risolvi il problema anche con carichi induttivi. Al passaggio per lo zero disattivi l'uscita OC0A ecc.

PS: non usare funzioni per restituire il valore di un timer ma anche in generale meglio non chiamare funzioni nei momenti critici.

Ciao.

Riesci a fornirmi qualche esempio di partenza? Ho cercato un blink che utilizzi questo metodo, ma ha problemi durante la compilazione.

Riesci a fornirmi qualche esempio di partenza? Ho cercato un blink che utilizzi questo metodo, ma ha problemi durante la compilazione.

PS: sono anni che non scrivo codice con arduino IDE.
Purtroppo no, perché il codice è per AVR (niente IDE, core), però hai parecchi spunti.
Il 328 ha 3 timer, due a 8 bit e uno a 16 bit, usa quello non usato per millis o se possibile non usare proprio arduino e il core o comunque se c'è modo disabilita tutto.

Tutti i timer possono lavorare in CTC mode. Ci sono due registri di comparazione, OCRnA e OCRnB per ogni timer. Ad n sostituisci il numero del timer, cioè OCR0A per il timer 0, OCR1A per il timer 1 e così via.

In CTC la logica del timer compara continuamente il valore del contatore TCNn con entrambe i registri OCRn (A/B), se TCNn == OCRn (A/B) accadranno delle cose in base a come hai configurato i regsistri.

In base a come configuri può accadere:
Il pin pwm (OC0A o OC0B ecc) cambia stato (toggle)
Il pin pwm viene settato (set)
Il pin viene pulito (clear)

Tutto questo e molto altro lo trovi nel datasheet, se non capisci qualcosa se hai un dubbio posta e spera che ci sia qualcuno che ha il tempo/voglia di argomentare.

PS: Con carico induttivo il triac può triggerarsi da solo oppure spegnersi da solo, per evitare lo spegnimento si usa al posto del semplice impulso si invia un treno d'onda che dura fino al passaggio dello zero o pochi us prima.

Ciao.