#include <FastLED.h>
#include <SD.h>
#include <AsyncTimer.h>
#include "LCDIC2.h"
LCDIC2 lcd(0x27, 16, 2);
#define POTENTIOMETER_R 0
#define POTENTIOMETER_G 1
#define POTENTIOMETER_B 2
#define POTENTIOMETER_SPEED 3
#define POTENTIOMETER_RANDOM_COLOR 4
#define POTENTIOMETER_NOISE 5
#define LED_PIN 3
#define CS_PIN 10
#define NUM_LEDS 30
#define MAX_LINES 10
CRGB leds[NUM_LEDS];
String* textLines = nullptr;
uint8_t numLines = 0;
uint8_t potRed;
uint8_t potGreen;
uint8_t potBlue;
uint16_t potSpeed = 400;
uint8_t potRandomColor;
uint8_t potNoise = 0;
uint16_t zPosition = 0;
// Grouping of LEDS and Letters
struct LEDGroup {
uint8_t start;
uint8_t end;
bool active;
};
struct Letter {
char name;
const uint8_t* groups;
uint8_t groupCount;
};
// LED groups definiition
LEDGroup groups[] = {
{ 0, 9, false }, { 10, 19, false }, { 20, 29, false }
};
// Active groups for each letter
const uint8_t groupsA[] = { 0, 1, 2};
const uint8_t groupsB[] = { 0};
const uint8_t groupsC[] = { 2};
// Define all letters
Letter letters[] = {
{ 'a', groupsA, sizeof(groupsA) / sizeof(groupsA[0]) },
{ 'b', groupsB, sizeof(groupsB) / sizeof(groupsB[0]) },
{ 'c', groupsC, sizeof(groupsC) / sizeof(groupsC[0]) }
};
AsyncTimer timer;
uint8_t currentLine = 0;
uint16_t currentLetter = 0;
// Colors
DEFINE_GRADIENT_PALETTE(NorthernLightsPalette) {
0, 0, 207, 82,
62, 3, 46, 62,
143, 25, 100, 106,
192, 0, 198, 144,
255, 0, 223, 150
};
CRGBPalette16 currentPalette = CRGBPalette16(NorthernLightsPalette);
void setup() {
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
Serial.begin(9600);
lcd.begin(16, 2);
pinMode(POTENTIOMETER_R, OUTPUT);
pinMode(POTENTIOMETER_G, OUTPUT);
pinMode(POTENTIOMETER_B, OUTPUT);
pinMode(POTENTIOMETER_SPEED, OUTPUT);
pinMode(POTENTIOMETER_RANDOM_COLOR, OUTPUT);
pinMode(POTENTIOMETER_NOISE, OUTPUT);
initializeSDCard();
startTextAnimation();
}
void loop() {
timer.handle(); // Handle all timer events
displayLinesWithScroll(currentLine);
/* --- READ POTENTIOMETERS --- */
potRed = map(analogRead(POTENTIOMETER_R), 0, 1023, 0, 255);
potGreen = map(analogRead(POTENTIOMETER_G), 0, 1023, 0, 255);
potBlue = map(analogRead(POTENTIOMETER_B), 0, 1023, 0, 255);
potSpeed = map(analogRead(POTENTIOMETER_SPEED), 0, 1023, 1000, 100);
potRandomColor = map(analogRead(POTENTIOMETER_RANDOM_COLOR), 0, 1023, 0, 255);
potNoise = map(analogRead(POTENTIOMETER_NOISE), 0, 1023, 0, 255);
EVERY_N_MILLIS(100) {
for (int i = 0; i < sizeof(groups) / sizeof(groups[0]); i++) {
if (groups[i].active) {
// Turn on LEDs in the active group
for (int j = groups[i].start; j <= groups[i].end; j++) {
uint8_t noise = inoise8(j * 50, 0, zPosition);
uint8_t dynamicHue = (millis() / 10 + j * 10) % 255;
CRGB randomColor = CHSV(dynamicHue, 255, 255);
CRGB color1 = blend(CRGB(potRed, potGreen, potBlue), randomColor, potRandomColor);
CRGB color2 = ColorFromPalette(currentPalette, noise);
leds[j] = blend(color1, color2, potNoise);
}
} else {
// Turn off LEDs in the inactive group
for (int j = groups[i].start; j <= groups[i].end; j++) {
leds[j] = CRGB::Black;
/* uint8_t randomN = random(0, 255);
if (randomN >= potRandomColor) {
uint8_t noise = inoise8(j * 50, 0, zPosition);
CRGB color1 = blend(CRGB(potRed, potGreen, potBlue), CRGB::Black, 200);
CRGB color2 = ColorFromPalette(currentPalette, noise);
leds[j] = blend(color1, color2, potNoise);
} else {
leds[j] = CRGB::Black;
} */
}
}
}
}
EVERY_N_MILLISECONDS(10) {
zPosition += 5;
}
FastLED.show(); // Update the LEDs
}
// Function to find a letter by name
Letter* findLetter(char letter) {
for (int i = 0; i < sizeof(letters) / sizeof(Letter); i++) {
if (letters[i].name == letter) {
return &letters[i];
}
}
return nullptr;
}
// Function to light up each letter in a line
void lightUpNextLetter() {
if (currentLine >= numLines) {
currentLine = 0; // Loop back to the first line after the last
}
String line = textLines[currentLine];
if (currentLetter < line.length()) {
char letter = line[currentLetter];
lightUpLetter(letter); // Light up the current letter
currentLetter++;
timer.setTimeout(lightUpNextLetter, potSpeed); // Set the next letter
} else {
lightUpLetter(' ');
currentLetter = 0; // Reset letter index
currentLine++;
timer.setTimeout(lightUpNextLetter, potSpeed * 4); // Delay before the next line
}
}
// Function to light up a letter
void lightUpLetter(char letter) {
// Turn off all groups initially
for (int i = 0; i < sizeof(groups) / sizeof(groups[0]); i++) {
groups[i].active = false;
}
// Find the letter and activate the corresponding groups
Letter* l = findLetter(letter);
if (l != nullptr) {
for (int i = 0; i < l->groupCount; i++) {
int groupIndex = l->groups[i];
groups[groupIndex].active = true;
}
}
}
// Start the text animation
void startTextAnimation() {
timer.setTimeout(lightUpNextLetter, 0); // Start immediately
}
void initializeSDCard() {
Serial.print("Initializing SD card... ");
if (!SD.begin(CS_PIN)) {
Serial.println("Card initialization failed!");
while (true);
}
Serial.println("initialization done.");
// Open and read the file containing text
File textFile = SD.open("abc.csv");
if (textFile) {
textLines = new String[MAX_LINES]; // Allocate memory for the lines
while (textFile.available() && numLines < MAX_LINES) {
String line = textFile.readStringUntil('\n');
line.trim(); // Remove trailing whitespace
if (line.length() > 0) {
textLines[numLines++] = line; // Store the line
}
}
textFile.close();
} else {
Serial.println("Error opening abc.csv!");
}
Serial.println("Lines loaded from SD card:");
for (int i = 0; i < numLines; i++) {
Serial.println(textLines[i]);
}
}
void displayLinesWithScroll(uint8_t currentLine) {
static int scrollPos = 0; // Tracks the scroll position for both rows
static unsigned long lastScrollTime = 0; // Timer to control scroll speed
const int scrollDelay = 300; // Delay in milliseconds for scrolling
// Handle text retrieval
String currentText = textLines[currentLine];
String nextText = (currentLine + 1 < numLines) ? textLines[currentLine + 1] : " ";
// Calculate max scroll positions
int maxScrollCurrent = max(0, currentText.length() - 16);
int maxScrollNext = max(0, nextText.length() - 16);
// Only update scrolling at a timed interval
if (millis() - lastScrollTime > scrollDelay) {
lastScrollTime = millis(); // Reset timer
// Display current line (first row)
lcd.setCursor(0, 0);
lcd.print(" "); // Clear the row
lcd.setCursor(0, 0);
lcd.print(currentText.substring(scrollPos, scrollPos + 16));
// Display next line (second row)
lcd.setCursor(0, 1);
lcd.print(" "); // Clear the row
lcd.setCursor(0, 1);
lcd.print(nextText.substring(scrollPos, scrollPos + 16));
// Increment scroll position
if (scrollPos < max(maxScrollCurrent, maxScrollNext)) {
scrollPos++;
} else {
scrollPos = 0; // Reset scrolling when done
}
}
}