Confused by oscilloscope - square wave signal to RPM

You can remove random spurious readings using a simple median filter.

MedianFilterLib - Arduino Reference

1 Like

would this be the correct way to use the code for only rising edges?

// Measures the HIGH width, LOW width, frequency, and duty-cycle of a pulse train
// on Arduino NANO Pin 8 (ICP1 pin).  

// Note: Since this uses Timer1, Pin 9 and Pin 10 can't be used for
// analogWrite().

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

  // For testing, uncomment one of these lines and connect
  // Pin 3 or Pin 5 to Pin 8
  // analogWrite(3, 64);  // 512.00, 1528.00, 2040.00, 25.10%, 490.20 Hz
  // analogWrite(5, 64);  // 260.00, 764.00, 1024.00, 25.39%, 976.56 Hz

  noInterrupts ();  // protected code
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TIMSK1 = 0;

  TIFR1 |= _BV(ICF1); // clear Input Capture Flag so we don't get a bogus interrupt
  TIFR1 |= _BV(TOV1); // clear Overflow Flag so we don't get a bogus interrupt

  TCCR1B = _BV(CS10) | // start Timer 1, no prescaler
           _BV(ICES1); // Input Capture Edge Select (1=Rising, 0=Falling)

  TIMSK1 |= _BV(ICIE1); // Enable Timer 1 Input Capture Interrupt
  TIMSK1 |= _BV(TOIE1); // Enable Timer 1 Overflow Interrupt
  interrupts ();
}

//volatile uint32_t PulseHighTime = 0;
volatile uint32_t PulseTime = 0;
//volatile uint32_t PulseLowTime = 0;
volatile uint16_t Overflows = 0;

ISR(TIMER1_OVF_vect)
{
  Overflows++;
}

ISR(TIMER1_CAPT_vect)
{
  static uint32_t firstRisingEdgeTime = 0;
  //static uint32_t fallingEdgeTime = 0;
  static uint32_t secondRisingEdgeTime = 0;

  uint16_t overflows = Overflows;

  // If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (ICR1 < 1024))
    overflows++;

  if (PulseTime == 0)
  {
    if (TCCR1B & _BV(ICES1))
    {
      // Interrupted on Rising Edge
      if (firstRisingEdgeTime)  // Already have the first rising edge...
      {
        // ... so this is the second rising edge, ending the low part
        // of the cycle.
        secondRisingEdgeTime = overflows; // Upper 16 bits
        secondRisingEdgeTime = (secondRisingEdgeTime << 16) | ICR1;
        //PulseLowTime = secondRisingEdgeTime - fallingEdgeTime;
        PulseTime = secondRisingEdgeTime - firstRisingEdgeTime;
        firstRisingEdgeTime = 0;
      }
      else
      {
        firstRisingEdgeTime = overflows; // Upper 16 bits
        firstRisingEdgeTime = (firstRisingEdgeTime << 16) | ICR1;
        //TCCR1B &= ~_BV(ICES1); // Switch to Falling Edge
      }
    }
//    else
//    {
//      // Interrupted on Falling Edge
//      fallingEdgeTime = overflows; // Upper 16 bits
//      fallingEdgeTime = (fallingEdgeTime << 16) | ICR1;
//      TCCR1B |= _BV(ICES1); // Switch to Rising Edge
//      PulseHighTime = fallingEdgeTime - firstRisingEdgeTime;
//    }
  }
}

void loop()
{
  noInterrupts();
//  uint32_t pulseHighTime = PulseHighTime;
//  uint32_t pulseLowTime = PulseLowTime;
    uint32_t pulseTime = PulseTime;
  interrupts();

  // If a sample has been measured
  if (pulseTime)
  {
    // Display the pulse length in microseconds
//    Serial.print("High time (microseconds): ");
//    Serial.println(pulseHighTime / 16.0, 2);
//    Serial.print("Low time (microseconds): ");
//    Serial.println(pulseLowTime / 16.0, 2);
      Serial.print("Pulse time (microseconds): ");
      Serial.println(pulseTime / 16.0, 2);

//    uint32_t cycleTime = pulseHighTime + pulseLowTime;
//    Serial.print("Cycle time (microseconds): ");
//    Serial.println(cycleTime / 16.0, 2);

//    float dutyCycle = pulseHighTime / (float)cycleTime;
//    Serial.print("Duty cycle (%): ");
//    Serial.println(dutyCycle * 100.0, 2);

//    float frequency = (float)F_CPU / cycleTime;
//    Serial.print("Frequency (Hz): ");
//    Serial.println(frequency, 2);
//    Serial.println();

    delay(1000);  // Slow down output

    // Request another sample
    noInterrupts();
    PulseTime = 0;
    interrupts();
  }
}

Yes. That's a better idea.

  1. Change the code to only look for rising edges.
  2. Use a median filter to ignore spurious readings.

I'm not going to look in detail at it but, instead, refer you to this page: Gammon Forum : Electronics : Microprocessors : Timers and counters
(a) it has a ready made frequency meter using input capture on one edge only, which is exactly what you want.
(b) it appears to have the same parentage as the @johnwasser version.

However, if you are lucky, John will help you out.

1 Like

More like:

// Measures the HIGH width, LOW width, frequency, and duty-cycle of a pulse train
// on Arduino NANO Pin 8 (ICP1 pin).

// Note: Since this uses Timer1, Pin 9 and Pin 10 can't be used for
// analogWrite().

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

  // For testing, uncomment one of these lines and connect
  // Pin 3 or Pin 5 to Pin 8
  // analogWrite(3, 64);  // 512.00, 1528.00, 2040.00, 25.10%, 490.20 Hz
  // analogWrite(5, 64);  // 260.00, 764.00, 1024.00, 25.39%, 976.56 Hz

  noInterrupts ();  // protected code
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TIMSK1 = 0;

  TIFR1 |= _BV(ICF1); // clear Input Capture Flag so we don't get a bogus interrupt
  TIFR1 |= _BV(TOV1); // clear Overflow Flag so we don't get a bogus interrupt

  TCCR1B = _BV(CS10) | // start Timer 1, no prescaler
           _BV(ICES1); // Input Capture Edge Select (1=Rising, 0=Falling)

  TIMSK1 |= _BV(ICIE1); // Enable Timer 1 Input Capture Interrupt
  TIMSK1 |= _BV(TOIE1); // Enable Timer 1 Overflow Interrupt
  interrupts ();
}

volatile uint16_t Overflows = 0;

ISR(TIMER1_OVF_vect)
{
  Overflows++;
}

ISR(TIMER1_CAPT_vect)
{
  static uint32_t previousRisingEdgeTime = 0;

  uint32_t thisRisingEdgeTime;
  uint16_t overflows = Overflows;

  // If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (ICR1 < 1024))
    overflows++;

  // Interrupted on Rising Edge
  thisRisingEdgeTime = overflows; // Upper 16 bits
  thisRisingEdgeTime = (thisRisingEdgeTime << 16) | ICR1;

  uint32_t pulseDuration = thisRisingEdgeTime - previousRisingEdgeTime;
  previousRisingEdgeTime = thisRisingEdgeTime;

  // This is a good place to add 'pulseDuration' to the median filter.
  // Make sure the median filter is marked 'volatile'.
}

void loop()
{
  static uint32_t previousMedian;
  uint32_t thisMedian;

  noInterrupts();
  // This would be where you get 'thisMedian'
  // from the median filter.
  interrupts();

  // If a sample has been measured
  if (thisMedian != previousMedian)
  {
    // Display the pulse length in microseconds
    Serial.print("Pulse time (microseconds): ");
    Serial.println(thisMedian / 16.0, 2);
    previousMedian = thisMedian;
  }
}
1 Like

I appreciate your help johnwasser, thank you very much.

I have been looking over the github documentation for the library RayLivingston provided for a couple hours now, and its kind of going over my head a bit

is this the correct way to use it?

// Measures the HIGH width, LOW width, frequency, and duty-cycle of a pulse train
// on Arduino NANO Pin 8 (ICP1 pin).

// Note: Since this uses Timer1, Pin 9 and Pin 10 can't be used for
// analogWrite().

#include <MedianFilterLib.h>

MedianFilter < volatile > medianFilter ( 5 );

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

  // For testing, uncomment one of these lines and connect
  // Pin 3 or Pin 5 to Pin 8
  // analogWrite(3, 64);  // 512.00, 1528.00, 2040.00, 25.10%, 490.20 Hz
  // analogWrite(5, 64);  // 260.00, 764.00, 1024.00, 25.39%, 976.56 Hz

  noInterrupts ();  // protected code
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TIMSK1 = 0;

  TIFR1 |= _BV(ICF1); // clear Input Capture Flag so we don't get a bogus interrupt
  TIFR1 |= _BV(TOV1); // clear Overflow Flag so we don't get a bogus interrupt

  TCCR1B = _BV(CS10) | // start Timer 1, no prescaler
           _BV(ICES1); // Input Capture Edge Select (1=Rising, 0=Falling)

  TIMSK1 |= _BV(ICIE1); // Enable Timer 1 Input Capture Interrupt
  TIMSK1 |= _BV(TOIE1); // Enable Timer 1 Overflow Interrupt
  interrupts ();
}

volatile uint16_t Overflows = 0;

ISR(TIMER1_OVF_vect)
{
  Overflows++;
}

ISR(TIMER1_CAPT_vect)
{
  static uint32_t previousRisingEdgeTime = 0;

  uint32_t thisRisingEdgeTime;
  uint16_t overflows = Overflows;

  // If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (ICR1 < 1024))
    overflows++;

  // Interrupted on Rising Edge
  thisRisingEdgeTime = overflows; // Upper 16 bits
  thisRisingEdgeTime = (thisRisingEdgeTime << 16) | ICR1;

  uint32_t pulseDuration = thisRisingEdgeTime - previousRisingEdgeTime;
  previousRisingEdgeTime = thisRisingEdgeTime;

  // This is a good place to add 'pulseDuration' to the median filter.
  // Make sure the median filter is marked 'volatile'.
  medianFilter.AddValue (pulseDuration);
}

void loop()
{
  static uint32_t previousMedian;
  uint32_t thisMedian;

  noInterrupts();
  // This would be where you get 'thisMedian'
  // from the median filter.
  thisMedian == medianFilter.GetFiltered ();
  interrupts();

  // If a sample has been measured
  if (thisMedian != previousMedian)
  {
    // Display the pulse length in microseconds
    Serial.print("Pulse time (microseconds): ");
    Serial.println(thisMedian / 16.0, 2);
    previousMedian = thisMedian;
  }
}```

The MedianFilterInt example that comes with the library makes me think the declaration should be more like:

volatile MedianFilter<uint32_t> medianFilter(5); // 5 samples

If any of the spurious values get through the filter, increase the number of samples.

1 Like

This was correct, it did in fact work - but not before I had to remove "medianFilter.GetFiltered ();" using "GetFiltered" resulting in literally nothing in the serial output. I don't still fully understand how the median filter works, but regardless - I replaced the GetFiltered with "medianFilter.AddValue (pulseDuration);", added a 75% tolerance to the "if" statement and voila, the output is now buttery smooth:

This is the fastest and most accurate the output has ever been. Thank you all so much.

I am not completely out of the woods, however. Now that I have established a working code for measuring the RPM, I thought it would simply be a case of assigning code for the neopixels and job done. Unfortunately, this is not the case.

I have a startup sequence for the neopixels in void setup() and that works, so I know that the arduino can still address them inside this code. The problem is very quickly after the arduino initializes, like about 30 seconds, the arduino freezes up and the serial output dies. I have changed nothing about my neopixel code that I know works from the original sketches.

The most glaring issue is that now that I am using interrupts, the neopixel library doesn't like that and is messing with things somehow. I have a few theories to test using noInterrupts(), and I guess I could use a second arduino dedicated to driving the neopixels, but I'd rather avoid that if possible.

This is the working code as it now stands:

// Measures the HIGH width, LOW width, frequency, and duty-cycle of a pulse train
// on Arduino NANO Pin 8 (ICP1 pin).
// Note: Since this uses Timer1, Pin 9 and Pin 10 can't be used for
// analogWrite().

// Adafruit NeoPixel RGBW strips (2x8 LEDS) 

#include <MedianFilterLib.h>

//MedianFilter < volatile > medianFilter ( 5 ); // 5 samples
volatile MedianFilter<uint32_t> medianFilter(5); // 5 samples
unsigned long pulse = 0;
float sensorRPM;
int RPMno = 3000; // base rpm for led activation
int space = 100; //delays for led startup sequence

//Input for headlight circuit
#define headlights 10
bool val = 0;

//NeoPixels
#include <Adafruit_NeoPixel.h>
#define PIN 6
#define NUM_LEDS 16
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_GRBW + NEO_KHZ800);
// full brightness
uint32_t r = strip.Color  (200, 0, 0, 0);
uint32_t g = strip.Color  (0, 180, 0, 0);
uint32_t b = strip.Color  (0, 0, 255, 0);
uint32_t w = strip.Color  (0, 0, 0, 120);
uint32_t y = strip.Color  (200, 120, 0, 0);
// dimmed
uint32_t rd = strip.Color  (8, 0, 0, 0);
uint32_t gd = strip.Color  (0, 7, 0, 0);
uint32_t bd = strip.Color  (0, 0, 10, 0);
uint32_t wd = strip.Color  (0, 0, 0, 3);
uint32_t yd = strip.Color  (10, 5, 0, 0);
// off/no color
uint32_t o = strip.Color   (0, 0, 0, 0);

bool ledState = 1;



void setup()
{
  //initiate neopixels
   strip.begin();
  //set strip to nothing, flushes potential random colors
  strip.clear();
  strip.fill(o);
  strip.show();
  ledStartup();
   
  //assign input for relay
  pinMode(headlights, INPUT);
  digitalWrite(headlights, HIGH); //enables pullup resistor - HIGH on an input pin
                                 //also, it inverts signal: < 3v = HIGH, > 3v = LOW
  
  Serial.begin(9600);
  while (!Serial);

  // For testing, uncomment one of these lines and connect
  // Pin 3 or Pin 5 to Pin 8
  // analogWrite(3, 64);  // 512.00, 1528.00, 2040.00, 25.10%, 490.20 Hz
  // analogWrite(5, 64);  // 260.00, 764.00, 1024.00, 25.39%, 976.56 Hz

  noInterrupts ();  // protected code
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TIMSK1 = 0;

  TIFR1 |= _BV(ICF1); // clear Input Capture Flag so we don't get a bogus interrupt
  TIFR1 |= _BV(TOV1); // clear Overflow Flag so we don't get a bogus interrupt

  TCCR1B = _BV(CS10) | // start Timer 1, no prescaler
           _BV(ICES1); // Input Capture Edge Select (1=Rising, 0=Falling)

  TIMSK1 |= _BV(ICIE1); // Enable Timer 1 Input Capture Interrupt
  TIMSK1 |= _BV(TOIE1); // Enable Timer 1 Overflow Interrupt
  interrupts ();
}

volatile uint16_t Overflows = 0;

ISR(TIMER1_OVF_vect)
{
  Overflows++;
}

ISR(TIMER1_CAPT_vect)
{
  static uint32_t previousRisingEdgeTime = 0;

  uint32_t thisRisingEdgeTime;
  uint16_t overflows = Overflows;

  // If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (ICR1 < 1024))
    overflows++;

  // Interrupted on Rising Edge
  thisRisingEdgeTime = overflows; // Upper 16 bits
  thisRisingEdgeTime = (thisRisingEdgeTime << 16) | ICR1;

  //uint32_t pulseDuration = thisRisingEdgeTime - previousRisingEdgeTime;
  pulse = thisRisingEdgeTime - previousRisingEdgeTime;
  previousRisingEdgeTime = thisRisingEdgeTime;

  // This is a good place to add 'pulseDuration' to the median filter.
  // Make sure the median filter is marked 'volatile'.
  //medianFilter.AddValue (pulseDuration);
}

void loop()
{
  static uint32_t previousMedian;
  uint32_t thisMedian;

  dimmer();

  noInterrupts();
  // This would be where you get 'thisMedian'
  // from the median filter.
  thisMedian = medianFilter.AddValue (pulse);
  interrupts();

  // If a sample has been measured
  if (thisMedian != previousMedian && thisMedian > previousMedian*0.75)
  {
    // Display the pulse length in microseconds
    //Serial.print("Pulse time (microseconds): ");
    //Serial.println(thisMedian / 16.0, 2);
    
    //measure revs from pulse_length
    //first calculate how many sparks happening per second, 1000ms divided by delay between sparks
    float sparksPerSec = 1000/((thisMedian / 16.0)*0.001);
    //next multiply by 60 for sparks per minute - two revs per spark
    sensorRPM = sparksPerSec*120;
    Serial.println(sensorRPM);
    previousMedian = thisMedian; 
  }
  //delay(100);
}


//__      ______ _____ _____  
// \ \    / / __ \_   _|  __ \ 
//  \ \  / / |  | || | | |  | |
//   \ \/ /| |  | || | | |  | |
//    \  / | |__| || |_| |__| |
//     \/   \____/_____|_____/ 


//     _ _                               
//  __| (_)_ __ ___  _ __ ___   ___ _ __ 
// / _` | | '_ ` _ \| '_ ` _ \ / _ \ '__|
//| (_| | | | | | | | | | | | |  __/ |   
// \__,_|_|_| |_| |_|_| |_| |_|\___|_|                             

void dimmer (void)
{
  
 val = digitalRead(headlights);
  if (val == LOW) leds();
   else            ledsDimmed();
   
}


//  _          _     
// | | ___  __| |___ 
// | |/ _ \/ _` / __|
// | |  __/ (_| \__ \
// |_|\___|\__,_|___/  font = ogre
                                                                 
void leds(void) 
{  
  
if (sensorRPM > 0 && sensorRPM < RPMno) 
 {
  strip.clear();
  strip.fill(o); //----------------
  //strip.setPixelColor(o, 0);
  strip.show(); 
 }
 
if (sensorRPM > RPMno && sensorRPM < (RPMno + 90)) 
 {
  strip.clear();
  strip.fill(g, 14);  //gg--------------
  strip.show(); 
 }
 
if (sensorRPM > (RPMno + 90) && sensorRPM < (RPMno + 220)) 
 {
  strip.clear();
  strip.fill(g, 12);  //gggg------------
  strip.show(); 
  } 
  
if (sensorRPM > (RPMno + 220) && sensorRPM < (RPMno + 330)) 
 {
  strip.clear();
  strip.fill(g, 10);  //gggggg----------
  strip.show(); 
  } 
  
if (sensorRPM > (RPMno + 330) && sensorRPM < (RPMno + 440)) 
 { 
  strip.clear();
  strip.fill(y,8);   //yyyyyyyy--------         
  strip.show(); 
  } 
  
if (sensorRPM > (RPMno + 440) && sensorRPM < (RPMno + 550)) 
 {
  strip.clear();
  strip.fill(y,6);   //yyyyyyyyyy------
  strip.show();
 }
 
if (sensorRPM > (RPMno + 550) && sensorRPM < (RPMno + 660)) 
 {
  strip.clear();
  strip.fill(y,4);         //yyyyyyyyyyyy----
  strip.show();  
 }
 
if (sensorRPM > (RPMno + 660) && sensorRPM < (RPMno + 770)) 
 {
  strip.clear();strip.fill(r,2);         //rrrrrrrrrrrrrr--         
  strip.show(); 
 }
 
if (sensorRPM > (RPMno + 770) && sensorRPM < (RPMno + 880)) 
 {
  strip.clear();
  strip.fill(r,0);        //rrrrrrrrrrrrrr
  strip.show(); 
 } 
 
if (sensorRPM > (RPMno + 880) && sensorRPM < (RPMno + 1100)) 
 {
  strip.clear();
  strip.fill(w);        //wwwwwwwwwwwwwwww 
  strip.show(); 
 }


if (sensorRPM > (RPMno + 1100)) 
 {
  strip.clear();
  strip.fill(o);
  strip.show(); 
 }
 
}

//  _          _        ___ _                              _ 
// | | ___  __| |___   /   (_)_ __ ___  _ __ ___   ___  __| |
// | |/ _ \/ _` / __| / /\ / | '_ ` _ \| '_ ` _ \ / _ \/ _` |
// | |  __/ (_| \__ \/ /_//| | | | | | | | | | | |  __/ (_| |
// |_|\___|\__,_|___/___,' |_|_| |_| |_|_| |_| |_|\___|\__,_|
                                                          
void ledsDimmed(void)  
{  
  
if (sensorRPM > 0 && sensorRPM < RPMno) 
 {
  strip.clear();
  strip.fill(o); //----------------
  //strip.setPixelColor(o, 0);
  strip.show(); 
 }
 
if (sensorRPM > RPMno && sensorRPM < (RPMno + 90)) 
 {
  strip.clear();
  strip.fill(gd, 14);  //gg--------------
  strip.show(); 
 }
 
if (sensorRPM > (RPMno + 90) && sensorRPM < (RPMno + 220)) 
 {
  strip.clear();
  strip.fill(gd, 12);  //gggg------------
  strip.show(); 
  } 
  
if (sensorRPM > (RPMno + 220) && sensorRPM < (RPMno + 330)) 
 {
  strip.clear();
  strip.fill(gd, 10);  //gggggg----------
  strip.show(); 
  } 
  
if (sensorRPM > (RPMno + 330) && sensorRPM < (RPMno + 440)) 
 { 
  strip.clear();
  strip.fill(yd,8);   //yyyyyyyy--------         
  strip.show(); 
  } 
  
if (sensorRPM > (RPMno + 440) && sensorRPM < (RPMno + 550)) 
 {
  strip.clear();
  strip.fill(yd,6);   //yyyyyyyyyy------
  strip.show();
 }
 
if (sensorRPM > (RPMno + 550) && sensorRPM < (RPMno + 660)) 
 {
  strip.clear();
  strip.fill(yd,4);         //yyyyyyyyyyyy----
  strip.show();  
 }
 
if (sensorRPM > (RPMno + 660) && sensorRPM < (RPMno + 770)) 
 {
  strip.clear();
  strip.fill(rd,2);         //rrrrrrrrrrrrrr--         
  strip.show(); 
 }
 
if (sensorRPM > (RPMno + 770) && sensorRPM < (RPMno + 880)) 
 {
  strip.clear();
  strip.fill(rd,0);        //rrrrrrrrrrrrrr
  strip.show(); 
 } 
 
if (sensorRPM > (RPMno + 880) && sensorRPM < (RPMno + 1100)) 
 {
  strip.clear();
  strip.fill(wd);        //wwwwwwwwwwwwwwww 
  strip.show(); 
 }
}

//  _          _      __ _             _               
// | | ___  __| |___ / _\ |_ __ _ _ __| |_ _   _ _ __  
// | |/ _ \/ _` / __|\ \| __/ _` | '__| __| | | | '_ \ 
// | |  __/ (_| \__ \_\ \ || (_| | |  | |_| |_| | |_) |
// |_|\___|\__,_|___/\__/\__\__,_|_|   \__|\__,_| .__/ 
//                                             |_|    
void ledStartup(void)
{
  strip.fill(o);
  strip.show();
  delay(space);
  
  strip.fill(gd, 14);
  strip.show();
  delay(space);
  
  strip.fill(gd, 12);
  strip.show();
  delay(space);
  
  strip.fill(gd, 10); 
  strip.show();
  delay(space);
  
  strip.fill(yd,8);
  strip.show();
  delay(space);
  
  strip.fill(yd,6);
  strip.show();
  delay(space);
   
  strip.fill(yd,4);
  strip.show();
  delay(space);
  
  strip.fill(rd,2);
  strip.show();
  delay(space);
  
  strip.fill(rd);  
  strip.show();
  delay(space);
//---------------------------  
  strip.fill(wd);
  strip.show();
  delay(space*2);
//----------------------------
  strip.clear();
  strip.fill(rd);  
  strip.show();
  delay(space);

  strip.clear();
  strip.fill(rd,2);
  strip.show();
  delay(space);

  strip.clear();
  strip.fill(yd,4);
  strip.show();
  delay(space);

  strip.clear();
  strip.fill(yd,6);
  strip.show();
  delay(space);

  strip.clear();
  strip.fill(yd,8);
  strip.show();
  delay(space);
  
  strip.clear();
  strip.fill(gd, 10); 
  strip.show();
  delay(space);
  
  strip.clear();
  strip.fill(gd, 12);
  strip.show();
  delay(space);
  
  strip.clear();
  strip.fill(gd, 14);
  strip.show();
  delay(space);
  
  strip.fill(o);
  strip.show();
}

Nonsense. I have applications using tons of interrupts with NeoPixels, with zero problems. If your interrupts are causing a problem with NeoPixels, then you're doing something wrong in your interrupt handlers.

You seem to have many similar constructs. Here you should look at using ‘else if ’ statements to avoid unnecessary calculations after your match has been found.

It is not clear how you are calling the containing function but, if the engine speed does not change enough to force a new display pattern, you should avoid calling that function. Again to minimize unnecessary calculations.

The .show() function in the neopixels library turns off interrupts. I can see how it could get tangled up with the timer capture and overflow interrupts

How fast do you need to see the display change?
What happens if you call dimmer() every 250 ms on a millis() timer?

How fast do you need to update rpm? What if you only run the timer capture cycle periodically?

Perhaps you you run program like a sequential state change

  1. get rpm
    2)update median filter
    3)display results

While the low cost scopes can be useful but you have to know their limitations. Don't feel too bad as a full blown professional scope will have the same issue but at a much much smaller pulse width.

What is happening is the scope takes samples every xxxx seconds. Likely there is a fixed number of samples for the full screen. Then you set a time base with a lone time/division, some pulses occur in-between samples so they are not seen.

Without knowing how you are reading the signal on your Arduino it cannot be determined if the Arduino is experiencing the same issue.

You should also look in your scope manual and see how many samples / screen. You should be able to see how a pulse or pulses can be completely missed.

Well that's reassuring lol, thanks. So its just a matter of figuring out how to command the neopixels correctly, then.

Frankly I'm not sure precisely, but in theory the leds would update the fastest when in 1st gear, full throttle, as rpm will then increase the fastest. This isn't to say that in testing in neutral sometimes the revs will increase quicker (i set the base activation to 3000rpm for testing), but ideally neither scenarios should freeze up the system. Perhaps ill try and record some video of the tachometer to try and get an idea.

That's a good idea about the dimmer delay, im not exactly turning the headlights on and off all the time so there's no need to check for it constantly is there.

When I first created the sketch, a couple years ago, I attempted using 'else' if' statements and I distinctly remember them not working. That said, I am more knowledgable now so I'll give it a go. I understand what you're saying about minimizing calculations, I agree this could be improved.

Alright so update: I just went for a drive and its definitely something other than the timing code causing the freeze. I ran just the timing with serial output of the rpm, and it never froze up. It hanged sometimes when they revs changed too fast, but i know that's a tolerance thing.

I changed how the dimmer works, instead a boolean is stored and recalled every 250ms using millis(), the dimmer() void no longer calls the leds or ledsDimmed voids, instead an if/else statement for the dimmer boolean inside void loop() does instead, this way the leds are not showed through the dimmer function. I also managed to get else if statements for showing colors working.

But despite all of that, seemingly around 3000rpm the arduino just freezes up, and abruptly. The serial output shows a page full of numbers around 3000 and then the last output is 0.45, every time. I tried putting the if and else statements for leds() and ledsDimmed() between noInterrupts() and interrupts() and that didn't help.

Have you got access to a signal generator, by any chance? I ask because a car is electrically very noisy, and the last time I installed an Arduino in a vehicle, crashing was a major problem ('scuse the pun).

Apart from that, a variable frequency, variable m/s ratio square wave generator is what you need, and if you don't have one, kits are available very cheaply. Testing your device in your workshop (rather than in the car) has two major advantages: first, it would show whether it's a problem with the Arduino or due to the electrically noisy environment; second, debugging is vastly easier on your workbench.

Please post your latest version of the code.

So this is in a vehicle? Automotive electrical systems are extremely noisy.

Can you tell us:

  1. Physically where is pin 8 going?
  2. Where do you connect to ground in the vehicle?
  3. are you connected to ground in two places?
  4. Is your hardware a bare Nano connected to the ECM and the Neopixels?
  5. Do you have any filtering either on the 12V or the ECM in?
*/
// Measures the HIGH width, LOW width, frequency, and duty-cycle of a pulse train
// on Arduino NANO Pin 8 (ICP1 pin).
// Note: Since this uses Timer1, Pin 9 and Pin 10 can't be used for
// analogWrite().

// Adafruit NeoPixel RGBW strips (2x8 LEDS) 

#include <MedianFilterLib.h>

//MedianFilter < volatile > medianFilter ( 5 ); // 5 samples
volatile MedianFilter<uint32_t> medianFilter(5); // 5 samples
unsigned long pulse = 0;
float sensorRPM;
int RPMno = 4000; // base rpm for led activation
int space = 100; //delays for led startup sequence

//Input for headlight circuit
#define headlights 10
bool dimmer = LOW;
unsigned long millisDimmer = 0;

//NeoPixels
#include <Adafruit_NeoPixel.h>
#define PIN 6
#define NUM_LEDS 16
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_GRBW + NEO_KHZ800);
// full brightness
uint32_t r = strip.Color  (200, 0, 0, 0);
uint32_t g = strip.Color  (0, 180, 0, 0);
uint32_t b = strip.Color  (0, 0, 255, 0);
uint32_t w = strip.Color  (0, 0, 0, 120);
uint32_t y = strip.Color  (200, 120, 0, 0);
// dimmed
uint32_t rd = strip.Color  (8, 0, 0, 0);
uint32_t gd = strip.Color  (0, 7, 0, 0);
uint32_t bd = strip.Color  (0, 0, 10, 0);
uint32_t wd = strip.Color  (0, 0, 0, 3);
uint32_t yd = strip.Color  (10, 5, 0, 0);
// off/no color
uint32_t o = strip.Color   (0, 0, 0, 0);

bool ledState = 1;

unsigned long millisLeds = 0;

void setup()
{
  //initiate neopixels
   strip.begin();
  //set strip to nothing, flushes potential random colors
  strip.clear();
  strip.fill(o);
  strip.show();
  ledStartup();
   
  //assign input for relay
  pinMode(headlights, INPUT);
  digitalWrite(headlights, HIGH); //enables pullup resistor - HIGH on an input pin
                                 //also, it inverts signal: < 3v = HIGH, > 3v = LOW
  
  Serial.begin(9600);
  while (!Serial);

  // For testing, uncomment one of these lines and connect
  // Pin 3 or Pin 5 to Pin 8
  // analogWrite(3, 64);  // 512.00, 1528.00, 2040.00, 25.10%, 490.20 Hz
  // analogWrite(5, 64);  // 260.00, 764.00, 1024.00, 25.39%, 976.56 Hz

  noInterrupts ();  // protected code
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TIMSK1 = 0;

  TIFR1 |= _BV(ICF1); // clear Input Capture Flag so we don't get a bogus interrupt
  TIFR1 |= _BV(TOV1); // clear Overflow Flag so we don't get a bogus interrupt

  TCCR1B = _BV(CS10) | // start Timer 1, no prescaler
           _BV(ICES1); // Input Capture Edge Select (1=Rising, 0=Falling)

  TIMSK1 |= _BV(ICIE1); // Enable Timer 1 Input Capture Interrupt
  TIMSK1 |= _BV(TOIE1); // Enable Timer 1 Overflow Interrupt
  interrupts ();

  millisLeds = millis();
  millisDimmer = millis();
}

volatile uint16_t Overflows = 0;

ISR(TIMER1_OVF_vect)
{
  Overflows++;
}

ISR(TIMER1_CAPT_vect)
{
  static uint32_t previousRisingEdgeTime = 0;

  uint32_t thisRisingEdgeTime;
  uint16_t overflows = Overflows;

  // If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (ICR1 < 1024))
    overflows++;

  // Interrupted on Rising Edge
  thisRisingEdgeTime = overflows; // Upper 16 bits
  thisRisingEdgeTime = (thisRisingEdgeTime << 16) | ICR1;

  //uint32_t pulseDuration = thisRisingEdgeTime - previousRisingEdgeTime;
  pulse = thisRisingEdgeTime - previousRisingEdgeTime;
  previousRisingEdgeTime = thisRisingEdgeTime;

  // This is a good place to add 'pulseDuration' to the median filter.
  // Make sure the median filter is marked 'volatile'.
  //medianFilter.AddValue (pulseDuration);
}

void loop()
{
  static uint32_t previousMedian;
  uint32_t thisMedian;

  if ( millis() >= millisDimmer + 250)
   {
    dimmer = digitalRead(headlights);
    millisDimmer = millis();
   }
  
  noInterrupts();
  // This would be where you get 'thisMedian'
  // from the median filter.
  thisMedian = medianFilter.AddValue (pulse);
  interrupts();

  // If a sample has been measured
  if (thisMedian != previousMedian  && thisMedian >= 0.75*previousMedian)
   {
     // Display the pulse length in microseconds
     //Serial.print("Pulse time (microseconds): ");
     //Serial.println(thisMedian / 16.0, 2);
    
     //measure revs from pulse_length
     //first calculate how many sparks happening per second, 1000ms divided by delay between sparks
     float sparksPerSec = 1000/((thisMedian / 16.0)*0.001);
     //next multiply by 60 for sparks per minute - two revs per spark
     sensorRPM = sparksPerSec*120;
     Serial.println(sensorRPM);
     //Serial.println(thisMedian);
     previousMedian = thisMedian; 
    }

  noInterrupts();
  if (millis() >= millisLeds + 50)
  {
    if (dimmer = LOW){
     leds(); }
    else {
     ledsDimmed(); }  
     millisLeds = millis();
  }
  interrupts();
}


//__      ______ _____ _____  
// \ \    / / __ \_   _|  __ \ 
//  \ \  / / |  | || | | |  | |
//   \ \/ /| |  | || | | |  | |
//    \  / | |__| || |_| |__| |
//     \/   \____/_____|_____/ 


//     _ _                               
//  __| (_)_ __ ___  _ __ ___   ___ _ __ 
// / _` | | '_ ` _ \| '_ ` _ \ / _ \ '__|
//| (_| | | | | | | | | | | | |  __/ |   
// \__,_|_|_| |_| |_|_| |_| |_|\___|_|                             

//void dimmer (void)
//{
//  
// val = digitalRead(headlights);
//  if (val == LOW) leds();
//   else            ledsDimmed();
//   
//}


//  _          _     
// | | ___  __| |___ 
// | |/ _ \/ _` / __|
// | |  __/ (_| \__ \
// |_|\___|\__,_|___/  font = ogre
                                                                 
void leds(void) 
{  
  
if (sensorRPM > 0 && sensorRPM < RPMno) 
 {
  strip.clear();
  strip.fill(o); //----------------
  strip.show(); 
 }
 
else if (sensorRPM > RPMno && sensorRPM < (RPMno + 90)) 
 {
  strip.clear();
  strip.fill(g, 14);  //gg--------------
  strip.show(); 
 }
 
else if (sensorRPM > (RPMno + 90) && sensorRPM < (RPMno + 220)) 
 {
  strip.clear();
  strip.fill(g, 12);  //gggg------------
  strip.show(); 
  } 
  
else if (sensorRPM > (RPMno + 220) && sensorRPM < (RPMno + 330)) 
 {
  strip.clear();
  strip.fill(g, 10);  //gggggg----------
  strip.show(); 
  } 
  
else if (sensorRPM > (RPMno + 330) && sensorRPM < (RPMno + 440)) 
 { 
  strip.clear();
  strip.fill(y,8);   //yyyyyyyy--------         
  strip.show(); 
  } 
  
else if (sensorRPM > (RPMno + 440) && sensorRPM < (RPMno + 550)) 
 {
  strip.clear();
  strip.fill(y,6);   //yyyyyyyyyy------
  strip.show();
 }
 
else if (sensorRPM > (RPMno + 550) && sensorRPM < (RPMno + 660)) 
 {
  strip.clear();
  strip.fill(y,4);         //yyyyyyyyyyyy----
  strip.show();  
 }
 
else if (sensorRPM > (RPMno + 660) && sensorRPM < (RPMno + 770)) 
 {
  strip.clear();strip.fill(r,2);         //rrrrrrrrrrrrrr--         
  strip.show(); 
 }
 
else if (sensorRPM > (RPMno + 770) && sensorRPM < (RPMno + 880)) 
 {
  strip.clear();
  strip.fill(r);        //rrrrrrrrrrrrrr
  strip.show(); 
 } 
 
else if (sensorRPM > (RPMno + 880) && sensorRPM < (RPMno + 1100)) 
 {
  strip.clear();
  strip.fill(w);        //wwwwwwwwwwwwwwww 
  strip.show(); 
 }


else if (sensorRPM > (RPMno + 1100)) 
 {
  strip.clear();
  strip.fill(o);
  strip.show(); 
 }
 
}

//  _          _        ___ _                              _ 
// | | ___  __| |___   /   (_)_ __ ___  _ __ ___   ___  __| |
// | |/ _ \/ _` / __| / /\ / | '_ ` _ \| '_ ` _ \ / _ \/ _` |
// | |  __/ (_| \__ \/ /_//| | | | | | | | | | | |  __/ (_| |
// |_|\___|\__,_|___/___,' |_|_| |_| |_|_| |_| |_|\___|\__,_|
                                                          
void ledsDimmed(void)  
{  
  
if (sensorRPM > 0 && sensorRPM < RPMno) 
 {
  strip.clear();
  strip.fill(o); //----------------
  strip.show(); 
 }
 
else if (sensorRPM > RPMno && sensorRPM < (RPMno + 90)) 
 {
  strip.clear();
  strip.fill(gd, 14);  //gg--------------
  strip.show(); 
 }
 
else if (sensorRPM > (RPMno + 90) && sensorRPM < (RPMno + 220)) 
 {
  strip.clear();
  strip.fill(gd, 12);  //gggg------------
  strip.show(); 
  } 
  
else if (sensorRPM > (RPMno + 220) && sensorRPM < (RPMno + 330)) 
 {
  strip.clear();
  strip.fill(gd, 10);  //gggggg----------
  strip.show(); 
  } 
  
else if (sensorRPM > (RPMno + 330) && sensorRPM < (RPMno + 440)) 
 { 
  strip.clear();
  strip.fill(yd,8);   //yyyyyyyy--------         
  strip.show(); 
  } 
  
else if (sensorRPM > (RPMno + 440) && sensorRPM < (RPMno + 550)) 
 {
  strip.clear();
  strip.fill(yd,6);   //yyyyyyyyyy------
  strip.show();
 }
 
else if (sensorRPM > (RPMno + 550) && sensorRPM < (RPMno + 660)) 
 {
  strip.clear();
  strip.fill(yd,4);         //yyyyyyyyyyyy----
  strip.show();  
 }
 
else if (sensorRPM > (RPMno + 660) && sensorRPM < (RPMno + 770)) 
 {
  strip.clear();
  strip.fill(rd,2);         //rrrrrrrrrrrrrr--         
  strip.show(); 
 }
 
else if (sensorRPM > (RPMno + 770) && sensorRPM < (RPMno + 880)) 
 {
  strip.clear();
  strip.fill(rd);        //rrrrrrrrrrrrrr
  strip.show(); 
 } 
 
else if (sensorRPM > (RPMno + 880) && sensorRPM < (RPMno + 1100)) 
 {
  strip.clear();
  strip.fill(wd);        //wwwwwwwwwwwwwwww 
  strip.show(); 
 }

else if (sensorRPM > (RPMno + 1100)) 
 {
  strip.clear();
  strip.fill(o);
  strip.show(); 
 }
}

//  _          _      __ _             _               
// | | ___  __| |___ / _\ |_ __ _ _ __| |_ _   _ _ __  
// | |/ _ \/ _` / __|\ \| __/ _` | '__| __| | | | '_ \ 
// | |  __/ (_| \__ \_\ \ || (_| | |  | |_| |_| | |_) |
// |_|\___|\__,_|___/\__/\__\__,_|_|   \__|\__,_| .__/ 
//                                             |_|    
void ledStartup(void)
{
  strip.fill(o);
  strip.show();
  delay(space);
  
  strip.fill(gd, 14);
  strip.show();
  delay(space);
  
  strip.fill(gd, 12);
  strip.show();
  delay(space);
  
  strip.fill(gd, 10); 
  strip.show();
  delay(space);
  
  strip.fill(yd,8);
  strip.show();
  delay(space);
  
  strip.fill(yd,6);
  strip.show();
  delay(space);
   
  strip.fill(yd,4);
  strip.show();
  delay(space);
  
  strip.fill(rd,2);
  strip.show();
  delay(space);
  
  strip.fill(rd);  
  strip.show();
  delay(space);
//---------------------------  
  strip.fill(wd);
  strip.show();
  delay(space*2);
//----------------------------
  strip.clear();
  strip.fill(rd);  
  strip.show();
  delay(space);

  strip.clear();
  strip.fill(rd,2);
  strip.show();
  delay(space);

  strip.clear();
  strip.fill(yd,4);
  strip.show();
  delay(space);

  strip.clear();
  strip.fill(yd,6);
  strip.show();
  delay(space);

  strip.clear();
  strip.fill(yd,8);
  strip.show();
  delay(space);
  
  strip.clear();
  strip.fill(gd, 10); 
  strip.show();
  delay(space);
  
  strip.clear();
  strip.fill(gd, 12);
  strip.show();
  delay(space);
  
  strip.clear();
  strip.fill(gd, 14);
  strip.show();
  delay(space);
  
  strip.fill(o);
  strip.show();
}
  1. Pin 8 is not connected to anything, but pin 11/D8 is connected to the ECM ignition trigger wire IGT. See the schematic in my first post. That wire is still connected to D3, as thats where it was originally soldered to and I just added a jumper wire for testing of the timing code.
  2. Ground is connected to both the chassis (on the left side of nano), and also the ground of the 5v dc-dc converter on the right side (on the 5v side - 12v side of the converter is connected to chassis).
  3. See above.
  4. A bare nano, powered by a 5v dc-dc converter, connected to the ECM, the neopixels, and also the wire coming from the stereo headlight-trigger wire, voltage divided. Again, see schematic.
  5. Aside from a sole 250k resistor, no.

I reiterate that the RPM reading is working correctly now, and that the problem is no longer therein. Without any code for neopixels or headlights, it functions perfectly without issue.

You are doing quite a lot with interrupts being suspended. This cannot be correct.

Alright. I was just testing a theory and it didn't work anyway so it's gone.