Created
January 29, 2012 01:27
-
-
Save dccourt/1696604 to your computer and use it in GitHub Desktop.
Analogue comparator-based frequency counter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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