#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
//#define DEBUG true
#ifdef DEBUG
#include <TinyDebug.h>
#endif
// matrix size
#define WIDTH 2
#define HEIGHT 2
#define CentreX (WIDTH >> 1) - 1
#define CentreY (HEIGHT >> 1) - 1
// NUM_LEDS = WIDTH * HEIGHT
#define PIXELPIN 1
#define NUM_LEDS 4
#define LAST_VISIBLE_LED 3
#define BRIGHTNESS 250
#define FIRST_LAYER_MINIMUM 140
#define SECOND_LAYER_MINIMUM 20
#define FIRESPEED 4
#define FLAMEHEIGHT 234 // the higher the value, the higher the flame
#define MOVETACT 2 //every how many cycles the flames shall move up the LED sheet
#define STEP_DELAY_MS 100
uint8_t multscale8(uint8_t i, uint8_t scale);
uint8_t nmultscale8(uint8_t i, uint8_t scale);
// Attiny85 compatible perlin implementation
extern const uint8_t myperm[256];
uint8_t myFade(uint8_t t);
uint8_t mygrad(uint8_t hash, uint8_t x);
uint8_t myinoise8(uint8_t x);
uint8_t mygrad3d(uint8_t hash, uint8_t x, uint8_t y, uint8_t z);
uint8_t myinoise83d(uint8_t x, uint8_t y, uint8_t z);
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIXELPIN, NEO_GRB + NEO_KHZ800);
extern const uint8_t fire_palette[192];
uint8_t getColorFromPalette(uint8_t heatValue, uint8_t color);
uint8_t XY (uint8_t x, uint8_t y);
// parameters and buffer for the noise array
#define NUM_NOISE_LAYERS 1
// two layers of perlin noise make the fire effect
#define FIRENOISE 0
uint8_t x[NUM_NOISE_LAYERS];
uint8_t y[NUM_NOISE_LAYERS];
uint8_t z[NUM_NOISE_LAYERS];
uint8_t scale_x[NUM_NOISE_LAYERS];
uint8_t scale_y[NUM_NOISE_LAYERS];
uint8_t noise[NUM_NOISE_LAYERS][WIDTH][HEIGHT];
uint8_t noise2[NUM_NOISE_LAYERS][WIDTH][HEIGHT];
uint8_t heat[NUM_LEDS];
void setup() {
#ifdef DEBUG
Debug.begin();
#endif
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
delay(200); //security wait to avoid bright white LED's at startup
strip.begin(); // Initialize the strip
#ifdef DEBUG
Debug.print(strip.numPixels());
Debug.println(" LEDs initialized");
Debug.print("NUM_LEDS is initialized to ");
Debug.println(NUM_LEDS);
#endif
if (strip.numPixels() == 0){
strip = Adafruit_NeoPixel(NUM_LEDS, PIXELPIN, NEO_GRB + NEO_KHZ800);
strip.begin();
}
strip.clear(); // Initialize all pixels to 'off'
strip.setBrightness(BRIGHTNESS); // Set brightness
strip.show();
delay(200);
}
void loop() {
delay(STEP_DELAY_MS);
Fire2018_2();
strip.show();
}
void Fire2018_2() {
// some changing values
// these values are produced by perlin noise to add randomness and smooth transitions
// millis are divided by 8 to have only relevant values;
// then only the last 8 bit are used because myinoise8 takes 8 bit values.
uint8_t ctrl1 = myinoise8(((11 * millis())>>3) & 255);
uint8_t ctrl2 = myinoise8(((13 * millis())>>3) & 255);
uint8_t ctrl = ((ctrl1 + ctrl2) >> 1);
static uint8_t move = 0 ;
// parameters for the fire heat map
x[FIRENOISE] = 3 * ctrl * FIRESPEED;
y[FIRENOISE] = 20 * millis() * FIRESPEED;
z[FIRENOISE] = 5 * millis() * FIRESPEED;
scale_x[FIRENOISE] = ctrl1 >> 1;
scale_y[FIRENOISE] = ctrl2 >> 1;
//calculate the perlin noise data for the fire
for (uint8_t x_count = 0; x_count < WIDTH; x_count++) {
uint8_t xoffset = scale_x[FIRENOISE] * (x_count - CentreX);
for (uint8_t y_count = 0; y_count < HEIGHT; y_count++) {
uint8_t yoffset = scale_y[FIRENOISE] * (y_count - CentreY);
uint8_t data = ((myinoise83d(x[FIRENOISE] + xoffset, y[FIRENOISE] + yoffset, z[FIRENOISE])) + 1);
noise[FIRENOISE][x_count][y_count] = data;
}
}
if (move == 0) {
//copy everything one line up
for (uint8_t y = 0; y < HEIGHT - 1; y++) {
for (uint8_t x = 0; x < WIDTH; x++) {
heat[XY(x, y)] = heat[XY(x, y + 1)];
}
}
// draw lowest line - seed the fire where it is brightest and hottest
for (uint8_t x = 0; x < WIDTH; x++) {
heat[XY(x, HEIGHT-1)] = noise[FIRENOISE][WIDTH-x][CentreY];
if (heat[XY(x, HEIGHT-1)] < FIRST_LAYER_MINIMUM) heat[XY(x, HEIGHT-1)] = FIRST_LAYER_MINIMUM;
}
move = MOVETACT;
}
else move -=1;
// dim the flames based on FIRENOISE noise.
// if the FIRENOISE noise is strong, the led goes out fast
// if the FIRENOISE noise is weak, the led stays on stronger.
// once the heat is gone, it stays dark.
for (uint8_t y = 0; y < HEIGHT - 1; y++) {
for (uint8_t x = 0; x < WIDTH; x++) {
uint8_t dim = noise[FIRENOISE][x][y];
// high value for Flameheight = high flames
dim = multscale8(dim, FLAMEHEIGHT);
heat[XY(x, y)] = multscale8(heat[XY(x, y)] , dim);
}
}
for (uint8_t y = 0; y < HEIGHT; y++) {
for (uint8_t x = 0; x < WIDTH; x++) {
// dim the result based on SMOKENOISE noise
// this is not saved in the heat map - the flame may dim away and come back
// next iteration.
uint8_t scaled_heat = heat[XY(x, y)];
if (y==1 and scaled_heat < SECOND_LAYER_MINIMUM) scaled_heat = SECOND_LAYER_MINIMUM;
// map the colors based on heatmap
// use the heat map to set the color of the LED from the "hot" palette
uint8_t red = getColorFromPalette(scaled_heat, 0);
uint8_t green = getColorFromPalette(scaled_heat, 1);
uint8_t blue = getColorFromPalette(scaled_heat, 2);
strip.setPixelColor(XY(x, y), red, green, blue);
#ifdef DEBUG
Debug.print("LED ");
Debug.print(XY(x, y));
Debug.print(" of ");
Debug.print(strip.numPixels());
Debug.print(" -> red:");
Debug.print(red);
Debug.print(" green:");
Debug.print(green);
Debug.print(" blue:");
Debug.println(blue);
#endif
}
}
}
/*******************************************************************************/
/* a personal try on Perlin noise here */
// Attiny85 compatible perlin perm LUT implementation
const uint8_t myperm[256] PROGMEM = {58, 194, 244, 85, 166, 49, 150, 135, 35, 200, 7, 251, 39, 9, 214, 46, 105, 162, 103, 202, 139, 224, 209, 107, 101, 83, 71, 130, 208, 43, 52, 171, 12, 113, 73, 197, 168, 178, 147, 227, 250, 170, 14, 231, 163, 93, 213, 192, 238, 137, 246, 131, 124, 182, 128, 193, 245, 126, 38, 183, 247, 24, 129, 115, 81, 97, 228, 99, 167, 40, 96, 210, 59, 42, 232, 133, 111, 203, 149, 86, 48, 254, 68, 92, 155, 117, 41, 32, 122, 78, 188, 158, 13, 142, 127, 44, 63, 60, 75, 110, 17, 222, 184, 196, 249, 146, 62, 235, 169, 25, 31, 212, 116, 74, 165, 102, 190, 229, 154, 79, 164, 84, 108, 253, 57, 10, 123, 218, 215, 144, 114, 236, 201, 26, 134, 8, 51, 179, 141, 181, 95, 220, 80, 230, 187, 191, 176, 185, 76, 53, 109, 189, 6, 119, 91, 27, 148, 255, 233, 36, 45, 240, 156, 145, 237, 121, 204, 87, 65, 239, 241, 98, 225, 174, 206, 19, 77, 199, 30, 205, 248, 211, 172, 22, 180, 219, 94, 234, 223, 151, 4, 61, 198, 54, 140, 89, 242, 125, 207, 195, 138, 50, 143, 132, 243, 159, 173, 106, 118, 152, 37, 252, 67, 161, 5, 21, 175, 72, 153, 23, 186, 70, 29, 112, 34, 56, 120, 33, 18, 28, 0, 157, 47, 226, 1, 100, 221, 82, 104, 2, 55, 177, 88, 16, 136, 217, 160, 11, 3, 90, 20, 64, 216, 69, 15, 66};
//Attiny85 compatible scale and nscale (negative scale) function
uint8_t multscale8(uint8_t i, uint8_t scale) {
// makes a brightness value dark for low values of "scale"
return (i * (uint16_t)scale) >> 8;
}
uint8_t nmultscale8(uint8_t i, uint8_t scale) {
// makes a brightness value dark for high values of "scale"
return i - ((i * (uint16_t)scale) >> 8);
}
// Attiny compatible myFade function for Perlin Noise
uint8_t myFade(uint8_t t) {
uint32_t temp = t;
return temp * temp * temp * (temp * (temp * 6 - 15) + 10) >> 8; // Beachte den Rechts-Shift, um das Ergebnis zu normieren
}
// Attiny compatible 1D grad function for Perlin Noise
uint8_t mygrad(uint8_t hash, uint8_t x) {
uint8_t h = hash & 15;
return ((h & 8) ? -x : x);
}
// Attiny compatible 8 Bit 1D Perlin Noise function
uint8_t myinoise8(uint8_t x) {
uint8_t xf = x & 255;
uint8_t xi = x >> 8;
uint8_t u = myFade(xf);
uint8_t a = pgm_read_byte(&myperm[xi]);
uint8_t b = pgm_read_byte(&myperm[xi + 1]);
uint8_t x1 = mygrad(a, xf);
uint8_t x2 = mygrad(b, xf - 1); // Korrigiert
return ((1 - u) * x1 + u * x2);
}
// Attiny compatible 3D grad function for Perlin Noise
uint8_t mygrad3d(uint8_t hash, uint8_t x, uint8_t y, uint8_t z) {
uint8_t h = hash & 15;
uint8_t u = (h < 8) ? x : y;
uint8_t v = (h < 4) ? y : ((h == 12 || h == 14) ? x : z);
int16_t gradValue = ((h & 1) ? -u : u) + ((h & 2) ? -v : v);
return (gradValue < 0) ? 0 : (gradValue > 255) ? 255 : (uint8_t)gradValue;
}
// Attiny compatible 8 Bit 3D Perlin Noise function
uint8_t myinoise83d(uint8_t x, uint8_t y, uint8_t z) {
uint8_t xf = x & 255;
uint8_t yf = y & 255;
uint8_t zf = z & 255;
uint8_t xi = x >> 8;
uint8_t yi = y >> 8;
uint8_t zi = z >> 8;
uint8_t u = myFade(xf);
uint8_t v = myFade(yf);
uint8_t w = myFade(zf);
uint8_t xiInc = xi + 1;
uint8_t yiInc = yi + 1;
uint8_t ziInc = zi + 1;
uint8_t aaa = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xi]) + yi]) + zi]);
uint8_t aba = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xi]) + yiInc]) + zi]);
uint8_t aab = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xi]) + yi]) + ziInc]);
uint8_t abb = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xi]) + yiInc]) + ziInc]);
uint8_t baa = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xiInc]) + yi]) + zi]);
uint8_t bba = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xiInc]) + yiInc]) + zi]);
uint8_t bab = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xiInc]) + yi]) + ziInc]);
uint8_t bbb = pgm_read_byte(&myperm[pgm_read_byte(&myperm[pgm_read_byte(&myperm[xiInc]) + yiInc]) + ziInc]);
uint8_t x1 = mygrad3d(aaa, xf, yf, zf);
uint8_t x2 = mygrad3d(baa, xf - 1, yf, zf);
uint8_t x3 = mygrad3d(aba, xf, yf - 1, zf);
uint8_t x4 = mygrad3d(bba, xf - 1, yf - 1, zf);
uint8_t x5 = mygrad3d(aab, xf, yf, zf - 1);
uint8_t x6 = mygrad3d(bab, xf - 1, yf, zf - 1);
uint8_t x7 = mygrad3d(abb, xf, yf - 1, zf - 1);
uint8_t x8 = mygrad3d(bbb, xf - 1, yf - 1, zf - 1);
uint8_t y1 = (1 - u) * x1 + u * x2;
uint8_t y2 = (1 - u) * x3 + u * x4;
uint8_t y3 = (1 - u) * x5 + u * x6;
uint8_t y4 = (1 - u) * x7 + u * x8;
uint8_t z1 = (1 - v) * y1 + v * y2;
uint8_t z2 = (1 - v) * y3 + v * y4;
return ((1 - w) * z1 + w * z2);
}
/******************************************************/
/* Fire color palette here */
uint8_t getColorFromPalette(uint8_t heatValue, uint8_t color){
if (color >= 3) color = 2;
uint8_t index = heatValue >> 2;
index = index * 3;
if (index > 191) index = 191;
return pgm_read_byte(&fire_palette[index + color]);
}
// the list contains 3-tuples r, g, b in a long array
const uint8_t fire_palette[192] PROGMEM = {
11 , 5 , 0 ,
14 , 7 , 0 ,
17 , 8 , 0 ,
20 , 9 , 0 ,
25 , 13 , 0 ,
31 , 17 , 0 ,
36 , 19 , 0 ,
45 , 23 , 0 ,
55 , 27 , 0 ,
58 , 28 , 0 ,
62 , 31 , 0 ,
66 , 33 , 0 ,
69 , 35 , 0 ,
73 , 37 , 0 ,
77 , 39 , 0 ,
81 , 41 , 0 ,
84 , 44 , 0 ,
88 , 45 , 0 ,
92 , 48 , 0 ,
95 , 50 , 0 ,
99 , 52 , 0 ,
103 , 54 , 0 ,
106 , 56 , 0 ,
110 , 59 , 0 ,
114 , 61 , 0 ,
118 , 63 , 0 ,
121 , 65 , 0 ,
125 , 68 , 0 ,
129 , 70 , 0 ,
132 , 72 , 0 ,
136 , 74 , 0 ,
140 , 77 , 0 ,
143 , 79 , 0 ,
147 , 82 , 0 ,
151 , 85 , 0 ,
155 , 90 , 0 ,
158 , 95 , 0 ,
162 , 99 , 0 ,
166 , 102 , 0 ,
169 , 107 , 0 ,
173 , 112 , 0 ,
177 , 116 , 0 ,
180 , 119 , 0 ,
184 , 125 , 0 ,
188 , 130 , 0 ,
192 , 135 , 0 ,
195 , 140 , 0 ,
199 , 144 , 0 ,
203 , 149 , 0 ,
206 , 153 , 0 ,
210 , 156 , 0 ,
214 , 162 , 9 ,
217 , 167 , 11 ,
221 , 171 , 13 ,
225 , 177 , 15 ,
229 , 180 , 18 ,
232 , 186 , 18 ,
236 , 190 , 21 ,
240 , 195 , 21 ,
243 , 199 , 22 ,
247 , 204 , 22 ,
251 , 210 , 24 ,
255 , 217 , 24 ,
};
/*************************************************************/
/* physical LED layout here */
uint8_t XY (uint8_t x, uint8_t y) {
// any out of bounds address maps to the first hidden pixel
// https://macetech.github.io/FastLED-XY-Map-Generator/
if ( (x >= WIDTH) || (y >= HEIGHT) ) {
return (LAST_VISIBLE_LED +1);
}
const uint8_t XYTable[] = {
1, 2,
0, 3,
};
uint8_t i = (y * WIDTH) + x;
uint8_t j = XYTable[i];
return j;
}