Determine la frecuencia de entrada de onda cuadrada con ICR en Atmega328p

1

Estoy tratando de obtener la frecuencia de entrada de una onda cuadrada usando el registro de captura de entrada de un Atmega328p. Hasta ahora, funciona de forma esporádica, es decir, cuando ingreso una onda cuadrada de 75 kHz, la salida se ve así:

244 244 75117 74766 75117 75117 79207 80402 82051 82901 84656 85561 87431 244 244 244 88888 90395 244 244 244 -941176 -271186 244 -246153 244 244 244

¿Alguien sabe por qué este podría ser el caso? He intentado meterme con los tipos de datos, pero de lo contrario no estoy realmente seguro de cuál podría ser el problema. El código que he escrito está abajo.

// # of overflows
volatile long T1Ovs;

// timestamp variables (store TCNT at time of input capture interrupt)
volatile long Capt1, Capt2;

// capture flag
volatile uint8_t Flag;

volatile long ticks;
volatile double period;
volatile long frequency;

void initTimer1(void)
{
  TCNT1 = 0;
  // initialize timer to 0

  //timer/counter1 control register b
  TCCR1B |= (1<<ICES1);
  // input capture edge select; rising edge triggers capture

  //timer/counter1 interrupt mask register
  TIMSK1 |= (1<<ICIE1);
  // ICIE1: input capture interrupt enable

  TIMSK1 |= (1<<TOIE1);
  // timer/counter1 overflow interrupt enable
}

void startTimer1(void)
{
  TCCR1B = (1<<CS10);
  //start timer with pre-scaler = 1

  sei();
  //enable global interrupts

}

ISR(TIMER1_CAPT_vect) // interrupt handler on input capture match (rising edge in this case)
{
  if (Flag == 0)
  {
    Capt1 = ICR1;
    // save timestamp at interrupt (input capture is updated with the counter (TCNT1)
    // value each time an event occurs on the ICP1 pin (digital pin 8, PINB0)

    T1Ovs = 0;
    // reset overflows
  }

  if (Flag ==1)
  {
    Capt2 = ICR1;
  }

  Flag ++;
}

ISR(TIMER1_OVF_vect) // interrupt handled on timer1 overflow
{
  T1Ovs++; // increment number of overflows
}


void setup()
{
  Serial.begin(9600);


  initTimer1();
  startTimer1();
}

void loop()
{
  if (Flag == 2)
  {
    ticks = Capt2 - Capt1 + T1Ovs * 0x10000L;
    // (second timestamp) - (first stamp) + (# of overflows) * (ticks/overflow = 65535)

    frequency = 16000000/ticks;
    // ticks * seconds/ticks = seconds
    // 1/seconds = Hz

    Flag = 0;
    // reset flags

    T1Ovs = 0;
    // reset overflow count

    TIFR1 = 0b00000000; // clear interrupt registers

    Serial.println(frequency);


    TIMSK1 |= (1 << ICIE1); // enable capture interrupt
    TIMSK1 |= (1 << TOIE1); // enable overflow interrupt

  }
}

Gracias de antemano!

ACTUALIZACIÓN ***********************************

La segunda iteración del código, que usa tipos enumerados para hacer una máquina de estado:

  typedef enum {
      CAPTURE_1,
      CAPTURE_2,
      WAIT
  } timer_state_t;

  timer_state_t flag = WAIT;
   volatile long Capt1, Capt2;

  volatile long T1Ovs;

  void InitTimer1(void)
  {
     //Set Initial Timer value
     TCNT1=0;
     //First capture on rising edge
     TCCR1B|=(1<<ICES1);
     //Enable input capture and overflow interrupts
     TIMSK1|=(1<<ICIE1)|(1<<TOIE1);
  }
  void StartTimer1(void)
  {
  //Start timer without prescaler
  TCCR1B|=(1<<CS10);
  //Enable global interrutps
     sei();
  }

  ISR(TIMER1_CAPT_vect) {
   switch(flag) {
   case CAPTURE_1:
       Capt1 = ICR1;

       flag = CAPTURE_2;
       break;
   case CAPTURE_2:
       Capt2 = ICR1;

       flag = WAIT;
                    Serial.println(flag);

       break;
      }



  }

  ISR(TIMER1_OVF_vect)
  {
    T1Ovs++;
  }

  void setup()
  {
    Serial.begin(9600);

    InitTimer1();
    StartTimer1();
  }

  void loop() {
    flag = CAPTURE_1;

    while (flag != WAIT);
     Serial.println("loop");

    Serial.println(Capt2 - Capt1 + T1Ovs * 0x10000);
  }
    
pregunta user3753934

2 respuestas

1

Aquí se actualiza su código para que funcione, con comentarios que comienzan con " J: " y explican los cambios ...

  typedef enum {
      CAPTURE_1,
      CAPTURE_2,
      WAIT
  } timer_state_t;

  volatile timer_state_t flag = WAIT;

  // J:This is a 16-bit timer, so these values will always fit into an unsigned int
  volatile unsigned int Capt1, Capt2, CaptOvr;

  // J:Mind as well make this unsigned and give it 2x range since it can never be negative. 
  volatile unsigned long T1Ovs;

  void InitTimer1(void)
  {
     //Set Initial Timer value
     // J:All measurements against TCNT are relative, so no need to reset
     // TCNT1=0;

     // J: Note we need to set up all the timer control bits because we do not know what state they are in
     // J: If, for example, the WGM bits are set to a PWM mode then the TCNT is going to be resetting out from under us rather than monotonically counting up to MAX

     TCCR1A = 0x00;

     //First capture on rising edge
     TCCR1B =(1<<ICES1);
     //Enable input capture and overflow interrupts
     TIMSK1|=(1<<ICIE1)|(1<<TOIE1);
  }

 // J: Note that it would be ok to start the timer when we assign TCCR1B in InitTimer since nothing will happen when the ISR is called until we set flag to CAPTURE1

  void StartTimer1(void)
  {
  //Start timer without prescaler

  // J: Note that we know that the other CS bits are 0 becuase of the Assignment in InitTimer
  TCCR1B |= (1<<CS10);  

  //Enable global interrutps
  // J: Interrupts are turned on by  Arduino platform startup code
  //  sei();
  }

  ISR(TIMER1_CAPT_vect) {

   switch(flag) {
   case CAPTURE_1:
       Capt1 = ICR1;

       // J: Reset the overflow to 0 each time we start a measurement
       T1Ovs=0;
       doubleOverflowError=0;
       flag = CAPTURE_2;
       break;

   case CAPTURE_2:
       Capt2 = ICR1;

       // J: Grab a snap shot of the overflow count since the timer will keep counting (and overflowing);
       CaptOvr = T1Ovs;    
       flag = WAIT;

       //J: Generally bad to print in ISRs
       //Serial.println(flag);

       break;
      }
  }


  ISR(TIMER1_OVF_vect)
  {
    T1Ovs++;

    // J: Just to be correct, check for overflow of the overflow, otherwise if it overflows we would get an incorrect answer.
    if (!T1Ovs) {
      doubleOverflowError=1;
    }
  }

  void setup()
  {

    Serial.begin(9600);

    InitTimer1();
    StartTimer1();
  }

  void loop() {
    // J: No need to bracket this set with cli() becuase the counter will not be counting until wait is updated

    flag = CAPTURE_1;

    while (flag != WAIT);


    // J: Parenthesis and explicit cast for good luck! ( and to ensure correct size and order for operations) 


     if (doubleOverflowError) {
         Serial.println( "Double Overflow Error! Use a bigger prescaller!");
     } else {
         Serial.println( ( (unsigned long) (Capt2) + (CaptOvr * 0x10000UL) )-Capt1 );
     } 
}
    
respondido por el bigjosh
0

En el ISR, haces un par de verificaciones que dependen del valor de flag , y luego lo incrementas incondicionalmente.

En su caso normal, lo que sucede es esto:

  1. La rutina principal establece la marca en 0
  2. ISR se ejecuta, ve que el indicador es 0, almacena el valor en Capt1, incrementa el indicador
  3. ISR se ejecuta de nuevo, ve que el indicador es 1, almacena el valor en Capt2, incrementa el indicador
  4. La rutina principal ve que el indicador es 2, genera un resultado, restablece todo

Sin embargo, es completamente posible que el ISR se ejecute dos veces antes de que su rutina principal pueda verificar el valor de la bandera. En cuyo caso, lo que sucede es esto:

  1. La rutina principal establece la marca en 0
  2. ISR se ejecuta, ve que el indicador es 0, almacena el valor en Capt1, incrementa el indicador
  3. ISR se ejecuta de nuevo, ve que el indicador es 1, almacena el valor en Capt2, incrementa el indicador
  4. ISR se ejecuta de nuevo, incrementa la marca a 3
  5. La rutina principal se ejecuta, la bandera no es 2, no hace nada
  6. Repita los pasos 4 y 5 de forma independiente hasta que la bandera vuelva a 2 a tiempo para que el principal la atrape.

Mientras tanto, su contador de desbordamiento ha estado aumentando y subiendo, por lo que el intervalo correcto entre capturas se agrega a un múltiplo de 255 indeterminado del intervalo del recuento de desbordamiento.

La forma más fácil de solucionar esto (y hacer que el código sea más claro al mismo tiempo) es marcar una enumeración y hacer que pase por tres estados: captura 1, captura 2, espera, en una máquina de estados que se reinicie principal. Aquí hay un ejemplo simplificado:

typedef enum {
    CAPTURE_1,
    CAPTURE_2,
    IDLE
} timer_state_t

timer_state_t flag = IDLE;
volatile long Capt1, Capt2;

ISR(TIMER1_CAPT_vect) {
    switch(flag) {
    case CAPTURE_1:
        Capt1 = ICR1;
        flag = CAPTURE_2;
        break;
    case CAPTURE_2:
        Capt2 = ICR1;
        flag = IDLE;
        break;
    }
}

void main() {
    flag = CAPTURE_1;
    while(flag != IDLE); // Wait until we're done
    // Do something with Capt2 and Capt1
}

Otra alternativa es simplemente almacenar un recuento único (y un valor de desbordamiento, si es necesario), y cada vez que la captura ISR se active, calcule un nuevo valor para la frecuencia, guárdelo de manera global y reinícielo. medición continua. Eso se vería algo como esto:

volatile long interval = -1;

ISR(TIMER1_CAPT_vect) {
    static long capt = 0; // The value of this variable persists between calls to the ISR
    interval = capt - ICR1;
    capt = ICR1;
}

void main() {
    while(interval == -1); // Wait until we have a useful value
    // Do something with interval
}
    
respondido por el Nick Johnson

Lea otras preguntas en las etiquetas