Skip to main content
Arduino

Return to Answer

+ why the measured frequency was off.
Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

As stated in my comment, your code is kind of fine. Id mostly works, but it looses a few microseconds on each iteration. If you are looking at multi-kHz frequencies, you probably want to avoid loosing microseconds. The solution is simple: never reset the counter. I changed you ISR as follows:

uint16_t previous_counter;
void pin_ISR(){
 uint16_t counter = TCNT1;
 f = 2000000/(counter - previous_counter);
 previous_counter = counter;
}

and now it gives the correct frequency... on average. There are some variations though, dues mostly to delays caused by other interrupts.

Edit 1: If you want to avoid the fluctuations caused by other interrupts, your best bet is probably to use the input capture feature of Timer 1.

Edit 2: Here is why your measurement was somewhat off. Your original ISR essentially does something like this:

void pin_ISR() {
 uint16_t temporary_value = TCNT1;
 loose_some_time();
 TCNT1 = 0;
}

The time you loose in the ISR is the part of the signal period you are not measuring, which means you are underestimating the period by that much. In the ISR you posted, this is mostly the time taken by a 32-bit division, which is quite significant. A simple improvement would be to clear TCNT1 right after reading it:

void pin_ISR() {
 uint16_t temporary_value = TCNT1;
 TCNT1 = 0;
 loose_some_time();
}

and now the average period you measure is almost right. It is not perfect though, as the whole reading and writing of TCNT1 takes 8 CPU cycles.

The alternative I am suggesting, where you never clear the timer, is the correct way of measuring a period with a timer interrupt: you don't loose a single CPU cycle. The best way is to use input capture: you get rid of the software-induced jitter.

Note: You should also take care of the very valid points raised in Jot's answer, about making f volatile and reading it with interrupts disabled.

As stated in my comment, your code is kind of fine. Id mostly works, but it looses a few microseconds on each iteration. If you are looking at multi-kHz frequencies, you probably want to avoid loosing microseconds. The solution is simple: never reset the counter. I changed you ISR as follows:

uint16_t previous_counter;
void pin_ISR(){
 uint16_t counter = TCNT1;
 f = 2000000/(counter - previous_counter);
 previous_counter = counter;
}

and now it gives the correct frequency... on average. There are some variations though, dues mostly to delays caused by other interrupts.

Edit: If you want to avoid the fluctuations caused by other interrupts, your best bet is probably to use the input capture feature of Timer 1.

As stated in my comment, your code is kind of fine. Id mostly works, but it looses a few microseconds on each iteration. If you are looking at multi-kHz frequencies, you probably want to avoid loosing microseconds. The solution is simple: never reset the counter. I changed you ISR as follows:

uint16_t previous_counter;
void pin_ISR(){
 uint16_t counter = TCNT1;
 f = 2000000/(counter - previous_counter);
 previous_counter = counter;
}

and now it gives the correct frequency... on average. There are some variations though, dues mostly to delays caused by other interrupts.

Edit 1: If you want to avoid the fluctuations caused by other interrupts, your best bet is probably to use the input capture feature of Timer 1.

Edit 2: Here is why your measurement was somewhat off. Your original ISR essentially does something like this:

void pin_ISR() {
 uint16_t temporary_value = TCNT1;
 loose_some_time();
 TCNT1 = 0;
}

The time you loose in the ISR is the part of the signal period you are not measuring, which means you are underestimating the period by that much. In the ISR you posted, this is mostly the time taken by a 32-bit division, which is quite significant. A simple improvement would be to clear TCNT1 right after reading it:

void pin_ISR() {
 uint16_t temporary_value = TCNT1;
 TCNT1 = 0;
 loose_some_time();
}

and now the average period you measure is almost right. It is not perfect though, as the whole reading and writing of TCNT1 takes 8 CPU cycles.

The alternative I am suggesting, where you never clear the timer, is the correct way of measuring a period with a timer interrupt: you don't loose a single CPU cycle. The best way is to use input capture: you get rid of the software-induced jitter.

Note: You should also take care of the very valid points raised in Jot's answer, about making f volatile and reading it with interrupts disabled.

+ Input capture option.
Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

As stated in my comment, your code is kind of fine. Id mostly works, but it looses a few microseconds on each iteration. If you are looking at multi-kHz frequencies, you probably want to avoid loosing microseconds. The solution is simple: never reset the counter. I changed you ISR as follows:

uint16_t previous_counter;
void pin_ISR(){
 uint16_t counter = TCNT1;
 f = 2000000/(counter - previous_counter);
 previous_counter = counter;
}

and now it gives the correct frequency... on average. There are some variations though, dues mostly to delays caused by other interrupts.

Edit: If you want to avoid the fluctuations caused by other interrupts, your best bet is probably to use the input capture feature of Timer 1.

As stated in my comment, your code is kind of fine. Id mostly works, but it looses a few microseconds on each iteration. If you are looking at multi-kHz frequencies, you probably want to avoid loosing microseconds. The solution is simple: never reset the counter. I changed you ISR as follows:

uint16_t previous_counter;
void pin_ISR(){
 uint16_t counter = TCNT1;
 f = 2000000/(counter - previous_counter);
 previous_counter = counter;
}

and now it gives the correct frequency... on average. There are some variations though, dues mostly to delays caused by other interrupts.

As stated in my comment, your code is kind of fine. Id mostly works, but it looses a few microseconds on each iteration. If you are looking at multi-kHz frequencies, you probably want to avoid loosing microseconds. The solution is simple: never reset the counter. I changed you ISR as follows:

uint16_t previous_counter;
void pin_ISR(){
 uint16_t counter = TCNT1;
 f = 2000000/(counter - previous_counter);
 previous_counter = counter;
}

and now it gives the correct frequency... on average. There are some variations though, dues mostly to delays caused by other interrupts.

Edit: If you want to avoid the fluctuations caused by other interrupts, your best bet is probably to use the input capture feature of Timer 1.

Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

As stated in my comment, your code is kind of fine. Id mostly works, but it looses a few microseconds on each iteration. If you are looking at multi-kHz frequencies, you probably want to avoid loosing microseconds. The solution is simple: never reset the counter. I changed you ISR as follows:

uint16_t previous_counter;
void pin_ISR(){
 uint16_t counter = TCNT1;
 f = 2000000/(counter - previous_counter);
 previous_counter = counter;
}

and now it gives the correct frequency... on average. There are some variations though, dues mostly to delays caused by other interrupts.

lang-cpp

AltStyle によって変換されたページ (->オリジナル) /