MD_Parola, Eight 8x8 LED MAX7219 PCBs connected serially. Possible to enable endless scrolling?

Hi all,

I have an older meeting display sign which has and does work well in every respect.
I would like to endless message scrolling. Is it possible?

Many thanks
Andy

#include <ESP8266WiFi.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <EEPROM.h>

// EEPROM addresses for WiFi credentials
#define EEPROM_SIZE 512
#define SSID_ADDR 0
#define PASS_ADDR 32

// LED matrix configuration
#define MAX_DEVICES 8
#define CLK_PIN   14 // SCK
#define DATA_PIN  13 // MOSI
#define CS_PIN    15 // SS
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);

// WiFi parameters
char ssid[32] = "**********";
char password[32] = "**********";
WiFiServer server(80);

// Message buffer
const uint8_t MESG_SIZE = 255;
char curMessage[MESG_SIZE];
char WebResponse[] = "HTTP/1.1 200 OK\nContent-Type: text/html\n\n";

// Message input webpage
char WebPage[] =
  "<!DOCTYPE html>"
  "<html>"
  "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
  "<title>ESP8266 AND MAX7219</title>"
  "<style>"
  "html, body { font-family: Helvetica; text-align: center; background-color: #f0f0f0; }"
  "form { margin: 20px auto; width: 300px; padding: 20px; border: 1px solid #ccc; background-color: #fff; }"
  "</style>"
  "<script>"
  "strLine = \"\";"
  "function SendText() {"
  "  nocache = \"/&nocache=\" + Math.random() * 1000000;"
  "  var request = new XMLHttpRequest();"
  "  strLine = \"&MSG=\" + document.getElementById(\"txt_form\").Message.value;"
  "  request.open(\"GET\", strLine + nocache, false);"
  "  request.send(null);"
  "  alert(\"Message sent!\");"
  "}"
  "</script>"
  "</head>"
  "<body>"
  "<h1>ESP8266 and MAX7219 LED Matrix</h1>"
  "<form id=\"txt_form\" name=\"frmText\">"
  "<label>Message: <input type=\"text\" name=\"Message\" maxlength=\"255\"></label><br><br>"
  "<input type=\"submit\" value=\"Send Text\" onclick=\"SendText()\">"
  "</form>"
  "</body>"
  "</html>";

// AP mode configuration webpage
char ConfigPage[] =
  "<!DOCTYPE html>"
  "<html>"
  "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
  "<title>ESP8266 WiFi Config</title>"
  "<style>"
  "html, body { font-family: Helvetica; text-align: center; background-color: #f0f0f0; }"
  "form { margin: 20px auto; width: 300px; padding: 20px; border: 1px solid #ccc; background-color: #fff; }"
  "</style>"
  "</head>"
  "<body>"
  "<h1>Configure WiFi</h1>"
  "<form method=\"GET\">"
  "<label>SSID: <input type=\"text\" name=\"ssid\" maxlength=\"31\"></label><br><br>"
  "<label>Password: <input type=\"text\" name=\"pass\" maxlength=\"31\"></label><br><br>"
  "<input type=\"submit\" value=\"Save and Reboot\">"
  "</form>"
  "</body>"
  "</html>";

uint8_t htoi(char c)
{
  c = toupper(c);
  if (c >= '0' && c <= '9') return c - '0';
  if (c >= 'A' && c <= 'F') return c - 'A' + 0xa;
  return 0;
}

boolean getText(char *szMesg, char *psz, uint8_t len)
{
  boolean isValid = false;
  char *pStart = strstr(szMesg, "/&MSG=");
  if (pStart)
  {
    pStart += 6;
    char *pEnd = strstr(pStart, "/&");
    if (pEnd)
    {
      while (pStart != pEnd && (psz - curMessage < len))
      {
        if (*pStart == '%' && isdigit(*(pStart + 1)))
        {
          char c = 0;
          pStart++;
          c += (htoi(*pStart++) << 4);
          c += htoi(*pStart++);
          *psz++ = c;
        }
        else
          *psz++ = *pStart++;
      }
      *psz = '\0';
      isValid = true;
    }
  }
  return isValid;
}

void saveCredentials(const char* newSSID, const char* newPass)
{
  for (int i = 0; i < 32; i++)
  {
    EEPROM.write(SSID_ADDR + i, i < strlen(newSSID) ? newSSID[i] : 0);
    EEPROM.write(PASS_ADDR + i, i < strlen(newPass) ? newPass[i] : 0);
  }
  EEPROM.commit();
}

void loadCredentials()
{
  char tempSSID[32] = "";
  char tempPass[32] = "";
  bool valid = true;

  for (int i = 0; i < 32; i++)
  {
    tempSSID[i] = EEPROM.read(SSID_ADDR + i);
    tempPass[i] = EEPROM.read(PASS_ADDR + i);
    if (tempSSID[i] > 126 || (tempSSID[i] < 32 && tempSSID[i] != 0)) valid = false;
  }

  if (valid && tempSSID[0] != 0 && strlen(tempSSID) > 0)
  {
    strncpy(ssid, tempSSID, 32);
    strncpy(password, tempPass, 32);
  }
}

void handleWiFi(void)
{
  static char szBuf[1024];
  WiFiClient client = server.available();
  if (!client || !client.connected()) return;

  unsigned long timeStart = millis();
  uint16_t idxBuf = 0;
  while (client.available() && millis() - timeStart < 1000)
  {
    char c = client.read();
    if (c == '\r' || c == '\n')
    {
      szBuf[idxBuf] = '\0';
      client.flush();
      if (getText(szBuf, curMessage, MESG_SIZE))
      {
        strcat(curMessage, "        "); // Add spaces for continuous scrolling
        P.displayZoneText(0, curMessage, PA_LEFT, 50, 0, PA_SCROLL_LEFT, PA_NO_EFFECT);
      }
      client.print(WebResponse);
      client.print(WebPage);
      client.stop();
      return;
    }
    else if (idxBuf < sizeof(szBuf) - 1)
      szBuf[idxBuf++] = c;
  }
  client.stop();
}

void handleAPMode()
{
  WiFiClient client = server.available();
  if (client)
  {
    String request = client.readStringUntil('\r');
    client.flush();

    if (request.indexOf("GET /?ssid=") != -1)
    {
      String newSSID = "";
      String newPass = "";
      int ssidStart = request.indexOf("ssid=") + 5;
      int ssidEnd = request.indexOf("&pass=");
      int passStart = request.indexOf("pass=") + 5;
      int passEnd = request.indexOf(" HTTP/");

      if (ssidStart != -1 && ssidEnd != -1)
        newSSID = request.substring(ssidStart, ssidEnd);
      if (passStart != -1 && passEnd != -1)
        newPass = request.substring(passStart, passEnd);

      if (newSSID.length() > 0 && newPass.length() > 0)
      {
        saveCredentials(newSSID.c_str(), newPass.c_str());
        client.print(WebResponse);
        client.print("<html><body><h1>Credentials Saved</h1><p>Rebooting...</p></body></html>");
        client.stop();
        delay(1000);
        ESP.restart();
      }
    }
    else
    {
      client.print(WebResponse);
      client.print(ConfigPage);
      client.stop();
    }
  }
}

void setup()
{
  EEPROM.begin(EEPROM_SIZE);
  loadCredentials();
  P.begin();
  P.setZone(0, 0, MAX_DEVICES - 1); // Standard zone for all modules
  P.setZoneEffect(0, true, PA_FLIP_UD); // Flip up-down first
  P.setZoneEffect(0, true, PA_FLIP_LR); // Then flip left-right for 180 degrees
  //P.setTextAlignment(PA_CENTER);

  curMessage[0] = '\0';
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - start < 15000)
    delay(500);

  if (WiFi.status() != WL_CONNECTED)
  {
    WiFi.disconnect();
    WiFi.mode(WIFI_AP);
    WiFi.softAP("ESP8266_Config", "1234");
    strcpy(curMessage, "AP Mode: ESP8266_Config");
  }
  else
  {
    sprintf(curMessage, "%03d:%03d:%03d:%03d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]);
  }
  strcat(curMessage, ""); // Add spaces for continuous scrolling
  server.begin();
  P.displayZoneText(0, curMessage, PA_RIGHT, 50, 0, PA_SCROLL_RIGHT, PA_NO_EFFECT);
}

void loop()
{
  if (WiFi.status() == WL_CONNECTED)
    handleWiFi();
  else
    handleAPMode();
  P.displayAnimate();
}

Hello, what is your processor board?

Hi, ESP8266

@negativ3

Hi

Try moving this line
P.displayZoneText(0, curMessage, PA_RIGHT, 50, 0, PA_SCROLL_RIGHT, PA_NO_EFFECT);

into loop()

with some added code

if (P.displayAnimate()) 
{
P.displayZoneText(0, curMessage, PA_RIGHT, 50, 0, PA_SCROLL_RIGHT, PA_NO_EFFECT);

P.displayReset();
}

Hi, each time through the loop P.displayAnimate only moves the message along one column of dots. will the "if" not reset the display after only one call? Away for a couple hours so cannot test.

P.displayAnimate() only returns true when the animation is finished and this case will reset it to the start again. In fact, just P.displayReset() is sufficient after the test without setting up all the display parameters again.

I shall give it a try!
Thank you for the assumption reset!