Neopixel-Feuereffekt für das SilentBase 802 - Programmversion 4

Nachdem in Programmversion 3 der Fokus auf der Beschleunigung des Codes lag, spielt Version 4 mit der Möglichkeit der FastLED-Bibliothek, Farbpaletten hinzuzufügen.

// Beleuchtung BeQuiet SilentBase 802
// Arduino Nano (Every)

//------------------------- Eingebundene Bibliotheken ---------------------//
#include <OneWire.h>                            // Bibliothek für die Kommunikation über OneWire
#include <DallasTemperature.h>                  // Bibliothek für die digitalen Temperatursensoren DS18B20
#include <FastLED.h>                            // Bibliothek für die NeoPixel

// Debug-Level
//#define DEBUG_EFFECT                            // Ausgabe sehr vieler Daten an die serielle Schnittstelle
//#define DEBUG_SENSOR                            // Ausgabe der Sensordaten und Betriebszustände an die serielle Schnittstelle

//------------------------- Definition der Inputs und Outputs ---------------------//
#define NEOPIN1       2                         // NeoPixel-Strip rechte Seite
#define NEOPIN2       4                         // NeoPixel-Strip linke Seite
#define POWERPIN      3                         // Schaltet über den MOSFET die NeoPixel-Streifen ein
#define LEDPIN1       6                         // LED 1
#define LEDPIN2       8                         // LED 2
#define ONE_WIRE_BUS 11                         // Datenleitung für die Temperatursensoren DS18B20

//------------------------- Definition der NeoPixel-Streifen ---------------------//
#define COLOR_ORDER GRB            
#define CHIPSET     WS2812B
#define NUM_LEDS    71
#define BRIGHTNESS  200

//-------------------------- Definition der Auflösung der Temperatursensoren ----//
// 9 bit resolution: 0,5°C increments, takes 94 ms for A/D conversion
// 10 bit resolution: 0,25°C increments, takes 188 ms for A/D conversion
// 11 bit resolution: 0,125°C increments, takes 375 ms for A/D conversion
// 12 bit resolution: 0,0625°C increments, takes 750 ms for A/D conversion
#define TEMPERATURE_PRECISION 11

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

// arrays to hold device addresses
DeviceAddress sensor0, sensor1, sensor2;

// Definiert die Variablen
bool gReverseDirection = true;                  // Kehrt den Effekt um, wenn wahr

float temperature;                              // Die höchste an den drei Sensoren gemessene Temperatur
float temperature_min = 20;                     // Konfiguriert den unteren Grenzwert der Gehäusetemperatur
float temperature_max = 40;                     // Konfiguriert den oberen Grenzwert der Gehäusetemperatur

boolean LEDstate;
boolean POWERstate = 0;                         // Wird "1" sobald der Loop aufgerufen wird

int cooling;                                    // Variable für Beleuchtungseffekt Flammen
int sparking;                                   // Variable für Beleuchtungseffekt Flammen
static byte heat[71];                           // Ein Array für die Temperaturwerte
  
// Definiert die NeoPixel-Strips
CRGB strip[NUM_LEDS];

// Fire2012 with programmable Color Palette
//
// This code is the same fire simulation as the original "Fire2012",
// but each heat cell's temperature is translated to color through a FastLED
// programmable color palette, instead of through the "HeatColor(...)" function.
//
// Four different static color palettes are provided here, plus one dynamic one.
// 
// The three static ones are: 
//   1. the FastLED built-in HeatColors_p -- this is the default, and it looks
//      pretty much exactly like the original Fire2012.
//
//  To use any of the other palettes below, just "uncomment" the corresponding code.
//
//   2. a gradient from black to red to yellow to white, which is
//      visually similar to the HeatColors_p, and helps to illustrate
//      what the 'heat colors' palette is actually doing,
//   3. a similar gradient, but in blue colors rather than red ones,
//      i.e. from black to blue to aqua to white, which results in
//      an "icy blue" fire effect,
//   4. a simplified three-step gradient, from black to red to white, just to show
//      that these gradients need not have four components; two or
//      three are possible, too, even if they don't look quite as nice for fire.
//
// The dynamic palette shows how you can change the basic 'hue' of the
// color palette every time through the loop, producing "rainbow fire".

CRGBPalette16 gPal;

// Definiert die Tracking-Variablen für die IF-Abfragen
unsigned long previousMillisSensors = 0;
unsigned long previousMillisLED = 0;
unsigned long previousMillisEffect = 0;
unsigned long previousMillisSerialPrint = 0;

// Definiert die Intervalle für die IF-Abfragen
int intervalSensors = 1000;                     // Delay für Auslesen der Temperatursensoren
int intervalLED = 500;                          // Delay für die Ausgabe der Temperatur als Blinkfrequenz der LED2
int intervalEffect = 15;                        // Delay für Effekte
int intervalSerialPrint = 1000;                 // Delay für serielle Ausgabe


//-------------------------- Setup ------------------------------------------------------------------//
void setup() {
  digitalWrite(POWERPIN, LOW);                  // Schaltet den MOSFET aus
  Serial.begin(115200);
  
  // Initialisiere die Output-Pins
  pinMode(NEOPIN1, OUTPUT);
  pinMode(NEOPIN2, OUTPUT);
  pinMode(POWERPIN, OUTPUT);
  pinMode(LEDPIN1, OUTPUT);
  pinMode(LEDPIN2, OUTPUT);
  
 
  // Initialisiere die NeoPixel-Strips
  digitalWrite(POWERPIN, HIGH);                 // Schaltet den MOSFET ein

  FastLED.addLeds<CHIPSET, NEOPIN1, COLOR_ORDER>(strip, NUM_LEDS).setCorrection( TypicalLEDStrip );
  FastLED.addLeds<CHIPSET, NEOPIN2, COLOR_ORDER>(strip, NUM_LEDS).setCorrection( TypicalLEDStrip );
  FastLED.setBrightness( BRIGHTNESS ); 
  
  // This first palette is the basic 'black body radiation' colors,
  // which run from black to red to bright yellow to white.
  gPal = HeatColors_p;
  
  // These are other ways to set up the color palette for the 'fire'.
  // First, a gradient from black to red to yellow to white -- similar to HeatColors_p
  //   gPal = CRGBPalette16( CRGB::Black, CRGB::Red, CRGB::Yellow, CRGB::White);
  
  // Second, this palette is like the heat colors, but blue/aqua instead of red/yellow
  //   gPal = CRGBPalette16( CRGB::Black, CRGB::Blue, CRGB::Aqua,  CRGB::White);
  
  // Third, here's a simpler, three-step gradient, from black to red to white
  //   gPal = CRGBPalette16( CRGB::Black, CRGB::Red, CRGB::White);

  // Start up the sensor library
  sensors.begin();

  // locate devices on the bus
  Serial.print("Locating devices...");
  Serial.print("Found ");
  Serial.print(sensors.getDeviceCount(), DEC);
  Serial.println(" devices.");

  // Search for devices on the bus and assign based on an index. Ideally,
  // you would do this to initially discover addresses on the bus and then
  // use those addresses and manually assign them (see above) once you know
  // the devices on your bus (and assuming they don't change).
  //
  // method 1: by index
  if (!sensors.getAddress(sensor0, 0)) Serial.println("Unable to find address for Device 0");
  if (!sensors.getAddress(sensor1, 1)) Serial.println("Unable to find address for Device 1");
  if (!sensors.getAddress(sensor2, 2)) Serial.println("Unable to find address for Device 2");

  // show the addresses we found on the bus
  Serial.print("Device 0 Address: ");
  printAddress(sensor0);
  Serial.println();

  Serial.print("Device 1 Address: ");
  printAddress(sensor1);
  Serial.println();

  Serial.print("Device 2 Address: ");
  printAddress(sensor2);
  Serial.println();
  
  // set the resolution to 11 bit per device
  sensors.setResolution(sensor0, TEMPERATURE_PRECISION);
  sensors.setResolution(sensor1, TEMPERATURE_PRECISION);
  sensors.setResolution(sensor2, TEMPERATURE_PRECISION);

  #ifdef DEBUG_SENSOR
  Serial.print("Device 0 Resolution: ");
  Serial.println(sensors.getResolution(sensor0), DEC);

  Serial.print("Device 1 Resolution: ");
  Serial.println(sensors.getResolution(sensor1), DEC);

  Serial.print("Device 2 Resolution: ");
  Serial.println(sensors.getResolution(sensor2), DEC);
  #endif
  
  delay (2000);
}

//-------------------------- Loop -------------------------------------------------------------------//
void loop() {
  // Aktuelle Zeit abfragen
  unsigned long currentMillis = millis();

  // Schaltet die NeoPixel ein und initialisiert die NeoPixel
  if(POWERstate == 0) {
    POWERstate = 1;
    digitalWrite(LEDPIN1, HIGH);                   // Schaltet LED1 ein
  }

  // Auslesen der Temperatursensoren und Berechnen einiger Variablen zur Beeinflussung des Effekts
  if ((unsigned long)(currentMillis - previousMillisSensors) >= intervalSensors) {
  
    // Request to all devices on the bus
    //temperature conversion - non-blocking / async
    unsigned long start = micros();       
    sensors.setWaitForConversion(false);  // makes it async
    sensors.requestTemperatures();
    sensors.setWaitForConversion(true);
    unsigned long stop = micros();
    
    #ifdef DEBUG_SENSOR
    Serial.print("Time used for reading sensors: ");
    Serial.print(stop - start);
    Serial.println(" microseconds");

    // print the device information
    printData(sensor0);
    printData(sensor1);
    printData(sensor2);
    #endif
    
    // Berechnen der höchsten Temperatur an den verfügbaren Sensoren
    float tempSensor0 = sensors.getTempC(sensor0);
    float tempSensor1 = sensors.getTempC(sensor1);
    float tempSensor2 = sensors.getTempC(sensor2);
    temperature = max(tempSensor0, tempSensor1);
    temperature = max(temperature, tempSensor2);

    #ifdef DEBUG_SENSOR
    Serial.print("Highest temperature: ");
    Serial.print(temperature);
    Serial.println(" °C");
    #endif
    
    // Berechenen der Blinkfrequenz von LED2
    constrain(temperature, temperature_min, temperature_max);
    intervalLED = map(temperature, temperature_min, temperature_max, 500, 100);
    #ifdef DEBUG_SENSOR
    Serial.print("Blink frequency LED2: "); Serial.print(intervalLED); Serial.println(" ms");
    #endif
    
    // Berechnung von Cooling für den Effekt Fire 2012: Legt fest, wie stark die aufsteigenden Flammen abkühlen.
    // Weniger Cooling = höhere Flammen, mehr Cooling = kürzere Flammen.
    // Werte zwischen 20 und 100 sind empfohlen, ein guter Standard ist 55.
    cooling = map(temperature, temperature_min, temperature_max, 100, 40);

    // Berechnung von Sparking für den Effekt Fire2012: Legt fest, wie oft ein Funke auflohdert.
    // Höhere Werte = stürmisches Feuer, niedrige Werte = ruhigeres Feuer.
    // Werte zwischen 50 und 200 sind empfohlen, ein guter Standard ist 120.
    sparking = map(temperature, temperature_min, temperature_max, 30, 100);

    #ifdef DEBUG_SENSOR
    //Serial.print("cooling: ");
    Serial.print(cooling); Serial.print("\t");
    //Serial.print("sparkling: ");
    Serial.println(sparking);
    #endif
    
  //Speichere die aktuelle Zeit in die zughörige Variable
  previousMillisSensors = currentMillis;
  }


  // Ausgabe der Temperatur als Blinkfrequenz der LED1
  if ((unsigned long)(currentMillis - previousMillisLED) >= intervalLED) {

    if(LEDstate == 0) {
      digitalWrite(LEDPIN2, HIGH);
      //Serial.println("LED an");
      LEDstate = 1;
    }
    else if(LEDstate == 1) {
      digitalWrite(LEDPIN2, LOW);
      //Serial.println("LED aus");
      LEDstate = 0;
    }
    
    
  //Speichere die aktuelle Zeit in die zughörige Variable
  previousMillisLED = currentMillis;
  }

  // Feuer-Effekt: Fire2012 by Mark Kriegsman, July 2012
  if ((unsigned long)(currentMillis - previousMillisEffect) >= intervalEffect) {

    #ifdef DEBUG_EFFECT
    unsigned long start = micros();                                     // Zeitstempel Start
    #endif
    
    // Add entropy to random number generator; we use a lot of it.
    random16_add_entropy( random());
  
    // This basic one-dimensional 'fire' simulation works roughly as follows:
    // There's a underlying array of 'heat' cells, that model the temperature
    // at each point along the line.  Every cycle through the simulation, 
    // four steps are performed:
    //  1) All cells cool down a little bit, losing heat to the air
    //  2) The heat from each cell drifts 'up' and diffuses a little
    //  3) Sometimes randomly new 'sparks' of heat are added at the bottom
    //  4) The heat from each cell is rendered as a color into the leds array
    //     The heat-to-color mapping uses a black-body radiation approximation.
    // Temperature is in arbitrary units from 0 (cold black) to 255 (white hot).

    // Step 1.  Cool down every cell a little
    for( int i = 0; i < NUM_LEDS; i++) {
      heat[i] = qsub8( heat[i],  random8(0, ((cooling * 10) / NUM_LEDS) + 2));
    }
  
    // Step 2.  Heat from each cell drifts 'up' and diffuses a little
    for( int k= NUM_LEDS - 1; k >= 2; k--) {
      heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2] ) / 3;
    }
    
    // Step 3.  Randomly ignite new 'sparks' of heat near the bottom
    if( random8() < sparking ) {
      int y = random8(7);
      heat[y] = qadd8( heat[y], random8(160,255) );
    }

    // Step 4.  Map from heat cells to LED colors
    for( int j = 0; j < NUM_LEDS; j++) {
      // Scale the heat value from 0-255 down to 0-240
      // for best results with color palettes.
      uint8_t colorindex = scale8( heat[j], 240);
      CRGB color = ColorFromPalette( gPal, colorindex);
      int pixelnumber;
      if( gReverseDirection ) {
        pixelnumber = (NUM_LEDS-1) - j;
      } else {
        pixelnumber = j;
      }
      strip[pixelnumber] = color;
      //strip2[pixelnumber] = color;
    }
    FastLED.show(); // display this frame
  
    #ifdef DEBUG_EFFECT
    unsigned long stop = micros();                                    // Zeitstempel Stopp
    // Ausgabe der Zeit in Microsekunden, die für die Berechnung des Effekts benötigt wird
    //Serial.print("Time used for calculating effect: ");
    Serial.println(stop - start);
    //Serial.println(" microseconds");
    #endif
    
  //Speichere die aktuelle Zeit in die zughörige Variable
  previousMillisEffect = currentMillis;
  }
}

//-------------------------- Funktionen --------------------------------------//
// function to print a device address
void printAddress(DeviceAddress deviceAddress)
{
  for (uint8_t i = 0; i < 8; i++)
  {
    // zero pad the address if necessary
    if (deviceAddress[i] < 16) Serial.print("0");
    Serial.print(deviceAddress[i], HEX);
  }
}

// function to print the temperature for a device
void printTemperature(DeviceAddress deviceAddress)
{
  float tempC = sensors.getTempC(deviceAddress);
  Serial.print("Temp C: ");
  Serial.print(tempC);
}

// function to print a device's resolution
void printResolution(DeviceAddress deviceAddress)
{
  Serial.print("Resolution: ");
  Serial.print(sensors.getResolution(deviceAddress));
  Serial.println();
}

// main function to print information about a device
void printData(DeviceAddress deviceAddress)
{
  Serial.print("Device Address: ");
  printAddress(deviceAddress);
  Serial.print(" ");
  printTemperature(deviceAddress);
  Serial.println();
}