I'm trying to measure RPM using an int0 (highest priority).
The problem is: Every few seconds I get a "spike" at exactly double the frequency.
It seems to be more apparent at low frequencies (<10Hz).
The input circuit is as in picture below.
I'm using a proper signal generator and an oscilloscope... the input signal is solid and it's a 0-10V signal.
For code, I tried different options. Right now, I reduced it to the most simple logic, but the problem still persists:
Variables Used:
volatile unsigned long t;
unsigned long RPMtimePrev;
volatile unsigned long RPMperiod;
int RpmScale=100; //constant for scaling the input
float tempRPM;
I guess you did not use noInterrupts(), interrupts() around all accesses to RPMperiod.
RPMperiod is longer then a single byte, so you have to access it with interrupts disabled from main code.
To minimize the time interrupts are off, you could use something like (uncompiled, untested)
noInterrupts();
unsigned long lastPeriod = RPMperiod;
interrupts();
tempRPM=(float)100000/((float)lastPeriod /(float)RpmScale);
Ok, see attached zipped folder the whole code/project.
Here's a video of what I'm seeing:
I have configured 2 pin interrupts. One for RPM one for SPEED
They both read from the same frequency generator and both interrupts are configured exactly the same.
int1 seems to be get bad readings more often than int0, so I assumed it has to do with the priority of the interrupts.
Half way in the video, I connect int0 input wire to ground; only int1 receives the signal, but still bad readings.
Whandall:
I guess you did not use noInterrupts(), interrupts() around all accesses to RPMperiod.
RPMperiod is longer then a single byte, so you have to access it with interrupts disabled from main code.
To minimize the time interrupts are off, you could use something like (uncompiled, untested)
BTW are all these float casts sensible?
I tried the noInterrupts(), interrupts() and it doesn't fix it.
As far as casts go, I tried to do it in many different ways with same results. If you have a suggestion...I'll try it!
I’ll try using noInterrups again, even though ... wouldn’t ignoring an interrupt create a similar issue? The next calculated “period” would be twice as long (it would half my RPM/speed reading)
I think it’s inyeresting it always display exactly double the value, not some random value.
void setup()
{
...
attachInterrupt(digitalPinToInterrupt(3), MeasureRPM, RISING);
...
}
volatile unsigned long RPMt;
unsigned long RPMtimePrev;
volatile unsigned long RPMperiod;
void MeasureRPM()
{
RPMt=micros();
RPMperiod= RPMt-RPMtimePrev;
RPMtimePrev=RPMt;
}
void CalcRPM()
{
float tempRPM;
tempRPM=(float)10000/(RPMperiod/(float)RpmScale); //calculate a temporary RPM value
if ( tempRPM < 500.0 ) //Only consider realistic numbers. Ignore RPM readings that is too great.
{
NewRpm=tempRPM; //Valid RPM is passed downstream as the new RPM reading
}
intRPM=NewRpm;
}
void Display()
{
...
display.println(RPMperiod);
...
}
Because RPMperiod is shared with an interrupt service routine it must be volatile (which it is) and it must be protected with a critical section (which it is not). Post #2 has a method for doing that. RPMperiod has to be protected for ALL accesses outside of the interrupt service routine (CalcRPM and Display).
I have a small example that illustrates the atomic problem
(enclosing accesses to volatile variables of more than one byte in noInterrupt and interrupt).
void setup() {
Serial.begin(250000);
Serial.println(F("Wait for error...\r\n\r\nfrom - to - difference"));
// set up Timer 1
TCCR1A = 0; // normal operation
TCCR1B = bit(WGM12) | bit(CS10); // CTC, no pre-scaling
OCR1A = 7999; // compare A register value for 2 kHz
TIMSK1 = bit (OCIE1A); // interrupt on Compare A Match
}
volatile unsigned long Counter;
ISR(TIMER1_COMPA_vect)
{
Counter++;
}
void loop() {
static unsigned long before;
static unsigned long current;
// Counter is incremented with 2 kHz
// noInterrupts(); // uncomment to make it work
current = Counter;
// interrupts(); // uncomment to make it work
unsigned long difference = current - before;
// loop is so fast, you should never see a difference bigger than 1
if (difference > 1) {
Serial.print(before, HEX);
Serial.write(' ');
Serial.print(current, HEX);
Serial.print(F(" - "));
Serial.println(difference, HEX);
}
before = current;
}
I tried using the noInterrupts() while copying the RPMperiod to a temporary variable which then gets used in the rest of the program. In my code it doesn't make any difference; still having the random double frequency reading.
The fact the "noise" it's always exactly double my normal reading, regardless of input frequency, is really puzzling me. If it was caused by interrupts writing to the variable, shouldn't we expect more random numbers?
Both the RPM and SPEED are picked up from same source. Why is the problem more visible on the SPEED (int1) signal?
I moved the bare minimum circuit to a breadboard with no optocoupler. connected 0-5V signal, straight from signal generator to arduino, and still have the same issue.
I have the program spread across multiple sections, because it's part of a much bigger project. The RPM and SPEED signals is mere inputs to the bulk of the program. I stripped everything else to troubleshoot this issue.
One thing that seems to make things better is to over-drive the input (use more than 5v on the input) which is bad, but makes the noise appear a lot less often.
The more i look into this the more questions I have >:(
I continued stripping the program... when I removed the OLED display library and used the serial monitor to show the values, the problem finally went away!
At this point in time I'm sure that the <Adafruit_GFX.h> and/or <Adafruit_SSD1306.h> libraries for the OLED display are not "Interrupt friendly"; or at least not the way I'm using it.