IMG_1798 - Edited (1)

This box shows the time (NTP sync) and a few data feeds of my home monitoring system. It uses a 32×16 LED matrix driven by an Arduino Nano, and ESP8266 Wifi module (NodeMCU v1). I put it in a laser cut wooden box, and powered it from a USB phone charger.

IMG_1796 - Edited

DISPLAY

I got this (monochrome) LED matrix a few years ago from Seeedstudio (now only available in RGB); it is based on 74HC595 and 74HC138 chips to do the multiplexing, and came with this basic library. Easy to make it work with an Arduino, but no fonts included in the library, only digits. I found a good 8×8 font bitmap here and added it to my code. As the library only allows writing a character to each tile of 8×8 LEDs, i only got 2 rows of 4 characters.

I did try to drive the matrix directly from an ESP8266 (NodeMCUv1, via Arduino IDE) but i could not get that to work (timing problem) so i decided to stick with the Arduino Nano for the matrix driver, and the ESP8266 for fetching the data and pushing them over a Serial connection to the Arduino.

IMG_1781 - Edited

SERIAL CONNECTION

The Arduino and NodeMCU share the 5V power supply (5V on the Arduino, Vin on the NodeMCU), and their only connection is the Serial TX pin on the NodeMCU to the Serial RX pin (D0) on the Arduino, with a 220Ω resistor in between so the ESP8266 TX pin does not get the full 5V of the RX on the Arduino.

The ESP8266 sends simple Strings at 9600 baud, in the format “T1448*” for time 14:48 and “S1k2W*” for 1k2W as in the above picture.

NTP CLOCK

Network Time Servers make the time available online, so a piece of cake for the ESP8266 Wifi module to sync the time, with this NTPclient library (Arduino IDE).

My initial idea was to build just a clock to hang in our living room, to make sure the kids leave on time in the morning to catch their ferry to school. I wanted to sync this clock over the internet, so i realised i could add some more data feeds to the display, and i got plenty to choose from (see above).

IOT FEED

I have a home monitoring system based on the OpenEnergyMonitor platform, and its excellent emoncms.org cloud server. This system has been running for over 3 years, collecting electricity consumption (6 phases) and temperature, humidity, wind data, with solar powered nodes.

Screenshot 2017-06-10 at 15.25.53

The top line of the display is reserved for the time, and on the second line i alternate a few data feeds:

  • Total energy consumption of our house, in Watts. As i only have 4 digits, i have to format the data, e.g. 945W, 1k2W, 14kW to fit the display. This is done on the ESP8266 so the Arduino just has to display the String it receives.
  • Outside temperature, in degrees Celsius, with no decimals, to fit 4 digits.

IMG_1797 - Edited IMG_1802 - Edited IMG_1798 - Edited

POWER SUPPLY

The LED matrix came with 2 thick power supply wires, which made me a bit worried about current needed. I tested this with my USB power monitor, and the entire project only draws up to 500mA, no problem at all for my USB phone charger. The matrix is a bit too bright, so i tried using PWM on the OE pin (output enable) by modifying the library, but that did not seem to make a difference. I assume it would work by putting a PWM signal on the matrix power wires, with a MOSFET on an Arduino pin but i did not test that.

IMG_1785 - Edited

I soldered 15-pin female headers on a perf board, for the Arduino Nano and the NodeMCU, and a few wires for power and the Serial connection, and the ribbon cable to the display: 8 data pins plus GND.

IMG_1786 - Edited

As the box seemed to have trouble sometimes connecting to my home Wifi network (about 10m away, 1 floor up), resulting in a 00:00 time display and no data feed, i added a large capacitor (3300uF) on the power rail and that seems to fix it.

IMG_1834 - Edited

CODE for ESP8266 NodeMCU v1

/*
NTPclient library: Copyright 2016 German Martin (gmag11@gmail.com). All rights reserved.

NODEMCU V1 (Arduino IDE) for Clock IoT LED Matrix project

sends instructions to Arduino NANO over SERIAL
T1234* = time 12:34
S135W* = text 135W

retrieve feed value from emoncms: https://emoncms.org/feed/value.json?id=15158
or https://emoncms.org/feed/fetch.json?ids=15158,30152

Tom Tobback June 2017
*/

#include <TimeLib.h>
#include <NtpClientLib.h>
#include <ESP8266WiFi.h>

#define YOUR_WIFI_SSID "xx"
#define YOUR_WIFI_PASSWD "xx"
#define EMONCMS "80.243.190.58" // emoncms.org

boolean debug = false;

int timeZone = 8;
int NTPrefresh_interval = 633; // in sec
int i = 0;
int x = 0;
unsigned long send_time_timestamp;
unsigned long send_text_timestamp;
unsigned long send_time_interval = 10000; // in msec
unsigned long send_text_interval = 8000; // in msec
unsigned long feeds[] = {
15158, // total kW
30152 // temp outside
};
int feeds_number = sizeof(feeds) / 4; // unsigned long is 32-bit = 4-byte

boolean syncEventTriggered = false; // True if a time event has been triggered
NTPSyncEvent_t ntpEvent; // Last triggered event

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

void setup()
{
static WiFiEventHandler e1, e2;

Serial.begin(9600);

WiFi.mode(WIFI_STA);
WiFi.begin(YOUR_WIFI_SSID, YOUR_WIFI_PASSWD);

NTP.onNTPSyncEvent([](NTPSyncEvent_t event) {
ntpEvent = event;
syncEventTriggered = true;
});

e1 = WiFi.onStationModeGotIP(onSTAGotIP);// As soon WiFi is connected, start NTP Client
e2 = WiFi.onStationModeDisconnected(onSTADisconnected);

}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

void loop()
{

if (syncEventTriggered) {
processSyncEvent(ntpEvent);
// if (hour() != 0 && minute() != 0) syncEventTriggered = false; // in case time = 00:00 then need to trigger sync again!
syncEventTriggered = false;
}

if ((millis() - send_time_timestamp) > send_time_interval) {
send_time_timestamp = millis();
if (debug) {
Serial.print(i); Serial.print(" ");
Serial.print(NTP.getTimeDateString()); Serial.print(" ");
Serial.print(NTP.isSummerTime() ? "Summer Time. " : "Winter Time. ");
Serial.print("WiFi is ");
Serial.print(WiFi.isConnected() ? "connected" : "not connected"); Serial.print(". ");
Serial.print("Uptime: ");
Serial.print(NTP.getUptimeString()); Serial.print(" since ");
Serial.println(NTP.getTimeDateString(NTP.getFirstSync()).c_str());
}
i++;

String timeString = "T"; // HHMM
if (hour() < 10) timeString += "0";
timeString += hour();
if (minute() < 10) timeString += "0";
timeString += minute();
timeString += "*";
Serial.print(timeString); // to ARDUINO LED MATRIX
if (debug) Serial.println();
}

delay(1000);

if ((millis() - send_text_timestamp) > send_text_interval) {
send_text_timestamp = millis();
if (debug) {
Serial.print("fetching emoncms data: ");
}
unsigned long current_feed = feeds[x % feeds_number];
float FeedValue = getFeedValue(current_feed);
x++;

String textString = "S";

switch (current_feed) {
case 15158: { // WATTS
int watt = FeedValue * 1000.0;
if (watt < 1000 && watt >= 100) { // xxx -> xxxW
textString += watt;
}
else {
if (watt < 10000 && watt >= 1000) { // xxxx -> xkxW
watt = (watt + 50) / 100; // keep 2 digits, with rounding rather than truncating
textString += watt / 10;
textString += "k";
textString += watt % 10; // for rounding rather than truncating
}
else {
if (watt >= 10000) { // xxxxx -> xxkW
watt = watt / 1000;
textString += watt;
textString += "k";
}
}
}
textString += "W";
}
break;
case 30152: { // TEMP
int temp = FeedValue + 0.5; // for rounding rather than truncating
if (temp < 10 && temp > 0) textString += " ";
textString += temp;
textString += "^C";
}
break;
}

textString += "*";
Serial.print(textString); // to ARDUINO LED MATRIX
if (debug) Serial.println();
}

delay(1000);
}
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

float getFeedValue(unsigned long f) {

WiFiClient client;
if (!client.connect(EMONCMS, 80)) {
if (debug) Serial.println(" connection to emoncms.org failed");
return 0;
}

String url = "/feed/value.json?apikey=xx&id=";
url += f;

client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: emoncms.org\r\n" +
"Connection: close\r\n\r\n");
delay(10);
if (debug) Serial.println(url);

unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
if (debug) Serial.println(">>> Client Timeout !");
client.stop();
return false;
}
}

String data = "";
while (client.available()) { // read the remaining text from serial otherwise connection cannot be closed
// String line = client.readStringUntil('\"');
// Serial.print(line);
client.readStringUntil('\"');
data = client.readStringUntil('\"');
}
if (debug) Serial.print("received data on feed ");
if (debug) Serial.print(f);
if (debug) Serial.print(": ");
if (debug) Serial.println(data);

client.stop();

return data.toFloat();
}
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

// Start NTP only after IP network is connected
void onSTAGotIP(WiFiEventStationModeGotIP ipInfo) {
if (debug) Serial.printf("Got IP: %s\r\n", ipInfo.ip.toString().c_str());
NTP.begin("pool.ntp.org", timeZone, false); // 3rd argument is daylight savings time
NTP.setInterval(NTPrefresh_interval);
}

// Manage network disconnection
void onSTADisconnected(WiFiEventStationModeDisconnected event_info) {
if (debug) Serial.printf("Disconnected from SSID: %s\n", event_info.ssid.c_str());
if (debug) Serial.printf("Reason: %d\n", event_info.reason);
//NTP.stop(); // NTP sync can be disabled to avoid sync errors
}

void processSyncEvent(NTPSyncEvent_t ntpEvent) {
if (ntpEvent) {
if (debug) Serial.print("Time Sync error: ");
if (ntpEvent == noResponse)
if (debug) Serial.println("NTP server not reachable");
else if (ntpEvent == invalidAddress)
if (debug) Serial.println("Invalid NTP server address");
}
else {
if (debug) Serial.print("SYNC: Got NTP time: ");
if (debug) Serial.println(NTP.getTimeDateString(NTP.getLastNTPSync()));
}
}

CODE for ARDUINO Nano (LED matrix driver)

/**
LED Matrix library for http://www.seeedstudio.com/depot/ultrathin-16x32-red-led-matrix-panel-p-1582.html
The LED Matrix panel has 32x16 pixels. Several panel can be combined together as a large screen.

The example is for ATmega168/328 and depends on Timer1 library (http://playground.arduino.cc/code/timer1).

LCD matrix 32x16, in 8 tiles of 8x8
font[] = full alphabeth
digits[] = narrow numbers for clock

Receive instructions over SERIAL (5 symbols):
T1234* = time 12:34
S135W* = text 135W

Tom Tobback June 2017
*/

#include "LEDMatrix.h"
#include <TimerOne.h>
#include <avr/wdt.h>

#define WIDTH 32
#define HEIGHT 16

boolean debug = false;

// LEDMatrix(a, b, c, d, oe, r1, stb, clk);
LEDMatrix matrix(4, 5, 6, 7, 8, 9, 10, 11);

// Display Buffer 128 = 64 * 16 / 8
uint8_t displaybuf[WIDTH * HEIGHT / 8] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
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 (^) // change to degree symbol
{ 0x3C, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00}, // U+005E (^) // change to degree symbol
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_)
{ 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`)
{ 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,
B00000100,
B00111000,
B00000000
}
};

void timer_isr() {
matrix.scan();
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

void setup() {
Serial.begin(9600);
matrix.begin(displaybuf, WIDTH, HEIGHT);
matrix.clear();

Timer1.initialize(1000); // was 1200
Timer1.attachInterrupt(timer_isr);

drawFont('B', 0);
drawFont('u', 1);
drawFont('f', 2);
drawFont('f', 3);
drawFont('L', 4);
drawFont('a', 5);
drawFont('b', 6);
drawFont('s', 7);
delay(5000);
matrix.clear();
wdt_enable(WDTO_8S);

}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

void loop() {

wdt_reset();

if (Serial.available()) {
String incoming = Serial.readStringUntil('*');
if (debug) Serial.print("received:");
if (debug) Serial.println(incoming);
if (debug) Serial.println(incoming.length());

if (incoming.length() == 5) { // only process String of 5 symbols
char buf [incoming.length() + 1];
incoming.toCharArray(buf, incoming.length() + 1);

if (buf[0] == 'T') {
Serial.print("received time:"); // format should be HHMM
for (int i = 1; i < incoming.length(); i++) {
Serial.write(buf[i]);
}
Serial.println();
// if (buf[1] != 48) drawDigit(buf[1] - 48, 0); // only print if not 0 ; ascii code to number
drawDigit(buf[1] - 48, 0); // ascii code to number
drawDigit(buf[2] - 48, 1); // ascii code to number
drawDigit(buf[3] - 48, 2); // ascii code to number
drawDigit(buf[4] - 48, 3); // ascii code to number
matrix.drawPoint(15, 2, 1);
matrix.drawPoint(15, 5, 1);
}

if (buf[0] == 'S') {
Serial.print("received text:"); // any format
for (int i = 1; i <= 4; i++) { // only display 4 first symbols
Serial.write(buf[i]);
drawFont(buf[i], 3 + i); // ascii code
}
Serial.println();
}
}
}
}

/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////

void drawFont(int c, int p) { // c for ascii code, p for tile number
uint8_t *pDst = displaybuf + p + (p / 4) * 28; // over tile 3 should jump to new row
const uint8_t *pSrc = font[c];
for (uint8_t i = 0; i < 8; i++) {
*pDst = flipByte(*pSrc);
pDst += WIDTH / 8;
pSrc++;
}
}

/////////////////////////////////////////////////////////////////////////////////////////

void drawDigit(int c, int p) { // c for digit, p for tile number
uint8_t *pDst = displaybuf + p + (p / 4) * 28; // over tile 3 should jump to new row
const uint8_t *pSrc = digits[c];
for (uint8_t i = 0; i < 8; i++) {
*pDst = *pSrc;
pDst += WIDTH / 8;
pSrc++;
}
}

/////////////////////////////////////////////////////////////////////////////////////////

byte flipByte(byte c) {
char r = 0;
for (byte i = 0; i < 8; i++) {
r <<= 1;
r |= c & 1;
c >>= 1;
}
return r;
}
IoT clock with LED matrix (Arduino & ESP8266)

2 thoughts on “IoT clock with LED matrix (Arduino & ESP8266)

  • 18/11/2017 at 00:18
    Permalink

    Hi friend!
    1) Does it work with monochrome 16×64 screen (HUB08)?
    2) I did not understand how to make the nodeMCU connections;
    3) Can work only with nodeMCU?

    Thank you

    Reply
    • 01/01/2018 at 16:28
      Permalink

      Hi Daniel,
      My panel is also HUB08 as far as i know (one set of RGB pins and 4 bits for the 16 rows) – it seems the library can accept different widths so maybe 64 will work as well.
      The NodeMCU is programmed with the Arduino IDE, and the only pin used is TX (serial transmission), connected to the Arduino’s RX.
      It can probably work with the NodeMCU only, if you can modify the library to use an ESP8266 timer. But i think it’s better to separate the wifi activity from the display activity, using 2 MCUs. Otherwise you may get a lot of noise in the display.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *