This post was last edited by MioChan on 2024-5-31 12:53
This post mainly introduces the code implementation of the network bullet screen dot matrix clock based on Beetle ESP32-C6
The implementation code of the TCP API server that accepts string requests and the NTP service timing can also be found in the previous post
I originally wanted to use a 64x32 LED dot matrix screen, but because this board has fewer pins, even if I tried to use all of them, it was not enough. But I thought that it would be better to use only one color instead of RGB screen to achieve monochrome. But when I finished writing the code and started compiling, I found that the commonly used libraries for driving dot matrix screens would report various errors when compiled on this target development board. I really couldn't find any good solution. There were no examples of using this mini development board to drive this screen in domestic and foreign forums. But I didn't have enough ability, so I had to settle for the next best thing. We used the MAX7219 four-in-one module, which cost about 17 yuan. Then use the following two libraries to drive it.
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
The dot matrix font part in the code refers to the M5stack floating dot matrix clock project. The original project gives the mirrored font, and this project turns it into a positive image. The code can be compiled and burned directly after installing the necessary libraries in the Arduino IDE.
The screen can be directly installed with the following definitions connected to these 5 pins
#define MAX_DEVICES 4
#define CS_PIN 5
#define CLK_PIN 23
#define Din_PIN 4
The overall structure of the code is relatively simple, and there are comments in it. If you have any questions, you can communicate directly in the post
#include <WiFi.h>
#include <NTPClient.h>
#include <ArduinoJson.h>
#include <NetworkUdp.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
//配网及其引脚定义
const int led = 15;
const char* ssid = "XXXX";
const char* password = "XXXX";
int status = WL_IDLE_STATUS;
String receivedContent = "";
const int timeZone = 8;
const char* ntpServer = "pool.ntp.org";
NetworkUDP udp;
WiFiServer server(80);
NTPClient timeClient(udp,ntpServer, timeZone * 3600, 60000);
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CS_PIN 5
#define CLK_PIN 23
#define Din_PIN 4
//用于驱动点阵屏显示
MD_Parola Display = MD_Parola(HARDWARE_TYPE,Din_PIN,CLK_PIN, CS_PIN, MAX_DEVICES);
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE,Din_PIN,CLK_PIN, CS_PIN, MAX_DEVICES);
void setup() {
Display.begin();
mx.begin();
Display.setIntensity(3);//设置亮度,0为最亮
Display.displayClear();
Display.setTextAlignment(PA_CENTER);//字体居中显示
Serial.begin(9600);
pinMode(led, OUTPUT);
digitalWrite(led, 0);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Display.print("Connected");
timeClient.begin();
timeClient.update();
server.begin();
Display.displayClear();
}
//定义点阵字符
//Small_font_0-10 分别为0-10数字以及:
//uint8_t clock_0-7 表示秒针位置指示
//sun moon为太阳和月亮的图形
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, 0x8, 0x3E};
uint8_t Small_font_5[] = {0x2E, 0x2A, 0x3A};
uint8_t Small_font_6[] = {0x3A, 0x2A, 0x3E};
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};
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};
uint8_t sun[] = {0x24, 0x00, 0xbd, 0x3c, 0x3c, 0xbd, 0x00, 0x24};
uint8_t moon[] = {0x1c, 0x3e, 0x47, 0x03, 0x23, 0x72, 0x24, 0x00};
uint8_t * bitmap_data[] = {
Small_font_0,
Small_font_1,
Small_font_2,
Small_font_3,
Small_font_4,
Small_font_5,
Small_font_6,
Small_font_7,
Small_font_8,
Small_font_9,
Small_font_10,
clock_0,
clock_1,
clock_2,
clock_3,
clock_4,
clock_5,
clock_6,
clock_7,
sun,
moon
};
//用于在点阵上绘制bitmap
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);
}
//用于绘制水平横线
void drawHorizontalLine(int y, int x_start, int x_end,bool pixel) {
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
for (int x = x_start; x <= x_end; x++) {
mx.setPoint(y, x, pixel);
delay(10);
}
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
//检测是否有设备Post字符串
void checkForNewClient() {
WiFiClient client = server.available();
if (client) {
Serial.println("New client");
digitalWrite(led, 1);
// Read the first line of the request
String firstLine = client.readStringUntil('\n');
// Check if this is a POST request
if (firstLine.startsWith("POST")) {
// Read the headers and find the Content-Length
int contentLength = -1;
while (client.connected()) {
String line = client.readStringUntil('\n');
if (line.startsWith("Content-Length:")) {
contentLength = line.substring(15).toInt();
}
// Check if the end of headers is reached (empty line)
if (line == "\r") {
break;
}
}
// Read the request body
if (contentLength > 0) {
String requestBody = client.readStringUntil('\n');
// Parse JSON from the request body
DynamicJsonDocument doc(1024);
deserializeJson(doc, requestBody);
String content = doc["content"];
if (content != "") {
Serial.println("Received content: " + content);
receivedContent =content; // Update the received string
} else {
Serial.println("No content received");
}
}
}
digitalWrite(led,0);
// Send response to the client
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/plain");
client.println("Connection: close");
client.println();
client.println("Response sent");
client.stop();
Serial.println("Client disconnected");
}
}
int hours;
int minutes ;
int seconds;
String times;
int Clock_variable = 11;
unsigned long startTime;
const unsigned long runDuration = 10000;
void loop() {
times=timeClient.getFormattedTime();
hours = times.substring(0, 2).toInt();
minutes = times.substring(3, 5).toInt();
seconds = times.substring(6, 8).toInt();
if (minutes%30 ==0 ) timeClient.update();
checkForNewClient();
if (receivedContent=="")
{
display_bitmap(22, 3, hours/ 10);
display_bitmap(18, 3, hours % 10);
display_bitmap(14, 1, 10);
display_bitmap(12, 3, minutes / 10);
display_bitmap(8, 3, minutes % 10);
display_bitmap(4, 5, seconds/8 +11);
if (seconds==0) drawHorizontalLine(7, 0,23,false);
if ((hours >= 6) && (hours <= 18)) {
display_bitmap(31, 8, 19);
} else {
display_bitmap(31, 8, 20);
}
// drawHorizontalLine(7, (23 - seconds / 10),23,true);
// drawHorizontalLine(7, (15 - seconds % 10),15,true);
startTime = millis();
delay(2000);
}
else
{
if (Display.displayAnimate()) {
Display.displayScroll(receivedContent.c_str(), PA_LEFT, PA_SCROLL_LEFT, 50);//滚动显示文字
if (millis() - startTime >= runDuration)
{
Display.displayClear(); // 清除显示
receivedContent="";
}
}
}
}
The following is the actual effect, but it looks rather ugly. We will print a shell and add a soft light panel to the screen.
Measure the size and draw a shell
Use Bambu Studio to slice and print directly. The 3mf file is attached and you can pick it up if you need it.
The final effect of the actual product is that my board has been soldered with pin headers, so I can't put it in and can only plug it in externally. When designing, I have already considered the position of the board, battery and type-c port. If there is no pin header, the board can be placed directly behind the screen and the welding wire can be connected.
When the clock receives a text Post request from other devices, it will automatically start playing the barrage. Below is the effect of the text barrage (I am posting Hello EEWorld here). The text will scroll from left to right and will enter the clock mode again after playing for a few seconds.
The barrage delivery can be achieved directly using the iOS shortcut commands, as explained in detail in the previous post and instructions.
At this point, our little clock is finished.