How to control MAX7219 Led Matrix with ESP8266 Nodemcu over WiFi.

How to control MAX7219 Led Matrix with ESP8266  Nodemcu over WiFi.
One of the uses of the Esp8266 Nodemcu is enabling us to control devices on web pages over a WiFi network. In one of my previous tutorials I showed how to create a web server using Nodemcu;
  • Creating a Nodemcu Webserver with Arduino IDE.
  • In this tutorial we want to control scrolling text on the MAX7219 led Matrix display using a custom webpage. Before proceeding you can first learn how to control this type of led matrix using Arduino from the link below:
  • Max7219 Led Matrix Interfacing with Arduino.
  • The Max7219 led matrix display and Esp8266 Nodemcu are connected as shown below. In my case i have used only four four led matrices but the setup can accommodate up to eight matrices and can be specified in the code.
    max7219 esp8266 nodemcu schematic

    Code for Max7219 Led matrix with Esp8266 Nodemcu

    This code includes the ESPWiFi.h for enabling the Nodemcu to create a webserver over a Wifi network, the MD_MAX72xx.h for controlling the Led matrix. Take time to learn how this library works with the display before proceeding.
    You can also change the design and appearance of the web page by changing the HTML and CSS part of the code but only do this if you have prior knowledge on web development and design otherwise the web page may not be displayed!
    #include <ESP8266WiFi.h>
    #include <MD_MAX72xx.h>
    #include <SPI.h>
    
    #define	PRINT_CALLBACK	0
    #define DEBUG 0
    #define LED_HEARTBEAT 0
    
    #if DEBUG
    #define	PRINT(s, v)	{ Serial.print(F(s)); Serial.print(v); }
    #define PRINTS(s)   { Serial.print(F(s)); }
    #else
    #define	PRINT(s, v)
    #define PRINTS(s)
    #endif
    
    
    #if LED_HEARTBEAT
    #define HB_LED  D2
    #define HB_LED_TIME 500 // in milliseconds
    #endif
    
    // 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	MAX_DEVICES	4
    
    #define	CLK_PIN		D5 // or SCK
    #define	DATA_PIN	D7 // or MOSI
    #define	CS_PIN		D8 // or SS
    
    // SPI hardware interface
    //MD_MAX72XX mx = MD_MAX72XX(CS_PIN, MAX_DEVICES);
    #define HARDWARE_TYPE MD_MAX72XX::FC16_HW  //edit this as per your LED matrix hardware type
    MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
    // Arbitrary pins
    //MD_MAX72XX mx = MD_MAX72XX(DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
    
    // WiFi login parameters - network name and password
    const char* ssid = "wifi SSID";                   // edit your wifi SSID here
    const char* password = "wifi password";            // edit your wifi password here
    
    // WiFi Server object and parameters
    WiFiServer server(80);
    
    // Global message buffers shared by Wifi and Scrolling functions
    const uint8_t MESG_SIZE = 255;
    const uint8_t CHAR_SPACING = 1;
    const uint8_t SCROLL_DELAY = 75;
    
    char curMessage[MESG_SIZE];
    char newMessage[MESG_SIZE];
    bool newMessageAvailable = false;
    
    char WebResponse[] = "HTTP/1.1 200 OK\nContent-Type: text/html\n\n";
    
    char WebPage[] =
    "<!DOCTYPE html>" \
    "<html>" \
    "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" \
    "<title>MYTECTUTOR ESP8266 AND MAX7219</title>" \
    "<style>" \
    "html, body" \ 
    "{" \
    "font-family: Helvetica; "\
    "display: block;"\ 
    "margin: 0px auto;"\ 
    "text-align: center;"\
    "background-color: #cad9c5;" \
    "}" \
    "#container " \
    "{" \
    "width: 100%;" \
    "height: 100%;" \
    "margin-left: 5px;" \
    "margin-top: 20px;" \
    "border: solid 2px;" \
    "padding: 10px;" \
    "background-color: #2dfa53;" \
    "}" \          
    "</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);" \
    "}" \
    "</script>" \
    "</head>" \
    "<body>" \
    "<H1><b>ESP8266 and MAX7219 LED Matrix WiFi Control</b></H1>" \ 
    
    "<div id=\"container\">"\
    "<form id=\"txt_form\" name=\"frmText\">" \
    "<label>Message:<input type=\"text\" name=\"Message\" maxlength=\"255\"></label>%ltbr><br>" \
    "</form>" \
    "<br>" \
    "<input type=\"submit\" value=\"Send Text\" onclick=\"SendText()\">" \
    "<p><b>Visit link for more: </b>" \ 
    "<a href=\"https://www.mytectutor.com\">www.mytectutor.com</a></p>" \
    "</div>" \
    "</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);
    }
    
    boolean getText(char *szMesg, char *psz, uint8_t len)
    {
      boolean isValid = false;  // text received flag
      char *pStart, *pEnd;      // pointer to start and end of text
    
      // get pointer to the beginning of the text
      pStart = strstr(szMesg, "/&MSG=");
    
      if (pStart != NULL)
      {
        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
          isValid = true;
        }
      }
    
      return(isValid);
    }
    
    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
        newMessageAvailable = getText(szBuf, newMessage, MESG_SIZE);
        PRINT("\nNew Msg: ", newMessage);
        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 scrollDataSink(uint8_t dev, MD_MAX72XX::transformType_t t, uint8_t col)
    // Callback function for data that is being scrolled off the display
    {
    #if PRINT_CALLBACK
      Serial.print("\n cb ");
      Serial.print(dev);
      Serial.print(' ');
      Serial.print(t);
      Serial.print(' ');
      Serial.println(col);
    #endif
    }
    
    uint8_t scrollDataSource(uint8_t dev, MD_MAX72XX::transformType_t t)
    // Callback function for data that is required for scrolling into the display
    {
      static enum { S_IDLE, S_NEXT_CHAR, S_SHOW_CHAR, S_SHOW_SPACE } state = S_IDLE;
      static char		*p;
      static uint16_t	curLen, showLen;
      static uint8_t	cBuf[8];
      uint8_t colData = 0;
    
      // finite state machine to control what we do on the callback
      switch (state)
      {
      case S_IDLE: // reset the message pointer and check for new message to load
        PRINTS("\nS_IDLE");
        p = curMessage;      // reset the pointer to start of message
        if (newMessageAvailable)  // there is a new message waiting
        {
          strcpy(curMessage, newMessage); // copy it in
          newMessageAvailable = false;
        }
        state = S_NEXT_CHAR;
        break;
    
      case S_NEXT_CHAR: // Load the next character from the font table
        PRINTS("\nS_NEXT_CHAR");
        if (*p == '\0')
          state = S_IDLE;
        else
        {
          showLen = mx.getChar(*p++, sizeof(cBuf) / sizeof(cBuf[0]), cBuf);
          curLen = 0;
          state = S_SHOW_CHAR;
        }
        break;
    
      case S_SHOW_CHAR:	// display the next part of the character
        PRINTS("\nS_SHOW_CHAR");
        colData = cBuf[curLen++];
        if (curLen < showLen)
          break;
    
        // set up the inter character spacing
        showLen = (*p != '\0' ? CHAR_SPACING : (MAX_DEVICES*COL_SIZE)/2);
        curLen = 0;
        state = S_SHOW_SPACE;
        // fall through
    
      case S_SHOW_SPACE:	// display inter-character spacing (blank column)
        PRINT("\nS_ICSPACE: ", curLen);
        PRINT("/", showLen);
        curLen++;
        if (curLen == showLen)
          state = S_NEXT_CHAR;
        break;
    
      default:
        state = S_IDLE;
      }
    
      return(colData);
    }
    
    void scrollText(void)
    {
      static uint32_t	prevTime = 0;
    
      // Is it time to scroll the text?
      if (millis() - prevTime >= SCROLL_DELAY)
      {
        mx.transform(MD_MAX72XX::TSL);	// scroll along - the callback will load all the data
        prevTime = millis();			// starting point for next time
      }
    }
    
    void setup()
    {
    #if DEBUG
      Serial.begin(115200);
      PRINTS("\n[MD_MAX72XX WiFi Message Display]\nType a message for the scrolling display from your internet browser");
    #endif
    
    #if LED_HEARTBEAT
      pinMode(HB_LED, OUTPUT);
      digitalWrite(HB_LED, LOW);
    #endif
    
      // Display initialisation
      mx.begin();
      mx.setShiftDataInCallback(scrollDataSource);
      mx.setShiftDataOutCallback(scrollDataSink);
    
      curMessage[0] = newMessage[0] = '\0';
    
      // 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 loop()
    {
    #if LED_HEARTBEAT
      static uint32_t timeLast = 0;
    
      if (millis() - timeLast >= HB_LED_TIME)
      {
        digitalWrite(HB_LED, digitalRead(HB_LED) == LOW ? HIGH : LOW);
        timeLast = millis();
      }
    #endif
    
      handleWiFi();
      scrollText();
    }
    
    
    
    web page appearance
    When the above code is uploaded to the Nodemcu, the URL can be checked from the serial monitor and can be used to access the web page for controlling the text being displayed on the MAX7219 Led diaplay.
    From this web page we can enter a message and that message will be displayed on the matrix.

    3 thoughts on “How to control MAX7219 Led Matrix with ESP8266 Nodemcu over WiFi.

    1. So trying to compile the code it is telling me that “‘D8’ was not declared in this scope” Do you know where to fix this? I cannot seem to find the error to correct it. Everything seems to be defined.

    2. Hi thanks to your effort, this is working well with my nodeMCU board and MAX7219 display.

    3. The reason why D8 is not declared is probably because you did not select the right board in the Arduino IDE.

    Comments are closed.

    Back to top