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:
- Upon startup, the ESP8266 connects to a pre-configured Wi-Fi network.
- A web server is launched on the ESP8266.
- The user accesses the control panel via browser (by typing the device's local IP).
- A web page is displayed with a text input field and control buttons.
- 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/