I've been working with an infrared LED/receiver to measure RPM on a nano V3. The IR LED/Receiver simply supplies input to an LM393 opamp that triggers pin D2 each time a revolution is complete. For testing purposes I'm using a 80mm fan which operates between 2200 - 3100 RPM (depending on input voltage 11-12.8V).
I've used this same setup with TI MSP boards and Pi boards and I've not experienced any spurious interrupts being triggered. However, when using D2 as the interrupt pin, I'm receiving roughly 3 spurious interrupts between each valid interrupt and I'm not sure what the cause is. I don't know if there may be a timer conflict or the like that is causing the issue on the nano.
I'm using micros() once at the beginning of the interrupt handler to obtain a timestamp and the time between interrupts to determined the RPM (e.g. 1e7 / period). Normally the time period between interrupts ~27000 microseconds, which translates into ~2200 RPM. The problem is that between valid time periods, additional interrupts are triggered at periods like 56 microseconds or 2300 microseconds. (over 1,000,000 RPM and 25,600 RPM, respectively). I'm trying to track down how those are triggered and why.
I can smooth them out by filtering, but that just adds to the code required in the interrupt handler which I'd like to eliminate.
The code essentially has the interrupt triggered on pin D2 and then computes the RPM within the handler. The body of the code then has a 100 ms timer that updates the RPM and max RPM values shown on the terminal. The code is the following:
#ifdef __cplusplus
extern "C"
{
#include "numeric-str-cnv.h"
}
#endif
#define USESERIAL 1
#define SERIALBAUD 115200 /**< baud for Linux terminal in minicom */
#define PIN_IRSNSR 2 /**< only D2, D3 support interrupts on nano */
#define PCTCHGMAX 70 /**< max percent change revolution time */
#ifdef USERPMMS
#define RT_100MS 100 /**< 100 ms expressed in us */
#define MILLISECRPM 6e4 /**< microseconds per RPM */
#define REVMINMS 10 /**< min ms in rev (6000 RPM) */
#define REVMAXMS 1000 /**< min ms in rev ( 1 RPM) */
#else
#define RT_100MS 1e5 /**< 100 ms expressed in us */
#define MICROSECRPM 6e7 /**< microseconds per RPM */
#define REVMINUS 10000 /**< min usec in rev (6000 RPM) */
#define REVMAXUS 1e6 /**< min usec in rev ( 1 RPM) */
#endif
static volatile unsigned long us_start = 0, /**< last rising edge */
us_end = 0, /**< current rising edge */
us_rev = 0, /**< usec difference */
us_rev_last = 0; /**< last valid rev */
static volatile uint16_t rpm = 0, /**< RPM measured */
rpm_max = 0; /**< maximum RPM */
/**
* @brief percentage change between last value (vold) and current (vnew) used
* to eliminate spurious interrupts triggered.
*
* @param vnew current value.
* @param vold previous value.
*
* @return returns percentage change between values new and old.
*/
int pctchg (long int vnew, long int vold)
{
long int diff = 0;
/* deal with positive values only */
vnew = vnew < 0 ? -vnew : vnew;
vold = vold < 0 ? -vold : vold;
/* difference between old and new */
diff = vnew > vold ? vnew - vold : vold - vnew;
/* return percentage change */
return (diff * 100) / vnew;
}
/**
* @brief read IR sernsor rising edge and compute current RPM.
*
* @note millis() and micros() not updated in ISR, so read in loop()
* for all timed events.
*/
void isr_irsnsr (void)
{
/* get current timestamp */
#ifdef USERPMMS
us_end = millis();
#else
us_end = micros();
#endif
/* calculate time for revolution */
us_rev = us_end - us_start;
/* if first or invalid, save start for next */
if (!us_start || us_rev < REVMINUS || REVMAXUS < us_rev) {
us_start = us_end;
return;
}
/* if last period not set, update value and get next */
if (!us_rev_last) {
us_rev_last = us_rev;
us_start = us_end;
return;
}
/* compare percentage change between time periods with max */
if (pctchg (us_rev, us_rev_last) > PCTCHGMAX) {
us_rev_last = us_rev;
us_start = us_end;
return;
}
/* calculate angular rate in RPM */
#ifdef USERPMMS
rpm = (unsigned long)MILLISECRPM / us_rev;
#else
rpm = (unsigned long)MICROSECRPM / us_rev;
#endif
/* save max RPM */
if (rpm > rpm_max) {
rpm_max = rpm;
}
/* save rev time, update start time for next rev */
us_rev_last = us_rev;
us_start = us_end;
}
void setup()
{
/* initialize led pins as an OUTPUT: */
pinMode (LED_BUILTIN, OUTPUT);
/* initialize sensor pin as input */
pinMode (PIN_IRSNSR, INPUT);
/* attach interrupt on input pint to isr_irsnsr on rising edge
*
* PCMSK2 ^ PCINT18 (enable pin 2 PCINT18 in pin control mask)
* EICRA - bits 0,1 - INT0, bits 2,3 - INT1
* 1 - CHANGE
* 2 - FALLING
* 3 - RISING
*/
attachInterrupt (digitalPinToInterrupt (PIN_IRSNSR), isr_irsnsr, RISING);
#ifdef USESERIAL
Serial.begin (SERIALBAUD);
delay (1000);
Serial.println ("\nIR sensor RPM:\033[?25l\n\n rpm : \n");
#endif
}
void loop()
{
#ifdef USESERIAL
char buf[IVMAXC] = "";
#endif
static unsigned long rt_start = 0,
rt_accum_100ms = 0;
#ifdef USERPMMS
unsigned long rt_now = millis(),
#else
unsigned long rt_now = micros(),
#endif
rt_diff = rt_now - rt_start;
uint16_t rpm_out = rpm;
rt_accum_100ms += rt_diff;
rt_start = rt_now;
/* 100 ms repeating events */
if (rt_accum_100ms >= RT_100MS) {
digitalWrite (LED_BUILTIN, !digitalRead (LED_BUILTIN));
#ifdef USESERIAL
#ifdef USERPMMS
if (rt_now - us_end > REVMAXMS) {
#else
if (rt_now - us_end > REVMAXUS) {
#endif
rpm_out = 0;
}
Serial.print ("\033[2A\033[8C");
Serial.println (uint2strdec_w (buf, rpm_out, 4));
Serial.print (" max : ");
Serial.println (uint2strdec_w (buf, rpm_max, 4));
#endif
rt_accum_100ms = 0;
}
}
I've also dumped the values for the time between when the interrupts are being called so I could see the actual data. It is consistent, so there is something triggering the additional interrupts. Each line of data below holds the time periods for interrupts triggered with a 100 ms period (each line represents one iteration of the 100ms timing of the main program loop). The values were buffered in an array and then output at 100 ms intervals. Below you have 3 parts, the start of the motor turning (first two values are skipped), then steady-state, and then the motor spinning down:
9966532 56 17692 56
130176 56 9884 56
90012 56 7720 56 74548 56
6612 56 65660 56 5936 56
59800 56 5436 565551256508856
52268 56 4788 56
49656 56 4572 56 47544 56 4364 56
45764 56 4216 56 44272 56 4068 56
42972 56 3952 56 41860 56 3840 52 40860 56 3756 56
39988 56 3660 56 39208 56 3596 56
38512 52 3520 563788056346056
37308 56 3400 56 36772 56 3352 56 36284 56 3300 52
35836 56 3252 56 35424 56 3208 56
35036 56 3176 56 34680 56 3136 56 34352 56 3108 56
<snip>
27536 56 2360 56 27536 52 2360 56 27532 52 2364 52 2752856235656
27520 56 2360 56 27516 56 2360 56 27508 56 2356 56
27504 56 2356 56 27496 56 2352 56 2749256235256
27480 56 2352 56 27472 56 2352 56 27464 56 2352 56 27456 56 2348 56
27456 56 2344 56 27452562344562744856234456
27440 52 2348 56 27432 56 2348 56 27428 56 2348 56
27424 56 2344 56 274205623445627416562344562741656234056
27412 56 2348 56 27404 56 2344 56 27400 56 2348 56
27404562340562740856233656274045623405627396
2400 56 27392 56 2348 56 27392 56 2344 56 27392 56234056
27396 56 2336 56 27396 56 2344 56 27384 56 2344 56
27388 56 2344 56 27380 56 2340 56 27384 562340522738056234056
27376 56 2340 56 27368 56 2344 56 27368 56 2336 56
27368 56 2340 56 27364 562340562736056234056
27356 56 2340 56 27356 56 2336 56 27356 56 2336 56 27356 56 2336 56
27356 5623405627356562336562735656234056
27356 56 2336 56 27360 56 2336 56 27360 56 2336 56
27356 56234056273525623365627356562336562734456233656
27344 56 2332 56 27340 56 2336 56 27336 52233656
27332 52 2340 56 27328 56 2332 56 27328 56 2336 56
27324 56 2328 56 27328 56 2336 56 27324 562332562732856233256
27328 56 2336 56 27328 56 2332 56 27328 56 2332 56
<snip>
45668 56 4352 56 4746856453256
49392 56 4740 56 51480 56 4948 60
53756 56 5184 56 56256 56
5448 56 58996 56 5728 56
62040566036566544056
6392 56 69260 56 6804 52
73584 56 7252 56
78548 56 7784 56
84308 56 8400 56
9112056915256
99296 10104 52
109312 52 11184 56
121912 56
12620 52
138552 56 14584 56
162008 56 17504 56
198848 562248056
269344 56 34076 56
560544 56 56 56 56 56 56 56 56
134440 56 56 88 56 56 56 56 5656565656
The first interrupt triggered in each 100 ms timing period is correct 99% of the time, e.g. 27332 with an odd one every once in a while, e.g. 2340. However, the next three interrupts make no sense, e.g. 52 2340 56. That sequence then repeats two more time throughout the 100 ms period, e.g.
27332 52 2340 56 27328 56 2332 56 27328 56 2336 56
The 273xx microsecond values are correct, the rest are not.
I'm pretty well stumped on this one, but I suspect it has something to do with the interplay between the timers at use, but I'm not familiar enough with the nano to put my finger on it?
The IR LED/Receiver board is a simple design. It is essentially the following (just adjust the 3.3V for 5V for the arduino):
In each case, the sensor has an activity LED that shows when the RPM is being measured and when the interrupt should be triggered. In this case, the activity LED corresponding the valid measurements are shown. There is no activity shown when the spurious events occur, so I'm confident it isn't noise coming from the IR sensor board.
I'm open to any help and suggestions. It is probably something I missed in the Arduino language reference or the 328p datasheet. So I won't be surprised if it is something basic. Let me know if I need to provide additional information. I tried include as much as I could to make this make sense.



