#include <Adafruit_NeoPixel.h>
#define LED_PIN 6
#define NUM_LEDS 60
#define TRIG_PIN 10
#define ECHO_PIN 11
#define BTN_A 2
#define BTN_B 3
Adafruit_NeoPixel ring(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
int hh = 10, mm = 9, sc = 0; // sc instead of SS (SS is reserved by SPI)
unsigned long prevMs = 0;
uint8_t dispMode = 0;
bool prevA = HIGH, prevB = HIGH;
unsigned long dbA = 0, dbB = 0;
// --- ultrasonic ---
long ping() {
digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long t = pulseIn(ECHO_PIN, HIGH, 23000);
return t ? t * 17L / 1000 : 60;
}
// --- colour wheel 0-255 ---
uint32_t wheel(uint8_t p) {
p = 255 - p;
if (p < 85) return ring.Color(255-p*3, 0, p*3);
if (p < 170) { p -= 85; return ring.Color(0, p*3, 255-p*3); }
p -= 170; return ring.Color(p*3, 255-p*3, 0);
}
uint32_t scalec(uint32_t c, uint8_t f) {
return ring.Color(
((c>>16)&0xFF)*f/255,
((c>> 8)&0xFF)*f/255,
( c &0xFF)*f/255);
}
// ---- MODE 0 : Clock ----
void modeClock() {
ring.clear();
for (int i = 0; i < 60; i += 5)
ring.setPixelColor(i, ring.Color(14, 14, 14));
// hour (red)
int hc = (int)round(((hh%12)*60.0f + mm) / 720.0f * 60.0f) % 60;
for (int d = -2; d <= 2; d++) {
int px = (hc+d+60)%60;
uint8_t b = (d==0)?230:(abs(d)==1?100:35);
uint32_t old = ring.getPixelColor(px);
ring.setPixelColor(px, ring.Color(min(255,(int)((old>>16)&0xFF)+b),(old>>8)&0xFF,old&0xFF));
}
// minute (green)
int mc = (int)round((mm*60.0f+sc)/3600.0f*60.0f) % 60;
ring.setPixelColor((mc-1+60)%60, scalec(ring.Color(0,255,80),70));
ring.setPixelColor(mc, ring.Color(0,255,80));
ring.setPixelColor((mc+1)%60, scalec(ring.Color(0,255,80),70));
// second (cyan)
ring.setPixelColor(sc%60, ring.Color(0,210,255));
ring.show();
}
// ---- MODE 1 : Equalizer ----
uint8_t eqH[8] = {20,42,16,52,34,28,46,22};
int8_t eqV[8] = { 1,-1, 1,-1, 1,-1, 1,-1};
unsigned long eqMs = 0;
void modeEQ() {
if (millis()-eqMs > 65) {
for (int i=0;i<8;i++) {
eqH[i] += eqV[i]*(int8_t)random(2,7);
if (eqH[i]>=58||eqH[i]<=4){eqV[i]=-eqV[i];eqH[i]=constrain(eqH[i],4,58);}
}
eqMs = millis();
}
ring.clear();
for (int b=0;b<8;b++) {
int ctr = b*7+3;
uint8_t br = map(eqH[b],4,58,40,255);
uint32_t c = wheel(map(eqH[b],4,58,0,185));
for (int d=-2;d<=2;d++) {
int px = (ctr+d+60)%60;
uint8_t f = (d==0)?255:(abs(d)==1?130:45);
ring.setPixelColor(px, scalec(c,(uint16_t)br*f/255));
}
}
ring.show();
}
// ---- MODE 2 : Radar ----
int radarHead = 0;
unsigned long radarMs = 0;
void modeRadar() {
if (millis()-radarMs > 22){radarHead=(radarHead+1)%60; radarMs=millis();}
ring.clear();
for (int i=0;i<18;i++){
int px=(radarHead-i+60)%60;
ring.setPixelColor(px, ring.Color(0, map(i,0,17,210,4), 0));
}
long cm = ping();
if (cm < 40){
int obj = map(cm,2,40,0,59);
ring.setPixelColor(obj, ring.Color(255,80,0));
ring.setPixelColor((obj+1)%60, ring.Color(80,20,0));
ring.setPixelColor((obj-1+60)%60, ring.Color(80,20,0));
}
ring.show();
}
// ---- MODE 3 : Rainbow ----
uint16_t rbPos = 0;
unsigned long rbMs = 0;
void modeRainbow() {
long cm = constrain(ping(),2,40);
uint8_t spd = map(cm,2,40,14,1);
if (millis()-rbMs > spd){rbPos=(rbPos+1)%256; rbMs=millis();}
for (int i=0;i<NUM_LEDS;i++) ring.setPixelColor(i, wheel((i*4+rbPos)&0xFF));
ring.show();
}
// ---- buttons ----
void checkButtons() {
unsigned long now = millis();
bool a = digitalRead(BTN_A);
bool b = digitalRead(BTN_B);
if (prevA==HIGH && a==LOW && now-dbA>200){
dispMode=(dispMode+1)%4; dbA=now;
ring.fill(ring.Color(25,25,25)); ring.show(); delay(55); ring.clear();
}
if (prevB==HIGH && b==LOW && now-dbB>200){
mm=(mm+5)%60; sc=0; dbB=now;
}
prevA=a; prevB=b;
}
void setup() {
ring.begin();
ring.setBrightness(75);
ring.show();
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(BTN_A, INPUT_PULLUP);
pinMode(BTN_B, INPUT_PULLUP);
// boot spiral
for (int i=0;i<NUM_LEDS;i++){ring.setPixelColor(i,wheel(i*4));ring.show();delay(16);}
delay(250);
for (int i=NUM_LEDS-1;i>=0;i--){ring.setPixelColor(i,0);ring.show();delay(9);}
prevMs = millis();
randomSeed(42);
}
void loop() {
checkButtons();
if (millis()-prevMs >= 1000){
prevMs += 1000;
if(++sc >= 60){ sc=0; if(++mm >= 60){ mm=0; if(++hh >= 24) hh=0; }}
}
switch(dispMode){
case 0: modeClock(); break;
case 1: modeEQ(); break;
case 2: modeRadar(); break;
case 3: modeRainbow(); break;
}
delay(8);
}