Skip to content

Instantly share code, notes, and snippets.

@dccourt
Created January 29, 2012 01:27
Show Gist options
  • Save dccourt/1696604 to your computer and use it in GitHub Desktop.
Save dccourt/1696604 to your computer and use it in GitHub Desktop.
Analogue comparator-based frequency counter
// Count zero crossings using analog comparator
// atmega328p:
// Uses pin 7 as the anacomp -ve input (AIN1)
// Uses pin 6 as the anacomp +ve input (AIN0)
// attiny85:
// Uses pin 1 as AIN1
// Uses pin 0 as AIN0
#ifdef __AVR_ATtiny85__
#define AIN1 1
#define AIN0 0
#else
#define AIN1 7
#define AIN0 6
#endif
#define SAMPLE_WINDOW_SIZE 5
#define SAMPLE_MILLIS 50
volatile unsigned int crossings;
volatile unsigned int crossing_counts[SAMPLE_WINDOW_SIZE];
unsigned char timerLoadValue;
unsigned char latency;
unsigned char n;
int timer;
volatile unsigned long timer2;
volatile char sample_ready;
long oldmicro;
int diags;
int oldfreq;
#define TIMER_CLOCK_FREQ 16000000.0 //2MHz for /8 prescale from 16MHz
#ifdef __AVR_ATtiny85__
// TBD: Will need to use timer 1 or timer 0 for ATTiny
#error ATTiny clock code not yet written
#endif
//Setup Timer2.
//Configures the 8-Bit Timer2 to generate an interrupt
//at the specified frequency.
//Returns the timer load value which must be loaded into TCNT2
//inside your ISR routine.
//See the example usage below.
unsigned char SetupTimer2(float timeoutFrequency){
unsigned char result; //The timer load value.
/* We need to work out what divisor of the chip clock can be
used to get an 8-bit counter preload value that represents
the requested frequency.
timer preload_val = scaled_freq / requested_freq
=> scaled_freq = clock_freq / scaler
=> preload_val = clock_freq / scaler / requested_freq
=> 256 > clock_freq / scaler / requested_freq
=> clock_freq / scaler < 256 * requested_freq
=> 1 / scaler < 256 * requested_freq / clock_freq
=> scaler > clock_freq / (256 * requested_freq)
*/
Serial.print("clock/requested:");
Serial.println(TIMER_CLOCK_FREQ / timeoutFrequency);
int min_scaler = TIMER_CLOCK_FREQ / (256 * timeoutFrequency);
Serial.print("Min scaler:");
Serial.println(min_scaler);
// Need to convert min_scaler into a power-of-2 value to use.
// Allowable values are actually 1, 8, 32, 64, 128, 256, 1024 -
// see data sheet, section 18.10.
int scaler = 1;
while ((scaler < min_scaler) && (scaler < 1024))
{
scaler <<= 1;
// skip disallowed values
if ((scaler == 2) || (scaler == 4) || (scaler == 16) || (scaler == 512))
{
scaler <<= 1;
}
}
if (scaler < min_scaler)
{
// Output a warning.
Serial.println("Requested timer frequency too low - unable to find a suitable divider");
}
else
{
Serial.print("Chosen prescaler:");
Serial.println(scaler);
}
long scaled_freq = TIMER_CLOCK_FREQ / scaler;
// Convert the scaler value into register settings
switch (scaler)
{
case 1:
TCCR2B = 0b00000001;
break;
case 8:
TCCR2B = 0b00000010;
break;
case 32:
TCCR2B = 0b00000011;
break;
case 64:
TCCR2B = 0b00000100;
break;
case 128:
TCCR2B = 0b00000101;
break;
case 256:
TCCR2B = 0b00000110;
break;
case 1024:
TCCR2B = 0b00000111;
break;
default:
Serial.print("Unrecognised scaler: ");
Serial.println(scaler);
break;
}
//Calculate the timer load value
result=(int)((256.0-(scaled_freq/timeoutFrequency))+0.5);
//Timer2 Settings: Timer mode 0
TCCR2A = 0;
//Timer2 Overflow Interrupt Enable
TIMSK2 = 1<<TOIE2;
//load the timer for its first cycle
TCNT2=result;
Serial.print("Timer2 reload value:");
Serial.println(result);
return(result);
}
void setup()
{
Serial.begin(57600);
crossings = 0;
n = 0;
timer = 0;
timer2 = 0;
sample_ready = 0;
diags = 0;
oldfreq = 0;
// Set up the comparator.
pinMode(AIN0, INPUT);
pinMode(AIN1, INPUT);
// ACI : Ana Comp Interrupt flag - writing a 1 clears it, hardware autoclears it when calling ISR
// ACIE : Ana Comp Int Enable
// ACIS1, ACIS0 : Ana Comp Int mode select:
// 0 0 Int on output toggle
// 0 1 Reserved
// 1 0 Int on falling output edge
// 0 1 Int on rising output edge
// (See section 16 of ATTiny85 datasheet)
ACSR = 0 | (1 << ACI) | (1 << ACIE) | (1 << ACIS1); // Enable interrupts; falling edge mode.
// Set up a timer interrupt.
timerLoadValue = SetupTimer2(1000);
}
void loop()
{
// Calculate average crossings and output once we have a full sample window.
// (This is indicated by the timer ISR setting sample_ready == true.
while (!sample_ready);
// Reset so that we'll wait again on the next loop.
sample_ready = 0;
// Grab a copy of the data as calculation could take a while.
int copy[SAMPLE_WINDOW_SIZE];
memcpy(copy, (const void *)crossing_counts, sizeof(crossing_counts));
// Debugging : Print contents of the copy[] array every 10 cycles.
/*
diags ++;
if (diags % 10 == 0)
{
for (int ii = 0; ii < SAMPLE_WINDOW_SIZE; ii++)
{
Serial.print(copy[ii]);
Serial.print(',');
}
Serial.println("\x08.");
}
*/
long avg_count = 0;
for (int ii = 0; ii < SAMPLE_WINDOW_SIZE; ii++)
{
avg_count += copy[ii];
}
avg_count *= (1000 / SAMPLE_MILLIS) / SAMPLE_WINDOW_SIZE;
/* Debugging : check that the interval between full samples is
as expected:
long micro = micros();
long microdiff = micro - oldmicro;
oldmicro = micro;
Serial.print(microdiff);
Serial.print(' ');
*/
// Filter noise. If we're within 100Hz of our previous value,
// then assume that we're reading a steady tone.
if ((abs(avg_count - oldfreq) < 100) && (avg_count != 0))
{
Serial.println(avg_count);
}
oldfreq = avg_count;
}
ISR(ANALOG_COMP_vect)
{
crossings ++;
}
//Timer2 overflow interrupt vector handler, called once per ms.
ISR(TIMER2_OVF_vect) {
timer++;
// timer2++;
// Sample input freq every 100ms
if (timer == SAMPLE_MILLIS)
{
crossing_counts[n++] = crossings;
timer = 0;
crossings = 0;
if (n == SAMPLE_WINDOW_SIZE)
{
n = 0;
sample_ready = 1;
}
}
//Capture the current timer value. This is how much error we
//have due to interrupt latency and the work in this function
latency=TCNT2;
//Reload the timer and correct for latency.
TCNT2=latency+timerLoadValue;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment