You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
8.4 KiB
172 lines
8.4 KiB
// Adafruit Circuit Playground Fidget Spinner Tachometer
|
|
//
|
|
// This sketch uses the light sensor built in to Circuit Playground
|
|
// to detect the speed (in revolutions per second) of a fidget spinner.
|
|
// Load the sketch on a Circuit Playground (either classic or express)
|
|
// and notice the first three NeoPixels will turn on bright white.
|
|
// Hold a spinning fidget spinner very close to (but not touching) the
|
|
// light sensor (look for the eye graphic on the board, it's right
|
|
// below the three lit NeoPixels) and look at the serial monitor
|
|
// to see it print out the speed of the spinner. This works best
|
|
// holding the spinner perpendicular to the sensor, like:
|
|
// ||
|
|
// || <- Spinner
|
|
// ||
|
|
// ________ <- Circuit Playground
|
|
//
|
|
// Author: Tony DiCola
|
|
// License: MIT License (https://en.wikipedia.org/wiki/MIT_License)
|
|
#include <Adafruit_CircuitPlayground.h>
|
|
|
|
// Configuration values. You don't have to change these but they're
|
|
// interesting to adjust the logic. If your spinner has more or less
|
|
// than three arms be sure to change the SPINNER_ARMS value below:
|
|
#define SPINNER_ARMS 3 // Number of arms on the fidget spinner.
|
|
// This is used to calculate the true
|
|
// revolutions per second of the spinner
|
|
// as one full revolution of the spinner
|
|
// will actually see this number of cycles
|
|
// pass by the light sensor. Set this to
|
|
// the value 1 to ignore this calculation
|
|
// and just see the raw cycles / second.
|
|
|
|
#define SAMPLE_DEPTH 512 // How many samples to take when measuring
|
|
// the spinner speed. The larger this value
|
|
// the more memory that will be consumed but
|
|
// the slower a spinner speed that can be
|
|
// detected (larger sample depths mean longer
|
|
// period waves can be detected). You're
|
|
// limited by the amount of memory on the
|
|
// board for this value. On a classic
|
|
// Circuit Playground with 2kb of memory
|
|
// you can only go up to about 512 or so (each
|
|
// sample is 2 bytes, so 1kb of space total).
|
|
// On an express board you can go much higher,
|
|
// like up to 10240 for 20kb of sample data.
|
|
|
|
#define SAMPLE_PERIOD_US 1500 // Amount of time in microseconds to delay
|
|
// between each light sensor sample. This is
|
|
// a balance between how fast and slow of
|
|
// a spinner reading that can be measured.
|
|
// The higher this value the slower a spinner
|
|
// you can measure, but at the tradeoff of
|
|
// not accurately measuring fast spinners.
|
|
// Low values (even 0) mean very fast speeds
|
|
// can be detected but slow speeds (below 10hz)
|
|
// are harder to detect. You can increase the
|
|
// sample depth to help improve the range
|
|
// of detection speeds, but there's a limit
|
|
// based on the memory available.
|
|
|
|
#define THRESHOLD 400 // How big the amplitude of a cyclic
|
|
// signal has to be before the measurement
|
|
// logic kicks in. This is a value from
|
|
// 0 to 1023 and might need to be adjusted
|
|
// up or down if the detection is too
|
|
// sensitive or not sensitive enough.
|
|
// Raising this value will make the detection
|
|
// less sensitive and require a very large
|
|
// difference in amplitude (i.e. a very close
|
|
// or highly reflective spinner), and lowering
|
|
// the value will make the detection more
|
|
// sensitive and potentially pick up random
|
|
// noise from light in the room.
|
|
|
|
#define MEASURE_PERIOD_MS 1000 // Number of milliseconds to wait
|
|
// between measurements. Default is
|
|
// one second (1000 milliseconds).
|
|
|
|
|
|
void setup() {
|
|
// Initialize serial monitor at 115200 baud.
|
|
Serial.begin(115200);
|
|
|
|
// Initialize CircuitPlayground library and turn on the first 3 pixels (near the light sensor)
|
|
// to pure white at maximum brightness.
|
|
CircuitPlayground.begin(255); // 255 means set pixel colors to max brightness.
|
|
CircuitPlayground.clearPixels();
|
|
CircuitPlayground.setPixelColor(0, 255, 255, 255); // Set pixel 0, 1, 2 to white.
|
|
CircuitPlayground.setPixelColor(1, 255, 255, 255);
|
|
CircuitPlayground.setPixelColor(2, 255, 255, 255);
|
|
}
|
|
|
|
|
|
void loop() {
|
|
// Pause between measurements.
|
|
delay(MEASURE_PERIOD_MS);
|
|
|
|
// Collect samples from the light sensor as quickly as possible.
|
|
// Keep track of the amount of time (in microseconds) between samples
|
|
// so the sampling frequency can later be determined.
|
|
uint16_t samples[SAMPLE_DEPTH] = {0};
|
|
uint32_t start = micros();
|
|
for (int i=0; i<SAMPLE_DEPTH; ++i) {
|
|
samples[i] = CircuitPlayground.lightSensor();
|
|
delayMicroseconds(SAMPLE_PERIOD_US);
|
|
}
|
|
uint32_t elapsed_uS = micros() - start;
|
|
float elapsed = elapsed_uS / 1000000.0; // Elapsed time in seconds.
|
|
|
|
// Find the min and max values in the collected samples.
|
|
uint16_t minval = samples[0];
|
|
uint16_t maxval = samples[0];
|
|
for (int i=1; i<SAMPLE_DEPTH; ++i) {
|
|
minval = min(minval, samples[i]);
|
|
maxval = max(maxval, samples[i]);
|
|
}
|
|
|
|
// Check the amplitude of the signal (difference between min and max)
|
|
// is greater than the threshold to continue detecting speed.
|
|
uint16_t amplitude = maxval - minval;
|
|
if (amplitude < THRESHOLD) {
|
|
// Didn't make it past the threshold so start over with another measurement attempt.
|
|
return;
|
|
}
|
|
|
|
// Compute midpoint of the signal (halfway between min and max values).
|
|
uint16_t midpoint = minval + (amplitude/2);
|
|
|
|
// Count how many midpoint crossings were found in the signal.
|
|
// These are instances where two readings either straddle or land on
|
|
// the midpoint. The midpoint crossings will happen twice for every
|
|
// complete sine wave cycle (once going up and again coming down).
|
|
int crossings = 0;
|
|
for (int i=1; i<SAMPLE_DEPTH; ++i) {
|
|
uint16_t p0 = samples[i-1];
|
|
uint16_t p1 = samples[i];
|
|
if ((p1 == midpoint) ||
|
|
((p0 < midpoint) && (p1 > midpoint)) ||
|
|
((p0 > midpoint) && (p1 < midpoint))) {
|
|
crossings += 1;
|
|
}
|
|
}
|
|
|
|
// Compute signal frequency, RPM, and period.
|
|
// The period is the amount of time it takes for a complete
|
|
// sine wave cycle to occur. You can calculate this by dividing the
|
|
// amount of time that elapsed during the measurement period by the
|
|
// number of midpoint crossings cut in half (because each complete
|
|
// sine wave cycle will have 2 midpoint crossings). However since
|
|
// fidget spinners have multiple arms you also divide by the number
|
|
// of arms to normalize the period into a value that represents the
|
|
// time taken for a complete revolution of the entire spinner, not
|
|
// just the time between one arm and the next.
|
|
float period = elapsed / (crossings / SPINNER_ARMS / 2.0);
|
|
|
|
// Once the period is calculated it can be converted into a frequency
|
|
// value (i.e revolutions per second, how many times the spinner spins
|
|
// around per second) and more common RPM value (revolutions per minute,
|
|
// just multiply frequency by 60 since there are 60 seconds in a minute).
|
|
float frequency = 1.0 / period;
|
|
float rpm = frequency * 60.0;
|
|
|
|
// Print out the measured values!
|
|
Serial.print("Frequency: ");
|
|
Serial.print(frequency, 3);
|
|
Serial.print(" (hz)\t\tRPM: ");
|
|
Serial.print(rpm, 3);
|
|
Serial.print("\t\tPeriod: ");
|
|
Serial.print(period, 3);
|
|
Serial.println(" (seconds)");
|
|
}
|