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.
253 lines
6.8 KiB
253 lines
6.8 KiB
#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 (uint16_t 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); |
|
};
|
|
|