Counting and displaying RPM

This page contains the information on all activities related to counting and displaying the number of revolutions per minute.


05/30/2015: First Prototype

The idea for his first prototype was that we would have some sort of device that would generate a square pulse for each engine revolution. By measuring the time between two pulses (the t in figure 1 below) we could easily compute the number of revolutions per minutes (RPM).

Measuring elapsed time between two pulses
Figure 1 - Measuring elapsed time between two pulses

Inspired by a robotic lab at high school where he had to measure the length of a pulse in order to evaluate the distance measured by a HC-SR04 sonar, Étienne wrote an Arduino sketch for measuring the time between two pulses, convert it to RPM, and display it on a LCD. The code for this sketch is shown in figure 2.

#include 
#include 
#include 
LiquidCrystal_I2C lcd(0x27,16,2);

void setup()
{
  lcd.init();
  lcd.backlight();
  pinMode(3,INPUT);
}

int RPM()
{
  unsigned long gap = pulseIn(3,LOW) + pulseIn(3,HIGH);
  int rpm = 0;
  if (gap != 0)
  {
    rpm = 60000000/gap;
  }
  return rpm;
}

void loop()
{
  int rpm = RPM();
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(rpm);
  delay(500);
}
Figure 2 - Code for counting RPM

In order to test the RPM counting sketch, Étienne wrote a second sketch for generating pulses at specific frequencies. The code for this sketch is shown in figure 3.

void setup()
{
  pinMode(3,OUTPUT);
}

int rpm = 2000;
unsigned long gap = 60000/rpm;
  
void loop()
{
  digitalWrite(3,LOW);
  delay(gap);
  digitalWrite(3,HIGH);
  delay(1);
}
Figure 3 - Code for generating pulses

The figure 4 shows two Arduinos running the two sketches. The Arduino connected to a PC with a USB cable is the pulse generator. The Arduino connected to the display is the one that measure the elapsed time between two pulses and convert that to RPM.

Initial prototype
Figure 4 - Two Arduinos: the pulse generator and the RPM counter

Using the PC, Étienne ran the generator at different frequencies (by modifying the rpm variable in the sketch) and he notices that the displayed RPM was not exactly matching the frequency of the generator. This is due to the timing of some instructions that is not accounted for. We will fix that at a later time.


06/20/2015: Using a 3.3v Arduino

We used a 3.3v Arduino mini pro instead of the original 5v Arduino, and we added a 5v to 3.3v level converter between the Arduino and the LCD. We are running the same code as in the original prototype. Nothing else was changed.

RPM counting using a 3.3v Arduino
Figure 5 - RPM counting using a 3.3v Arduino

The top Arduino Uno (in blue) is running the pulse generator sketch. The 3.3v Arduino mini pro (with the red led on the bread board) is running the RPM counting sketch. The cable attached to the LCD is leading to the level converter in the middle of the screen.


06/27/2015: Count pulses and compute RPM with interrupt

This week, we modified the code to use an ISR (interrupt service routine) to count the RPM. In this version of the code, we use the pin 8 to receive pulses from the engine. Here is a brief description of the code is working:

The complete code could be found in the figure 6 below.

#include 
#include 

// Number of pulses to use while computing the RPM.
#define PULSE_COUNT   16

LiquidCrystal_I2C lcd(0x27, 16, 2);

// Count of pulses from the engine.
static uint32_t pulseCount = 0;

// Current RPM value computed with the elapsed time of the last 16 pulses. 
static uint16_t rpm = 0;

// Get the count of pulses from the engine.
uint32_t getPulseCount()
{
  // Disable interrupts while we read the count value. This way we will be sure that the ISR will not change it while we are reading it.
  cli();
  uint32_t count = pulseCount;
  sei();
  return count;
}

// Get the current RPM value.
uint16_t getRPM()
{
  // Disable interrupts while we read the RPM value. This way we will be sure that the ISR will not change it while we are reading it.
  cli();
  uint16_t value = rpm;
  sei();
  return value;
}

void setup()
{
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();

  // We count engine pulse on pin 8.
  pinMode(8, INPUT);

  // Allow pin change interrupts for PB0 (digital pin 8).  
  PCMSK0 = (1 << PINB0);

  // Enable pin change interrupt 0.
  PCICR = (1 << PCIE0);
}

// Interrupt service routine for the interrupt 0.
// This function is called every time the state of pin 8 changes.
ISR(PCINT0_vect)
{
  static unsigned long previousMicros[PULSE_COUNT] = {0};   // Time in microseconds for the last PULSE_COUNT pulses.
  static uint8_t index = 0;                                 // Current index in previousMicros.
  static uint8_t previousState = 0;                         // Previous state of the pin 8.
  uint8_t state = (PINB >> PINB0) & 0x01;                   // Current state of the pin 8.
  
  if (state != previousState) {
    // Count only changes from 0 to 1.
    if (state != 0) {
      ++pulseCount;

      // Increment the index in our array of previous times in microseconds.
      if (++index >= PULSE_COUNT) {
        index = 0;      
      }

      // Compute the elapsed time in microseconds for the last PULSE_COUNT pulses.
      unsigned long currentMicros = micros();
      unsigned long elapsedMicros = currentMicros - previousMicros[index];
      previousMicros[index] = currentMicros;

      // Compute the RPM using the elapsed time in microseconds for the last PULSE_COUNT pulses.
      rpm = (uint16_t)((60 * 1000000UL * PULSE_COUNT + elapsedMicros / 2) / elapsedMicros);
    }
    previousState = state;
  }
}

void loop()
{
  uint16_t rpm = getRPM();
  unsigned long pulseCount = getPulseCount();
  
  Serial.print(rpm);
  Serial.print(" ");
  Serial.println(pulseCount);
  
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(rpm);
  lcd.setCursor(0,1);
  lcd.print(pulseCount);
  
  delay(500);
}
Figure 6 - Complete code for measuring the engine's RPM using an ISR

07/11/2015: Pulse generator using a timer

This week, we modified the code of the pulse generator hoping to gain some accuracy. The pulse generator is used to simulate engine pulses for testing the RPM counter module. In this version of the code, we use used a timer and an ISR (interrupt service routine) that is called every time the timer's counter reaches its maximum value. Here is a brief description of how the code is working:

The complete code could be found in the figure 7 below.

#include "TimerOne.h"

int defaultRPM = 2000;

void setupTimer(unsigned long timeInUs)
{
  Timer1.initialize(timeInUs);       // Initialize timer1, and set a timeInUs micro second period.
  Timer1.attachInterrupt(timerISR);  // Attaches timerISR() as a timer overflow interrupt.
}

void setRPM(int RPM)
{
  unsigned long timeInUs = 60000000 / RPM;
  setupTimer(timeInUs);
  Serial.println(timeInUs);
}

void timerISR()
{
  sei();
  digitalWrite(3, HIGH);
  delay(1); // Stay high for 1 ms.
  digitalWrite(3, LOW);
}

void setup()
{
  pinMode(3, OUTPUT);
  Serial.begin(9600);
  setRPM(defaultRPM);
}

void loop()
{
  // Read a new RPM value.
  String line = Serial.readStringUntil('\n');
  int RPM = line.toInt();
  
  if (line != "") {
   if (RPM == 0) {
    Serial.println("error");
   } else {
    Serial.print("setting RPM to ");
    Serial.println(RPM);
    
    // Set timer to generate pulses corresponding to the given RPM.
    setRPM(RPM);
   }
  }
}
Figure 7 - Complete code for generating pulses using a timer and an ISR

When we first checked the output pulse on an oscilloscope we found that the pulse duration in millisecond was not exactly what was expected.

After adding some debugging code for checking the time in microseconds needed to generate 1000 pulses, we found a match between the expected time in microseconds measured by the Arduino and the expected duration of the pulses. This was suggesting that the code is ok and the problem lies somewhere else.

After some reading on the web, we found that the cheaper versions of the Arduino are using ceramic resonators instead of crystals for generating the clock. This leads to small timing errors.

Here is one of the nice references we found on that subject: Arduino clock frequency stability.