This blog is to explain how I achieved scrolling text on 2 zone FC16 display module (MAX7219) with Big Font and customizable text using a web page.

I have used Parola library and the example code provided in it. Click here to download the Library.

For base of this project, I have merged two exmaple codes

  1. Parola_Scrolling_ESP8266
  2. Parola_Double_Height_v2

The schematic of the circuit is as follows

Circuit

code is as follows


//
#include <ESP8266WiFi.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include "Font_Data1.h"

// Turn debugging on and off
#define DEBUG 0

#if DEBUG
#define PRINTS(s)   { Serial.print(F(s)); }
#define PRINT(s, v) { Serial.print(F(s)); Serial.print(v); }
#else
#define PRINTS(s)
#define PRINT(s, v)
#endif

// Define the main direction for scrolling double height.
// if 1, scroll left; if 0, scroll right
#define SCROLL_LEFT 1

// WiFi Server object and parameters
WiFiServer server(80);

// Hardware adaptation parameters for scrolling
bool invertUpperZone = false;
textEffect_t scrollUpper, scrollLower;

// Define the number of devices we have in the chain and the hardware interface
// NOTE: These pin numbers may not work with your hardware and may need changing
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_ZONES 2
#define ZONE_SIZE 8
#define MAX_DEVICES (MAX_ZONES * ZONE_SIZE)

#define ZONE_UPPER  1
#define ZONE_LOWER  0

#define PAUSE_TIME 0
#define SCROLL_SPEED 50

#define CLK_PIN   14
#define DATA_PIN  13
#define CS_PIN    15

// HARDWARE SPI
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// SOFTWARE SPI
//MD_Parola P = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);

// WiFi login parameters - network name and password
const char* ssid = "rajeshsirsikar";
const char* password = "Naina123$$";

#define BUFFER_SIZE 512
#define MAX_MESG  8

char msgL[BUFFER_SIZE];
char msgH[BUFFER_SIZE]; // allocated memory in setup()
char curMessage[BUFFER_SIZE];
char newMessage[BUFFER_SIZE];
char  szTimeL[MAX_MESG];    // mm:ss\0
char  szTimeH[MAX_MESG]; 

bool newMessageAvailable = false;

//Web response page

const char WebResponse[] = "HTTP/1.1 200 OK\nContent-Type: text/html\n\n";

char WebPage[] =
  "<!DOCTYPE html>" \
  "<html>" \
  "<head>" \
  "<title>MajicDesigns Test Page</title>" \

  "<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);" \
  "}" \
  "</script>" \
  "</head>" \

  "<body>" \
  "<p>MD_MAX72xx set message</p>" \

  "<form id="txt_form" name="frmText">" \
  "<label>Msg:<input type="text" name="Message" maxlength="255"></label><br><br>" \
  "</form>" \
  "<br>" \
  "<input type="submit" value="Send Text" onclick="SendText()">" \
  "</body>" \
  "</html>";

char *err2Str(wl_status_t code)
{
  switch (code)
  {
    case WL_IDLE_STATUS:    return ("IDLE");           break; // WiFi is in process of changing between statuses
    case WL_NO_SSID_AVAIL:  return ("NO_SSID_AVAIL");  break; // case configured SSID cannot be reached
    case WL_CONNECTED:      return ("CONNECTED");      break; // successful connection is established
    case WL_CONNECT_FAILED: return ("CONNECT_FAILED"); break; // password is incorrect
    case WL_DISCONNECTED:   return ("CONNECT_FAILED"); break; // module is not configured in station mode
    default: return ("??");
  }
}

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);
}

//===============================================================================================================

void getData(char *szMesg, uint8_t len)

{
  char *pStart, *pEnd;      // pointer to start and end of text

  // check text message
  pStart = strstr(szMesg, "/&MSG=");
  if (pStart != NULL)
  {
    char *psz = newMessage;

    pStart += 6;  // skip to start of data
    pEnd = strstr(pStart, "/&");

    if (pEnd != NULL)
    {
      while (pStart != pEnd)
      {
        if ((*pStart == '%') && isdigit(*(pStart + 1)))
        {
          // replace %xx hex code with the ASCII character
          char c = 0;
          pStart++;
          c += (htoi(*pStart++) << 4);
          c += htoi(*pStart++);
          *psz++ = c;
        }
        else
          *psz++ = *pStart++;
      }

      *psz = '\0'; // terminate the string
      newMessageAvailable = (strlen(newMessage) != 0);
      PRINT("\nNew Msg: ", newMessage);

    }
  }

}

// =======================================================================


void handleWiFi(void)
{
  static enum { S_IDLE, S_WAIT_CONN, S_READ, S_EXTRACT, S_RESPONSE, S_DISCONN } state = S_IDLE;
  static char szBuf[1024];
  static uint16_t idxBuf = 0;
  static WiFiClient client;
  static uint32_t timeStart;

  switch (state)
  {
    case S_IDLE:   // initialise
      PRINTS("\nS_IDLE");
      idxBuf = 0;
      state = S_WAIT_CONN;
      break;

    case S_WAIT_CONN:   // waiting for connection
      {
        client = server.available();
        if (!client) break;
        if (!client.connected()) break;

#if DEBUG
        char szTxt[20];
        sprintf(szTxt, "%03d:%03d:%03d:%03d", client.remoteIP()[0], client.remoteIP()[1], client.remoteIP()[2], client.remoteIP()[3]);
        PRINT("\nNew client @ ", szTxt);
#endif

        timeStart = millis();
        state = S_READ;
      }
      break;

    case S_READ: // get the first line of data
      PRINTS("\nS_READ ");

      while (client.available())
      {
        char c = client.read();

        if ((c == '\r') || (c == '\n'))
        {
          szBuf[idxBuf] = '\0';
          client.flush();
          PRINT("\nRecv: ", szBuf);
          state = S_EXTRACT;
        }
        else
          szBuf[idxBuf++] = (char)c;
      }
      if (millis() - timeStart > 1000)
      {
        PRINTS("\nWait timeout");
        state = S_DISCONN;
      }
      break;

    case S_EXTRACT: // extract data
      PRINTS("\nS_EXTRACT");
      // Extract the string from the message if there is one
      getData(szBuf, 512);
      state = S_RESPONSE;
      break;

    case S_RESPONSE: // send the response to the client
      PRINTS("\nS_RESPONSE");
      // Return the response to the client (web page)
      client.print(WebResponse);
      client.print(WebPage);
      state = S_DISCONN;
      break;

    case S_DISCONN: // disconnect client
      PRINTS("\nS_DISCONN");
      client.flush();
      client.stop();
      state = S_IDLE;
      break;

    default:  state = S_IDLE;
  }
}

//====================================================================================

void setup(void)
{
  uint8_t max = 0;

		#if DEBUG
		  Serial.begin(57600);
		  PRINTS("\n[Double_Height_v2]");
		#endif

		  // set up global parameters
		  invertUpperZone = (HARDWARE_TYPE == MD_MAX72XX::GENERIC_HW || HARDWARE_TYPE == MD_MAX72XX::PAROLA_HW);
		  if (invertUpperZone)
		  {
		#if SCROLL_LEFT // invert and scroll left
			scrollUpper = PA_SCROLL_RIGHT;
			scrollLower = PA_SCROLL_LEFT;
		#else           // invert and scroll right
			scrollUpper = PA_SCROLL_LEFT;
			scrollLower = PA_SCROLL_RIGHT;
		#endif
		  } 
		  else // not invert
		  {
		#if SCROLL_LEFT // not invert and scroll left
			scrollUpper = PA_SCROLL_LEFT;
			scrollLower = PA_SCROLL_LEFT;
		#else           // not invert and scroll right
			scrollUpper = PA_SCROLL_RIGHT;
			scrollLower = PA_SCROLL_RIGHT;
		#endif
		  }

		// 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(BigFont);
		  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);
		  }
		  PRINT("\nFLIP_UD=", P.getZoneEffect(ZONE_UPPER, PA_FLIP_UD));
		  PRINT("\nFLIP_LR=", P.getZoneEffect(ZONE_UPPER, PA_FLIP_LR));
		  PRINT("\nSCROLL_LEFT=", SCROLL_LEFT);

		  // Connect to and initialise WiFi network
		  PRINT("\nConnecting to ", ssid);

		  WiFi.begin(ssid, password);

		  while (WiFi.status() != WL_CONNECTED)
		  {
			PRINT("\n", err2Str(WiFi.status()));
			delay(500);
		  }
		  PRINTS("\nWiFi connected");

		  // Start the server
		  server.begin();
		  PRINTS("\nServer started");


		  // Set up first message as the IP address
		  sprintf(curMessage, "%03d:%03d:%03d:%03d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]);
		  PRINT("\nAssigned IP ", curMessage);


		}

//============================================================================================================================

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

		  *pH = '\0'; // terminate the string
		}

//=======================================================================================================

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

		  handleWiFi();
		  P.displayAnimate();

		  if (P.getZoneStatus(ZONE_LOWER) && P.getZoneStatus(ZONE_UPPER))
		  {			  
	  
  			if (newMessageAvailable)
				{
					{
					  strcpy(curMessage, newMessage);
						newMessageAvailable = false;
					  }

						char *msgL = curMessage;
						
						// set up the string
						createHString(msgH, msgL);
						
						P.displayClear();
						P.displayZoneText(ZONE_LOWER, msgL, PA_LEFT, SCROLL_SPEED, PAUSE_TIME, scrollLower, scrollLower);
						P.displayZoneText(ZONE_UPPER, msgH, PA_LEFT, SCROLL_SPEED, PAUSE_TIME, scrollUpper, scrollUpper);
						
						// synchronize the start and run the display
						P.synchZoneStart();

				}
		  }
		}