The Tachometer code is relatively simple.  We'll explain each section in detail.  But if you just want to get up and running, you can skip down to "The Complete Code" and continue from there.

Libraries and Variables:

We’ll start by including the necessary libraries, and defining some global variables.  Note that some of these are defined as ‘volatile’.  These variables are accessed both in the main loop and in the interrupt handler.  The ‘volatile’ modifier instructs the compiler that these variables can change at any time and it should not attempt to optimize the code it generates for them

Download: file
#include <Wire.h>
#include "Adafruit_LEDBackpack.h"

// External interrpt pin for sensor
#define interruptPin 3

Adafruit_7segment matrix = Adafruit_7segment();

long lastUpdate = 0;  // for timing display updates
volatile long accumulator = 0;  // sum of last 8 revolution times
volatile unsigned long startTime = 0; // start of revolution in microseconds
volatile unsigned int revCount = 0; // number of revolutions since last display update

Setup():

In the setup code, we initialize the display, attach the interrupt handler and set the input mode of the pin we are using for the sensor.

The pinMode() function sets the mode to Input, but also enables the internal pullup resistor.  This assures that the sensor reading will be a logic HIGH until it detects a reflection from the target on the drive spindle.

The call to attachInterrupt() specifies the interrupt pin we are using, the address of the interrupt handler and whether we want to trigger on the rising or falling edge of the signal.  Since we are just counting one pulse per revolution, we don't really care if it is triggered on the rising edge or falling edge.

Download: file
//-------------------------------------
// Setup - runs once at startup
//-------------------------------------
void setup()
{
  // Initialize the serial port and display
  Serial.begin(115200);
  matrix.begin(0x70);

  // Enable the pullup resistor and attach the interrupt
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), tach_interrupt, FALLING);
}

The Interrupt Handler:

The interrupt handler is invoked once per revolution as triggered by the sensor.  It calculates the number of microseconds since the last interrupt and adds that to an ‘accumulator’.   But before adding, we subtract 1/8 of the accumulator value.  So when the speed stabilizes, the accumulator will converge on the sum of the times for the last 8 revolutions.

Download: file
//-------------------------------------
// Interrupt Handler
// IR reflective sensor - target passed
// Calculate revolution time
//-------------------------------------
void tach_interrupt()
{
  // calculate the microseconds since the last interrupt
  long usNow = micros();
  long elapsed = usNow - startTime;
  startTime = usNow;  // reset the clock
  
  // Accumulate the last 8 interrupt intervals
  accumulator -= (accumulator >> 3);
  accumulator += elapsed;
  revCount++;
}

The Loop:

Once per second, the loop code calculates the rpm based on the accumulator value and updates the display.

Download: file
//-------------------------------------
// Main Loop - runs repeatedly.
// Calculate RPM and Update LCD Display
//-------------------------------------
void loop()
{
  if (millis() - lastUpdate > 1000) // update every second
  {
    unsigned int rpm = 0;
    // divide number of microseconds in a minute, by the average interval.
    if (revCount > 0)
    {
      rpm = 60000000 / (accumulator>>3);
    }
    
    Serial.println(rpm);
    matrix.println(rpm);
    matrix.writeDisplay();
    
    lastUpdate = millis();
    revCount = 0;
  }
}

The Complete Code:

Copy and paste this into your Arduino IDE and upload to the Pro Trinket.

Download: file
//-------------------------------------
// Pro Trinket Tachometer
//-------------------------------------
#include <Wire.h>
#include "Adafruit_LEDBackpack.h"

// External interrpt pin for sensor
#define interruptPin 3

Adafruit_7segment matrix = Adafruit_7segment();

long lastUpdate = 0;  // for timing display updates
volatile long accumulator = 0;  // sum of last 8 revolution times
volatile unsigned long startTime = 0; // start of revolution in microseconds
volatile unsigned int revCount = 0; // number of revolutions since last display update

//-------------------------------------
// Setup - runs once at startup
//-------------------------------------
void setup()
{
  // Initialize the serial port and display
  Serial.begin(115200);
  matrix.begin(0x70);

  // Enable the pullup resistor and attach the interrupt
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), tach_interrupt, FALLING);
}

//-------------------------------------
// Main Loop - runs repeatedly.
// Calculate RPM and Update LCD Display
//-------------------------------------
void loop()
{
  if (millis() - lastUpdate > 1000) // update every second
  {
    unsigned int rpm = 0;
    // divide number of microseconds in a minute, by the average interval.
    if (revCount > 0)
    {
      rpm = 60000000 / (accumulator>>3);
    }
    
    Serial.println(rpm);
    matrix.println(rpm);
    matrix.writeDisplay();
    
    lastUpdate = millis();
    revCount = 0;
  }
}

//-------------------------------------
// Interrupt Handler
// IR reflective sensor - target passed
// Calculate revolution time
//-------------------------------------
void tach_interrupt()
{
  // calculate the microseconds since the last interrupt
  long usNow = micros();
  long elapsed = usNow - startTime;
  startTime = usNow;  // reset the clock
  
  // Accumulate the last 8 interrupt intervals
  accumulator -= (accumulator >> 3);
  accumulator += elapsed;
  revCount++;
}

This guide was first published on Jul 09, 2017. It was last updated on Jul 09, 2017.

This page (Code Design) was last updated on Oct 15, 2020.