RGB thermometer with ATTiny85 and WS2812

Below is a concise description of how the sketch turns an ATtiny85 + WS2812 strip into a temperature‐sensing thermometer with ambient‐light–adjusted brightness.

  • Hardware Setup (fully configurable in code)
    • WS2812 strip on PB0 → DATA. You choose NUM_LEDS (e.g. 60) and color order (GRB).
    • Thermistor divider on PB2 → NTC. You set the fixed resistor value (R_SERIES), the NTC’s nominal resistance (R_NOMINAL), nominal temperature (T_NOMINAL), and B-coefficient (B_COEFF).
    • Photoresistor (LDR) divider on PB3 → LDR. You configure the series resistor (R_LDR_SERIES), nominal lux (LUX_NOMINAL), and LDR gamma (LDR_GAMMA).
    • Power rails: VCC = +5 V, GND = 0 V.

  • ADC Reference
    • Uses analogReference(DEFAULT) so ADC measures 0…VCC.
    • All voltage-conversion factors use the VCC constant (e.g. 5.0 V).
  • Temperature Measurement (fully parameterized)
    • analogRead(NTC_PIN) → voltage → resistance via R_SERIES.
    • Steinhart–Hart B-parameter equation with your R_NOMINALT_NOMINALB_COEFF to compute °C.
    • Normalizes between T_MIN and T_MAX into a 0.0…1.0 fraction.
  • LED Mapping (user-settable)
    • Computes LED index: round(fraction * (NUM_LEDS - 1)).
    • Picks hue: interpolates between HUE_LOW (cold) and HUE_HIGH (hot).
  • Ambient-Light & Brightness (fully adjustable)
    • analogRead(LDR_PIN) → voltage → resistance via R_LDR_SERIES.
    • Converts to lux using your LUX_NOMINAL and LDR_GAMMA.
    • Constrains lux to [LUX_MIN, LUX_MAX] and maps to brightness [BRIGHT_MIN, BRIGHT_MAX].
    • Disables “glow” when lux exceeds LUX_GLOW_OFF.
  • Glow Effect (configurable)
    • Radius GLOW_SIZE LEDs on each side of the main “thermometer” LED.
    • Fall-off per step via GLOW_FALLOFF (exponential).
  • Update Loop
    • Runs every UPDATE_MS milliseconds (e.g. 5000 ms) to smooth sensor readings and LED updates.

All key parts—LED count, resistor values, thermistor characteristics, lux mapping, glow radius—are declared as constants at the top of the sketch so you can easily adapt this thermometer to different hardware or sensitivity requirements.

#include <FastLED.h>

/* ───────── CONFIGURATION ───────── */

// Choose display units:
//   false → show °C
//   true  → show °F
#define DISPLAY_FAHRENHEIT  false

// LED strip
#define NUM_LEDS    60
#define DATA_PIN    0      // PB0
#define CHIPSET     WS2811
#define COLOR_ORDER GRB

// ADC reference & supply voltage
#define ADC_REF     DEFAULT
const float VCC = 5.0f;

// Thermistor (10 kΩ NTC) on PB2 (A1)
#define NTC_PIN     A1
const float R_SERIES  = 10000.0f;
const float R_NOMINAL = 10000.0f;
const float T_NOMINAL =    25.0f;
const float B_COEFF   =  3950.0f;

// Temperature range for color mapping
// These are in °C; if DISPLAY_FAHRENHEIT is true,
// they’ll be converted to °F automatically.
const float   T_MIN    = -15.0f;  // bottom of strip
const float   T_MAX    =  40.0f;  // top of strip
const uint8_t HUE_LOW  = 160;     // green at cold
const uint8_t HUE_HIGH =   0;     // red at hot

// Ambient‐light sensor (LDR) on PB3 (A3)
#define LDR_PIN        A3
const float R_LDR_SERIES  = 10000.0f;
const float R_LDR_NOMINAL = 10000.0f;
const float LUX_NOMINAL   =    10.0f;
const float LDR_GAMMA     =     0.8f;

// Brightness mapping
const float   LUX_MIN      =    0.0f;
const float   LUX_MAX      =  500.0f;
const uint8_t BRIGHT_MIN   =    5;
const uint8_t BRIGHT_MAX   =  255;
const float   LUX_GLOW_OFF =   60.0f;

// Glow effect
const uint8_t GLOW_SIZE    = 1;
const float   GLOW_FALLOFF = 0.08f;

// Update interval
const unsigned long UPDATE_MS = 5000;

CRGB leds[NUM_LEDS];
unsigned long lastUpdate = 0;

void setup() {
  analogReference(ADC_REF);
  FastLED.addLeds<CHIPSET, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);
  FastLED.setBrightness(BRIGHT_MAX);
  FastLED.clear();
  FastLED.show();
}

void loop() {
  if (millis() - lastUpdate < UPDATE_MS) return;
  lastUpdate = millis();

  // 1) Read temperature in °C
  int rawT = analogRead(NTC_PIN);
  float vT  = rawT * (VCC / 1023.0f);
  float rT  = R_SERIES * (VCC / vT - 1.0f);
  float inv = 1.0f / (T_NOMINAL + 273.15f) + log(rT / R_NOMINAL) / B_COEFF;
  float tempC = 1.0f / inv - 273.15f;

  // 2) Possibly convert to °F
  float temp = DISPLAY_FAHRENHEIT
               ? (tempC * 9.0f / 5.0f + 32.0f)
               : tempC;

  // 3) Determine mapping range
  float minT = T_MIN;
  float maxT = T_MAX;
  if (DISPLAY_FAHRENHEIT) {
    minT = T_MIN * 9.0f / 5.0f + 32.0f;
    maxT = T_MAX * 9.0f / 5.0f + 32.0f;
  }

  // 4) Normalize temperature to [0…1]
  float f = constrain((temp - minT) / (maxT - minT), 0.0f, 1.0f);

  // 5) Read ambient light & set brightness/glow
  int rawL = analogRead(LDR_PIN);
  float vL   = rawL * (VCC / 1023.0f);
  float rLDR = R_LDR_SERIES * (VCC / vL - 1.0f);
  float lux  = LUX_NOMINAL * powf(rLDR / R_LDR_NOMINAL, -1.0f / LDR_GAMMA);
  lux = constrain(lux, LUX_MIN, LUX_MAX);

  uint8_t globalB = map(int(lux * 10),
                        int(LUX_MIN * 10), int(LUX_MAX * 10),
                        BRIGHT_MIN, BRIGHT_MAX);
  FastLED.setBrightness(globalB);

  uint8_t glow = (lux < LUX_GLOW_OFF) ? GLOW_SIZE : 0;

  // 6) Draw thermometer bar
  FastLED.clear();
  int idx = int(round(f * (NUM_LEDS - 1)));
  uint8_t hue = uint8_t(round(HUE_LOW + f * (HUE_HIGH - HUE_LOW)));

  for (int d = -int(glow); d <= int(glow); d++) {
    int i = idx + d;
    if (i < 0 || i >= NUM_LEDS) continue;
    float scale = powf(GLOW_FALLOFF, fabsf((float)d));
    leds[i] = CHSV(hue, 255, 255);
    leds[i].nscale8_video(uint8_t(scale * 255));
  }
  FastLED.show();
}


Leave a Reply