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ă
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ă (
SRCLRla +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ă.