Miguel_Corza
Published © CC BY-NC

Heart_project

It was a creative gift for my girlfriend Jennifer. I dedicate this project to her with lots of love! ❤️

IntermediateFull instructions provided6 hours139
Heart_project

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
LED, Purple
LED, Purple
×5
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
×1
bare copper
×1
9V battery (generic)
9V battery (generic)
×1
Flash Memory Card, SD Card
Flash Memory Card, SD Card
×1
SD module
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
Helping Hand Tool, with Magnifying Glass
Helping Hand Tool, with Magnifying Glass

Story

Read more

Custom parts and enclosures

untitled_sketch_bb_kZzWncNaeT.jpg

Schematics

whatsapp_video_2025-03-27_at_1_28_03_pm_KcxZJnm7aS.mp4

Code

Jenni's_Heart

Arduino
void sequenceEffect() {
  static int currentLed = 0;

  if (millis() - previousEffectMillis >= effectInterval) {
    previousEffectMillis = millis();

    // Apagar todos los LEDs
    for (int i = 0; i < numLeds; i++) {
      digitalWrite(leds[i], LOW);
    }

    // Encender el LED actual
    digitalWrite(leds[currentLed], HIGH);

    // Mover al siguiente LED
    currentLed = (currentLed + 1) % numLeds;
  }
}

// Funcin para el efecto de parpadeo
void blinkEffect() {
  static bool ledState = LOW;

  if (millis() - previousEffectMillis >= effectInterval) {
    previousEffectMillis = millis();

    // Cambiar el estado de los LEDs
    ledState = !ledState;
    for (int i = 0; i < numLeds; i++) {
      digitalWrite(leds[i], ledState);
    }
  }
}

// Funcin para el efecto de ola (centrado en el pin 3)

void waveEffect() {
  static int waveStep = 0;
  static int waveDirection = 1;

  if (millis() - previousEffectMillis >= effectInterval) {
    previousEffectMillis = millis();

    // Apagar todos los LEDs
    for (int i = 0; i < numLeds; i++) {
      digitalWrite(leds[i], LOW);
    }

    // Encender los LEDs correspondientes al paso actual
    int middle = numLeds / 2; // Punto central
    int start = middle - waveStep;
    int end = middle + waveStep;

    // Asegurarse de no salirse de los lmites del arreglo
    if (start >= 0 && end < numLeds) {
      digitalWrite(leds[start], HIGH);
      digitalWrite(leds[end], HIGH);
    }

    // Actualizar el paso de la ola
    waveStep += waveDirection;

    // Cambiar la direccin si se alcanza el extremo
    if (waveStep > middle || waveStep < 0) {
      waveDirection = -waveDirection;
    }
  }
}

jennisHeart.ino

Arduino
/* corazon para mi chefsita de caramelo
27/03/25
hecho con mucho amor de
Miguel Angel Perez Corza

*/
#include <U8g2lib.h>
#include <Wire.h>
#include <SdFat.h>

#define SD_CS 8 // Pin CS de la tarjeta microSD
#define BUTTON_PIN 2 // Botn que cambia las imagenes en D2

SdFat SD;
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// Definicin de pines de LEDs PWM
const int leds[] = {5, 3, 6, 9, 10}; // Pines PWM
const int numLeds = sizeof(leds) / sizeof(leds[0]);


// Variables para los leds
int currentEffect = 0; // 0: Parpadeo, 1: Secuencia, 2: Ola
unsigned long previousEffectMillis = 0;
const unsigned long effectInterval = 200; // Intervalo para cambiar el estado de los LEDs (en ms)

#pragma pack(push, 1) 
struct BMPHeader {
  uint16_t signature;
  uint32_t fileSize;
  uint16_t reserved1;
  uint16_t reserved2;
  uint32_t dataOffset;
  uint32_t headerSize;
  int32_t width;
  int32_t height;
  uint16_t planes;
  uint16_t bitDepth;
};
#pragma pack(pop)

void drawBMP(const char* filename) {
  if (!SD.begin(SD_CS)) {
    Serial.println("Error al inicializar la SD");
    return;
  }

  File bmpFile = SD.open(filename, FILE_READ);
  if (!bmpFile) {
    Serial.println("Error al abrir el archivo BMP");
    return;
  }

  BMPHeader header;
  bmpFile.read(&header, sizeof(BMPHeader));

  if (header.signature != 0x4D42 || header.bitDepth != 1) {
    Serial.println("Formato BMP no soportado");
    bmpFile.close();
    return;
  }

  uint16_t bytesPerRow = (header.width + 7) / 8;
  uint8_t buffer[bytesPerRow];

  u8g2.firstPage();
  do {
    for (int y = 0; y < 64; y++) {
      bmpFile.seek(header.dataOffset + (63 - y) * bytesPerRow);
      bmpFile.read(buffer, bytesPerRow);
      for (int x = 0; x < 128; x += 16) {
        // Leer 2 bytes (16 pxeles)
        uint16_t twoBytes = (buffer[x / 8] << 8) | buffer[(x / 8) + 1];
        for (int bit = 0; bit < 16; bit++) {
          if (twoBytes & (0x8000 >> bit)) {
            u8g2.drawPixel(x + bit, y);
          }
        }
      }
    }
  } while (u8g2.nextPage());
  
  bmpFile.close();
}

void playAnimation() {  // esta es la parte donde aparece el bucle de corazon como las chicas super poderozas
  static unsigned long previousMillis = 0;
  static int frameIndex = 0;
  static bool loadingNextFrame = false;
  static const char* nextFrame = nullptr;
  const unsigned long frameInterval = 60; // Intervalo entre frames (en ms) sesupone no es perseptible al ojo humano pero el ram de arduino tiene retrazo
  const char* animationFrames[] = {
    "f1.bmp", "f2.bmp", "f3.bmp", "f4.bmp", "f5.bmp",
    "f6.bmp", "f7.bmp", "f8.bmp", "f9.bmp", "f10.bmp",
    "f11.bmp","f12.bmp","f13.bmp"
  };

  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= frameInterval) {
    previousMillis = currentMillis;

    if (nextFrame != nullptr) {
      drawBMP(nextFrame);
      nextFrame = nullptr; // Reiniciar el frame precargado
    }

    // Precargar el siguiente frame
    int nextFrameIndex = (frameIndex + 1) % 13;
    nextFrame = animationFrames[nextFrameIndex];
    frameIndex = nextFrameIndex; // Actualizar el ndice del frame actual
  }
}

bool showRandomImage() {
  // Generar un nmero aleatorio entre 1 y 20
  int randomNum = random(1, 50); // Nmeros del 1 al 50 son las imagenes aleatorias

  // Crear un buffer para almacenar el nombre del archivo
  char filename[8]; // Suficiente para "XX.bmp" (2 dgitos + ".bmp" + carcter nulo)

  // Construir el nombre del archivo
  snprintf(filename, sizeof(filename), "%d.bmp", randomNum);

  // Mostrar la imagen seleccionada
  drawBMP(filename);
  return true; // Indica que la imagen se est mostrando
}


void setup() {
  Serial.begin(9600);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  u8g2.begin();
  SD.begin(SD_CS);

  // Configurar pines de LEDs como salidas
  for (int i = 0; i < numLeds; i++) {
    pinMode(leds[i], OUTPUT);
  }

  // Inicializar la semilla aleatoria
  randomSeed(analogRead(0));
}

void loop() {
  static bool showingRandomImage = false;
  static unsigned long lastDebounceTime = 0; // Tiempo de la ltima pulsacin vlida
  const unsigned long debounceDelay = 200; // Tiempo de debouncing en milisegundos

  // Reproducir la animacin en bucle si no se est mostrando una imagen aleatoria
  if (!showingRandomImage) {
    playAnimation();
  }

  int buttonState = digitalRead(BUTTON_PIN);

  if (buttonState == LOW && (millis() - lastDebounceTime) > debounceDelay) {
    lastDebounceTime = millis(); // Registrar el tiempo de la pulsacin vlida

    if (!showingRandomImage) {
      showingRandomImage = true; // Indicar que se est mostrando una imagen aleatoria y la muestra
      showRandomImage(); 
    } else {
      showingRandomImage = false; // Regresar a la animacin
    }

    // Cambiar al siguiente efecto de LEDs
    currentEffect = (currentEffect + 1) % 3; // 0: Parpadeo, 1: Secuencia, 2: Ola
  }

  // Ejecutar el efecto de LEDs actual
  switch (currentEffect) {
    case 0:
      blinkEffect();
      break;
    case 1:
      sequenceEffect();
      break;
    case 2:
      waveEffect();
      break;
  }
}

Credits

Miguel_Corza
3 projects • 3 followers
I am currently a student of electronics and a future engineer. I enjoy creating and inventing new electronic projects.

Comments