Motor PID control

I want to use PID to control motor speed. The motor I have has an opto-interrupter that gives six pulses per revolution. The problem I've been running into is how to sample the pulses. I've tried interrupts and the pulseIn function but didn't get great results. I want to use it as a tachometer calibrator, so the RPM needs to be stable and repeatable.

The interrupt method I used counted pulses for the duration it took to run the code. My issue with this is it seems like if the PID function took longer or shorter to calculate, the pulse count would be off because it would be accumulating longer.

I just wasn't able to get pulseIn to work. I'm a novice and new to this sort of thing.

What would you guys recommend for measuring the frequency of the motor? I want to run it up to 2000 RPM, so it should be able to measure 200 pulses per second. In the past I've seen some code that measured the time interval between pulses, would this be feasible for this application?

Sorry I can't post the code I was using for the interrupt method, it was straight out of a Magazine and I don't want to cause any copyright infringements. The code I was trying for the pulseIn function was straight from the PID playground page.
http://www.arduino.cc/playground/Code/PIDLibrary

Thanks for your help,

Here's a similar thread...

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1258765801/9#9

Read that and then post back here and we can discuss. These arduino-mechanical projects are interesting to me so I'll be more than happy to offer some help.

Allright, sorry it took so long. I looked at the code from the page you mentioned, and it looks good but I can't get it to work with the motor. I think the pulses are too close together for it to measure properly. At full speed, the input is about 1kHz with six pulses per rev, so 10000RPM (Input/6*60). The code I used, straight from the other site with corrections, is as follows.

// Program to count pulses per revolution in an automobile
// TODO: Includes a running average to insure proper RPM output
// TODO: Output a clean averaged 5v Sq wave of RPM
// NOTE: May need to go to (us) as opposed to (ms) for better resolution

boolean Cycle = false;                  // set to 0 for PulseStartTime and set to
                        //   1 for PulseEndTime
unsigned long PulseStartTime;   // Saves Start of pulse in ms
unsigned long PulseEndTime;     // Saves End of pulse in ms
unsigned long PulseTime;        // Stores dif between start and stop of pulse
unsigned long RPM = 0;          // RPM to ouptut (30*1000/PulseTime)

void setup()
{
 Serial.begin(9600);            // OPENS SERIAL PORT SETS DATA TO 9600 bps
 attachInterrupt(0, RPMPulse, RISING); // Attaches interrupt to Digi Pin 2
}

void RPMPulse() {
  if (Cycle)                // Check to see if start pulse
  {
    PulseStartTime = millis();  // stores start time
    Cycle = true;           // not defined.  Maybe this should be just "Cycle = true;" ??sets counter for start of pulse
  }
  else // otherwise it must be end pulse
  {
    detachInterrupt(0);         // Turns off inturrupt for calculations
    PulseEndTime = millis();    // stores end time
    Cycle = false;                  // resets counter for pulse cycle
    calcRPM();                  // call to calculate pulse time
  }
}
 


void calcRPM()
{
  PulseTime = PulseEndTime - PulseStartTime; // Gets pulse duration
 // Serial.print("PulseTime =");               // Output pulse time for debug
 // Serial.print(PulseTime);                   // Pulse debug output
//  Serial.println(" ");                        
  RPM = 30*1000/PulseTime*2;                 // Calculates RPM
  attachInterrupt(0, RPMPulse, RISING);      // re-attaches interrupt to Digi Pin 2
}

void loop()
{
 Serial.print("RPM = ");      // Output RPM for debug
 Serial.print(int(RPM));      // RPM debug output
 Serial.println(" ");
 delay(1000);                  // 1 sec delay for debug output
}

Is there any way to modify this code to read 1000Hz input with .1Hz resolution?

I've tried two other things, which I'll post in two separate replies next.

Steve

Next up, I tried using PID with Frequency Counter input. The PID code is from Arduino PID library, and Frequency Counter code is from Page has moved

The input works beautifully, it has the resolution is almost as high as I need and reads out to
±10RPM, but the gate time is too high so the PID doesn’t function correctly. The code is below.

// Input on D5
// Output on D6
// Frequency Counter Lib example and PID Lib example

// Problems with code: Frequency has enough resolution at ±10 RPM, but sample times are
// too slow for PID to function properly in this instance.  

#include <FreqCounter.h>
#include <PID_Beta6.h>

double Setpoint, Input, Output;
unsigned long frq;
unsigned long RPM;
int cnt;
PID pid(&Input, &Output, &Setpoint,.1,.1,0);


void setup() {

  Serial.begin(9600);        // connect to the serial port

 

  Serial.println("Frequency Counter");
  pid.SetSampleTime(1000); 
  pid.SetMode(AUTO);
  Output = 0; 

}



void loop() {
  Setpoint =analogRead(5)*.9766;

  FreqCounter::f_comp=106;
  FreqCounter::start(1000);
  
  while (FreqCounter::f_ready == 0) 
  Input = RPM;
  
  
  frq=FreqCounter::f_freq;
  RPM = frq*10;
  Serial.print("Input:");
  Serial.print(RPM);
  Serial.print("   Setpoint:");
  Serial.print(Setpoint);
  Serial.print("   Output:");
  Serial.println(Output);
  delay(200);

  if(abs(Setpoint-Input)>100)pid.SetTunings(1,.1,0);  //aggressive
  else pid.SetTunings(.05,.1,0); //comparatively moderate
  pid.Compute();
  delay(200);

  analogWrite(6,Output);

  
}

I think I’m going to ditch this idea, without re-writing lots of code I’m not going to be able to get the resolution I need at the speed I want.

Next up uses PID from the Arduino PID Library and tachometer code that I can’t find again to post the source. It uses a byte value that can’t go over 255. So far this code works the best of all of them, it actually controls the speed and the PID functions properly, but the resolution is poor at ±10Hz. On top of that, I can’t scale the values the way I’d like for some reason.

//Input on D2
//Output on D11

//Problems with code:  Not enough resolution, as is ±10Hz resolution which translates to ± 600 RPM
//If delay below TachoInHz is increased to get higher count and divide down for more resolution, 
//sample times are too slow for PID to control motor speed.  Also, counts can't go over 1000 or 
//else TachoInHz would have to be int or long and interrupts would have to be disabled.

 #include <PID_Beta6.h>

 double Input, Output, Setpoint;

 PID pid(&Input, &Output, &Setpoint, 3,4,1);  



int pinTachoInHz = 2; // D2 for Tacho Input - Measuring the Hz
byte TachoInHz = 0;  // declare this as a byte if the count is always less than 255
                        // so you don't have to disable interrupts when using in loop
long intTachoInHz_Millis = 0;


 void setup()
 {
   pid.SetSampleTime(50);

   pid.SetMode(AUTO);

  Serial.begin(9600);
  
  pinMode(pinTachoInHz, INPUT);
  digitalWrite(pinTachoInHz, HIGH);  // Turn on pullup resistor
  attachInterrupt(0, count, HIGH); // call count when pin 2 goes high

 }


 void loop()
 {


  Setpoint = analogRead(5); 
  
   TachoInHz= 0;
   delay(50); //wait to accumulate pulses
   byte tachoCount = TachoInHz;  // get a snapshot of the count
   float actualSpeed = tachoCount*10*.98; //scale actual speed to external measured frequency

   Serial.print("Input Speed:");
   Serial.print(int(actualSpeed)); 
   Serial.print("     Setpoint:");
   Serial.print(int(Setpoint));   
   Serial.print("     Output:");
   Serial.println(int(Output));


   //Set Tuning Parameters based on how close we are to setpoint
   if(abs(Setpoint-Input)>100)pid.SetTunings(.5,.5,0);  //aggressive
   else pid.SetTunings(.1,.1,0); //comparatively moderate


   Input = actualSpeed; //Setupt PID input for measured frequency input
   
   pid.Compute();

   analogWrite(11,Output);

 }
 
void count(){
   TachoInHz++;
}

I think what I need is code similar to the page Mitch posted,
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1258765801/9#9
where it disables the interrupt so it can use long values, but I can’t seem to get it to work. At this point, all I want is code that will measure a square wave in with a frequency resolution of .1Hz with a sample time around 100ms or less if possible, or at least as low as possible. 1s is too long.

Thanks for any help,
Steve

Hey, good to see some activity on this again…

First off, I’m confused. I don’t mean to seem picky but it’s important to clarify your problem statement.
Post #1: “2000 RPM, so it should be able to measure 200 pulses per second”
Reply #2: “input is about 1kHz with six pulses per rev, so 10000RPM (Input/6*60)” and “read 1000Hz input with .1Hz resolution”
Reply #3: “measure a square wave with a frequency resolution of .1Hz with a sample time around 100ms or less”

I understand you have a motor that will turn at 2000RPM max speed. There are 6 digital pulses per revolution.
Do you need to measure the rotational speed to an accuracy of +/- 0.1Hz?
What is the information about 1KHz and 10k RPM? This is contradictory to the 2k RPM originally stated.

The Arduino chip is likely capable. Here are some considerations…

The Arduino operates at 16 MHz. That’s only 62 nanoseconds between clock ticks.
Input signal is 2000RPM * 6 pulses/revolution = 200 pulses per second = 200 Hz
16 million / 200 = 80 thousand ← there are 80 thousand clock ticks between input pulses
An accuracy of 0.1Hz requires a real-time constraint of 0.1 / 200 * 80 thousand = 40 clock ticks resolution ← The code you write must respond to an interrupt within 40 clock ticks (but after I wrote the rest of my reply, I don’t think this is your real requirement).

This is rather tight. But you also said you want “sample time around 100ms”. I’m kind of wondering if what you mean is that you want to calculate the RPM every 100ms. (I’m figuring this out as I type and this interpretation sounds about right. Please confirm.)

In that case you have 16 MHz * 0.1 = 1.6 million clock ticks between RPM calculations. That’s lots of time to run instructions.

So you could do this with a couple of interrupts.
An input interrupt to capture the timestamp every time the shaft sensor fires.
A second interrupt to expire every 100ms to calculate the RPM based on the history of timestamps.
And a main loop to manage the goal speed and drive the PID control according to the actual RPM calculated in the interrupts.

I apologize, I forgot that I had posted that 2000RPM part. During the last week my requirements have changed. The first design was indeed at 2000 RPM, but now I'm trying to get to 10000 RPM. The motor still uses 6 pulses per revolution, but now just faster. So, a maximum input of 1kHz is required. Sorry about that, I should have posted that first.

What I mean by 100ms sample time is that the time it takes to calculate the frequency shouldn't be more than 100ms. In order to respond to the fast rotating motor the PID portion needs to run as many times a second as possible. I played with it for a while, and even at 250ms the results were undesirable. No matter the tuning values I fed into it the motor would "hunt" and would be unstable. Also, it needs to respond to varying loads quickly and correct accordingly, the faster the output adjusts the better.

The .1Hz part is for the final accuracy requirement. We were hoping for ±1RPM resolution, but 10 would probably suffice. .1Hz * 60 is 6RPM, so that should be good enough. Better would be nice, but we can live.

I think I can handle the input capture interrupt, but how would you go about an interrupt that expires every 100ms?

Thanks so much for the reply. I really was lost with the other examples, it seemed like there had to be a better way. I'll try to work on some code this week and post it for critiquing.

Steve

I see. So these actually are tighter requirements than I thought.
Still doable I think.

Re; an interrupt every 100ms:
If you look at the datasheet for the atmega168, timer1 has "Output Compare Units". "Clear Timer on Compare Match" mode (CTC mode) will be of interest. You can setup timer1 to count to a pre-defined value, and when it reaches that value generate an interrupt and restart itself at 0.
Register setup would look something like this...

TCCR1B = B01001011; // rising edges, CTC mode, prescaler = 64
OCR1A = 25000; // 25000 x 64 / 16 million -> interrupt every 100ms
TCNT1 = 0; // reset timer1
TIMSK1 = B00000010; // turn on Output Compare A Interrupt

Then the ISR has to be defined...

ISR(TIMER1_COMPA_vect) { 
 CallSteve1sFunctionToUpdateTheRPMEstimate();
}

And re; .1Hz accuracy. Over what sample population do you need this accuracy? Do you need this between each rotation pulse (every 60 degrees)? Or is that accuracy required over a couple of full revolutions?

Great input for the interrupt idea, I would have never found that on my own. It'll probably take a week for me to get back to coding with Christmas coming up, but hopefully I'll get something going pretty soon.

The .1Hz accuracy is fine if sampled over many revolutions. The only reason I stated that is so the final calculated RPM accuracy was as high as possible. Once I get the control part working we're going to add an LCD to display RPM and were hoping to be able to calibrate it to ±1 count.

Allright, I'm about to be exposed for the fraud that I am. I simply can't write code, so here's my attempt to understand your post. I know I coded the subroutine for the 100ms interrupt wrong, could you possibly point me in the right direction?

Here's the first bunch of code, just the two interrupts:

long PulseStart;
long PulseTotal;
long PulseTime;
int Count;
long RPM;
void setup()
{
  Serial.begin(9600); 

  TCCR1B = B01001011; // rising edges, CTC mode, prescaler = 64
  OCR1A = 25000; // 25000 x 64 / 16 million -> interrupt every 100ms
  TCNT1 = 0; // reset timer1
  TIMSK1 = B00000010; // turn on Output Compare A Interrupt
  
  attachInterrupt(0, TimeStamp, RISING); // Attaches Int0 to TimeStamp subroutine
  PulseStart=millis(); // Get first timestamp
  Count=0;  //start counter off at zero
}

void TimeStamp()
{
  PulseTime=(millis()-PulseStart); //Add time difference between current pulse and last pulse
  PulseStart=PulseTime; //Put current place in another variable to subtract out next time
  PulseTotal=PulseTime+PulseTotal; //Accumulate total time
  Count++; //Accumulate count to average RPM later

}

ISR(TIMER1_COMPA_vect) {
 RPM=(1/(PulseTotal/Count))*60; //Calculate RPM
 PulseTotal=0; //Reset time accumulator
 Count=0; //Reset count accumulator
}

void loop()
{
 Serial.print("RPM = ");      // Output RPM for debug
 Serial.print(int(RPM));      // RPM debug output
 Serial.print(" ");
 delay(1000);                  // 1 sec delay for debug output

}

I was honestly surprised that it compiled. Of course the output doesn't work, but that isn't unexpected. I started trying to debug it myself, so I stripped it down to the input interrupt.

long PulseStart;
long PulseTotal;
long PulseTime;
int Count;
long RPM;
void setup()
{
  Serial.begin(9600); 

  attachInterrupt(0, TimeStamp, RISING); // Attaches interrupt to Digi Pin 2
  PulseStart=millis();
  Count=0; 
}

void TimeStamp()
{
  PulseTime=millis();
  PulseTime=PulseTime-PulseStart; //Takes difference between current timestamp and last timestamp
  PulseStart=PulseTime;  //Puts current timestamp into temp variable to use next time
  PulseTotal=PulseTime+PulseTotal; //Accumulate total
  Count=Count+1;  //Accumulate count

}


void loop()
{
Serial.print("PulseTime:");
Serial.print(PulseTime);
Serial.print("  Pulse Total:");
Serial.println(PulseTotal);
delay(100);
}

This sort of works, the counter increments properly, the actual timestamp works properly, but the math is gibberish. It jumps around between a value that sort of makes sense and one that doesn't based on a high or low input. Also, the time between pulses always grows, so I've done some bad coding again. Like I said, this is a demonstration of my coding experience. I'll take any help I can get gratefully as I'm just about at the edge of my experience here...

I don't have any time to run this myself, but I did note that your calc in TimeStamp() is wrong...

PulseTime stores the time between pulses. PulseStart should store the running millis() timestamp of the last pulse.
But you assign it to the delta value... Instead I suggest the following (note I renamed one of your variables for better legibility).

void TimeStamp()
{
 TimeNow = millis();
 PulseTime = TimeNow - LastPulseTime;
 LastPulseTime = TimeNow();
 PulseTotal+=PulseTime;
 Count++;
}

And your RPM math is going to have problems. RPM is an integer and you're going to encounter rounding error the way you've written your equation. Also note that PulseTotal stores a time with units of milliseconds. You're mixing your units.

I think what you've laid out is generally a good idea though.
How about some more info on your project too. Is this some sort of Hard Drive speed control?

The project is for a Tachometer calibrator. We're a calibration lab and ours is starting to get very aged, made in the 70's. The only calibrators we could find are mostly avionics are in the many thousands. What we need is a motor that we can control and display the speed to mostly calibrate handheld tachometers, i.e.
http://www.shimpoinst.com/dt105a_dt107a.php
The optical tachometers are easy, we just use a LED strobe jig we built with a traceable frequency source as the input. The contact portion is much more difficult. We often have customers requesting data at certain points, which is why we were trying to get a controller involved instead of just an open-loop system where you could set it close enough and display the frequency.

Thanks :slight_smile: It's more fun (and easier) to help on these forums when you know what it's for.

I forgot... I was going to post this too:
http://www.arduino.cc/playground/Code/TimedAction

This could replace the ugly 100ms interrupt setup I suggested with a much cleaner implementation.

Having some trouble. Here’s the code I’m debugging. Don’t worry about the timed action just yet, I’m still working on proper pulse time calculation. I moved the values around as you suggested, and it does appear to work at slow speeds:

#include <TimedAction.h>
long TimeNow;
long LastPulseTime;
long PulseTime;
long TotalPulseTime;
int PulseCount;
long RPM;

TimedAction timedAction = TimedAction(100,CalculateRPM);

void setup()
{
  Serial.begin(9600); 

  attachInterrupt(0, TimeStamp, RISING); // Attaches interrupt to Digi Pin 2
  LastPulseTime=millis();
  PulseCount=0; 
}

void TimeStamp()
{
  TimeNow=millis();
  Serial.print("TimeNow:");
  Serial.print(TimeNow);
  PulseTime=TimeNow-LastPulseTime; //Takes difference between current timestamp and last timestamp
  Serial.print(" PulseTime:");
  Serial.print(PulseTime);
  LastPulseTime=TimeNow;  //Puts current timestamp into temp variable to use next time
  Serial.print(" LastPulseTime:");
  Serial.print(LastPulseTime);
  TotalPulseTime+=PulseTime; //Accumulate total
  Serial.print(" TotalPulseTime:");
  Serial.print(TotalPulseTime);
  PulseCount++;  //Accumulate count
  Serial.print( "PulseCount:");
  Serial.println(PulseCount);
  
}

void loop()
{
  timedAction.check();
}
 
void CalculateRPM(){

 // PulseCount=0;
 // TotalPulseTime=0;

}

The problem is this. At high speeds, the time stamp stamps the same value. Here is the output:

TimeNow:27777 PulseTime:907 LastPulseTime:27777 TotalPulseTime:27776PulseCount:229
TimeNow:28528 PulseTime:751 LastPulseTime:28528 TotalPulseTime:28527PulseCount:230
TimeNow:28946 PulseTime:418 LastPulseTime:28946 TotalPulseTime:28945PulseCount:231
TimeNow:29553 PulseTime:607 LastPulseTime:29553 TotalPulseTime:29552PulseCount:232
TimeNow:30371 PulseTime:818 LastPulseTime:30371 TotalPulseTime:30370PulseCount:233
TimeNow:30568 PulseTime:197 LastPulseTime:30568 TotalPulseTime:30567PulseCount:234
TimeNow:30755 PulseTime:187 LastPulseTime:30755 TotalPulseTime:30754PulseCount:235
TimeNow:31419 PulseTime:664 LastPulseTime:31419 TotalPulseTime:31418PulseCount:236
TimeNow:31560 PulseTime:141 LastPulseTime:31560 TotalPulseTime:31559PulseCount:237
TimeNow:32209 PulseTime:649 LastPulseTime:32209 TotalPulseTime:32208PulseCount:238
TimeNow:32280 PulseTime:71 LastPulseTime:32280 TotalPulseTime:32279PulseCount:239
TimeNow:32294 PulseTime:14 LastPulseTime:32294 TotalPulseTime:32293PulseCount:240
TimeNow:32942 PulseTime:648 LastPulseTime:32942 TotalPulseTime:32941PulseCount:241
TimeNow:33078 PulseTime:136 LastPulseTime:33078 TotalPulseTime:33077PulseCount:242
TimeNow:33187 PulseTime:109 LastPulseTime:33187 TotalPulseTime:33186PulseCount:243
TimeNow:33227 PulseTime:40 LastPulseTime:33227 TotalPulseTime:33226PulseCount:244
TimeNow:33785 PulseTime:558 LastPulseTime:33785 TotalPulseTime:33784PulseCount:245
TimeNow:33881 PulseTime:96 LastPulseTime:33881 TotalPulseTime:33880PulseCount:246
TimeNow:34420 PulseTime:539 LastPulseTime:34420 TotalPulseTime:34419PulseCount:247
TimeNow:34525 PulseTime:105 LastPulseTime:34525 TotalPulseTime:34524PulseCount:248
TimeNow:35949 PulseTime:1424 LastPulseTime:35949 TotalPulseTime:35948PulseCount:249
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:250
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:251
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:252
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:253
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:254
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:255
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:256
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:257
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:258
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:259
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:260
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:261
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:262
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:263
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:264
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:265
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:266
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:267
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:268
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:269
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:270
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:271
TimeNow:35949 PulseTime:0 LastPulseTime:35949 TotalPulseTime:35948PulseCount:272

The top part is being rotated by hand, and it seems to work properly. When I put power on the motor, the TimeNow value freezes and just prints the same one. I figured this is probably because of all the Serial printing done in the interrupt loop. So, I took it all out and put it in the TimedAction loop so it would only print ever 100ms

#include <TimedAction.h>
long TimeNow;
long LastPulseTime;
long PulseTime;
long TotalPulseTime;
int PulseCount;
long RPM;

TimedAction timedAction = TimedAction(100,CalculateRPM);

void setup()
{
  Serial.begin(9600); 

  attachInterrupt(0, TimeStamp, RISING); // Attaches interrupt to Digi Pin 2
  LastPulseTime=millis();
  PulseCount=0; 
}

void TimeStamp()
{
  TimeNow=millis();
  PulseTime=TimeNow-LastPulseTime; //Takes difference between current timestamp and last timestamp
  LastPulseTime=TimeNow;  //Puts current timestamp into temp variable to use next time
  TotalPulseTime+=PulseTime; //Accumulate total
  PulseCount++;  //Accumulate count
}

void loop()
{
  timedAction.check();
}
 
void CalculateRPM(){
  Serial.print("TotalPulseTime:");
  Serial.print(TotalPulseTime);
  Serial.print(" PulseCount:");
  Serial.println(PulseCount);
  PulseCount=0;
  TotalPulseTime=0;


}

Now here’s the output with the motor running at about 6kRPM

TotalPulseTime:82 PulseCount:53
TotalPulseTime:81 PulseCount:56
TotalPulseTime:80 PulseCount:58
TotalPulseTime:82 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:80 PulseCount:58
TotalPulseTime:82 PulseCount:55
TotalPulseTime:82 PulseCount:51
TotalPulseTime:80 PulseCount:49
TotalPulseTime:79 PulseCount:49
TotalPulseTime:82 PulseCount:52
TotalPulseTime:82 PulseCount:57
TotalPulseTime:81 PulseCount:59
TotalPulseTime:82 PulseCount:59
TotalPulseTime:81 PulseCount:59
TotalPulseTime:82 PulseCount:55
TotalPulseTime:81 PulseCount:50
TotalPulseTime:83 PulseCount:50
TotalPulseTime:81 PulseCount:50
TotalPulseTime:80 PulseCount:54
TotalPulseTime:81 PulseCount:58
TotalPulseTime:82 PulseCount:59
TotalPulseTime:79 PulseCount:59
TotalPulseTime:82 PulseCount:57
TotalPulseTime:81 PulseCount:52
TotalPulseTime:81 PulseCount:49
TotalPulseTime:82 PulseCount:49
TotalPulseTime:81 PulseCount:51
TotalPulseTime:80 PulseCount:54
TotalPulseTime:81 PulseCount:58
TotalPulseTime:81 PulseCount:59
TotalPulseTime:81 PulseCount:59
TotalPulseTime:81 PulseCount:60
TotalPulseTime:81 PulseCount:61
TotalPulseTime:80 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:80 PulseCount:59
TotalPulseTime:80 PulseCount:58
TotalPulseTime:82 PulseCount:54
TotalPulseTime:80 PulseCount:51
TotalPulseTime:81 PulseCount:51
TotalPulseTime:80 PulseCount:49
TotalPulseTime:81 PulseCount:49
TotalPulseTime:81 PulseCount:49
TotalPulseTime:80 PulseCount:54
TotalPulseTime:83 PulseCount:60
TotalPulseTime:81 PulseCount:60
TotalPulseTime:81 PulseCount:59
TotalPulseTime:82 PulseCount:60
TotalPulseTime:80 PulseCount:60
TotalPulseTime:80 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:80 PulseCount:59
TotalPulseTime:80 PulseCount:59
TotalPulseTime:82 PulseCount:55
TotalPulseTime:80 PulseCount:52
TotalPulseTime:81 PulseCount:52
TotalPulseTime:81 PulseCount:51
TotalPulseTime:81 PulseCount:50
TotalPulseTime:82 PulseCount:51
TotalPulseTime:82 PulseCount:52
TotalPulseTime:81 PulseCount:57
TotalPulseTime:80 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:80 PulseCount:59
TotalPulseTime:81 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:81 PulseCount:59
TotalPulseTime:81 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:81 PulseCount:60
TotalPulseTime:81 PulseCount:59
TotalPulseTime:82 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:81 PulseCount:60
TotalPulseTime:81 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:80 PulseCount:59
TotalPulseTime:81 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:81 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:80 PulseCount:59
TotalPulseTime:81 PulseCount:60
TotalPulseTime:81 PulseCount:60
TotalPulseTime:80 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:82 PulseCount:60
TotalPulseTime:80 PulseCount:59
TotalPulseTime:82 PulseCount:60

I was loading it down with my fingers, you can see the pulse count drop down to 51 from about 60, but total pulse time stays the same

Brain fart. The total pulse time would be the same over the same interval, just more pulses per second. Duh. So it may be working. Doesn't look like I'm going to get the resolution I need, maybe I'll try micros()

Allright, we’re getting somewhere.

#include <TimedAction.h>
long TimeNow;
long LastPulseTime;
long PulseTime;
long TotalPulseTime;
int PulseCount;
long uSPerPulse;

TimedAction timedAction = TimedAction(100,CalculateRPM); // Set up a timed action class that occurs every 100mS
void setup()
{
  Serial.begin(9600); // Set up serial communication

  attachInterrupt(0, TimeStamp, RISING); // Attaches interrupt to Digi Pin 2
  LastPulseTime=micros(); // Give LastPulseTime a value
  PulseCount=0; // Initialize PulseCount
}

void TimeStamp()
{
  TimeNow=micros(); // Gets current time stamp in microseconds 
  PulseTime=TimeNow-LastPulseTime; // Takes difference between current timestamp and last timestamp
  LastPulseTime=TimeNow;  //Sets current timestamp to last timestamp to be used next time around for difference
  TotalPulseTime+=PulseTime; //Accumulate total
  PulseCount++;  //Accumulate count
}

void loop()
{
  timedAction.check();
}
 
void CalculateRPM(){
  uSPerPulse=(TotalPulseTime/PulseCount);
  Serial.print(" TotalPulseTime:");
  Serial.print(TotalPulseTime);
  Serial.print(" PulseCount:");
  Serial.print(PulseCount);
  Serial.print(" Divided:");
  Serial.println(uSPerPulse);
  PulseCount=0;
  TotalPulseTime=0;

}

This works like a DREAM. It has 1uS resolution and reads accurately as high as I could test it (11kRPM). However, I can’t figure how to calculate RPM without screwing up the math. I believe it should be RPM = (1/ ((TotalPulseTime*1000000)/PulseCount))*60, but this causes some major overflow problems or needs more bits or something, because the result is garble. As I said, I’m no programmer, does it show? Any ideas for how to do the RPM calculation?

If RPM = (1/ ((TotalPulseTime*1000000)/PulseCount))*60,

move the 60 inside the parens:

RPM = 60/(TotalPulseTime*1000000)/PulseCount)

or

RPM = (60 * PulseCount) / (TotalPulseTime * 1000000)

Cancel a 10 top and bottom:

RPM = (6 * PulseCount) / (TotalPulseTime * 100000)

Depending on how large TotalPulseTime gets to be, a long may or may not be large enough to avoid overflow.

While PualS’s tips are good, recognize in your code you are checking the RPM every 100 ms.
You stated in your first post you get 6 pulses per revolution.
Therefore every 100 ms the number of revolutions can be calculated…
myRevs_per100ms = PulseCount / 6
Note this is not an integer result.

Then RPM directly is [with units]…
myRPM = myRevs_per100ms * 600 [deciseconds per minute]

push it all together and simplify…

RPM = PulseCount * 100;

Note this is an integer result. I’m sure you’re smiling at how simple that equation looks :slight_smile:

I had a second look at the TimedAction library. It isn't what I thought it was. It simply relies on polling the millis() function in the main loop and does not rely on an interrupt to trigger an event (in your case CalculateRPM()). I thought I'd seen a library that better packaged an output compare interrupt but I can't find it, and TimedAction is not it.

Sorry for the mistake. As a result, when you start putting more meat into your main loop you may find that CalculateRPM() does not run as regularly as you want/need. You may then need to resort back to the original interrupt setup I recommended.

Glad you're making some headway. When do I get to start doing some PID stuff here? :slight_smile: :slight_smile: