looking for good RPM sketch for wind turbine monitoring

I'm looking for a good sketch to monitor RPM on an arduino mega with timer capture inputs. I have seen several on the forum but cannot seem to get them to work. Range of 100 to 25k RPM. IDE 1.6.7.

Currently, signal is on PL0(ICP4) or PL1(ICP5). I have a divide by 2, D flipflop on the input, so signal is symmetrical and 50% duty cycle.

using the following, the serial monitor just gives me nan 0 0 repeatedly

// period of pulse accumulation and serial output, milliseconds
#define MainPeriod 100

long previousMillis = 0; // will store last time of the cycle end
volatile unsigned long duration=0; // accumulates pulse width
volatile unsigned int pulsecount=0;
volatile unsigned long previousMicros=0;

void setup() {
Serial.begin(19200);
attachInterrupt(0, myinthandler, RISING);
}

void loop()
{
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= MainPeriod)
{
previousMillis = currentMillis;
// need to bufferize to avoid glitches
unsigned long _duration = duration;
unsigned long _pulsecount = pulsecount;
duration = 0; // clear counters
pulsecount = 0;
float Freq = 1e6 / float(_duration);
Freq *= _pulsecount; // calculate F
// output time and frequency data to RS232
Serial.print(currentMillis);
Serial.print(" "); // separator!
Serial.print(Freq);
Serial.print(" ");
Serial.print(_pulsecount);
Serial.print(" ");
Serial.println(_duration);
}
}

void myinthandler() // interrupt handler
{
unsigned long currentMicros = micros();
duration += currentMicros - previousMicros;
previousMicros = currentMicros;
pulsecount++;
}

thanks in advance,
tim

You need noInterrupts()/interrupts() wrapped around the code in loop() that
reads/writes the volatile variables - otherwise they will be garbled occasionally.

Your NaN's just indicate you've not had any interrupts within the sampling period.

This line is wrong:

    previousMillis = currentMillis;

change it to

    previousMillis += MainPeriod ;

Then you'll get proper regular scheduling of your code every 100ms without drift and error.

I'm looking for a good sketch to monitor RPM on an arduino mega with timer capture inputs. I have seen several on the forum but cannot seem to get them to work. Range of 100 to 25k RPM. IDE 1.6.7.

Currently, signal is on PL0(ICP4) or PL1(ICP5). I have a divide by 2, D flipflop on the input, so signal is symmetrical and 50% duty cycle.

The code you posted is for an external interrupt and not an input capture interrupt. Why do you wish to use the timer input caputure instead of the external interrupt?

If you put the input to pin 2 which matches your attachInterrupt(0, myinthandler, RISING) does the sketch work?

If you have code written for timer capture inputs, please post that using the code tags </> so that your

code looks like this

Your "bufferized" transfer is not actually protected. You make the protected copies by disabling and restarting the interrupts.

// need to bufferize to avoid glitches
    noInterrupts();//disable interrupts for protected transfer
    unsigned long _duration = duration;
    unsigned long _pulsecount = pulsecount;
    duration = 0; // clear counters
    pulsecount = 0;
    interrupts(); //reenable interrupts
  1. made the code changes(see below), still get nan on serial monitor.
  2. motivation to use timer counter was to keep things on the same connector as a board I have made. mostly want to get something to work.
  3. "change to pin2", do you mean physical pin to on mega2560 chip()digital pin 0, RX0), or to digital input pin 2(PE4)

// period of pulse accumulation and serial output, milliseconds
#define MainPeriod 100

long previousMillis = 0; // will store last time of the cycle end
volatile unsigned long duration=0; // accumulates pulse width
volatile unsigned int pulsecount=0;
volatile unsigned long previousMicros=0;

void setup() {
Serial.begin(19200);
attachInterrupt(0, myinthandler, RISING);
}

void loop()
{
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= MainPeriod)
{
//previousMillis = currentMillis;
previousMillis += MainPeriod;
// need to bufferize to avoid glitches
noInterrupts();
unsigned long _duration = duration;
unsigned long _pulsecount = pulsecount;
duration = 0; // clear counters
pulsecount = 0;
interrupts();
float Freq = 1e6 / float(_duration);
Freq *= _pulsecount; // calculate F
// output time and frequency data to RS232
Serial.print(currentMillis);
Serial.print(" "); // separator!
Serial.print(Freq);
Serial.print(" ");
Serial.print(_pulsecount);
Serial.print(" ");
Serial.println(_duration);
}
}

void myinthandler() // interrupt handler
{
unsigned long currentMicros = micros();
duration += currentMicros - previousMicros;
previousMicros = currentMicros;
pulsecount++;
}

here is the timer counter sketch I tried. serial monitor only says "timing...".
7ms period signal on TC0, 50% duty cycle

/***
InputCapture.ino

Timer 1 high-resolution timing facility.

Copyright (C) 2008-2012 Bill Roy

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

***/
#include "Arduino.h"
#include <avr/interrupt.h>

/*
TO DO
analog threshold-based tick timer
set comparator to use internal reference
*/

// rising/falling edge tracking
// per '168 data sheet page 121, must clear ICF1 after edge direction change
//
volatile uint8_t rising;
//#define catchRisingEdge() (TCCR1B |= (1<<ICES1); TIFR1 |= (1<<ICF1); rising = 1;);
//#define catchFallingEdge() (TCCR1B &= ~(1<<ICES1); TIFR1 |= (1<<ICF1); rising = 0;);

// Event queue
#define MAX_EVENT_BUFF 32
unsigned long long last_event;
volatile byte event_buffer_head;
byte event_buffer_tail;
unsigned long long event_buffer[MAX_EVENT_BUFF];

unsigned long long eventRead(void) {
unsigned long long event;
if (event_buffer_tail == event_buffer_head) {
Serial.println("Event buffer underrun");
return -1;
}
event = event_buffer[event_buffer_tail] - last_event;
last_event = event_buffer[event_buffer_tail];
byte oldsreg = SREG;
cli();
if (++event_buffer_tail >= MAX_EVENT_BUFF) event_buffer_tail = 0;
SREG = oldsreg;
return event;
}

byte eventAvailable(void) {
byte head;
byte oldsreg = SREG;
cli();
head = event_buffer_head;
SREG = oldsreg;
return event_buffer_tail != head;
}

// Our virtual timer counts 2^64 clocks (provided sizeof(unsigned long long) == 8)
// The low order 16 bits are maintained by timer1 and are snapshotted by the
// TIMER1_CAPT input capture interrupt below.
//
// We maintain the high order 48 bits here by incrementing the virtual timer.
//
volatile unsigned long long t1_virtual_count;
ISR(TIMER1_OVF_vect) {
t1_virtual_count += 0x10000ULL;
}

unsigned long lost_event_count;

// Interrupt capture handler
//
ISR(TIMER1_CAPT_vect) {

byte l = ICR1L; // grab captured timer value
byte h = ICR1H;

// watch for the other edge to catch the half-pulse width
//rising ? catchFallingEdge() : catchRisingEdge();
if (rising) { TCCR1B &= ~(1<<ICES1); TIFR1 |= (1<<ICF1); rising = 0; }
else {TCCR1B |= (1<<ICES1); TIFR1 |= (1<<ICF1); rising = 1;}

// push the timestamp into the buffer
byte newhead = event_buffer_head + 1;
if (newhead >= MAX_EVENT_BUFF) newhead = 0;
if (newhead != event_buffer_tail) {
event_buffer[event_buffer_head] = t1_virtual_count + (h << 8) + l;
event_buffer_head = newhead;
}
else ++lost_event_count;
}

void printEvent(unsigned long long n, unsigned long base) {
char buf[32]; // stack for the digits
char *ptr = buf;
if (n == 0ULL) {
Serial.write('0');
return;
}
while (n > 0) {
*ptr++ = n % base;
n /= base;
}
while (--ptr >= buf) Serial.write((*ptr < 10) ? (*ptr + '0') : (*ptr - 10 + 'A'));
}

void initTimer(void) {

// Input Capture setup
// ICNC1: Enable Input Capture Noise Canceler
// ICES1: =1 for trigger on rising edge
// CS10: =1 set prescaler to 1x system clock (F_CPU)
TCCR1A = 0;
TCCR1B = (0<<ICNC1) | (0<<ICES1) | (1<<CS10);
TCCR1C = 0;

//catchFallingEdge(); // initialize to catch
{ TCCR1B &= ~(1<<ICES1); TIFR1 |= (1<<ICF1); rising = 0; }

// Interrupt setup
// ICIE1: Input capture
// TOIE1: Timer1 overflow
TIFR1 = (1<<ICF1) | (1<<TOV1); // clear pending
TIMSK1 = (1<<ICIE1) | (1<<TOIE1); // and enable

// Set up the Input Capture pin, ICP1, which corresponds to Arduino D8
pinMode(49, INPUT);
//digitalWrite(8, 0); // leave floating to count 60 Hz etc.
digitalWrite(8, 1); // or enable the pullup
}

void setup(void) {
pinMode(13, OUTPUT);

// Power up the light sensor
//pinMode(9, OUTPUT); digitalWrite(9, 0);
//pinMode(10, OUTPUT); digitalWrite(10, 1);

Serial.begin(57600);
initTimer();
Serial.println("timing...");
}

long count, sumt, bogon_count;
unsigned long updatetime;

void loop60hz(void) {
long t;
long dt;

while (eventAvailable()) {
t = (long) eventRead();
dt = t - (F_CPU/60);
if (abs(dt) < 2000L) {
count = count + 1;
sumt += dt;
}
else ++bogon_count;

if (millis() > updatetime) {
Serial.println("");
Serial.print("t="); Serial.print(t);
//Serial.print(" dt="); Serial.print(dt);
Serial.print(" sdt="); Serial.print(sumt);
Serial.print(" n="); Serial.print(count);
Serial.print(" a="); Serial.print(sumt/count);
Serial.print(" e="); Serial.print(lost_event_count);
Serial.print(" b="); Serial.print(bogon_count);
updatetime = millis() + 1000L;
}
Serial.print(" "); Serial.print(dt);
}
}

void loop(void) {
while (eventAvailable()) {
Serial.print(count++); Serial.write(':');
printEvent(eventRead(), 10); Serial.write(' '); Serial.println(lost_event_count);
}
}

digital input pin 2(PE4)

That's the pin which is used by attachInterrupt(0, myinthandler, RISING);

In order to avoid confusion, the IDE now supports

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);

Seehttp://www.arduino.cc/en/Reference/AttachInterrupt

  1. motivation to use timer counter was to keep things on the same connector as a board I have made. mostly want to get something to work.

The code for the external interrupt is less complex, and will be easier for you to understand and get working.

Switch the interrupt to digital pin 2 and see what you get. The code looks good and gives the correct values when I test it with tone() to generate input on digital pin 2.

Please read the documention on how to use the forum and how to post your code using code tags.

thanks Cattledog, works great! I'll read more

added my Freq code to my main project, get an error on the statement attachInterrupt(), error code below, full sketch, attached. I included the error handler after the loop() code just an in the single function sketch, but in my bigger program this was oddly not accepted but I don't understand why.

C:\Users\twilkers\Documents\Arduino\tim projects\temp_sketch_mega_Mar26_2016_delay1secw_intRPM\temp_sketch_mega_Mar26_2016_delay1secw_intRPM.ino: In function 'void setup()':

temp_sketch_mega_Mar26_2016_delay1secw_intRPM:127: error: 'myinthandler' was not declared in this scope

  attachInterrupt(0, myinthandler, RISING);

                     ^

temp_sketch_mega_Mar26_2016_delay1secw_intRPM:167: error: expected primary-expression before '/' token

/ 

^

temp_sketch_mega_Mar26_2016_delay1secw_intRPM:170: error: expected primary-expression before 'if'

  if (!volume.init(card)) {

  ^

temp_sketch_mega_Mar26_2016_delay1secw_intRPM:170: error: expected ';' before 'if'

C:\Users\twilkers\Documents\Arduino\tim projects\temp_sketch_mega_Mar26_2016_delay1secw_intRPM\temp_sketch_mega_Mar26_2016_delay1secw_intRPM.ino: In function 'void loop()':

temp_sketch_mega_Mar26_2016_delay1secw_intRPM:452: error: a function-definition is not allowed here before '{' token

{

^

temp_sketch_mega_Mar26_2016_delay1secw_intRPM:457: error: expected '}' at end of input

}

^

Multiple libraries were found for "Ethernet.h"
Used: C:\Users\twilkers\Documents\Arduino\libraries\Ethernet
Not used: C:\Program Files (x86)\Arduino\libraries\Ethernet
exit status 1
'myinthandler' was not declared in this scope

temp_sketch_mega_Mar26_2016_delay1secw_intRPM.ino (17.3 KB)

There is a missing closing bracket at the end of loop. Add a bracket just above the interrupt handler.

Also, add a second / with the comment on line 167 right above //SD code.

With those two changes, the program compiles for me.

I've made a revcounter using pulses from an alternator - works fine.

use the pulseIn function - note that my app could have asymmetrical pulses.

the relevant bit is :

pulselow = pulseIn( pulsepin, LOW,20000); // measure pulsewidth low
pulsehigh = pulseIn( pulsepin, HIGH,20000); // measure pulsewidth high
pulselen = pulselow + pulsehigh; // total pulsewidth

freq = 4000000 / pulselen ; // gives freq in Hz * 4
//and scale noting that typically 500hz ~= 2000rpm

regards

Allan

I'm sort of confused on the pin mapping for AT2560.

The application works with the following pin assignment which attaches to PE4.
attachInterrupt(0, myinthandler, RISING);

but when I look up mega2560 pin mapping at this location, which tells me it should be assigned to digital pin 2.

where is the correct pin assignment document?

maybe I should not pay any attention to something that has "hacking" in the path.

Definitive invormation is in the datasheet for the ATmega2560 plus the table(s) in
/hardware/arduino/variants/mega/pins_arduino.h
(or /hardware/arduino/avr/variants/mega/pins_arduino.h in 1.6 and later)

For Mega:
interrupt number, Arduino pin, port/bit
0, 2, E4
1, 3, E5
2, 21, D0
3, 20, D1
4, 19, D2
5, 18, D3

I have this RPM sketch working now and it is quite stable except for a giant number at program initiation and sporadically during operation. I'm not so worried about the initial number as it will purge automatically, but would prefer to not have the occasional giant number in the data set as I plan for automatically graphing.

This anomaly occurs when using a simulated input from a 555 timer, a stable source.

thanks in advance for your comments.

here is the source

// period of pulse accumulation and serial output, milliseconds
#define MainPeriod 100

long previousMillis = 0; // will store last time of the cycle end
volatile unsigned long duration=0; // accumulates pulse width
volatile unsigned int pulsecount=0;
volatile unsigned long previousMicros=0;

void setup() {
   Serial.begin(19200); 
  attachInterrupt(0, myinthandler, RISING);
}


void loop()
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= MainPeriod) 
  {
    //previousMillis = currentMillis;  
     previousMillis += MainPeriod;
    // need to bufferize to avoid glitches
    noInterrupts();
    unsigned long _duration = duration;
    unsigned long _pulsecount = pulsecount;
    duration = 0; // clear counters
    pulsecount = 0;
    interrupts();
    float Freq = 1e6 / float(_duration);
    Freq *= _pulsecount; // calculate F
    // output time and frequency data to RS232
    Serial.print(currentMillis);
    Serial.print(" "); // separator!
    Serial.print(Freq);
    Serial.print(" "); 
    Serial.print(_pulsecount);
    Serial.print(" ");
    Serial.println(_duration);
  }
}

void myinthandler() // interrupt handler
{
  unsigned long currentMicros = micros();
  duration += currentMicros - previousMicros;
  previousMicros = currentMicros;
  pulsecount++;
}

sample data 4_6.txt (1.79 KB)

sampledata4_6.txt does not match the output of the program provided. When I run the code provided with a test signal it is stable.

Are you writing to an SD card or some terminal program other than the IDE serial monitor?

Do you see your issue when you only look at the IDE serial monitor output from the sketch you provided? You may wish to start a new thread, if the issue does not lie in the interrupt code but in the data storage/transfer.

Please provide the code which produced the output with the problems.

Hi,

I tried to upload the full bundle yesterday but something happened in the upload. I'll try again.

the full code writes to a web page. there is SD code in the sketch, but it is not used at this time.

I tried to upload yesterday in 7zip format and the site would not accept. here is a zip.

the full sketch puts data out to a webpage. there is SD card code in the sketch but it is unused.

temp_sketch_mega_Mar31_2016_delay1secw_intRPMave.zip (5.02 KB)

There is way too much code here to effectively troubleshoot. No one will be able to figure out what is wrong with a large code posted as a zip file which many posters will not open.

You will need to break it down some, and perhaps write a simplified sketch to demonstrate the problem. Strip out temperature, SD, etc. Pull out every thing not needed but which still demonstrates the problem.

 //Serial.print(_pulsecount);
    Serial.print(sensorRPM[array_index]);

Is the data valid at this point, before any transfer?

On thing to be checking is whether you are ever going outside of array bounds into memory you don't control?

I haven't caught the anomaly in the serial monitor, seems to show up only post mortem over several hours of system operation.

I've removed SD card support and trying to rely on interrupts for system timing now, specifically the previously mentioned RPM routine. Interrupts work fine in the stand RPM sketch, but not with the revised system.

Does attachInterrupt() also enable interrupts? Or only control the interrupt vector?
The RPM sketch doesn't seem to have anything specific to enable interrupts but they do happen.

Does attachInterrupt() also enable interrupts? Or only control the interrupt vector?
The RPM sketch doesn't seem to have anything specific to enable interrupts but they do happen.

Yes, the attachInterrupt() function enables the underlying external interrupt.