Objective: Measure the frequency of a square wave greater than 100 kHz.
Methods:
Conclusion: Neither method works for this scenario.
What’s the solution?
There are two approaches that we can use
Let us explore them below:
Input Capture Mode (ATmega328P - Arduino UNO)
volatile uint8_t overflowCount = 0;
volatile uint32_t t = 0;
uint16_t firstCapture = 0;
uint16_t secondCapture = 0;
void setup() {
Serial.begin(115200);
TCCR1A = 0;
TCCR1B = (1 << ICES1) | (1 << CS10); // Rising edge, No prescaler (16 MHz)
}
void loop() {
if (countSignal()) {
float timeMicroseconds = t * 0.0625; // Convert clock cycles to microseconds (1/16 MHz)
float frequency = 1e6 / timeMicroseconds; // Convert period to frequency
Serial.print("Frequency: ");
Serial.print(frequency);
Serial.print(" Hz, Time: ");
Serial.print(timeMicroseconds);
Serial.println(" µs");
}
delay(500);
}
bool countSignal() {
cli(); // Disable interrupts
overflowCount = 0; // Reset overflow counter before starting
TIFR1 |= (1 << ICF1) | (1 << TOV1); // Clear input capture & overflow flags
// Wait for first rising edge
while (!(TIFR1 & (1 << ICF1)));
firstCapture = ICR1; // Store first capture value
TIFR1 = (1 << ICF1); // Clear flag
// Wait for second rising edge while counting overflows
while (!(TIFR1 & (1 << ICF1))) {
if (TIFR1 & (1 << TOV1)) {
overflowCount++;
TIFR1 = (1 << TOV1); // Clear overflow flag
}
}
secondCapture = ICR1; // Store second capture
sei(); // Re-enable interrupts
// Correct time calculation
t = (overflowCount * 65536UL) + (secondCapture - firstCapture);
return t > 0; // Return true if valid measurement
}
Setup (setup()
)
Main Loop (loop()
)
countSignal()
is called to calculate the signal period.Signal Measurement (countSignal()
)
cli();
TIFR1 = (1 << ICF1) | (1 << TOV1);
TCCR1A = 0;
TCCR1B = (1 << ICES1) | (1 << CS10);
firstCapture
, and clear the flag uint16_t firstCapture = ICR1;
uint16_t secondCapture = ICR1;
t = (overflowCount * 65536UL) + (secondCapture - firstCapture);
sei();
Conclusion
Why 1 MHz is the Maximum Detectable Frequency?
1️. After First Capture:
2️. Waiting for Second Capture:
3️. Limit on Frequency Detection:
Conclusion: The approach is accurate up to 1 MHz but fails for higher frequencies due to the loop execution time.
Alternatively, we can count the number of rising edges of the signal in a fixed time interval (e.g., 1 second).
The frequency F is directly equal to the number of pulses counted in 1 second.
uint8_t OldTimerA0, oldTimerB0, oldTimerMask;
volatile uint32_t timerTick = 0;
volatile uint16_t overflowCount = 0;
volatile bool countValue = false;
float frequency = 0, timePeriod = 0;
uint16_t count = 0;
void setup() {
Serial.begin(115200);
}
void loop() {
disableTimer0(); // Stop Timer0 to avoid conflicts
startCount(); // Start frequency measurement
while (!countValue); // Wait for measurement to complete
Serial.print("Frequency: ");
Serial.print(frequency);
Serial.println(" Hz");
Serial.print("Time Period: ");
Serial.print(timePeriod, 10);
Serial.println(" Sec");
enableTimer0(); // Restore Timer0
delay(1000); // Wait before next measurement
}
void disableTimer0() {
OldTimerA0 = TCCR0A;
oldTimerB0 = TCCR0B;
oldTimerMask = TIMSK0;
TCCR0A = TCCR0B = TIMSK0 = 0; // Stop Timer0
}
void enableTimer0() {
TCCR0A = OldTimerA0;
TCCR0B = oldTimerB0;
TIMSK0 = oldTimerMask;
}
void startCount() {
cli(); // Disable interrupts
// Configure Timer2 for 1-second measurement
TCCR2A = (1 << WGM21); // CTC mode
TCCR2B = (1 << CS22) | (1 << CS21) | (1 << CS20); // Prescaler 1024
OCR2A = 156; // 1-second interrupt
TIMSK2 = (1 << OCIE2A); // Enable Timer2 Compare Match Interrupt
// Configure Timer1 for external pulse counting
TCCR1A = 0;
TCCR1B = (1 << CS12) | (1 << CS11) | (1 << CS10); // External clock, rising edge
TIMSK1 = (1 << TOIE1); // Enable Timer1 overflow interrupt
countValue = false;
overflowCount = 0;
TCNT1 = 0;
sei(); // Enable interrupts
}
ISR(TIMER1_OVF_vect) {
overflowCount++; // Increment overflow count
}
ISR(TIMER2_COMPA_vect) {
if (count == 100) { // 100 interrupts = 1 second
cli(); // Disable interrupts for calculation
timerTick = ((uint32_t)overflowCount * 65536) + TCNT1; // Total pulses
frequency = timerTick; // Frequency = total pulses in 1 second
timePeriod = 1.0 / frequency; // Time period
// Stop timers
TCCR1A = TCCR1B = TIMSK1 = 0;
TCCR2A = TCCR2B = TIMSK2 = 0;
TCNT1 = TCNT2 = 0;
countValue = true; // Set measurement complete flag
count = 0; // Reset counter
sei(); // Re-enable interrupts
}
count++;
}
setup()
loop()
delay()
and millis()
.startCount()
.startCount().
(overflowCount = 0, TCNT1 = 0)
.sei()
).disableTimer0().
enableTimer0()
.ISR(TIMER1_OVF_vect)
ISR(TIMER2_COMPA_vect)
cli();
→ Disable interrupts to prevent data corruption.timerTick = ((uint32_t)overflowCount * 65536) + TCNT1;
frequency = timerTick;
→ Pulses per second.timePeriod = 1.0 / frequency;
→ Time period (seconds per pulse).loop()
that measurement is complete.sei();
→ Re-enables interrupts.
Input Capture Mode
Counting Pulse Counting using Timer Counter
Input Capture Mode
Counting Pulse Counting using Timer Counter