This ESP8266-based project runs a simple web server with 1 page to control an 8×8 LED monochrome matrix. It is powered by USB, and portable, as it uses the WiFiManager library to connect to any available wifi network. It allows you to control the LED matrix from a browser on a phone, tablet, laptop.
The web single page looks as below and allows to:
- create an 8×8 binary image by toggling the pixel on/off
- save/load 1 image, stored in permanent EEPROM
- input text to be scrolled on the matrix, with a slider for speed
- show a binary-coded decimal clock, using NTP
- a slider for the overall brightness (not visible on below screenshot)
The hardware is fairly simple:
- an ESP8266 in NodeMCU v1.0 package, programmed with the Arduino IDE
- 8×8 LED matrix with MAX7219 driver
- an LED to show the wifi connection status
- a USB power cord
The code is a bit more work, combining several elements of other people’s work:
- WiFiManager library
- LedControl library
- Javascript for web page
- 8×8 fonts
Here’s a little video that shows the startup. It displays the image previously saved in EEPROM, then attempts to connect to the last knows wifi network. When not found, the yellow LED remains on, and it launches an access point to setup the connection (WiFiManager).
Once it connects to the wifi network, it shows a welcome message, and the IP address of the web server. Then briefly the saved image, and then the BCD clock, showing the time 13:54
Below picture shows time 14:38 (ignore the first 2 rows and columns, and the last 2 columns: the dots there indicate the rows and columns that show the actual data)
- row 3 shows 0001 = 1
- row 4 shows 0100 = 4
- row 6 shows 0011 = 3
- row 7 shows 1000 = 8
Here is the code:
/*
BuffaloLabs 8x8 LED matrix
March 2018
using LedControl library
fonts.h has font characters and digits
wifi manager to config wifi
web server to create 8x8 image
showing network time in BCD
dht11 sensor
*/
#include <ESP8266WiFi.h>
//////////////////// WIFI MANAGER ////////////////////////////
#include <DNSServer.h> //needed for wifimanager library
#include <ESP8266WebServer.h> //needed for wifimanager library
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
/////////////////// WEB SERVER /////////////////////////////
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
ESP8266WebServer server(80);
#include "webpage.h"
/////////////////////////////////// NTP ////////////////////
#include <TimeLib.h>
#include <WiFiUdp.h>
unsigned int localPort = 8888; // local port to listen for UDP packets
static const char ntpServerName[] = "us.pool.ntp.org";
const int timeZone = 8; // Asia HK
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP
int prevHour; // when the clock was displayed
int prevMin;
boolean showClock = false; // display clock or not
boolean showSaved = true; // display saved matrix or not
unsigned long timestamp_clock = 0;
///////////////////// DHT11 /////////////////////////////
#include <DHT.h> // power from USB 5V seems to give more accurate results
#define DHTTYPE DHT11
#define DHTPIN 0 // = D3 on NodeMCU
DHT dht(DHTPIN, DHTTYPE, 11); // 11 works fine for ESP8266
int DHTtemp; // sensor returns no decimals, better use int
int DHThum;
unsigned long timestamp_dht = 0;
//////////////////// MATRIX ////////////////////////////////
#include "fonts.h"
#include <LedControl.h>
int DIN = 14; // D5
int CS = 5; // D1
int CLK = 4; // D2
LedControl lc = LedControl(DIN, CLK, CS, 0);
String last_matrix = "0000000000000000000000000000000000000000000000000000000000000000"; // to remember matrix
#include <EEPROM.h>
int eeprom_addr = 0;
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println("LED MATRIX web server");
EEPROM.begin(512);
lc.shutdown(0, false); //The MAX72XX is in power-saving mode on startup
// lc.setScanLimit(0, 8); // necessary??
lc.setIntensity(0, 3); // Set the brightness to maximum value 0-15
lc.clearDisplay(0); // and clear the display
displaySaved(); // display last saved matrix
pinMode(LED_BUILTIN, OUTPUT); // blink onboard LED
for (int i = 0; i < 10; i++) {
digitalWrite(LED_BUILTIN, LOW);
delay(100);
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
}
/////////////// CONFIGURE WIFI ////////////////////////////
Serial.println("configure wifi manager...");
WiFiManager wifiManager;
// wifiManager.resetSettings(); // known networks are saved so need to reset for testing
wifiManager.autoConnect("matrix");
digitalWrite(LED_BUILTIN, LOW); // inverted pin - LOW is on
Serial.println("connected to wifi!");
////////////// START NTP //////////////////////////////
Serial.print("Starting UDP");
Udp.begin(localPort);
Serial.print(" on local port: ");
Serial.println(Udp.localPort());
setSyncProvider(getNtpTime);
setSyncInterval(300);
////////////// CONFIGURE SERVER ///////////////////////////
server.on("/", handleRoot);
server.begin();
Serial.print("HTTP server started on ");
Serial.println(WiFi.localIP());
scrollText("BuffaloLabs IP:" + WiFi.localIP().toString(), 50);
displaySaved(); // display last saved matrix
dht.readHumidity(); // first reading seems wrong
delay(3000); // for DHT
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
void loop() {
server.handleClient();
if (millis() - timestamp_dht > 10000) { // update reading every 10s
int h = dht.readHumidity();
int t = dht.readTemperature();
if (h>0 && h<=100) DHThum = h;
if (t>-20 && t<100) DHTtemp = t;
Serial.println("Temp: " + String(DHTtemp) + " Hum: " + String(DHThum));
timestamp_dht = millis();
}
if (timeStatus() != timeNotSet && (showClock || showSaved)) { // if time is set and we're not in draw mode
if (hour() != prevHour || minute() != prevMin) { //update the display only if time has changed
prevHour = hour();
prevMin = minute();
String HMM = String(prevHour);
HMM += ":";
if (prevMin < 10) HMM += "0";
HMM += prevMin;
scrollText(HMM + " T:" + String(DHTtemp) + "`C H:" + String(DHThum) + "%", 50);
if (showClock) displayClockBCD();
if (showSaved) displaySaved();
}
}
if (timeStatus() == timeNotSet) {
setSyncProvider(getNtpTime);
}
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
void printByte(const byte character [])
{
int i = 0;
for (i = 0; i < 8; i++)
{
lc.setRow(0, i, character[i]);
}
}
/////////////////////////////////////////////////////////////////////////
void printInt(int character [])
{
int i = 0;
for (i = 0; i < 8; i++)
{
lc.setRow(0, i, character[i]);
}
}
/////////////////////////////////////////////////////////////////////////
void printFont(const byte character[] )
{
int i = 0;
for (i = 0; i < 8; i++)
{
lc.setRow(0, i, flipByte(character [i]));
}
}
/////////////////////////////////////////////////////////////////////////
byte flipByte(byte c) {
char r = 0;
for (byte i = 0; i < 8; i++) {
r <<= 1;
r |= c & 1;
c >>= 1;
}
return r;
}
/////////////////////////////////////////////////////////////////////////
void handleRoot() {
server.send(200, "text/html", webpage_html);
String data = server.arg(0);
if (server.argName(0) == "matrix") {
displayMatrix(data);
last_matrix = data;
Serial.println("received data and updated the matrix");
showClock = false;
showSaved = false;
}
if (server.argName(0) == "intensity") {
lc.setIntensity(0, data.toInt()); // Set the brightness to maximum value 0-15
Serial.print("updated intensity to ");
Serial.println(data);
}
if (server.argName(0) == "text") {
Serial.println("scrolling text: " + data);
int d = map(server.arg(1).toInt(), 1, 10, 200, 20);
scrollText(data, d);
if (!showClock) {
displayMatrix(last_matrix);
} else {
displayClockBCD();
}
}
if (server.argName(0) == "save") {
Serial.println("saving matrix");
for (int u = 0; u < 8; u++) { // for every row, construct 1 byte
byte row = 0;
for (int i = 0; i < 8; i++) {
bitWrite(row, 7 - i, last_matrix.charAt(8 * u + i) - 48);
}
// Serial.println(row);
EEPROM.write(eeprom_addr + u, row);
}
EEPROM.commit();
}
if (server.argName(0) == "load") {
Serial.println("loading matrix");
displaySaved();
showClock = false;
showSaved = true;
}
if (server.argName(0) == "clock") {
Serial.println("showing clock");
showClock = true;
showSaved = false;
displayClockBCD();
}
}
/////////////////////////////////////////////////////////////////////////
void displayMatrix(String t) {
for (int i = 0; i < 64; i++) {
lc.setLed(0, i / 8, i % 8, t.charAt(i) - 48);
}
}
/////////////////////////////////////////////////////////////////////////
void displaySaved() {
last_matrix = "";
byte a[8];
for (int u = 0; u < 8; u++) {
a[u] = EEPROM.read(eeprom_addr + u);
for (int i = 0; i < 8; i++) {
last_matrix += bitRead(a[u], 7 - i);
}
}
printByte(a);
}
/////////////////////////////////////////////////////////////////////////
void scrollText(String t, int s) { // text and speed
int l = t.length() + 1; // store length
t = " " + t + " "; // add space before and after
for (int i = 0; i <= l * 8; i++) { // index of moving window over 2 chars
int this_frame[8] = {0, 0, 0, 0, 0, 0, 0, 0}; // define as int holding 2 chars (2x byte)
for (int u = 0; u < 8; u++) { // for each row
int left = (int) flipByte(font[t.charAt(i / 8)] [u]); // left char
int right = (int) flipByte(font[t.charAt(i / 8 + 1)] [u]); // right char
this_frame[u] = (left << 8) | right; // combine chars to 16 bits
this_frame[u] = this_frame[u] << i % 8; // move window to very left
this_frame[u] = this_frame[u] >> 8; // move result to very right
this_frame[u] = this_frame[u] & 0x00FF; // mask result to byte on right
}
printInt(this_frame);
delay(s);
}
}
/////////////////////////////////////////////////////////////////////////
time_t getNtpTime()
{
IPAddress ntpServerIP; // NTP server's ip address
while (Udp.parsePacket() > 0) ; // discard any previously received packets
Serial.println("Transmit NTP Request");
// get a random server from the pool
WiFi.hostByName(ntpServerName, ntpServerIP);
Serial.print(ntpServerName);
Serial.print(": ");
Serial.println(ntpServerIP);
sendNTPpacket(ntpServerIP);
uint32_t beginWait = millis();
while (millis() - beginWait < 2500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Serial.println("Receive NTP Response");
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 = (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
}
}
Serial.println("No NTP Response :-(");
return 0; // return 0 if unable to get the time
}
/////////////////////////////////////////////////////////////////////////
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
//////////////////////////////////////////////////////////////////////////////////
void displayClockBCD()
{
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(" ");
Serial.print(day());
Serial.print(".");
Serial.print(month());
Serial.print(".");
Serial.print(year());
Serial.println();
byte a[8];
a[0] = B10100000;
a[1] = B11100000 | (hour() / 10);
a[2] = B10100000 | (hour() % 10);
a[3] = B00000000;
a[4] = B00000000;
a[5] = B11100000 | (minute() / 10);
a[6] = B10100000 | (minute() % 10);
a[7] = B00000000;
printByte(a);
/*
matrix.fillScreen(GREEN);
matrix.drawPixel(4, 1, RED * bitRead(hour() / 10, 1));
matrix.drawPixel(5, 1, RED * bitRead(hour() / 10, 0));
matrix.drawPixel(2, 2, RED * bitRead(hour() % 10, 3));
matrix.drawPixel(3, 2, RED * bitRead(hour() % 10, 2));
matrix.drawPixel(4, 2, RED * bitRead(hour() % 10, 1));
matrix.drawPixel(5, 2, RED * bitRead(hour() % 10, 0));
matrix.drawPixel(3, 5, RED * bitRead(minute() / 10, 2));
matrix.drawPixel(4, 5, RED * bitRead(minute() / 10, 1));
matrix.drawPixel(5, 5, RED * bitRead(minute() / 10, 0));
matrix.drawPixel(2, 6, RED * bitRead(minute() % 10, 3));
matrix.drawPixel(3, 6, RED * bitRead(minute() % 10, 2));
matrix.drawPixel(4, 6, RED * bitRead(minute() % 10, 1));
matrix.drawPixel(5, 6, RED * bitRead(minute() % 10, 0));
matrix.show();
*/
}
//////////////////////////////////////////////////////////////////////////////////
void printDigits(int digits)
{
Serial.print(":");
if (digits < 10)
Serial.print('0');
Serial.print(digits);
}
and the fonts.h
const byte font[128][8] = { // http://arduino-er.blogspot.hk/2014/08/port-ascii-font-to-arduino-88-led-matrix.html
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space)
{ 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!)
{ 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (")
{ 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#)
{ 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($)
{ 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%)
{ 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&)
{ 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (')
{ 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (()
{ 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ())
{ 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*)
{ 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,)
{ 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.)
{ 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/)
{ 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0)
{ 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1)
{ 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2)
{ 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3)
{ 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4)
{ 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5)
{ 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6)
{ 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7)
{ 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8)
{ 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9)
{ 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:)
{ 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (//)
{ 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<)
{ 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=)
{ 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>)
{ 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?)
{ 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@)
{ 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A)
{ 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B)
{ 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C)
{ 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D)
{ 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E)
{ 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F)
{ 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G)
{ 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H)
{ 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I)
{ 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J)
{ 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K)
{ 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L)
{ 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M)
{ 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N)
{ 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O)
{ 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P)
{ 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q)
{ 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R)
{ 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S)
{ 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T)
{ 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U)
{ 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V)
{ 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W)
{ 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X)
{ 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y)
{ 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z)
{ 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([)
{ 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\)
{ 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (])
{ 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_)
{ B00011000, B00100100, B00100100, B00011000, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`) // edited to degrees symbol
{ 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a)
{ 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b)
{ 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c)
{ 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d)
{ 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e)
{ 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f)
{ 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g)
{ 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h)
{ 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i)
{ 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j)
{ 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k)
{ 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l)
{ 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m)
{ 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n)
{ 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o)
{ 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p)
{ 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q)
{ 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r)
{ 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s)
{ 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t)
{ 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u)
{ 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v)
{ 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w)
{ 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x)
{ 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y)
{ 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z)
{ 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({)
{ 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|)
{ 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (})
{ 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~)
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // U+007F
};
const byte digits[10][8] = { // from http://xantorohara.github.io/led-matrix-editor/#
{
B00111000,
B01000100,
B01000100,
B01000100,
B01000100,
B01000100,
B00111000,
B00000000
}, {
B00001000,
B00011000,
B00001000,
B00001000,
B00001000,
B00001000,
B00011100,
B00000000
}, {
B00111000,
B01000100,
B00000100,
B00001000,
B00010000,
B00100000,
B01111100,
B00000000
}, {
B00111000,
B01000100,
B00000100,
B00011000,
B00000100,
B01000100,
B00111000,
B00000000
}, {
B00000100,
B00001100,
B00010100,
B00100100,
B01111100,
B00000100,
B00000100,
B00000000
}, {
B01111100,
B01000000,
B01111000,
B00000100,
B00000100,
B01000100,
B00111000,
B00000000
}, {
B00111000,
B01000100,
B01000000,
B01111000,
B01000100,
B01000100,
B00111000,
B00000000
}, {
B01111100,
B00000100,
B00000100,
B00001000,
B00010000,
B00100000,
B00100000,
B00000000
}, {
B00111000,
B01000100,
B01000100,
B00111000,
B01000100,
B01000100,
B00111000,
B00000000
}, {
B00111000,
B01000100,
B01000100,
B00111100,
B00000100,
B01000100,
B00111000,
B00000000
}
};
and the webpage.h
/*
inspired by www.pial.net 8*8 dot matrix font generator
*/
const char webpage_html[] PROGMEM = R"=====(
<!DOCTYPE html>
<html lang="en-US">
<style>
body {
font-family: arial;
font-size: 20px;
background-color:#eeeeee;
}
div {
background-color:#cccccc;
padding: 5px;
border: 5px solid #eeeeee;
}
input {
font-size: 20px;
}
.slider {
-webkit-appearance: none;
width: 300px;
height: 25px;
background: #bbbbbb;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
}
.slider:hover {
opacity: 1;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
background: #888888;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 25px;
height: 25px;
background: #888888;
cursor: pointer;
}
</style>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>BuffaloLabs 8x8 LED matrix</title>
</head>
<body>
<div style="background-color:#888888">BuffaloLabs 8x8 LED matrix</div>
<div>
<table style="cursor: pointer; width: 240px; height: 240px;" border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td id="1" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="2" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="3" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="4" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="5" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="6" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="7" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="8" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
</tr>
<tr>
<td id="9" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="10" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="11" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="12" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="13" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="14" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="15" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="16" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
</tr>
<tr>
<td id="17" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="18" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="19" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="20" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="21" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="22" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="23" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="24" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
</tr>
<tr>
<td id="25" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="26" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="27" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="28" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="29" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="30" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="31" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="32" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
</tr>
<tr>
<td id="33" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="34" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="35" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="36" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="37" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="38" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="39" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="40" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
</tr>
<tr>
<td id="41" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="42" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="43" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="44" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="45" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="46" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="47" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="48" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
</tr>
<tr>
<td id="49" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="50" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="51" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="52" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="53" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="54" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="55" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="56" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
</tr>
<tr>
<td id="57" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="58" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="59" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="60" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="61" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="62" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="63" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
<td id="64" style="height: 10px; width: 10px; background-color: #e2e2e0; border: 1px solid #cccccc;" onmousedown="switchValue(this)"> 0</td>
</tr>
</tbody>
</table>
<input onclick="resetmatrix()" type="button" value="Reset" />
<input onclick="savematrix()" type="button" value="Save" />
</div>
<div>
Input text:<br>
<input type="text" id="textbox">
<input onclick="sendText()" type="button" value="Send" />
<br>
Scrolling speed:<br>
<input type="range" min="1" max="10" value="7" class="slider" id="speed">
</div>
<div>
<input onclick="showclock()" type="button" value="Show BCD Clock" />
<input onclick="loadmatrix()" type="button" value="Show Saved Matrix" />
</div>
<div>
Brightness:<br>
<input onchange="sendIntensity()" type="range" min="1" max="15" value="3" class="slider" id="intensity">
</div>
<script type="text/javascript"><!--
function switchValue(obj)
{
if(obj.innerHTML == " 0")
{
obj.style.backgroundColor = "#FF0000";
obj.innerHTML = " 1";
} else {
obj.style.backgroundColor = "#E2E2E0";
obj.innerHTML = " 0";
}
sendBinary();
}
function resetmatrix()
{
var tdObj;
for(var i=1;i<65;i++)
{
tdObj = document.getElementById(i);
tdObj.style.backgroundColor = "#E2E2E0";
tdObj.innerHTML = " 0";
}
sendBinary();
}
function savematrix()
{
var params = "save=";
var req = new XMLHttpRequest();
req.open('POST', '?' + params, true);
req.send();
}
function loadmatrix()
{
var params = "load=";
var req = new XMLHttpRequest();
req.open('POST', '?' + params, true);
req.send();
}
function sendBinary()
{
var tdObj;
var params = "matrix=";
for(var i=1;i<65;i++)
{
tdObj = document.getElementById(i);
if(tdObj.innerHTML == " 0")
{
params += "0";
} else {
params += "1";
}
}
var req = new XMLHttpRequest();
req.open('POST', '?' + params, true);
req.send();
}
function sendIntensity()
{
var slider = document.getElementById("intensity");
var params = "intensity=";
params += slider.value;
var req = new XMLHttpRequest();
req.open('POST', '?' + params, true);
req.send();
}
function sendText()
{
var t = document.getElementById("textbox");
var s = document.getElementById("speed");
var params = "text=";
params += t.value;
params += "&speed=";
params += s.value;
var req = new XMLHttpRequest();
req.open('POST', '?' + params, true);
req.send();
}
function showclock()
{
var params = "clock=";
var req = new XMLHttpRequest();
req.open('POST', '?' + params, true);
req.send();
}
</script>
</body></html>
)=====" ;
love the article, keep posting!
Nice project, would like to see the code!
hi, i’ve added the code, sorry for the delay, hope it’s useful to you.