#include <Adafruit_NeoPixel.h>
#define PIN 6 // change to whatever pin you are using to send data to the LED array. This is now replaced by the array panelPin[]
#define N_LEDS 256 // number of LEDs in one panel. Originally this was the total number of LEDs in all panels.
#define imageRows 16 // the size of the image that is going to be displayed. Height normally 16.
#define imageCols 167 // Change this number depending on the width of your scrolling image.
// number of rows and columns in each LED panel
#define numRows 16
#define numCols 16
#define eyeRows 16
#define eyeCols 48
// #define moonRows 48
// #define moonCols 48
// #define batUpRows 16
// #define batUpCols 19
// #define batDownRows 16
// #define batDownCols 19
// #define witchRows 16
// #define witchCols 19
// #define fireDuration 100
// #define fireCooldown 17
#define brightness 100
Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_LEDS, PIN, NEO_GRB + NEO_KHZ800);
// int numPanels = N_LEDS/numRows/numCols;
int numPanels = 3;
int panelPin[3] = {5,6,7}; // Panel 0 connected to pin 5, panel 1 to pin 6, panel 2 to pin 7.
int displayCols = numCols*numPanels;
int offset = displayCols;
int offset2 = 0;
int count = 0;
int pixel = 0;
int imageStartCol = 0;
int imageRow = 0;
int imageRow2 = 0;
int imageCol = 0;
int witchRow = 0;
int witchCol = 0;
int fireValue = 0;
int r=0;
int g=0;
int b=0;
int red=0;
int green=0;
int blue=0;
int hueIndex = 0;
int hue1 = 0;
int hue2 = 0;
int x=0;
int y=0;
int panel = 0;
int flag = 1; // alternate between witch and bat, and change rainbow
//define the width and height of the screen and the buffers
//#define screenHeight numRows
int w = displayCols;
int h = numRows;
byte fire[17][49]; // this will contain numbers from 0 to 255. Only 16x48 actually used, extra rows contain zeros for update calculations.
const uint8_t palette[64][3] PROGMEM =
{{0, 0, 0}, {0, 1, 1}, {0, 2, 1}, {0, 4, 2},
{5, 5, 3}, {10, 5, 4}, {15, 4, 3}, {25, 3, 2},
{33, 3, 1}, {40, 2, 1}, {48, 2, 0}, {55, 1, 0},
{63, 0, 0}, {63, 0, 0}, {63, 3, 0}, {63, 7, 0},
{63, 10, 0}, {63, 13, 0}, {63, 16, 0}, {63, 20, 0},
{63, 23, 0}, {63, 26, 0}, {63, 29, 0}, {63, 33, 0},
{63, 36, 0}, {63, 39, 0}, {63, 39, 0}, {63, 40, 0},
{63, 40, 0}, {63, 41, 0}, {63, 42, 0}, {63, 42, 0},
{63, 43, 0}, {63, 44, 0}, {63, 44, 0}, {63, 45, 0},
{63, 45, 0}, {63, 46, 0}, {63, 47, 0}, {63, 47, 0},
{63, 48, 0}, {63, 49, 0}, {63, 49, 0}, {63, 50, 0},
{63, 51, 0}, {63, 51, 0}, {63, 52, 0}, {63, 53, 0},
{63, 53, 0}, {63, 54, 0}, {63, 55, 0}, {63, 55, 0},
{63, 56, 0}, {63, 57, 0}, {63, 57, 0}, {63, 58, 0},
{63, 58, 0}, {63, 59, 0}, {63, 60, 0}, {63, 60, 0},
{63, 61, 0}, {63, 62, 0}, {63, 62, 0}, {63, 63, 0}};
// the image data is stored in program memory (flash memory), because there is not enough RAM in an Arduino to store the whole image.
const uint8_t hhw[imageRows][imageCols][3] PROGMEM =
#include "faces.h" // Happy Halloween written in white with black background
;
// const uint8_t eyes[eyeRows][eyeCols][3] PROGMEM =
// #include "faces.h" // cat eyes, sized to fill the 3 LED panels
// ;
void setup() {
strip.begin();
for (panel = 0; panel < numPanels; panel++) {
strip.setPin(panelPin[panel]);
strip.show(); // Turn OFF all pixels ASAP
}
strip.setBrightness(brightness); // Set BRIGHTNESS
}
void loop() {
// scrolling text followed by fire effect
// make sure the fire buffer is zero in the beginning
for(y = 0; y <= h; y++) {
for(x = 0; x <= w; x++) {
fire[y][x] = 0;
}
}
for (offset = displayCols; offset > -imageCols-fireDuration; offset--) {
offset2 = offset+imageCols; // this quantity is used several times, store it to save computation time
// compute fire effect first. Fire effect only applies for columns greater than (imageCols-offset) or 0, and less than displayCols
if (offset > -imageCols-fireDuration+fireCooldown) {
for(x = max(offset2,0); x < w; x++) { // start new fires
if (fire[h - 1][x] > 0) {
fire[h - 1][x] = random(3)*16+207;
}
else {
fire[h - 1][x] = random(15)*16;
}
if (fire[h - 1][x] < 220) fire[h - 1][x] = 0;
}
}
else { // no new fires for final cooldown period
for (x=0; x<w; x++) {
fire[h - 1][x] = 0;
}
}
//do the fire calculations for every pixel, from top to bottom
for(y = 0; y < h - 1; y++) {
for(x = max(offset2,0); x < w; x++) {
fire[y][x] =
byte((int(fire[y + 1][(x - 1 + w) % w])
+ int(fire[y + 1][x])
+ int(fire[y + 1][x + 1])
+ int(fire[y + 2][x]))
/5);
}
}
// now update the display, one pixel at a time
for(panel=0; panel<numPanels; panel++) {
pixel = 0;
imageStartCol = panel*numCols-offset;
imageRow = numRows - 1;
for (y=0; y<(numRows>>1); y++) {
hueIndex = ((offset2)<<2) + (imageRow<<4)*flag;
if (hueIndex>=384) {
hueIndex -= 384;
}
if (hueIndex>=384) {
hueIndex -= 384;
}
if (hueIndex>=384) {
hueIndex -= 384;
}
// create rainbow hues that transition from red (hueIndex 0) to green (hueIndex 128) to blue "hueIndex 256) to red (hueIndex 384)
hue1 = max(hueIndex,128);
hue2 = max(hueIndex,256);
// red = 128-hueIndex + hue1-128 + hue2-256; // red, green, and blue only go to 128 to avoid overflow when multiplying by 255
red = hue1 - hueIndex + hue2-256; // simplify
// green = hueIndex - ((hue1-128)<<1) + hue2-256;
green = hueIndex - ((hue1)<<1) + hue2; // simplify
// blue = hue1-128 - ((hue2-256)<<1);
blue = hue1+384 - ((hue2)<<1); // simplify
for (x=0; x<numCols; x++) {
imageCol = imageStartCol + x;
if ((imageCol < 0)) { // Draw black (text not present yet)
r = 0;
g = 0;
b = 0;
}
else if ((imageCol >= imageCols)) { // draw fire
fireValue = int(fire[imageRow][x+panel*numCols]);
if (fireValue < 64) {
r = int(pgm_read_byte_near(&palette[fireValue][0]))<<2;
g = int(pgm_read_byte_near(&palette[fireValue][1]))<<2;
b = int(pgm_read_byte_near(&palette[fireValue][2]))<<2;
}
else {
r = 255;
g = 255;
b = 255;
}
}
else { // draw rainbow text
r = (int(pgm_read_byte_near(&hhw[imageRow][imageCol][0]))*red)>>7; // use the bitshift function to divide by 128
g = (int(pgm_read_byte_near(&hhw[imageRow][imageCol][1]))*green)>>7;
b = (int(pgm_read_byte_near(&hhw[imageRow][imageCol][2]))*blue)>>7;
}
drawPixel(strip.Color(r, g, b)); // Draw pixel
pixel++;
}
imageRow--;
hueIndex = (offset2<<2) + (imageRow<<4)*flag;
if (hueIndex>=384) {
hueIndex -= 384;
}
if (hueIndex>=384) {
hueIndex -= 384;
}
if (hueIndex>=384) {
hueIndex -= 384;
}
hue1 = max(hueIndex,128);
hue2 = max(hueIndex,256);
// red = 128-hueIndex + hue1-128 + hue2-256;
red = hue1 - hueIndex + hue2-256; // simplify
// green = hueIndex - ((hue1-128)<<1) + hue2-256;
green = hueIndex - ((hue1)<<1) + hue2; // simplify
// blue = hue1-128 - ((hue2-256)<<1);
blue = hue1+384 - ((hue2)<<1); // simplify
for (x=numCols-1; x>=0; x--) {
imageCol = imageStartCol + x;
if ((imageCol < 0)) {
r = 0;
g = 0;
b = 0;
}
else if ((imageCol >= imageCols)) { // draw fire
fireValue = int(fire[imageRow][x+panel*numCols]);
if (fireValue < 64) {
r = int(pgm_read_byte_near(&palette[fireValue][0]))<<2;
g = int(pgm_read_byte_near(&palette[fireValue][1]))<<2;
b = int(pgm_read_byte_near(&palette[fireValue][2]))<<2;
}
else {
r = 255;
g = 255;
b = 255;
}
}
else { // draw rainbow text
r = (int(pgm_read_byte_near(&hhw[imageRow][imageCol][0]))*red)>>7;
g = (int(pgm_read_byte_near(&hhw[imageRow][imageCol][1]))*green)>>7;
b = (int(pgm_read_byte_near(&hhw[imageRow][imageCol][2]))*blue)>>7;
}
drawPixel(strip.Color(r, g, b)); // Draw image pixel
pixel++;
}
imageRow--;
}
strip.setPin(panelPin[panel]);
strip.show();
}
// delay(0); // you can change this number to change the scrolling speed. Smaller number will be faster scrolling.
}
// show eyes
for (offset = 0; offset<3; offset++) { // re-using offset variable from the text display for eye blink counter
for(panel=0; panel<numPanels; panel++) {
pixel = 0;
imageStartCol = panel*numCols;
imageRow = numRows - 1;
for (y=0; y<numRows>>1; y++) {
for (x=0; x<numCols; x++) {
imageCol = imageStartCol + x;
r = int(pgm_read_byte_near(&eyes[imageRow][imageCol][0]));
g = int(pgm_read_byte_near(&eyes[imageRow][imageCol][1]));
b = int(pgm_read_byte_near(&eyes[imageRow][imageCol][2]));
drawPixel(strip.Color(r, g, b)); // Draw pixel
pixel++;
}
imageRow--;
for (x=numCols-1; x>=0; x--) {
imageCol = imageStartCol + x;
r = int(pgm_read_byte_near(&eyes[imageRow][imageCol][0]));
g = int(pgm_read_byte_near(&eyes[imageRow][imageCol][1]));
b = int(pgm_read_byte_near(&eyes[imageRow][imageCol][2]));
drawPixel(strip.Color(r, g, b)); // Draw image pixel
pixel++;
}
imageRow--;
}
strip.setPin(panelPin[panel]);
strip.show();
}
// strip.show();
delay(1000); // delay between blinks
// for (pixel = 0; pixel < N_LEDS; pixel++) { // blink eyes
// r = 0;
// g = 0;
// b = 0;
// drawPixel(strip.Color(r, g, b)); // Draw image pixel
// }
for(panel=0; panel<numPanels; panel++) { // blink eyes
strip.setPin(panelPin[panel]);
strip.clear();
strip.show();
}
// strip.show();
delay(80);
}
// rising moon with flying witch passing in front
// for (count = -numRows<<1; count < (moonRows<<1)-1; count++) {
// offset = (count>>1);
// offset2 = ((count+1)>>1);
// // now update the display, one pixel at a time
// // pixel = 0;
// for(panel=0; panel<numPanels; panel++) {
// pixel = 0;
// imageStartCol = panel*numCols;
// imageRow = numRows - 1 + offset;
// imageRow2 = numRows - 1 + offset2;
// witchRow = numRows - 1;
// for (y=0; y<numRows>>1; y++) {
// for (x=0; x<numCols; x++) {
// imageCol = imageStartCol + x;
// witchCol = imageCol + count - moonRows;
// if ((imageRow < 0) || (imageRow2 >= moonRows)) { // Draw black (moon not present yet)
// r = 0;
// g = 0;
// b = 0;
// }
// else if ((witchCol >= 0) && (witchCol < witchCols)) { // draw moon with witch or bat
// if (flag == 1) { // draw moon with witch
// r = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][0]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][0])))>>1)*long(pgm_read_byte_near(&witch[witchRow][witchCol][0]))>>8);
// g = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][1]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][1])))>>1)*long(pgm_read_byte_near(&witch[witchRow][witchCol][1]))>>8);
// b = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][2]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][2])))>>1)*long(pgm_read_byte_near(&witch[witchRow][witchCol][2]))>>8);
// }
// else { // draw moon with bat
// if (offset == offset2) {
// r = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][0]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][0])))>>1)*long(pgm_read_byte_near(&bu[witchRow][witchCol][0]))>>8);
// g = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][1]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][1])))>>1)*long(pgm_read_byte_near(&bu[witchRow][witchCol][1]))>>8);
// b = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][2]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][2])))>>1)*long(pgm_read_byte_near(&bu[witchRow][witchCol][2]))>>8);
// }
// else {
// r = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][0]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][0])))>>1)*long(pgm_read_byte_near(&bd[witchRow][witchCol][0]))>>8);
// g = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][1]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][1])))>>1)*long(pgm_read_byte_near(&bd[witchRow][witchCol][1]))>>8);
// b = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][2]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][2])))>>1)*long(pgm_read_byte_near(&bd[witchRow][witchCol][2]))>>8);
// }
// }
// }
// else { // draw moon
// r = (int(pgm_read_byte_near(&moon[imageRow][imageCol][0]))+int(pgm_read_byte_near(&moon[imageRow2][imageCol][0])))>>1;
// g = (int(pgm_read_byte_near(&moon[imageRow][imageCol][1]))+int(pgm_read_byte_near(&moon[imageRow2][imageCol][1])))>>1;
// b = (int(pgm_read_byte_near(&moon[imageRow][imageCol][2]))+int(pgm_read_byte_near(&moon[imageRow2][imageCol][2])))>>1;
// }
// drawPixel(strip.Color(r, g, b)); // Draw pixel
// pixel++;
// }
// imageRow--;
// imageRow2--;
// witchRow--;
// for (x=numCols-1; x>=0; x--) {
// imageCol = imageStartCol + x;
// witchCol = imageCol + count - moonRows;
// if ((imageRow < 0) || (imageRow2 >= moonRows)) {
// r = 0;
// g = 0;
// b = 0;
// }
// else if ((witchCol >= 0) && (witchCol < witchCols)) { // draw moon with witch or bat
// if (flag == 1) { // draw moon with witch
// r = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][0]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][0])))>>1)*long(pgm_read_byte_near(&witch[witchRow][witchCol][0]))>>8);
// g = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][1]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][1])))>>1)*long(pgm_read_byte_near(&witch[witchRow][witchCol][1]))>>8);
// b = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][2]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][2])))>>1)*long(pgm_read_byte_near(&witch[witchRow][witchCol][2]))>>8);
// }
// else { // draw moon with bat
// if (offset == offset2) {
// r = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][0]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][0])))>>1)*long(pgm_read_byte_near(&bu[witchRow][witchCol][0]))>>8);
// g = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][1]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][1])))>>1)*long(pgm_read_byte_near(&bu[witchRow][witchCol][1]))>>8);
// b = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][2]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][2])))>>1)*long(pgm_read_byte_near(&bu[witchRow][witchCol][2]))>>8);
// }
// else {
// r = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][0]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][0])))>>1)*long(pgm_read_byte_near(&bd[witchRow][witchCol][0]))>>8);
// g = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][1]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][1])))>>1)*long(pgm_read_byte_near(&bd[witchRow][witchCol][1]))>>8);
// b = int(((long(pgm_read_byte_near(&moon[imageRow][imageCol][2]))+long(pgm_read_byte_near(&moon[imageRow2][imageCol][2])))>>1)*long(pgm_read_byte_near(&bd[witchRow][witchCol][2]))>>8);
// }
// }
// }
// else { // draw moon
// r = (int(pgm_read_byte_near(&moon[imageRow][imageCol][0]))+int(pgm_read_byte_near(&moon[imageRow2][imageCol][0])))>>1;
// g = (int(pgm_read_byte_near(&moon[imageRow][imageCol][1]))+int(pgm_read_byte_near(&moon[imageRow2][imageCol][1])))>>1;
// b = (int(pgm_read_byte_near(&moon[imageRow][imageCol][2]))+int(pgm_read_byte_near(&moon[imageRow2][imageCol][2])))>>1;
// }
// drawPixel(strip.Color(r, g, b)); // Draw image pixel
// pixel++;
// }
// imageRow--;
// imageRow2--;
// witchRow--;
// }
// strip.setPin(panelPin[panel]);
// strip.show();
// }
// // strip.show();
// delay(0); // you can change this number to change the scrolling speed. Smaller number will be faster scrolling.
// }
flag++;
if (flag>2) flag = 1;
}
static void drawPixel(uint32_t c) {
strip.setPixelColor(pixel , c); // Draw new pixel
}