基于矽递科技(Seeed Studio)圆形显示屏互动式圣诞雪球,借助互动式雪球模拟体验圣诞的魔力!使用矽递科技的圆形显示屏以及XIAO ESP32S3 开发板。
用一款以圣诞为主题的互动式雪球让节日氛围鲜活起来!该项目使用矽递科技(Seeed Studio)圆形显示屏和(XIAO)ESP32S3开发板打造出极具视觉吸引力的雪景动画,包含动态飘落的雪花、风效以及触摸交互功能。凭借双缓冲技术实现的流畅动画,该项目能提供专业且无闪烁的体验。
Seeed Studio XIAO系列是小型开发板,共享类似的硬件结构,尺寸实际上是拇指大小。这里的代号“小”代表它的一半特征“小”,另一半将是“羊角面包”。
Seeed Studio XIAO ESP32S3 Sense集成了摄像头传感器、数字麦克风和SD卡支持。结合嵌入式ML计算能力和摄影能力,这款开发板是使用智能语音和视觉AI的绝佳工具。
开发特点:
- 动态雪景动画:模拟飘落的雪花颗粒,其速度和风效均可调节。
- 触摸交互:只需轻触屏幕,即可循环切换三张漂亮的圣诞主题背景图。
- 流畅渲染:采用双缓冲技术实现无缝视觉效果,无闪烁现象。
- 可定制背景:轻松添加自己的 PNG 图像,对雪球进行个性化设置。
你将学到的内容:
- 如何将矽递科技圆形显示屏与xiao ESP32S3 开发板配合使用。
- 利用 TFT_eSPI 库实现双缓冲以呈现流畅动画。
- 使用 lv_xiao_round_screen 库处理触摸输入。
- 模拟粒子效果以实现逼真的雪景动画。
环境准备
硬件
对于该项目,我们需要以下设备:
适用于晓开发板的矽递科技圆形显示屏、XIAO ESP32S3 开发板,我选用xiao ESP32S3 开发板是因为内存方面的考虑。PNGDEC(PNG 解码库)运行大约需要 40KB 的内存。
软件准备
要使用圆形显示屏,请前往 “晓开发板圆形显示屏入门” 页面安装必要的库。尝试运行一些示例,看看一切是否运行正常。
库
对于这个项目,我们将使用随适用于晓开发板的矽递科技圆形显示屏附带的库。按照 “晓开发板圆形显示屏入门” 教程中的规定安装所有库。之后,你还需要以下内容:
- PNGdec 库。
- 更新 LVGL 库(或者不安装来自矽递科技 GitHub 的那个版本)
图像
我们的图像是存储在闪存数组中的 PNG 图像,使用 PNGdec 库进行显示。所有图像都必须是 PNG 格式。以下是我使用过的图像 —— 全部由人工智能生成。
我们需要准备好背景图像,以便 TFT_eSPI 库能够显示它们,并且这些图像能很好地适配晓开发板的圆形显示屏。
准备图像
调整图像大小,我们的XIAO开发板圆形显示屏分辨率为 240×240。我们需要对图像进行尺寸调整。下面我将展示如何使用 GIMP(一款图像处理软件)来操作。
1.打开图像
2.选择 “图像”>“缩放图像”
3.将宽度和高度都设置为 240。由于 “保持比例” 选项(链条图标所示)已被选中,一旦你更改了宽度,高度也会相应地改变。
4.点击 “缩放” 按钮。
5.保存图像(我打算覆盖原来的图像)。
现在图像已经准备好了,让我们来创建闪存数组吧。
创建闪存数组
注意:这些操作说明包含在 TFT_eSPI 库的 Flash_PNG 示例当中。要创建闪存数组,进入 “文件转 C 语言风格数组转换器”。
创建数组的步骤如下:
1、使用 “浏览” 功能上传图像。在上传图像之后……
2、我们需要设置一些选项
所有其他选项都会变灰(即不可用、无法进行设置操作)。
3、让我们将数据类型更改为字符型(char)。
4、点击 “转换” 按钮。这将会把图像转换为数组。
5、现在你可以按下 “另存为文件” 按钮来保存你的图像,并将其添加到你的 Arduino(开源电子原型平台)代码中,或者按下 “复制到剪贴板” 按钮。
如果你选择 “复制到剪贴板”,那么你需要点击 Arduino 编辑器右侧的三个点(省略号图标),然后选择 “新建标签页”。
给它取个名字(一般来说是你的图像名加上.h 扩展名)。
最终你所有的图像都会以.h 文件的形式存在。
代码
以下是对代码主要功能的一些解释,代码中也包含了一些注释。
头文件与库
我们首先引入一些库:
#include <PNGdec.h>
#include <TFT_eSPI.h>
#include <Wire.h>
#include "background1.h"
#include "background2.h"
#include "background3.h"
#define USE_TFT_ESPI_LIBRARY
#include "lv_xiao_round_screen.h"
请记住,你需要安装矽递科技(Seeed Studio)相关的库。
背景图像以下是管理背景图像的函数:
struct Background {
const uint8_t *data;
size_t size;
};
const Background backgrounds[] = {
{(const uint8_t *)background1, sizeof(background1)},
{(const uint8_t *)background2, sizeof(background2)},
{(const uint8_t *)background3, sizeof(background3)},
};
结构体:每个背景图像都作为一个 Background
结构体进行存储,该结构体包含:
data
:指向 PNG 数据的指针。
size
:PNG 文件的大小。
数组:backgrounds
数组存储了所有的背景图像。currentBackground
变量用于追踪当前显示的背景图像。
雪花粒子模拟
- 粒子初始化
void initParticles() {
for (int i = 0; i < numParticles; i++) {
particles[i].x = random(0, sprite.width());
particles[i].y = random(0, sprite.height());
particles[i].speed = random(3, 8);
}
}
它使用随机位置和速度来初始化 numParticles
个粒子。
- 粒子更新
void updateParticles() {
for (int i = 0; i < numParticles; i++) {
particles[i].speed += random(-1, 2);
particles[i].speed = constrain(particles[i].speed, 3, 8);
particles[i].y += particles[i].speed;
particles[i].x += random(-1, 2);
if (particles[i].y > sprite.height()) {
particles[i].y = 0;
particles[i].x = random(0, sprite.width());
particles[i].speed = random(3, 8);
}
if (particles[i].x < 0) particles[i].x = sprite.width();
if (particles[i].x > sprite.width()) particles[i].x = 0;
}
}
通过以下方式更新粒子位置:
- 下落效果:每个粒子向下移动。
- 风效影响:添加轻微的水平偏移。
- 循环机制:当粒子从底部离开时,重置到顶部。
- 粒子渲染
void renderParticlesToSprite() {
for (int i = 0; i < numParticles; i++) {
sprite.fillCircle(particles[i].x, particles[i].y, 2, TFT_WHITE);
}
}
它将每个粒子渲染为一个小的白色圆圈。
PNG 解码
int16_t rc = png.openFLASH((uint8_t *)backgrounds[currentBackground].data,
backgrounds[currentBackground].size,
pngDrawToSprite);
if (rc!= PNG_SUCCESS) {
Serial.println("Failed to open PNG file!");
return;
}
png.decode(NULL, 0);
使用 png.openFLASH()
函数加载并解码当前的背景 PNG 图像。
触摸交互
if (chsc6x_is_pressed()) {
currentBackground = (currentBackground + 1) % numBackgrounds;
delay(300);
}
使用 chsc6x_is_pressed()
检测触摸事件,并通过递增 currentBackground
变量来切换背景图像。
设置与循环
设置部分:
void setup() {
Serial.begin(115200);
tft.begin();
tft.fillScreen(TFT_BLACK);
sprite.createSprite(240, 240);
pinMode(TOUCH_INT, INPUT_PULLUP);
Wire.begin();
initParticles();
}
初始化显示屏、触摸输入以及雪花粒子。
主循环:
void loop() {
sprite.fillScreen(TFT_BLACK);
int16_t rc = png.openFLASH((uint8_t *)backgrounds[currentBackground].data,
backgrounds[currentBackground].size,
pngDrawToSprite);
if (rc == PNG_SUCCESS) {
png.decode(NULL, 0);
updateParticles();
renderParticlesToSprite();
sprite.pushSprite(0, 0);
}
if (chsc6x_is_pressed()) {
currentBackground = (currentBackground + 1) % numBackgrounds;
delay(300);
}
delay(10);
}
清除图像缓存(sprite),渲染当前帧(背景 + 粒子),并检查用户输入。
双缓冲
为了减少雪花闪烁并提高动画的流畅度,我们使用双缓冲技术。
这使得我们能够在屏幕外的缓冲区进行绘制,然后再将其显示在屏幕上。
本项目中的双缓冲
在这个项目中,TFT_eSPI
库的 TFT_eSprite
类实现了双缓冲。
- 图像缓存(sprite)创建
在 setup()
函数中创建图像缓存(屏幕外缓冲区):
sprite.createSprite(240, 240);
- 绘制缓冲区
所有绘制操作(背景渲染和雪花粒子动画)都在图像缓存(sprite)上进行:
sprite.fillScreen(TFT_BLACK);
renderParticlesToSprite();
- 更新显示
在图像缓存中完整绘制完一帧后,通过一次操作将其推送到显示屏上:
sprite.pushSprite(0, 0);
这会立即将缓冲区的内容传输到屏幕上。
-
复用
在循环开始时清除图像缓存,以便每一帧都能复用它:
sprite.fillScreen(TFT_BLACK);
使用双缓冲的优势
- 流畅的雪花动画:下落的雪花粒子能够无缝更新,不会出现闪烁现象。
- 动态背景切换:触摸触发的背景切换能够在无可见延迟或瑕疵的情况下完成。
- 高效渲染:在内存(RAM)中进行绘制比逐行直接更新显示屏要快。
总结
我希望有人能制作一个 3D 球体,把适用于晓开发板的矽递科技圆形显示屏放在里面,然后将其挂在圣诞树上。
我也希望修改代码,使其能从 SD 卡加载图像,而不是使用闪存数组来存储图像。
希望你们喜欢这个项目,为你们的圣诞节增添一点奇妙氛围。
源代码: *附件:代码资料.7z
作者:Bruno Santos
来源:https://www.hackster.io/feiticeir0/seeed-studio-round-display-interactive-christmas-snow-globe-65b972