Contatore da 1us per periodimetro - Input capture - enum

Grazie agli esempi di Nick Gammon:

// Duty cycle calculation using input capture unit
// Author: Nick Gammon
// Date: 5 November 2013
// Input: Pin D8 

#include "LedControl.h"
#define DIN A0 
#define CS  A1 
#define CLK A2 
LedControl disp = LedControl(DIN,CLK,CS);

volatile boolean first;
volatile boolean triggered;
volatile unsigned long overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;

uint8_t misura=1; // Positivo.
uint32_t t_positivo;
uint32_t t_negativo;
uint32_t t_periodo;

uint8_t puls_prec=1;
uint8_t stato=1;
uint32_t t_intermittenza=0;
bool acc_sp=true;


// timer overflows (every 65536 counts)
  
void setup () 
{
// Serial.begin(115200);
pinMode (8, INPUT); // Ingresso
pinMode (9, INPUT_PULLUP); // Pulsante a Gnd

disp.shutdown (0,false);            // wakeup MAX7219
disp.setIntensity (0,4);            // between 0 and 15
disp.clearDisplay (0);
prepareForInterrupts (); // set up for interrupts
}

void loop () 
{
if(!digitalRead(9)) 
  {
  if (puls_prec==1)
    {
    stato+=1;
    puls_prec=0;
    }
  }
else puls_prec=1;

if(stato>5) stato=1;

if (!triggered) return; // wait till we have a reading
unsigned long elapsedTime = finishTime - startTime; // period is elapsed time
if(misura==1) t_positivo=float(elapsedTime)*62.5e-9*1e6;  // convert to microseconds
else t_periodo=float(elapsedTime)*62.5e-9*1e6;
delay(50);
misura=3-misura;
prepareForInterrupts ();   
// if (misura==1)
//   {
//   Serial.print("positivo:"); Serial.print(t_positivo); Serial.print("us  ");
//   Serial.print("negativo:"); Serial.print(t_periodo-t_positivo); Serial.print("us  ");
//   Serial.print("periodo:"); Serial.print(t_periodo); Serial.print("us  ");
//   Serial.print("duty cycle:"); Serial.print((float)t_positivo/t_periodo*100); Serial.println("%");
//   }
if(misura==1)
  {
  t_negativo=t_periodo-t_positivo;
  switch (stato)
    {
    case 1:  // Positivo
    disp.setRow (0,7,0x62);
    scriviValore(t_positivo);
    break;
    case 2: // Negativo
    disp.setRow  (0,7,0x1C);
    scriviValore(t_negativo);
    break;
    case 3: // Periodo
    disp.setChar  (0,7,'p',false);
    scriviValore(t_periodo);
    break;
    case 4: // Frequenza
    disp.setChar  (0,7,'f',false);    
    scriviValore((uint32_t)(1000000.0/t_periodo+.5)); // .5: approssimazione.
    break;
    case 5: // Duty Cycle
    disp.setChar (0,7,'d',false);
    scriviValore(10000.0*t_positivo/t_periodo);
    }
  }
}

void scriviValore(uint32_t v)
{
uint32_t v_temp=v;
if(stato==4 && v>10000)
  {
  if(v<=40000)
    {
    if(millis()-t_intermittenza>=250) // Intermittenza per f>10kHz.
      {
      t_intermittenza=millis();
      acc_sp=!acc_sp;
      }
    }
  else acc_sp=false; // Per f>40000 spegne i display.
  }
else acc_sp=true;
  
if(stato!=4 || acc_sp)
  {
  for(uint8_t p=0; p<7; p++)
    {
    if(v>=pow(10,p))
      {
      uint8_t x=v_temp%10; v_temp/=10;
      if (stato==5) disp.setChar (0,p,x+48,p==2); // Duty Cycle: mette il punto nella posizione 2.
      else disp.setChar (0,p,x+48,p==3||p==6); // Nelle posizioni 3 e 6, se c'è un numero accende anche il punto decimale.
      }
    else disp.setChar (0,p,' ',false);
    }
  }
else if (stato==4 && v>40000) // Per frequenza maggiori di 40kHz visualizza "   OVER ".
  {
  for(uint8_t p=0; p<7; p++)
    {
         if(p==4) disp.setRow (0,p,0x7E); // O
    else if(p==3) disp.setRow (0,p,0x3E); // V
    else if(p==2) disp.setRow (0,p,0x4F); // E
    else if(p==1) disp.setRow (0,p,0x77); // R
    else disp.setChar (0,p,' ',false);
    }
  }
else
  {
  for(uint8_t p=0; p<7; p++) disp.setChar (0,p,' ',false);
  }
}

ISR (TIMER1_OVF_vect) 
  {
  overflowCount++;
  }  // end of TIMER1_OVF_vect

ISR (TIMER1_CAPT_vect)
  {
  unsigned int timer1CounterValue; // grab counter value before it changes any more
  timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;
  if ((TIFR1&bit(TOV1)) && timer1CounterValue<0x7FFF) overflowCopy++; // if just missed an overflow
  if (triggered) return; // wait until we noticed last one
  if (first)
    {
    startTime = (overflowCopy<<16) + timer1CounterValue;
    TIFR1 |= bit (ICF1);     // clear Timer/Counter1, Input Capture Flag
    TCCR1B =  bit (CS10);    // No prescaling, Input Capture Edge Select (falling on D8)
    first = false;
    return;  
    }
  finishTime = (overflowCopy << 16) + timer1CounterValue;
  triggered = true;
  TIMSK1 = 0;    // no more interrupts for now
  }

void prepareForInterrupts ()
  {
  noInterrupts ();  // protected code
  first = true;
  triggered = false;  // re-arm for next time
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  
  TIFR1 = bit (ICF1) | bit (TOV1);  // clear flags so we don't get a bogus interrupt
  TCNT1 = 0;          // Counter to zero
  overflowCount = 0;  // Therefore no overflows yet
  
  // Timer 1 - counts clock pulses
  TIMSK1 = bit (TOIE1) | bit (ICIE1);   // interrupt on Timer 1 overflow and input capture
  // start Timer 1, no prescaler
  if (misura == 1) TCCR1B =  bit (CS10) | bit (ICES1);  // plus Input Capture Edge Select (rising on D8) - Impulso positivo.
  else TCCR1B =  bit (CS10) | !bit (ICES1); // Periodo totale.
  interrupts ();
  }

Non sono riuscito a usare enum per misura passando il valore a prepareForInterrupts... Come dovrei fare?

Comunque funziona! :slight_smile:

// Periodi da Duty cycle calculation using input capture unit di Nick Gammon 5 Nov 2013
// Input: Pin D8 

#include "LedControl.h"
#define DIN A0 
#define CS  A1 
#define CLK A2 
LedControl disp = LedControl(DIN,CLK,CS);

volatile boolean first;
volatile boolean triggered;
volatile unsigned long overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;

uint8_t misura=1; // Alto.
float t_alto;
float t_basso;
float t_periodo;

uint8_t puls_prec=1;
uint8_t stato=1;
uint32_t t_intermittenza=0; // Prende il tempo per l'intermittenza.
#define MS_INTERMITT 500 // Tempo (ms) di intermittenza (velocità).
bool acc_sp=true;
  
void setup () 
{
// Serial.begin(115200);
pinMode (8, INPUT); // Ingresso
pinMode (9, INPUT_PULLUP); // Pulsante a Gnd con 100nF in parallelo.

disp.shutdown (0,false); // Wakeup MAX7219
disp.setIntensity (0,4); // Da 0 a 15
disp.clearDisplay (0);
prepareForInterrupts (); // set up for interrupts
}

void loop () 
{
if(!digitalRead(9)) 
  {
  if (puls_prec==1)
    {
    stato+=1;
    puls_prec=0;
    }
  }
else puls_prec=1;

if(stato>5) stato=1;

if (!triggered) return; // Wait till we have a reading.
unsigned long elapsedTime = finishTime - startTime; // Period is elapsed time.
if(misura==1) t_alto=float(elapsedTime)/16.0; // *62.5e-9*1e6  // Convert to microseconds.
else t_periodo=float(elapsedTime)/16.0; // *62.5e-9*1e6
delay(100);
misura=3-misura;
prepareForInterrupts ();   

if(misura==1)
  {
  t_basso=t_periodo-t_alto;
  switch (stato)
    {
    case 1:  // Alto
    disp.setRow (0,7,0x62);
    scriviValore(t_alto);
    break;
    case 2: // Basso
    disp.setRow  (0,7,0x1C);
    scriviValore(t_basso);
    break;
    case 3: // Periodo
    disp.setChar  (0,7,'p',false);
    scriviValore(t_periodo);
    break;
    case 4: // Frequenza
    disp.setChar  (0,7,'f',false);    
    scriviValore((uint32_t)(1000000.0/t_periodo+.5)); // .5: approssimazione.
    break;
    case 5: // Duty Cycle
    disp.setChar (0,7,'d',false);
    scriviValore(10000.0*t_alto/t_periodo);
    }
  // Serial.print("alto:"); Serial.print(t_alto); Serial.print("us  ");
  // Serial.print("basso:"); Serial.print(t_basso); Serial.print("us  ");
  // Serial.print("periodo:"); Serial.print(t_periodo); Serial.print("us  ");
  // Serial.print("duty cycle:"); Serial.print((float)t_alto/t_periodo*100); Serial.println("%");
  }
}

void scriviValore(uint32_t v)
{
uint32_t v_temp=v;
if(stato==4 && v>60000) // Frequenza maggiore di 60kHz.
  {
  if(v<=100000) // <=100kHz.
    {
    if(millis()-t_intermittenza>=MS_INTERMITT) // Intermittenza.
      {
      t_intermittenza+=MS_INTERMITT;
      acc_sp=!acc_sp;
      }
    }
  else acc_sp=false; // Per f>100000 spegne i display.
  }
else acc_sp=true;
  
if(stato!=4 || acc_sp)
  {
  for(uint8_t p=0; p<7; p++)
    {
    if(v>=pow(10,p))
      {
      uint8_t x=v_temp%10; v_temp/=10;
      if (stato==5) disp.setChar (0,p,x+48,p==2); // Duty Cycle: mette il punto nella posizione 2.
      else disp.setChar (0,p,x+48,p==3||p==6); // Nelle posizioni 3 e 6, se c'è un numero accende anche il punto decimale.
      }
    else disp.setChar (0,p,' ',false); // Cancella gli zeri non significativi.
    }
  }
else if (stato==4 && v>100000) // Per frequenza maggiori di 100kHz visualizza "   OVER ".
  {
  for(uint8_t p=0; p<7; p++)
    {
         if(p==4) disp.setRow (0,p,0x7E); // O
    else if(p==3) disp.setRow (0,p,0x3E); // V
    else if(p==2) disp.setRow (0,p,0x4F); // E
    else if(p==1) disp.setRow (0,p,0x77); // R
    else disp.setChar (0,p,' ',false);
    }
  }
else
  {
  for(uint8_t p=0; p<7; p++) disp.setChar (0,p,' ',false);
  }
}

ISR (TIMER1_OVF_vect) 
  {
  overflowCount++; // timer overflows every 65536 counts
  }

ISR (TIMER1_CAPT_vect)
  {
  unsigned int timer1CounterValue; // Grab counter value before it changes any more
  timer1CounterValue = ICR1;  // See datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;
  if ((TIFR1&bit(TOV1)) && timer1CounterValue<0x7FFF) overflowCopy++; // If just missed an overflow
  if (triggered) return; // Wait until we noticed last one
  if (first)
    {
    startTime = (overflowCopy<<16) + timer1CounterValue;
    TIFR1 |= bit (ICF1);     // Clear Timer/Counter1, Input Capture Flag
    TCCR1B =  bit (CS10);    // No prescaling, Input Capture Edge Select (falling on D8)
    first = false;
    return;  
    }
  finishTime = (overflowCopy << 16) + timer1CounterValue;
  triggered = true;
  TIMSK1 = 0;    // No more interrupts for now
  }

void prepareForInterrupts ()
  {
  noInterrupts ();  // Protected code
  first = true;
  triggered = false;  // Re-arm for next time
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  
  TIFR1 = bit (ICF1) | bit (TOV1);  // Clear flags so we don't get a bogus interrupt
  TCNT1 = 0;          // Counter to zero
  overflowCount = 0;  // Therefore no overflows yet
  
  // Timer 1 - Counts clock pulses
  TIMSK1 = bit (TOIE1) | bit (ICIE1);   // Interrupt on Timer 1 overflow and input capture
  // start Timer 1, no prescaler
  if (misura == 1) TCCR1B =  bit (CS10) | bit (ICES1);  // Plus Input Capture Edge Select (rising on D8) - Impulso positivo.
  else TCCR1B =  bit (CS10) | !bit (ICES1); // Periodo totale.
  interrupts ();
  }

Per migliorare la stabilità della lettura, soprattutto quando sono pochi microsecondi, potrei bloccare l'interrupt di millis() (usando micros() dove attualmente uso millis()) o altro?

millis() e micro() usano entrambi il Timer0 ... solo che millis() ritorna il valore di un contatore che è incremetato da una ISR (TIM0_OVF_vect) ogni millisecondo (circa), mentre micros() ritotna un valore che è calcolato sfruttando sempre timer0_overflow_count ma tenendo conto anche di altre cose ...

Questa è la ISR che incrementa i contatori:

#if defined(TIM0_OVF_vect)
ISR(TIM0_OVF_vect)
#else
ISR(TIMER0_OVF_vect)
#endif
{
	// copy these to local variables so they can be stored in registers
	// (volatile variables must be read from memory on every access)
	unsigned long m = timer0_millis;
	unsigned char f = timer0_fract;

	m += MILLIS_INC;
	f += FRACT_INC;
	if (f >= FRACT_MAX) {
		f -= FRACT_MAX;
		m += 1;
	}

	timer0_fract = f;
	timer0_millis = m;
	timer0_overflow_count++;
}

Questa è la funzione millis():

unsigned long millis()
{
	unsigned long m;
	uint8_t oldSREG = SREG;

	// disable interrupts while we read timer0_millis or we might get an
	// inconsistent value (e.g. in the middle of a write to timer0_millis)
	cli();
	m = timer0_millis;
	SREG = oldSREG;

	return m;
}

... e infine questo è quello che fa la micros()

unsigned long micros() {
	unsigned long m;
	uint8_t oldSREG = SREG, t;
	
	cli();
	m = timer0_overflow_count;
#if defined(TCNT0)
	t = TCNT0;
#elif defined(TCNT0L)
	t = TCNT0L;
#else
	#error TIMER 0 not defined
#endif

#ifdef TIFR0
	if ((TIFR0 & _BV(TOV0)) && (t < 255))
		m++;
#else
	if ((TIFR & _BV(TOV0)) && (t < 255))
		m++;
#endif

	SREG = oldSREG;
	
	return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}

Come vedi, entrambe le funzioni, come PRIMA cosa fanno una cli(), ovvero disabilitano gli interrupts :wink:

Guglielmo

P.S.: Ah, tieni conto di questa cosa:

// the prescaler is set so that timer0 ticks every 64 clock cycles, and the
// the overflow handler is called every 256 ticks.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))

// the whole number of milliseconds per timer0 overflow
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)

// the fractional number of milliseconds per timer0 overflow. we shift right
// by three to fit these numbers into a byte. (for the clock speeds we care
// about - 8 and 16 MHz - this doesn't lose precision.)
#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
#define FRACT_MAX (1000 >> 3)

Uhmm... Tenerne conto nel senso che l'overflow saltuariamente provoca irregolarità?

Nel senso che comunque il millisecondo è ... circa un millisecondo :wink:

16 MHz / (64 x 256) = 976.5625 µsec. che poi vengono corretti con artifici vari.

Se cerchi ci sono, negli anni passati, precedenti discussioni sulla cosa ...

Guglielmo

Sì, lo so, ma la precisione di millis() non mi interessa. Il conteggio dei tempi viene fatto con i timer. Vorrei solo evitare interferenze da parte di funzioni che non uso o che potrei realizzare in qualche altro modo.

... e cosa cambia scusa? Se il timer ha il clock a 16MHz, ecc. ecc. sempre un certo errore c'è :wink:

Guglielmo

Vengono contati i cicli di clock. Queste sono alcune righe estratte qua e là dal programma:

if (first)
  {
  startTime = (overflowCopy << 16) + timer1CounterValue;
  ...
  }
finishTime = (overflowCopy << 16) + timer1CounterValue;
unsigned long elapsedTime = finishTime - startTime; // Period is elapsed time.
t_alto = float (elapsedTime) *62.5e-9 *1e6;  // Convert to microseconds.
// che ho trasformato in : float (elapsedTime) /16;

Ah, già, dimenticavo, tu non stai usando Timer0 ... :grin:

Guglielmo

Alla fine ho scoperto che posso fare la misura di Ton e Toff con il mio Racal-Dana 1991, con i due ingressi A e B in parallelo (COM A), impostando A sul fronte di salita e B sul fronte di discesa per misurare Ton; A sul fonte di discesa e B sul fronte di salita per misurare Toff! :slight_smile: