Electronica Hobby

Leduri decorative (v1 - 74HC595 + ATmega328P)

Prototip decorativ cu animații LED pe două axe, controlat de ATmega328P și 4x 74HC595 în lanț. Acest proiect înlocuiește ideea anterioară „Circuit cu leduri auto” — am renunțat la varianta auto pentru a evita ambiguități legale și pentru a păstra proiectul simplu, sigur și 100% hobby/indoor.

Video demonstrativ

Dacă nu vezi playerul, deschide clipul pe YouTube.

Schemă electronică

Schema proiectului Leduri decorative cu ATmega328P și 4x74HC595
Lanț SPI → 4x74HC595 (U5, U6, U7, U8). Corecție față de PCB v1: la 74HC595, SRCLR (pin 10) trebuie legat la +5V, iar OE (pin 13) la GND. Gerberul nu este publicat pentru această versiune; recomand folosirea exclusivă a schemei. Deschide SVG (zoom).

Cum funcționează

  • MCU: ATmega328P generează date SPI (MOSI/SCK) către lanțul de 4x74HC595; pinul LATCH (RCLK) strobează ieșirile simultan.
  • LED-uri: două „axe” de câte 7 poziții (galben → portocaliu → roșu) se intersectează în LED-ul verde (U8-QA). Animațiile aprind secvențial grupuri de câte 2 LED-uri per poziție.
  • Alimentare: 12V stabilizat; fiecare LED are rezistență serie. (Versiunea auto a fost abandonată; folosește proiectul exclusiv indoor.)
  • Firmware: motor de animații cu 12 efecte, schimbare automată la 12 secunde; LED-ul verde poate pulsa (heartbeat) sau „urmări” activitatea.

Cod Arduino (ATmega328P)

Setări rapide: Board „Arduino Uno”, Upload via USBasp sau bootloader. La nevoie, inversează ordinea lanțului/bitilor din constantele REVERSE_CHAIN/REVERSE_BITS. Durata unui efect: MODE_DUR_MS = 12000 (12 s).

Vezi codul complet (12 efecte, 12s/mod)

#include <SPI.h>

/* Config hardware 74HC595 */
const uint8_t PIN_LATCH = 8;          // PB0 -> RCLK/STCP
const bool REVERSE_CHAIN = true;      // dacă ordinea pare inversă, schimbă
const bool REVERSE_BITS  = false;     // la tine verdele a mers cu 'false'

// Verde = U8-QA (fix)
#define U8_GREEN_MASK  0x01

// Cum participă verdele
enum { FOLLOW_AXES = 0, HEARTBEAT = 1, WITH_ACTIVITY = 2 };
const uint8_t CENTER_MODE = HEARTBEAT;

// Timpi (efect nou la 12s)
const uint32_t MODE_DUR_MS   = 12000;
const uint32_t FRAME_MS_FAST = 110;
const uint32_t FRAME_MS_MED  = 140;
const uint32_t FRAME_MS_SLOW = 180;

static inline uint8_t rev8(uint8_t x){
  x=(x&0xF0)>>4 | (x&0x0F)<<4;
  x=(x&0xCC)>>2 | (x&0x33)<<2;
  x=(x&0xAA)>>1 | (x&0x55)<<1;
  return x;
}
static inline uint8_t txMap(uint8_t b){ return REVERSE_BITS ? rev8(b) : b; }

void write595_4(uint8_t u5,uint8_t u6,uint8_t u7,uint8_t u8){
  u8 &= U8_GREEN_MASK;               // pe U8 permitem doar QA (LED verde)
  digitalWrite(PIN_LATCH, LOW);
  if (!REVERSE_CHAIN){
    SPI.transfer(txMap(u5)); SPI.transfer(txMap(u6));
    SPI.transfer(txMap(u7)); SPI.transfer(txMap(u8));
  } else {
    SPI.transfer(txMap(u8)); SPI.transfer(txMap(u7));
    SPI.transfer(txMap(u6)); SPI.transfer(txMap(u5));
  }
  digitalWrite(PIN_LATCH, HIGH);
}

// Măști QA..QH
const uint8_t QA=1<<0, QB=1<<1, QC=1<<2, QD=1<<3,
              QE=1<<4, QF=1<<5, QG=1<<6, QH=1<<7;

// Mapping „cruce”
inline void addAxis1(int pos, uint8_t &u5, uint8_t &u6, uint8_t &u7, uint8_t &u8){
  switch(pos){
    case -3: u7|=(QF|QE); break;
    case -2: u6|=(QF|QE); break;
    case -1: u5|=(QF|QE); break;
    case  0: u8|=U8_GREEN_MASK; break;
    case +1: u5|=(QA|QB); break;
    case +2: u6|=(QA|QB); break;
    case +3: u7|=(QA|QB); break;
  }
}
inline void addAxis2(int pos, uint8_t &u5, uint8_t &u6, uint8_t &u7, uint8_t &u8){
  switch(pos){
    case -3: u7|=(QH|QG); break;
    case -2: u6|=(QH|QG); break;
    case -1: u5|=(QH|QG); break;
    case  0: u8|=U8_GREEN_MASK; break;
    case +1: u5|=(QC|QD); break;
    case +2: u6|=(QC|QD); break;
    case +3: u7|=(QC|QD); break;
  }
}
uint8_t applyCenter(uint8_t u5,uint8_t u6,uint8_t u7,uint8_t u8,uint32_t now){
  switch (CENTER_MODE){
    case FOLLOW_AXES: return u8; // setat în addAxis* la pos==0
    case HEARTBEAT: { uint32_t ph = now % 1000; if (ph < 420) u8 |= U8_GREEN_MASK; else u8 &= ~U8_GREEN_MASK; return u8; }
    case WITH_ACTIVITY:
    default: if (u5||u6||u7) u8 |= U8_GREEN_MASK; else u8 &= ~U8_GREEN_MASK; return u8;
  }
}

/* ===== efecte (12) ===== */
void eff_wipe_axis1(uint32_t t){ static int8_t p=-3; uint8_t u5=0,u6=0,u7=0,u8=0;
  addAxis1(p,u5,u6,u7,u8); u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8);
  if(t%FRAME_MS_MED==0){ p++; if(p>+3)p=-3; } }

void eff_wipe_axis2(uint32_t t){ static int8_t p=+3; uint8_t u5=0,u6=0,u7=0,u8=0;
  addAxis2(p,u5,u6,u7,u8); u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8);
  if(t%FRAME_MS_MED==0){ p--; if(p<-3)p=+3; } }

void eff_bounce_axis1(uint32_t t){ static int8_t p=0,dir=1; uint8_t u5=0,u6=0,u7=0,u8=0;
  addAxis1(p,u5,u6,u7,u8); u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8);
  if(t%FRAME_MS_SLOW==0){ if(p==+3)dir=-1; if(p==-3)dir=+1; p+=dir; } }

void eff_bounce_axis2(uint32_t t){ static int8_t p=0,dir=-1; uint8_t u5=0,u6=0,u7=0,u8=0;
  addAxis2(p,u5,u6,u7,u8); u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8);
  if(t%FRAME_MS_SLOW==0){ if(p==+3)dir=-1; if(p==-3)dir=+1; p+=dir; } }

void eff_expand_cross(uint32_t t){ static uint8_t s=0; uint8_t u5=0,u6=0,u7=0,u8=0;
  for(int k=0;k<=(int)s;k++){ addAxis1(+k,u5,u6,u7,u8); addAxis1(-k,u5,u6,u7,u8);
                              addAxis2(+k,u5,u6,u7,u8); addAxis2(-k,u5,u6,u7,u8); }
  u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8);
  if(t%FRAME_MS_MED==0){ s++; if(s>3)s=0; } }

void eff_collapse_cross(uint32_t t){ static int8_t s=3; uint8_t u5=0,u6=0,u7=0,u8=0;
  for(int k=0;k<=(int)s;k++){ addAxis1(+k,u5,u6,u7,u8); addAxis1(-k,u5,u6,u7,u8);
                              addAxis2(+k,u5,u6,u7,u8); addAxis2(-k,u5,u6,u7,u8); }
  u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8);
  if(t%FRAME_MS_MED==0){ s--; if(s<0)s=3; } }

void eff_cross_blink_alt(uint32_t t){ static bool on=false; uint8_t u5=0,u6=0,u7=0,u8=0;
  if(on){ for(int p=-3;p<=+3;p++) addAxis1(p,u5,u6,u7,u8); }
  else  { for(int p=-3;p<=+3;p++) addAxis2(p,u5,u6,u7,u8); }
  u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8);
  if(t%(FRAME_MS_SLOW*2)==0) on=!on; }

void eff_runner_loop(uint32_t t){ static uint8_t k=0; uint8_t u5=0,u6=0,u7=0,u8=0;
  if(k<=6) addAxis1(-3+k,u5,u6,u7,u8); else addAxis2(+3-(k-7),u5,u6,u7,u8);
  u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8);
  if(t%FRAME_MS_FAST==0){ k++; if(k>=14) k=0; } }

void eff_twinkle_slow(uint32_t t){ static uint32_t seed=0xA5A5C3D5; auto rnd=[&](){seed^=seed<<13;seed^=seed>>17;seed^=seed<<5;return seed;};
  uint8_t u5=0,u6=0,u7=0,u8=0;
  for(uint8_t i=0;i<3;i++){ int ax=(rnd()>>3)&1; int p=((int)(rnd()>>5)%7)-3; if(ax==0) addAxis1(p,u5,u6,u7,u8); else addAxis2(p,u5,u6,u7,u8); }
  u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8); delay(90); }

void eff_comet_axis1(uint32_t t){ static int8_t p=-3; uint8_t u5=0,u6=0,u7=0,u8=0;
  addAxis1(p,u5,u6,u7,u8); if(p-1>=-3) addAxis1(p-1,u5,u6,u7,u8);
  u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8);
  if(t%FRAME_MS_FAST==0){ p++; if(p>+3) p=-3; } }

void eff_comet_axis2(uint32_t t){ static int8_t p=+3; uint8_t u5=0,u6=0,u7=0,u8=0;
  addAxis2(p,u5,u6,u7,u8); if(p+1<=+3) addAxis2(p+1,u5,u6,u7,u8);
  u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8);
  if(t%FRAME_MS_FAST==0){ p--; if(p<-3) p=+3; } }

void eff_breath_center(uint32_t t){
  static uint16_t ph=0; ph=(ph+1)%200; bool on = (ph<120) ? true : (ph<180 ? (ph%6<3) : false);
  uint8_t u5=0,u6=0,u7=0,u8=0; if(on) u8|=U8_GREEN_MASK; write595_4(u5,u6,u7,u8); delay(35);
}
void eff_wave_both(uint32_t t){
  static uint8_t k=0; uint8_t u5=0,u6=0,u7=0,u8=0; const int8_t lut[7]={-3,-2,-1,0,+1,+2,+3};
  addAxis1(lut[k],u5,u6,u7,u8); addAxis2(lut[(k+3)%7],u5,u6,u7,u8);
  u8=applyCenter(u5,u6,u7,u8,t); write595_4(u5,u6,u7,u8); if(t%FRAME_MS_MED==0){ k=(k+1)%7; }
}

/* listă efecte */
typedef void (*EffectFn)(uint32_t);
EffectFn effects[] = {
  eff_wipe_axis1, eff_wipe_axis2, eff_bounce_axis1, eff_bounce_axis2,
  eff_expand_cross, eff_collapse_cross, eff_cross_blink_alt, eff_runner_loop,
  eff_twinkle_slow, eff_comet_axis1, eff_comet_axis2, eff_wave_both
};
const uint8_t MODE_COUNT = sizeof(effects)/sizeof(effects[0]);

/* setup/loop */
uint32_t t0=0;
void setup(){
  pinMode(PIN_LATCH, OUTPUT);
  digitalWrite(PIN_LATCH, LOW);
  SPI.begin();
  SPI.beginTransaction(SPISettings(100000, MSBFIRST, SPI_MODE0));
  for(uint8_t i=0;i<4;i++) write595_4(0,0,0,0); // clear
  t0 = millis();
}
void loop(){
  static uint8_t mode=0; uint32_t now=millis();
  effects[mode](now);
  if(now - t0 >= MODE_DUR_MS){
    t0 = now; mode = (mode+1)%MODE_COUNT;
    write595_4(0xFF,0xFF,0xFF,0x00); delay(90);
    write595_4(0x00,0x00,0x00,0x00); delay(90);
  }
}
        

Note & context

  • Acest proiect este destinat uzului decorativ, indoor. Nu include fișiere Gerber/publicabile pentru PCB; s-a corectat doar în schemă (SRCLR la +5 V).
  • Circuit cu leduri auto” a fost retras: domeniul auto are reguli stricte; pentru a evita ambiguități legale, am păstrat doar varianta decorativă.