concertina

Elecronic concertina
git clone https://git.woozle.org/neale/concertina.git

commit
c18a343
parent
e99ed1c
author
Neale Pickett
date
2026-02-23 20:18:37 -0700 MST
Working with bellows, about to pivot to rust
4 files changed,  +100, -2
M Makefile
+7, -2
 1@@ -11,6 +11,11 @@ clean:
 2 build/%/concertina.bin: cmd/concertina
 3 	tinygo build -o $@ -target=$* ./$</...
 4 
 5-layouts.h: layouts.txt mklayouts.py
 6-	./mklayouts.py $< > $@
 7 
 8+flash-test: flash-qtpy-test
 9+flash-qtpy-test: build/qtpy/bellows-test.bin
10+	stty -F /dev/ttyACM0 1200 && sleep 1 # Reset into bootloader
11+	bossac --offset=0x2000 -w -v $< -R
12+
13+build/%/bellows-test.bin: cmd/bellows-test
14+	tinygo build -o $@ -target=$* ./$</...
A cmd/bellows-test/bellows-test.go
+81, -0
 1@@ -0,0 +1,81 @@
 2+package main
 3+
 4+import (
 5+	"context"
 6+	"image/color"
 7+	"machine"
 8+	"time"
 9+
10+	"git.woozle.org/neale/concertina/pkg/led"
11+	"tinygo.org/x/drivers/ssd1306"
12+	"tinygo.org/x/drivers/vl6180x"
13+)
14+
15+// For crying out loud.
16+// https://github.com/tinygo-org/drivers/issues/837`
17+var White = color.RGBAModel.Convert(color.White).(color.RGBA)
18+var Black = color.RGBAModel.Convert(color.Black).(color.RGBA)
19+
20+func main() {
21+	led.Builtin.Enable()
22+
23+	if err := machine.I2C0.Configure(machine.I2CConfig{}); err != nil {
24+		led.Flash(context.Background(), led.Builtin, time.Second/4, time.Second/4)
25+	}
26+
27+	display := ssd1306.NewI2C(machine.I2C0)
28+	display.Configure(
29+		ssd1306.Config{
30+			Width:   64,
31+			Height:  32,
32+
33+			// https://github.com/tinygo-org/drivers/issues/842
34+			ResetCol: ssd1306.ResetValue{0x20, 0x20+64-1},
35+
36+			Address: 0x3C,
37+		},
38+	)
39+	display.ClearDisplay()
40+	for i := 0; i < 64; i += 4 {
41+		display.FillRectangle(0, 30, int16(i), 2, White)
42+		display.Display()
43+	}
44+
45+	prox := vl6180x.New(machine.I2C0)
46+	prox.Configure(false)
47+	prox.StartRangeContinuous(40) // milliseconds between samples
48+
49+	machine.D3.Configure(machine.PinConfig{machine.PinInputPullup})
50+
51+	var distance uint16
52+	for {
53+		if machine.D3.Get() {
54+			continue
55+		}
56+
57+		led.Builtin.SetColor(color.RGBA{0, 0, 1, 0})
58+		start := time.Now()
59+		distance = prox.Read()
60+		duration := time.Now().Sub(start)
61+		led.Builtin.SetColor(color.RGBA{0, 0, 0, 0})
62+
63+		// The top bar shows how long it took to read the value.
64+		// One pixel is 100 microseconds.
65+		// I'm seeing values around 2ms
66+		duration_ms := duration / (100 * time.Microsecond)
67+		if duration_ms > 50 {
68+			duration_ms = 50
69+		}
70+		display.FillRectangle(int16(duration_ms), 0, 10, 8, Black)
71+		display.FillRectangle(0, 0, int16(duration_ms), 8, White)
72+
73+		// The bottom bar is roughly what the bellows is reading.
74+		distance = distance / 2
75+		if distance > 50 {
76+			distance = 50
77+		}
78+		display.FillRectangle(0, 16, 64, 8, Black)
79+		display.FillRectangle(0, 16, int16(distance), 8, White)
80+		display.Display()
81+	}
82+}
M cmd/concertina/bellows.go
+10, -0
 1@@ -1,6 +1,8 @@
 2 package main
 3 
 4 import (
 5+	"time"
 6+
 7 	"tinygo.org/x/drivers"
 8 	"tinygo.org/x/drivers/vl6180x"
 9 )
10@@ -9,6 +11,12 @@ import (
11 // Set this lower to make the bellows more "twitchy"
12 const BellowsSlop = 2
13 
14+// BellowsSamplePeriod is the duration between samples.
15+// I think (I'm not sure) that this device blocks the I2C bus while it does a sample,
16+// making everything less responsive. Using the built-in continuous range sampling mechanism
17+// should mean the most recent sample is always immediately available.
18+const BellowsSamplePeriod = time.Second / 10
19+
20 type Bellows struct {
21 	vl6180x.Device
22 	prev uint16
23@@ -19,6 +27,7 @@ func NewBellows(i2c drivers.I2C) *Bellows {
24 		Device: vl6180x.New(i2c),
25 	}
26 	b.Device.Configure(false) // this argument is ignored!
27+	//b.Device.StartRangeContinuous(uint16(BellowsSamplePeriod / time.Millisecond))
28 	return b
29 }
30 
31@@ -29,5 +38,6 @@ func (b *Bellows) Direction() int16 {
32 	distance := b.Read()
33 	dir := int16(b.prev -distance) / BellowsSlop
34 	b.prev = distance
35+	return int16(distance)
36 	return dir
37 }
M cmd/concertina/concertina.go
+2, -0
 1@@ -101,6 +101,7 @@ func (c *Concertina) Loop() {
 2 	// get the bellows direction
 3 	c.display.FillRectangle(0, 16, 100, 4, Black)
 4 	dir := c.bellows.Direction()
 5+	//dir := int16(-4)
 6 	if (dir < 0) {
 7 		c.display.FillRectangle(50+dir, 16, -dir, 4, White)
 8 	} else if (dir > 0) {
 9@@ -141,6 +142,7 @@ func main() {
10 			time.Sleep(1 * time.Hour)
11 		}
12 	}
13+	time.Sleep(2 * time.Second)
14 
15 	// Okay, you can stop blinking now
16 	blinkCancel()