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.
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.
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.
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.
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.
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.
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.
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; }
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
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.