#include "TimerOne.h"
/***************************************************************/
/************************** CONSTANTS **************************/
/***************************************************************/
// The amount of time in milliseconds the heart must be fully on after it completes.
const int HeartCompletedOnTimer = 2000;
// The amount of time in milliseconds the heart must fade out after it completes.
const int HeartCompletedOffTimer = 1000;
// The timer interval used to advance column, in microseconds.
const int ScreenColumnTimer = 2200;
// The digital pin that is used to read the heart beat detector.
const byte BeatInputPin = 23;
// Minimum time in milliseconds between a zero to one transition of valid beat bit.
// This is used to filter some false positive beats.
const int BeatUpTimer = 140;
// Minimum time in milliseconds between a one to zero transition of valid beat bit.
// This is used to filter some false positive beats.
const int BeatDownTimer = 150;
// The amount of light to increment when a led fades in
const byte FadeInAmount = 64;
// The minimum amount of light to decrement when a led fades out.
const byte FadeOutMinAmount = 4;
// The maximum amount of light to decrement when a led fades out.
const byte FadeOutMaxAmount = 24;
// Pins used to drive led matrix rows.
const byte ScreenRowPins[] = {
2, 3, 5, // PWM TIMER 3
6, 7, 8, // PWM TIMER 4
9, 10 // PWM TIMER 2
};
// Pins used to drive led matrix columns.
const byte ScreenColumnPins[] = {
14, 15, 16, 17, 18, 19, 20, 21
};
/***************************************************************/
/**************************** FIELDS ***************************/
/***************************************************************/
// Counter used to advance the screen column.
byte ScreenColumnCounter = 0;
// This portion of memory represent the content of the led matrix.
byte Screen[8][8];
// Keep track of the previous beat input pin state, synchronous to the main loop.
boolean BeatInputOld = true;
// The heart beat state, filtered from false positives.
boolean BeatValidated = true;
// When true, indicates that the heart is completed and the timer for restarting again started.
boolean HeartCompleted = false;
// Keep track of the last time in milliseconds when a valid beat input went from zero to one.
unsigned long BeatUpTime = 0;
// Keep track of the last time in milliseconds when a valid beat input went from one to zero.
unsigned long BeatDownTime = 0;
// Keep track of the last time in milliseconds when the hearth was completed.
unsigned long HeartCompletedTime = 0;
// HeartPixels array contains all the coordinates pair
// of pixels that can draw a full hearth.
// Coordinates pairs are encoded as (y + x * 8).
byte HeartPixels[] = {
8, 16, 40, 48,
1, 9, 17, 25, 33, 41, 49, 57,
2, 10, 18, 26, 34, 42, 50, 58,
3, 11, 19, 27, 35, 43, 51, 59,
4, 12, 20, 28, 36, 44, 52, 60,
13, 21, 29, 37, 45, 53,
22, 30, 38, 46,
31, 39
};
// A table of random number we use to fade out leds in randomized time.
byte HeartFadeOutTable[8][8];
// Number of currently used pins in the HeartPixels array.
byte HeartPixelsCounter = 0;
// The number of elements in HeartPixels array.
const byte HeartPixelsSize = sizeof(HeartPixels);
/***************************************************************/
/************************** FUNCTIONS **************************/
/***************************************************************/
// Randomize the HeartFadeOutTable matrix to give a different fadeout timing for each led.
// A random value between FadeOutMinAmount and FadeOutMaxAmount is used for each pixel.
void RandomizeHeartFadeOutTable() {
for (byte x = 0; x < 8; ++x) {
for (byte y = 0; y < 8; ++y) {
HeartFadeOutTable[x][y] = random(FadeOutMinAmount, FadeOutMaxAmount);
}
}
}
// Shuffle the HeartPixels array using a pseudorandom number generator.
// The array at the end will contains the same items but in random order
void ShuffleHeartPixelsArray() {
// The array shuffler just swap every item in the array with a random item.
for (byte a=0; a < HeartPixelsSize; ++a)
{
// Pix a random index.
byte r = random(a, HeartPixelsSize);
// Swap current item with the random index.
byte temp = HeartPixels[a];
HeartPixels[a] = HeartPixels[r];
HeartPixels[r] = temp;
}
}
// Interrupt function called to output a column to the led matrix.
void outputScreen() {
// It's time to process one screen row.
// We turn off the previous column.
digitalWrite(ScreenColumnPins[ScreenColumnCounter], LOW);
// We increment the column counter, wrapping to zero when it reaches 7.
ScreenColumnCounter = (ScreenColumnCounter + 1) & 0x07;
for (byte i = 0; i < 8; ++i) {
// Turn off the row.
digitalWrite(ScreenRowPins[i], 0);
// We write the PWM value into the row.
analogWrite(ScreenRowPins[i], Screen[ScreenColumnCounter][i]);
}
// We turn on the new column.
digitalWrite(ScreenColumnPins[ScreenColumnCounter], HIGH);
if (ScreenColumnCounter == 7) {
// Last column, we can update the screen content.
if (!BeatValidated) {
// Fade out.
for (byte x = 0; x < 8; ++x) {
for (byte y = 0; y < 8; ++y) {
byte v = Screen[x][y];
byte f = HeartFadeOutTable[x][y];
Screen[x][y] = v > f ? v - f : 0;
}
}
} else {
// Fade in.
// For each pixel in the heart ...
for (byte i = 0; i < HeartPixelsCounter; ++i) {
byte coordinates = HeartPixels[i];
// Decode the coordinates stored in HeartPixels[i]
byte y = coordinates & 0x7;
byte x = coordinates >> 3;
// Pixel fade in
byte v = Screen[x][y];
Screen[x][y] = (v < 255 - FadeInAmount) ? v + FadeInAmount : 255;
}
}
}
}
/***************************************************************/
/**************************** SETUP ****************************/
/***************************************************************/
void setup() {
// Setup input pin.
// Set the BeatInputPin as input and enable the pullup resistor.
pinMode(BeatInputPin, INPUT_PULLUP);
// Setup output pins.
for (byte i = 0; i < 8; ++i) {
pinMode(ScreenColumnPins[i], OUTPUT);
digitalWrite(ScreenColumnPins[i], LOW);
pinMode(ScreenRowPins[i], OUTPUT);
analogWrite(ScreenRowPins[i], 0);
}
// Clear the screen.
for (byte x = 0; x < 8; ++x) {
for (byte y = 0; y < 8; ++y) {
Screen[x][y] = 0;
}
}
// Shuffle the heart pixel array so the pixel sequence looks random.
ShuffleHeartPixelsArray();
// Fill the fadeout table with random numbers
RandomizeHeartFadeOutTable();
// Setup the PWM timers for faster frequency.
// We need higher frequency because we are using a multiplexed led matrix.
// pwmFrequencyFlag = 0x04 means 488.28125 hertz (default)
// pwmFrequencyFlag = 0x03 means 976.5625 hertz
// pwmFrequencyFlag = 0x02 means 3926.25 hertz
const byte pwmFrequencyFlag = 0x03;
TCCR2B = (TCCR2B & 0xF8) | pwmFrequencyFlag;
TCCR3B = (TCCR3B & 0xF8) | pwmFrequencyFlag;
TCCR4B = (TCCR4B & 0xF8) | pwmFrequencyFlag;
// Setup the timer interrupt to output the screen to the led matrix
Timer1.initialize(ScreenColumnTimer);
Timer1.attachInterrupt(outputScreen);
}
/***************************************************************/
/************************** MAIN LOOP **************************/
/***************************************************************/
void loop()
{
// Gets current time in milliseconds.
unsigned long now = millis();
if (HeartCompleted) {
// This code gets executed only when the heart was completed.
unsigned long timeDiff = now - HeartCompletedTime;
if (timeDiff < HeartCompletedOnTimer) {
// The heart is completed, keep it on until HeartCompletedOnTimer milliseconds passes.
BeatValidated = true;
} else if (timeDiff < (HeartCompletedOnTimer + HeartCompletedOffTimer)) {
// The heart is completed and we already waited the HeartCompletedOnTimer, fade it out.
BeatValidated = false;
} else {
// We need to reset the hearth.
HeartCompleted = false;
// Reset the counter so we start drawing pixels again from the beginning.
HeartPixelsCounter = 0;
// Shuffle the array so the pixel sequence will follow a different order.
ShuffleHeartPixelsArray();
// Fill the fadeout table with random numbers
RandomizeHeartFadeOutTable();
}
} else {
// Beat detector code
// We read the current beat input pin state.
boolean beatInput = digitalRead(BeatInputPin);
// If currently read input is different from the old input...
if (beatInput != BeatInputOld) {
BeatInputOld = beatInput;
if (beatInput) {
// Check if enough time since last time the pin was down passed.
if (now - BeatDownTime > BeatDownTimer) {
BeatUpTime = now;
if (!BeatValidated) {
BeatValidated = true;
if (HeartPixelsCounter < HeartPixelsSize - 1) {
// We add a pixel to the screen.
++HeartPixelsCounter;
} else {
// The heart is completed!
HeartCompleted = true;
// Store the completion time, in milliseconds.
HeartCompletedTime = now;
}
}
}
}
else if (BeatValidated) {
// Store the time when the pin became down.
BeatDownTime = now;
}
}
else if (now - BeatUpTime > BeatUpTimer) {
// The pin is high for too much time
BeatValidated = false;
}
}
}
No comments:
Post a Comment