└── ReactTable.ino /ReactTable.ino: -------------------------------------------------------------------------------- 1 | //#define FASTLED_ESP32_I2S 2 | #include 3 | #include 4 | 5 | // === Pattern mode ========================================================= 6 | 7 | /** Mode 8 | * This code supports four different animations. You can choose one of them, 9 | * or set "cycle" to true to cycle through all of them at some set interval. 10 | */ 11 | enum Mode { SolidMode, ConfettiMode, SpinnerMode, GearMode, FireMode, 12 | SurfaceMode, RippleMode, DiffusionMode, AttractorMode, MirrorMode }; 13 | 14 | #define NUM_MODES 10 15 | 16 | // -- Mode choice 17 | Mode g_Mode = SurfaceMode; 18 | 19 | // -- To cycle modes, set cycle to true and choose an interval (in milliseconds) 20 | bool g_Cycle = false; 21 | const int TIME_PER_PATTERN = 15000; 22 | 23 | #define MODE_PIN 14 24 | 25 | // === Multi-cell pin settings ============================================== 26 | 27 | /** IR channel selector 28 | * 29 | * Each analog multiplexer takes 16 analog inputs and produces one The 30 | * particular output is chosen by setting four digital inputs to the 31 | * binary representation of the analog input desired. For example, to 32 | * read input number 13, which is 1101 in binary, set bit 3 to HIGH, 33 | * bit 2 to HIGH bit 1 to LOW, and bit 0 to HIGH. 34 | * 35 | * The four definitions below indicate which pins on the 36 | * microcontroller are connected to the four digital inputs on the 37 | * multiplexer. 38 | */ 39 | #define IR_CHANNEL_BIT_0 5 40 | #define IR_CHANNEL_BIT_1 18 41 | #define IR_CHANNEL_BIT_2 23 42 | #define IR_CHANNEL_BIT_3 19 43 | 44 | /** IR input pins 45 | * 46 | * My design requires four multiplexers because we have 61 IR analog 47 | * inputs. The way I set it up, four digital inputs are connected to 48 | * all four of the multiplexers. Each multiplexer then has its own 49 | * input pin to read the value. To read any one of the 64 possible 50 | * analog inputs, we take the low 4 bits of the input number and send 51 | * that to the channel selectors (above). Then we read in the input on 52 | * the pin specified by the next two bits of the input. 53 | */ 54 | int IR_INPUTS[] = {32, 33, 34, 35}; // { 27, 33, 34, 35 }; 55 | 56 | /** Cell configuration 57 | * 58 | * My table has 61 cells, with 12 WS2812 LEDs per cell. 59 | */ 60 | #define NUM_CELLS 61 61 | #define LEDS_PER_CELL 12 62 | #define NUM_LEDS (NUM_CELLS * LEDS_PER_CELL) 63 | 64 | class Cell; 65 | Cell * g_Cells[NUM_CELLS]; 66 | 67 | /** Storage for LEDs */ 68 | CRGB g_LEDs[NUM_LEDS]; 69 | 70 | /** Kind of LEDs */ 71 | #define COLOR_ORDER GRB 72 | #define CHIPSET WS2812 73 | 74 | /** LED strip setup 75 | * Set up the wiring of the LED strips here. I've wired mine so that each pair of 76 | * columns of rings (a total of 11 rings) has a data input. That's 11 * 12 = 132 77 | * LEDs on each pin, except for the last one. 78 | */ 79 | void LED_setup() 80 | { 81 | FastLED.addLeds(g_LEDs, 132*0, 132).setCorrection( TypicalLEDStrip ); 82 | FastLED.addLeds(g_LEDs, 132*1, 132).setCorrection( TypicalLEDStrip ); 83 | FastLED.addLeds(g_LEDs, 132*2, 132).setCorrection( TypicalLEDStrip ); 84 | FastLED.addLeds(g_LEDs, 132*3, 132).setCorrection( TypicalLEDStrip ); 85 | FastLED.addLeds(g_LEDs, 132*4, 132).setCorrection( TypicalLEDStrip ); 86 | FastLED.addLeds(g_LEDs, 132*5, 72).setCorrection( TypicalLEDStrip ); 87 | } 88 | 89 | /** Animation speed */ 90 | #define FRAMES_PER_SECOND 30 91 | 92 | /** Default brightness */ 93 | uint8_t g_Brightness = 40; 94 | 95 | /** Cell mapping 96 | * 97 | * This data structure holds the physical information about a cell: 98 | * ir_index: the index of the IR sensor in the multiplexer 99 | * ring_index: the index of the LED ring in the logical "strip" of LEDs 100 | * x,y: the position of the center of the cell in centimeters 101 | */ 102 | 103 | struct CellMapEntry 104 | { 105 | uint8_t m_ir_index; 106 | uint16_t m_ring_index; 107 | uint8_t m_x; 108 | uint8_t m_y; 109 | }; 110 | 111 | CellMapEntry g_CellMap[] = { 112 | { 0, 60, 2, 2 }, { 1, 49, 12, 2 }, { 2, 38, 22, 2 }, { 3, 27, 32, 2 }, { 4, 16, 42, 2 }, { 5, 5, 52, 2 }, 113 | { 6, 54, 7, 5 }, { 7, 43, 17, 5 }, { 8, 32, 27, 5 }, { 9, 21, 37, 5 }, { 10, 10, 47, 5 }, 114 | { 11, 59, 2, 8 }, { 12, 48, 12, 8 }, { 13, 37, 22, 8 }, { 14, 26, 32, 8 }, { 15, 15, 42, 8 }, { 16, 4, 52, 8 }, 115 | { 17, 53, 7, 11 }, { 18, 42, 17, 11 }, { 19, 31, 27, 11 }, { 20, 20, 37, 11 }, { 21, 9, 47, 11 }, 116 | { 22, 58, 2, 14 }, { 23, 47, 12, 14 }, { 24, 36, 22, 14 }, { 25, 25, 32, 14 }, { 26, 14, 42, 14 }, { 27, 3, 52, 14 }, 117 | { 28, 52, 7, 17 }, { 29, 41, 17, 17 }, { 30, 30, 27, 17 }, { 31, 19, 37, 17 }, { 32, 8, 47, 17 }, 118 | { 33, 57, 2, 20 }, { 34, 46, 12, 20 }, { 35, 35, 22, 20 }, { 36, 24, 32, 20 }, { 37, 13, 42, 20 }, { 38, 2, 52, 20 }, 119 | { 39, 51, 7, 23 }, { 40, 40, 17, 23 }, { 41, 29, 27, 23 }, { 42, 18, 37, 23 }, { 43, 7, 47, 23 }, 120 | { 44, 56, 2, 26 }, { 45, 45, 12, 26 }, { 46, 34, 22, 26 }, { 47, 23, 32, 26 }, { 48, 12, 42, 26 }, { 49, 1, 52, 26 }, 121 | { 50, 50, 7, 29 }, { 51, 39, 17, 29 }, { 52, 28, 27, 29 }, { 53, 17, 37, 29 }, { 54, 6, 47, 29 }, 122 | { 55, 55, 2, 32 }, { 56, 44, 12, 32 }, { 57, 33, 22, 32 }, { 58, 22, 32, 32 }, { 59, 11, 42, 32 }, { 60, 0, 52, 32 } 123 | }; 124 | 125 | /** Surface coordinate system 126 | * 127 | * The surface is a logical 2-D grid measured in centimeters. In surface 128 | * mode, the LEDs sample their values from this single grid, allowing the 129 | * entire table to be treated as a single image. 130 | */ 131 | 132 | #define SURFACE_WIDTH 54 133 | #define SURFACE_HEIGHT 34 134 | 135 | /** Surface 136 | * 137 | * Fill this 2-D grid with values to display a single image. 138 | */ 139 | uint8_t g_Surface[SURFACE_WIDTH][SURFACE_HEIGHT]; 140 | 141 | /** Ring coordinate mapping 142 | * 143 | * Precompute the position of each LED relative to the surface coordinate 144 | * system. The result is a sampling pattern for each LED: surface elements 145 | * and ratios of mixing those elements. 146 | * 147 | * My model: a ring has a diameter of 4cm; each LED is .5cm square, so the 148 | * inner diameter of the ring is 3cm. The middle of the ring is at 3.5cm. 149 | */ 150 | 151 | struct SurfaceSample 152 | { 153 | int dx; 154 | int dy; 155 | int top_left_part; 156 | int top_right_part; 157 | int bottom_left_part; 158 | int bottom_right_part; 159 | }; 160 | 161 | SurfaceSample g_SurfaceSamples[LEDS_PER_CELL]; 162 | 163 | int modi(float v, int * i) 164 | { 165 | double v_whole, v_frac; 166 | v_frac = modf(v, &v_whole); 167 | int vi = (int) v_whole; 168 | int vf = (int) (10.0 * v_frac); 169 | 170 | *i = vi; 171 | return vf; 172 | } 173 | 174 | void computePixelOffsets() 175 | { 176 | Serial.println(); 177 | for (int i = 0; i < LEDS_PER_CELL; i++) { 178 | // -- Divide the ring into 12 equal angles 179 | float frac = ((float) i) / ((float) LEDS_PER_CELL); 180 | float angle = frac * 3.1415926535897 * 2.0; 181 | 182 | // -- X and Y along a ring of diameter 3.0cm 183 | // These values correspond to the top left corner of each LED 184 | float x = (cos(angle) + 1.0) * 1.5; 185 | float y = (sin(angle) + 1.0) * 1.5; 186 | 187 | // -- Figure out how much of each surface pixel should be sampled by the LED 188 | int xl, xlf; 189 | xlf = modi(x, &xl); 190 | 191 | int xr, xrf; 192 | xrf = modi(x + 0.99, &xr); 193 | 194 | int yt, ytf; 195 | ytf = modi(y, &yt); 196 | 197 | int yb, ybf; 198 | ybf = modi(y + 0.99, &yb); 199 | 200 | int top_left = (10 - xlf) * (10 - ytf); 201 | 202 | int top_right = 0; 203 | if (xl != xr) { 204 | top_right = xrf * (10 - ytf); 205 | } 206 | 207 | int bottom_left = 0; 208 | if (yt != yb) { 209 | bottom_left = (10 - xlf) * ybf; 210 | } 211 | 212 | int bottom_right = 0; 213 | if ((xl != xr) && (yt != yb)) { 214 | bottom_right = xrf * ybf; 215 | } 216 | 217 | g_SurfaceSamples[i].dx = xl; 218 | g_SurfaceSamples[i].dy = yt; 219 | g_SurfaceSamples[i].top_left_part = top_left; 220 | g_SurfaceSamples[i].top_right_part = top_right; 221 | g_SurfaceSamples[i].bottom_left_part = bottom_left; 222 | g_SurfaceSamples[i].bottom_right_part = bottom_right; 223 | 224 | /* 225 | Serial.print("Pixel "); Serial.print(i); Serial.print(" at "); 226 | Serial.print(x); Serial.print(" , "); Serial.print(y); 227 | Serial.print(" : \n"); 228 | Serial.print(" X left : "); Serial.print(xl); Serial.print("+"); Serial.print(xlf); Serial.println(); 229 | Serial.print(" X right : "); Serial.print(xr); Serial.print("+"); Serial.print(xrf); Serial.println(); 230 | Serial.print(" Y top : "); Serial.print(yt); Serial.print("+"); Serial.print(ytf); Serial.println(); 231 | Serial.print(" Y bottom: "); Serial.print(yb); Serial.print("+"); Serial.print(ybf); Serial.println(); 232 | Serial.print(" Top left : "); Serial.print(top_left); 233 | Serial.print(" Top right : "); Serial.print(top_right); 234 | Serial.print(" Bottom left : "); Serial.print(bottom_left); 235 | Serial.print(" Bottom right : "); Serial.print(bottom_right); Serial.println(); 236 | */ 237 | } 238 | } 239 | 240 | // === Cells ================================================================ 241 | 242 | /** Cell 243 | * 244 | * Each cell is managed by an instance of this class, which includes 245 | * all the data and methods needed to read the IR input and set the 246 | * corresponding ring of LEDs. 247 | * 248 | */ 249 | class Cell 250 | { 251 | protected: 252 | // -- LED ring information 253 | uint16_t m_led_index; 254 | CRGBPalette16 m_palette; 255 | 256 | // -- IR sensor 257 | int m_ir_input; 258 | int m_ir_channel; 259 | uint16_t m_ir_min; 260 | uint16_t m_ir_max; 261 | uint8_t m_ir_channel_selector[4]; 262 | uint16_t m_level; 263 | 264 | // -- Physical position 265 | int m_center_x; 266 | int m_center_y; 267 | int m_left; 268 | int m_top; 269 | 270 | // -- Data for the patterns 271 | uint16_t m_position; 272 | byte m_heat[LEDS_PER_CELL]; 273 | bool m_new_pattern; 274 | 275 | struct Flame { 276 | uint8_t fuel; 277 | uint8_t heat; 278 | }; 279 | 280 | Flame m_flames[LEDS_PER_CELL]; 281 | 282 | public: 283 | Cell( CellMapEntry& info ) 284 | : m_led_index(info.m_ring_index * LEDS_PER_CELL), 285 | m_palette(RainbowColors_p), 286 | m_ir_input(info.m_ir_index >> 4), 287 | m_ir_channel(info.m_ir_index & 0xF), 288 | m_ir_min(100), 289 | m_ir_max(0), 290 | m_level(0), 291 | m_center_x(info.m_x), 292 | m_center_y(info.m_y), 293 | m_position(0), 294 | m_new_pattern(true) 295 | { 296 | // -- Precompute the IR selector signal 297 | m_ir_channel_selector[0] = (m_ir_channel & 0x1) ? HIGH : LOW; 298 | m_ir_channel_selector[1] = (m_ir_channel & 0x2) ? HIGH : LOW; 299 | m_ir_channel_selector[2] = (m_ir_channel & 0x4) ? HIGH : LOW; 300 | m_ir_channel_selector[3] = (m_ir_channel & 0x8) ? HIGH : LOW; 301 | 302 | // -- Compute top left for convenience 303 | m_left = m_center_x - 2; 304 | m_top = m_center_y - 2; 305 | } 306 | 307 | // ----- Getters -------- 308 | 309 | int getCenterX() const { return m_center_x; } 310 | int getCenterY() const { return m_center_y; } 311 | 312 | // ----- IR Sensors ----- 313 | 314 | /** Set the IR max and min 315 | * Determined by the calibration phase. 316 | */ 317 | void setIRMax(uint16_t ir_max) { 318 | m_ir_max = ir_max; 319 | } 320 | 321 | void setIRMin(uint16_t ir_min) { 322 | m_ir_min = ir_min; 323 | } 324 | 325 | /** Read raw IR value 326 | * We can have several MUXs, each with 16 channels. To read a specific 327 | * IR value, we specify which MUX (the "input") and which channel. 328 | */ 329 | uint16_t rawIR() 330 | { 331 | uint16_t val; 332 | 333 | // -- Select the channel 334 | digitalWrite(IR_CHANNEL_BIT_0, m_ir_channel_selector[0]); 335 | digitalWrite(IR_CHANNEL_BIT_1, m_ir_channel_selector[1]); 336 | digitalWrite(IR_CHANNEL_BIT_2, m_ir_channel_selector[2]); 337 | digitalWrite(IR_CHANNEL_BIT_3, m_ir_channel_selector[3]); 338 | 339 | // -- Finally, read the analog value 340 | val = analogRead(IR_INPUTS[m_ir_input]); 341 | 342 | return val; 343 | } 344 | 345 | /** Sense IR 346 | * Read and map to the calibrated range 347 | */ 348 | uint8_t senseIR() 349 | { 350 | uint16_t val = rawIR(); 351 | 352 | // -- Pin the value in between the min and max from calibration 353 | if (val < m_ir_min) val = m_ir_min; 354 | if (val > m_ir_max) val = m_ir_max; 355 | 356 | // -- Map to 8-bit value 357 | uint8_t level = map(val, m_ir_min, m_ir_max, 0, 255); 358 | return level; 359 | } 360 | 361 | /** Sense IR with decay 362 | * 363 | * This version decays the IR value slowly, causing the visual 364 | * effects to linger. 365 | */ 366 | uint8_t senseIRwithDecay(uint8_t down_speed, uint8_t up_speed) 367 | { 368 | uint8_t cur_level = senseIR(); 369 | 370 | if (cur_level < m_level) { 371 | uint8_t new_level = qsub8(m_level, down_speed); 372 | if (cur_level < new_level) { 373 | m_level = new_level; 374 | } else { 375 | m_level = cur_level; 376 | } 377 | } else { 378 | if (cur_level > m_level) { 379 | uint8_t new_level = qadd8(m_level, up_speed); 380 | if (cur_level > new_level) { 381 | m_level = new_level; 382 | } else { 383 | m_level = cur_level; 384 | } 385 | } 386 | } 387 | return m_level; 388 | } 389 | 390 | // ----- Set LEDs in this ring ----- 391 | 392 | void setLED(int local_index, CRGB color) { 393 | if (local_index < 0) local_index = 0; 394 | if (local_index >= LEDS_PER_CELL) local_index = LEDS_PER_CELL - 1; 395 | g_LEDs[m_led_index + local_index] = color; 396 | } 397 | 398 | void setLEDHue(int local_index, uint8_t hue, uint8_t brightness = 255) { 399 | // -- Get the color from the palette 400 | CRGB color = ColorFromPalette(m_palette, hue); 401 | if (brightness < 255) color.nscale8_video(brightness); 402 | setLED(local_index, color); 403 | } 404 | 405 | void setAllLEDs(CRGB color) { 406 | for (int i = 0; i < LEDS_PER_CELL; i++) { 407 | g_LEDs[m_led_index + i] = color; 408 | } 409 | } 410 | 411 | void setAllLEDsHue(uint8_t hue, uint8_t brightness = 255) { 412 | // -- Get the color from the palette 413 | CRGB color = ColorFromPalette(m_palette, hue); 414 | if (brightness < 255) color.nscale8_video(brightness); 415 | setAllLEDs(color); 416 | } 417 | 418 | void fadeToBlackBy(uint8_t howmuch) { 419 | for (int i = 0; i < LEDS_PER_CELL; i++) { 420 | g_LEDs[m_led_index + i].fadeToBlackBy(howmuch); 421 | } 422 | } 423 | 424 | // ----- Dithered pixels ----- 425 | 426 | /* Set a pixel 427 | * 428 | * In these methods, the position is expressed as a 16-bit 429 | * fixed-precision number that maps to whatever number of actual 430 | * LEDs there are in the strip/ring. This function dithers the 431 | * adjacent pixels to create a smooth transition. It needs to be 432 | * fast! 433 | */ 434 | void setPixel(uint16_t pos, CRGB color) 435 | { 436 | // -- Scale the position from 0-0xFFFF to 0-m_num_leds 437 | // but keep the fractional part. The result is a 16-bit 438 | // value where the high 8 bits are in [0,num_leds] and 439 | // the low 8 bits are the faction in [0,0xFF] 440 | uint32_t pos32 = (uint32_t) pos; 441 | uint16_t scaled_pos = (pos32 * LEDS_PER_CELL) >> 8; 442 | 443 | // -- Break the position into the whole and fractional parts. The 444 | // fractional part is represented by the brightness of two 445 | // adjacent LEDs. 446 | int indexA = scaled_pos >> 8; 447 | uint8_t offsetA = scaled_pos & 0xFF; 448 | 449 | // -- Pixels can straddle two LEDs, so compute the other partial LED. 450 | int indexB; 451 | if (indexA == 0) indexB = LEDS_PER_CELL - 1; 452 | else indexB = indexA - 1; 453 | uint8_t offsetB = 0xFF - offsetA; // A little less than full on 454 | 455 | // -- Scale the brightness and assign the LEDs 456 | if (offsetA > 10) nblend(g_LEDs[m_led_index + indexA], color, offsetA); 457 | if (offsetB > 10) nblend(g_LEDs[m_led_index + indexB], color, offsetB); 458 | } 459 | 460 | /** Set a pixel 461 | * This version just chooses the color from the palette. 462 | */ 463 | void setPixelHue(uint16_t pos, uint8_t hue, uint8_t brightness = 255) 464 | { 465 | // -- Get the color from the palette 466 | CRGB color = ColorFromPalette(m_palette, hue); 467 | if (brightness < 255) color.nscale8_video(brightness); 468 | setPixel(pos, color); 469 | } 470 | 471 | // ----- Patterns ----- 472 | 473 | // -- Call this method to trigger pattern initialization 474 | void newPattern() { m_new_pattern = true; } 475 | 476 | void SolidPattern() 477 | { 478 | if (m_new_pattern) { 479 | m_palette = RainbowColors_p; 480 | m_new_pattern = false; 481 | } 482 | uint8_t level = senseIRwithDecay(12, 4); 483 | if (level > 230) level = 230; 484 | setAllLEDsHue(level); 485 | } 486 | 487 | void ConfettiPattern() 488 | { 489 | if (m_new_pattern) { 490 | m_palette = RainbowColors_p; 491 | m_new_pattern = false; 492 | } 493 | fadeToBlackBy(80); 494 | uint8_t level = senseIRwithDecay(15, 4); 495 | uint8_t prob = 255 - level; 496 | uint8_t coin = random8(); 497 | if (coin < prob) { 498 | int pos = random8(LEDS_PER_CELL); 499 | setLEDHue(pos, level + random8(64)); 500 | } 501 | } 502 | 503 | void SpinnerPattern(int min_speed, int max_speed) 504 | { 505 | // -- Initialize if necessary 506 | if (m_new_pattern) { 507 | m_position = random16(); 508 | m_palette = RainbowColors_p; 509 | m_new_pattern = false; 510 | } 511 | 512 | setAllLEDs(CRGB::Black); // fadeToBlackBy(80); 513 | uint8_t level = senseIRwithDecay(12, 4); 514 | int speed = map(level, 0, 255, max_speed, min_speed); 515 | m_position += speed; 516 | if (level > 230) level = 230; 517 | setPixelHue(m_position, level); 518 | } 519 | 520 | void GearPattern(int min_speed, int max_speed) 521 | { 522 | // -- Initialize if necessary 523 | if (m_new_pattern) { 524 | m_position = random16(); 525 | m_palette = RainbowColors_p; 526 | m_new_pattern = false; 527 | } 528 | 529 | setAllLEDs(CRGB::Black); // fadeToBlackBy(80); 530 | uint8_t level = senseIRwithDecay(12, 4); 531 | int speed = map(level, 0, 255, max_speed, min_speed); 532 | m_position += speed; 533 | if (level > 230) level = 230; 534 | setPixelHue(m_position, level); 535 | setPixelHue(m_position + 0x5555, level); 536 | setPixelHue(m_position + 0xAAAA, level); 537 | } 538 | 539 | void FirePattern(int sparking, int burn_rate) 540 | { 541 | if (m_new_pattern) { 542 | m_palette = HeatColors_p; 543 | for (int i = 0; i < LEDS_PER_CELL; i++) { 544 | m_flames[i].heat = 0; 545 | m_flames[i].fuel = random(40); 546 | } 547 | m_new_pattern = false; 548 | } 549 | 550 | uint8_t level = senseIR(); 551 | 552 | for (int i = 0; i < LEDS_PER_CELL; i++) { 553 | if (m_flames[i].fuel > 0) { 554 | uint8_t burn = burn_rate; // random8(burn_rate); 555 | m_flames[i].fuel = qsub8(m_flames[i].fuel, burn); 556 | m_flames[i].heat = qadd8(m_flames[i].heat, burn); 557 | } else { 558 | uint8_t cool = 20; 559 | m_flames[i].heat = qsub8(m_flames[i].heat, cool); 560 | } 561 | 562 | byte colorindex = scale8( m_flames[i].heat, 170); 563 | setLEDHue( i, colorindex); 564 | 565 | if (m_flames[i].heat < 10) { 566 | uint8_t prob = map(level, 0, 255, sparking, 5); 567 | if ( random8() < prob ) { 568 | uint8_t max_fuel = map(level, 0, 255, 150, 0); 569 | uint8_t more_fuel = random8(max_fuel) + 100; 570 | m_flames[i].fuel += more_fuel; 571 | } 572 | } 573 | } 574 | } 575 | 576 | // --- Surface patterns --------------- 577 | 578 | CRGB getSurfaceColor(int x, int y, uint8_t scaledown) 579 | { 580 | if (x < 0 || x >= SURFACE_WIDTH) return CRGB::Black; 581 | if (y < 0 || y >= SURFACE_HEIGHT) return CRGB::Black; 582 | uint8_t hue = g_Surface[x][y]; 583 | if (hue == 255) return CRGB::Black; 584 | CRGB color = ColorFromPalette(m_palette, hue); 585 | color %= scaledown; 586 | return color; 587 | } 588 | 589 | void SurfacePattern() 590 | { 591 | if (m_new_pattern) { 592 | m_palette = RainbowColors_p; 593 | m_new_pattern = false; 594 | } 595 | 596 | for (int i = 0; i < LEDS_PER_CELL; i++) { 597 | // -- Compute the coordinates of this LED 598 | SurfaceSample & s = g_SurfaceSamples[i]; 599 | 600 | // -- Compute the absolute position of this LED on the surface 601 | int x = m_left + s.dx; 602 | int y = m_top + s.dy; 603 | 604 | // -- Look up the surface color and set the LED 605 | CRGB color = getSurfaceColor(x, y, s.top_left_part); 606 | 607 | if (s.top_right_part > 0) { 608 | color += getSurfaceColor(x+1, y, s.top_right_part); 609 | } 610 | 611 | if (s.bottom_left_part > 0) { 612 | color += getSurfaceColor(x, y+1, s.bottom_left_part); 613 | } 614 | 615 | if (s.bottom_right_part > 0) { 616 | color += getSurfaceColor(x+1, y+1, s.bottom_right_part); 617 | } 618 | 619 | setLED(i, color); 620 | } 621 | } 622 | }; 623 | 624 | // === Surfaces ============================================================= 625 | 626 | void initializeSurface() 627 | { 628 | // -- Precompute LED locations for surface view 629 | computePixelOffsets(); 630 | 631 | for (int x = 0; x < SURFACE_WIDTH; x++) { 632 | for (int y = 0; y < SURFACE_HEIGHT; y++) { 633 | g_Surface[x][y] = 255; 634 | } 635 | } 636 | } 637 | 638 | bool setSurface(int x, int y, uint8_t val) 639 | { 640 | if (x >= 0 and x < SURFACE_WIDTH and y >= 0 and y < SURFACE_HEIGHT) { 641 | g_Surface[x][y] = val; 642 | return true; 643 | } else { 644 | return false; 645 | } 646 | } 647 | 648 | bool mixSurface(int x, int y, uint8_t val) 649 | { 650 | if (x >= 0 and x < SURFACE_WIDTH and y >= 0 and y < SURFACE_HEIGHT) { 651 | uint8_t old_val = g_Surface[x][y]; 652 | if (old_val != 255) { 653 | val = (val + old_val)/2; 654 | } 655 | g_Surface[x][y] = val; 656 | return true; 657 | } else { 658 | return false; 659 | } 660 | } 661 | 662 | uint8_t getSurface(int x, int y) 663 | { 664 | if (x >= 0 and x < SURFACE_WIDTH and y >= 0 and y < SURFACE_HEIGHT) { 665 | return g_Surface[x][y]; 666 | } else { 667 | return 0; 668 | } 669 | } 670 | 671 | uint16_t g_f_center_x = SURFACE_WIDTH/2; 672 | uint16_t g_f_center_y = SURFACE_HEIGHT/2; 673 | uint8_t g_val = 128; 674 | 675 | void ComputeCenterOfMass() 676 | { 677 | uint32_t total_x = 0; 678 | uint32_t total_y = 0; 679 | uint32_t total_weight = 0; 680 | uint8_t m = 255; 681 | 682 | for (int i = 0; i < NUM_CELLS; i++) { 683 | Cell * cell = g_Cells[i]; 684 | uint32_t x = cell->getCenterX(); 685 | uint32_t y = cell->getCenterY(); 686 | uint8_t v = cell->senseIR(); 687 | if (v < m) { 688 | m = v; 689 | } 690 | 691 | uint32_t val = (256 - v)/16; 692 | total_x += x * val; 693 | total_y += y * val; 694 | total_weight += val; 695 | } 696 | 697 | if (total_weight > 5) { 698 | // Serial.println(total_weight); 699 | g_f_center_x = total_x / total_weight; 700 | g_f_center_y = total_y / total_weight; 701 | g_val = m; 702 | } 703 | } 704 | 705 | void ComputeCenter(bool decay) 706 | { 707 | uint16_t new_center_x = 0; 708 | uint16_t new_center_y = 0; 709 | uint8_t m = 255; 710 | for (int i = 0; i < NUM_CELLS; i++) { 711 | Cell * cell = g_Cells[i]; 712 | uint32_t x = cell->getCenterX(); 713 | uint32_t y = cell->getCenterY(); 714 | uint8_t val = cell->senseIR(); 715 | if (val < m) { 716 | m = val; 717 | new_center_x = cell->getCenterX(); 718 | new_center_y = cell->getCenterY(); 719 | } 720 | } 721 | if (m < 250) { 722 | if (decay) { 723 | g_f_center_x = (g_f_center_x + new_center_x)/2; 724 | g_f_center_y = (g_f_center_y + new_center_y)/2; 725 | } else { 726 | g_f_center_x = new_center_x; 727 | g_f_center_y = new_center_y; 728 | } 729 | g_val = m; 730 | } 731 | } 732 | 733 | void CenterSurface() 734 | { 735 | int center_x; 736 | int center_y; 737 | ComputeCenter(true); 738 | center_x = g_f_center_x; // fixed_to_int(g_f_center_x); 739 | center_y = g_f_center_y; // fixed_to_int(g_f_center_y); 740 | // Serial.print(center_x); Serial.print(" "); Serial.println(center_y); 741 | 742 | for (int x = 0; x < SURFACE_WIDTH; x++) { 743 | for (int y = 0; y < SURFACE_HEIGHT; y++) { 744 | g_Surface[x][y] = 255; 745 | } 746 | } 747 | 748 | int size = 15 - g_val/20; 749 | 750 | int left = center_x - size; 751 | int right = center_x + size; 752 | int top = center_y - size; 753 | int bottom = center_y + size; 754 | 755 | for (int i = left; i < right; i++) { 756 | setSurface(i, top, g_val); 757 | setSurface(i, bottom, g_val); 758 | } 759 | 760 | for (int j = top; j < bottom; j++) { 761 | setSurface(left, j, g_val); 762 | setSurface(right, j, g_val); 763 | } 764 | 765 | /* 766 | for (int x = 0; x < SURFACE_WIDTH; x++) { 767 | for (int y = 0; y < SURFACE_HEIGHT; y++) { 768 | int diff_x = (x - center_x); 769 | int diff_y = (y - center_y); 770 | uint16_t dist2 = diff_x * diff_x + diff_y * diff_y; 771 | if (dist2 < 150 - (g_val/2)) { 772 | g_Surface[x][y] = g_val; 773 | } else { 774 | g_Surface[x][y] = 255; 775 | } 776 | } 777 | } 778 | */ 779 | } 780 | 781 | class Ripple 782 | { 783 | private: 784 | bool is_on; 785 | uint16_t f_center_x; 786 | uint16_t f_center_y; 787 | uint8_t radius; 788 | uint8_t color; 789 | int num_visible; 790 | 791 | public: 792 | Ripple() : is_on(false), 793 | f_center_x(0), 794 | f_center_y(0), 795 | radius(0), 796 | color(128) 797 | {} 798 | 799 | bool isOn() { return is_on; } 800 | 801 | void init(uint16_t f_x, uint16_t f_y, uint8_t c) 802 | { 803 | is_on = true; 804 | f_center_x = f_x; 805 | f_center_y = f_y; 806 | radius = 1; 807 | color = c; 808 | } 809 | 810 | void drawOne(uint16_t f_x, uint16_t f_y) 811 | { 812 | int x = f_x; // fixed_to_int(f_x); 813 | int y = f_y; // fixed_to_int(f_y); 814 | if (mixSurface(x, y, color)) { 815 | num_visible++; 816 | } 817 | } 818 | 819 | void draw() 820 | { 821 | if (is_on) { 822 | num_visible = 0; 823 | // int increment = 64/radius; 824 | for (int angle = 0; angle < 64; angle += 4) { 825 | // -- Values 0 -- 255 826 | uint8_t x8 = cos8(angle) - 128; 827 | uint8_t y8 = sin8(angle) - 128; 828 | 829 | uint16_t f_delta_x = (x8 * radius)/128; 830 | uint16_t f_delta_y = (y8 * radius)/128; 831 | drawOne(f_center_x + f_delta_x, f_center_y + f_delta_y); 832 | drawOne(f_center_x - f_delta_x, f_center_y + f_delta_y); 833 | drawOne(f_center_x + f_delta_x, f_center_y - f_delta_y); 834 | drawOne(f_center_x - f_delta_x, f_center_y - f_delta_y); 835 | 836 | } 837 | } 838 | 839 | radius++; 840 | if (radius > 50 || num_visible == 0) { 841 | is_on = false; 842 | } 843 | } 844 | }; 845 | 846 | #define NUM_RIPPLES 8 847 | Ripple g_ripples[NUM_RIPPLES]; 848 | uint32_t g_last_new = 0; 849 | int g_old_center_x = 0; 850 | int g_old_center_y = 0; 851 | 852 | void RippleSurface() 853 | { 854 | for (int x = 0; x < SURFACE_WIDTH; x++) { 855 | for (int y = 0; y < SURFACE_HEIGHT; y++) { 856 | g_Surface[x][y] = 255; 857 | } 858 | } 859 | 860 | bool make_new = false; 861 | uint32_t cur = millis(); 862 | if (cur - g_last_new > 150) { 863 | g_last_new = cur; 864 | ComputeCenter(false); 865 | int center_x = g_f_center_x; // fixed_to_int(g_f_center_x); 866 | int center_y = g_f_center_y; // fixed_to_int(g_f_center_y); 867 | if ((center_x != g_old_center_x) || (center_y != g_old_center_y)) { 868 | make_new = true; 869 | g_old_center_x = center_x; 870 | g_old_center_y = center_y; 871 | } 872 | } 873 | 874 | for (int i = 0; i < NUM_RIPPLES; i++) { 875 | if (g_ripples[i].isOn()) { 876 | g_ripples[i].draw(); 877 | } else { 878 | if (make_new) { 879 | g_ripples[i].init(g_f_center_x, g_f_center_y, g_val); 880 | make_new = false; 881 | } 882 | } 883 | } 884 | } 885 | 886 | void AttractorSurface() 887 | { 888 | 889 | } 890 | 891 | void DiffusionSurface() 892 | { 893 | 894 | } 895 | 896 | void MirrorSurface() 897 | { 898 | 899 | } 900 | 901 | 902 | // === Main logic =========================================================== 903 | 904 | /** Calibrate the IR sensors 905 | * 906 | * Read four values from each sensor, separated by 100ms. Take the 907 | * average to be the max, which is used in Cell::senseIR to map all 908 | * IR readings to a canonical range. 909 | */ 910 | void calibrate() 911 | { 912 | uint16_t total_ir[NUM_CELLS]; 913 | 914 | for (int i = 0; i < NUM_CELLS; i++) total_ir[i] = 0; 915 | 916 | for (int rounds = 0; rounds < 4; rounds++) { 917 | for (int i = 0; i < NUM_CELLS; i++) { 918 | uint16_t raw = g_Cells[i]->rawIR(); 919 | total_ir[i] += raw; 920 | } 921 | delay(100); 922 | } 923 | 924 | for (int i = 0; i < NUM_CELLS; i++) { 925 | g_Cells[i]->setIRMax(total_ir[i] / 4); 926 | g_Cells[i]->setIRMin(140); 927 | CRGB gr = CRGB::Green; 928 | g_Cells[i]->setAllLEDs(gr); 929 | FastLED.show(); 930 | delay(10); 931 | } 932 | } 933 | 934 | void initialize() 935 | { 936 | for (int i = 0; i < NUM_CELLS; i++) { 937 | g_Cells[i] = new Cell(g_CellMap[i]); 938 | } 939 | 940 | // -- Calibrate the IR sensors 941 | calibrate(); 942 | 943 | // -- Initialize the surface view 944 | initializeSurface(); 945 | } 946 | 947 | void changeToPattern(Mode newmode) 948 | { 949 | g_Mode = newmode; 950 | 951 | for (int i = 0; i < NUM_CELLS; i++) { 952 | g_Cells[i]->newPattern(); 953 | } 954 | 955 | Serial.print("New pattern "); 956 | Serial.println(newmode); 957 | } 958 | 959 | uint32_t last_change = 0; 960 | 961 | void setup() 962 | { 963 | delay(500); 964 | 965 | // -- Set up the pins 966 | /* 967 | pinMode(LED_PIN_1, OUTPUT); 968 | pinMode(LED_PIN_2, OUTPUT); 969 | pinMode(LED_PIN_3, OUTPUT); 970 | */ 971 | pinMode(17,OUTPUT); 972 | pinMode(16,OUTPUT); 973 | pinMode(4,OUTPUT); 974 | pinMode(2,OUTPUT); 975 | pinMode(15,OUTPUT); 976 | pinMode(12,OUTPUT); 977 | 978 | for (int i = 0; i < 4; i++) { 979 | pinMode(IR_INPUTS[i], INPUT); 980 | } 981 | 982 | pinMode(IR_CHANNEL_BIT_0, OUTPUT); 983 | pinMode(IR_CHANNEL_BIT_1, OUTPUT); 984 | pinMode(IR_CHANNEL_BIT_2, OUTPUT); 985 | pinMode(IR_CHANNEL_BIT_3, OUTPUT); 986 | 987 | Serial.begin(115200); 988 | delay(200); 989 | 990 | // -- Add all the LEDs 991 | LED_setup(); 992 | FastLED.setBrightness(g_Brightness); 993 | 994 | // -- Initialize the cells and calibrate 995 | 996 | fill_solid(g_LEDs, NUM_LEDS, CRGB::Yellow); 997 | FastLED.show(); 998 | delay(1000); 999 | 1000 | initialize(); 1001 | delay(500); 1002 | 1003 | fill_solid(g_LEDs, NUM_LEDS, CRGB::Black); 1004 | FastLED.show(); 1005 | delay(1000); 1006 | 1007 | Serial.println(); 1008 | Serial.println("Ready..."); 1009 | last_change = millis(); 1010 | } 1011 | 1012 | uint32_t g_total_time = 0; 1013 | uint32_t g_frame_count = 0; 1014 | 1015 | adel waitbutton(int pin) 1016 | { 1017 | abegin: 1018 | while (1) { 1019 | await (digitalRead(pin) == HIGH); 1020 | adelay (50); 1021 | if (digitalRead(pin) == HIGH) { 1022 | await (digitalRead(pin) == LOW); 1023 | afinish; 1024 | } 1025 | } 1026 | aend; 1027 | } 1028 | 1029 | adel changemode() 1030 | { 1031 | abegin: 1032 | while (1) { 1033 | andthen( waitbutton( MODE_PIN ) ); 1034 | 1035 | g_Mode = Mode((g_Mode + 1) % NUM_MODES); 1036 | changeToPattern(g_Mode); 1037 | /* 1038 | if (g_Mode == SolidMode) changeToPattern(ConfettiMode); 1039 | else if (g_Mode == ConfettiMode) changeToPattern(SpinnerMode); 1040 | else if (g_Mode == SpinnerMode) changeToPattern(GearMode); 1041 | else if (g_Mode == GearMode) changeToPattern(FireMode); 1042 | else if (g_Mode == FireMode) changeToPattern(SolidMode); 1043 | */ 1044 | } 1045 | 1046 | aend; 1047 | } 1048 | 1049 | adel table() 1050 | { 1051 | uint32_t start; 1052 | uint32_t end; 1053 | bool is_surface; 1054 | abegin: 1055 | 1056 | while (1) { 1057 | start = millis(); 1058 | is_surface = (g_Mode >= SurfaceMode); 1059 | 1060 | if (is_surface) { 1061 | if (g_Mode == SurfaceMode) CenterSurface(); 1062 | if (g_Mode == RippleMode) RippleSurface(); 1063 | if (g_Mode == DiffusionMode) DiffusionSurface(); 1064 | if (g_Mode == AttractorMode) AttractorSurface(); 1065 | if (g_Mode == MirrorMode) MirrorSurface(); 1066 | } 1067 | 1068 | // -- Sense the IR and render the pattern 1069 | for (int i = 0; i < NUM_CELLS; i++) { 1070 | if (g_Mode == SolidMode) g_Cells[i]->SolidPattern(); 1071 | if (g_Mode == ConfettiMode) g_Cells[i]->ConfettiPattern(); 1072 | if (g_Mode == SpinnerMode) g_Cells[i]->SpinnerPattern(400, 8000); 1073 | if (g_Mode == GearMode) g_Cells[i]->GearPattern(400, 8000); 1074 | if (g_Mode == FireMode) g_Cells[i]->FirePattern(30, 50); 1075 | if (is_surface) g_Cells[i]->SurfacePattern(); 1076 | } 1077 | 1078 | if (g_Cycle) { 1079 | // -- Periodically switch to a different mode 1080 | uint32_t cur_time = millis(); 1081 | if (cur_time - last_change > TIME_PER_PATTERN) { 1082 | last_change = cur_time; 1083 | if (g_Mode == SolidMode) changeToPattern(ConfettiMode); 1084 | else if (g_Mode == ConfettiMode) changeToPattern(SpinnerMode); 1085 | else if (g_Mode == SpinnerMode) changeToPattern(GearMode); 1086 | else if (g_Mode == GearMode) changeToPattern(FireMode); 1087 | else if (g_Mode == FireMode) changeToPattern(SolidMode); 1088 | } 1089 | } 1090 | 1091 | FastLED.show(); 1092 | end = millis(); 1093 | g_total_time += (end - start); 1094 | g_frame_count++; 1095 | 1096 | /** Just for testing: compute the time to render a frame 1097 | if (g_frame_count == 40) { 1098 | float fps = ((float) g_total_time) / ((float) g_frame_count); 1099 | Serial.print("ms per frame: "); 1100 | Serial.println(fps); 1101 | g_frame_count = 0; 1102 | g_total_time = 0; 1103 | } 1104 | */ 1105 | 1106 | adelay(1000/FRAMES_PER_SECOND); 1107 | } 1108 | 1109 | aend; 1110 | } 1111 | 1112 | void loop() 1113 | { 1114 | arepeat( table() ); 1115 | arepeat( changemode() ); 1116 | } 1117 | --------------------------------------------------------------------------------