Aurora Pulse Orb

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.

Bill of Materials

Wiring Map

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.

Build Steps

  1. Prepare the power rail. Tin the 5V and GND pads on the ring, solder the 470 µF capacitor directly between them (watch polarity), and bring two wires back to the Nano’s 5V/GND pins.
  2. Mount sensing hardware. Fix the BME280 near the airflow (not sealed inside the globe). Route SDA/SCL to A4/A5 and keep the wires twisted to reduce noise.
  3. Add the touch pad. Stick a copper disc to the base, solder a single wire to D3, and add the 100 kΩ pull-down from D3 to GND on perfboard so the input stays LOW when untouched.
  4. Wire the NeoPixel data line. Place the 330 Ω resistor inline between D6 and DIN. Secure the wire to relieve strain so the first LED pad does not lift.
  5. Load required libraries. In the Arduino IDE open Library Manager and install “Adafruit BME280 Library” (which also installs Adafruit Unified Sensor) and “Adafruit NeoPixel”.
  6. Flash the sketch and test. Upload the code below. Open Serial Monitor at 115200 baud to verify readings. Tap the copper pad: LEDs should freeze until you tap again.

Sketch

#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));
}

Test & Tuning

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.

Troubleshooting. Random flashing usually means the NeoPixel ring sees voltage dip; add the bulk capacitor and keep USB cables short. If Serial reports `Could not find a valid BME280 sensor!`, confirm the address (0x76 vs 0x77) and re-solder SDA/SCL.