package main import ( "image/color" "machine" "time" "tinygo.org/x/drivers/ws2812" ) const ( numLEDs = 20 numRows = 4 numCols = 5 numEffects = 7 frameDelay = 20 * time.Millisecond debounce = 200 * time.Millisecond numStars = 8 starStep = 7 ) const ( pinLED = machine.D6 pinBtn = machine.D3 ) type star struct { pos uint8 // LED index hue uint8 // colour on the wheel br uint8 // current brightness (0-255) dir int8 // +1 rising, -1 falling } var ( starList [numStars]star starsOK bool leds [numLEDs]color.RGBA strip ws2812.Device // xorshift32 PRNG – seeded with a compile-time constant rngState uint32 = 0xDEADBEEF ) // wheel maps 0-255 → smooth full-hue spectrum (R→G→B→R) func wheel(pos uint8) color.RGBA { switch { case pos < 85: return color.RGBA{R: pos * 3, G: 255 - pos*3, B: 0} case pos < 170: pos -= 85 return color.RGBA{R: 255 - pos*3, G: 0, B: pos * 3} default: pos -= 170 return color.RGBA{R: 0, G: pos * 3, B: 255 - pos*3} } } // dim scales a colour by brightness (0-255) func dim(c color.RGBA, brightness uint8) color.RGBA { return color.RGBA{ R: uint8(uint16(c.R) * uint16(brightness) >> 8), G: uint8(uint16(c.G) * uint16(brightness) >> 8), B: uint8(uint16(c.B) * uint16(brightness) >> 8), } } // idx converts grid coordinates to LED index (simple row-major) func idx(row, col int) int { return row*numCols + col } // clear sets all LEDs to off func clear() { for i := range leds { leds[i] = color.RGBA{} } } // rand returns the next pseudo-random uint32 func rand() uint32 { rngState ^= rngState << 13 rngState ^= rngState >> 17 rngState ^= rngState << 5 return rngState } // effectRainbow cycles through all the colors slowly func effectRainbow(step uint32) { c := wheel(uint8(step >> 2)) // full cycle ≈ 20 s for i := range leds { leds[i] = c } } // effectRowSweep lit a row at a time, the color/hue changes func effectRowSweep(step uint32) { clear() row := int(step/40) % numRows c := wheel(uint8(step >> 1)) for col := 0; col < numCols; col++ { leds[idx(row, col)] = c } } // effectColSweep lit a column at a time, the color/hue changes func effectColSweep(step uint32) { clear() col := int(step/40) % numCols c := wheel(uint8(step >> 1)) for row := 0; row < numRows; row++ { leds[idx(row, col)] = c } } // initStars resets the data for the stars effect func initStars() { for i := range starList { starList[i] = star{ pos: uint8(rand() % numLEDs), hue: uint8(rand()), br: uint8(i * (255 / numStars)), dir: 1, } } starsOK = true } // effectStarts lit random LEDs and dimm them slowly func effectStars(_ uint32) { if !starsOK { initStars() } clear() for i := range starList { s := &starList[i] // Advance brightness nb := int16(s.br) + int16(s.dir)*starStep switch { case nb >= 255: s.br = 255 s.dir = -1 case nb <= 0: // Star died – pick a new random one s.br = 0 s.dir = 1 s.pos = uint8(rand() % numLEDs) s.hue = uint8(rand()) default: s.br = uint8(nb) } leds[s.pos] = dim(wheel(s.hue), s.br) } } // effectBreathing shows classic breathing effect func effectBreathing(step uint32) { t := uint8(step) var br uint8 if t < 128 { br = t * 2 } else { br = (255 - t) * 2 } hue := uint8(step >> 7) for i := range leds { leds[i] = dim(wheel(hue), br) } } // effectRainbowRows shows a different color in each row and change its hue slowly func effectRainbowRows(step uint32) { base := uint8(step >> 2) for row := 0; row < numRows; row++ { c := wheel(base + uint8(row*64)) for col := 0; col < numCols; col++ { leds[idx(row, col)] = c } } } // effectWave runs a cool wave effect func effectWave(step uint32) { clear() head := int(step/8) % numCols hue := uint8(step >> 1) bri := [numCols]uint8{255, 110, 35, 0, 0} for col := 0; col < numCols; col++ { dist := (head - col + numCols) % numCols if dist >= numCols || bri[dist] == 0 { continue } c := dim(wheel(hue+uint8(col*12)), bri[dist]) for row := 0; row < numRows; row++ { leds[idx(row, col)] = c } } } func main() { pinLED.Configure(machine.PinConfig{Mode: machine.PinOutput}) strip = ws2812.New(pinLED) pinBtn.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) var ( effect int step uint32 prevBtn bool lastPress time.Time ) for { btnDown := !pinBtn.Get() if btnDown && !prevBtn && time.Since(lastPress) > debounce { effect = (effect + 1) % numEffects lastPress = time.Now() starsOK = false } prevBtn = btnDown switch effect { case 0: effectRainbow(step) case 1: effectRowSweep(step) case 2: effectColSweep(step) case 3: effectStars(step) case 4: effectBreathing(step) case 5: effectRainbowRows(step) case 6: effectWave(step) } strip.WriteColors(leds[:]) step++ time.Sleep(frameDelay) } }