Wednesday 19 June 2019

arduino - Determine input frequency of square wave w/ ICR in Atmega328p


I'm trying to obtain the input frequency of a square wave using the input capture register of an Atmega328p. So far, it works sporadically -- which is to say, when I input a 75 kHz square wave, the output looks like this:


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


Does anyone know why this might be the case? I've tried messing with the data types, but otherwise I'm not really sure what the problem could be. The code I've written is below.


// # 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< // input capture edge select; rising edge triggers capture

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


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

void startTimer1(void)
{
TCCR1B = (1< //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

}
}

Thanks in advance!


UPDATE***********************************



The second iteration of code, using enumerated types to make a state machine:


  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< //Enable input capture and overflow interrupts
TIMSK1|=(1<
}
void StartTimer1(void)
{
//Start timer without prescaler
TCCR1B|=(1< //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);
}


Answer



Here is your code updated to work, with comments starting with "J:" explaining the changes...


  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< //Enable input capture and overflow interrupts

TIMSK1|=(1< }

// 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<
//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 );
}
}

No comments:

Post a Comment

arduino - Can I use TI&#39;s cc2541 BLE as micro controller to perform operations/ processing instead of ATmega328P AU to save cost?

I am using arduino pro mini (which contains Atmega328p AU ) along with cc2541(HM-10) to process and transfer data over BLE to smartphone. I...