EmoPot:会“表达情绪”的智能花盆,可视化植物健康状态
组件与物料
| Arduino Nano R3 | ×1 |
| DFRobot Gravity: 土壤湿度传感器 (模拟量输出) | ×1 |
| 0.96″ I2C OLED 显示屏 | ×1 |
所需工具
- 烙铁 (通用)
- 3D 打印机 (通用)
应用与在线服务
- Arduino IDE
项目简介
本项目制作一个能“表达情绪”的智能花盆 😜。通过土壤湿度传感器监测含水量,在 OLED 上显示对应情绪表情。植物需要水分如同我们需要食物,所以这种拟人化表达很有道理 😋。这是一个有趣的小项目,能直观监控花盆湿度。
花盆根据水位显示不同情绪表情。
完成后你将学会:
- 读取土壤湿度传感器模拟量 (Arduino ADC)
- 在 OLED 上显示图片/动画
- 用 Arduino 实现 OLED 动画播放
硬件部分
花盆结构设计
先要有花盆。我用 Fusion 360 设计了带电子仓的花盆,有 3D 打印机可自行打印;没有也没关系,任意花盆加个电子盒即可。

电子连接:
- Arduino A5 → OLED SCL
- Arduino A4 → OLED SDA
- Arduino A0 → 土壤湿度传感器 AO
编程部分
代码看似繁杂实则简单。开头引入库 (可能需手动安装):
Adafruit_GFX.hAdafruit_SSD1306.h
为每种情绪表情的每一帧动画编写函数 (相信我这非常耗时)。setup() 初始化显示屏。loop() 中 sensorValue = analogRead(A0); 读取传感器,根据湿度阈值分段播放对应情绪动画帧。
不同湿度下的情绪表情演示
(视频演示)
完整代码
#include
#include
#include
#include
#include
#define OLED_RESET 0
Adafruit_SSD1306 display(OLED_RESET);
int sensorValue = 0;
int xx = 40;
/****************************************** SAD (难过) ****************************************************/
// 12 帧难过动画帧数据 (frame01 ~ frame121),每帧 128×64 位图数组
// 此处省略冗长数组,完整代码见附件下载
const unsigned char PROGMEM frame01 [] = { ... };
const unsigned char PROGMEM frame011 [] = { ... };
const unsigned char PROGMEM frame21 [] = { ... };
const unsigned char PROGMEM frame31 [] = { ... };
const unsigned char PROGMEM frame41 [] = { ... };
const unsigned char PROGMEM frame51 [] = { ... };
const unsigned char PROGMEM frame61 [] = { ... };
const unsigned char PROGMEM frame71 [] = { ... };
const unsigned char PROGMEM frame81 [] = { ... };
const unsigned char PROGMEM frame91 [] = { ... };
const unsigned char PROGMEM frame101 [] = { ... };
const unsigned char PROGMEM frame111 [] = { ... };
const unsigned char PROGMEM frame121 [] = { ... };
// const unsigned char PROGMEM frame131 [] = { ... };
/************************************************* SMILE (微笑) *************************************************/
const unsigned char PROGMEM frame02 [] = { ... };
const unsigned char PROGMEM frame012 [] = { ... };
const unsigned char PROGMEM frame22 [] = { ... };
const unsigned char PROGMEM frame32 [] = { ... };
const unsigned char PROGMEM frame42 [] = { ... };
const unsigned char PROGMEM frame52 [] = { ... };
const unsigned char PROGMEM frame62 [] = { ... };
const unsigned char PROGMEM frame72 [] = { ... };
const unsigned char PROGMEM frame82 [] = { ... };
const unsigned char PROGMEM frame92 [] = { ... };
const unsigned char PROGMEM frame102 [] = { ... };
const unsigned char PROGMEM frame112 [] = { ... };
const unsigned char PROGMEM frame122 [] = { ... };
const unsigned char PROGMEM frame132 [] = { ... };
/************************************** NEUTRAL (平静) ***************************************************/
const unsigned char PROGMEM frame03 [] = { ... };
const unsigned char PROGMEM frame013 [] = { ... };
const unsigned char PROGMEM frame23 [] = { ... };
const unsigned char PROGMEM frame33 [] = { ... };
const unsigned char PROGMEM frame43 [] = { ... };
const unsigned char PROGMEM frame53 [] = { ... };
const unsigned char PROGMEM frame63 [] = { ... };
const unsigned char PROGMEM frame73 [] = { ... };
const unsigned char PROGMEM frame83 [] = { ... };
/*************************************** BLUSHING (害羞) **************************************************************/
const unsigned char PROGMEM frame04 [] = { ... };
const unsigned char PROGMEM frame14 [] = { ... };
const unsigned char PROGMEM frame24 [] = { ... };
const unsigned char PROGMEM frame34 [] = { ... };
const unsigned char PROGMEM frame44 [] = { ... };
const unsigned char PROGMEM frame54 [] = { ... };
const unsigned char PROGMEM frame64 [] = { ... };
const unsigned char PROGMEM frame74 [] = { ... };
const unsigned char PROGMEM frame84 [] = { ... };
const unsigned char PROGMEM frame94 [] = { ... };
/**************************************************** CRYING (哭泣) **********************************************/
const unsigned char PROGMEM frame05 [] = { ... };
const unsigned char PROGMEM frame15 [] = { ... };
const unsigned char PROGMEM frame25 [] = { ... };
const unsigned char PROGMEM frame35 [] = { ... };
const unsigned char PROGMEM frame45 [] = { ... };
const unsigned char PROGMEM frame55 [] = { ... };
const unsigned char PROGMEM frame65 [] = { ... };
const unsigned char PROGMEM frame75 [] = { ... };
const unsigned char PROGMEM frame85 [] = { ... };
const unsigned char PROGMEM frame95 [] = { ... };
// 困惑表情
const unsigned char PROGMEM framec [] = { ... };
/********************************************************************************************************************************/
void setup() {
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setFont(&FreeSans9pt7b);
display.setTextColor(WHITE);
display.setCursor(0, 0);
}
void loop() {
sensorValue = analogRead(A0); // 读取土壤湿度
if (sensorValue >= 80 && sensorValue < 250) {
// 微笑 - 土壤湿润
display.clearDisplay(); display.drawBitmap(xx, 0, frame02, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame012, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame22, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame32, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame42, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame52, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame62, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame72, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame82, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame92, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame102, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame112, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame122, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame132, 48, 48, 1); display.display(); delay(30);
}
else if (sensorValue >= 250 && sensorValue < 350) {
// 害羞 - 湿度适中偏高
display.clearDisplay(); display.drawBitmap(xx, 0, frame04, 48, 48, 1); display.display(); delay(50);
display.clearDisplay(); display.drawBitmap(xx, 0, frame14, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame24, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame34, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame44, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame54, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame64, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame74, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame84, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame94, 48, 48, 1); display.display(); delay(70);
}
else if (sensorValue >= 350 && sensorValue < 550) {
// 平静 - 理想湿度区间
display.clearDisplay(); display.drawBitmap(xx, 0, frame03, 48, 48, 1); display.display(); delay(60);
display.clearDisplay(); display.drawBitmap(xx, 0, frame013, 48, 48, 1); display.display(); delay(60);
display.clearDisplay(); display.drawBitmap(xx, 0, frame23, 48, 48, 1); display.display(); delay(60);
display.clearDisplay(); display.drawBitmap(xx, 0, frame33, 48, 48, 1); display.display(); delay(60);
display.clearDisplay(); display.drawBitmap(xx, 0, frame43, 48, 48, 1); display.display(); delay(60);
display.clearDisplay(); display.drawBitmap(xx, 0, frame53, 48, 48, 1); display.display(); delay(60);
display.clearDisplay(); display.drawBitmap(xx, 0, frame63, 48, 48, 1); display.display(); delay(60);
display.clearDisplay(); display.drawBitmap(xx, 0, frame73, 48, 48, 1); display.display(); delay(60);
display.clearDisplay(); display.drawBitmap(xx, 0, frame83, 48, 48, 1); display.display(); delay(500);
}
else if (sensorValue >= 550 && sensorValue < 700) {
// 难过 - 缺水
display.clearDisplay(); display.drawBitmap(xx, 0, frame011, 48, 48, 1); display.display(); delay(50);
display.clearDisplay(); display.drawBitmap(xx, 0, frame21, 48, 48, 1); display.display(); delay(70);
display.clearDisplay(); display.drawBitmap(xx, 0, frame31, 48, 48, 1); display.display(); delay(70);
display.clearDisplay(); display.drawBitmap(xx, 0, frame41, 48, 48, 1); display.display(); delay(70);
display.clearDisplay(); display.drawBitmap(xx, 0, frame51, 48, 48, 1); display.display(); delay(70);
display.clearDisplay(); display.drawBitmap(xx, 0, frame61, 48, 48, 1); display.display(); delay(70);
display.clearDisplay(); display.drawBitmap(xx, 0, frame71, 48, 48, 1); display.display(); delay(70);
display.clearDisplay(); display.drawBitmap(xx, 0, frame81, 48, 48, 1); display.display(); delay(70);
display.clearDisplay(); display.drawBitmap(xx, 0, frame91, 48, 48, 1); display.display(); delay(70);
display.clearDisplay(); display.drawBitmap(xx, 0, frame101, 48, 48, 1); display.display(); delay(70);
display.clearDisplay(); display.drawBitmap(xx, 0, frame111, 48, 48, 1); display.display(); delay(70);
display.clearDisplay(); display.drawBitmap(xx, 0, frame121, 48, 48, 1); display.display(); delay(100);
}
else if (sensorValue >= 700 && sensorValue < 800) {
// 哭泣 - 严重缺水
display.clearDisplay(); display.drawBitmap(xx, 0, frame05, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame15, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame25, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame35, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame45, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame55, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame65, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame75, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame85, 48, 48, 1); display.display(); delay(30);
display.clearDisplay(); display.drawBitmap(xx, 0, frame95, 48, 48, 1); display.display(); delay(70);
}
else {
// 困惑 - 传感器异常或超量程
display.clearDisplay(); display.drawBitmap(xx, 0, framec, 48, 48, 1); display.display(); delay(30);
}
}
3D 打印模型
花盆 3D 模型文件见附件。
原理图

制造工艺