// NeoMatrix example for single NeoPixel Shield.
// By Torsten Jaeger <www.spassantechnik.de> Nov-Dez, 2023
// Contains code (c) Adafruit, license BSD
// The idea for this animation comes from the old starship Enterprise,
// where such a bar animation was sometimes shown on computer screens
// to visualise the activity of the computer.
// You can see what I mean in this clip on YouTube: https://www.youtube.com/watch?v=UDdxCHii_ZE
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
#include <EEPROM.h>
// EEPROM address to save the value to
#define EEPROM_VALUE_ADDR 0
// Specific value which is placed in front to each value in the EEPROM
// to make sure that this value belongs to this sketch
#define EEPROM_VALUE_SIGNATURE 777
#define DEBUG true
#define NEOPIXEL_PIN 6 // Which pin on the Arduino is connected to the NeoPixels?
#define PUSH_BUTTON_PIN 2 // Must be connected to a PIN that supports interrupts
#define MATRIX_WIDTH 16 // Size in X direction
#define MATRIX_HEIGHT 16 // Size in Y direction
#define MIN_BAR_LENGTH 3
#define MAX_BAR_LENGTH 10
#define MAX_BARS_ON_MATRIX 7 // The total number of bars that may be drawn at once. A reasonable value is less than 10
#define MAX_BAR_COLORS 4
#define ANIMATION_DELAY_NORMAL 500
// When setting up the NeoPixel library, we tell it how many pixels,
// and which pin to use to send signals. Note that for older NeoPixel
// strips you might need to change the third parameter -- see the
// strandtest example for more information on possible values.
Adafruit_NeoPixel pixels(MATRIX_WIDTH * MATRIX_HEIGHT, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
byte occupied_field[MATRIX_WIDTH][MATRIX_HEIGHT]; // To know which pixels have already been drawn so that we can avoid line overlaps
float eeprom_value = 0.0;
int current_colors = MAX_BAR_COLORS;
volatile long lastButtonAction = 0;
boolean buttonPressedState = false;
unsigned long start_millis = millis();
unsigned long max_millis = 0;
unsigned long duration = 0;
int counter = 0;
// ----------------------------------------------
// |
// ,---.,---.|--- . .,---.
// `---.|---'| | || |
// `---'`---'`---'`---'|---'
// |
// ----------------------------------------------
void setup() {
if (DEBUG) {
Serial.begin(9600);
dumpEeprom();
}
randomSeed(analogRead(0));
pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
pinMode(PUSH_BUTTON_PIN, INPUT_PULLUP);
/**
It has been shown that detecting a keystroke, via interrupt, is not the simplest,
but the most reliable method of detecting a keystroke.
When the button is pressed it calls the function buttenPressed. In this a flag
variable is set that the button was pressed. Which operation is then executed due
to the button press is decided later in the loop function.
*/
attachInterrupt(digitalPinToInterrupt(PUSH_BUTTON_PIN), buttonPressedFunction, LOW);
getFromEeprom(EEPROM_VALUE_ADDR, &eeprom_value);
if (eeprom_value < 255) {
current_colors = eeprom_value;
}
}
// -----------------------------------------------
// ISR which is called when the button was pressed
// -----------------------------------------------
void buttonPressedFunction()
{
// We are adding a type of debouncing here
if (millis() - lastButtonAction > 200) {
lastButtonAction = millis();
buttonPressedState = true;
}
}
// ----------------------------------------------
// |
// | ,---.,---.,---.
// | | || || |
// `---'`---'`---'|---'
// |
// ----------------------------------------------
void loop() {
start_millis = millis();
pixels.clear(); // Set all pixel colors to 'off'
for (int i = 0; i < MATRIX_WIDTH; i++) {
for (int j = 0; j < MATRIX_HEIGHT; j++) {
occupied_field[i][j] = 0;
}
}
int color = 0;
int tmp_x, tmp_y;
int x1_bar, x2_bar, y1_bar, y2_bar;
int r, g, b;
int a_value;
// the button was pressed
if (buttonPressedState) {
buttonPressedState = false;
current_colors--;
if (current_colors <= -1) {
current_colors = MAX_BAR_COLORS;
}
eeprom_value = current_colors;
saveToEeprom(EEPROM_VALUE_ADDR, &eeprom_value);
}
for (int i = 0; i < MAX_BARS_ON_MATRIX; i++)
{
r = 0; g = 0; b = 0;
if (current_colors > 0) {
color = random(current_colors);
if (color == 0) {
r = 150; // red
} else if (color == 1) {
r = 150; g = 80; // orange
} else if (color == 2) {
g = 150; // green
} else if (color == 3) {
b = 150; g = 80; // blue
}
}
// Equal distribution of horizontal and vertical lines
if (random(10) % 2 == 0) {
// -------------------------------------
// Vertical bar
// -------------------------------------
x1_bar = random(1, MATRIX_WIDTH + 1);
x2_bar = x1_bar;
y1_bar = random(1, MATRIX_HEIGHT + 1);
y2_bar = random(y1_bar, MATRIX_HEIGHT + 1);
if (y2_bar - y1_bar < MIN_BAR_LENGTH) {
i--;
continue;
}
if (y2_bar - y1_bar > MAX_BAR_LENGTH) {
i--;
continue;
}
} else {
// -------------------------------------
// Horizontal bar
// -------------------------------------
y1_bar = random(1, MATRIX_HEIGHT + 1);
y2_bar = y1_bar;
x1_bar = random(1, MATRIX_WIDTH + 1);
x2_bar = random(x1_bar, MATRIX_WIDTH + 1);
if (x2_bar - x1_bar < MIN_BAR_LENGTH) {
i--;
continue;
}
if (x2_bar - x1_bar > MAX_BAR_LENGTH) {
i--;
continue;
}
}
// In the case that the line could not be drawn.
// Let's repeat the loop iteration and hope that the new
// random values lead to the successful drawing of a line.
if (drawLine(x1_bar, y1_bar, x2_bar, y2_bar, r, g, b) == false) {
i--;
continue;
}
}
pixels.show(); // Display all lines at once
// This section ensures that the animation always runs at the same speed.
// The longest animation duration is checked. If an animation is completed faster,
// the system waits until the difference to the longest duration is reached.
// This standardises the animation after a few iterations and makes it appear more uniform overall.
duration = millis() - start_millis;
if (duration > max_millis) {
max_millis = duration; // longest animation duration is checked
}
delay(max_millis - duration); // waits until the difference to the longest duration is reached
delay(ANIMATION_DELAY_NORMAL);
}
// ----------------------------------------------
// Places a pixel in the coordinate system that
// results from the dimensions of the matrix
// ----------------------------------------------
void setPixel(int x, int y, int r, int g, int b) {
// Converting the x/y coordinates into the one-dimensional LED array
int matrixPos = 0;
if (x % 2 == 0) {
matrixPos = MATRIX_WIDTH * x - 1 - (y - 1);
} else {
matrixPos = MATRIX_WIDTH * (x - 1) + (y - 1);
}
pixels.setPixelColor(matrixPos, pixels.Color(r, g, b));
// Here we set the field in our 2-dimensional array that are occupied by an active pixel.
occupied_field[x - 1][y - 1] = 1;
}
// ----------------------------------------------
// Paints a horizontal or vertical line with the
// corresponding colour from the RGB values
// ----------------------------------------------
boolean drawLine(int x1, int y1, int x2, int y2, int r, int g, int b) {
for (int y = y1; y <= y2; y++)
{
for (int x = x1; x <= x2; x++)
{
// Detect already drawn pixels on our line path
if (occupied_field[x - 1][y - 1] == 1) {
return false; // Don't draw lines that overwrite others
}
}
}
// OK; there are no lines already drawn on our path. We can now draw our line
for (int y = y1; y <= y2; y++)
{
for (int x = x1; x <= x2; x++)
{
//if ((tmp_x <= tmp_y && tmp_x >= tmp_y) || (x1 == x2 || y1 == y2)) {
setPixel(x, y, r, g, b);
}
}
return true; // The line was successfully drawn
}
// ----------------------------------------------
// Function that reads a value from the EEPROM
// ----------------------------------------------
void getFromEeprom(int addr, float *value)
{
int type = 0;
if (EEPROM.get(addr, type) != EEPROM_VALUE_SIGNATURE) {
return; // not our data signature
}
addr += sizeof(EEPROM_VALUE_SIGNATURE);
EEPROM.get(addr, *value);
if (DEBUG) {
Serial.print("Value from EEPROM: ");
Serial.println(*value);
}
}
// ----------------------------------------------
// Function stores a specific value to the EEPROM
// ----------------------------------------------
void saveToEeprom(int addr, float *value)
{
EEPROM.put(addr, EEPROM_VALUE_SIGNATURE);
addr += sizeof(EEPROM_VALUE_SIGNATURE);
EEPROM.put(addr, *value);
if (DEBUG) {
Serial.print("Value to EEPROM: ");
Serial.println(*value);
}
}
// ----------------------------------------------
// Shows a small part of the EEPROM content in
// hexadecimal representation for debug purposes
// Example output:
// 09 03 00 00 60 40 FF FF FF FF
// FF FF FF FF FF FF FF FF FF FF
// FF FF FF FF FF FF FF FF FF FF
// ----------------------------------------------
void dumpEeprom()
{
Serial.println("---[EEPROM content]-----------");
for (int i = 1; i <= 100; i++) {
if (EEPROM[i - 1] < 15) {
Serial.print("0");
}
Serial.print(EEPROM[i - 1], HEX);
Serial.print(" ");
if (i % 10 == 0) {
Serial.println("");
}
}
Serial.println("------------------------------");
}