hikari/main.go

245 lines
4.8 KiB
Go
Raw Normal View History

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)
}
}