// Christmas Tree RGB
// Equipo 7
// Tecnología Digital | 2024-2s
// Melody change when button is pressed
// You can change leds and music behavior on "Behavior Options"
#include <ArduinoTrace.h>
#define UP 0
#define DOWN 1
// -------------------- Structures -----------------------
struct Note {
int frequency;
int duration;
};
struct Melody {
Note* sheet; // Matrix of music sheet. Ex: Corchea Do - Blanca Re -> {{Do[1], c}, {Re[1], b}}
int length;
int tempo;
};
// ------------------ Behavior Options ------------------------
const bool melodyNeverEnds = true;
bool ledsSyncWithMelody = true;
// -------------------- Leds RGB (Pins) -----------------------
const int rgb_1[] = {32, 33, 25}; // D32 - red, D33 - green, D25 - blue
const int rgb_2[] = {26, 27, 14}; // D26 - red, D27 - green, D14 - blue
const int rgb_3[] = {15, 2, 4}; // D15 - red, D2 - green, D4 - blue
const int rgb_4[] = {21, 22, 23}; // D21 - red, D22 - green, D23 - blue
// -------------------- Leds (Pins) ----------------------------
const int led_yellow = 12; // D12
const int led_red = 13; // D13
// -------------------- Other (Pins) ----------------------------
const int buzzerPin = 18; // D18
const int buttonPin = 19; // D19
const int buttonPinMute = 5; // D5
// ------------- Music Note Values (ms) -------------------
int tempo = 300; // Duration of a whole note (ms)
int getMusicValue(int times) {
if(times == 1006) return (tempo/2) + tempo; // Dotted whole note (6 times) - redonda con puntillo
if(times == 1003) return ((tempo/2)/2) + (tempo/2); // Dotted half note (3 times) - blanca con puntillo
if(times == 10012) return ((tempo/4)/2) + (tempo/4); // Dotted Quarter note (1 1/2 times) - negra con puntillo
return tempo/times;
// Whole note (times = 1) - redonda
// Half note (times = 2) - blanca
// Quarter note (times = 4) - negra
// Eigth note (times = 8) - corchea
// Sixteenth note (times = 16) - semicorchea
}
// ------------- Music Notes (Hz Frecuencies) ------------------
// Sub octava, octava, octava 1, octava 2
const int Do[] = {131, 262, 523, 1047};
const int Re[] = {147, 294, 587, 1174};
const int Mi[] = {165, 330, 659, 1318};
const int Fa[] = {175, 349, 698, 1397};
const int Sol[] = {196, 392, 784, 1568};
const int La[] = {220, 440, 880, 1760};
const int Si[] = {247, 494, 987, 1975};
const int Do_s[] = {139, 277, 554, 1109};
const int Re_s[] = {156, 311, 622, 1245};
const int Fa_s[] = {185, 370, 740, 1480};
const int Sol_s[] = {208, 415, 831, 1661};
const int La_s[] = {233, 466, 932, 1864};
// ----------------- Time vars (millis()) ------------------------
// For music
unsigned long previousMusicMillis = 0;
int currentNoteIndex = 0;
bool isPlaying = true;
Melody* currentMelody = nullptr;
// For colors
unsigned long previousColorMillis = 0;
int colorChangeDuration = 200;
unsigned long previousColorStarMillis = 0;
int fadeStarDuration = 20;
const int minPWM = 3;
const int maxPWM = 200;
byte fadeDirection = UP;
int fadeValue = 0;
byte fadeIncrement = 2;
unsigned long lastUpdateTime = 0;
int currentColor = 0; // ActualColor (0: rojo, 1: verde, 2: azul)
int brightness = 0;
int increment = 1;
int fadeColorDuration = 7;
unsigned long lastUpdateTime2 = 0;
int currentColor2 = 0; // ActualColor (0: rojo, 1: verde, 2: azul)
int brightness2 = 0;
int increment2 = 1;
int fadeColorDuration2 = 20;
// For buttons
unsigned long previousButtonMillis = 0;
unsigned long previousButtonMuteMillis = 0;
bool lastButtonState = LOW;
bool lastButtonMuteState = LOW;
const int checkBtn_delay = 100;
// ------------------------ Led helpers ---------------------------
void Color(int R, int G, int B, int led) {
const int* pinsLED = nullptr;
if (led == 1) pinsLED = rgb_1;
else if (led == 2) pinsLED = rgb_2;
else if (led == 3) pinsLED = rgb_3;
else if (led == 4) pinsLED = rgb_4;
if (pinsLED != nullptr) {
analogWrite(pinsLED[0], R); // Red
analogWrite(pinsLED[1], G); // Green
analogWrite(pinsLED[2], B); // Blue
} else {
Serial.println("Error: not valid LED");
}
}
void setupRGB(int led) {
const int* pinsLED = nullptr;
if (led == 1) pinsLED = rgb_1;
else if (led == 2) pinsLED = rgb_2;
else if (led == 3) pinsLED = rgb_3;
else if (led == 4) pinsLED = rgb_4;
if (pinsLED != nullptr) {
for (int i = 0; i < 3; i++) {
pinMode(pinsLED[i], OUTPUT);
}
} else {
Serial.println("Error: not valid LED");
}
}
void updateColorSequence() {
unsigned long currentMillis = millis();
if (currentMillis - previousColorMillis >= colorChangeDuration) {
// Change colors for all RGB LEDs
Color(random(255), random(255), random(255), 2);
Color(random(255), random(255), random(255), 3);
analogWrite(led_red, random(255));
// Reset the timer
previousColorMillis = currentMillis;
}
}
void changeColors() {
Color(random(255), random(255), random(255), 2);
Color(random(255), random(255), random(255), 3);
analogWrite(led_red, random(255));
}
void doTheFade() {
unsigned long currentMillis = millis();
if (currentMillis - previousColorStarMillis >= fadeStarDuration) {
if (fadeDirection == UP) {
fadeValue = fadeValue + fadeIncrement;
if (fadeValue >= maxPWM) {
// At max, limit and change direction
fadeValue = maxPWM;
fadeDirection = DOWN;
}
} else {
//if we aren't going up, we're going down
fadeValue = fadeValue - fadeIncrement;
if (fadeValue <= minPWM) {
// At min, limit and change direction
fadeValue = minPWM;
fadeDirection = UP;
}
}
// Only need to update when it changes
analogWrite(led_yellow, fadeValue);
// reset millis for the next iteration (fade timer only)
previousColorStarMillis += fadeStarDuration;
}
}
void fadeRGBLed() {
unsigned long currentMillis = millis();
// Actualiza para un cambio suave
if (currentMillis - lastUpdateTime >= fadeColorDuration) {
lastUpdateTime = currentMillis;
// Actualiza el brillo del color actual
brightness += increment;
// Si llega al máximo o mínimo brillo, cambia de dirección
if (brightness >= 255 || brightness <= 0) {
increment = -increment;
// Si estamos de regreso al nivel mínimo de brillo, cambia al siguiente color
if (brightness <= 0) {
currentColor = (currentColor + 1) % 3; // Alterna entre 0, 1 y 2
}
}
// Ajusta el brillo del LED RGB
int red = (currentColor == 0) ? brightness : 50;
int green = (currentColor == 1) ? brightness : 50;
int blue = (currentColor == 2) ? brightness : 50;
if(red == 0 && green == 0 && blue == 0){
red = 255;
green = 100;
blue = 50;
}
Color(red, green, blue, 1); // rgb_1
}
// Actualiza para un cambio suave del segundo pin
if (currentMillis - lastUpdateTime2 >= fadeColorDuration2) {
lastUpdateTime2 = currentMillis;
// Actualiza el brillo del color actual
brightness2 += increment2;
// Si llega al máximo o mínimo brillo, cambia de dirección
if (brightness2 >= 255 || brightness2 <= 0) {
increment2 = -increment2;
// Si estamos de regreso al nivel mínimo de brillo, cambia al siguiente color
if (brightness2 <= 0) {
currentColor2 = (currentColor2 + 1) % 3; // Alterna entre 0, 1 y 2
}
}
// Ajusta el brillo del LED RGB en `rgb_1`
int red2 = (currentColor2 == 0) ? brightness2 : 0;
int green2 = (currentColor2 == 1) ? brightness2 : 200;
int blue2 = (currentColor2 == 2) ? brightness2 : 0;
Color(red2, green2, blue2, 4);
}
}
// ----------------------- Music Helpers -----------------------
void startMelody(Melody* melody) {
currentMelody = melody;
currentNoteIndex = 0;
isPlaying = true;
previousMusicMillis = millis();
tempo = currentMelody->tempo;
// Play first note
tone(buzzerPin, currentMelody->sheet[currentNoteIndex].frequency);
}
void updateMelody() {
if (!isPlaying || currentMelody == nullptr) return;
unsigned long currentMillis = millis();
// Calculate note duration with a small pause between notes
int noteDuration = getMusicValue(currentMelody->sheet[currentNoteIndex].duration);
int pauseBetweenNotes = noteDuration + 0.3;
int delay_note = noteDuration + pauseBetweenNotes;
// Check if it's time to change the note
if (currentMillis - previousMusicMillis >= (noteDuration + pauseBetweenNotes)) {
// DUMP(noteDuration + pauseBetweenNotes);
// Stop the previous tone
noTone(buzzerPin);
// Move to the next note
currentNoteIndex++;
// Check if we've reached the end of the melody
if (currentNoteIndex >= currentMelody->length) {
if(!melodyNeverEnds) isPlaying = false;
else startMelody(currentMelody);
return;
}
// Play the next note (if silence, only update duration)
int note = currentMelody->sheet[currentNoteIndex].frequency;
if(note > 0)
tone(buzzerPin, currentMelody->sheet[currentNoteIndex].frequency);
// Reset the timer
previousMusicMillis = currentMillis;
// If leds sync with melody, change led's color
if(ledsSyncWithMelody) changeColors();
}
}
// ------------------------ Melodies ---------------------------
// Silent Night --------------------------------------
Note melodySilentNight[] = {
{Sol[1], 10012}, {La[1], 4}, {Sol[1], 10012}, {Mi[1], 1003},
{Sol[1], 10012}, {La[1], 4}, {Sol[1], 10012}, {Mi[1], 1003},
{Re[2], 2}, {Re[2], 10012}, {Si[1], 1003},
{Do[2], 2}, {Do[2], 10012}, {Sol[1], 1003},
{La[1], 2}, {La[1], 10012}, {Do[1], 10012},
{Si[1], 4}, {La[1], 10012}, {Sol[1], 10012},
{La[1], 4}, {Sol[1], 10012}, {Mi[1], 10012},
{La[1], 2}, {La[1], 10012}, {Do[1], 10012},
{Si[1], 4}, {La[1], 10012}, {Sol[1], 10012},
{La[1], 4}, {Sol[1], 10012}, {Mi[1], 10012},
{Re[2], 2}, {Re[2], 10012}, {Fa[2], 10012},
{Re[2], 4}, {Si[1], 10012}, {Do[2], 1003}, {Mi[2], 1003},
{Do[2], 10012}, {Sol[1], 4}, {Mi[1], 10012}, {Sol[1], 10012},
{Fa[1], 4}, {Re[1], 10012}, {Do[1], 1}
};
Melody silentNight = {
melodySilentNight,
46,
1000
};
// Campana sobre campana -------------------------
Note melodyCampana[] = {
{Sol[1], 10012}, {Sol[1], 4}, {Sol[1], 4}, {Sol[1], 10012},
{Fa_s[1], 4}, {Sol[1], 4}, {La[1], 10012}, {Fa_s[1], 4},
{Re[1], 2}, {La[1], 10012}, {La[1], 4}, {Si[1], 4}, {Do[2], 10012},
{Si[1], 4}, {La[1], 4}, {Si[1], 10012}, {La[1], 4}, {Sol[1], 2},
{Sol[1], 10012}, {Sol[1], 4}, {Sol[1], 4}, {Sol[1], 10012},
{Fa_s[1], 4}, {Sol[1], 4}, {La[1], 10012}, {Fa_s[1], 4},
{Re[1], 2}, {La[1], 10012}, {La[1], 4}, {Si[1], 4}, {Do[2], 10012},
{Si[1], 4}, {La[1], 4}, {Si[1], 10012}, {La[1], 4}, {Sol[1], 10012},
{0, 4}, {Si[1], 4}, {Re[2], 10012}, {0, 4}, {Re[2], 4}, {Mi[2], 4},
{Re[2], 4}, {Do[2], 4}, {Mi[2], 4}, {Re[2], 10012}, {0, 4},
{Re[2], 4}, {Mi[2], 4}, {Re[2], 4}, {Mi[2], 4}, {Fa_s[2], 4},
{Sol[2], 4}, {Re[2], 4}, {Si[1], 4}, {Mi[2], 4}, {Re[2], 4},
{Do[2], 4}, {Si[1], 4}, {La[1], 4}, {Sol[1], 2},
};
Melody campanaSobreCampana = {
melodyCampana,
61,
500
};
// Jingle Bells ----------------------------------
Note melodyJingleBell[] = {
{Re[1], 10012}, {Si[1], 10012}, {La[1], 10012}, {Sol[1], 10012}, {Re[1], 1},
{Re[1], 10012}, {Si[1], 10012}, {La[1], 10012}, {Sol[1], 10012}, {Mi[1], 1},
{Mi[1], 10012}, {Do[2], 10012}, {Si[1], 10012}, {La[1], 10012}, {Fa_s[1], 1},
{Re[2], 10012}, {Re[2], 10012}, {Do[2], 10012}, {La[1], 10012}, {Si[1], 1},
{Re[1], 10012}, {Si[1], 10012}, {La[1], 10012}, {Sol[1], 10012}, {Re[1], 1},
{Re[1], 10012}, {Si[1], 10012}, {La[1], 10012}, {Sol[1], 10012}, {Mi[1], 1},
{Mi[1], 10012}, {Do[2], 10012}, {Si[1], 10012}, {La[1], 10012},
{Re[2], 10012}, {Re[2], 10012}, {Re[2], 10012}, {Re[2], 10012},
{Mi[2], 10012}, {Re[2], 10012}, {Si[1], 10012}, {La[1], 10012}, {Sol[1], 1},
{Si[1], 10012}, {Si[1], 10012}, {Si[1], 2},
{Si[1], 10012}, {Si[1], 10012}, {Si[1], 2},
{Si[1], 10012}, {Re[2], 10012}, {Sol[1], 10012}, {La[1], 10012}, {Si[1], 1},
{Do[2], 10012}, {Do[2], 10012}, {Do[2], 2},
{Do[2], 10012}, {Si[1], 10012}, {Si[1], 10012}, {Si[1], 4}, {Si[1], 4},
{Si[1], 10012}, {La[1], 10012}, {La[1], 10012}, {Si[1], 10012},
{La[1], 2}, {Re[2], 2},
{Si[1], 10012}, {Si[1], 10012}, {Si[1], 2},
{Si[1], 10012}, {Si[1], 10012}, {Si[1], 2},
{Si[1], 10012}, {Re[2], 10012}, {Sol[1], 10012}, {La[1], 10012}, {Si[1], 1},
{Do[2], 10012}, {Do[2], 10012}, {Do[2], 2},
{Do[2], 10012}, {Si[1], 10012}, {Si[1], 10012}, {Si[1], 4}, {Si[1], 4},
{Re[2], 10012}, {Re[2], 10012}, {Do[2], 10012}, {La[1], 10012}, {Sol[1], 1}
};
Melody jingleBell = {
melodyJingleBell,
82,
500
};
// ------------------------ Globals Melodies -----------------
Melody* melodies[] = {
&silentNight,
&campanaSobreCampana,
&jingleBell
};
const int numMelodies = 3;
int currentMelodyIndex = 0;
// ------------------------- Button Helpers ---------------------
void checkButtonAndChangeMelody() {
unsigned long currentMillis = millis();
if (currentMillis - previousButtonMillis >= checkBtn_delay) {
bool currentButtonState = digitalRead(buttonPin);
if (currentButtonState == HIGH && lastButtonState == LOW) {
// Change melody
currentMelodyIndex = (currentMelodyIndex + 1) % numMelodies;
startMelody(melodies[currentMelodyIndex]);
DUMP(currentMelodyIndex);
}
// Update states
lastButtonState = currentButtonState;
previousButtonMillis = currentMillis;
}
}
void checkButtonAndMuteMelody() {
unsigned long currentMillis = millis();
if (currentMillis - previousButtonMuteMillis >= checkBtn_delay) {
bool currentButtonState = digitalRead(buttonPinMute);
if (currentButtonState == HIGH && lastButtonMuteState == LOW) {
// Conmute playing
isPlaying = !isPlaying;
if(!isPlaying){
ledsSyncWithMelody = false;
noTone(buzzerPin);
}// Automatic change led colors
else ledsSyncWithMelody = true;
DUMP(isPlaying);
}
// Update states
lastButtonMuteState = currentButtonState;
previousButtonMuteMillis = currentMillis;
}
}
// ---------------- // ---------------- // ---------------- // ---------------- // ----------------
void setup() {
Serial.begin(115200);
pinMode(led_yellow, OUTPUT);
pinMode(led_red, OUTPUT);
setupRGB(1);
setupRGB(2);
setupRGB(3);
setupRGB(4);
pinMode(buzzerPin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP);
pinMode(buttonPinMute, INPUT_PULLUP);
// Hello Christmas Tree
analogWrite(led_yellow, fadeValue);
analogWrite(led_red, 255);
Color(0, 0, 255, 1);
Color(0, 0, 255, 2);
Color(0, 0, 255, 3);
Color(0, 0, 255, 4);
tone(buzzerPin, La[1]);
delay(300);
noTone(buzzerPin);
// Init
analogWrite(led_red, 0);
Color(0, 0, 0, 1);
Color(0, 0, 0, 2);
Color(0, 0, 0, 3);
Color(0, 0, 0, 4);
startMelody(&jingleBell);
DUMP(isPlaying);
Serial.println("Hello Christmas Tree");
}
void loop() {
updateMelody();
if(!ledsSyncWithMelody) updateColorSequence();
checkButtonAndChangeMelody();
checkButtonAndMuteMelody();
doTheFade();
fadeRGBLed();
}