chipKIT® Development Platform

Inspired by Arduino™

Digital signal frequency counter

Created Fri, 12 Jun 2015 12:57:17 +0000 by citizen3942


citizen3942

Fri, 12 Jun 2015 12:57:17 +0000

I am developing a digital signal frequency counter and I stumbled upon a very annoying situation. I need to display the signal frequency on 6 digit segment indicators (multiplexing) and unfortunately above 42kHz I start to get readout errors. I tried to disable the readout display timer interrupt and then the readout was correct (sent the number via serial port to PC).

So basically those two interrupts start to interfere eachother and I need to get rid of that effect. Any suggestions?

Here is my code:

#include <Timer.h>
#include "math.h"

// Create a new timer object using T4
Timer4 timer;

//data storage variables
volatile unsigned long Counter=0;
volatile unsigned long prevCount=0;
unsigned long Start; //Start time variable in microseconds
unsigned long Stop; //Stop time variable in microseconds


const int acqTime = 500; //aquire signal for 250 ms
const unsigned long refrTime = 500; //refresh the display digits at 500 hz
const int SAMPLES=250;

const int digitPins[6] = {
  28,29,30,31,32,33};                 //6 common anode pins of the display
const int segmentPins[8] = {
  13,40,39,38,37,36,35,34};      //8 segment pins of the display

const int FREQ_PIN = 2;

int digitBuffer[6]={0};

boolean digits[10][7] = {
{1,1,1,1,1,1,0},//0
{0,1,1,0,0,0,0},//1
{1,1,0,1,1,0,1},//2
{1,1,1,1,0,0,1},//3
{0,1,1,0,0,1,1},//4
{1,0,1,1,0,1,1},//5
{1,0,1,1,1,1,1},//6
{1,1,1,0,0,0,0},//7
{1,1,1,1,1,1,1},//8
{1,1,1,1,0,1,1},//9
};
  
unsigned long freq=0,freqTmp=0;
float freqFloat=0;
int digitCnt=0;

//updates digit buffer
void updateBuffer(){
  boolean dim=true;
  freqTmp=freq;
  for(int i=0;i<6;i++){ //add digits to buffer
    digitBuffer[5-i] = freqTmp % 10;
    freqTmp /= 10;
  }
  for(int i=0;i<6;i++){ //dim pre-zeroes
	if(digitBuffer[i]==0&&dim==true&&i<5){
		digitBuffer[i]=10;
	}else{
		dim=false;
	}
  }
}

//update segment digital pins
void updateSegments(int digit, boolean dot=0){
	for(int i=0;i<8;i++){
		digitalWrite(segmentPins[i], HIGH);
	}
	
	for(int i=0;i<7;i++){
		if(digit==10){
			//do nothing - leave all digital pins high
		}else{
			if(digits[digit][i]){
				digitalWrite(segmentPins[i], LOW);
			}
		}
	}
	
	if(dot){ //add dot if set true
		digitalWrite(segmentPins[7], LOW);
	}
	
}

//writes the number on display
void updateDisp(){
  
  updateBuffer();
  
    for(int j=0; j<6; j++){
        digitalWrite(digitPins[j], LOW);
    }

    if (digitCnt==2){ //add a dot to the 3rd digit
            if(digitBuffer[digitCnt]==10){ //if there is empty digit, then do not add dot		
                updateSegments(digitBuffer[digitCnt]);
            }else{
                updateSegments(digitBuffer[digitCnt],1);
            }
	}else{
		updateSegments(digitBuffer[digitCnt]);
	}
  
	digitalWrite(digitPins[digitCnt], HIGH);
	
	digitCnt++;
	if(digitCnt == 6){
		digitCnt = 0;
	}
}

// Be sure to set the ISR routine to be an interrupt
void __attribute__((interrupt)) isr() {
        
        // And here we update the display.
        updateDisp();

	// And tell the chip we have handled the interrupt by
	// clearing the flag.
	clearIntFlag(_TIMER_4_IRQ);
}

void setup() {
	for(int i=0;i<6;i++)
	{
		pinMode(digitPins[i],OUTPUT);
	}
	for(int i=0;i<8;i++)
	{
		pinMode(segmentPins[i],OUTPUT);
	}
        
        pinMode(FREQ_PIN, INPUT); //frequency input pin
        
	timer.setFrequency(refrTime);

	// Link in out ISR routine. Note, it is a real ISR routine,
	// so requires all the right attributes etc.
	timer.attachInterrupt(isr);

	// Start the timer running,
	timer.start();

    Serial.begin(115200);
        
    attachInterrupt(1,counter,RISING);
}

void loop() {
  if(Counter>=SAMPLES && prevCount<Counter){
    Stop=micros();
    freqFloat=float(Counter)*1000/(Stop-Start); //calcualte floating point frequency
    freq=freqFloat*1000;
    //if(freq>=42000){
    //  freq=freq+((6.0/refrTime)*freq-refrTime);
    //}
    Serial.println(freq);
    Counter=0;
    Start=micros();
  }else{
    freq=0;
  }
  delay(acqTime);
}

void counter(){
     prevCount=Counter;
     Counter++;
}

majenko

Fri, 12 Jun 2015 13:25:01 +0000

You're already using the Timer library, so why not use it for the frequency counting as well? In there is (or should be if you have a recent version) an example that uses two timers to accurately count the frequency pretty much in the background. I used it just the other day to measure a 1.88MHz signal.

It uses one timer to act as the "time base" for the frequency counting (the period over which the pulses are counted) and another with the input set to it's external pin (what pin and timer combination you can use depends on your board), which just counts up in the background.

#include <Timer.h>

// Create a new timer object using T4
Timer4 timer;
// And a counter object using T5
Timer5 counter;

// This is where we will store the current incoming frequency.
volatile uint32_t currentHz = 0;

void setup() {
    // We want the maximum resolution we can get.
    // Like this we can measure 65535 pulses (it's a 16
    // bit timer), so up to 655350Hz with a 10Hz resolution
    // before it wraps round.

    // To measure higher frequencies we can either increase
    // the prescaler (and multiply the result accordingly)
    // or decrease the sample time.

    counter.setPrescaler(1);
    counter.setPeriod(0xFFFF);

    // The timer should get its clock source from the input pin
    counter.setClockSource(TIMER_IN);

    // Ensure the counter is empty at the beginning.
    counter.reset();

    // We'll run at a frequency of 10Hz. That's 100ms per tick.  We can't go
    // much slower than that, the chip is too fast.
    timer.setFrequency(10);

    // Link in out ISR routine. Note, it is a real ISR routine,
    // so requires all the right attributes etc.
    timer.attachInterrupt(isr);

    // Start the timer and counter running,
    timer.start();
    counter.start();

    Serial.begin(115200);
}

void loop() {
    // For now we'll just dump the current frequency to the
    // serial terminal.
    Serial.print(currentHz);
    Serial.println("Hz");
    delay(100);
}

// Be sure to set the ISR routine to be an interrupt
void __attribute__((interrupt)) isr() {

    // We're running at a frequency of 10Hz, so the reading will
    // be the number of pulses in 0.1 seconds. Multiply
    // it by 10 to get the number of pulses in 1 second, which
    // is the number of Hz, of course.
    currentHz = counter.getAndResetCount() * 10;

    // And tell the chip we have handled the interrupt by
    // clearing the flag.
    clearIntFlag(_TIMER_4_IRQ);
}

With your display code in an interrupt as well (I'd make sure that was a lower priority than the counter interrupt) your loop() would be empty ;)


citizen3942

Fri, 12 Jun 2015 13:53:14 +0000

I cannot seem to get signal from counter.setClockSource(TIMER_IN); I am using uC32 board and the digital signal input is on pin I/O pin 2 which is supposedly interrupt number 1. And looking at the Timer library the TIMER_IN case has a value of 1. What might be the problem here?


majenko

Fri, 12 Jun 2015 14:13:32 +0000

Ok, the uC32... that's tricky.

The timer input pin is a fixed pin per timer. On the uC32 there is only one timer input pin, and that is for timer 1 (T1CK). However, that pin isn't presented on any header, instead it is routed to the "secondary oscillator X2". Not only that, it goes via resistor R34 and has a capacitor to ground (C22).

[attachment=0]x2.png[/attachment]

It's a bit of a pain with these lower-end 64-pin chips. You would get better results with one of the boards that either has a 100-pin chip (say a MAX32), or one which presents all the pins to headers (say a Fubarino SD).

If you're careful you could try removing C22 (which would affect the quality of your signal and limit your maximum frequency) and try connecting your input signal direct to the indicated pin. You would need to ensure that the "counter" was running on Timer1 not timer 5.


citizen3942

Fri, 12 Jun 2015 14:29:54 +0000

Its kind of a nuisance to do it in that way. It might be a bit silly question but is it possible to just adjust the code in Timer library so that it would take the clock input from INT1 pin?


majenko

Fri, 12 Jun 2015 15:09:30 +0000

No, it's not possible. It's a feature of the hardware of the chip, not the library. The library just sets the input to the timer module to it's external pin, and that pin is fixed and can never be changed.


citizen3942

Fri, 12 Jun 2015 15:34:49 +0000

Well, I did not desolder the C22 capacitor and the code I compiled (below). Manages to read the input signal frequency, but it has a small error in readout. Namely it is about 50-60 Hz less than the set frequency. I believe it is because of multiplexing those segment digits. I have to sort out that problem as well and then the frequency counter works as it is supposed to. The next level would be to change the timebase so that the readout would be in 1Hz precision.

#include <Timer.h>

// Create a new timer object using T4
Timer3 display;
// Create a new timer object using T4
Timer4 timer;
// And a counter object using T5
Timer1 counter;

// This is where we will store the current incoming frequency.
volatile uint32_t currentHz = 0;

int digitCnt=0;
int digitBuffer[6]={0};
boolean digits[10][7] = {
{1,1,1,1,1,1,0},//0
{0,1,1,0,0,0,0},//1
{1,1,0,1,1,0,1},//2
{1,1,1,1,0,0,1},//3
{0,1,1,0,0,1,1},//4
{1,0,1,1,0,1,1},//5
{1,0,1,1,1,1,1},//6
{1,1,1,0,0,0,0},//7
{1,1,1,1,1,1,1},//8
{1,1,1,1,0,1,1},//9
};
const int digitPins[6] = {
  28,29,30,31,32,33};                 //6 common anode pins of the display
const int segmentPins[8] = {
  13,40,39,38,37,36,35,34};      //8 segment pins of the display
unsigned long freqTmp=0;


//updates digit buffer
void updateBuffer(){
  boolean dim=true;
  freqTmp=currentHz;
  for(int i=0;i<6;i++){ //add digits to buffer
    digitBuffer[5-i] = freqTmp % 10;
    freqTmp /= 10;
  }
  for(int i=0;i<6;i++){ //dim pre-zeroes
	if(digitBuffer[i]==0&&dim==true&&i<5){
		digitBuffer[i]=10;
	}else{
		dim=false;
	}
  }
}

//update segment digital pins
void updateSegments(int digit, boolean dot=0){
	for(int i=0;i<8;i++){
		digitalWrite(segmentPins[i], HIGH);
	}
	
	for(int i=0;i<7;i++){
		if(digit==10){
			//do nothing - leave all digital pins high
		}else{
			if(digits[digit][i]){
				digitalWrite(segmentPins[i], LOW);
			}
		}
	}
	
	if(dot){ //add dot if set true
		digitalWrite(segmentPins[7], LOW);
	}
	
}

//writes the number on display
void updateDisp(){
  
  updateBuffer();
  
    for(int j=0; j<6; j++){
        digitalWrite(digitPins[j], LOW);
    }

    if (digitCnt==2){ //add a dot to the 3rd digit
            if(digitBuffer[digitCnt]==10){ //if there is empty digit, then do not add dot		
                updateSegments(digitBuffer[digitCnt]);
            }else{
                updateSegments(digitBuffer[digitCnt],1);
            }
	}else{
		updateSegments(digitBuffer[digitCnt]);
	}
  
	digitalWrite(digitPins[digitCnt], HIGH);
	
	digitCnt++;
	if(digitCnt == 6){
		digitCnt = 0;
	}
}

// Be sure to set the ISR routine to be an interrupt
void __attribute__((interrupt)) isr() {

    // We're running at a frequency of 10Hz, so the reading will
    // be the number of pulses in 0.1 seconds. Multiply
    // it by 10 to get the number of pulses in 1 second, which
    // is the number of Hz, of course.
    currentHz = counter.getAndResetCount() * 10;
    
    // And tell the chip we have handled the interrupt by
    // clearing the flag.
    clearIntFlag(_TIMER_4_IRQ);
}

// Be sure to set the ISR routine to be an interrupt
void __attribute__((interrupt)) isrDisplay() {

    //update the display digits
    updateDisp();
    
    // And tell the chip we have handled the interrupt by
    // clearing the flag.
    clearIntFlag(_TIMER_3_IRQ);
}

void setup() {
    // We want the maximum resolution we can get.
    // Like this we can measure 65535 pulses (it's a 16
    // bit timer), so up to 655350Hz with a 10Hz resolution
    // before it wraps round.

    //setup display I/O pins
    for(int i=0;i<6;i++)
    {
      pinMode(digitPins[i],OUTPUT);
    }
    for(int i=0;i<8;i++)
    {
      pinMode(segmentPins[i],OUTPUT);
    }
    
    // To measure higher frequencies we can either increase
    // the prescaler (and multiply the result accordingly)
    // or decrease the sample time.

    counter.setPrescaler(1);
    counter.setPeriod(0xFFFF);

    // The timer should get its clock source from the input pin
    counter.setClockSource(TIMER_IN);

    // Ensure the counter is empty at the beginning.
    counter.reset();

    // We'll run at a frequency of 10Hz. That's 100ms per tick.  We can't go
    // much slower than that, the chip is too fast.
    timer.setFrequency(10);

    // Link in out ISR routine. Note, it is a real ISR routine,
    // so requires all the right attributes etc.
    timer.attachInterrupt(isr);
    
    // Run display digit multiplexing timer
    display.setFrequency(500);
    
    //attatch IRSR routine
    display.attachInterrupt(isrDisplay);
    
    // Start the timer and counter running,
    timer.start();
    counter.start();
    display.start();

    Serial.begin(115200);
}

void loop() {
    // For now we'll just dump the current frequency to the
    // serial terminal.
    Serial.print(currentHz);
    Serial.println("Hz");
    delay(100);
}

majenko

Fri, 12 Jun 2015 17:40:13 +0000

You should ensure that the display ISR is running at a lower priority than the timer ISR.

You can specify the priority in the constructor for the Timer object:

// Create a new display timer object using T3 at priority 3
Timer3 display(3);
// Create a new timer object using T4 at priority 7 (maximum)
Timer4 timer(7);
// And a counter object using T1 - we don't use the interrupt so don't care what the priority is.
Timer1 counter;

C22 is quite a small capacitor, so the frequency limit it would impose would be reasonably high. It basically acts, with the resistor, as a low-pass filter.


citizen3942

Mon, 15 Jun 2015 07:33:11 +0000

I tried setting the priorities, but it still shows me about 30 Hz minus the set value. I have resolution at 5Hz - timer object ISR rate is at 5Hz.

Edit: Even when I disable the display ISR the frequency value shows on serial monitor 25-30Hz less than the set value. I wonder why is that so.

Edit2: Isn't it so that I need to add 5 missing counts + 0 byte to the counter value in order to correct the result? I don't want to believe it is the best way to do it.

currentHz = (counter.getAndResetCount()+6) * 5;

In the end I still want the counter to be at 1Hz precision. Maybe by implementing some sort of "shifting" frequecy counting algorithm or using timer overflows?


majenko

Mon, 15 Jun 2015 10:07:05 +0000

I can't see why it would be giving you the wrong result... The function that gets and resets the count is just this:

uint32_t Timer::getAndResetCount() {
    uint32_t t = *_tmr;
    *_tmr = 0;
    return t;
}

There is absolute minimal time between getting the current count and resetting it to 0 - not enough time for 5 counts at the kHz frequencies you're using. I guess that routine could be re-written using assembly to ensure that the load from *_tmr and the store to *_tmr are in adjacent instructions, but that means work...


citizen3942

Mon, 15 Jun 2015 13:47:10 +0000

I believe I figured out the reason.

uint32_t Timer::getAndResetCount() {
    uint32_t t = *_tmr;
    *_tmr = 0;
    return t;
}

As you can see the code above the counter starts counting from zero, hence the zero is actually also a pulse, which in the end result is not accounted for. Thus when the timer frame is 5Hz, every time the counter value is requested and resetted, one pulse is actually the 0-th pulse. So every time the counter value is represented it is represented as in COUNTS-1*[timeframe frequency] whereas it should be outputted as COUNT+1*[timeframe frequency]. The *_tmr value should start counting from 1 not 0. I believe this is a good explanation for that behaviour. And the explanation is understood mutually.


majenko

Mon, 15 Jun 2015 14:54:14 +0000

But count value "0" isn't a pulse count, it's an initial value before it gets a pulse. The first pulse to come in will increment that to 1, so one pulse in = a count of 1 in _tmr.