Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
John Bradnam
Published © GPL3+

Stroboscope

A simple tool to measure speeds of rotating objects

IntermediateFull instructions provided12 hours1,596
Stroboscope

Things used in this project

Hardware components

Microchip ATtiny1614 Microprocessor
×1
OLED Display 128x32 0.91 inches with I2C Interface
DIYables OLED Display 128x32 0.91 inches with I2C Interface
Also include a 4-pin female pin header (6mm high variant NOT the standard 8.5mm one)
×1
8mmx8mm switches with button tops
3 x momentary push buttons, 1 x latched push button
×1
1W White LED with star heatsink
×1
Aluminum Heatsink 20mm wide x 6mm high
Search for "5pcs/lot 150x20x6 MM 3 x 1W LEDs Heatsink Aluminum Heat Sink Radiator for IC Cooling Electronic Chipset Heat Dissipation"
×1
PT4115E LED Driver
×1
Other components
1 x 2K7 1206 resistor, 1 x 8K2 1206 resistor, 1 x 0.33R 2512 resistor, 1 x 68uH CD75 inductor, 1 x 47uF/16V 3528 tantalum capacitor, 2 x 0.1uF 1206 capacitors, 1 x 3V9 SOD8C Zener diode, 1 x 1N5914 DO-214AC Schottky diode, 1 x 78M05 TO252 5V Regulator, 1 x DC power socket
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

Files for slicer software

Schematics

Schematic

PCB

Eagle files

Schematic & PCB in Eagle format

Code

StroboscopeV1.ino

Arduino
// Stroboscope V1
// by John Bradnam
//
// V1: jlb (jbrad2089@gmail.com)
//  - Wrote software for a ATtiny1614 and 128x32 I2C OLED display
//  - Limited range from 1Hz to 500Hz (60rpm to 30,000rpm)

/**
 * ATTiny1614 Pins mapped to Ardunio Pins
 *
 *             +--------+
 *         VCC + 1   14 + GND
 * (SS)  0 PA4 + 2   13 + PA3 10 (SCK)
 *       1 PA5 + 3   12 + PA2 9  (MISO)
 * (DAC) 2 PA6 + 4   11 + PA1 8  (MOSI)
 *       3 PA7 + 5   10 + PA0 11 (UPDI)
 * (RXD) 4 PB3 + 6    9 + PB0 7  (SCL)
 * (TXD) 5 PB2 + 7    8 + PB1 6  (SDA)
 *             +--------+
 *
 * Board: ATtiny3224/1624/1614/1604/824/814/804/424/414/404/...
 * Chip: ATtiny1614
 * Clock: 20MHz internal
 */

#include <U8g2lib.h>
#include "Button.h"

#define ON_BTN 0        //PA4
#define DOWN_BTN 1      //PA5
#define UP_BTN 2        //PA6
#define RANGE_BTN 3     //PA7
#define LED_PIN 10      //PA3

U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

//Range values
enum rangeEnum { X1, X10, X100 };

//Button Management
void rangeButtonPressed(void);
void downButtonPressed(void);
void upButtonPressed(void);

Button* rangeButton;
Button* downButton;
Button* upButton;

int32_t speed = 50;       //50Hz
int32_t delta = 1000/50;  //20mS
rangeEnum range = X1;

char textBuffer[20];      //Used for formatting text

//-----------------------------------------------------------------------------
//Initialise Hardware
void setup() 
{
  //Serial.begin(9600);

  //Initialise LED pin
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  //Initialse buttons
  pinMode(ON_BTN, INPUT_PULLUP);
  rangeButton = new Button(RANGE_BTN);
  rangeButton->Repeat(rangeButtonPressed);
  downButton = new Button(DOWN_BTN);
  downButton->Repeat(downButtonPressed);
  upButton = new Button(UP_BTN);
  upButton->Repeat(upButtonPressed);

  //Setup display
  u8g2.begin();
  u8g2.setFont(u8g2_font_helvB12_tr); // helvetica bold
  u8g2.drawStr(20,14,"ATtiny1614");   // x,y,text
  u8g2.drawStr(15,29,"Stroboscope");
  u8g2.sendBuffer();                // transfer internal memory to the display
  delay(3000);
  updateDisplay();
}

//-------------------------------------------------------------------------
// Handle main loop

void loop() 
{
  rangeButton->Pressed();
  downButton->Pressed();
  upButton->Pressed();
  
  if (digitalRead(ON_BTN) == LOW)
  {
    //Switch on LED for 200uS
    PORTA.OUTSET = PIN3_bm;
    delayMicroseconds(200);
    PORTA.OUTCLR = PIN3_bm; 
  }
  delay(delta);
}

//---------------------------------------------------------------
// Handle Menu button
void rangeButtonPressed()
{
  range = (range == X100) ? X1 : (rangeEnum)((int)range + 1);
  updateDisplay();
}

//---------------------------------------------------------------
// Handle DOWN button
void downButtonPressed()
{
  uint16_t multipler = round(pow(10, (int)range));
  int32_t lastSpeed = speed;
  speed = min(500, speed + multipler);
  if (speed != lastSpeed)
  {
    delta = 1000/speed;
    updateDisplay();
  }
}

//---------------------------------------------------------------
//Handle UP btton
void upButtonPressed()
{
  uint16_t multipler = round(pow(10, (int)range));
  int32_t lastSpeed = speed;
  speed = max(1, speed - multipler);
  if (speed != lastSpeed)
  {
    delta = max(1, 1000/speed);
    updateDisplay();
  }
}

//---------------------------------------------------------------
// Update the display
void updateDisplay()
{    
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_helvB12_tr); // helvetica bold
  uint16_t multipler = round(pow(10, (int)range));
  sprintf(textBuffer,"X%u",multipler);
  u8g2.drawStr(0,14,textBuffer);
  formatCommas(speed,textBuffer);
  strcat(textBuffer, " Hz");
  int twidth = u8g2.getStrWidth(textBuffer);
  int tx = max(128 - twidth, 0);
  u8g2.drawStr(tx,14,textBuffer);
  formatCommas(speed * 60,textBuffer);
  strcat(textBuffer, " rpm");
  twidth = u8g2.getStrWidth(textBuffer);
  tx = max(128 - twidth, 0);
  u8g2.drawStr(tx,29,textBuffer);
  u8g2.sendBuffer();                // transfer internal memory to the display
}

//---------------------------------------------------------------
// format a number with commas
void formatCommas(int32_t n, char *out)
{
  int c;
  char buf[20];
  char *p;
  
  sprintf(buf, "%ld", n);
  c = 2 - strlen(buf) % 3;
  for (p = buf; *p != 0; p++) 
  {
    *out++ = *p;
    if (c == 1) 
    {
       *out++ = ',';
    }
    c = (c + 1) % 3;
  }
  *--out = 0;
}

Button.h

C Header File
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#pragma once
#include "Arduino.h"

#define DEBOUNCE_DELAY 15

//Repeat speed
#define REPEAT_START_SPEED 500
#define REPEAT_INCREASE_SPEED 25
#define REPEAT_MAX_SPEED 5

class Button
{
	public:
	//Simple constructor
	Button(int pin);
	Button(int name, int pin);
	Button(int name, int pin, int analogLow, int analogHigh, bool activeLow = true);

  //Background function called when in a wait or repeat loop
  void Background(void (*pBackgroundFunction)());
	//Repeat function called when button is pressed
  void Repeat(void (*pRepeatFunction)());
	//Test if button is pressed
	bool IsDown(void);
	//Test whether button is pressed and released
	//Will call repeat function if one is provided
	bool Pressed();
	//Return button state (HIGH or LOW) - LOW = Pressed
	int State();
  //Return button name
  int Name();

	private:
		int _name;
		int _pin;
		bool _range;
		int _low;
		int _high;
		bool _activeLow;
		void (*_repeatCallback)(void);
		void (*_backgroundCallback)(void);
};

Button.cpp

C/C++
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#include "Button.h"

Button::Button(int pin)
{
	_name = pin;
	_pin = pin;
	_range = false;
	_low = 0;
	_high = 0;
	_backgroundCallback = NULL;
	_repeatCallback = NULL;
	pinMode(_pin, INPUT_PULLUP);
}

Button::Button(int name, int pin)
{
	_name = name;
	_pin = pin;
	_range = false;
	_low = 0;
	_high = 0;
	_backgroundCallback = NULL;
	_repeatCallback = NULL;
	pinMode(_pin, INPUT_PULLUP);
}

Button::Button(int name, int pin, int analogLow, int analogHigh, bool activeLow)
{
	_name = name;
	_pin = pin;
	_range = true;
	_low = analogLow;
	_high = analogHigh;
	_activeLow = activeLow;
	_backgroundCallback = NULL;
	_repeatCallback = NULL;
	pinMode(_pin, INPUT);
}

//Set function to invoke in a delay or repeat loop
void Button::Background(void(*pBackgroundFunction)())
{
	_backgroundCallback = pBackgroundFunction;
}

//Set function to invoke if repeat system required
void Button::Repeat(void(*pRepeatFunction)())
{
	_repeatCallback = pRepeatFunction;
}

bool Button::IsDown()
{
	if (_range)
	{
		int value = analogRead(_pin);
		return (value >= _low && value < _high);
	}
	else
	{
		return (digitalRead(_pin) == LOW);
	}
}

//Tests if a button is pressed and released
//  returns true if the button was pressed and released
//	if repeat callback supplied, the callback is called while the key is pressed
bool Button::Pressed()
{
	bool pressed = false;
	if (IsDown())
	{
		unsigned long wait = millis() + DEBOUNCE_DELAY;
		while (millis() < wait)
		{
			if (_backgroundCallback != NULL)
			{
				_backgroundCallback();
			}
		}
		if (IsDown())
		{
			//Set up for repeat loop
			if (_repeatCallback != NULL)
			{
				_repeatCallback();
			}
			unsigned long speed = REPEAT_START_SPEED;
			unsigned long time = millis() + speed;
			while (IsDown())
			{
				if (_backgroundCallback != NULL)
				{
					_backgroundCallback();
				}
				if (_repeatCallback != NULL && millis() >= time)
				{
					_repeatCallback();
					long faster = speed - REPEAT_INCREASE_SPEED;
					speed = (faster >= REPEAT_MAX_SPEED) ? faster : REPEAT_MAX_SPEED;
					time = millis() + speed;
				}
			}
			pressed = true;
		}
	}
	return pressed;
}

//Return current button state
int Button::State()
{
	if (_range)
	{
		int value = analogRead(_pin);
		if (_activeLow)
		{
			return (value >= _low && value < _high) ? LOW : HIGH;
		}
		else
		{
			return (value >= _low && value < _high) ? HIGH : LOW;
		}
	}
	else
	{
		return digitalRead(_pin);
	}
}

//Return current button name
int Button::Name()
{
	return _name;
}

Credits

John Bradnam

John Bradnam

147 projects • 181 followers

Comments