#define FREQ_PIN (2) // Arduino D2 pin
#define PWM_PIN (11) // Arduino D11 pin
volatile uint16_t ovf_count = 0;
volatile uint8_t edge_count = 0;
volatile boolean done = false;
volatile uint32_t saved_ticks[2] = {0,0};
ISR(TIMER1_OVF_vect) {
ovf_count++; // Increment Timer1 overflow counter
}
// Initialize Timer1
void init_timer1() {
uint8_t SREG_tmp = SREG; // Save the status register
ovf_count = 0; // Reset overflow counter
cli(); // Disable interrupts
TIMSK1 = 0;
TCNT1 = 0; // Reset Timer1 count register
TCCR1A = 0;
TCCR1B = 0;
TIFR1 |= _BV(TOV1); // Clear Timer1 overflow flag
// Use Timer1 in Normal mode, f_CPU/8: 16MHz/8=2MHz => 0.5usec step
TIMSK1 |= (1<<TOIE1); // Enable Timer1 overflow interrupt
TCCR1B |= (1<<CS11); // Start Timer1
SREG = SREG_tmp; // Restore the status register
}
void ext_isr() { // ISR for External Interrupt
static uint32_t saved_value;
uint32_t tickcount = get_tickcount();
if (!done) {
if (edge_count==1) {
saved_ticks[0] = tickcount;
}
else if (edge_count==5) {
saved_ticks[1] = tickcount;
done = true;
}
edge_count++; // Increment the rising-edge counter
}
}
// Get the current value of Timer1 counter (with 0.5usec precision)
uint32_t get_tickcount() {
// Save the SREG register
uint8_t SREG_tmp = SREG;
// Disable global interrupt
cli();
// Read the current value of TCNT1 register of Timer1
uint16_t count_value = TCNT1;
// In normal operation the Timer/Counter Overflow Flag (TOV1)
// will be set in the same clock cycle as the TCNT1 becomes zero.
if ( TIFR1 & _BV(TOV1) ) { // Check Timer1 overflow flag
TIFR1 |= _BV(TOV1); // Clear overflow flag
++ovf_count; // Increment the overflow counter
}
// Calculate Timer1 tick count
uint32_t ticks = ovf_count;
ticks = ((ticks << 16) + count_value);
// Restore the SREG register
SREG = SREG_tmp;
return ticks;
}
void measure( uint32_t *period, uint32_t *freq ) {
// Save the current values of TCCR0A/B
uint8_t TCCR0A_tmp = TCCR0A;
uint8_t TCCR0B_tmp = TCCR0B;
uint32_t tick_diff;
// Set TCCR0A/B registers to 0 (disable Timer0)
TCCR0A = 0;
TCCR0B = 0;
edge_count = 0;
done = false;
ovf_count = 0;
// Wait until the done flag is set.
while (!done){}
tick_diff = saved_ticks[1] - saved_ticks[0];
// Restore the saved values of TCCR0A/B
TCCR0A = TCCR0A_tmp;
TCCR0B = TCCR0B_tmp;
*period = 10*tick_diff/8;
*period -= 25*(*period)/10000; // Correction
*freq = 100*1000000ul/(*period);
}
void set_timer2_prescaler( uint16_t prescaler ) {
uint8_t bits = 0; // CS[2:0] bits
switch (prescaler) {
case 1: bits = 1; break; // 0b001
case 8: bits = 2; break; // 0b010
case 32: bits = 3; break; // 0b011
case 64: bits = 4; break; // 0b100
case 128: bits = 5; break; // 0b101
case 256: bits = 6; break; // 0b110
case 1024: bits = 7; break; // 0b111
default: break; // Timer stopped
}
TCCR2B &= ~(_BV(CS22) | _BV(CS21) | _BV(CS20));
if (bits & 1) { TCCR2B |= _BV(CS20); }
if (bits & 2) { TCCR2B |= _BV(CS21); }
if (bits & 4) { TCCR2B |= _BV(CS22); }
}
void init_timer2_pwm( uint16_t prescaler, uint8_t max_value ) {
DDRB |= _BV(DDB3); // Set direction of PB3 pin to output.
// Use Fast PWM (non-inverting) with 50% duty cycle
// Enable Timer2 OC2A output for PWM
// WGM2[2:0] = "111" => BOTTOM=0, TOP=OCR2A
// COM2A[1:0] = "01" => Toggle OC2A on compare match.
TCCR2A = _BV(WGM21) | _BV(WGM20) | _BV(COM2A0);
TCCR2B = _BV(WGM22);
set_timer2_prescaler( prescaler );
OCR2A = max_value; // Set the MAX value for period
}
void setup() {
Serial.begin( 115200 );
Serial.println( "Frequency Measurement with Arduino Board" );
Serial.println( "Timer0 = off" );
init_timer1();
// freq. = 16MHz/(8*10)/2 = 100kHz
// freq. = 16MHz/(8*256)/2 = 3906.Hz
// Use Timer2 to create a PWM signal, frequency of 100kHz
init_timer2_pwm( 8, 10-1 );
delay(100);
// Enable the external interrupt
attachInterrupt( digitalPinToInterrupt(FREQ_PIN),
ext_isr, RISING );
}
void loop() {
static char sbuf[40];
uint32_t period, freq;
// Measure the period and frequency of the input signal
measure( &period, &freq );
// The period value must be divided by 10.
sprintf( sbuf, "Period = %lu.%1u us, ",
period/10, period%10 );
Serial.print( sbuf );
// The frequency value must be divided by 10.
sprintf( sbuf, "Freq. = %lu.%1u Hz",
freq/10, freq%10 );
Serial.println( sbuf );
delay(1000);
}
/*
ข้อสังเกต: การทำงานของโค้ดตัวอย่างนี้ มีการปิดการทำงานของวงจร Timer0 ชั่วคราว
โดยปรกติแล้ว Arduino Sketch จะใช้ Timer0
ในการสร้างฐานเวลาของระบบที่มีการเกิดอินเทอร์รัพท์ทุก ๆ 4 ไมโครวินาที
และใช้กับคำสั่ง เช่น micros() และ millis()
https://iot-kmutnb.github.io/blogs/arduino/arduino_atmega328p_freq_counter/
*/