用一款以圣诞为主题的互动式雪球让节日氛围鲜活起来!项目使用矽递科技(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 #include #include
#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 变量用于追踪当前显示的背景图像。
雪花粒子模拟
1. 粒子初始化
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 个粒子。
2. 粒子更新
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; }}
(左右移动查看全部内容)
通过以下方式更新粒子位置:
下落效果:每个粒子向下移动。
风效影响:添加轻微的水平偏移。
循环机制:当粒子从底部离开时,重置到顶部。
3. 粒子渲染
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); // 约100帧每秒}
(左右移动查看全部内容)
清除图像缓存(sprite),渲染当前帧(背景 + 粒子),并检查用户输入。
双缓冲
为了减少雪花闪烁并提高动画的流畅度,我们使用双缓冲技术。
这使得我们能够在屏幕外的缓冲区进行绘制,然后再将其显示在屏幕上。
本项目中的双缓冲
在这个项目中,TFT_eSPI 库的 TFT_eSprite 类实现了双缓冲。
1. 图像缓存(sprite)创建
在 setup() 函数中创建图像缓存(屏幕外缓冲区):
sprite.createSprite(240, 240); // 匹配显示屏尺寸
(左右移动查看全部内容)
2. 绘制缓冲区
所有绘制操作(背景渲染和雪花粒子动画)都在图像缓存(sprite)上进行:
sprite.fillScreen(TFT_BLACK); // 清除图像缓存renderParticlesToSprite(); // 绘制雪花粒子
(左右移动查看全部内容)
3. 更新显示
在图像缓存中完整绘制完一帧后,通过一次操作将其推送到显示屏上:
sprite.pushSprite(0, 0);
(左右移动查看全部内容)
这会立即将缓冲区的内容传输到屏幕上。
4. 复用
在循环开始时清除图像缓存,以便每一帧都能复用它:
sprite.fillScreen(TFT_BLACK);
(左右移动查看全部内容)
使用双缓冲的优势
流畅的雪花动画:下落的雪花粒子能够无缝更新,不会出现闪烁现象。
动态背景切换:触摸触发的背景切换能够在无可见延迟或瑕疵的情况下完成。
高效渲染:在内存(RAM)中进行绘制比逐行直接更新显示屏要快。
总结
我希望有人能制作一个 3D 球体,把适用于晓开发板的矽递科技圆形显示屏放在里面,然后将其挂在圣诞树上。
我也希望修改代码,使其能从 SD 卡加载图像,而不是使用闪存数组来存储图像。
希望你们喜欢这个项目,为你们的圣诞节增添一点奇妙氛围。
-
嵌入式
+关注
关注
5161文章
19784浏览量
319685 -
显示屏
+关注
关注
28文章
4620浏览量
76871 -
开发板
+关注
关注
25文章
5768浏览量
106409 -
ESP32
+关注
关注
21文章
1028浏览量
19537
发布评论请先 登录

深入解读led互动地砖屏的原理与互动地砖屏和常规显示屏的区别
ESP32硬定时器可以用来扫描HUB75D的LED32*16的显示屏吗?
圆形LED显示屏的详细介绍
基于ESP32构建一个具有3.5英寸大显示屏的互联网广播设备

带有ESP32和OLED显示屏的Instagram追随者计数器

带OLED显示屏的LoRa节点和3个带ESP32的继电器

评论