Share My Creation Web-Controlled LED Matrix Display with ESP8266

Project: Web-Controlled LED Matrix Display with ESP8266​


This project uses the ESP8266 microcontroller to control an LED Matrix display through a web interface, allowing real-time remote message updates. It’s ideal for info panels, dynamic notifications, or even smart decorations.


Components Used:​


  • ESP8266 (NodeMCU or Wemos D1 Mini)
  • LED Matrix Display (typically with MAX7219 chip)
  • 5V Power Supply for the matrix
  • Jumper wires
  • Wi-Fi connection

Features:​


  • Access the display through a web browser (Web Server hosted on the ESP8266)
  • Simple and intuitive interface for sending text messages
  • Automatic scrolling text
  • Real-time content updates without reprogramming
  • Ability to adjust scroll speed and brightness

How It Works:​


  1. Upon startup, the ESP8266 connects to a pre-configured Wi-Fi network.
  2. A web server is launched on the ESP8266.
  3. The user accesses the control panel via browser (by typing the device's local IP).
  4. A web page is displayed with a text input field and control buttons.
  5. When a message is sent, the text is processed and displayed on the LED matrix, scrolling from right to left.

Optional Features:​


  • Remote adjustment of brightness and speed via sliders
  • MQTT support for smart home or assistant integration
  • Access Point mode to work without external Wi-Fi
  • Save the last message in EEPROM/Flash to retain it after reboot

Libraries Used (Arduino IDE / B4R):​


  • ESP8266WiFi.h
  • ESP8266WebServer.h
  • WiFiManager.h (optional, to simplify Wi-Fi setup)
  • LedControl.h or MD_MAX72XX.h

B4R:
Sub Process_Globals
    Public Serial1 As Serial
    Public Wifi As ESP8266WiFi
    Private led As LedControl
    Public DINPin As Pin
    Public CSpin As Pin
    Public CLKPin As Pin
    Public Net  As Pin
    Public Timer1 As Timer
    Public Timerinterval As Long = 5 ' Intervalo do timer em milissegundo
    Public maxDevices As Byte = 4 ' Número de matrizes de LEDs conectadas
    Public scrollPos As Int       ' Posição de rolagem do texto
    Public buffer(400) As Byte    ' Buffer para armazenar o texto
    Public esp As ESP8266
    Public eeprom As EEPROM
    Public eeprom2 As EEPROM
    Private const MAGIC_EEPROM As Byte = 213
    Private sr As B4RSerializator
    Private bc As ByteConverter
   
End Sub

Private Sub AppStart
    Serial1.Initialize(115200)
    Log("Iniciando ESP8266...")
    Timer1.Initialize("Timer1_Tick", Timerinterval)
    ' Inicializa pinos para MAX7219
    DINPin.Initialize(12, DINPin.MODE_OUTPUT) ' D7 (GPIO15)
    CSpin.Initialize(5, CSpin.MODE_OUTPUT)    ' D4 (GPIO5)
    CLKPin.Initialize(14, CLKPin.MODE_OUTPUT) ' D5 (GPIO14)
    Net.Initialize(4, Net.MODE_OUTPUT)        ' D2 (GPIO4)
    Net.DigitalWrite(True)
    ConnectToNetwork(0)
   
    'Inicializa o MAX7219
    led.Initialize(DINPin.PinNumber, CLKPin.PinNumber, CSpin.PinNumber, maxDevices)
   
    For i = 0 To maxDevices - 1
        led.Shutdown(i, True)  'DESLIGA para reset total
        Delay(100)             'Espera para garantir que desligou
        led.Shutdown(i, False)
        led.SetIntensity(i, 2) ' Ajusta brilho (0-15)
    Next
    scrollPos = buffer.Length - (maxDevices * 8)
   
End Sub

private Sub ConnectToNetwork(tag As Byte)
    Dim SSID, PASS As String
    Wifi.Disconnect
    Dim datas() As Byte = GetStoredData
    Dim ObjectsBuffer(16) As Object
    Dim Objects() As Object = sr.ConvertBytesToArray(datas, ObjectsBuffer)
    SSID = bc.StringFromBytes(Objects(0))
    PASS = bc.StringFromBytes(Objects(1))
   
    Log("Trying to connect to: [",SSID, "] password: [", PASS,"]")
    'example of connecting to a local network
    If Wifi.Connect2(SSID,PASS) Then
        Log("Conectado a  Internet IP :",Wifi.LocalIp)
        Timer1.Enabled = True
        Net.DigitalWrite(False)
    Else
        Log("Failed to connect to network")
        CallSubPlus("ConnectToNetwork",60*1000,1) 'try to connect again after 1mn
        Net.DigitalWrite(True)
    End If
    WiFiServer.Start
End Sub

Public Sub SaveNetworkDetails(Data() As Byte)
    Log("data to eeprom=",Data)
    eeprom.WriteBytes(Array As Byte(MAGIC_EEPROM, Data.Length), 0)
    eeprom.WriteBytes(Data, 2)
    Delay(200)
    esp.Restart
End Sub

Public Sub GetStoredDataLength As Byte
    Dim header() As Byte = eeprom.ReadBytes(0, 2)
    If header(0) = MAGIC_EEPROM Then
        Return header(1)
    End If
    Return 0
End Sub

Sub    GetStoredData As Byte()
    Dim length As Byte = GetStoredDataLength
    If length > 0 Then
        Dim Data() As Byte = eeprom.ReadBytes(2, length)
        Return Data
    Else
        Dim Data(2) As Byte =sr.ConvertArrayToBytes(Array( "_".getbytes,"_".getbytes,"_"))
        Return Data
    End If
End Sub

Public Sub ClearStoredDataLength
    Dim header() As Byte = eeprom.ReadBytes(0, 2)
    If header(0) = MAGIC_EEPROM Then
        header(1) = 0
        eeprom.WriteBytes(header,0)
        Log("clear done")
    End If
End Sub

Sub esp_reset(tag As Byte)
    esp.Restart
End Sub

public Sub StopAP
    Log("Stop AP")
    RunNative("stopAP", Null)
End Sub
#if C
  #include <ESP8266WiFi.h>
  void stopAP (B4R::Object* u) {
  WiFi.softAPdisconnect(1);
  }
#end if
Sub SaveMessage(Mensagem As String)
    Dim bufferIndex As Int = 0

    ' Limpa o buffer antes de gravar nova mensagem
    For i = 0 To buffer.Length - 1
        buffer(i) = 0
    Next
   
    For i = 0 To Mensagem.Length - 1
        Dim charIndex As Int = Mensagem.GetBytes(i) - 32
        If charIndex >= 0 And charIndex <= 94 Then
            For j = 0 To 4  ' Cada caractere tem 5 colunas
                If bufferIndex < buffer.Length - 8 Then  ' Garante espaço para o final
                    buffer(bufferIndex) = GetByte(charIndex * 5 + j)
                    bufferIndex = bufferIndex + 1
                End If
            Next
            ' Adiciona um pequeno espaço entre caracteres
            If bufferIndex < buffer.Length - 8 Then
                buffer(bufferIndex) = 0
                bufferIndex = bufferIndex + 1
            End If
        End If
    Next
End Sub

Sub Timer1_Tick
   
'   Desloca o buffer para a direita
'    Dim firstByte As Byte =  buffer(buffer.Length - 1)
'    For i = buffer.Length - 1 To 1 Step -1
'        buffer(i) = buffer(i - 1)
'    Next
'    buffer(0) = firstByte
   
   
   
    ' Desloca o buffer para a esquerda continuamente
    Dim firstByte As Byte = buffer(0)
    For i = 0 To buffer.Length - 2
        buffer(i) = buffer(i + 1)
    Next
    buffer(buffer.Length - 1) = firstByte  ' Mantém o fluxo do texto
   
    ' Atualiza as matrizes de LED usando setLed
    For matrix = 0 To maxDevices
        For col = 0 To 7
            Dim rowData As Byte = buffer(matrix * 8 + col)
            For row = 0 To 7
                Dim ledState As Boolean = Bit.And(rowData, Bit.ShiftLeft(1, row)) <> 0
                led.SetLed(maxDevices  - matrix,row, col, ledState)
            Next
        Next
    Next
End Sub

' Função para obter os dados da fonte de caracteres
Sub GetByte(Index As UInt) As Byte
    Return RunNative("getdata", Index)
End Sub

#if C
#include <avr/pgmspace.h>
const PROGMEM byte data[] = {
    0x00, 0x00, 0x00, 0x00, 0x00,  // (space)
    0x00, 0x00, 0x2f, 0x00, 0x00,  // !
    0x00, 0x07, 0x00, 0x07, 0x00,  // "
    0x14, 0x7f, 0x14, 0x7f, 0x14,  // #
    0x24, 0x2a, 0x7f, 0x2a, 0x12,  // $
    0xc4, 0xc8, 0x10, 0x26, 0x46,  // %
    0x36, 0x49, 0x55, 0x22, 0x50,  // &
    0x00, 0x05, 0x03, 0x00, 0x00,  // '
    0x00, 0x1c, 0x22, 0x41, 0x00,  // (
    0x00, 0x41, 0x22, 0x1c, 0x00,  // )
    0x14, 0x08, 0x3E, 0x08, 0x14,  // *
    0x08, 0x08, 0x3E, 0x08, 0x08,  // +
    0x00, 0x00, 0x50, 0x30, 0x00,  // ,
    0x10, 0x10, 0x10, 0x10, 0x10,  // -
    0x00, 0x60, 0x60, 0x00, 0x00,  // .
    0x20, 0x10, 0x08, 0x04, 0x02,  // /
    0x3E, 0x51, 0x49, 0x45, 0x3E,  // 0
    0x00, 0x42, 0x7F, 0x40, 0x00,  // 1
    0x42, 0x61, 0x51, 0x49, 0x46,  // 2
    0x21, 0x41, 0x45, 0x4B, 0x31,  // 3
    0x18, 0x14, 0x12, 0x7F, 0x10,  // 4
    0x27, 0x45, 0x45, 0x45, 0x39,  // 5
    0x3C, 0x4A, 0x49, 0x49, 0x30,  // 6
    0x01, 0x71, 0x09, 0x05, 0x03,  // 7
    0x36, 0x49, 0x49, 0x49, 0x36,  // 8
    0x06, 0x49, 0x49, 0x29, 0x1E,  // 9
    0x00, 0x36, 0x36, 0x00, 0x00,  // :
    0x00, 0x56, 0x36, 0x00, 0x00,  // ;
    0x08, 0x14, 0x22, 0x41, 0x00,  // <
    0x14, 0x14, 0x14, 0x14, 0x14,  // =
    0x00, 0x41, 0x22, 0x14, 0x08,  // >
    0x02, 0x01, 0x51, 0x09, 0x06,  // ?
    0x32, 0x49, 0x59, 0x51, 0x3E,  // @
    0x0C, 0x1E, 0x3F, 0x1E, 0x0C,  // A
    0x7F, 0x49, 0x49, 0x49, 0x36,  // B
    0x3E, 0x41, 0x41, 0x41, 0x22,  // C
    0x7F, 0x41, 0x41, 0x41, 0x3E,  // D
    0x7F, 0x49, 0x49, 0x49, 0x41,  // E
    0x7F, 0x09, 0x09, 0x09, 0x01,  // F
    0x3E, 0x41, 0x49, 0x49, 0x7A,  // G
    0x7F, 0x08, 0x08, 0x08, 0x7F,  // H
    0x00, 0x41, 0x7F, 0x41, 0x00,  // I
    0x20, 0x40, 0x41, 0x3F, 0x01,  // J
    0x7F, 0x08, 0x14, 0x22, 0x41,  // K
    0x7F, 0x40, 0x40, 0x40, 0x40,  // L
    0x7F, 0x02, 0x0C, 0x02, 0x7F,  // M
    0x7F, 0x04, 0x08, 0x10, 0x7F,  // N
    0x3E, 0x41, 0x41, 0x41, 0x3E,  // O
    0x7F, 0x09, 0x09, 0x09, 0x06,  // P
    0x3E, 0x41, 0x51, 0x21, 0x5E,  // Q
    0x7F, 0x09, 0x19, 0x29, 0x46,  // R
    0x46, 0x49, 0x49, 0x49, 0x31,  // S
    0x01, 0x01, 0x7F, 0x01, 0x01,  // T
    0x3F, 0x40, 0x40, 0x40, 0x3F,  // U
    0x1F, 0x20, 0x40, 0x20, 0x1F,  // V
    0x3F, 0x40, 0x38, 0x40, 0x3F,  // W
    0x63, 0x14, 0x08, 0x14, 0x63,  // X
    0x07, 0x08, 0x70, 0x08, 0x07,  // Y
    0x61, 0x51, 0x49, 0x45, 0x43,  // Z
    0x00, 0x7F, 0x41, 0x41, 0x00,  // [
    0x02, 0x04, 0x08, 0x10, 0x20,  // "\"
    0x00, 0x41, 0x41, 0x7F, 0x00,  // ]
    0x04, 0x02, 0x01, 0x02, 0x04,  // ^
    0x40, 0x40, 0x40, 0x40, 0x40,  // _
    0x00, 0x01, 0x02, 0x04, 0x00,  // `
    0x20, 0x54, 0x54, 0x54, 0x78,  // a
    0x7F, 0x48, 0x44, 0x44, 0x38,  // b
    0x38, 0x44, 0x44, 0x44, 0x20,  // c
    0x38, 0x44, 0x44, 0x48, 0x7F,  // d
    0x38, 0x54, 0x54, 0x54, 0x18,  // e
    0x08, 0x7E, 0x09, 0x01, 0x02,  // f
    0x08, 0x14, 0x54, 0x54, 0x3C,  // g
    0x7F, 0x08, 0x04, 0x04, 0x78,  // h
    0x00, 0x44, 0x7D, 0x40, 0x00,  // i
    0x20, 0x40, 0x44, 0x3D, 0x00,  // j
    0x00, 0x7F, 0x10, 0x28, 0x44,  // k
    0x00, 0x41, 0x7F, 0x40, 0x00,  // l
    0x7C, 0x04, 0x18, 0x04, 0x78,  // m
    0x7C, 0x08, 0x04, 0x04, 0x78,  // n
    0x38, 0x44, 0x44, 0x44, 0x38,  // o
    0x7C, 0x14, 0x14, 0x14, 0x08,  // p
    0x08, 0x14, 0x14, 0x18, 0x7C,  // q
    0x7C, 0x08, 0x04, 0x04, 0x08,  // r
    0x48, 0x54, 0x54, 0x54, 0x20,  // s
    0x04, 0x3F, 0x44, 0x40, 0x20,  // t
    0x3C, 0x40, 0x40, 0x20, 0x7C,  // u
    0x1C, 0x20, 0x40, 0x20, 0x1C,  // v
    0x3C, 0x40, 0x30, 0x40, 0x3C,  // w
    0x44, 0x28, 0x10, 0x28, 0x44,  // x
    0x0C, 0x50, 0x50, 0x50, 0x3C,  // y
    0x44, 0x64, 0x54, 0x4C, 0x44,  // z
    0x00, 0x08, 0x36, 0x41, 0x41,  // {
    0x00, 0x00, 0x7F, 0x00, 0x00,  // |
    0x41, 0x41, 0x36, 0x08, 0x00,  // }
    0x10, 0x08, 0x08, 0x10, 0x08,  // ~
    //0x0C, 0x1E, 0x3F, 0x1E, 0x0C,  // ?
   
};
B4R::Object beo1;
B4R::Object* getdata(B4R::Object* o) {
int idx = (int)o->toLong();
return beo1.wrapNumber(pgm_read_byte_near(data + idx));
}
#end if

https://www.facebook.com/share/r/16QESfuZN9/
 

Cesar_Morisco

Active Member
Licensed User
Longtime User
Hello everyone
As written in the message, we need to improve, Record
a message in memory, Control the brightness of the LEDs, Control the speed of the message, Include some and ready, Symbols, Hearts, Smilies, etc.
 
Top