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.
269 lines
6.9 KiB
269 lines
6.9 KiB
#include "MD_RobotEyes.h" |
|
#include "MD_RobotEyes_Data.h" |
|
|
|
// Debugging macros |
|
#define DEBUG 0 |
|
|
|
#if DEBUG |
|
#define PRINTS(s) { Serial.print(F(s)); } |
|
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); } |
|
#define PRINTX(s, v) { Serial.print(F(s)); Serial.print(F("0x")); Serial.print(v, HEX); } |
|
#else |
|
#define PRINTS(s) |
|
#define PRINT(s, v) |
|
#define PRINTX(s, v) |
|
#endif |
|
|
|
MD_RobotEyes::MD_RobotEyes(void) : |
|
_timeBlinkMinimum(5000), _animState(S_IDLE), |
|
_autoBlink(true), _nextEmotion(E_NEUTRAL) |
|
{ |
|
}; |
|
|
|
void MD_RobotEyes::loadEye(uint8_t module, uint8_t ch) |
|
{ |
|
uint8_t buf[EYE_COL_SIZE]; |
|
uint8_t size = _M->getChar(ch, EYE_COL_SIZE, buf); |
|
|
|
for (uint8_t i = 0; i < size; i++) |
|
{ |
|
_M->setColumn(module, i, buf[i]); |
|
} |
|
} |
|
|
|
void MD_RobotEyes::drawEyes(uint8_t L, uint8_t R) |
|
// Draw the left and right eyes |
|
{ |
|
MD_MAX72XX::fontType_t *savedFont = _M->getFont(); |
|
|
|
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF); |
|
_M->setFont(_RobotEyes_Font); |
|
|
|
_M->clear(_sd, _sd+1); // clear out display modules |
|
|
|
// Load the data and show it |
|
loadEye(_sd+LEFT_MODULE_OFFSET, L); |
|
loadEye(_sd+RIGHT_MODULE_OFFSET, R); |
|
|
|
_M->setFont(savedFont); |
|
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON); |
|
} |
|
|
|
#if DEBUG |
|
void MD_RobotEyes::dumpSequence(const animFrame_t* pBuf, uint8_t numElements) |
|
// Debugging routine to display an animation table in PROGMEM |
|
{ |
|
for (uint8_t i = 0; i < numElements; i++) |
|
{ |
|
animFrame_t f; |
|
|
|
memcpy_P(&f, &pBuf[i], sizeof(animFrame_t)); |
|
PRINT("\n[", i); |
|
PRINT("]: L:", f.eyeData[LEFT_EYE_INDEX]); |
|
PRINT(" R:", f.eyeData[RIGHT_EYE_INDEX]); |
|
PRINT(" T:", f.timeFrame); |
|
} |
|
} |
|
#endif |
|
|
|
uint8_t MD_RobotEyes::loadSequence(emotion_t e) |
|
// Load the next emotion from the static data. |
|
// Set global variables to the required values |
|
{ |
|
// run through the lookup table to find the sequence data |
|
for (uint8_t i = 0; i < ARRAY_SIZE(lookupTable); i++) |
|
{ |
|
memcpy_P(&_animEntry, &lookupTable[i], sizeof(animTable_t)); |
|
if (_animEntry.e == e) |
|
{ |
|
#if DEBUG |
|
dumpSequence(_animEntry.seq, _animEntry.size); |
|
#endif |
|
break; |
|
} |
|
} |
|
|
|
// set up the current index depending on direction of animation |
|
if (_animReverse) _animIndex = _animEntry.size - 1; else _animIndex = 0; |
|
|
|
return(_animEntry.size); |
|
} |
|
|
|
void MD_RobotEyes::loadFrame(animFrame_t* pBuf) |
|
// Load the idx'th frame from the frame sequence PROGMEM to normal memory pBuf |
|
{ |
|
memcpy_P(pBuf, &_animEntry.seq[_animIndex], sizeof(animFrame_t)); |
|
} |
|
|
|
void MD_RobotEyes::showText(bool bInit) |
|
// Print the text string to the LED matrix modules specified. |
|
// Message area is padded with blank columns after printing. |
|
{ |
|
static enum { S_LOAD, S_SHOW, S_SPACE } state; |
|
static uint8_t curLen, showLen; |
|
static uint8_t cBuf[EYE_COL_SIZE]; |
|
|
|
if (bInit) |
|
{ |
|
PRINT("\nText: ", _pText); |
|
_timeLastAnimation = millis(); |
|
_M->clear(_sd, _sd + 1); |
|
state = S_LOAD; |
|
} |
|
|
|
// Is it time to scroll the text? |
|
if (millis() - _timeLastAnimation < FRAME_TIME/2) |
|
return; |
|
|
|
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF); |
|
|
|
// Now scroll the text |
|
_M->transform(_sd, _sd+1, MD_MAX72XX::TSL); // scroll along by one place |
|
_timeLastAnimation = millis(); // starting time for next scroll |
|
|
|
// Now work out what's next using finite state machine to control what we do |
|
switch (state) |
|
{ |
|
case S_LOAD: // Load the next character from the font table |
|
// if we reached end of message or empty string, reset the message pointer |
|
if (*_pText == '\0') |
|
{ |
|
_pText = nullptr; |
|
break; |
|
} |
|
|
|
// otherwise load the character |
|
showLen = _M->getChar(*_pText++, ARRAY_SIZE(cBuf), cBuf); |
|
curLen = 0; |
|
state = S_SHOW; |
|
// fall through to the next state |
|
|
|
case S_SHOW: // display the next part of the character |
|
_M->setColumn(_sd, 0, cBuf[curLen++]); |
|
if (curLen == showLen) |
|
{ |
|
showLen = (*_pText == '\0' ? 2*EYE_COL_SIZE : 1); // either 1 space or pad to the end of the display if finished |
|
curLen = 0; |
|
state = S_SPACE; |
|
} |
|
break; |
|
|
|
case S_SPACE: // display inter-character spacing (blank columns) |
|
_M->setColumn(_sd, 0, 0); |
|
curLen++; |
|
if (curLen >= showLen) |
|
state = S_LOAD; |
|
break; |
|
|
|
default: |
|
state = S_LOAD; |
|
} |
|
|
|
_M->control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON); |
|
} |
|
|
|
void MD_RobotEyes::begin(MD_MAX72XX *M, uint8_t moduleStart) |
|
// initialize other stuff after libraries have started |
|
{ |
|
#if DEBUG |
|
Serial.begin(57600); |
|
#endif |
|
PRINTS("\n[MD_RobotEyes Debug]"); |
|
|
|
_M = M; |
|
_sd = moduleStart; |
|
|
|
setAnimation(E_NEUTRAL, false); |
|
}; |
|
|
|
bool MD_RobotEyes::runAnimation(void) |
|
// Animate the eyes |
|
// Return true if there is no animation happening |
|
{ |
|
static animFrame_t thisFrame; |
|
|
|
switch (_animState) |
|
{ |
|
case S_IDLE: // no animation running - wait for a new one or blink if time to do so |
|
if (_pText != nullptr) // there is some text to show |
|
{ |
|
PRINTS("\nIDLE: showing text"); |
|
showText(true); |
|
_animState = S_TEXT; |
|
break; |
|
} |
|
// otherwise fall through and try for an animation |
|
|
|
case S_RESTART: // back to start of current animation |
|
if (_nextEmotion != E_NONE) // check if we have an animation in the queue |
|
{ |
|
PRINTS("\nRESRT: showing animation"); |
|
_timeLastAnimation = millis(); |
|
|
|
// set up the next animation |
|
loadSequence(_nextEmotion); |
|
_nextEmotion = E_NONE; |
|
_animState = S_ANIMATE; |
|
} |
|
else if (_autoBlink) // check if we should be blinking |
|
{ |
|
if (((millis() - _timeLastAnimation) >= _timeBlinkMinimum) && (random(1000) > 700)) |
|
{ |
|
PRINTS("\nRESRT: forcing blink"); |
|
setAnimation(E_BLINK, true); |
|
_animState = S_RESTART; |
|
} |
|
} |
|
break; |
|
|
|
case S_ANIMATE: // process the next frame for this sequence |
|
PRINT("\nPROCESS: Frame:", _animIndex); |
|
loadFrame(&thisFrame); |
|
drawEyes(thisFrame.eyeData[LEFT_EYE_INDEX], thisFrame.eyeData[RIGHT_EYE_INDEX]); |
|
if (_animReverse) _animIndex--; else _animIndex++; |
|
|
|
_timeStartPause = millis(); |
|
_animState = S_PAUSE; |
|
break; |
|
|
|
case S_PAUSE: // pause this frame for the required time |
|
{ |
|
if ((millis() - _timeStartPause) < thisFrame.timeFrame) |
|
break; |
|
|
|
// check if this is the end of animation |
|
if ((!_animReverse && _animIndex >= _animEntry.size) || |
|
(_animReverse && _animIndex < 0)) |
|
{ |
|
PRINTS("\nPAUSE: Animation end") |
|
if (_autoReverse) // set up the same emotion but in reverse |
|
{ |
|
PRINTS(" & auto reverse"); |
|
_nextEmotion = _animEntry.e; |
|
_animReverse = true; // set this flag for the restart state |
|
_autoReverse = false; // clear the flag for this animation sequence |
|
_animState = S_RESTART; |
|
} |
|
else |
|
_animState = S_IDLE; |
|
} |
|
else |
|
_animState = S_ANIMATE; |
|
} |
|
break; |
|
|
|
case S_TEXT: // currently displaying text |
|
{ |
|
showText(); |
|
if (_pText == nullptr) |
|
_animState = S_IDLE; |
|
} |
|
break; |
|
|
|
default: // something is wrong - reset the FSM |
|
_animState = S_IDLE; |
|
break; |
|
} |
|
|
|
return(_animState == S_IDLE); |
|
};
|
|
|