Merging MD_Parola with MD_MAX72XX

I am trying to create a cycling display which will cycle between screens showing texts with animations from the MD_Parola library and then show the time with the animation in the MD_MAX72XX library.
This is the animation for the clock that I desire

See for the first video and skip to 1:03 for the clock animation.
Hardware and Software I am using :-

  1. Arduino nano
  2. MAX7219 controlled dot matrix modules.(They are classified as FC16_HW in the Parola library).
  3. DS1307 rtc module.
  4. MD_Parola library for text animations & MD_MAX72XX library for clock animation.

I was guided by the creator of the library @marco_c for this exact approach here :-

This is what I am facing,
So far I am able to convert the pushwheel example( the example he was telling me to use for the clock animation) to work with the MD_Parola library. See for the updateDisplay() and displayValue() functions in the given below code.

I tried to use a flag ( displayflag ) to keep the Parola and MAX72XX functions separately, which work until its time to display the clock, which seems to revert back to the sprite animation as soon as the first LSB of the second needs to be animated, I cannot even see the animation completed and it skips back to sprite animation rather than to wait for the 5 seconds that I desire.

It seems to me that the MD_MAX72XX functions are either resetting the microcontroller to begin initialization or the displayflag variable changes unintentionally to true as soon as the time comes to animate.

Please note that if there is no need for animation i.e. for example if I put now.hour() in place of now.second() in the displayValue() function the displayflag never sets back to true and gets stuck on the displayValue() function display the clock and don't revert back to the cycle.

Here is my code :-

#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include "RTClib.h"

#define MAX_DEVICES 8
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW

#define CLK_PIN 13
#define DATA_PIN 11
#define CS_PIN 10

MD_Parola matrix = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
RTC_DS1307 rtc;

#define CHAR_SPACING 1            // pixels between characters
#define CHAR_COLS 8               // should match the fixed width character columns
#define ANIMATION_FRAME_DELAY 10  // in milliseconds

boolean displayflag = true;
int prevseconds = 0;
int counter = 0;
int seconds = 0;

struct displayParameters {
  const char *message;
  uint16_t speed;
  uint16_t pause;
  textEffect_t entryEffect;
  textEffect_t exitEffect;
};

displayParameters params[] = {
  { "INTRO", 30, 1000, PA_SPRITE, PA_SPRITE },
  { "Wait", 20, 1000, PA_GROW_UP, PA_GROW_UP },
};

const uint8_t F_PMAN1 = 6;
const uint8_t W_PMAN1 = 8;
const uint8_t PROGMEM pacman1[F_PMAN1 * W_PMAN1] =  // gobbling pacman animation
  {
    0x00,
    0x81,
    0xc3,
    0xe7,
    0xff,
    0x7e,
    0x7e,
    0x3c,
    0x00,
    0x42,
    0xe7,
    0xe7,
    0xff,
    0xff,
    0x7e,
    0x3c,
    0x24,
    0x66,
    0xe7,
    0xff,
    0xff,
    0xff,
    0x7e,
    0x3c,
    0x3c,
    0x7e,
    0xff,
    0xff,
    0xff,
    0xff,
    0x7e,
    0x3c,
    0x24,
    0x66,
    0xe7,
    0xff,
    0xff,
    0xff,
    0x7e,
    0x3c,
    0x00,
    0x42,
    0xe7,
    0xe7,
    0xff,
    0xff,
    0x7e,
    0x3c,
  };

struct digitData {
  uint8_t oldValue, newValue;  // ASCII value for the character
  uint8_t index;               // animation progression index
  uint32_t timeLastFrame;      // time the last frame started animating
  uint8_t charCols;            // number of valid cols in the charMap
  uint8_t charMap[CHAR_COLS];  // character font bitmap
};

void updateDisplay(uint16_t numDigits, struct digitData *d)
// do the necessary to display current bitmap buffer to the LED display
{
  uint8_t curCol = 0;

  matrix.getGraphicObject()->control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
  matrix.getGraphicObject()->clear();

  for (int8_t i = numDigits - 1; i >= 0; i--) {
    for (int8_t j = d[i].charCols - 1; j >= 0; j--) {
      matrix.getGraphicObject()->setColumn(curCol++, d[i].charMap[j]);
    }
    curCol += CHAR_SPACING;
  }

  matrix.getGraphicObject()->control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}

boolean displayValue(uint16_t value)
// Display the required value on the LED matrix and return true if an animation is current
// Finite state machine will ignore new values while animations are underway.
// Needs to be called repeatedly to ensure animations are completed smoothly.
{
  const uint8_t DIGITS_SIZE = 2;
  static struct digitData digit[DIGITS_SIZE];

  const uint8_t ST_INIT = 0, ST_WAIT = 1, ST_ANIM = 2;
  static uint8_t state = ST_INIT;

  // finite state machine to control what we do
  switch (state) {
    case ST_INIT:  // Initialize the display - done once only on first call
      for (int8_t i = DIGITS_SIZE - 1; i >= 0; i--) {
        // separate digits
        digit[i].oldValue = '0' + value % 10;
        value = value / 10;
      }

      // Display the starting number
      for (int8_t i = DIGITS_SIZE - 1; i >= 0; i--) {
        digit[i].charCols = matrix.getGraphicObject()->getChar(digit[i].oldValue, CHAR_COLS, digit[i].charMap);
      }
      updateDisplay(DIGITS_SIZE, digit);

      // Now we wait for a change
      state = ST_WAIT;
      break;

    case ST_WAIT:  // not animating - save new value digits and check if we need to animate
      for (int8_t i = DIGITS_SIZE - 1; i >= 0; i--) {
        // separate digits
        digit[i].newValue = '0' + value % 10;
        value = value / 10;

        if (digit[i].newValue != digit[i].oldValue) {
          // a change has been found - we will be animating something
          state = ST_ANIM;
          // initialize animation parameters for this digit
          digit[i].index = 0;
          digit[i].timeLastFrame = 0;
        }
      }

      if (state == ST_WAIT)  // no changes - keep waiting
        break;
      // else fall through as we need to animate from now

    case ST_ANIM:  // currently animating a change
      // work out the new intermediate bitmap for each character
      // 1. Get the 'new' character bitmap into temp buffer
      // 2. Shift this buffer down or up by current index amount
      // 3. Shift the current character by one pixel up or down
      // 4. Combine the new partial character and the existing character to produce a frame
      for (int8_t i = DIGITS_SIZE - 1; i >= 0; i--) {
        if ((digit[i].newValue != digit[i].oldValue) &&                    // values are different
            (millis() - digit[i].timeLastFrame >= ANIMATION_FRAME_DELAY))  // timer has expired
        {
          uint8_t newChar[CHAR_COLS] = { 0 };

          matrix.getGraphicObject()->getChar(digit[i].newValue, CHAR_COLS, newChar);
          if (((digit[i].newValue > digit[i].oldValue) ||  // incrementing
               (digit[i].oldValue == '9' && digit[i].newValue == '0'))
              &&                                                        // wrapping around on increase
              !(digit[i].oldValue == '0' && digit[i].newValue == '9'))  // not wrapping around on decrease
          {
            // scroll down
            for (uint8_t j = 0; j < digit[i].charCols; j++) {
              newChar[j] = newChar[j] >> (COL_SIZE - 1 - digit[i].index);
              digit[i].charMap[j] = digit[i].charMap[j] << 1;
              digit[i].charMap[j] |= newChar[j];
            }
          } else {
            // scroll up
            for (uint8_t j = 0; j < digit[i].charCols; j++) {
              newChar[j] = newChar[j] << (COL_SIZE - 1 - digit[i].index);
              digit[i].charMap[j] = digit[i].charMap[j] >> 1;
              digit[i].charMap[j] |= newChar[j];
            }
          }

          // set new parameters for next animation and check if we are done
          digit[i].index++;
          digit[i].timeLastFrame = millis();
          if (digit[i].index >= COL_SIZE)
            digit[i].oldValue = digit[i].newValue;  // done animating
        }
      }

      updateDisplay(DIGITS_SIZE, digit);  // show new display

      // are we done animating?
      {
        boolean allDone = true;

        for (uint8_t i = 0; allDone && (i < DIGITS_SIZE); i++) {
          allDone = allDone && (digit[i].oldValue == digit[i].newValue);
        }

        if (allDone) state = ST_WAIT;
      }
      break;

    default:
      state = 0;
  }

  return (state == ST_WAIT);  // animation has ended
}

// const int debugled = 12;
// boolean debugflag = false;

void setup() {
  rtc.begin();
  matrix.begin();
  // pinMode(debugled, OUTPUT);
  matrix.setSpriteData(pacman1, W_PMAN1, F_PMAN1, pacman1, W_PMAN1, F_PMAN1);
}

void loop() {
  DateTime now = rtc.now();
  static uint8_t index = 0;

  if (displayflag == false) {
    displayValue(now.hour());
    if (now.second() != prevseconds) {
      counter++;
      prevseconds = now.second();
    }
  } else {
    counter = 0;
    if (matrix.displayAnimate()) {
      if (index >= ARRAY_SIZE(params)) {
        matrix.displayReset();
        displayflag = false;
      }
      matrix.displayText(params[index].message, PA_CENTER, params[index].speed, params[index].pause, params[index].entryEffect, params[index].exitEffect);
      index++;
    }
  }
}

I am sorry in advance for any breach in forum rules as my english is quite poor.
Any help is highly appreciated :slight_smile:

One thing that jumps out at me is you are not resetting index to zero in loop().

very unspecific
What do you mean by that?
In this sentence you are using so hard generalised words that it could mean anything.
If there is one basic rule about programming in "classical" programming languages it is
be as specific as ever possible.

don't waste readers time
Without beeing precise you are waisting the readers or supporters time because you make them ask back for the details that you thought you would leave out.

make it efficient and detailed
If you arrange your postings in paragraphs maybe even with bold titles what the paragraph is about this will be the most efficient way of asking for help

@marco_c Well that was mistake left out in this code that I posted but it was there previously. I was thinking of resetting the index after toggling the displayflag variable. So I would have placed the index = 0; here :-

if (displayflag == false) {
    index = 0; //I WOULD HAVE PLACED IT HERE. 
    displayValue(now.hour());
    if (now.second() != prevseconds) {
      counter++;
      prevseconds = now.second();
    }
  } else {
    counter = 0;
    if (matrix.displayAnimate()) {
      if (index >= ARRAY_SIZE(params)) {
        matrix.displayReset();
        displayflag = false;
      }
      matrix.displayText(params[index].message, PA_CENTER, params[index].speed, params[index].pause, params[index].entryEffect, params[index].exitEffect);
      index++;
    }

@StefanL38 I am so sorry for my wording, here let me edit my original post so it would be more readable.

Firstly, I would recommend (if you are not already doing this) that you actually post the code that you tested and make comments on rather than some other version as this makes it confusing for those reading the code.

I don't agree with the placement of index = 0. It should be set to zero in the if (index >= ...) statement.

  1. If you know it is out of bounds, reset it where you check. This makes the intent of the code clear and easier to debug.
  2. More importantly, just below the if statement you are actually using index , which may still be out of bounds because you have not reset it. This will reference a params[] array element that does not exist, with undefined consequences (a reset or system hang is common).

It still needs to be improved, but is this something you want to do?

Firstly I would like to apologize for my late replies but that can't be helped as I fell sick the last few days.

@marco_c Good sir I have already posted the code and my modifications of the pushwheel example as given below. I have two things to say to you and they are :-

  1. My logic on resetting the index variable is that the variable should be reset away from the code block in which its being used, here in the case of index variable it is being used in the displayflag == true condition so I have incremented here, and I have reset the variable at the condition displayflag == false as its not being used there.
    I don't know how nice it is to reset the variables this way but this method worked out for me always.
  2. Index is actually not out of bounds and is behaving normally which I checked when the used the displayValue() function provided by @ruilviana. It seems that my modification of the displayValue() function is causing the problem.
    Can anyone point out to me where I am making a mistake. Here is my modification of the displayValue() function, which is a update from what is provided in the original code.
boolean displayValue(uint16_t hours, uint16_t minutes, uint16_t seconds) {
  const uint8_t DIGITS_SIZE = 8;  //"HH:MM:SS"
  static struct digitData digit[DIGITS_SIZE];

  const uint8_t ST_INIT = 0, ST_WAIT = 1, ST_ANIM = 2;
  static uint8_t state = ST_INIT;

  // finite state machine to control what we do
  switch (state) {
    case ST_INIT:  // Initialize the display - done once only on first call
      digit[7].oldValue = '0' + seconds % 10;
      digit[6].oldValue = '0' + seconds / 10;
      digit[5].oldValue = ':';
      digit[4].oldValue = '0' + minutes % 10;
      digit[3].oldValue = '0' + minutes / 10;
      digit[2].oldValue = ':';
      digit[1].oldValue = '0' + hours % 10;
      digit[0].oldValue = '0' + hours / 10;


      // Display the starting number
      for (int8_t i = DIGITS_SIZE - 1; i >= 0; i--) {
        digit[i].charCols = matrix.getGraphicObject()->getChar(digit[i].oldValue, CHAR_COLS, digit[i].charMap);
      }
      updateDisplay(DIGITS_SIZE, digit);

      // Now we wait for a change
      state = ST_WAIT;
      break;

    case ST_WAIT:  // Check for the values displayed for changes in them
      digit[7].newValue = '0' + seconds % 10;
      digit[6].newValue = '0' + seconds / 10;
      digit[5].newValue = ':';
      digit[4].newValue = '0' + minutes % 10;
      digit[3].newValue = '0' + minutes / 10;
      digit[2].newValue = ':';
      digit[1].newValue = '0' + hours % 10;
      digit[0].newValue = '0' + hours / 10;

      for (uint8_t i = 8; i > 0; i--) {
        if (digit[i].newValue != digit[i].oldValue) {
          state = ST_ANIM;
          digit[i].index = 0;
          digit[i].timeLastFrame = 0;
        }
      }

      if (state == ST_WAIT) {
      }
      break;

    case ST_ANIM:
      for (uint8_t i = 8; i > 0; i--) {
        //update direction from seconds to hours
        if ((digit[i].newValue != digit[i].oldValue) && (millis() - digit[i].timeLastFrame >= ANIMATION_FRAME_DELAY)) {
          //if mismatch is detected then start the timer for the animation
          uint8_t newChar[CHAR_COLS] = { 0 };
          matrix.getGraphicObject()->getChar(digit[i].newValue, CHAR_COLS, newChar);
          if (((digit[i].newValue > digit[i].oldValue) || (digit[i].oldValue == '9' && digit[i].newValue == '0')) && !(digit[i].oldValue == '0' && digit[i].newValue == '9')) {
            for (uint8_t j = 0; j < digit[7].charCols; j++) {
              newChar[j] = newChar[j] >> (COL_SIZE - 1 - digit[i].index);
              digit[i].charMap[j] = digit[i].charMap[j] << 1;
              digit[i].charMap[j] |= newChar[j];
            }
          } else {
            // scroll up
            for (uint8_t j = 0; j < digit[7].charCols; j++) {
              newChar[j] = newChar[j] << (COL_SIZE - 1 - digit[i].index);
              digit[i].charMap[j] = digit[i].charMap[j] >> 1;
              digit[i].charMap[j] |= newChar[j];
            }
          }
        }
        // set new parameters for next animation and check if we are done
        digit[i].index++;
        digit[i].timeLastFrame = millis();
        if (digit[i].index >= COL_SIZE)
          digit[i].oldValue = digit[i].newValue;  // done animating
      }
      updateDisplay(DIGITS_SIZE, digit);  // show new display

      // are we done animating?
      {
        boolean allDone = true;
        for (uint8_t i = 0; allDone && (i < DIGITS_SIZE); i++) {
          allDone = allDone && (digit[i].oldValue == digit[i].newValue);
        }
        if (allDone) state = ST_WAIT;
      }
      break;
    default:
      state = 0;
  }

  return (state == ST_WAIT);  // animation has ended
}

Also my many thanks to @ruilviana who created an entire simulation on a whim. Yes kind sir this is what I wanted. No offense I am able to do things on my own but I asked when I got stuck.
My many thanks to both @marco_c and @ruilviana .

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.