Parola - Switching between single zone double height display to multi zone

I am trying to do a clock that will alternate between a single zone double height display (Max7219 7 x 2) to multi zone (2 zones of 7 Max 7219). Does anyone know if this is possible and how do you do it?

Everything is possible. :grinning:

Where is the code you have worked on so far? Read the instructions for how to post it (section 7).

And the description of what you have constructed.

Absolutely possible. You need to initialise the library with maximum zones and redefine the boundaries when you want to change them. The Zone_Dynamic example does this.

Thank you so much for your replies. Sorry for the noob post. I have modified the example file for "Parola_D_Height_Clock" to get time from the internet instead of using a fake clock time. and I can get the double height clock time with a 7 x 2 Max7219 matrix LED setup. However, I just can't figure out how do you alternate between a Double height clock and a single height with multi zones. What I want to do is to have the display change from simple large fonts (Double height) to 2 halves. Top showing the time and the bottom half with scrolling text showing the day and date.

The code below has the LED display initialised in setup(). If I were to have the displays change between the 2, should it be in loop()?

// Header file includes
#include <ESP8266WiFi.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include "Font_Data.h"

// Define the number of devices we have in the chain and the hardware interface
// NOTE: These pin numbers will probably not work with your hardware and may
// need to be adapted
#define DEBUG(x)
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_ZONES 2
#define ZONE_SIZE 7
#define MAX_DEVICES (MAX_ZONES * ZONE_SIZE)

#define ZONE_UPPER  1
#define ZONE_LOWER  0

#define DATA_PIN  D7
#define CS_PIN    D6
#define CLK_PIN   D5

// =======================================================================
// Your config below!
// =======================================================================
const char* ssid     = "xxxx";     // SSID of local network
const char* password = "xxxxxxxxx";   // Password on network
long utcOffset = 1;                    // UTC for Warsaw,Poland
// =======================================================================

int h, m, s, day, month, year, dayOfWeek;
//int summerTime = 0; // calculated in code from date
long localEpoc = 0;
long localMillisAtUpdate = 0;
String date;
String buf="";

// Hardware SPI connection
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary output pins
// MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);

#define SPEED_TIME  75
#define PAUSE_TIME  0

#define MAX_MESG  6

// Hardware adaptation parameters for scrolling
bool invertUpperZone = false;

// Global variables
char  szTimeL[MAX_MESG];    // mm:ss\0
char  szTimeH[MAX_MESG];

void getTime(char *psz, bool f = true)
{
  WiFiClient client;
  if(!client.connect("www.google.com", 80)) {
    return;
  }
  client.print(String("GET / HTTP/1.1\r\n") +
               String("Host: www.google.com\r\n") +
               String("Connection: close\r\n\r\n"));

  int repeatCounter = 10;
  while (!client.available() && repeatCounter--) {
    delay(200); 
  }

  String line;
  client.setNoDelay(false);
  int dateFound = 0;
  while(client.connected() && client.available() && !dateFound) {
    line = client.readStringUntil('\n');
    line.toUpperCase();
    // Date: Thu, 19 Nov 2015 20:25:40 GMT
    if(line.startsWith("DATE: ")) {
      localMillisAtUpdate = millis();
      dateFound = 1;
      date = line.substring(6, 22);
      date.toUpperCase();
      decodeDate(date);
      //Serial.println(line);
      h = line.substring(23, 25).toInt();
      m = line.substring(26, 28).toInt();
      s = line.substring(29, 31).toInt();
      if(h+utcOffset>23) {
        if(++day>31) { day=1; month++; };  // needs better patch
        if(++dayOfWeek>7) dayOfWeek=1; 
      }
      localEpoc = h * 60 * 60 + m * 60 + s;
      //h = 15; // testing Hour
      if(h+utcOffset>23) {h=h-24;}
      h = h + utcOffset;
      sprintf(psz, "%2d%c%02d", h, (f ? ':' : ' '), m);
    }
  }
  client.stop();
}

void createHString(char *pH, char *pL)
{
  for (; *pL != '\0'; pL++)
    *pH++ = *pL | 0x80;   // offset character
    *pH = '\0'; // terminate the string
}

void setup(void)
{
  buf.reserve(500);
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  invertUpperZone = (HARDWARE_TYPE == MD_MAX72XX::GENERIC_HW || HARDWARE_TYPE == MD_MAX72XX::PAROLA_HW);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  // initialise the LED display
  P.begin(MAX_ZONES);

  // Set up zones for 2 halves of the display
  P.setZone(ZONE_LOWER, 0, ZONE_SIZE - 1);
  P.setZone(ZONE_UPPER, ZONE_SIZE, MAX_DEVICES - 1);
  P.setFont(numeric7SegDouble);

  P.setCharSpacing(P.getCharSpacing() * 2); // double height --> double spacing

  if (invertUpperZone)
  {
    P.setZoneEffect(ZONE_UPPER, true, PA_FLIP_UD);
    P.setZoneEffect(ZONE_UPPER, true, PA_FLIP_LR);

    P.displayZoneText(ZONE_LOWER, szTimeL, PA_RIGHT, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
    P.displayZoneText(ZONE_UPPER, szTimeH, PA_LEFT, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
  }
  else
  {
    P.displayZoneText(ZONE_LOWER, szTimeL, PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
    P.displayZoneText(ZONE_UPPER, szTimeH, PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
  }
}

void loop(void)
{
  static uint32_t	lastTime = 0; // millis() memory
  static bool	flasher = false;  // seconds passing flasher

  P.displayAnimate();
  if (P.getZoneStatus(ZONE_LOWER) && P.getZoneStatus(ZONE_UPPER))
  {
    // Adjust the time string if we have to. It will be adjusted
    // every second at least for the flashing colon separator.
    if (millis() - lastTime >= 1000)
    {
      lastTime = millis();
      getTime(szTimeL, flasher);
      createHString(szTimeH, szTimeL);
      flasher = !flasher;

      P.displayReset();

      // synchronise the start
      P.synchZoneStart();
    }
  }
}

void decodeDate(String date)
{
  switch(date.charAt(0)) {
    case 'M': dayOfWeek=1; break;
    case 'T': dayOfWeek=(date.charAt(1)=='U')?2:4; break;
    case 'W': dayOfWeek=3; break;
    case 'F': dayOfWeek=5; break;
    case 'S': dayOfWeek=(date.charAt(1)=='A')?6:7; break;
  }
  int midx = 6;
  if(isdigit(date.charAt(midx))) midx++;
  midx++;
  switch(date.charAt(midx)) {
    case 'F': month = 2; break;
    case 'M': month = (date.charAt(midx+2)=='R') ? 3 : 5; break;
    case 'A': month = (date.charAt(midx+1)=='P') ? 4 : 8; break;
    case 'J': month = (date.charAt(midx+1)=='A') ? 1 : ((date.charAt(midx+2)=='N') ? 6 : 7); break;
    case 'S': month = 9; break;
    case 'O': month = 10; break;
    case 'N': month = 11; break;
    case 'D': month = 12; break;
  }
  day = date.substring(5, midx-1).toInt();
  year = date.substring(midx+4, midx+9).toInt();
  return;
}

void updateTime()
{
  long curEpoch = localEpoc + ((millis() - localMillisAtUpdate) / 1000);
  long epoch = round(curEpoch + 3600 * utcOffset + 86400L) % 86400L;
  h = ((epoch  % 86400L) / 3600) % 24;
  m = (epoch % 3600) / 60;
  s = epoch % 60;
}

If I were to have the displays change between the 2, should it be in loop()?

You need to be clear when the display should change. I see no evidence of this in the code you have provided. What event will cause the change from one mode to the other? When you detect this event, that is when you change the whole display over to the other mode. If this is in loop() then that is where to do it. If it is somewhere else, then that is the place to do it.

I would advise that you write a function that does this using a parameter to decide which mode to set in the function, or write 2 separate functions to set up the display (one for each mode). then call this from setup() (to keep it consistent) and call it from wherever else you need to. This way your code will be a bit more modular and you can experiment if you need to by just changing the function call location.

The code I have here is only for a double height clock display. I would want the displays to switch every set amount of time(i.e every minute or few minutes.). So i will create 2 functions 1 for each display then I will call them in setup or loop?

Im confused with the code. There is parts of the display setup in setup()

  // initialise the LED display
  P.begin(MAX_ZONES);

  // Set up zones for 2 halves of the display
  P.setZone(ZONE_LOWER, 0, ZONE_SIZE - 1);
  P.setZone(ZONE_UPPER, ZONE_SIZE, MAX_DEVICES - 1);
  P.setFont(numeric7SegDouble);

  P.setCharSpacing(P.getCharSpacing() * 2); // double height --> double spacing

  if (invertUpperZone)
  {
    P.setZoneEffect(ZONE_UPPER, true, PA_FLIP_UD);
    P.setZoneEffect(ZONE_UPPER, true, PA_FLIP_LR);

    P.displayZoneText(ZONE_LOWER, szTimeL, PA_RIGHT, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
    P.displayZoneText(ZONE_UPPER, szTimeH, PA_LEFT, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
  }
  else
  {
    P.displayZoneText(ZONE_LOWER, szTimeL, PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
    P.displayZoneText(ZONE_UPPER, szTimeH, PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
  }
}

And then in Loop you have another part:

  P.displayAnimate();
  if (P.getZoneStatus(ZONE_LOWER) && P.getZoneStatus(ZONE_UPPER))
  {
    // Adjust the time string if we have to. It will be adjusted
    // every second at least for the flashing colon separator.
    if (millis() - lastTime >= 1000)
    {
      lastTime = millis();
      getTime(szTimeL, flasher);
      createHString(szTimeH, szTimeL);
      flasher = !flasher;

      P.displayReset();

      // synchronise the start
      P.synchZoneStart();

If I were to create a function for the double height clock, which should I put into the function?

As understand what you want to do is this:

  • Display a double height display of time
  • At some event, change the display to 2 lines, with time and scrolling other information.
  • At some event, change back to double height display
  • etc

So you will ALWAYS need 2 zones. This means that the zone completion/renewal in loop() should basically remain the same for both cases.

Switching between the 2x and 1x displays therefore means changing the font for the zones and the messages you display.

marco_c:
As understand what you want to do is this:

  • Display a double height display of time
  • At some event, change the display to 2 lines, with time and scrolling other information.
  • At some event, change back to double height display
  • etc

So you will ALWAYS need 2 zones. This means that the zone completion/renewal in loop() should basically remain the same for both cases.

Switching between the 2x and 1x displays therefore means changing the font for the zones and the messages you display.

Thank you for your help so far. I am still not sure where to put this code to switch between the 2x and 1x displays

I have not edited the code to put the part which sets up the double height display (Not sure if Im correct):

// Header file includes
#include <ESP8266WiFi.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include "Font_Data.h"

// Define the number of devices we have in the chain and the hardware interface
// NOTE: These pin numbers will probably not work with your hardware and may
// need to be adapted
#define DEBUG(x)
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_ZONES 2
#define ZONE_SIZE 7
#define MAX_DEVICES (MAX_ZONES * ZONE_SIZE)

#define ZONE_UPPER  1
#define ZONE_LOWER  0

#define DATA_PIN  D7
#define CS_PIN    D6
#define CLK_PIN   D5

// =======================================================================
// Your config below!
// =======================================================================
const char* ssid     = "xxxx";     // SSID of local network
const char* password = "xxxxxxxx";   // Password on network
long utcOffset = 1;                    // UTC for Warsaw,Poland
// =======================================================================

int h, m, s, day, month, year, dayOfWeek;
//int summerTime = 0; // calculated in code from date
long localEpoc = 0;
long localMillisAtUpdate = 0;
String date;
String buf="";

// Hardware SPI connection
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// Arbitrary output pins
// MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);

#define SPEED_TIME  75
#define PAUSE_TIME  0

#define MAX_MESG  6

// Hardware adaptation parameters for scrolling
bool invertUpperZone = false;

// Global variables
char  szTimeL[MAX_MESG];    // mm:ss\0
char  szTimeH[MAX_MESG];

void getTime(char *psz, bool f = true)
{
  WiFiClient client;
  if(!client.connect("www.google.com", 80)) {
    return;
  }
  client.print(String("GET / HTTP/1.1\r\n") +
               String("Host: www.google.com\r\n") +
               String("Connection: close\r\n\r\n"));

  int repeatCounter = 10;
  while (!client.available() && repeatCounter--) {
    delay(200); 
  }

  String line;
  client.setNoDelay(false);
  int dateFound = 0;
  while(client.connected() && client.available() && !dateFound) {
    line = client.readStringUntil('\n');
    line.toUpperCase();
    // Date: Thu, 19 Nov 2015 20:25:40 GMT
    if(line.startsWith("DATE: ")) {
      localMillisAtUpdate = millis();
      dateFound = 1;
      date = line.substring(6, 22);
      date.toUpperCase();
      decodeDate(date);
      //Serial.println(line);
      h = line.substring(23, 25).toInt();
      m = line.substring(26, 28).toInt();
      s = line.substring(29, 31).toInt();
      if(h+utcOffset>23) {
        if(++day>31) { day=1; month++; };  // needs better patch
        if(++dayOfWeek>7) dayOfWeek=1; 
      }
      localEpoc = h * 60 * 60 + m * 60 + s;
      //h = 15; // testing Hour
      if(h+utcOffset>23) {h=h-24;}
      h = h + utcOffset;
      sprintf(psz, "%2d%c%02d", h, (f ? ':' : ' '), m);
    }
  }
  client.stop();
}

void createHString(char *pH, char *pL)
{
  for (; *pL != '\0'; pL++)
    *pH++ = *pL | 0x80;   // offset character
    *pH = '\0'; // terminate the string
}

void setup(void)
{
  buf.reserve(500);
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  invertUpperZone = (HARDWARE_TYPE == MD_MAX72XX::GENERIC_HW || HARDWARE_TYPE == MD_MAX72XX::PAROLA_HW);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  Dheightclock();
}


void loop(void)
{
  static uint32_t	lastTime = 0; // millis() memory
  static bool	flasher = false;  // seconds passing flasher

  P.displayAnimate();
  if (P.getZoneStatus(ZONE_LOWER) && P.getZoneStatus(ZONE_UPPER))
  {
    // Adjust the time string if we have to. It will be adjusted
    // every second at least for the flashing colon separator.
    if (millis() - lastTime >= 1000)
    {
      lastTime = millis();
      getTime(szTimeL, flasher);
      createHString(szTimeH, szTimeL);
      flasher = !flasher;

      P.displayReset();

      // synchronise the start
      P.synchZoneStart();
    }
  }
}

void decodeDate(String date)
{
  switch(date.charAt(0)) {
    case 'M': dayOfWeek=1; break;
    case 'T': dayOfWeek=(date.charAt(1)=='U')?2:4; break;
    case 'W': dayOfWeek=3; break;
    case 'F': dayOfWeek=5; break;
    case 'S': dayOfWeek=(date.charAt(1)=='A')?6:7; break;
  }
  int midx = 6;
  if(isdigit(date.charAt(midx))) midx++;
  midx++;
  switch(date.charAt(midx)) {
    case 'F': month = 2; break;
    case 'M': month = (date.charAt(midx+2)=='R') ? 3 : 5; break;
    case 'A': month = (date.charAt(midx+1)=='P') ? 4 : 8; break;
    case 'J': month = (date.charAt(midx+1)=='A') ? 1 : ((date.charAt(midx+2)=='N') ? 6 : 7); break;
    case 'S': month = 9; break;
    case 'O': month = 10; break;
    case 'N': month = 11; break;
    case 'D': month = 12; break;
  }
  day = date.substring(5, midx-1).toInt();
  year = date.substring(midx+4, midx+9).toInt();
  return;
}

void Dheightclock()
{
  // initialise the LED display
  P.begin(MAX_ZONES);

  // Set up zones for 2 halves of the display
  P.setZone(ZONE_LOWER, 0, ZONE_SIZE - 1);
  P.setZone(ZONE_UPPER, ZONE_SIZE, MAX_DEVICES - 1);
  P.setFont(numeric7SegDouble);

  P.setCharSpacing(P.getCharSpacing() * 2); // double height --> double spacing

  if (invertUpperZone)
  {
    P.setZoneEffect(ZONE_UPPER, true, PA_FLIP_UD);
    P.setZoneEffect(ZONE_UPPER, true, PA_FLIP_LR);

    P.displayZoneText(ZONE_LOWER, szTimeL, PA_RIGHT, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
    P.displayZoneText(ZONE_UPPER, szTimeH, PA_LEFT, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
  }
  else
  {
    P.displayZoneText(ZONE_LOWER, szTimeL, PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
    P.displayZoneText(ZONE_UPPER, szTimeH, PA_CENTER, SPEED_TIME, PAUSE_TIME, PA_PRINT, PA_NO_EFFECT);
  }
}

So is it correct if I created another function and call in under setup like how I called the function Dheightclock?

void setup(void)
{
  buf.reserve(500);
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  invertUpperZone = (HARDWARE_TYPE == MD_MAX72XX::GENERIC_HW || HARDWARE_TYPE == MD_MAX72XX::PAROLA_HW);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  Dheightclock();
}

Something like that.

There must have been code for setting the double height and you removed it, because this

invertUpperZone = (HARDWARE_TYPE == MD_MAX72XX::GENERIC_HW || HARDWARE_TYPE == MD_MAX72XX::PAROLA_HW);

is part of that code.

Please refer to the double height examples to work out which code you need to copy. It is all there but you need to start understanding what you are reading in the code.

marco_c:
Something like that.

There must have been code for setting the double height and you removed it, because this

invertUpperZone = (HARDWARE_TYPE == MD_MAX72XX::GENERIC_HW || HARDWARE_TYPE == MD_MAX72XX::PAROLA_HW);

is part of that code.

Please refer to the double height examples to work out which code you need to copy. It is all there but you need to start understanding what you are reading in the code.

OK I now move this line into the function Dheightclock(). And here is where I am really confused.

  1. Dheightclock() is called in setup and doesn't this only happen once when the program runs? If I were to keep switching between 2x and 1 x display, isn't this supposed to be in loop to keep switching between the 2 different displays?

  2. The only place I can see that the program calls to display the time is this code:

sprintf(psz, "%2d%c%02d", h, (f ? ':' : ' '), m);

which is located in the function getTime() which is called upon in loop(). I will want to call a different format to display the time and date in the 2x display. Where should this be?

Sorry but you are asking me very basic questions about how to program, not about what the libraries, etc do. I really don't have the time for the level of assistance you need, so someone else may want to help you instead.

I would suggest that you do a few basic tutorials on programming in C++ before you tackle this as it seems to me that you need to get more foundational knowledge on how programs/functions work before you tackle this reasonably challenging project for a beginner.