Measuring the Frequency of a TTL circuit

I am working on a related project right now and using the approach of user Graynomad. I have a single encoder signal and will use it to clock TIMER1. Next at a regular interval I can find the count of TIMER1 and know how many pulses have occurred. I like this approach because the timer does not interrupt the cpu.

However, I think you need to consider what else you will be doing with the Arduino board and what conflicts you will have. For example, I think TIMER1 is used for Arduino PWM (analog) out. I think TIMER0 is used for the millis() function. Being a beginner myself you should confirm this information elsewhere as I could be wrong.

I think your motor is at 1320 RPM for 11kHz out. Also, you will not hit 20kHz if your max speed is 1700RPM. ..maybe you are being conservative in your request to reach 20kHz max.

Do you know what the IC chip is doing? A quadrature encoder would have two signals coming out of it (from what I understand of course) so if the IC chip is also putting out two signals... what is it doing? Maybe it is a Schmitt trigger? Is it changing the logic levels?

How often do you need to get the RPM data? The CD4060 can divide the tach signal by as much as 16K in a single chip. Plus, by accumulating multiple pulses, it would remove most of the jitter you'll see if you measure every pulse.

The downside is that there could be an unacceptably long time between the prescaled pulses when the motor is spinning slowly.

We are using a quadrature encoder, however our IC chip takes the A/B Pulses and outputs on two pins their combined pulse. (ex. Pin 1 is putting out an 11khz pulse telling me that the motor is rotating at about 1300 RPMs in forward motion, while pin2 is kept in a low state. Vice versa for when the motor rotates backwards, causing pin 1 to go low and pin 2 to start pulsing) I would go with the Idea from Graynomad, save I do need PWM for driving said motor. But thanks for the ideas so far!
I was thinking about using this library: Arduino Playground - MsTimer2 And set a sample in the "Loop" portion of the program so that it is continously sampling and every time there is a change in state from low to high (thus eliminating the chance of sampling the same pulse twice) I could increment a counter. Then every 1 second, it outputs the count to the screen. If this sparks any ideas, let me know.

What is the range of the input frequency and the required display update rate. If it's 0-11k then using a prescaler could be a problem at low freqs as Ran said, unless you only have to update a display (or use the info in whatever way) every few seconds.


Rob

Just reread your last post

Vice versa for when the motor rotates backwards, causing pin 1 to go low and pin 2 to start pulsing

Are you saying that this chip swaps the function of the two pins according to the direction :astonished:

If so I don't see any easy way to detect this as you have to differentiate between a frequency and a DC level, and at slow speeds especialy there's no real difference.

Can you post a link to the decoder chip spec sheet?


Rob

Thanks for the replies, I will start digging for the spec sheet on Monday. In the mean time the decoder chip converts the AB signals, to a speed pulse on pin 1 for forward and if the motor reverses, it stops sending pulses on pin 1 and starts sending pulses on pin 2. Which should make it easier to sample the incoming data especially while I'm just trying to determine the RPM in one direction (for now :wink: )

EDIT

So in forward pin one (TTL)pulses and pin 2 is off. Then in reverse pin one turns off and pin 2 starts a TTL pulse at a proportional value to RPMs. Oh and we are using the Arduino MEGA.

So in forward pin one (TTL)pulses and pin 2 is off. Then in reverse pin one turns off and pin 2 starts a TTL pulse at a proportional value to RPMs.

IMO then that is a seriously bad chip, a pulse pin and direction pin would be better, but anyway,

To do this I think you have to

a) OR the two pins into a single interrupt (using an AND gate or two diodes) and run one of the pins to a digital input. Thus when you get an interrupt you test the input to see which pin was the culprit.
b) use two interrupts, one ISR sets a "direction" global, the other resets it.

Option b) is simpler and doesn't need any external components, but a) is better if you only have a single interrupt free.

NOTE: this is just for direction, it doesn't handle the counting.


Rob

Here is an attempt at coding, I'm looking for any thoughts for improvement and more importantly for thoughts of implementation.
Thanks ahead of time:
#include <MsTimer2.h>

//Output the value of count to the serial monitor

int buttonstate = 0;
long int counter = 0;
void flash()
{
Serial.println(counter, DEC);
counter = 0;
}

void setup()
{
pinMode(9, INPUT);
MsTimer2::start();
}

void loop()
{
buttonstate = digitalRead(9);
if(buttonstate == LOW)
{
buttonstate = digitalRead(9);
if(buttonstate == HIGH)
{
counter++;
}
}
}

So in this example you are waiting for a button to be pressed, then counting when it's released.

I think this has problems, you're not waiting for the button to be released, simply testing once straight after it was pressed when of course it will still be LOW.

  if(LOW == digitalRead(9))  {
    while (LOW == digitalRead(9)) {}; // wait for button to be released
    counter++;
  }

This assumes the input is clean and doesn't need debouncing. If you are using a button for initial testing (as the pin name implies) then you will need to debounce it.


Rob

I apologize for the naming, but I actually wanted to measure each pulse from the IC chip, so it should be a relatively clean TTL signal not really requiring debouncing (I hope). My biggest worry is that I will measure a "high" signal twice thus incrementing my timer twice for the same signal. So I guess you could sum up my entire problem as finding a way to measure the digital frequency with an arduino.
Any ideas for the coding would be great!

I have re-written the code and so let me know what you think-

#include <MsTimer2.h>

// Switch on LED on pin 13 each second

int signalstate = 0;
int laststate = 0;
long int counter = 0;
void flash() {
  Serial.println(counter, DEC);
  counter = 0;
}

void setup() 
{
  pinMode(9, INPUT);
  MsTimer2::start();
}
//each time through the loop it does a read on the digital port,
//nothing happens till the pin goes "LOW" then each time through
// it checks to see if the state has changed, if so, then and
//only then does it increment the counter.
void loop() 
{
  signalstate = digitalRead(9);
  if(signalstate == LOW)
  {
    laststate = 1;
  }
  if(laststate == 1 && signalstate == HIGH)
  {
    counter++;
    laststate = 0;
  }
  
}

So I re-wrote the program again due to my class using the Arduino Mega and the Mstimer2 not working with it. So here is the new revision, which when outputting to the screen at 1/10th of a second, is fairly accurate, but when going above that, it quickly loses accuracy, any ideas why?

#include <SimpleTimer.h>
SimpleTimer timer;


int signalstate = 0;
int laststate = 0;
long int counter = 0;
long int screen = 0;
void repeatMe() 
{
  long int tempequ = ((counter / 5) * 6);
  screen = tempequ;
  Serial.println(screen, DEC);
  counter = 0;
}
 
void setup() 
{
  pinMode(9, INPUT);
  Serial.begin(9600);
  timer.setInterval(100, repeatMe);
}
//each time through the loop it does a read on the digital port,
//nothing happens till the pin goes "LOW" then each time through
// it checks to see if the state has changed, if so, then and
//only then does it increment the counter.
void loop() 
{
  timer.run();
  signalstate = digitalRead(9);
  if(signalstate == LOW)
  {
    laststate = 1;
  }
  if(laststate == 1 && signalstate == HIGH)
  {
    counter++;
    laststate = 0;
  }
  
}

1/10th of a second, is fairly accurate, but when going above that, it quickly loses accuracy, any ideas why?

By that do you mean of the timer has a lower value and is therefore triggered at a faster rate?

If so it's possible that all the overhead in the code and the Serial.print becomes more significant at shorter periods. Try upping the bit rate to 115200 and see if that makes a difference.


Rob

Here is freq counter we use for DCDW, its speed is determined by the incoming freq and the number of digits of precision you specify. It also has code to reject noisy signals. Probably slower than you want and too many features, but you might find the autoscaling approach useful. There is a fundamental limit on precision vs measurement rate and this is one way to approach it.

long countFrequency (int pin, int precision)  
{
// counts frequency of an incoming pulse train
// this code is licensed under Creative Commons Attribution-ShareAlike
//
// pin              any digital input pin (int)
// precision        number of significant digits, 2, 3 or 4 (int)
// returns:         frequency in Hz (long)
//
// written for Dirt Cheep Dumb Wireless sensor telemetry boards
// using OOK radio links (SparkFun etc) which output noise on no RF in
// so this looks for consistent pulse width as criterion against noise
// returns negative numbers for all errors such as noise
//
  int pulses;                   // total number of pulses to time 
  int pulse;                    // pulse count
  unsigned long timeout= 10000; // microsecs for pulseIn() to wait before giving up
  unsigned long t0;             // start time hack
  unsigned long time;           // delay in millisec
  unsigned long needTime = 1;   // delay needed for precision in millisec
  unsigned long duration;       // length of one pulse 
  unsigned long totDuration;    // total duration of first ten pulses 
  unsigned long minDuration;    // minimum length of a valid pulse 
  unsigned long maxDuration;    // maximum length of a valid pulse 
  constrain (precision, 1, 5);                 // keepin it sane
  for (int i = 1; i < precision; i++) {
    needTime = needTime * 10;
  }  // millisecs of tone count needed to get desired precision
  long DCDWfrequency = 0;                      // nothin' yet
  totDuration = 0;                             // clear this to start
  for(pulse = 0; pulse < 10; pulse += 1)       // count 10 incoming pulses 
  {                                   
    duration = pulseIn(pin, LOW, timeout);     // wait for one complete pulse cycle
    if (duration == 0) {
      return -(long)pulse;
    }  // if pulse timout then abort, return neg of bad pulse
    totDuration += duration;
  } 
  maxDuration = totDuration / 7;
  minDuration = totDuration / 14;
  // now we have a range of average pulse durations
  // so start counting 'pulses' pulses, increasing 'pulses' by 10x each time
  // until a string of pulses meets the minimum total time for accuracy
  for (pulses = 10; pulses < 100000; pulses = pulses * 10)
  {
    DCDWfrequency = 0;                           // nothin' yet
    t0 = millis();                               // start time
    for(pulse = 0; pulse < pulses; pulse += 1)   // count incoming pulses 
    {                                   
      duration = pulseIn(pin, LOW, timeout);     // wait for one complete pulse cycle
      if (duration == 0) {
        return -(long)pulse;
      }    // pulse timout: abort, return neg of bad pulse
      if (duration > maxDuration)                  // pulse too long: abort, return error code
      {
        return -(long)duration;
      }
      if (duration < minDuration) 
      {
        return -(long)duration;
      }
    } 
    time = millis()-t0;                          // got all pulses, so time = now - start

    if (time > needTime)                              // if time is enough for precision we are done
    {
      if (debug)
      {
        Serial.print(precision);  
        Serial.print(" digits ");  
        Serial.print(needTime);  
        Serial.println(" ms need:   ");  
        Serial.print(pulses);  
        Serial.print(" pulses = ");  
        Serial.print(time);  
        Serial.println(" millisecs ");  
      }
      DCDWfrequency = 1000 * (unsigned long)pulses / time;  // frequency in Hz
      return (long)DCDWfrequency; 
    }
  }
  return -9999;
}

Thank you for your post, and we will probably give it a try if we can't get the freqcounter.h library to work, which is currently having problems, because it won't work with the arduino mega 2560. (Keeps saying sbi and cbi are not defined, which I don't understand) I'm pulling most of what I'm doing from this forum: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1231326297/15
Anyone with ideas on how to fix the freqcounter.h for the mega 2560, that would be amazing! I've tried entering the 2560 to the #define section, but no luck. Please help! Thanks ahead of time! :slight_smile:

AltairLabs:
Here is freq counter we use for DCDW, its speed is determined by the incoming freq and the number of digits of precision you specify. It also has code to reject noisy signals. Probably slower than you want and too many features, but you might find the autoscaling approach useful. There is a fundamental limit on precision vs measurement rate and this is one way to approach it.

That code looks interesting but it's not Arduino ready is it, no setup, loop, Serial.begin(), etc.

I tried adding stuff to try and run it, but gave up after two or three fixes. Compilers are so finicky. :smiley:

Lefty

Also, does anyone know off the top of their head, what the precision and maximum measurable frequency is for the Arduino? (using freqCounter.h)
Thanks again for all the great posts, keep them coming! (Oh after we finish this project, I plan on posting everything learned for others so they don't have to struggle like we are! :stuck_out_tongue: )

(Keeps saying sbi and cbi are not defined, which I don't understand)

That can be fixed with a define or or some inline assembler. SBI/CBI are single assembler instructions, I didn't look at the code but you can replace all occurances that are giving you trouble with

asm ("sbi");

and

asm ("cbi");

EDIT: Are we talking about the code a couple of posts up? There is no tCBI/SBI in that.


Rob

No the code above was a sample attempt at programming, however, we have finally achieved success! (With getting RPM) Using the extra Interrupt on the Arduino Mega, it was much easier.
So the way it works is by attaching the encoder's output (either A or B or the Index) to pin 18, then attaching the interrupt to interrupt 5
which is only triggered when pin 18 goes from low to high.
It then calls the incrementit function that does just that, increments the counter.
Then after 10, or 100 ms the count is tallied.
(Thus far tested to 17Khz or 1900+ RPM)

#include <MsTimer2.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(52, 50, 48, 46, 44, 42);

volatile int count = 0;
double out = 0.0;
double motorout = 0.0;
double kp = 3.0;
double setpoint = 1450.0;

void setup()
{
  lcd.begin(16, 2);
  pinMode(12, OUTPUT);
  Serial.begin(115200);   // start serial communication
  attachInterrupt(5, incrementit, RISING);  //attaches interrupt to the mega's pin 18 (interrupt 5)
  MsTimer2::set(100, flash); // 100ms period
  MsTimer2::start();
}



void loop()
{ 
//  out = (1450 - out);
//  lcd.print(out);
//  delay(500);
//  lcd.clear();
}

void incrementit()
{
  count++;
}
void flash() 
{
  lcd.clear();
  out = count;
  out = (setpoint - out);
  lcd.print(out);
  
  motorout = (setpoint+kp*out)*(5735.0/10000.0) - 661;
  motorout = constrain(motorout, 0, 255);
  Serial.println(motorout,DEC);
  analogWrite(12, motorout);
  //Serial.println(count,DEC);
  count = 0;
}