别再只用串口打印了!用Arduino UNO和0.96寸OLED做个桌面小动画(附完整代码)
用Arduino UNO和0.96寸OLED打造桌面动态艺术装置
你是否已经厌倦了单调的串口打印输出?Arduino UNO搭配0.96寸OLED屏幕可以变身为一个迷你的数字画布,为你的工作台增添一抹动态的科技艺术。本文将带你超越基础的数据显示,探索如何利用这块小小的屏幕创造生动的动画效果,从随机弹跳的球体到简约的数字时钟,让你的硬件项目更具观赏性和趣味性。
1. 硬件准备与环境搭建
1.1 所需组件清单
要开始这个创意项目,你需要准备以下硬件:
- Arduino UNO开发板:作为整个系统的控制核心
- 0.96寸OLED显示屏(I2C接口):推荐使用SSD1306驱动的版本
- 杜邦线若干:用于连接各组件
- USB数据线:为Arduino供电并上传程序
硬件连接非常简单,只需将OLED的四个引脚与Arduino对应连接:
| OLED引脚 | Arduino引脚 |
|---|---|
| GND | GND |
| VCC | 3.3V或5V |
| SCL | A5 |
| SDA | A4 |
1.2 软件环境配置
在开始编程前,需要安装必要的库文件:
- 打开Arduino IDE,点击"工具"->"管理库"
- 搜索并安装以下两个关键库:
- Adafruit_GFX_Library:提供基础的图形绘制功能
- Adafruit_SSD1306:针对SSD1306显示屏的专用驱动
提示:安装库时,建议选择最新稳定版本以确保兼容性
安装完成后,可以通过以下测试代码验证硬件连接是否正常:
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire); void setup() { display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(10, 20); display.println("OLED Test Success!"); display.display(); } void loop() {}2. 掌握Adafruit_GFX核心绘图功能
2.1 基础图形绘制
Adafruit_GFX库提供了一系列图形绘制函数,以下是几个最常用的:
drawPixel(x, y, color):在指定位置绘制单个像素点drawLine(x0, y0, x1, y1, color):绘制直线drawRect(x, y, w, h, color):绘制空心矩形fillRect(x, y, w, h, color):绘制实心矩形drawCircle(x, y, r, color):绘制空心圆形fillCircle(x, y, r, color):绘制实心圆形drawTriangle(x0,y0,x1,y1,x2,y2,color):绘制空心三角形
2.2 文本显示技巧
除了图形,文本显示也是OLED的重要功能。关键文本函数包括:
display.setTextSize(1); // 设置字体大小(1-8) display.setTextColor(SSD1306_WHITE); // 设置文本颜色 display.setCursor(0, 0); // 设置文本起始位置 display.println("Hello World!"); // 输出文本注意:OLED屏幕的坐标系原点(0,0)位于左上角,x轴向右增加,y轴向下增加
2.3 屏幕刷新优化
频繁刷新整个屏幕可能导致闪烁,可以采用以下优化策略:
- 局部刷新:只更新需要改变的部分
- 双缓冲技术:在内存中完成绘制后再一次性显示
- 合理设置刷新率:根据动画需求调整delay时间
3. 创建弹跳球动画
3.1 基础弹跳球实现
让我们从经典的弹跳球动画开始。以下代码实现了一个在屏幕边界反弹的球体:
#include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire); int ballX = SCREEN_WIDTH/2; int ballY = SCREEN_HEIGHT/2; int ballRadius = 5; int ballSpeedX = 2; int ballSpeedY = 3; void setup() { display.begin(SSD1306_SWITCHCAPVCC, 0x3C); } void loop() { display.clearDisplay(); // 更新球的位置 ballX += ballSpeedX; ballY += ballSpeedY; // 边界检测与反弹 if(ballX <= ballRadius || ballX >= SCREEN_WIDTH-ballRadius) { ballSpeedX = -ballSpeedX; } if(ballY <= ballRadius || ballY >= SCREEN_HEIGHT-ballRadius) { ballSpeedY = -ballSpeedY; } // 绘制球体 display.fillCircle(ballX, ballY, ballRadius, SSD1306_WHITE); display.display(); delay(20); // 控制动画速度 }3.2 添加物理效果
为了让动画更真实,可以引入简单的物理模拟:
- 重力加速度:让球体下落时加速
- 能量损失:每次碰撞后速度略微减小
- 随机扰动:增加运动的不确定性
改进后的物理模型代码片段:
float gravity = 0.1; float damping = 0.95; // 能量损失系数 void loop() { display.clearDisplay(); // 应用重力 ballSpeedY += gravity; // 更新位置 ballX += ballSpeedX; ballY += ballSpeedY; // 边界碰撞处理 if(ballY >= SCREEN_HEIGHT-ballRadius) { ballY = SCREEN_HEIGHT-ballRadius; ballSpeedY = -ballSpeedY * damping; // 添加随机水平扰动 ballSpeedX += random(-2, 3); } if(ballX <= ballRadius || ballX >= SCREEN_WIDTH-ballRadius) { ballSpeedX = -ballSpeedX * damping; } display.fillCircle(ballX, ballY, ballRadius, SSD1306_WHITE); display.display(); delay(20); }4. 构建迷你数字时钟动画
4.1 基础时钟实现
结合时间功能和简单动画,可以创建一个更实用的桌面时钟:
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include "RTClib.h" RTC_DS3231 rtc; Adafruit_SSD1306 display(128, 64, &Wire); void setup() { display.begin(SSD1306_SWITCHCAPVCC, 0x3C); if (!rtc.begin()) { while (1); } if (rtc.lostPower()) { rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); } } void loop() { DateTime now = rtc.now(); display.clearDisplay(); // 绘制时钟背景 display.drawRect(10, 10, 108, 44, SSD1306_WHITE); // 显示时间 display.setTextSize(2); display.setCursor(20, 20); if(now.hour() < 10) display.print("0"); display.print(now.hour()); display.print(":"); if(now.minute() < 10) display.print("0"); display.print(now.minute()); display.print(":"); if(now.second() < 10) display.print("0"); display.print(now.second()); // 显示日期 display.setTextSize(1); display.setCursor(20, 45); display.print(now.year()); display.print("/"); display.print(now.month()); display.print("/"); display.print(now.day()); display.display(); delay(200); }4.2 添加动画元素
让静态的时钟变得生动起来:
- 秒针动画:用动态弧线表示秒针移动
- 时间数字变化效果:数字变化时添加过渡动画
- 背景元素:随时间的天气图标或装饰元素
改进后的动画时钟代码片段:
void loop() { DateTime now = rtc.now(); static uint8_t lastSecond = 61; display.clearDisplay(); // 绘制动态秒针 float angle = map(now.second(), 0, 60, 0, 360) - 90; int centerX = 64, centerY = 32, radius = 20; int endX = centerX + radius * cos(angle * DEG_TO_RAD); int endY = centerY + radius * sin(angle * DEG_TO_RAD); display.drawCircle(centerX, centerY, radius, SSD1306_WHITE); display.drawLine(centerX, centerY, endX, endY, SSD1306_WHITE); // 数字变化动画 if(now.second() != lastSecond) { // 添加数字变化效果 display.fillRect(40, 10, 48, 20, SSD1306_BLACK); lastSecond = now.second(); } // 显示数字时间 display.setTextSize(2); display.setCursor(40, 10); if(now.hour() < 10) display.print("0"); display.print(now.hour()); display.print(":"); if(now.minute() < 10) display.print("0"); display.print(now.minute()); display.display(); delay(50); }5. 进阶创意项目:交互式粒子系统
5.1 粒子系统基础
粒子系统可以创造出更复杂的视觉效果。每个粒子都有位置、速度和生命周期等属性:
struct Particle { float x, y; // 位置 float vx, vy; // 速度 uint8_t life; // 生命周期 uint8_t radius; // 大小 }; #define MAX_PARTICLES 50 Particle particles[MAX_PARTICLES];5.2 粒子系统实现
完整的粒子系统实现代码:
#include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define MAX_PARTICLES 30 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire); struct Particle { float x, y; float vx, vy; uint8_t life; uint8_t radius; }; Particle particles[MAX_PARTICLES]; void setup() { display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // 初始化粒子 for(int i=0; i<MAX_PARTICLES; i++) { resetParticle(i); } } void resetParticle(int index) { particles[index].x = random(SCREEN_WIDTH); particles[index].y = random(SCREEN_HEIGHT); particles[index].vx = random(-2.0, 2.0); particles[index].vy = random(-2.0, 2.0); particles[index].life = random(50, 200); particles[index].radius = random(1, 4); } void loop() { display.clearDisplay(); // 更新并绘制所有粒子 for(int i=0; i<MAX_PARTICLES; i++) { if(--particles[i].life <= 0) { resetParticle(i); } particles[i].x += particles[i].vx; particles[i].y += particles[i].vy; // 边界检查 if(particles[i].x < 0 || particles[i].x >= SCREEN_WIDTH) { particles[i].vx = -particles[i].vx; } if(particles[i].y < 0 || particles[i].y >= SCREEN_HEIGHT) { particles[i].vy = -particles[i].vy; } // 绘制粒子 display.fillCircle(particles[i].x, particles[i].y, particles[i].radius, SSD1306_WHITE); } display.display(); delay(30); }5.3 添加交互功能
通过添加传感器,可以让粒子系统响应外部输入。例如,使用加速度计控制重力方向:
// 假设使用MPU6050加速度计 #include "MPU6050.h" MPU6050 mpu; void setup() { // ...其他初始化代码... mpu.initialize(); } void loop() { // 获取加速度数据 int16_t ax, ay, az; mpu.getAcceleration(&ax, &ay, &az); // 应用加速度到粒子 for(int i=0; i<MAX_PARTICLES; i++) { particles[i].vx += ax * 0.0001; particles[i].vy += ay * 0.0001; // ...其余粒子更新代码... } // ...其余绘制代码... }在实际项目中,我发现粒子系统的性能优化至关重要。当粒子数量较多时,可以尝试以下优化:减少粒子半径变化范围、简化碰撞检测、或者降低刷新频率。经过测试,在Arduino UNO上,30-50个粒子能够保持流畅的动画效果。
