Build: Arduino Tachometer (Reflective Style)

Disclosure: This post contains affiliate (and direct) links to some parts off Amazon for making a (relatively) hand-held device as well as the commercial product we replaced.

In the first book of the Maker’s End Inspiration Series, Arduino Inspiration, we’re controlling some computer fans to make a pleasant desk fan. To get some data on various fans, we needed a tachometer. So we wrote a different book. Because reasons.

Thus the first Maker’s End MEMO (Maker’s End Mini-Overview) publication, MEMO#001: Arduino, Interrupted, in which we provide a quick (but thorough) introduction to using interrupts on Arduinos of various flavors. As preparation for releasing a kit of the tachometer we started prototyping case designs in case we wanted to print the case instead of building a carrier plate for a commercially-available case (jury’s still out).

In this post we’ll take a quick look at the prototype case and code, discuss miniaturizing designs (spoiler: don’t!), and see where we go from here. There’s actually a video showing some of the design and wiring process, but it turned out to be really boring, so we didn’t post it :p

We whipped up a quick-and-dirty tach using a reflective sensor (read: when light bounces off of something back into the sensor it’s a “pulse” and we count it as a revolution) using the STEMTera for prototyping, then moved it as-is onto an Arduino Nano clone with USB power bank. (The kit will use a simple battery pack or LiPo.)

We 3D-printed a small mounting plate so we had something we could use and to start exploring the design space of such a small tool that still uses off-the-shelf parts without a custom PCB so we had a tool we could use around the shop. It’s roughly equivalent to a cheap “laser”-based reflective tachometer we also got off of Amazon as a simple way to test performance. Not quite as pretty, but customizable, accurate-enough, and a cute little project.

Any of these parts can be substituted. We built one with a different IR sensor and a white monochrome OLED, with essentially no code changes. We also have one that uses a 18650 or LiPo batter with a built-in charger and voltage converter–or just use a board with a built-in LiPo charger like an Adafruit Feather: we’ll post a followup article that uses a Feather that’ll send the data to your phone or to a computer for data storage as well.

Build the Circuit

There are two circuit “chunks”, both quite simple. First is hooking up the reflective sensor, second is hooking up the OLED. The Adafruit part comes with the resistors necessary for operation–we went ahead and made a slightly more “self-contained” sensor unit by soldering the resistors onto the sensor and running breadboard-ready wires for the initial prototype.

The OLED hookup, since it’s an I2C OLED, is as easy as hooking up Vcc and Gnd to the appropriate pins, and SCL/SDA to (in our case) the Uno (or Nano) A4/A5, the default I2C pins.

While we prototyped on an Uno, the “final” version uses a Nano (clone), so we’ll show that circuit–it’s the same on the Uno, though, in case you want a giant tachometer (or also go the smart route and prototype on something large).

Write the Code

The code is (roughly) as simple as you’d expect. There’s not a whole lot to talk about, but we’ll break it down anyway. The code for the kit version will be available on Github; the code here is just the prototyping to make sure it was accurate-enough.

Administrivia

It’s just a bunch of library headers (we don’t need all of them for our initial example, but the kit code uses them).

#include <Wire.h>

#include <gfxfont.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SPITFT.h>
#include <Adafruit_SPITFT_Macros.h>

#include <Adafruit_SSD1306.h>

// Useful constants
#define IR_LED  12    // Turns on IR emitter
#define IR_IN    2    // Input from IR sensor
#define DELAY  500    // Milliseconds between display updates

// Initialize OLED
// OLED_RESET is connected to SDA on OLED.
// NOTE: It's *analog* Pin 4 on the Arduino.
#define OLED_RESET 4  // Pin for OLED SDA connection
Adafruit_SSD1306 display(OLED_RESET);

// Revolution tick/pulse counter.
// NOTE: Volatile!
volatile unsigned int rev;

// For calculations/results.
unsigned long int rpm;      // Current RPM
unsigned long int maxRpm;   // Maximum calculated RPM
unsigned long int time;     // ... time.
unsigned long int prevTime; // The time before!

Interrupt Service Routine (ISR)

The ISR is the “meat” of the code even though it’s a single line long. ISRs can be confusing to new programmers, which is why MEMO#001: Arduino, Interrupted will exist–to demystify them. The only thing we do in the ISR is increment a value–the count of how many times we received a signal (pulse) from the IR sensor, which in turn pulses on each revolution of what we’re measuring.

void rpmCount() {
  rev++;
}

We’ll put up a few more posts on interrupts in general. Suffice to say that in Setup (below) we’ll tell the Arduino to run rpmCount every time we get a pulse, which just tells the code to add one (increment) the number of revolutions. We’ll see the rev value later, because we’ll clear it out when we display the current revolutions per minute (RPM).

Setup

Most of the setup method is self-explanatory: we do a bunch of stuff to the OLED so we can display the current RPM. We set a bunch of values to known startup values (zero in this case), set our IO pin directions, and turn on the IR emitter.

void setup() {
  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3c);

  display.clearDisplay(); // clearing the display
  display.setTextColor(WHITE); //setting the color
  display.setTextSize(1); //set the font size
  display.setCursor(0, 0); //set the cursor coordinates
  display.print("Tachyons are Go!");
  display.display();
  delay(500);

  // Start clean!
  rev    = 0;
  rpm    = 0;
  time   = 0;
  maxRpm = 0;

  // Set up IO
  pinMode(IR_IN, INPUT);
  pinMode(IR_LED, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  // Set up interrupt handler
  attachInterrupt(digitalPinToInterrupt(2), rpmCount, FALLING);
  
  digitalWrite(IR_LED, HIGH);   // turn the LED on (HIGH is the voltage level)
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
}

The only thing of note is this:

attachInterrupt(digitalPinToInterrupt(2), rpmCount, FALLING);

attachInterrupt “hooks up” an Arduino (really the processor interrupt) to a given pin, runs the provided code (rpmCount), every time the signal rises, falls, or changes: here we care when the pin changes from high (5V) to low (0V) because that’s what the sensor is wired to do.

The digitalPinToInterrupt function allows us to provide the Arduino pin number instead of knowing what CPU-level interrupt is used for a given pin. This can be different across Arduino boards (and non-Arduino boards programmed in the Arduino environment), so it’s a safer bet to use this convenience method.

Loop

The “meat” lives in loop as with many an Arduino sketch. And yes, we’re using delay here–sorry! You’ll undoubtedly will read (or have read already) that using delay is generally a bad practice, and for the kit code with a UX, it’ll go away (and it has to!). But for prototyping a simple tach with no UX it’s fine. We’ll be discussing this, and how to avoid delay, in MEMO#001: Arduino, Interrupted and in subsequent posts.

void loop() {
  delay(DELAY);

  detachInterrupt(digitalPinToInterrupt(2));

  long currTime = millis();
  long idleTime = currTime - prevTime;

  rpm = 30 * 1000 / (millis() - time) * rev * 2;

  if (rpm > maxRpm) {
    maxRpm = rpm;
  }

  time = millis();
  rev = 0;

  display.clearDisplay(); // clearing the display
  display.setTextColor(WHITE); //setting the color
  display.setTextSize(1); //set the font size
  display.setCursor(0, 0); //set the cursor coordinates
  display.print(rpm);
  display.display();

  prevTime = currTime;

  attachInterrupt(digitalPinToInterrupt(2), rpmCount, FALLING);
}

Most everything is related to talking to the OLED, but note we detach the interrupt via detachInterrupt before performing the RPM calculation and write to the OLED: this results in missed pulses for the duration of that code, but we don’t want to interrupt writing to the OLED or update the rev count during the calculation.

Usage

Because it’s a reflective tach whatever you’re measuring will need something shiny. We stuck on some reflective tape that came with the cheap reflective tach we purchased on Amazon.

Fan showing how to place shiny tape
Fan with Shiny Tap

Some things won’t need additional shiny, like a disc with holes in it, or may need to work the opposite way, e.g., you’ll need to stick black tape on in order to break the beam. Some measurements would require handling multiple pulses-per-rev. The code handles none of this, nor is there a way to switch modes–when the kit is released we’ll have UX to handle at least some of these concerns.

How Accurate Is It?

We tested against a cheap reflective tach (link in Products section) and were consistently within 1-5%, so it’s “accurate enough” for what we needed. With minor code tweaking it should be pretty much dead-on, but for quick checks of computer fans, an electric drill, and a Dremel tool, we were pleasantly surprised with how close it was to a cheap (albeit trashy) commercial model. Plus we can customize it to our heart’s content, which is never a bad thing.

Is It Worth Building?

Debatable, but ultimately, we think so. Our cheap commercial tachometer was $20 and the only thing our hacked version doesn’t have (but is trivial to add) is storing the max, min, and last measurement (although we’d need to add some non-volatile storage, or keep the device on in a power-sip mode).

Our build was roughly $10 (we over-spent on the OLED, but it was what we had laying around when we needed it) and could probably be even cheaper. If you bump up the cost to $20, we could add more functionality:

  • More storage (via an SD card)
  • Wireless connectivity (via WiFi, BLE, or other radio)
  • Swappable inputs (via a jack for various sensors, like break-beam)
  • Software continuously hackable
  • Multiple power options

We might switch to a different board for some of this, which would increase the price, but something like an Adafruit Feather or a random ESP board with on-board WiFi is still really cheap, and opens up a world of remote monitoring possibilities.

BUILD FTW!!1!

Resources

Products

Tell us what you think! (But be nice.)

This site uses Akismet to reduce spam. Learn how your comment data is processed.