This desk sculpture floats between art piece and instrumentation. A BME280 sensor samples temperature, humidity, and pressure; the readings animate a 24-pixel NeoPixel ring plus a single copper pad for tap-to-freeze interaction.
| Connection | Arduino Pin | Notes |
|---|---|---|
| BME280 VIN | 5V | Module tolerates 3.3–5 V; keep leads short. |
| BME280 GND | GND | Common ground between every module. |
| BME280 SDA | A4 | I²C data; enable pull-ups on board (default). |
| BME280 SCL | A5 | I²C clock. |
| NeoPixel 5V | 5V (after bulk cap) | Solder 470 µF across 5V/GND near the ring. |
| NeoPixel GND | GND | Must be the same ground as the Nano. |
| NeoPixel DIN | D6 via 330 Ω | Series resistor protects first LED. |
| Touch pad | D3 | Connect pad to D3, and 100 kΩ from D3 to GND. |
#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
constexpr uint8_t PIXEL_PIN = 6;
constexpr uint8_t TOUCH_PIN = 3;
constexpr uint16_t PIXELS = 24;
Adafruit_NeoPixel strip(PIXELS, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_BME280 bme;
bool hold = false;
unsigned long lastBlend = 0;
void setup() {
pinMode(TOUCH_PIN, INPUT);
strip.begin();
strip.show();
Wire.begin();
if (!bme.begin(0x76)) {
while (true) {
for (uint16_t i = 0; i < PIXELS; i++) {
strip.setPixelColor(i, strip.Color(255, 0, 0));
}
strip.show();
delay(100);
}
}
}
void loop() {
static bool lastTouch = false;
bool touch = digitalRead(TOUCH_PIN) == HIGH;
if (touch && !lastTouch) hold = !hold;
lastTouch = touch;
if (!hold && millis() - lastBlend > 80) {
lastBlend = millis();
float temp = bme.readTemperature();
float hum = bme.readHumidity();
float pres = bme.readPressure() / 100.0F;
renderRing(temp, hum, pres);
}
}
void renderRing(float temp, float hum, float pres) {
float hueBase = constrain(mapFloat(temp, 18, 32, 180, 20), 20, 180);
float brightness = constrain(mapFloat(hum, 30, 80, 80, 255), 80, 255);
float sparkle = constrain(mapFloat(pres, 980, 1025, 0.2, 0.9), 0.2, 0.9);
for (uint16_t i = 0; i < PIXELS; i++) {
float offset = (float)i / PIXELS;
float hue = fmod(hueBase + offset * 120.0, 360.0);
uint32_t color = hsvToRgb(hue, sparkle, brightness);
strip.setPixelColor(i, color);
}
strip.show();
}
float mapFloat(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
uint32_t hsvToRgb(float h, float s, float v) {
s = constrain(s, 0.0, 1.0);
v = constrain(v, 0.0, 255.0);
float c = (v / 255.0) * s;
float x = c * (1 - fabsf(fmod(h / 60.0, 2) - 1));
float m = (v / 255.0) - c;
float r, g, b;
if (h < 60) { r = c; g = x; b = 0; }
else if (h < 120) { r = x; g = c; b = 0; }
else if (h < 180) { r = 0; g = c; b = x; }
else if (h < 240) { r = 0; g = x; b = c; }
else if (h < 300) { r = x; g = 0; b = c; }
else { r = c; g = 0; b = x; }
return strip.Color((uint8_t)((r + m) * 255), (uint8_t)((g + m) * 255), (uint8_t)((b + m) * 255));
}
Wave a warm mug near the sensor or breathe gently to see humidity spikes roll around the ring. Adjust the `mapFloat` ranges if your lab runs colder or hotter so the gradients keep good contrast.