- commit
- ddad765
- parent
- 10d8dc5
- author
- Neale Pickett
- date
- 2026-02-12 12:30:07 -0700 MST
multi-architecture, plus builtin LED abstraction
10 files changed,
+287,
-28
+17,
-0
1@@ -0,0 +1,17 @@
2+//go:build xiao
3+
4+package main
5+
6+import (
7+ "machine"
8+ "time"
9+)
10+
11+func blink() {
12+ machine.LED.High()
13+ time.Sleep(200 * time.Millisecond)
14+ machine.LED.Low()
15+ time.Sleep(200 * time.Millisecond)
16+}
17+
18+
+32,
-27
1@@ -1,12 +1,13 @@
2 package main
3
4 import (
5+ "context"
6 "image/color"
7 "machine"
8 "time"
9 "tinygo.org/x/drivers"
10 "tinygo.org/x/drivers/ssd1306"
11- "tinygo.org/x/drivers/ws2812"
12+ "git.woozle.org/neale/concertina/pkg/led"
13 )
14
15 // For crying out loud.
16@@ -20,35 +21,31 @@ type Concertina struct {
17 i2c drivers.I2C
18 }
19
20-func NewConcertina(vs1053Reset machine.Pin) (c *Concertina) {
21- // Make the LED red during setup
22- machine.NEOPIXELS_POWER.Configure(machine.PinConfig{machine.PinOutput})
23- machine.NEOPIXELS_POWER.High()
24- machine.NEOPIXELS.Configure(machine.PinConfig{machine.PinOutput})
25- pixel := ws2812.New(machine.NEOPIXELS)
26- pixel.WriteColors([]color.RGBA{color.RGBA{0x10, 0, 0, 0}})
27-
28- vs1053Reset.Configure(machine.PinConfig{machine.PinOutput})
29- vs1053Reset.Low()
30- machine.DefaultUART.Configure(machine.UARTConfig{
31+func NewConcertina() (*Concertina, error) {
32+ // Initialize tone generator
33+ if err := machine.DefaultUART.Configure(machine.UARTConfig{
34 BaudRate: 31250,
35- TX: machine.UART_TX_PIN,
36- RX: machine.UART_RX_PIN,
37- })
38+ TX: VS1053_TX,
39+ RX: VS1053_RX,
40+ }); err != nil {
41+ return nil, err
42+ }
43+ VS1053_RESET.Configure(machine.PinConfig{machine.PinOutput})
44+ VS1053_RESET.Low() // Signal a reset
45+
46+ resetTimer := time.After(10 * time.Millisecond)
47
48 if err := machine.I2C0.Configure(machine.I2CConfig{}); err != nil {
49- println(err)
50- return
51+ return nil, err
52 }
53
54- c = &Concertina {
55+ c := &Concertina {
56 tonegen: MidiWriter{Writer: machine.DefaultUART},
57 i2c: machine.I2C0,
58 }
59
60-
61- // ssd1306 setup already has a delay, so we use that to reset the vs1053
62- vs1053Reset.High()
63+ <-resetTimer // Wait until this timer has elapsed
64+ VS1053_RESET.High()
65
66 c.display = ssd1306.NewI2C(c.i2c)
67 c.display.Configure(
68@@ -62,15 +59,23 @@ func NewConcertina(vs1053Reset machine.Pin) (c *Concertina) {
69
70 c.tonegen.SetPatch(22) // General MIDI harmonica
71
72- // init is done, turn off the LED
73- machine.NEOPIXELS_POWER.Low()
74-
75- return
76+ return c, nil
77 }
78
79 func main() {
80- c := NewConcertina(machine.D3)
81-
82+ // Blink the LED during startup.
83+ blinkCtx, blinkCancel := context.WithCancel(context.Background())
84+ go led.Flash(blinkCtx, led.Builtin, time.Second / 4, time.Second / 4)
85+
86+ c, err := NewConcertina()
87+ if err != nil {
88+ // let the little light blink forever and ever
89+ for {
90+ time.Sleep(1 * time.Hour)
91+ }
92+ }
93+ blinkCancel()
94+
95 var x int
96 for {
97 c.display.ClearBuffer()
M
go.mod
+1,
-1
1@@ -1,4 +1,4 @@
2-module woozle.org/neale/concertina
3+module git.woozle.org/neale/concertina
4
5 go 1.24.4
6
+12,
-0
1@@ -0,0 +1,12 @@
2+//go:build itsybitsy_m0
3+
4+package led
5+
6+import "machine"
7+import "apa102"
8+
9+var Builtin = &DotStar{
10+ apa102.NewSoftwareSPI(machine.PA00, machine.PA01),
11+}
12+
13+
+8,
-0
1@@ -0,0 +1,8 @@
2+//go:build qtpy
3+
4+package led
5+
6+import "machine"
7+
8+var Builtin = NewNeoPixel(machine.NEOPIXELS, machine.NEOPIXELS_POWER)
9+
+23,
-0
1@@ -0,0 +1,23 @@
2+//go:build xiao
3+
4+package led
5+
6+import "machine"
7+
8+var Builtin = Pin{
9+ Pin: machine.LED,
10+ pwm: machine.TCC2, // I think Arduino's variants/XIAO_m0/variant.cpp uses this timer
11+ invert: true,
12+}
13+
14+var RXL = Pin{
15+ Pin: machine.LED_RXL,
16+ pwm: machine.TCC0,
17+ invert: true,
18+}
19+
20+var TXL = Pin{
21+ Pin: machine.LED_TXL,
22+ pwm: machine.TCC0,
23+ invert: true,
24+}
+24,
-0
1@@ -0,0 +1,24 @@
2+package led
3+
4+import (
5+ "image/color"
6+ "tinygo.org/x/drivers/apa102"
7+)
8+
9+type DotStar struct {
10+ *apa102.Device
11+}
12+
13+func (d *DotStar) Enable() error {
14+ return nil
15+}
16+
17+func (d *DotStar) Disable() {
18+}
19+
20+func (d *DotStar) SetColor(c color.Color) {
21+ // sigh. https://github.com/tinygo-org/drivers/issues/837
22+ rgba := color.RGBAModel.Convert(c).(color.RGBA)
23+ d.WriteColors([]color.RGBA{rgba})
24+}
25+
+78,
-0
1@@ -0,0 +1,78 @@
2+package led
3+
4+import "image/color"
5+import "context"
6+import "time"
7+
8+type LED interface {
9+ // Enable the LED. This must be called before doing any LED operations.
10+ // This may increase power use by some boards.
11+ Enable() error
12+
13+ // Disable the LED. This may reduce power use by some boards.
14+ Disable()
15+
16+ // Set the LED color as close as possible to c.
17+ // On single-color LEDs (eg. Arduino Uno), this will only change the PWM duty cycle (brightness).
18+ // Setting the color to black will make the LED stop emitting light,
19+ // but if the LED requires a third power line (eg. ws2812), that will still be high.
20+ // Use Disable() to shut off all power to the LED.
21+ SetColor(c color.Color)
22+}
23+
24+// Red enables l and sets is color as close as possible to red, with
25+// brightness b. b=255 is full brightness.
26+//
27+// If your LED only emits one color, "as close as possible" means
28+// whatever color your LED emits :)
29+func Red(l LED, b uint8) {
30+ l.Enable()
31+ l.SetColor(color.RGBA{b, 0, 0, 0})
32+}
33+
34+// On is a shortcut to Red(l, 0xff)
35+func On(l LED) {
36+ Red(l, 0xff)
37+}
38+
39+// Off is a shortcut to Red(l, 0)
40+func Off(l LED) {
41+ Red(l, 0)
42+}
43+
44+// Flash l until ctx is done.
45+//
46+// You can use this to asynchronously flash:
47+//
48+// ctx, _ := context.WithTimeout(context.Background(), 5 * time.Second)
49+// blink := 500 * Millisecond
50+// go Flash(ctx, Builtin, blink, blink)
51+//
52+// Or you could flash forever, possibly to signal an error:
53+//
54+// blink := 500 * Millisecond
55+// Flash(context.Background(), Builtin, blink, blink)
56+func Flash(ctx context.Context, l LED, on time.Duration, off time.Duration) {
57+ ledOn := false
58+ var delay time.Duration
59+
60+ for {
61+ ledOn = !ledOn
62+ if ledOn {
63+ delay = on
64+ On(l)
65+ } else {
66+ delay = off
67+ Off(l)
68+ }
69+
70+ // Wait for something to happen
71+ select {
72+ case <-ctx.Done():
73+ Off(l)
74+ return
75+ case <-time.After(delay):
76+ continue
77+ }
78+ }
79+}
+37,
-0
1@@ -0,0 +1,37 @@
2+package led
3+
4+import (
5+ "machine"
6+ "image/color"
7+ "tinygo.org/x/drivers/ws2812"
8+)
9+
10+type NeoPixel struct {
11+ power machine.Pin
12+ device ws2812.Device
13+}
14+
15+func NewNeoPixel(data, power machine.Pin) *NeoPixel {
16+ return &NeoPixel{
17+ power: power,
18+ device: ws2812.NewWS2812(data),
19+ }
20+}
21+
22+func (p *NeoPixel) Enable() error {
23+ p.device.Pin.Configure(machine.PinConfig{machine.PinOutput})
24+ p.power.Configure(machine.PinConfig{machine.PinOutput})
25+ p.power.High()
26+ return nil
27+}
28+
29+func (p *NeoPixel) Disable() {
30+ p.power.Low()
31+}
32+
33+func (p *NeoPixel) SetColor(c color.Color) {
34+ // sigh. https://github.com/tinygo-org/drivers/issues/837
35+ rgba := color.RGBAModel.Convert(c).(color.RGBA)
36+ p.device.WriteColors([]color.RGBA{rgba})
37+}
38+
+55,
-0
1@@ -0,0 +1,55 @@
2+//go:build arduino
3+
4+package led
5+
6+import "machine"
7+
8+// BUG: machine should make this an exported interface, or export timerType
9+type PWM interface {
10+ Configure(config machine.PWMConfig) error
11+ Channel(pin machine.Pin) (channel uint8, err error)
12+ Set(chanel uint8, value uint32)
13+ Top() uint32
14+}
15+
16+type Pin struct {
17+ machine.Pin
18+ pwm PWM
19+ invert bool // Does a low pin turn the LED on?
20+}
21+
22+func (p Pin) Enable() {
23+ if p.pwm != nil {
24+ pwm.Configure(machine.PWMConfig{})
25+ pwm.Channel(p.Pin)
26+ return
27+ }
28+ p.Configure(machine.PinConfig{machine.PinOutput})
29+}
30+
31+func (p Pin) Disable() {
32+ // XXX: is there a standard way to detach the pwm?
33+}
34+
35+func (p Pin) SetColor(c color.Color) {
36+ // Our LED can only make one color. Convert c to gray so we can calculate intensity.
37+ g := color.RGBAModel.Convert(c).(color.Gray16)
38+ if p.pwm == nil {
39+ state := g.Y > 0 // Power the LED for anything other than black
40+ if (p.invert) {
41+ state = !state
42+ }
43+ p.Pin.Set(state)
44+ return
45+ }
46+
47+ // Top() is the maximum value you can pass in to set.
48+ // cycle is therefore a fraction of top.
49+ // BUG: https://tinygo.org/docs/tutorials/pwm/ could make this more clear.
50+ // XXX: Is this arithmetic going to overflow uint32?
51+ cycle := pwm.Top() * g.Y / 0xffff
52+ if p.invert {
53+ cycle = pwm.Top() - cycle
54+ }
55+ pwm.Set(cycle)
56+}