Some time ago, I saw a very interesting dot matrix clock on the Internet. It can report the weather, check the number of YouTube subscriptions, and have a nice time animation. You can use it as a regular alarm clock or you can connect it to Bluetooth and use it as a speaker. Many of its functions are very interesting, among which I like the time display animation effect most, but its price of more than 1,000 makes me give up the plan to buy it. But as a maker, why not make my own unique creative web clock?

So I made a creative dot matrix clock. Watch the demo video first:

Expected objectives and functions

  • Network automatic calibration time
  • Timely feedback when there is no network connection
  • One-click clock network configuration
  • Custom beautiful time display font
  • Time display animation
  • Automatic brightness adjustment
  • Time prompt

Bill of materials

  • One ESP8266 Wemos Mini development board;
  • Dupont line several;
  • 4 in 1 lattice module;
  • Laser cutting shell;
  • Oak smooth surface imitation wood grain sticker.

Circuit schematic diagram

The circuit connection is shown in the figure below:

Structure assembled

Insert the USB data cable into the development board from the back of the shell in the direction as shown in the following figure, fix the development board on the board with hot melt glue, keep it stable until the hot melt glue solidifies, and the main hot melt glue should not touch the data line.

Place the front part of the shell and the dot matrix screen into the groove of the panel as shown in the following figure. Fix the dot matrix with hot melt adhesive and keep it stable until the hot melt adhesive solidifies.

Then use DuPont wire to correctly connect the circuit according to the schematic diagram, and splice the bottom and left and right sides of the shell, and finally seal the top of the shell.

Cut the appropriate size of oak smooth surface imitation wood grain sticker, paste to the shell surface. Note that dot matrix position can be properly carved with a cutter USB download interface for power supply and program download or update.

The program design

Let’s go into the programming process in detail.

The development environment

We use Aduino software to write the program, the development board to choose ESP8266 type. How to configure the ESP8266 development environment in Arduino is beyond the scope of this article, please refer to the related information.

Program way of thinking

In order to achieve our expected goal, we first draw a functional mind map, and then according to the mind map to gradually realize the programming of the creative dot matrix clock.

Below we will discuss in detail how the various sub-functions of the creative dot matrix clock are implemented.

Get network time

As a clock, the most important function of course is to display the time. So how do you get time from the web?

The following example shows how to get the network time and store it in a variable, where the esp8266Wifi. h library is used to connect to the network, and the ntpClientlib. h library is used to get the network time of the NTP server. The simpleTimer.h library is used to set the timer refresh time per second. This example does not print the current time through the serial port, you can add serial printing code to debug the program.

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

SimpleTimer timer;

const PROGMEM char *ntpServer = "ntp1.aliyun.com";
int8_t timeZone = 8;

volatile int hour_variable;
volatile int minute_variable;
volatile int second_variable;

void Simple_timer() {
  hour_variable = NTP.getTimeHour24();
  minute_variable = NTP.getTimeMinute();
  second_variable = NTP.getTimeSecond();
}

void setup() {
  Serial.begin(9600);
  WiFi.begin("ssid"."password");
  while(WiFi.status() ! = WL_CONNECTED) { delay(500); Serial.print(".");
  }
    
  Serial.println("Local IP:");
  Serial.print(WiFi.localIP());
    
  NTP.setInterval(600);
  NTP.setNTPTimeout(1500);
  NTP.begin(ntpServer, timeZone, false);
    
  timer.setInterval(1000L, Simple_timer);
}

void loop() {
  timer.run();
}
Copy the code

Dot matrix display library: MD_Parola

MD_Parola is a modular scrolling text display library for MAX7219 dot matrix screen. Its main features are as follows:

  • Support dot matrix display text left, right or center alignment;

  • With text scrolling, enter and exit effects;

  • Able to control display parameters and animation speed;

  • Support hardware SPI interface;

  • Multiple display areas can be virtual in the dot matrix screen;

  • User-defined fonts and/or single character replacements;

  • Support double height display;

  • Support mixed text and graphics display.

The following example is a simple demonstration of scrolling a string using MD_Parola, where the MD_Parola object takes four parameters: SPI pin DIN, CLK, CS, and the number of dots. The following creative dot matrix clock display functions are developed from this library.

#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>MD_Parola P = MD_Parola,14,12,4 (13); //DIN(D7) CLK(D5) CS(D6) MD_MAX72XX mx = MD_MAX72XX(13,14,12,4); //DIN(D7) CLK(D5) CS(D6) voidsetup() {
  mx.begin();
  P.begin();
}

void loop() {
  if (P.displayAnimate()) {
    P.displayScroll("Mixly", PA_LEFT, PA_SCROLL_LEFT, 50); }}Copy the code

Modulus of bitmap

To display a picture on a bitmap screen, you first need to design a bitmap, and then take the mold of the pattern. PCtoLCD2002 mold taking software is used for lattice mold taking, and mold taking Settings are as follows:

The output mode is hexadecimal. Note that the format is SET to C51 format. Other parameters can be set according to the default mode.

Bitmap display function: display_bitmap()

Here we take the data format of the module as uint8_t array, we have custom font 0~9 and time separator “:”, plus some custom images, which leads to a large number of bitmaps. To facilitate the management of these bitmaps, we use the pointer array bitmap_data[] to manage our bitmaps. For easy display, we define the display_bitmap() function, which takes three parameters, showing abscissa, bitmap width and the position of bitmap_number in the pointer array bitmap_data[]. It should be noted that we did not specify the bitmap height here, because the resolution of the MAX7219 bitmap screen we used is 8×32, so we default the bitmap height to 8.

#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>MD_Parola P = MD_Parola,14,12,4 (13); //DIN(D7) CLK(D5) CS(D6) MD_MAX72XX mx = MD_MAX72XX(13,14,12,4); uint8_t bitmap_data1[] = {0x3e, 0x2a, 0x3e}; uint8_t bitmap_data2[] = {0x2e, 0x2a, 0x3e}; Uint8_t * bitmap_data[] = {bitmap_data1 bitmap_data2...... }; void display_bitmap(int abscissa, int width, int bitmap_number) { mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF); mx.setBuffer(abscissa, width, bitmap_data[bitmap_number]); mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON); }Copy the code

Time display: hour, minute

In MD_Parola library, because the font is too large and not beautiful, it takes too long to display, so we need to customize the font. The custom font is shown in the following figure. It is worth noting that the width of the bitmap 0 to 9 is 3 and the width of the separator ‘:’ is 1.

The modulus of the custom font is as follows:

uint8_t Small_font_0[] = {0x3e, 0x22, 0x3e};
uint8_t Small_font_1[] = {0x24, 0x3e, 0x20};
uint8_t Small_font_2[] = {0x3a, 0x2a, 0x2e};
uint8_t Small_font_3[] = {0x2a, 0x2a, 0x3e};
uint8_t Small_font_4[] = {0x0e, 0x08, 0x3e};
uint8_t Small_font_5[] = {0x2e, 0x2a, 0x3a};
uint8_t Small_font_6[] = {0x3e, 0x2a, 0x3a};
uint8_t Small_font_7[] = {0x02, 0x02, 0x3e};
uint8_t Small_font_8[] = {0x3e, 0x2a, 0x3e};
uint8_t Small_font_9[] = {0x2e, 0x2a, 0x3e};
uint8_t Small_font_10[] = {0x14};
Copy the code

Now let’s look at how to display the time. Here we just show hours and minutes.

Here we have a trick. We can place the bitmap 0 to 9 at position 0 to 9 of the pointer array bitmap_data[], and place the time separator “:” at position 10 of the array. Since we defined a bitmap function display_bitmap(), we can display numbers without any mapping. For example, display_bitmap(22, 3, 0) displays 0; Display_bitmap (22, 3, 1) just shows 1, isn’t that convenient?

To get the tens and ones bits of hour and minute, we need to divide and mod them, such as dividing hour 9 by 10 to get the tens place 0 (why not 0.9? That’s because our time variable is defined as an integer, and an integer divided by another integer can only be an integer. Still don’t understand? Then you should brush up on the basics of C. Mod 9 into 10 gives the ones place 9. From the analysis we display the time in the appropriate position to get the following time display function.

Finally, if the hour or minute has only one digit, we need to zero it to 01:01. The code for displaying the time is as follows:

display_bitmap(22, 3, hour_variable / 10);
display_bitmap(18, 3, hour_variable % 10);
display_bitmap(14, 1, 10);
display_bitmap(12, 3, minute_variable / 10);
display_bitmap(8, 3, minute_variable % 10);
Copy the code

Time display: seconds

Time is passing, but we don’t have seconds on it, so how do we perceive the progress of time? To solve this problem, we define the following series of bitmaps. Note that the width of the bitmap is defined as 5 instead of 8. We switch the following bitmap every second.

Mold the above lattice patterns respectively by using mold taking software:

uint8_t clock_0[] = {0x1c, 0x22, 0x2e, 0x22, 0x1c};
uint8_t clock_1[] = {0x1c, 0x22, 0x2a, 0x26, 0x1c};
uint8_t clock_2[] = {0x1c, 0x22, 0x2a, 0x2a, 0x1c};
uint8_t clock_3[] = {0x1c, 0x22, 0x2a, 0x32, 0x1c};
uint8_t clock_4[] = {0x1c, 0x22, 0x3a, 0x22, 0x1c};
uint8_t clock_5[] = {0x1c, 0x32, 0x2a, 0x22, 0x1c};
uint8_t clock_6[] = {0x1c, 0x2a, 0x2a, 0x22, 0x1c};
uint8_t clock_7[] = {0x1c, 0x26, 0x2a, 0x22, 0x1c};
Copy the code

In bitmap_data[], we have 8 bitmaps, so we define a static local variable Clock_variable. Set the initial value to 11, increase the value of the Clock_variable variable by 1 every second, and display the bitmap of the corresponding serial number. When the value of the Clock_variable is 19, reassign it to 11, and we achieve the design of the stopwatch animation. The procedure is as follows:

static int Clock_variable = 11;

display_bitmap(4, 5, Clock_variable);

Clock_variable = Clock_variable + 1;

if (Clock_variable == 19) {
  Clock_variable = 11;
}
Copy the code

Above we designed stopwatch animation, but there is still a problem, due to the space limitation of dot matrix screen, we can not use digital display accurate seconds, so what to do? We observed that there were two pixels of height left at the bottom of the lattice screen, so we could display the last line accurately to the number of seconds by points.

As shown in the figure above, the last line is preceded by 5 dots and followed by 9 dots, so the number of seconds is 59 seconds. The code for displaying the number of seconds is as follows:

if (second_variable / 10) {
  mx.drawLine(7, 22, 7, (23 - second_variable / 10), true);
}
if (second_variable % 10) {
  mx.drawLine(7, 14, 7, (15 - second_variable % 10), true);
}
Copy the code

Where mx.drawLine() is the function to draw a line segment, which has four parameters, namely: the starting x-coordinate of the line segment, the starting x-coordinate of the line segment, the ending x-coordinate, the ending x-coordinate, and the display state (true lights the line segment; False extinguishes line segment. According to the definition of 4 and 1 lattice coordinates we use, the abscissa is at most 7 and the ordinate is at most 31.

When the bits of the number of seconds is 0, the line segment will be cleared, and the line segment can be displayed repeatedly to display the current number of seconds. I’m not going to analyze the relationship between the position of the line segment, the length of the line segment and the number of seconds, but I’ll leave it to you to exercise your brain.

Time period icon display

In order to be aware of the time of day, we want different ICONS for different times of day. We have defined the sun and moon ICONS, both of which are 8 in width, as shown below.

The module data is obtained by using the mold taking software as follows:

uint8_t sun[] = {0x24, 0x00, 0xbd, 0x3c, 0x3c, 0xbd, 0x00, 0x24};
uint8_t moon[] = {0x38, 0x7c, 0xe2, 0xc0, 0xc4, 0x4e, 0x24, 0x00};
Copy the code

Continue to add the sun and moon module data to positions 19 and 20 of the pointer array bitmap_data[]. Here we define between 6 o ‘clock and 18 o ‘clock to display the sun at 31 and the moon at other times as follows:

if ((hour_variable >= 6) && (hour_variable <= 18)) {
  display_bitmap(31, 8, 19);
} else {
  display_bitmap(31, 8, 20);
}
Copy the code

WiFiManager

If we fixed the WiFi information in the application, then when the network environment changes, the clock will not work, then you need to modify the network information and upload the application, which is very troublesome. To do this, we needed a way to dynamically modify network information, and we used the WiFiManager library, which allows you to configure your WiFi connection through a web page. The following is a simple example of network configuration. After this example is uploaded successfully, a WiFi hotspot named ESP8266 will be enabled. You can connect this hotspot with your mobile phone to configure the network as prompted. You can also use other hotspot names here, such as the name of your work instead of ESP8266. It should be noted that the ESP8266 only supports 2.4g WiFi networks, not 5G.

#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>

WiFiServer server(80);

void setup(){
 WiFiManager wifiManager;
 wifiManager.autoConnect("ESP8266");
 server.begin();
}

void loop(){

}
Copy the code

WiFi Connection feedback

When the network environment changes, we may need to reconfigure the network, so we have defined the following bitmap for disconnection prompts. The bitmap is 19 wide and looks like WiFi has been hijacked by aliens. Isn’t it very vivid?

The module data is obtained by using the mold taking software as follows:

uint8_t wifi[] = {0x04, 0x06, 0x13, 0xDB, 0xDB, 0x13, 0x06, 0x04, 0x00, 0x70, 0x18, 0x7d, 0xb6, 0x3c, 0x3c, 0xb6, 0x7d, 0x18, 0x70};
Copy the code

Here we use! (WiFi.status() ! = WL_CONNECTED) statement to determine whether the network connection is disconnected. When the WiFi connection is successful! (WiFi.status() ! = WL_CONNECTED) returns true, so we can synchronize time; When WiFi is disconnected,! (WiFi.status() ! = WL_CONNECTED) return false, we display the hint of WiFi disconnection on the dot matrix screen, then use the network configuration function to configure the network, and display the normal time again after the network configuration is successful. The code is as follows:

if(! (WiFi.status() ! = WL_CONNECTED)) { hour_variable = NTP.getTimeHour24(); minute_variable = NTP.getTimeMinute(); second_variable = NTP.getTimeSecond(); }else {
  mx.clear();
  display_bitmap(25, 19, 21);
  WiFiManager wifiManager;
  wifiManager.autoConnect("ESP8266");
  server.begin();
  mx.clear();
}
Copy the code

Puppy animation Design

In order to make the clock dynamic, we add an animation effect of a dog for the clock, which is composed of two animation frames with a width of 8. First of all, we draw these two frames by using the mold taking software, then click the horizontal mirror button to get the mirrored image, and finally generate the font.

The module data is obtained by using the mold taking software as follows:

uint8_t PROGMEM dog[] = {0x8C, 0x4C, 0xFE, 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x0C, 0x0C, 0xFE, 0x30, 0x30, 0x30, 0xF8, 0x00,};
Copy the code

The following example demonstrates dividing the lattice into two regions, region 0 and region 1. The p.setzone () function divides the lattice into different display areas. It takes three parameters: the region number, the start lattice and the end lattice. There are two display areas, so the parameter is 2. The corresponding relationship between dot matrix number and area is shown in the figure below:

The p.setspriteData () function is the initialization function of the Sprite animation, which accepts 7 parameters: initialization area, animation start Sprite data, animation start Sprite width, animation start Sprite frame number, animation end Sprite data, animation end Sprite width, animation end Sprite frame number.

The p.animate () function has two functions, one is to animate and the other is to animate. When in the feedback state, the animation returns 1 when completed and 0 when incomplete. When executed as an animation function, the animation runs smoothly through repeated calls to the animate () function, so the program constantly calls the P.animate () function.

The p.gettzonestatus () function acts like the p.displayanimate () function, except that it returns only the display state of the area.

The p.displayzonetext () function is a string animation display function, which accepts 7 parameters, respectively: display area, display string, alignment, animation speed, text display time, animation entry effect, animation exit effect. The following code demonstrates how to display Sprite animations in a region. Here we show the string to be empty and the display time to be 0. The display string to be empty ensures that we only have the dog animation without text, and the display time to be 0 ensures that the dog animation is consistent.

void setup() {
  P.begin(2);
  mx.begin();
  P.setZone(0, 0, 2);
  P.setZone(1, 3, 3);
  P.setSpriteData(1, dog, 8, 2, dog, 8, 2);
}

void loop() {
  P.displayAnimate();
  if (P.getZoneStatus(1)) {
    P.displayZoneText(1, "", PA_CENTER, 100, 0, PA_SPRITE, PA_SPRITE); }}Copy the code

Automatic brightness control

When we sleep, we will not look at the time. At this time, reducing the brightness of the dot matrix display is helpful for energy conservation and environmental protection. Therefore, we need to automatically adjust the brightness of the dot matrix display according to the time period. The following code sets brightness to 1 between 0 and 6 PM and 10 at other times. The p. setintensity () function is the region brightness setting function, which has two parameters: display area and brightness value, where the brightness value ranges from 0 to 15.

if ((hour_variable >= 0) && (hour_variable < 6)) {
  P.setIntensity(0, 1);
  P.setIntensity(1, 1);
} else {
  P.setIntensity(0, 10);
  P.setIntensity(1, 10);
}
Copy the code

Code combination

Finally, follow the logical relationship between the above functions and put the code together. Due to space constraints, I won’t cover the full code here.

Directions for use

Firstly, connect the power supply and initialize the clock. At the same time, the following interface prompts you to configure the network. At this time, the development board will automatically open the password-free WiFi hotspot named ESP8266.

Open the mobile phone and connect to the network. The network configuration steps are as follows:

Network configuration (Using An Android phone as an example) :

  1. Open mobile phone Settings select WiFi Settings open WiFi;
  2. Connect clock hotspot ESP8266 (hotspot name set by program, can be changed to another name);
  3. Select Login to go to the network configuration page.
  4. Click “Configure WiFi” to enter the icon page and click “Scan” to scan nearby hotspots.
  5. Select WiFi and enter WiFi password.
  6. Click Save and wait for the network configuration to succeed.

Results show