像素智能时钟
作者: 老杰瑞
一、作品简介
本作品是基于esp32-Arduino开发的像素时钟,使用AWTRIX的服务器进行自定义数据显示。
作品原计划采用Seeed Studio XIAO ESP32C3作为主控,但因为遇到了在能力范围内修复不了的bug,最终采用esp32-worrm-32作为主控,Seeed Studio XIAO ESP32C3作为副主控进行手势识别读取传给主控。作品采用高质量的ws2812像素化显示屏,通过细小的LED像素点来呈现时间和其他相关信息,通过bmp250作为温度检测的模块,光敏电阻进行室内光强的测量。Esp32进行屏幕驱动和温度光强检测,再将数据回传服务器,由服务器下发显示的数据,进行数据显示。服务器使用树莓派4b进行搭建,与esp32通过mqtt协议进行通信,该项目为时钟项目,实现的时间显示,动画显示,也可以通过服务器设置显示相关api数据,扩展性强,可在网页端制作自定义动画和显示,能在日常生活中提供显示和装饰作用。通过修改上位机脚本,能实现多种自定义功能。
作品照片:
项目用到的板卡、芯片、模块等介绍:
Esp32 wroom-32开发板 和Seeed Studio XIAO ESP32C3开发板·、树莓派4b
Ws2812矩阵灯带,paj7650手势传感器,bp510温度模块,触摸模块
作品功能介绍:
作品分为服务器端和显示端,树莓派4b跑AWTRIX的服务器,esp32跑屏幕显示和触摸、手势、温度数据收集
图一:显示端
图二:服务器端
- 系统框图(图文结合)
硬件框架:
图三:硬件框架
设esp32-worrm-32作为主控,负责灯带显示和温度,触摸模块数据收集,esp32-c3负责收集手势传感器数据发给主控模拟触摸信号。树莓派4b跑AWTRIX服务器,与主控通过局域网进行mqtt连接传输显示数据。.
软件框架:
图四:软件框架
三、各部分功能说明(图文结合)
代码用vs+pio+arduino的框架进行编写
像素矩阵驱动代码:
使用FastLED和Adafruit_GFX库来驱动ws2812 LED矩阵
参考链接:
#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <FastLED.h>
#include <FastLED_NeoMatrix.h>
#include <Fonts/TomThumb.h>
class Matrix
{
private:
int type;
int tempCorrection;
#define NUMBER_LED = 256;
#ifdef ESP8266
#define MATRIX_PIN = D2;
#else
#define MATRIX_PIN = 15;
#endif
enum MsgType {
MsgType_Wifi,
MsgType_Host,
MsgType_Temp,
MsgType_Audio,
MsgType_Gest,
MsgType_LDR,
MsgType_Other
};
CRGB leds[256];
FastLED_NeoMatrix *matrix;
void hardwareAnimatedUncheck(int typ, int x, int y);
void hardwareAnimatedCheck(MsgType typ, int x, int y);
public:
void init(int type, int tempCorrection);
void setTempCorrection(int tempCorrection);
void setType(int type);
void hardwareCheck();
void serverSearch(int rounds, int typ, int x, int y);
void hardwareAnimatedSearch(int typ, int x, int y);
void setTextToMatrix(bool clear, byte red, byte green, byte blue, int xPos, int yPos, String text);
void flashProgress(unsigned int progress, unsigned int total);
void fillScreen(byte red, byte green, byte blue);
void drawPixel(uint16_t x_coordinate, uint16_t y_coordinate, uint16_t data);
void drawPixel(uint16_t x_coordinate, uint16_t y_coordinate, byte red, byte green, byte blue);
void clear();
void setBrightness(byte brightness);
void setCursor(uint16_t x, uint16_t y);
uint16_t getCursorX();
uint16_t getCursorY();
void print(String text);
void setTextColor(byte red, byte green, byte blue);
void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, byte red, byte green, byte blue);
void drawRect(uint16_t x0, uint16_t y0, uint16_t width, uint16_t height, byte red, byte green, byte blue);
void fillRect(uint16_t x0, uint16_t y0, uint16_t width, uint16_t height, byte red, byte green, byte blue);
void drawCircle(uint16_t x0, uint16_t y0, uint16_t radius, byte red, byte green, byte blue);
void fillCircle(uint16_t x0, uint16_t y0, uint16_t radius, byte red, byte green, byte blue);
private:
uint32_t Wheel(byte WheelPos, int pos);
};
在主函数中只要配置FastLED,根据LED矩阵(WS2812)
配置FastLED库,设置LED数量和连接的Arduino引脚
创建Adafruit_GFX兼容类,通过创建的类,你可以使用Adafruit_GFX提供的所有标准图形函数,如drawLine(), drawCircle(), fillRect()等。
这些函数最终会调用drawPixel()方法来在LED矩阵上绘制图形。
最后使用FastLED的show()方法来更新LED矩阵的显示。
网络配置
在网络配置方面直接使用了WiFiManager库在esp32上创建和管理WiFi连接的一部分。它主要负责设置和配置WiFi连接参数,以及为用户提供一个配置界面,用于输入和管理这些参数。
// 设置 WiFiManager 的回调函数和参数
wifiManager.setAPStaticIPConfig(IPAddress(172, 217, 28, 1), IPAddress(172, 217, 28, 1), IPAddress(255, 255, 255, 0));
//WiFiManager 创建的访问点(AP)配置为使用静态 IP 地址 172.217.28.1,默认网关也设置为相同的 IP 地址,并且使用子网掩码 255.255.255.0。
WiFiManagerParameter custom_awtrix_server("server", "AWTRIX Host", awtrix_server, 16);
//创建一个参数对象 custom_awtrix_server,并将其关联到 AWTRIX 服务器的主机名或 IP 地址。当用户使用设备时,可以通过配置界面输入 AWTRIX 服务器的地址,该地址将被存储在 awtrix_server 数组中,
// 其他WiFiManager参数设置,包括服务器地址、端口和矩阵类型等
WiFiManagerParameter custom_port("Port", "Matrix Port", Port, 6);
//创建一个参数对象 custom_port,并将其关联到 Matrix 端口号
WiFiManagerParameter custom_matrix_type("matrixType", "MatrixType", "0", 1);
//创建一个参数对象 custom_matrix_type,并将其关联到 Matrix 类型。用户可以在配置界面中选择 Matrix 的类型,这个选择将被存储为一个字符(通常是数字)
WiFiManagerParameter host_hint("<small>AWTRIX Host IP (without Port)<br></small><br><br>");
//创建一个提示信息的参数对象 host_hint,它将在配置界面中显示帮助用户理解如何配置 AWTRIX 服务器的主机名或 IP 地址。这对于向用户提供清晰的配置说明非常有用。提示信息将以 HTML 的形式呈现,以便更好地格式化和显示在配置界面中。
WiFiManagerParameter port_hint("<small>Communication Port (default: 7001)<br></small><br><br>");
//创建了一个名为 port_hint 的参数对象,用于提供用户在配置界面中输入通信端口号的提示信息。提示信息是 "Communication Port (default: 7001)",它告诉用户可以在这里输入通信端口号,并且提供了默认值为 7001。
WiFiManagerParameter matrix_hint("<small>0: Columns; 1: Tiles; 2: Rows <br></small><br><br>");
//创建了一个名为 matrix_hint 的参数对象,用于提供用户在配置界面中选择 Matrix 类型的提示信息。提示信息列出了三种选项(0: Columns; 1: Tiles; 2: Rows),并告诉用户可以在这里选择 Matrix 类型。
WiFiManagerParameter p_lineBreak_notext("<p></p>");
//创建一个分隔线
wifiManager.setSaveConfigCallback(saveConfigCallback);
wifiManager.setAPCallback(configModeCallback);
wifiManager.addParameter(&p_lineBreak_notext);
wifiManager.addParameter(&host_hint);
wifiManager.addParameter(&custom_awtrix_server);
wifiManager.addParameter(&port_hint);
wifiManager.addParameter(&custom_port);
wifiManager.addParameter(&matrix_hint);
wifiManager.addParameter(&custom_matrix_type);
wifiManager.addParameter(&p_lineBreak_notext);
在时钟第一次启动的时候会进wifi配网的模式,也可以在时钟开机显示boot画面的时候按特定按键重置网络状态
// 检测按钮状态,当按钮按下时进行重置
// 当数字引脚15为高电平时,执行以下循环
//清除数据
while (!digitalRead(0))
{
// 如果变量zahl发生变化,则更新显示屏的内容
if (zahl != zahlAlt)
{
matrix->clear();// 清除屏幕
matrix->setTextColor(matrix->Color(255, 0, 0));// 设置文字颜色为红色
matrix->setCursor(6, 6);// 设置光标位置
matrix->print("RESET ");// 打印文本“RESET”
matrix->print(zahl);// 打印变量zahl的值
matrix->show();// 显示内容
zahlAlt = zahl;// 更新zahlAlt的值为zahl的当前值
}
// 更新变量zahl的值,它与启动时间有关
zahl = 5 - ((millis() - zeit) / 1000);
// 如果计时器归零,执行重置操作
if (zahl == 0)
{
// 以下代码块用于重置设备的WiFi设置和配置文件
// 清除屏幕并显示“RESET!”
matrix->clear();
matrix->setTextColor(matrix->Color(255, 0, 0));
matrix->setCursor(6, 6);
matrix->print("RESET!");
matrix->show();
delay(1000);
// 如果 LittleFS 初始化成功,删除配置文件
if (LITTLEFS.begin())
{
delay(1000);
LITTLEFS.remove("/awtrix.json");
LITTLEFS.end();
delay(1000);
}
// 重置 WiFi 设置
wifiManager.resetSettings();
Serial.println("重置 WiFi 设置");
//ESP.reset();
}
}
// 如果无法连接 WiFiManager 自动连接热点
if (!wifiManager.autoConnect("AWTRIX Controller", "awtrixxx"))
{
//reset and try again, or maybe put it to deep sleep
//ESP.reset();
delay(5000);
}
与服务器通信
这部分代码直接使用了awtrix的代码,作者能力有限,只在按键操作方面进行了修改,适配手势操作
//is needed for the server search animation
// 处理 Arduino OTA
if (firstStart && !ignoreServer)
{
// 每500毫秒执行一次服务器搜索动画
if (millis() - myTime > 500)
{
serverSearch(myCounter, 0, 28, 0);
myCounter++;
if (myCounter == 4)
{
myCounter = 0;
}
myTime = millis();
}
}
//not during the falsh process
// 如果不处于更新过程中
if (!updating)
{
// 如果是USB连接或首次启动
if (USBConnection || firstStart)
{
int x = 100;
while (x >= 0)
{
x--;
//USB
if (Serial.available() > 0)
{
//read and fill in ringbuffer
// 读取并填充环形缓冲区
myBytes[bufferpointer] = Serial.read();
messageLength--;
for (int i = 0; i < 14; i++)
{
if ((bufferpointer - i) < 0)
{
myPointer[i] = 1000 + bufferpointer - i;
}
else
{
myPointer[i] = bufferpointer - i;
}
}
//prefix from "awtrix" == 6?
// 前缀是 "awtrix" 吗
if (myBytes[myPointer[13]] == 0 && myBytes[myPointer[12]] == 0 && myBytes[myPointer[11]] == 0 && myBytes[myPointer[10]] == 6)
{
//"awtrix" ?
if (myBytes[myPointer[9]] == 97 && myBytes[myPointer[8]] == 119 && myBytes[myPointer[7]] == 116 && myBytes[myPointer[6]] == 114 && myBytes[myPointer[5]] == 105 && myBytes[myPointer[4]] == 120)
{
messageLength = (int(myBytes[myPointer[3]]) << 24) + (int(myBytes[myPointer[2]]) << 16) + (int(myBytes[myPointer[1]]) << 8) + int(myBytes[myPointer[0]]);
SavemMessageLength = messageLength;
awtrixFound = true;
}
}
// 如果找到 "awtrix" 并且消息长度为0
if (awtrixFound && messageLength == 0)
{
byte tempData[SavemMessageLength];
int up = 0;
for (int i = SavemMessageLength - 1; i >= 0; i--)
{
if ((bufferpointer - i) >= 0)
{
tempData[up] = myBytes[bufferpointer - i];
}
else
{
tempData[up] = myBytes[1000 + bufferpointer - i];
}
up++;
}
USBConnection = true;
updateMatrix(tempData, SavemMessageLength);
awtrixFound = false;
}
bufferpointer++;
if (bufferpointer == 1000)
{
bufferpointer = 0;
}
}
else
{
break;
}
}
}
//Wifi
if (WIFIConnection || firstStart)
{
//Serial.println("wifi oder first...");
// 如果未连接到服务器,重新连接
if (!client.connected())
{
//Serial.println("nicht verbunden...");
reconnect();
if (WIFIConnection)
{
USBConnection = false;
WIFIConnection = false;
firstStart = true;
}
}
else
{
client.loop();
}
}
// 如果连接超时,重置连接状态
if (millis() - connectionTimout > 20000)
{
USBConnection = false;
WIFIConnection = false;
firstStart = true;
}
}
checkTaster(0);
checkTaster(1);
checkTaster(2);
checkTouchButtons();
//checkTaster(3);
//is needed for the menue...
// 如果忽略服务器,显示菜单
if (ignoreServer)
{
if (pressedTaster > 0)
{
matrix->clear();
matrix->setCursor(0, 6);
matrix->setTextColor(matrix->Color(0, 255, 50));
//matrix->print(myMenue.getMenueString(&menuePointer, &pressedTaster, &minBrightness, &maxBrightness));
matrix->show();
}
//get data and ignore
// 获取数据并忽略
if (Serial.available() > 0)
{
Serial.read();
}
}
完整代码见附件,全部有点大,就不放出来了
4原理图
图5:原理图
由于只做了一块板子,就用洞洞板进行飞线完成了电路板的制作,节省成本
5屏幕显示方式
图6:屏幕图
为了匀光效果,在ws2812的矩阵屏幕上,3d打印了一份光栅板进行隔光,防止显示太花再购买了硫酸纸,安装在光栅板上进行匀光,使得显示均匀
图7:无硫酸纸效果
图8:有硫酸纸效果
6服务器端
使用树莓派4b安装了awtrix服务器,给树莓派配了个120块钱的屏幕和25块钱的支架,放桌面上可以当作linux主机使用
图9:树莓派服务器
- 作品源码
在审核 下载链接粘贴到作品文档和作品提交帖中)
五、作品功能演示视频
功能演示:
d7c7c25a328d4beac1a5709b9cf877c2
六、项目总结
感谢得捷电子提供的元器件支持,在这次项目实现过程中学习到了很多,以前只玩过stm32和51单片机。这次第一次玩上了esp32和pio上的arduino开发。
在代码移植,库的适配上遇到了很多很多bug,也在一步一步的学习中逐渐搞定。
本次的项目其实还有很大的完善空间,下次再搞像素时钟的话,我的显示灯会更换spi控制的apa201来,这样就不会收到中断和WiFi的影响了,还有显示效果,现在依然还不能脱离awtrix服务器运行,今后会自己写出离线版本的像素时钟,还有一个遗憾是准备了功放和喇叭,由于期末周时间太紧还没有集成上去,想加音乐频谱的功能和蓝牙播放。以后还要继续完善,fastled库是真的牛皮,学习过程中受益匪浅。
感谢EEWorld提供本次创意大赛的机会,衷心祝愿EEWorld也会越办越好
.