别再只用串口打印了!用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引脚GNDGNDVCC3.3V或5VSCLA5SDAA41.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 i0; iMAX_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 i0; iMAX_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 i0; iMAX_PARTICLES; i) { particles[i].vx ax * 0.0001; particles[i].vy ay * 0.0001; // ...其余粒子更新代码... } // ...其余绘制代码... }在实际项目中我发现粒子系统的性能优化至关重要。当粒子数量较多时可以尝试以下优化减少粒子半径变化范围、简化碰撞检测、或者降低刷新频率。经过测试在Arduino UNO上30-50个粒子能够保持流畅的动画效果。