Wemos8266RelaysLedDisplay/lib/MD_MAX72XX/examples/MD_MAX72xx_Eyes/MD_EyePair.cpp

254 lines
6.8 KiB
C++
Raw Permalink Normal View History

#include "MD_EyePair.h"
// Packing and unpacking nybbles into a byte
#define PACK_RC(r, c) ((r<<4)|(c&0xf))
#define UNPACK_R(rc) (rc>>4)
#define UNPACK_C(rc) (rc&0xf)
#define SMALL_EYEBALL 0
// Class static variables
#if SMALL_EYEBALL
uint8_t MD_EyePair::_pupilData[] =
{
/* P_TL */ PACK_RC(2,5), /* P_TC */ PACK_RC(2,4), /* P_TR */ PACK_RC(2,3),
/* P_ML */ PACK_RC(3,5), /* P_MC */ PACK_RC(3,4), /* P_MR */ PACK_RC(3,3),
/* P_BL */ PACK_RC(4,5), /* P_BC */ PACK_RC(4,4), /* P_BR */ PACK_RC(4,3),
};
// Eye related information
uint8_t MD_EyePair::_eyeballData[EYEBALL_ROWS] = { 0x00, 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x00 }; // row data
#define LAST_BLINK_ROW 6 // last row for the blink animation
#else
uint8_t MD_EyePair::_pupilData[] =
{
/* P_TL */ PACK_RC(3,5), /* P_TC */ PACK_RC(3,4), /* P_TR */ PACK_RC(3,3),
/* P_ML */ PACK_RC(4,5), /* P_MC */ PACK_RC(4,4), /* P_MR */ PACK_RC(4,3),
/* P_BL */ PACK_RC(5,5), /* P_BC */ PACK_RC(5,4), /* P_BR */ PACK_RC(5,3),
};
uint8_t MD_EyePair::_eyeballData[EYEBALL_ROWS] = { 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c }; // row data
#define LAST_BLINK_ROW 7 // last row for the blink animation
#endif
// Random seed creation --------------------------
// Adapted from http://www.utopiamechanicus.com/article/arduino-better-random-numbers/
uint16_t MD_EyePair::bitOut(uint8_t port)
{
static bool firstTime = true;
uint32_t prev = 0;
uint32_t bit1 = 0, bit0 = 0;
uint32_t x = 0, limit = 99;
if (firstTime)
{
firstTime = false;
prev = analogRead(port);
}
while (limit--)
{
x = analogRead(port);
bit1 = (prev != x ? 1 : 0);
prev = x;
x = analogRead(port);
bit0 = (prev != x ? 1 : 0);
prev = x;
if (bit1 != bit0)
break;
}
return(bit1);
}
uint32_t MD_EyePair::seedOut(uint16_t noOfBits, uint8_t port)
{
// return value with 'noOfBits' random bits set
uint32_t seed = 0;
for (int i = 0; i<noOfBits; ++i)
seed = (seed << 1) | bitOut(port);
return(seed);
}
//------------------------------------------------------------------------------
MD_EyePair::MD_EyePair(void)
{
_pupilCurPos = P_MC;
_timeLast = 0;
_inBlinkCycle = false;
};
void MD_EyePair::drawEyeball()
// Draw the iris on the display(s)
{
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
_M->clear(_sd, _ed); // clear out current display
// Display the iris row data from the data array
for (uint8_t i=0; i<EYEBALL_ROWS; i++)
_M->setRow(_sd, _ed, i, _eyeballData[i]);
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
bool MD_EyePair::blinkEyeball(bool bFirst)
// Blink the iris. If this is the first call in the cycle, bFirst will be set true.
// Return true if the blink is still active, false otherwise.
{
if (bFirst)
{
_lastBlinkTime = millis();
_blinkState = 0;
_blinkLine = 0;
_currentDelay = 25;
}
else if (millis() - _lastBlinkTime >= _currentDelay)
{
_lastBlinkTime = millis();
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
switch(_blinkState)
{
case 0: // initialization - save the current eye pattern assuming both eyes are the same
for (uint8_t i=0; i<EYEBALL_ROWS; i++)
_savedEyeball[i] = _M->getRow(_sd, i);
_blinkState = 1;
// fall through
case 1: // blink the eye shut
_M->setRow(_sd, _ed, _blinkLine, 0);
_blinkLine++;
if (_blinkLine == LAST_BLINK_ROW) // this is the last row of the animation
{
_blinkState = 2;
_currentDelay *= 2;
}
break;
case 2: // set up for eye opening
_currentDelay /= 2;
_blinkState = 3;
// fall through
case 3:
_blinkLine--;
_M->setRow(_sd, _ed, _blinkLine, _savedEyeball[_blinkLine]);
if (_blinkLine == 0)
{
_blinkState = 99;
}
break;
}
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
return(_blinkState != 99);
}
void MD_EyePair::drawPupil(posPupil_t posOld, posPupil_t posNew)
// Draw the pupil in the current position. Needs to erase the
// old position first, then put in the new position
{
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
// first blank out the old pupil by writing back the
// eyeball background 'row'
{
uint8_t row = UNPACK_R(_pupilData[posOld]);
_M->setRow(_sd, _ed, row, _eyeballData[row]);
_M->setRow(_sd, _ed, row+1, _eyeballData[row+1]);
}
// now show the new pupil by displaying the new background 'row'
// with the pupil masked out of it
{
uint8_t row = UNPACK_R(_pupilData[posNew]);
uint8_t col = UNPACK_C(_pupilData[posNew]);
uint8_t colMask = ~((1<<col)|(1<<(col-1)));
_M->setRow(_sd, _ed, row, (_eyeballData[row]&colMask));
_M->setRow(_sd, _ed, row+1, (_eyeballData[row+1]&colMask));
}
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
bool MD_EyePair::posIsAdjacent(posPupil_t posCur, posPupil_t posNew)
// If the new pos is an adjacent position to the old, return true
// the arrangement is P_TL P_TC P_TR
// P_ML P_MC P_MR
// P_BL P_BC P_BR
{
switch (posCur)
{
case P_TL: return(posNew == P_TC || posNew == P_MC || posNew == P_ML);
case P_TC: return(posNew != P_BL && posNew != P_BC && posNew == P_BR);
case P_TR: return(posNew == P_TC || posNew == P_MC || posNew == P_MR);
case P_ML: return(posNew != P_TR && posNew != P_MR && posNew != P_BR);
case P_MC: return(true); // all are adjacent to center
case P_MR: return(posNew != P_TL && posNew != P_ML && posNew != P_BL);
case P_BL: return(posNew == P_ML || posNew == P_MC || posNew == P_BC);
case P_BC: return(posNew != P_TL && posNew != P_TC && posNew == P_TR);
case P_BR: return(posNew == P_BC || posNew == P_MC || posNew == P_MR);
}
return(false);
}
void MD_EyePair::begin(uint8_t startDev, MD_MAX72XX *M, uint16_t maxDelay)
// initialisz the eyes
{
_sd = startDev;
_ed = startDev + 1;
_M = M;
_timeDelay = _maxDelay = maxDelay;
randomSeed(seedOut(31, RANDOM_SEED_PORT));
drawEyeball();
drawPupil(_pupilCurPos, _pupilCurPos);
};
void MD_EyePair::animate(void)
// Animate the eye(s).
// this cane either be a blink or an eye movement
{
// do the blink if we are currently already blinking
if (_inBlinkCycle)
{
_inBlinkCycle = blinkEyeball(false);
return;
}
// Possible animation - only animate every timeDelay ms
if (millis() - _timeLast <= _timeDelay)
return;
// set up timers for next time
_timeLast = millis();
_timeDelay = random(_maxDelay);
// Do the animation most of the time, so bias the
// random number check to achieve this
if (random(1000) <= 900)
{
posPupil_t pupilNewPos = (posPupil_t)random(9);
if (posIsAdjacent(_pupilCurPos, pupilNewPos))
{
drawPupil(_pupilCurPos, pupilNewPos);
_pupilCurPos = pupilNewPos;
}
}
else
// blink the eyeball
_inBlinkCycle = blinkEyeball(true);
};