2026-04-30 09:16:53 +00:00
|
|
|
|
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
|
2026-05-06 15:36:12 +00:00
|
|
|
|
numStars = 8
|
|
|
|
|
|
starStep = 7
|
2026-04-30 09:16:53 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
2026-05-06 15:36:12 +00:00
|
|
|
|
pinLED = machine.D6
|
|
|
|
|
|
pinBtn = machine.D3
|
2026-04-30 09:16:53 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-06 15:36:12 +00:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-04-30 09:16:53 +00:00
|
|
|
|
|
|
|
|
|
|
var (
|
2026-05-06 15:36:12 +00:00
|
|
|
|
starList [numStars]star
|
|
|
|
|
|
starsOK bool
|
|
|
|
|
|
leds [numLEDs]color.RGBA
|
|
|
|
|
|
strip ws2812.Device
|
2026-04-30 09:16:53 +00:00
|
|
|
|
|
|
|
|
|
|
// xorshift32 PRNG – seeded with a compile-time constant
|
2026-05-06 15:36:12 +00:00
|
|
|
|
rngState uint32 = 0xDEADBEEF
|
2026-04-30 09:16:53 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 15:36:12 +00:00
|
|
|
|
// effectRainbow cycles through all the colors slowly
|
2026-04-30 09:16:53 +00:00
|
|
|
|
func effectRainbow(step uint32) {
|
|
|
|
|
|
c := wheel(uint8(step >> 2)) // full cycle ≈ 20 s
|
|
|
|
|
|
for i := range leds {
|
|
|
|
|
|
leds[i] = c
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 15:36:12 +00:00
|
|
|
|
// effectRowSweep lit a row at a time, the color/hue changes
|
2026-04-30 09:16:53 +00:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 15:36:12 +00:00
|
|
|
|
// effectColSweep lit a column at a time, the color/hue changes
|
2026-04-30 09:16:53 +00:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 15:36:12 +00:00
|
|
|
|
// initStars resets the data for the stars effect
|
2026-04-30 09:16:53 +00:00
|
|
|
|
func initStars() {
|
|
|
|
|
|
for i := range starList {
|
|
|
|
|
|
starList[i] = star{
|
|
|
|
|
|
pos: uint8(rand() % numLEDs),
|
|
|
|
|
|
hue: uint8(rand()),
|
2026-05-06 15:36:12 +00:00
|
|
|
|
br: uint8(i * (255 / numStars)),
|
2026-04-30 09:16:53 +00:00
|
|
|
|
dir: 1,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
starsOK = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 15:36:12 +00:00
|
|
|
|
// effectStarts lit random LEDs and dimm them slowly
|
2026-04-30 09:16:53 +00:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 15:36:12 +00:00
|
|
|
|
// effectBreathing shows classic breathing effect
|
2026-04-30 09:16:53 +00:00
|
|
|
|
func effectBreathing(step uint32) {
|
2026-05-06 15:36:12 +00:00
|
|
|
|
t := uint8(step)
|
2026-04-30 09:16:53 +00:00
|
|
|
|
var br uint8
|
|
|
|
|
|
if t < 128 {
|
2026-05-06 15:36:12 +00:00
|
|
|
|
br = t * 2
|
2026-04-30 09:16:53 +00:00
|
|
|
|
} else {
|
2026-05-06 15:36:12 +00:00
|
|
|
|
br = (255 - t) * 2
|
2026-04-30 09:16:53 +00:00
|
|
|
|
}
|
2026-05-06 15:36:12 +00:00
|
|
|
|
hue := uint8(step >> 7)
|
2026-04-30 09:16:53 +00:00
|
|
|
|
for i := range leds {
|
|
|
|
|
|
leds[i] = dim(wheel(hue), br)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 15:36:12 +00:00
|
|
|
|
// effectRainbowRows shows a different color in each row and change its hue slowly
|
2026-04-30 09:16:53 +00:00
|
|
|
|
func effectRainbowRows(step uint32) {
|
2026-05-06 15:36:12 +00:00
|
|
|
|
base := uint8(step >> 2)
|
2026-04-30 09:16:53 +00:00
|
|
|
|
for row := 0; row < numRows; row++ {
|
|
|
|
|
|
c := wheel(base + uint8(row*64))
|
|
|
|
|
|
for col := 0; col < numCols; col++ {
|
|
|
|
|
|
leds[idx(row, col)] = c
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-06 15:36:12 +00:00
|
|
|
|
// effectWave runs a cool wave effect
|
2026-04-30 09:16:53 +00:00
|
|
|
|
func effectWave(step uint32) {
|
|
|
|
|
|
clear()
|
2026-05-06 15:36:12 +00:00
|
|
|
|
head := int(step/8) % numCols
|
2026-04-30 09:16:53 +00:00
|
|
|
|
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 {
|
2026-05-06 15:36:12 +00:00
|
|
|
|
btnDown := !pinBtn.Get()
|
2026-04-30 09:16:53 +00:00
|
|
|
|
if btnDown && !prevBtn && time.Since(lastPress) > debounce {
|
|
|
|
|
|
effect = (effect + 1) % numEffects
|
|
|
|
|
|
lastPress = time.Now()
|
2026-05-06 15:36:12 +00:00
|
|
|
|
starsOK = false
|
2026-04-30 09:16:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|