Skip to content

Instantly share code, notes, and snippets.

@equaliser
Last active October 2, 2017 14:44
Show Gist options
  • Save equaliser/7075202 to your computer and use it in GitHub Desktop.
Save equaliser/7075202 to your computer and use it in GitHub Desktop.
Arduino dual audio frequency counter and pulse width measuring sketch.
/* DualFrequencyCounter */
// is a bit sketchy, but seems accurate enough.
// Relies on
// - Adafruit's SPI_VFD library
// - one of those fancy Samsung 20x2 VFDs
// - and the usual digitalWriteFast lib which I know you've already got.
// checked with a DSO Nano oscilloscope, not that they're all that great
// audio should probably be buffered, limited to 0-5v
// and fed through a comparator before input into pin 2 and 3
#include <SPI_VFD.h>
#include <digitalWriteFast.h>
#define TIMERLOAD 0
#define AVGLENGTH 21
#define SCREENROW0 0
#define SCREENROW1 1
#define CHANGERISE0 0
#define CHANGERISE1 1
#define CHANGEFALL0 2
#define CHANGEFALL1 3
SPI_VFD vfd(13, 11, 12); // init vfd: data, clock, strobe
volatile unsigned long lastTimeRise0, lastTimeFall0, thisTimeRise0, thisTimeFall0;
volatile unsigned long lastTimeRise1, lastTimeFall1, thisTimeRise1, thisTimeFall1;
volatile unsigned long int timerOverflowCount = 0;
byte avgCount0, avgCount1 = 0;
unsigned long avgTotal0[AVGLENGTH], avgTotal1[AVGLENGTH];
unsigned long pulseWidthArray0[AVGLENGTH], pulseWidthArray1[AVGLENGTH];
unsigned long pulseWidthHighArray0[AVGLENGTH], pulseWidthHighArray1[AVGLENGTH];
volatile byte change = 0;
void setup() {
//Serial.begin(115200); // for testing
// set up interrupts
attachInterrupt(0, changingInt0, CHANGE);
attachInterrupt(1, changingInt1, CHANGE);
// say helloooooo
vfd.begin(20, 2);
vfd.print("frequency counter");
delay(1000);
vfd.clear();
// initialize timer1
noInterrupts(); // disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = TIMERLOAD; // preload timer 65536-16MHz/8/32Hz
TCCR1B |= (1 << CS10); // 8 prescaler CS11 // 1 prescaler CS10
TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt
interrupts(); // enable all interrupts
}
void printRow(unsigned long ticks, const byte screenRow) {
float hz = getHertz(ticks);
if(hz>0) {
String spacer = "";
if(int(hz)==1000) { spacer=" "; } else
if(int(hz)<10) { spacer = " "; } else
if(int(hz)<100) { spacer = " "; } else
if(int(hz)<1000) { spacer = " "; } else
if(int(hz)<10000) { spacer = " "; }
vfd.setCursor(0, screenRow);
vfd.print(spacer);
vfd.print(hz, 2);
vfd.print("hz ");
}
}
void loop() {
unsigned long ticks0 = thisTimeRise0 - lastTimeRise0;
unsigned long ticks1 = thisTimeRise1 - lastTimeRise1;
unsigned long pulseHigh0 = thisTimeRise0 - thisTimeFall0;
unsigned long pulseHigh1 = thisTimeRise1 - thisTimeFall1;
unsigned long medianPulseWidthHigh;
// chuck out any ridiculously long or short periods
if (bitRead(change, CHANGERISE0)==1 && ticks0>400 && ticks0<4000000000) {
if(avgCount0 < AVGLENGTH) {
avgTotal0[avgCount0] = ticks0; // add current period onto our array for averaging
pulseWidthHighArray0[avgCount0] = pulseHigh0;
avgCount0++;
} else {
// when array full up, print median average
unsigned long medianTicks = median(avgCount0, avgTotal0);
printRow(medianTicks, SCREENROW0);
medianPulseWidthHigh = median(avgCount0, pulseWidthHighArray0);
printPulseWidth(medianPulseWidthHigh, medianTicks, SCREENROW0);
avgCount0 = 0;
}
change &= ~(1 << CHANGERISE0);
}
if (bitRead(change, CHANGERISE1)==1 && ticks1>400 && ticks1<4000000000) {
if(avgCount1 < AVGLENGTH) {
avgTotal1[avgCount1] = ticks1;
pulseWidthHighArray1[avgCount1] = pulseHigh1;
avgCount1++;
} else {
unsigned long medianTicks = median(avgCount1, avgTotal1);
printRow(medianTicks, SCREENROW1);
medianPulseWidthHigh = median(avgCount1, pulseWidthHighArray1);
printPulseWidth(medianPulseWidthHigh, medianTicks, 1);
avgCount1 = 0;
}
change &= ~(1 << CHANGERISE1);
}
}
// has to use a floating point thing as one of the operands otherwise it'll end up 1 or 0
float getPulseWidth(unsigned long pulseWidthHigh, unsigned long pulseWidth) {
return ((double)pulseWidthHigh / pulseWidth) * 100;
}
void printPulseWidth(unsigned long pulseWidthHigh, unsigned long pulseWidth, int screenRow) {
vfd.setCursor(15, screenRow);
float pwPercent = getPulseWidth(pulseWidthHigh, pulseWidth);
if(pwPercent<100) {
vfd.print(pwPercent, 1);
vfd.print("%");
}
}
float getHertz(unsigned long ticks) {
return 1/((0.0625 * ticks) / 1000000); // (x uS in a tick, * number of ticks) / microsec in a second = period
}
ISR(TIMER1_OVF_vect) { // interrupt service routine that wraps a user defined function supplied by attachInterrupt
timerOverflowCount++;
}
void changingInt0() {
if(digitalReadFast(2)) {
lastTimeRise0 = thisTimeRise0;
thisTimeRise0 = (timerOverflowCount * 65535) + TCNT1;
change |= 1 << CHANGERISE0;
} else {
lastTimeFall0 = thisTimeFall0;
thisTimeFall0 = (timerOverflowCount * 65535) + TCNT1;
change |= 1 << CHANGEFALL0;
}
}
void changingInt1() {
if(digitalReadFast(3)) {
lastTimeRise1 = thisTimeRise1;
thisTimeRise1 = (timerOverflowCount * 65535) + TCNT1;
change |= 1 << CHANGERISE1;
} else {
lastTimeFall1 = thisTimeFall1;
thisTimeFall1 = (timerOverflowCount * 65535) + TCNT1;
change |= 1 << CHANGEFALL1;
}
}
unsigned long int median(unsigned long int n, unsigned long int x[]) {
unsigned long temp;
int i, j;
// the following two loops sort the array x in ascending order
for(i=0; i<n-1; i++) {
for(j=i+1; j<n; j++) {
if(x[j] < x[i]) {
// swap elements
temp = x[i];
x[i] = x[j];
x[j] = temp;
}
}
}
if(n%2==0) {
// if there is an even number of elements, return mean of the two elements in the middle
return((x[n/2] + x[n/2 - 1]) / 2.0);
} else {
// else return the element in the middle
return x[n/2];
}
}
@funzyv
Copy link

funzyv commented Oct 2, 2017

hi equalizer,

Looking at your code and I like lot's of it. Just few questions:

  1. is there no need to reset timerOverflowCount ??
  2. why did you choose for this way of averaging. Is it working correctly?

regards,

Vincent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment