having trouble understanding what's happening in arduino - flywheel-real time

Hello guys,

I have trouble understanding why I am not able to extract the data in real time using this code. I am not sure what is happening. I tried looking up the web, but I didn't find anyone who had this problem. Cattledog and jremington helped me to set up the sensor and everything with the code. Go to this link to see what we have done. here

So, coming to the problem

#define pii 3.1415926535



byte countsPerRevolution = 113;
float rpm_exponentialavg = 0, sincoeffavg = 0, coscoeffavg = 0, thirdOrder, freq;
float  rpm, Inst_speed, sinval, cosval, sincomp, coscomp;
volatile unsigned long countTeeth = 0;
volatile boolean finishCount = false;
volatile  long period = 0;
long copy_period;
volatile unsigned long time_last;


volatile unsigned long interruptCount;
unsigned long copy_interruptCount;


void setup()
{
  Serial.begin(115200);
  Serial.println("start...");
  attachInterrupt(digitalPinToInterrupt(3), pulsePeriod, RISING);//interrupt on pin3
}


void loop()
{

  while (1)//remove overhead of loop()
  {
    if (finishCount == true)
    {
      finishCount = false;//reset flag
      detachInterrupt(0);
      // disable interrupts, make protected copy of time values
      copy_period = period;
      copy_interruptCount = interruptCount;
      attachInterrupt(0, pulsePeriod, RISING);
    
      freq = 1000000.0 / copy_period;
      rpm = (freq * 60) / 113;                               // to calculate the rpm of the flywheel

      rpm_exponentialavg = rpm_exponentialavg * 0.99115044 + rpm * 0.00884956;          // running average of the rpm being calculated

     
      Inst_speed = rpm_exponentialavg - rpm;          // to calculate the instantaneous speed of the flywheel for every tooth
      
      sinval = sin(2 * pii * (float)countTeeth / 113.0);      // to calculate the sine of angle between the teeth in radians.
      
      cosval = cos(2 * pii * (float)countTeeth / 113.0);    // to calculate the cosine of angle between the teeth in radians.
      
      countTeeth++;                                  // increment the counter for every tooth passing.
      
      sincomp = sinval * Inst_speed;               // to calculate the sine component to perform fourier transformation
      
      coscomp = cosval * Inst_speed;           // to calculate the cosine component to perform  FT.

      // to calculate the running averages of sine and cosine, we get

      sincoeffavg = sincoeffavg * 0.99115044 + sincomp * 0.00884956;

      coscoeffavg = coscoeffavg * 0.99115044 + coscomp * 0.00884956;

      thirdOrder = 2 * sqrt(sincoeffavg * sincoeffavg + coscoeffavg * coscoeffavg) * (2 * pii / 60) * rpm_exponentialavg * 6 * pii / 60;


      //debug prints
     
      Serial.println(copy_period);
      Serial.print(";");
      Serial.print(copy_interruptCount);
    }
  }
}

void pulsePeriod()//simplify isr
{
  period = (micros() - time_last);
  time_last = micros();
  finishCount = true;
  interruptCount++;
}

When I print the interrupt count and pulse period. This is what I see.

Interrupt Count   pulse period
1			464
4			464
6			468
8			468
9			468
11			452
14			448
16			436
18			428
20			420
22			416
25			416
27			416
29			424
32			424
34			436
36			444
38			452
40			460
42			468
45			468
47			464
49			460
51			448
53			440
55			432
57			420
60			420
62			412
64			416
67			420
69			428

If you see from the results it is clear that I am not able to extract data of each pulse periods. There is some difference between each interrupt count. I want to understand why this is happening. I anticipated that I will be able to extract each data in real time. That's not the case. Assuming my frequency is varying between 2200 to 3500hz. Arduino should be capable of doing it.

Thanks in advance for your help.

I really want some suggestions from you guys on this.

Suggestions recieved so far:

  1. Cattledog suggested me to buy new microcontroller with high clock speed.

My first instinct would be to ditch the floating point calculations

I agree with @AWOL.

Your ISR just needs to record the value of micros() and the fact that the ISR has occurred. Then the code in loop() can calculate the number of µsecs between pulses. Whether it is necessary to convert that to RPM (or anything else) depends on the application. But get the simple µsec per pulse reading working first.

This may get you started. It compiles but I have not tested it.

byte pulsePin = 2;
unsigned long prevPrintMillis;
unsigned long printIntervalMillis = 1000;

unsigned long prevPulseMicros;
unsigned long newPulseMicros;
volatile unsigned long latestPulseMicros;
volatile boolean newPulse = false;
unsigned long microsBetweenPulses;



void setup() {
    Serial.begin(9600);
    Serial.println("Starting my program");

    pinMode(pulsePin, INPUT_PULLUP);
    attachInterrupt(0, pulseDetect, RISING);
}

void loop() {
    if (newPulse == true) {
        prevPulseMicros = newPulseMicros;
        noInterrupts();
            newPulseMicros = latestPulseMicros;
            newPulse = false;
        interrupts();
        microsBetweenPulses = newPulseMicros - prevPulseMicros;
    }
    if (millis() - prevPrintMillis >= printIntervalMillis) {
        prevPrintMillis += printIntervalMillis;
        Serial.print("Micros Between Pulses ");
        Serial.println(microsBetweenPulses);
    }
}

void pulseDetect() {
    latestPulseMicros = micros();
    newPulse = true;
}

...R

Robin2:
Your ISR just needs to record the value of micros() and the fact that the ISR has occurred. Then the code in loop() can calculate the number of µsecs between pulses. Whether it is necessary to convert that to RPM (or anything else) depends on the application. But get the simple µsec per pulse reading working first.

My problem is not interrupt not being fast enough or the precision of the measured period of the pulse not good enough.
As you can see from the above post that interrupt service does gets incremented but it's just data is coming so fast, So I am not able to pull all the data from the buffer to do some calculations.
Btw, I used array to store the incoming data, and then I used that data to do calculation in excel and I was able to get all the periods of continuous signal.

I appreciate for your help in providing the code. But, you are calculating for a fixed gate time of 1000ms. In my case I want to calculate each period of continuous signal, because I will be controlling clutch shifting in real time. The main problem in my application is frequency modulating that's why I need to measure period of each pulse rather than fixed gate time.

This is my hypothesis. I think arduino uses FIFO buffer. Since, the data coming in is so fast. That data overides the buffer or the data changes its position in buffer.

I hope you understand what I am saying. I am not sure if my hypothesis is correct? what do you think? If you think you have a better hypothesis I would really like to listen.

AWOL:
My first instinct would be to ditch the floating point calculations

I ditched all the floating point calculations. It didn't improve much. My interrupt is increments every 2 counts for each measurement.

Note: I didn't miss any pulses FYI, I just have some problem with extracting the data. However, If I missed the pulses the increment counter wouldn't increment in the first place.

You do not want to detach and then re-attach a pin change interrupt. Attach it and leave it, there is absolutely no reason to detach.

Please post your current code without floating point. Also, what is the maximum rpm of the flywheel?

You do not want to detach and then re-attach a pin change interrupt. Attach it and leave it, there is absolutely no reason to detach.

Correct

Why did you change the code from

      noInterrupts();
      copy_period = period;
      copy_interruptCount = interruptCount;
      interrupts();

EDIT:

This is my hypothesis. I think arduino uses FIFO buffer. Since, the data coming in is so fast. That data overides the buffer or the data changes its position in buffer.

There is no buffer. The pulse intervals are being calculated and they are not buffered or stored. You can not transfer the time data from the ISR, process it, and print it out in the time before the next pulse triggers the interrupt.

Even at 115200 baud each character you print takes abut 80uS.

Could that be eating too much time?

I've used the pulseIn() function for a similar job without problems.

Allan

My bad I was tinkering with the code. I just need a small clarification on noInterrupts() & detachInterupt() do they have similar purpose? Sorry, if I am dumb I am from mechanical backgorund lately starting to understand how arduino works.

This is the code without floating point calculations.

#define pii 3.1415926535

//volatile byte  count = 0;
//byte numCount = 1; //number of pulse intervals to measure
byte countsPerRevolution = 113;


long  freq;
long  rpm;


volatile unsigned long countTeeth = 0;
volatile boolean finishCount = false;
volatile unsigned long period = 0;
unsigned long copy_period;
volatile unsigned long time_last;


volatile unsigned long interruptCount;
unsigned long copy_interruptCount;


void setup()
{
  Serial.begin(115200);
  Serial.println("start...");
  attachInterrupt(digitalPinToInterrupt(3), pulsePeriod, FALLING);//interrupt on pin3
}

void loop()
{

  while (1)//remove overhead of loop()
  {
    if (finishCount == true)
    {
      finishCount = false;//reset flag
      // disable interrupts, make protected copy of time values
      noInterrupts();
      copy_period = period;
      copy_interruptCount = interruptCount;
      interrupts();

      freq = 1000000 / period;
      rpm = (freq * 60) / 113;                               // to calculate the rpm of the flywheel

 
      //debug prints
      Serial.print(rpm);
      Serial.print(" ");
      Serial.println(copy_interruptCount);
    }
  }
}

void pulsePeriod()//simplify isr
{
  period = (micros() - time_last);
  time_last = micros();
  finishPulse = true;
  interruptCount++;

I made some changes to the code and removed some lines. Which is not primary importance.

allanhurst:
Even at 115200 baud each character you print takes abut 80uS.

Could that be eating too much time?

I've used the pulseIn() function for a similar job without problems.

Allan

I used pulseIn() which didn't give me better readings. My readings were off by + or - 50hz.

avr_fred:
You do not want to detach and then re-attach a pin change interrupt. Attach it and leave it, there is absolutely no reason to detach.

Please post your current code without floating point. Also, what is the maximum rpm of the flywheel?

--> I changed the detach interrupt according to your suggestion. The maximum rpm of the fly wheel can be 1600RPM. Fly wheel has 113 teeth, so (1600/60) * 113 which gives teeth frequency of 3000 hz.

I really appreciate for providing solutions. But, at this moment I want to know why this is happening so I can do some literature research, because right know, I dont know where I should look into. A hint can be helpful.

meme:
My problem is not interrupt not being fast enough or the precision of the measured period of the pulse not good enough.

You have not provided some basic data ...

  • How fast is the wheel rotating?
  • How many pulses do you get per revolution?

If the interval between pulses is short you could have the ISR count to (say) 8 or 16 and only take the time then so that the interval is the interval between 8 (or 16) pulses. I suggest 8 or 16 because it makes the maths easy for the Arduino.

...R

Robin2:
You have not provided some basic data ...

  • How fast is the wheel rotating?
  • How many pulses do you get per revolution?

If the interval between pulses is short you could have the ISR count to (say) 8 or 16 and only take the time then so that the interval is the interval between 8 (or 16) pulses. I suggest 8 or 16 because it makes the maths easy for the Arduino.

...R

The flywheel can rotate between 1200RPM to 1600 RPM, it has 113 teeth on it. So, I get 113 pulses per rev, if you convert it into frequency which is about 2260 hz to 3000 Hz.

Since, my signal has frequency modulation --> the time period of each and every pulses(113) changes for each revolution. I wish it was simple. :confused: So, it is critical for me to get the data of all pulses. Assuming arduino clock speed of 16Mhz, it shouldn't be hard. I think we are missing something --> can't wait to find out what it is.

P.S
Dammit my professor doesn't even give me time to figure out the problem regarding this. He wants a resonable explanation. Since, I am from mechanical background, Whatever you guys suggest me I'll do it. And, I will try to understand it.

At 3000 pulses per second there will be about 330 microsecs between pulses which is not enough time to do any complex calculations.

The whole purpose of a flywheel is to eliminate fluctations in the rotational speed. I suspect it would be perfectly adequate to measure the time after every 16 pulses - about 5000 µsecs. That is still about 7 times per revolution. (Indeed measuring the time after every 32 pulses would probably be sufficient). I have a small DC motor (in a 00 Gauge model train) and I can control the speed at 12,000 RPM within a few percent with a single pulse per revolution using code like I posted earlier.

So my suggestion is to use the ISR to count pulses and every 16 pulses record the value of micros() and set the variable newPulseTime to true.

...R

AllanHurst nailed it for you. That 330 usec/pulse is not long enough to process your serial prints. The latest code you're using will print ~10 characters which will take more than 860 usec to print.

I'd suggest that to prove it you get rid of the printing and store the data in an array. Collect some readings (a hundred?), detach the interrupt and then print the results. Initially, just store the period in the array and avoid any calculations. I suspect that once the the slow serial stuff is gone there will be enough time for your integer math operations, but I'd leave it out initially to verify you can keep up.

wildbill:
Collect some readings (a hundred?), detach the interrupt and then print the results.

That is another way of doing things.

But it depends on what the OP wants to do with the data. I have been assuming he wants to control the speed and the printing is just for debugging purposes.

Perhaps he will be good enough to tell us.

...R

Or using a running averageand print say every 1/2 second

Allan.

Robin2:
At 3000 pulses per second there will be about 330 microsecs between pulses which is not enough time to do any complex calculations.

...R

That really makes sense to me . Okay, I will try to increase the interval to 16 or 32 pulses as you suggested and I will see how it goes.

Basically, you guys are saying that in real time it's hard to keep up.

wildbill:
AllanHurst nailed it for you. That 330 usec/pulse is not long enough to process your serial prints. The latest code you're using will print ~10 characters which will take more than 860 usec to print.

I'd suggest that to prove it you get rid of the printing and store the data in an array. Collect some readings (a hundred?), detach the interrupt and then print the results. Initially, just store the period in the array and avoid any calculations. I suspect that once the the slow serial stuff is gone there will be enough time for your integer math operations, but I'd leave it out initially to verify you can keep up.

This makes complete sense to me. Thank you for wonderful explanation --> @allanhurst and @wildbill. So, serial print is the culprit here. Even cattle dog suggested me that without serial print it should be okay.

Suggestion Bucket list:

  1. Buy arduino due which has 84mhz clock speed, there might be chance. --> do you think this might work? If my professor is being naive and selfish that he wants each period.
  2. Instead of doing it for every pulse do it for 16 or 32 pulses. --> if he is not being selfish.

Thanks guys.

But one more clarification what happens if I increase the baud rate does it effect?

If you can send the data in raw format instead of human readable form you can send the data much faster.

Also in your counting you may (will) get jitter from other interrupts - millis and Serial related at least. If it turns out to be a problem you may use Input Capture feature of Timer/Counter 1. I am not sure if there is some ready to use library for this but it is not so difficult with Datasheet's aid.

The only reason to use the serial printing is to confirm that you are not missing any interrupts and that your calculations make sense.

I would benchmark your desired calculations (remove floats if you can) to see if they, and the response to them(shifting?), can be done in the 300us you have.

You can confirm this only printing the difference in interrupt count, which should only be '1' if you zero the count each time it is read. Set the serial baud rate to 250000 in monitor and sketch for fastest output.

Once you gain confidence in the code, you can implement without the serial output, and perhaps add some error checking on the interrupt count.

I also think that tightening up the ISR with only one call to micros() and calculating period in loop as Robin2 suggests is good practice.

#define pii 3.1415926535
byte countsPerRevolution = 113;
float rpm_exponentialavg = 0, sincoeffavg = 0, coscoeffavg = 0, thirdOrder, freq;
float  rpm, Inst_speed, sinval, cosval, sincomp, coscomp;
volatile unsigned long countTeeth = 0;
volatile boolean finishCount = false;
//volatile long period = 0;
unsigned long period;
//long copy_period;
//volatile unsigned long time_last;
unsigned long time_last;

volatile unsigned long interruptTime;
unsigned long copy_interruptTime;

volatile unsigned long interruptCount;
unsigned long copy_interruptCount;

void setup()
{
  Serial.begin(250000);
  Serial.println("start...");
  attachInterrupt(digitalPinToInterrupt(3), pulsePeriod, RISING);//interrupt on pin3
}


void loop()
{

  while (1)//remove overhead of loop()
  {
    if (finishCount == true)
    {
      finishCount = false;//reset flag
      //detachInterrupt(0);
      // disable interrupts, make protected copy of time values
      noInterrupts();
      //copy_period = period;
      copy_interruptTime = interruptTime;
      copy_interruptCount = interruptCount;
      interruptCount = 0;
      //attachInterrupt(0, pulsePeriod, RISING);
      interrupts();
      period = (copy_interruptTime - time_last);
      time_last = copyInterruptTime;
      //freq = 1000000.0 / copy_period;
      freq = 1000000.0 / period;
      rpm = (freq * 60) / 113;                               // to calculate the rpm of the flywheel

      rpm_exponentialavg = rpm_exponentialavg * 0.99115044 + rpm * 0.00884956;          // running average of the rpm being calculated


      Inst_speed = rpm_exponentialavg - rpm;          // to calculate the instantaneous speed of the flywheel for every tooth

      sinval = sin(2 * pii * (float)countTeeth / 113.0);      // to calculate the sine of angle between the teeth in radians.

      cosval = cos(2 * pii * (float)countTeeth / 113.0);    // to calculate the cosine of angle between the teeth in radians.

      countTeeth++;                                  // increment the counter for every tooth passing.

      sincomp = sinval * Inst_speed;               // to calculate the sine component to perform fourier transformation

      coscomp = cosval * Inst_speed;           // to calculate the cosine component to perform  FT.

      // to calculate the running averages of sine and cosine, we get

      sincoeffavg = sincoeffavg * 0.99115044 + sincomp * 0.00884956;

      coscoeffavg = coscoeffavg * 0.99115044 + coscomp * 0.00884956;

      thirdOrder = 2 * sqrt(sincoeffavg * sincoeffavg + coscoeffavg * coscoeffavg) * (2 * pii / 60) * rpm_exponentialavg * 6 * pii / 60;


      //debug prints

      //Serial.println(copy_period);
      // Serial.print(";");
      Serial.println(copy_interruptCount);
    }
  }
}

void pulsePeriod()//simplify isr
 {
  interruptTime = micros();
 // period = (interruptTime - time_last);
 // time_last = interruptTime;
  finishCount = true;
  interruptCount++;
 }

meme:

  1. Buy arduino due which has 84mhz clock speed, there might be chance.. --> do you think this might work?

As you have never given a description of your project how can anyone answer that. There have been several suggestions that would each be valid in different circumstances.

...R

Robin2:
As you have never given a description of your project how can anyone answer that. There have been several suggestions that would each be valid in different circumstances.

...R

Well, clutch shifting(automatic) in real time with arduino.

This is just basic description. What is your definition of project description, what details you need from me regarding this.

Having done some research, I found out that. I need to optimize my code without floating point calculations or divisions, and also I read in few posts that sin() takes about 180 ~ 240 usecs. I am not sure if this is right? Really sin() consumes a lot of time. Because, I don't think there is any problem in serial print when I tested, without sin() and cos() functions in main loop(). My interrupt count was always 1 @ cattledog.

Note: all the benchmark results were done on 16Mhz. I gathered this information from the old threads.

Float divisions:- 34usec.
Float multiplication: 9usec.
Sin(): 180~240usec.
sqrt(): 41usec.