本帖最后由 MioChan 于 2024-5-31 12:53 编辑
本帖主要介绍基于Beetle ESP32-C6 的网络弹幕点阵时钟的代码实现
TCP接受字符串请求的API服务器以及NTP服务授时的实现代码也可以参考上一帖
本来想使用64x32的LED点阵屏幕,但因为这个板子的引脚比较少,试了就算全用上也不太够,但我想到可以不做RGB屏只使用其中一个颜色实现单色不就好了,但写完代码开始编译的时候发现常用的那几个驱动点阵屏的库在这个目标开发板上编译都会各种报错,实在没找到啥好办法,国内外论坛也没有用这种迷你开发版驱动这个屏幕的例子,奈何能力不够,所以退而求其次,我们使用MAX7219 四合一模块,大概17元。然后使用下面两个库来驱动。
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
代码中点阵字体部分参考了M5stack悬浮点阵时钟这个项目,原项目给的是镜像后的字体,本项目将其变成了正像,代码直接在Arduino IDE中安装好必要的库后即可编译烧录。
屏幕可以直接安装下面的定义连到这5个引脚
#define MAX_DEVICES 4
#define CS_PIN 5
#define CLK_PIN 23
#define Din_PIN 4
代码整体结构比较简单,里面也有注释,有问题可以直接帖子里交流
#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="";
}
}
}
}
下面是实际的效果,不过这样的话感觉比较难看,我们还是打印个壳子顺便给屏幕加上柔光板。
测量好尺寸,画个外壳
直接用Bambu Studio切片并打印,3mf文件放在附件了有需要可以自取
最终实物的效果,因为我板子已经焊了排针,放不进去只能外挂了,当时设计的时候已经考虑了板子、电池和typec口的位置,没有排针的话板子直接放屏幕后面,焊条线连上就可以了
当时钟接受到其他设备的文本Post请求后,会自动开始播放弹幕,下面是文字弹幕的效果(我这里投的是Hello EEWorld),文字会从左向右滚动显示,播放几秒后会再次进入时钟模式。
弹幕投送直接用ios的快捷指令就能实现,详细解释之前那一帖以及说明。
到此,我们这个小时钟就制作完毕啦