Animated Robot Eyes 'Sleep' Function

Hello all,

I'm using the MD Max72xx library and two 8x8 LED matrix displays. I've loaded the example sketch and got it running correctly, along with a pir sensor. I'd like the eyes to appear only when motion is detected, run the animation, then shut off again. I'm brand new to these displays and do not understand how the animation code really works even after reading the documentation. Is there a simple way to do this?

// Program to exercise the MD_MAX72XX library
//
// Uses a sequence of bitmaps defined as a font to display animations of eyes to convey emotion.
// Eyes are coordinated to work together.
//
#include <MD_MAX72xx.h>
#include <SPI.h>
#include "MD_RobotEyes.h"

// NOTE: These pin numbers will probably not work with your hardware and may
// need to be adapted
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4

#define CLK_PIN   13  // or SCK
#define DATA_PIN  11  // or MOSI
#define CS_PIN    10  // or SS

//When the robot detects motion, he will wake up
int inputPin = 7;  //input pin for PIR sensor
int pirState = LOW; //start by assuming no motion is detected
int val = 0; //variable for reading the pin status

// SPI hardware interface
MD_MAX72XX M = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary pins
//MD_MAX72XX M = MD_MAX72XX(HADWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);

MD_RobotEyes E;

typedef struct
{
  char name[7];
  MD_RobotEyes::emotion_t e;
  uint16_t timePause;  // in milliseconds
} sampleItem_t;

const sampleItem_t eSeq[] =
{
  { "", MD_RobotEyes::E_NEUTRAL, 20000 },
  { "" , MD_RobotEyes::E_BLINK, 1000 },
  { "" , MD_RobotEyes::E_LOOK_R, 1000 },
  { "" , MD_RobotEyes::E_SCAN_LR, 1000 },
  { "" , MD_RobotEyes::E_WINK, 1000 },
  /*{ "Nutral", MD_RobotEyes::E_NEUTRAL, 20000 },
  { "Blink" , MD_RobotEyes::E_BLINK, 1000 },
  { "Wink"  , MD_RobotEyes::E_WINK, 1000 },
  { "Left"  , MD_RobotEyes::E_LOOK_L, 1000 },
  { "Right" , MD_RobotEyes::E_LOOK_R, 1000 },
  { "Up"    , MD_RobotEyes::E_LOOK_U, 1000 },
  { "Down"  , MD_RobotEyes::E_LOOK_D, 1000 },
  { "Angry" , MD_RobotEyes::E_ANGRY, 1000 },
  { "Sad"   , MD_RobotEyes::E_SAD, 1000 },
  { "Evil"  , MD_RobotEyes::E_EVIL, 1000 },
  { "Evil2" , MD_RobotEyes::E_EVIL2, 1000 },
  { "Squint", MD_RobotEyes::E_SQUINT, 1000 },
  { "Dead"  , MD_RobotEyes::E_DEAD, 1000 },
  { "ScanV" , MD_RobotEyes::E_SCAN_UD, 1000 },
  { "ScanH" , MD_RobotEyes::E_SCAN_LR, 1000 },
  */
};

void setup()
{
  M.begin();
  E.begin(&M);
  E.setText("On-On");
  do { } while (!E.runAnimation()); // wait for the text to finish

  pinMode(inputPin, INPUT);  //declare sensor as input
  Serial.begin(9600);
}

void loop()
{

val = digitalRead(inputPin);  // read input value
  if (val == HIGH) {            // check if the input is HIGH
  static uint32_t timeStartDelay;
  static uint8_t index = ARRAY_SIZE(eSeq);
  static enum { S_IDLE, S_TEXT, S_ANIM, S_PAUSE } state = S_IDLE;

  bool b = E.runAnimation();    // always run the animation

  switch (state)
  {

  case S_ANIM:  // checking animation is completed
    if (b)  // animation is finished
    {
      timeStartDelay = millis();
      state = S_PAUSE;
    }
    break;
  
  case S_PAUSE: // non blocking waiting for a period between animations
    if (millis() - timeStartDelay >= eSeq[index].timePause)
      state = S_IDLE;
    break;

 // default:
   //state = S_IDLE;
   // break;
  
  }
 
  if (pirState == LOW) {
      // we have just turned on
      Serial.println("Motion detected!");
      M.clear(); // clear out display modules
      // We only want to print on the output change, not state
      pirState = HIGH;
    }
  } else {
    if (pirState == HIGH){
      // we have just turned of
      Serial.println("Motion ended!");
      // We only want to print on the output change, not state
      pirState = LOW;
    }
  }
}

MD_RobotEyes.cpp (6.95 KB)

MD_RobotEyes.h (7.34 KB)

MD_RobotEyes_Data.h (12.2 KB)

Test_Robot_Sketch.ino.ino (3.31 KB)

More documentation here: Robot Eye Expressions using LED Matrix Display – Arduino++. Perhaps someone can help me understand this section here:

  static enum { S_IDLE, S_TEXT, S_ANIM, S_PAUSE } state = S_IDLE;

  bool b = E.runAnimation();    // always run the animation

To me, this seems like a set of instructions to follow each time one of the animations is run. Does this sound correct? I've selected the animations I want and removed the text in this section at the top of the code:

const sampleItem_t eSeq[] =
{
  { "", MD_RobotEyes::E_NEUTRAL, 20000 },
  { "" , MD_RobotEyes::E_BLINK, 1000 },
  { "" , MD_RobotEyes::E_LOOK_R, 1000 },
  { "" , MD_RobotEyes::E_SCAN_LR, 1000 },
  { "" , MD_RobotEyes::E_WINK, 1000 },

Update: Found this tutorial from the Bald Engineer: https://www.baldengineer.com/state-machine-with-enum-tutorial.html

I believe the S_Idle function is keeping the eyes in the animation loop. So far, I've added "E_None" with a wait of 5 seconds to the end of the animation loop as shown in this section of the code:

const sampleItem_t eSeq[] =
{
  { "", MD_RobotEyes::E_NEUTRAL, 20000 },
  { "" , MD_RobotEyes::E_BLINK, 1000 },
  { "" , MD_RobotEyes::E_LOOK_R, 1000 },
  { "" , MD_RobotEyes::E_SCAN_LR, 1000 },
  { "" , MD_RobotEyes::E_WINK, 1000 },
   { "", MD_RobotEyes::E_NONE, 50000 },
  /*{ "Nutral", MD_RobotEyes::E_NEUTRAL, 20000 },
  { "Blink" , MD_RobotEyes::E_BLINK, 1000 },

Next I'm going to try to figure out how to call E_None when the PIR is LOW.

The main loop in the library (or example code, can't remember) will run the neutral animation until a 'random' time has passed and it will blink.

You need to change the neutral expression to be eyes closed instead of open and remove the code for running the blink at random times. (ie, you need to change or add to the standard animation types defined because they all start with the eyes open instead of shut).

Thanks Marco, I'll update the first animation that I'm using to start with the eyes closed. I've figured out how to create and add my own animations to the sketch. Is it possible to call a single animation within my loop? So far, all I've done is comment/uncomment the animation calls in eSeq:

typedef struct
{
  char name[7];
  MD_RobotEyes::emotion_t e;
  uint16_t timePause;  // in milliseconds
} sampleItem_t;

const sampleItem_t eSeq[] =
{
  { "", MD_RobotEyes::E_WAKE, 20000 },
  { "" , MD_RobotEyes::E_BLINK, 1000 },
  { "" , MD_RobotEyes::E_LOOK_R, 1000 },
  { "" , MD_RobotEyes::E_SCAN_LR, 1000 },
  { "" , MD_RobotEyes::E_WINK, 1000 },
   { "", MD_RobotEyes::E_NONE, 50000 },
  /*{ "Nutral", MD_RobotEyes::E_NEUTRAL, 20000 },
  { "Blink" , MD_RobotEyes::E_BLINK, 1000 },

Sure. The array of structs is just for demo purposes so that it prints the text and runs the animation. To run just one animation, eliminate the examples array and just run your animation when you need it. You probably actually need to read the documentation in the libraries docs folder for MD_MAX72xx and look at the Robot_Eyes class to see what it does.

Hey @Marco_C, I've been trying to read through the comments in the RobotEyes.h file over the past couple of weeks but since I'm pretty new, a lot is going over my head. To disable the autoBlink I went to MD_RobotEyes.cpp and set autoBlink to false.

MD_RobotEyes::MD_RobotEyes(void) :
_nextEmotion(E_NEUTRAL), _animState(S_IDLE),
_autoBlink(false), _timeBlinkMinimum(5000)
{

I have two questions:

  1. Instead of adding a new 'eyesshut' animation, can't I just use S_IDLE to display nothing until I want another sequence of animations to run? If so, how?

  2. I'm now trying to figure out how to scroll the text across both displays instead of two individual ones. Currently the text scrolls duplicate on each display separately. I found this note in the docs and do not understand it. How can I stretch the text to display once and scroll across both screens?

 /**
  * Display a text message.
  *
  * At the end of the current animation, the text will be scrolled across the 'eyes'
  * and then the eyes are returned to the neutral expression
  *
  * \param p  a pointer to a char array containing a nul terminated string. 
              The string must remain in scope while the message is being displayed.
  */
  inline bool setText(char *pText) { if (_pText != nullptr) return(false); else _pText = pText; return(true); };
  1. Sure,you can do whatever you like. I think it would actually be better for you to ditch the Finite State Machine if you don't understand how it works and just do what you need to make it work the way you want.

  2. I assume a 'screen' is a led matrix module. Text should already scroll across both 'eyes' (matrix modules) as one display, as per the video on YouTube, so I don't understand your comment.

Marco_C That's the problem, I don't understand how to run an animation without the state machine. I don't see one clear function to play a specific animation and I know it's because I don't understand how the code works. Can you explain a few steps on how the code knows which animation to display? Also, how does it keep the next animation from starting before the first one is done?

I've tried adding the following to my loop, but this was a guess and didn't compile

setAnimation(MD_RobotEyes::E_BLINK e, bool r, bool b = false);
runAnimation();

Also, (http://tinypic.com/r/2con6h4/9)]Here is a video of what's happening with the text. It is supposed to display 'On-On' across both displays. I've read your article here about rotating text but wanted to tackle this problem first.

Can you explain a few steps on how the code knows which animation to display?

setAnimation() with the correct parameters to make it do what you want. From the comments in the header file:

 /**
  * Set the animation type and parameters.
  *
  * Set the next animations to the specified. Additionally, set whether the animation should
  * auto reverse the action (eg, blink down then back up again) and whether the animation 
  * should be run in reverse.
  * 
  * Animations are generally symmetric, so only half the animation needs to be specified.
  * If an animated expression needs to be held, the animation should be run without auto 
  * reverse, which holds the animation at the end point, and then later run the animation 
  * in reverse from the last position to return to the idle state.
  *
  * \param e  the type of emotion to be displayed, one of the emotion_T enumerated values.
  * \param r  if true, run auto reverse.
  * \param b  if true, start the animation from the end of the sequence.
  */

Also, how does it keep the next animation from starting before the first one is done?

runAnimation() is called repeatedly until it returns true. From the comments in the header file:

 /**
  * Animate the display.
  *
  * This method needs to be invoked as often as possible to ensure smooth animation. 
  *
  * The calling program should monitor the return value for 'true' in order to know when 
  * the animation has concluded. A 'true' return value means that the animation is complete.
  *
  * \return bool  true if the animation has completed, false otherwise.
  */

I've tried adding the following to my loop, but this was a guess and didn't compile

Looks to me like you need to do a basic tutorial on how to program in C/C++ before you go too much further. You need to replace the function prototype parameters with your actual parameters.

Minimalist code to run an animation would be:

setAnimation(...);
do {} while (!runAnimation);

This will block execution of the program until the animation is completed. To not block you need to use a Finite State Machine (FSM) at least keep track in some variable of whether you are animating or not.

If you setAnimation() before an animation is completed it will override the previous one.