├── .gitignore ├── README.md ├── adv └── adv.go ├── badge ├── badge.go ├── go.mod ├── go.sum ├── images.go ├── images │ └── tinygo-logo-small.qoi ├── leds.go ├── mandelbrot.go ├── noise.go ├── sensors.go ├── test-colors.go ├── test-tearing.go └── test-touch.go ├── cloud ├── control.py ├── go.mod ├── go.sum ├── main.go ├── pins_arduino.go ├── pins_esp32.go ├── pins_feather-stm32f405.go ├── pins_pico.go └── pins_pybadge.go ├── dress ├── go.mod ├── go.sum └── main.go ├── earring-leaf ├── Makefile ├── README.md ├── earrings.jpg ├── gerber.zip ├── main.c └── schematic.pdf ├── earring-ring-rgb36 ├── .gitignore ├── earring │ ├── Custom.pretty │ │ ├── MY-1220-03.kicad_mod │ │ ├── NH-B1212RGBA-HF.kicad_mod │ │ ├── NH-B1515RGBA-HF.kicad_mod │ │ ├── NH-Z1415RGBA-SG.kicad_mod │ │ ├── ProgramHeader.kicad_mod │ │ ├── ToolingHoleWithMouseBytes.kicad_mod │ │ └── ToolingHole_JLCPCB.kicad_mod │ ├── earring.kicad_pcb │ ├── earring.kicad_prl │ ├── earring.kicad_pro │ ├── earring.kicad_sch │ ├── earring.kicad_sch-bak │ ├── fabrication-toolkit-options.json │ └── fp-lib-table └── place-components.py ├── earring-ring ├── README.md ├── bitbang.go ├── bitbang.h ├── earring.jpg ├── go.mod ├── go.sum ├── main.go ├── pcb-v1-BOM.csv ├── pcb-v1-PickAndPlace.csv ├── pcb-v1-back.png ├── pcb-v1-front.png ├── pcb-v1-gerber.zip ├── pcb-v1.pcbdoc ├── pcb-v3-BOM.csv ├── pcb-v3-PickAndPlace.csv ├── pcb-v3-gerber.zip ├── pcb-v3.pcbdoc ├── schematic-v1.pdf ├── schematic-v3.pdf ├── simulation.go └── simulator.png ├── festival-camp ├── go.mod ├── go.sum └── main.go ├── games └── brick-breaker │ ├── go.mod │ ├── go.sum │ └── main.go ├── globe ├── Makefile └── globe.go ├── goggles-8ring ├── go.mod ├── go.sum └── main.go ├── hub75 ├── driver.go ├── driver_nrf52.go ├── driver_samd21.go ├── driver_samd51.go └── examples │ └── patterns │ ├── arduino-nano33.go │ ├── itsybitsy-m4.go │ ├── main.go │ └── pca10040.go ├── ledcube ├── README.md ├── baremetal.go ├── itsybitsy-m4.go ├── main.go └── unix.go ├── mch2022-leds ├── LICENSE.txt ├── README.md ├── go.mod ├── go.sum └── main.go ├── mch2022-noise ├── go.mod ├── go.sum ├── icon.png └── main.go ├── poi ├── Makefile ├── README.md ├── blue.go ├── go.mod ├── go.sum ├── main.go ├── nrf.go ├── red.go └── v2.go ├── spirit-level ├── README.md └── main.go ├── test-ws2812 ├── go.mod ├── go.sum ├── main.go ├── pins_arduino.go ├── pins_esp32.go ├── pins_feather-stm32f405.go ├── pins_pico.go ├── pins_pybadge.go └── pins_ws2812.go └── watch ├── README.md ├── assets ├── watchface-0.png ├── watchface-0.raw ├── watchface-1.png ├── watchface-1.raw ├── watchface-2.png ├── watchface-2.raw ├── watchface-3.png ├── watchface-3.raw ├── watchface-4.png ├── watchface-4.raw ├── watchface-5.png ├── watchface-5.raw ├── watchface-6.png ├── watchface-6.raw ├── watchface-7.png ├── watchface-7.raw ├── watchface-8.png ├── watchface-8.raw ├── watchface-9.png ├── watchface-9.raw ├── watchface-colon.png └── watchface-colon.raw ├── ble-none.go ├── ble.go ├── go.mod ├── go.sum ├── img └── simulator.png ├── notinygo.go ├── pinetime-wasp-bootloader.json ├── pinetime-wasp-bootloader.ld ├── tinygo.go ├── ui.go ├── watch.go └── watchface.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.elf 2 | *.hex 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sandbox for electronics projects 2 | 3 | This is my personal stash of small electronics projects, usually written using [TinyGo](https://tinygo.org). 4 | 5 | I won't guarantee anything about it. All I can say is that it did work for me at some point and I like sharing what I made. Many things are written once and then mostly forgotten so I probably won't provide any support for any of the code. 6 | 7 | ## License 8 | 9 | Feel free to use this code however you wish (in other words, you can treat it as public domain). Especially for small parts I won't mind if you just copy it in your project. For larger pieces of code or if you copy a project entirely, I would appreciate attribution somewhere (but it's not required). 10 | 11 | If you prefer having an actual license for legal reasons, you can use the [MIT license](https://opensource.org/license/mit/) - but again, the code is public domain so no need to actually use this: 12 | 13 | ``` 14 | Copyright 2023 Ayke van Laethem 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a 17 | copy of this software and associated documentation files (the 18 | “Software”), to deal in the Software without restriction, including 19 | without limitation the rights to use, copy, modify, merge, publish, 20 | distribute, sublicense, and/or sell copies of the Software, and to 21 | permit persons to whom the Software is furnished to do so, subject to 22 | the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included 25 | in all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS 28 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 29 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 30 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 31 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 32 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 33 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 34 | ``` 35 | -------------------------------------------------------------------------------- /adv/adv.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aykevl/go-ble/s132v6" 7 | ) 8 | 9 | func main() { 10 | println("starting...") 11 | time.Sleep(1 * time.Second) 12 | 13 | println("sd enabled:", sd.IsEnabled()) 14 | println("sd enable:", sd.Enable(sd.DefaultClockSource)) 15 | println("sd enabled:", sd.IsEnabled()) 16 | 17 | var app_ram_base uintptr = 0x200039c0 18 | ram, err := sd.EnableBLE(app_ram_base) 19 | println("ble enabled:", ram, err) 20 | 21 | adv := sd.NewAdvertisement() 22 | 23 | params := &sd.AdvParams{ 24 | Properties: sd.AdvProperties{ 25 | Type: sd.AdvTypeConnectableScannableUndirected, 26 | Fields: 0, 27 | }, 28 | Interval: 100, 29 | Duration: 0, // unlimited 30 | MaxAdvEvts: 0, 31 | FilterPolicy: 0, 32 | PrimaryPhi: 0, 33 | SecondaryPhi: 0, 34 | Fields: 0, 35 | } 36 | adv.Configure("\x02\x01\x06"+"\x07\x09TinyGo", "", params) // flags + local name 37 | println("ble adv configure:", err) 38 | 39 | println("ble adv start:", adv.Start()) 40 | 41 | for { 42 | time.Sleep(10 * time.Second) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /badge/badge.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aykevl/board" 7 | "github.com/aykevl/tinygl" 8 | "github.com/aykevl/tinygl/style" 9 | "github.com/aykevl/tinygl/style/basic" 10 | "tinygo.org/x/drivers/pixel" 11 | ) 12 | 13 | func main() { 14 | println("start") 15 | if board.Name == "simulator" { 16 | // Use the configuration for the Gopher Badge. 17 | board.Simulator.WindowWidth = 320 18 | board.Simulator.WindowHeight = 240 19 | board.Simulator.WindowPPI = 166 20 | board.Simulator.WindowDrawSpeed = time.Second * 16 / 62_500e3 // 62.5MHz, 16bpp 21 | } 22 | 23 | board.Buttons.Configure() 24 | run(board.Display.Configure(), board.Display.ConfigureTouch()) 25 | } 26 | 27 | func run[T pixel.Color](display board.Displayer[T], touchInput board.TouchInput) { 28 | // Determine size and scale of the screen. 29 | width, height := display.Size() 30 | scalePercent := board.Display.PPI() * 100 / 120 31 | 32 | // Initialize the screen. 33 | buf := pixel.NewImage[T](int(width), int(height)/4) 34 | screen := tinygl.NewScreen[T](display, buf, board.Display.PPI()) 35 | theme := basic.NewTheme(style.NewScale(scalePercent), screen) 36 | println("scale:", scalePercent, "=>", theme.Scale.Percent()) 37 | 38 | // Create badge homescreen. 39 | header := theme.NewText("Hello world!") 40 | header.SetBackground(pixel.NewColor[T](255, 0, 0)) 41 | header.SetColor(pixel.NewColor[T](255, 255, 255)) 42 | listbox := theme.NewListBox([]string{ 43 | "Noise", 44 | "Mandelbrot", 45 | "Display test colors", 46 | "Touch test", 47 | "Tearing test", 48 | "Sensors", 49 | "LEDs", 50 | "Images", 51 | }) 52 | listbox.SetGrowable(0, 1) // listbox fills the rest of the screen 53 | listbox.Select(0) // focus the first element 54 | home := tinygl.NewVerticalScrollBox[T](header, listbox, nil) 55 | 56 | // Handle touch events in the listbox. 57 | listbox.SetEventHandler(func(event tinygl.Event, index int) { 58 | if event == tinygl.TouchTap { 59 | runApp(index, display, screen, home, touchInput) 60 | } 61 | }) 62 | 63 | // Show screen. 64 | screen.SetChild(home) 65 | screen.Update() 66 | board.Display.SetBrightness(board.Display.MaxBrightness()) 67 | 68 | for { 69 | // TODO: wait for input instead of polling 70 | board.Buttons.ReadInput() 71 | for { 72 | event := board.Buttons.NextEvent() 73 | if event == board.NoKeyEvent { 74 | break 75 | } 76 | if !event.Pressed() { 77 | continue 78 | } 79 | switch event.Key() { 80 | case board.KeyUp: 81 | index := listbox.Selected() - 1 82 | if index < 0 { 83 | index = listbox.Len() - 1 84 | } 85 | listbox.Select(index) 86 | case board.KeyDown: 87 | index := listbox.Selected() + 1 88 | if index >= listbox.Len() { 89 | index = 0 90 | } 91 | listbox.Select(index) 92 | case board.KeyEnter, board.KeyA: 93 | runApp(listbox.Selected(), display, screen, home, touchInput) 94 | } 95 | } 96 | 97 | // Handle touch inputs. 98 | touches := touchInput.ReadTouch() 99 | if len(touches) > 0 { 100 | screen.SetTouchState(touches[0].X, touches[0].Y) 101 | } else { 102 | screen.SetTouchState(-1, -1) 103 | } 104 | 105 | screen.Update() 106 | time.Sleep(time.Second / 30) 107 | } 108 | } 109 | 110 | func runApp[T pixel.Color](index int, display board.Displayer[T], screen *tinygl.Screen[T], home *tinygl.VerticalScrollBox[T], touchInput board.TouchInput) { 111 | switch index { 112 | case 0: 113 | println("starting noise") 114 | noise(display, screen) 115 | case 1: 116 | println("starting Mandelbrot") 117 | mandelbrot(screen) 118 | case 2: 119 | println("starting display test colors") 120 | testColors(screen) 121 | case 3: 122 | println("starting touch test") 123 | testTouch(screen, touchInput) 124 | case 4: 125 | println("starting tearing test") 126 | testTearing(screen, touchInput) 127 | case 5: 128 | println("starting sensors") 129 | showSensors(screen) 130 | case 6: 131 | println("toggle LEDs") 132 | toggleLEDs() 133 | case 7: 134 | println("starting images") 135 | showImages(screen) 136 | } 137 | 138 | // The app used a different root element. Restore the homescreen. 139 | screen.SetChild(home) 140 | } 141 | -------------------------------------------------------------------------------- /badge/go.mod: -------------------------------------------------------------------------------- 1 | module ayke/things/badge 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/aykevl/board v0.0.0-20231124144211-feeba0ede83c 7 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 8 | github.com/aykevl/tinygl v0.0.0-20240131130748-3033a2fd9182 9 | tinygo.org/x/drivers v0.26.1-0.20231124130000-fef6564044f9 10 | ) 11 | 12 | require ( 13 | fyne.io/fyne/v2 v2.4.1 // indirect 14 | fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/fredbi/uri v1.1.0 // indirect 17 | github.com/fsnotify/fsnotify v1.7.0 // indirect 18 | github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934 // indirect 19 | github.com/fyne-io/glfw-js v0.0.0-20231117203605-bc7c6f97d52f // indirect 20 | github.com/fyne-io/image v0.0.0-20230811065323-ed435dc8bca6 // indirect 21 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect 22 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20231124074035-2de0cf0c80af // indirect 23 | github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 // indirect 24 | github.com/go-text/typesetting v0.0.0-20231120180320-af78120ccb13 // indirect 25 | github.com/godbus/dbus/v5 v5.1.0 // indirect 26 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 27 | github.com/gopherjs/gopherjs v1.17.2 // indirect 28 | github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect 29 | github.com/pmezard/go-difflib v1.0.0 // indirect 30 | github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect 31 | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect 32 | github.com/stretchr/testify v1.8.4 // indirect 33 | github.com/tevino/abool v1.2.0 // indirect 34 | github.com/yuin/goldmark v1.6.0 // indirect 35 | golang.org/x/image v0.14.0 // indirect 36 | golang.org/x/mobile v0.0.0-20231108233038-35478a0c49da // indirect 37 | golang.org/x/net v0.18.0 // indirect 38 | golang.org/x/sys v0.14.0 // indirect 39 | golang.org/x/text v0.14.0 // indirect 40 | gopkg.in/yaml.v3 v3.0.1 // indirect 41 | honnef.co/go/js/dom v0.0.0-20231030024858-cb489e859d05 // indirect 42 | ) 43 | -------------------------------------------------------------------------------- /badge/images.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | "time" 6 | 7 | "github.com/aykevl/board" 8 | "github.com/aykevl/tinygl" 9 | "github.com/aykevl/tinygl/image" 10 | "tinygo.org/x/drivers/pixel" 11 | ) 12 | 13 | // Image created using ImageMagic: 14 | // 15 | // convert tinygo-logo.png -background white -alpha remove -alpha off -resize 320x240 -depth 5 tinygo-logo-small.qoi 16 | // 17 | // (Remove alpha channel, resize to fit the screen, remove unnecessary bits, 18 | // convert to QOI format). 19 | // 20 | //go:embed images/tinygo-logo-small.qoi 21 | var tinygoLogo string 22 | 23 | func showImages[T pixel.Color](screen *tinygl.Screen[T]) { 24 | // Load image. 25 | img, err := image.NewQOI[T](tinygoLogo) 26 | if err != nil { 27 | println("could not load image:", err) 28 | return 29 | } 30 | mainImage := tinygl.NewImage[T](img) 31 | mainImage.SetBackground(pixel.NewColor[T](255, 255, 255)) 32 | screen.SetChild(mainImage) 33 | 34 | // Show screen. 35 | screen.Update() 36 | 37 | for { 38 | // Read keyboard inputs. 39 | board.Buttons.ReadInput() 40 | for { 41 | // Read keyboard events. 42 | event := board.Buttons.NextEvent() 43 | if event == board.NoKeyEvent { 44 | break 45 | } 46 | if event.Pressed() { 47 | switch event.Key() { 48 | case board.KeyB, board.KeyEscape: 49 | return 50 | } 51 | } 52 | } 53 | 54 | time.Sleep(time.Second / 10) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /badge/images/tinygo-logo-small.qoi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/badge/images/tinygo-logo-small.qoi -------------------------------------------------------------------------------- /badge/leds.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Demonstrate support for on-board RGB LEDs. 4 | 5 | import ( 6 | "time" 7 | 8 | "github.com/aykevl/board" 9 | "github.com/aykevl/ledsgo" 10 | ) 11 | 12 | var ledsStop chan struct{} 13 | 14 | func init() { 15 | board.AddressableLEDs.Configure() 16 | } 17 | 18 | func toggleLEDs() { 19 | if ledsStop != nil { 20 | // Stop running LEDs. 21 | close(ledsStop) 22 | ledsStop = nil 23 | return 24 | } 25 | 26 | // Start LEDs. 27 | ledsStop = make(chan struct{}) 28 | go showLEDs(ledsStop) 29 | } 30 | 31 | func showLEDs(stop chan struct{}) { 32 | array := board.AddressableLEDs 33 | for { 34 | select { 35 | case <-stop: 36 | // An exit was requested. 37 | // Set all LEDs back to black (off). 38 | for i := 0; i < array.Len(); i++ { 39 | array.SetRGB(i, 0, 0, 0) 40 | } 41 | array.Update() 42 | return 43 | default: 44 | // Continue showing LEDs. 45 | } 46 | 47 | // Update LEDs. 48 | now := time.Now() 49 | for i := 0; i < array.Len(); i++ { 50 | index := i*4096 + int(now.UnixNano()>>8) 51 | c := ledsgo.Color{H: uint16(index), S: 255, V: 255}.Rainbow() 52 | array.SetRGB(i, c.R, c.G, c.B) 53 | } 54 | array.Update() 55 | 56 | time.Sleep(time.Second / 60) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /badge/mandelbrot.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aykevl/board" 7 | "github.com/aykevl/ledsgo" 8 | "github.com/aykevl/tinygl" 9 | "github.com/aykevl/tinygl/gfx" 10 | "tinygo.org/x/drivers/pixel" 11 | ) 12 | 13 | const ( 14 | maxIterations = 50 15 | frac = 12 // fractional bits 16 | ) 17 | 18 | // Render a mandelbrot. Allow the user to move around the screen and zoom in/out 19 | // the fractal. 20 | func mandelbrot[T pixel.Color](screen *tinygl.Screen[T]) { 21 | width, height := screen.Size() 22 | stepY := int(2 << (frac * 2) / int64(height)) 23 | stepX := int((3 << (frac * 2)) / int64(width)) 24 | centerX := stepX * width / -6 25 | centerY := 0 26 | 27 | black := pixel.NewColor[T](0, 0, 0) 28 | canvas := gfx.NewCustomCanvas(black, 0, 0, func(screen *tinygl.Screen[T], displayX, displayY, displayWidth, displayHeight, x, y int) { 29 | buffer := screen.Buffer() 30 | bufferLines := buffer.Len() / displayWidth 31 | start := time.Now() 32 | i := centerY - (displayHeight/2)*stepY 33 | for startY := 0; startY < displayHeight; startY += bufferLines { 34 | chunkHeight := bufferLines 35 | if startY+chunkHeight >= displayHeight { 36 | chunkHeight = displayHeight - startY 37 | } 38 | img := buffer.Rescale(displayWidth, chunkHeight) 39 | for chunkY := 0; chunkY < chunkHeight; chunkY++ { 40 | r := centerX - (displayWidth/2)*stepX 41 | i += stepY 42 | for x := 0; x < displayWidth; x++ { 43 | r += stepX 44 | iterations := mandelbrotAt(r>>frac, i>>frac) 45 | //iterations := mandelbrotPreciseAt(r, i) 46 | rawColor := pixel.NewColor[T](0, 0, 0) 47 | if iterations != 255 { 48 | c := ledsgo.RainbowColors.ColorAt(uint16(iterations * 2048)) 49 | rawColor = pixel.NewColor[T](c.R, c.G, c.B) 50 | } 51 | img.Set(x, chunkY, rawColor) 52 | } 53 | } 54 | screen.Send(displayX, displayY+startY, img) 55 | } 56 | duration := time.Since(start) 57 | println("rendering took:", duration.String()) 58 | }) 59 | screen.SetChild(canvas) 60 | 61 | for { 62 | board.Buttons.ReadInput() 63 | for { 64 | event := board.Buttons.NextEvent() 65 | if event == board.NoKeyEvent { 66 | break 67 | } 68 | if !event.Pressed() { 69 | continue 70 | } 71 | switch event.Key() { 72 | case board.KeyA: 73 | stepX = stepX * 2 / 3 74 | stepY = stepY * 2 / 3 75 | canvas.RequestUpdate() 76 | case board.KeyB: 77 | stepX = stepX * 3 / 2 78 | stepY = stepY * 3 / 2 79 | canvas.RequestUpdate() 80 | case board.KeyLeft: 81 | centerX -= (width * stepX) / 8 82 | canvas.RequestUpdate() 83 | case board.KeyRight: 84 | centerX += (width * stepX) / 8 85 | canvas.RequestUpdate() 86 | case board.KeyUp: 87 | centerY -= (height * stepY) / 8 88 | canvas.RequestUpdate() 89 | case board.KeyDown: 90 | centerY += (height * stepY) / 8 91 | canvas.RequestUpdate() 92 | case board.KeyEscape: 93 | return 94 | } 95 | } 96 | 97 | screen.Update() 98 | board.Display.WaitForVBlank(time.Second / 30) 99 | } 100 | } 101 | 102 | func mandelbrotAt(x0, y0 int) int { 103 | // This check is expensive, so don't do it. 104 | //if x0 < -2< 2< 2<>(frac-1) + y0 118 | x = (x2-y2)>>frac + x0 119 | x2 = x * x 120 | y2 = y * y 121 | iteration++ 122 | if iteration == maxIterations { 123 | return 255 124 | } 125 | } 126 | return iteration 127 | } 128 | 129 | // Improved precision version of the mandelbrot function. 130 | // This is a bit slower (~40%) on chips with a 32x32=64 multiply instruction 131 | // (like smull on ARM, available in Cortex-M3 and above). It is _much_ slower on 132 | // chips without such a multiply instruction. 133 | func mandelbrotPreciseAt(_x0, _y0 int) int { 134 | const frac2 = 24 135 | x0 := int32(_x0) >> (frac*2 - frac2) 136 | y0 := int32(_y0) >> (frac*2 - frac2) 137 | x := int32(0) 138 | y := int32(0) 139 | iteration := 1 140 | x2 := int64(0) // .frac*2 141 | y2 := int64(0) // .frac*2 142 | for x2+y2 <= 4<<(frac2*2) { 143 | y = int32((int64(x)*int64(y))>>(frac2-1)) + y0 144 | x = int32((x2-y2)>>frac2) + x0 145 | x2 = int64(x) * int64(x) 146 | y2 = int64(y) * int64(y) 147 | iteration++ 148 | if iteration == maxIterations { 149 | return 255 150 | } 151 | } 152 | return iteration 153 | } 154 | -------------------------------------------------------------------------------- /badge/noise.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aykevl/board" 7 | "github.com/aykevl/ledsgo" 8 | "github.com/aykevl/tinygl" 9 | "github.com/aykevl/tinygl/gfx" 10 | "tinygo.org/x/drivers/pixel" 11 | ) 12 | 13 | const ( 14 | // Rectangle size for which a noise value is calculated. Must be a power of 15 | // two. 16 | // Lower values mean better quality, but increased computation needs. 17 | noisePixelSize = 8 18 | 19 | // Noise size. A large value means more detail (higher frequency noise). 20 | noiseSize = 16 21 | ) 22 | 23 | func noise[T pixel.Color](display board.Displayer[T], screen *tinygl.Screen[T]) { 24 | var colorMap [256]T 25 | for i := range colorMap { 26 | c := ledsgo.Color{H: uint16(i * 256), S: 255, V: 255}.Rainbow() 27 | colorMap[i] = pixel.NewLinearColor[T](c.R, c.G, c.B) 28 | } 29 | 30 | black := pixel.NewColor[T](0, 0, 0) 31 | var drawTimeSum time.Duration 32 | canvas := gfx.NewCustomCanvas[T](black, 0, 0, func(screen *tinygl.Screen[T], displayX, displayY, displayWidth, displayHeight, x, y int) { 33 | // TODO: many parameters (displayX, displayY, x, y) are ignored. They 34 | // should be used to paint noise at the right location on the screen. 35 | 36 | noiseWidth := (int(displayWidth)+noisePixelSize-1)/noisePixelSize + 1 37 | noiseLine := make([]uint16, noiseWidth) 38 | 39 | now := time.Now() 40 | z := uint32(now.UnixNano() >> 20) 41 | for i := 0; i < noiseWidth; i++ { 42 | value := ledsgo.Noise3(0, uint32(i*noisePixelSize)*noiseSize, z) 43 | noiseLine[i] = value 44 | } 45 | for y := 0; y < int(displayHeight); { 46 | buf := screen.Buffer() 47 | lines := buf.Len() / int(displayWidth) 48 | if y+lines >= int(displayHeight) { 49 | lines = int(displayHeight) - y 50 | } 51 | lines -= lines % noisePixelSize // round down 52 | buf = buf.Rescale(displayWidth, lines) 53 | for chunkY := 0; chunkY < lines; chunkY += noisePixelSize { 54 | valueTopLeft := noiseLine[0] 55 | valueBottomLeft := ledsgo.Noise3(uint32(y+chunkY+noisePixelSize)*noiseSize, 0, z) 56 | noiseLine[0] = valueBottomLeft 57 | for x := 0; x < int(displayWidth); x += noisePixelSize { 58 | valueTopRight := noiseLine[x/noisePixelSize+1] 59 | valueBottomRight := ledsgo.Noise3(uint32(y+chunkY+noisePixelSize)*noiseSize, uint32(x+noisePixelSize)*noiseSize, z) 60 | noiseLine[x/noisePixelSize+1] = valueBottomRight 61 | interpolatedTop := int(valueTopLeft) * noisePixelSize 62 | interpolatedDiffTop := int(valueTopRight) - int(valueTopLeft) 63 | interpolatedBottom := int(valueBottomLeft) * noisePixelSize 64 | interpolatedDiffBottom := int(valueBottomRight) - int(valueBottomLeft) 65 | for posX := 0; posX < noisePixelSize; posX++ { 66 | interpolatedDiffY := (interpolatedBottom - interpolatedTop) / noisePixelSize 67 | interpolated := interpolatedTop 68 | for posY := 0; posY < noisePixelSize; posY++ { 69 | c := colorMap[uint(interpolated/128/noisePixelSize)%256] 70 | buf.Set(x+posX, chunkY+posY, c) 71 | interpolated += interpolatedDiffY 72 | } 73 | interpolatedTop += interpolatedDiffTop 74 | interpolatedBottom += interpolatedDiffBottom 75 | } 76 | valueTopLeft = valueTopRight 77 | valueBottomLeft = valueBottomRight 78 | } 79 | } 80 | drawStart := time.Now() 81 | screen.Send(0, y, buf) 82 | drawTimeSum += time.Since(drawStart) 83 | y += lines 84 | } 85 | 86 | }) 87 | screen.SetChild(canvas) 88 | 89 | var frame uint32 90 | frameSumStart := time.Now() 91 | for { 92 | // Handle keyboard inputs, to exit from the noise demo. 93 | board.Buttons.ReadInput() 94 | for { 95 | event := board.Buttons.NextEvent() 96 | if event == board.NoKeyEvent { 97 | break 98 | } 99 | if !event.Pressed() { 100 | continue 101 | } 102 | switch event.Key() { 103 | case board.KeyEscape, board.KeyB: 104 | if event.Pressed() { 105 | return 106 | } 107 | } 108 | } 109 | 110 | // The canvas needs to be updated each cycle, so request an update. 111 | canvas.RequestUpdate() 112 | screen.Update() 113 | 114 | // Don't wait for VBlank to improve rendering speed. 115 | 116 | // Print draw statistics. 117 | // It prints the time each frame takes (excluding the print line below), 118 | // and the time spent starting the next transmission to the display. 119 | const numFrames = 32 120 | frame++ 121 | if frame%numFrames == 0 { 122 | frameTimeSum := time.Since(frameSumStart) 123 | println("time:", (frameTimeSum / numFrames).String(), (drawTimeSum / numFrames).String()) 124 | frameSumStart = time.Now() 125 | drawTimeSum = 0 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /badge/sensors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | 7 | "github.com/aykevl/board" 8 | "github.com/aykevl/tinygl" 9 | "github.com/aykevl/tinygl/style" 10 | "github.com/aykevl/tinygl/style/basic" 11 | "tinygo.org/x/drivers" 12 | "tinygo.org/x/drivers/pixel" 13 | ) 14 | 15 | func showSensors[T pixel.Color](screen *tinygl.Screen[T]) { 16 | // Determine size and scale of the screen. 17 | scalePercent := board.Display.PPI() * 100 / 120 18 | 19 | // Create UI. 20 | theme := basic.NewTheme(style.NewScale(scalePercent), screen) 21 | header := theme.NewText("Sensors") 22 | header.SetBackground(pixel.NewColor[T](0, 0, 255)) 23 | header.SetColor(pixel.NewColor[T](255, 255, 255)) 24 | battery := theme.NewText("battery: ...") 25 | battery.SetAlign(tinygl.AlignLeft) 26 | voltage := theme.NewText("voltage: ...") 27 | voltage.SetAlign(tinygl.AlignLeft) 28 | temperature := theme.NewText("temp: ...") 29 | temperature.SetAlign(tinygl.AlignLeft) 30 | acceleration := theme.NewText("accel: ...") 31 | acceleration.SetAlign(tinygl.AlignLeft) 32 | steps := theme.NewText("steps: ...") 33 | steps.SetAlign(tinygl.AlignLeft) 34 | vbox := theme.NewVBox(header, battery, voltage, temperature, acceleration, steps) 35 | screen.SetChild(vbox) 36 | 37 | // Show screen. 38 | screen.Update() 39 | 40 | // Initialize sensors. 41 | board.Power.Configure() 42 | board.Sensors.Configure(drivers.Acceleration | drivers.Temperature) 43 | 44 | for { 45 | // Read keyboard inputs. 46 | board.Buttons.ReadInput() 47 | for { 48 | // Read keyboard events. 49 | event := board.Buttons.NextEvent() 50 | if event == board.NoKeyEvent { 51 | break 52 | } 53 | if event.Pressed() { 54 | switch event.Key() { 55 | case board.KeyB, board.KeyEscape: 56 | return 57 | } 58 | } 59 | } 60 | 61 | // Read battery status. 62 | state, microvolts, percent := board.Power.Status() 63 | batteryText := "battery: " 64 | if percent >= 0 { 65 | batteryText += strconv.Itoa(int(percent)) + "% " 66 | } 67 | if state != board.Discharging { 68 | batteryText += state.String() 69 | } 70 | battery.SetText(batteryText) 71 | voltage.SetText("voltage: " + formatVoltage(microvolts)) 72 | 73 | // Read sensors. 74 | board.Sensors.Update(drivers.Temperature | drivers.Acceleration) 75 | celsius := (board.Sensors.Temperature() + 500) / 1000 76 | temperature.SetText("temp: " + strconv.Itoa(int(celsius)) + "C") 77 | ax, ay, az := board.Sensors.Acceleration() 78 | acceleration.SetText("accel: " + strconv.FormatInt(int64(ax/10000), 10) + " " + strconv.FormatInt(int64(ay/10000), 10) + " " + strconv.FormatInt(int64(az/10000), 10)) 79 | steps.SetText("steps: " + strconv.Itoa(int(board.Sensors.Steps()))) 80 | 81 | screen.Update() 82 | time.Sleep(time.Second / 5) 83 | } 84 | } 85 | 86 | func formatVoltage(microvolts uint32) string { 87 | volts := strconv.Itoa(int(microvolts / 1000_000)) 88 | decimals := strconv.Itoa(int(microvolts % 1000_000 / 10_000)) 89 | for len(decimals) < 2 { 90 | decimals = "0" + decimals 91 | } 92 | return volts + "." + decimals + "V" 93 | } 94 | -------------------------------------------------------------------------------- /badge/test-colors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aykevl/board" 7 | "github.com/aykevl/tinygl" 8 | "github.com/aykevl/tinygl/gfx" 9 | "tinygo.org/x/drivers/pixel" 10 | ) 11 | 12 | func testColors[T pixel.Color](screen *tinygl.Screen[T]) { 13 | black := pixel.NewColor[T](0, 0, 0) 14 | canvas := gfx.NewCustomCanvas(black, 0, 0, func(screen *tinygl.Screen[T], displayX, displayY, displayWidth, displayHeight, x, y int) { 15 | // Draw the test colors. 16 | img := screen.Buffer().Rescale(int(displayWidth), 1) 17 | for y := 0; y < int(displayHeight); y++ { 18 | for x := 0; x < int(displayWidth); x++ { 19 | gray := uint8(x * 255 / int(displayWidth)) 20 | var c T 21 | switch y * 14 / int(displayHeight) { 22 | case 0, 1: 23 | c = pixel.NewColor[T](gray, 0, 0) // red 24 | case 3, 4: 25 | c = pixel.NewColor[T](0, gray, 0) // green 26 | case 6, 7: 27 | c = pixel.NewColor[T](0, 0, gray) // blue 28 | case 9, 10: 29 | c = pixel.NewColor[T](gray, gray, gray) 30 | case 12, 13: 31 | r := gamma_lut[uint8(255-gray)] 32 | g := gamma_lut[uint8(gray)] 33 | c = pixel.NewColor[T](r, g, 0) 34 | } 35 | img.Set(x, 0, c) 36 | } 37 | screen.Send(x, y, img) 38 | } 39 | }) 40 | 41 | // Update the screen with the color test bands. 42 | screen.SetChild(canvas) 43 | screen.Update() 44 | 45 | // Wait for back button. 46 | for { 47 | board.Buttons.ReadInput() 48 | for { 49 | // Read keyboard events. 50 | event := board.Buttons.NextEvent() 51 | if event == board.NoKeyEvent { 52 | break 53 | } 54 | if event.Pressed() { 55 | switch event.Key() { 56 | case board.KeyB, board.KeyEscape: 57 | return 58 | } 59 | } 60 | } 61 | 62 | // Delay a bit, to not burn CPU power. 63 | // TODO: somehow respond to button presses etc immediately. 64 | time.Sleep(time.Second / 30) 65 | } 66 | } 67 | 68 | // Gamma brightness lookup table 69 | // gamma = 0.45 steps = 256 range = 0-255 70 | var gamma_lut = [256]uint8{ 71 | 0, 21, 29, 35, 39, 43, 47, 51, 54, 57, 59, 62, 64, 67, 69, 71, 72 | 73, 75, 77, 79, 81, 83, 85, 86, 88, 90, 91, 93, 94, 96, 97, 99, 73 | 100, 102, 103, 104, 106, 107, 108, 110, 111, 112, 113, 114, 116, 117, 118, 119, 74 | 120, 121, 122, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 75 | 137, 138, 139, 140, 141, 142, 143, 143, 144, 145, 146, 147, 148, 149, 150, 150, 76 | 151, 152, 153, 154, 155, 156, 156, 157, 158, 159, 160, 160, 161, 162, 163, 164, 77 | 164, 165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 173, 173, 174, 175, 175, 78 | 176, 177, 178, 178, 179, 180, 180, 181, 182, 182, 183, 184, 184, 185, 186, 186, 79 | 187, 188, 188, 189, 190, 190, 191, 192, 192, 193, 193, 194, 195, 195, 196, 197, 80 | 197, 198, 198, 199, 200, 200, 201, 201, 202, 203, 203, 204, 204, 205, 206, 206, 81 | 207, 207, 208, 208, 209, 210, 210, 211, 211, 212, 212, 213, 214, 214, 215, 215, 82 | 216, 216, 217, 217, 218, 219, 219, 220, 220, 221, 221, 222, 222, 223, 223, 224, 83 | 224, 225, 225, 226, 227, 227, 228, 228, 229, 229, 230, 230, 231, 231, 232, 232, 84 | 233, 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, 239, 239, 240, 240, 85 | 241, 241, 242, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 247, 247, 248, 86 | 248, 249, 249, 250, 250, 250, 251, 251, 252, 252, 253, 253, 254, 254, 255, 255, 87 | } 88 | -------------------------------------------------------------------------------- /badge/test-tearing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aykevl/board" 7 | "github.com/aykevl/tinygl" 8 | "github.com/aykevl/tinygl/gfx" 9 | "tinygo.org/x/drivers/pixel" 10 | ) 11 | 12 | func testTearing[T pixel.Color](screen *tinygl.Screen[T], touchInput board.TouchInput) { 13 | var ( 14 | white = pixel.NewColor[T](255, 255, 255) 15 | black = pixel.NewColor[T](0, 0, 0) 16 | green = pixel.NewColor[T](0, 255, 0) 17 | red = pixel.NewColor[T](255, 0, 0) 18 | ) 19 | const ( 20 | size = 16 21 | speed = 4 22 | ) 23 | 24 | // Create the canvas. 25 | width, height := screen.Size() 26 | canvas := gfx.NewCanvas(black, 32, 32) 27 | verticalRect := gfx.NewRect(white, 0, 0, size, height) 28 | horizontalRect := gfx.NewRect(white, 0, 0, width, size) 29 | teIndicator := gfx.NewRect(red, 0, 0, 8, 8) 30 | canvas.Add(verticalRect) 31 | canvas.Add(horizontalRect) 32 | canvas.Add(teIndicator) 33 | screen.SetChild(canvas) 34 | 35 | // Change the mode on each touch on the screen. 36 | mode := 0 37 | canvas.SetEventHandler(func(event tinygl.Event, x, y int) { 38 | if event == tinygl.TouchStart { 39 | mode = (mode + 1) % 4 40 | } 41 | }) 42 | 43 | lastRenderStart := time.Now() 44 | for cycle := 0; ; cycle++ { 45 | // Read keyboard inputs. 46 | board.Buttons.ReadInput() 47 | for { 48 | // Read keyboard events. 49 | event := board.Buttons.NextEvent() 50 | if event == board.NoKeyEvent { 51 | break 52 | } 53 | if event.Pressed() { 54 | switch event.Key() { 55 | case board.KeyA, board.KeyEnter: 56 | mode = (mode + 1) % 4 57 | case board.KeyB, board.KeyEscape: 58 | return 59 | } 60 | } 61 | } 62 | 63 | // Handle touch inputs. 64 | touches := touchInput.ReadTouch() 65 | if len(touches) > 0 { 66 | screen.SetTouchState(touches[0].X, touches[0].Y) 67 | } else { 68 | screen.SetTouchState(-1, -1) 69 | } 70 | 71 | moveHorizontal := mode/2 == 1 72 | avoidTearing := mode%2 == 1 73 | 74 | // Update dot in the top left corner that indicates whether tearing is 75 | // avoided or not. 76 | horizontalRect.SetHidden(moveHorizontal) 77 | verticalRect.SetHidden(!moveHorizontal) 78 | if avoidTearing { 79 | teIndicator.SetColor(green) 80 | } else { 81 | teIndicator.SetColor(red) 82 | } 83 | 84 | // Update vertical/horizontal moving bars. 85 | pixels := int(lastRenderStart.UnixNano() / (int64(1e9) / (speed * 60))) 86 | if moveHorizontal { 87 | maxOffset := (width*2 - size*2) 88 | offset := pixels % maxOffset 89 | if offset >= maxOffset/2 { 90 | offset = maxOffset - offset 91 | } 92 | verticalRect.Move(offset, 0) 93 | } else { 94 | maxOffset := (height*2 - size*2) 95 | offset := pixels % maxOffset 96 | if offset >= maxOffset/2 { 97 | offset = maxOffset - offset 98 | } 99 | horizontalRect.Move(0, offset) 100 | } 101 | 102 | // Render next frame. 103 | if avoidTearing { 104 | board.Display.WaitForVBlank(time.Second / 60) 105 | } else { 106 | duration := lastRenderStart.Add(time.Second / 60).Sub(time.Now()) 107 | time.Sleep(duration) 108 | } 109 | renderStart := time.Now() 110 | screen.Update() 111 | renderDuration := time.Since(renderStart) 112 | if renderDuration > time.Millisecond*7 { 113 | println("rendering took:", renderDuration.String()) 114 | } 115 | lastRenderStart = renderStart 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /badge/test-touch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aykevl/board" 7 | "github.com/aykevl/tinygl" 8 | "github.com/aykevl/tinygl/gfx" 9 | "tinygo.org/x/drivers/pixel" 10 | ) 11 | 12 | func testTouch[T pixel.Color](screen *tinygl.Screen[T], touchInput board.TouchInput) { 13 | // Determine size and scale of the screen. 14 | scalePercent := board.Display.PPI() * 100 / 120 15 | 16 | // Create canvas. 17 | black := pixel.NewColor[T](0, 0, 0) 18 | canvas := gfx.NewCanvas(black, 8, 8) 19 | screen.SetChild(canvas) 20 | 21 | // Create touch point. 22 | touch := gfx.NewRect(pixel.NewColor[T](255, 255, 255), 0, 0, scalePercent/4, scalePercent/4) 23 | canvas.Add(touch) 24 | touch.SetHidden(true) 25 | 26 | // Show screen. 27 | screen.Update() 28 | 29 | for { 30 | // Read keyboard inputs. 31 | board.Buttons.ReadInput() 32 | for { 33 | // Read keyboard events. 34 | event := board.Buttons.NextEvent() 35 | if event == board.NoKeyEvent { 36 | break 37 | } 38 | if event.Pressed() { 39 | switch event.Key() { 40 | case board.KeyB, board.KeyEscape: 41 | return 42 | } 43 | } 44 | } 45 | 46 | // Read touch inputs. 47 | // TODO: be able to show multiple touch points (multitouch isn't 48 | // implemented yet). 49 | touches := touchInput.ReadTouch() 50 | if len(touches) != 0 { 51 | _, _, width, height := touch.Bounds() 52 | touch.Move(int(touches[0].X)-width/2, int(touches[0].Y)-height/2) 53 | touch.SetHidden(false) 54 | } else { 55 | touch.SetHidden(true) 56 | } 57 | screen.Update() 58 | 59 | time.Sleep(time.Second / 30) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cloud/control.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import time 4 | 5 | import paho.mqtt.client as mqtt 6 | import serial.tools.list_ports 7 | 8 | CLOUD_SERIALS = [ 9 | '2E8A:000A', # pico 10 | ] 11 | 12 | EFFECTS = [ 13 | 'white', 14 | 'party', 15 | 'forest', 16 | 'ocean', 17 | 'lightning', 18 | ] 19 | 20 | def get_port(): 21 | for port in serial.tools.list_ports.comports(): 22 | if port.vid is None or port.pid is None: 23 | continue 24 | if '%04X:%04X' % (port.vid, port.pid) in CLOUD_SERIALS: 25 | return port.device 26 | return None # no port found 27 | 28 | class Cloud: 29 | def __init__(self, serial, client): 30 | self.serial = serial 31 | self.client = client 32 | self.on = False 33 | self.effect = 'party' 34 | self.brightness = 8 35 | 36 | def set_state(self, on): 37 | if on == 'ON': 38 | on = True 39 | elif on == 'OFF': 40 | on = False 41 | elif type(on) != bool: 42 | print('Unknown state:', on) 43 | return 44 | self.on = on 45 | self.update_cloud() 46 | self.client.publish('cloud/light/state', {True: 'ON', False: 'OFF'}[on], retain=True) 47 | 48 | def set_effect(self, effect): 49 | if effect not in EFFECTS: 50 | print('Unknown effect:', effect) 51 | return 52 | self.effect = effect 53 | self.update_cloud() 54 | self.client.publish('cloud/light/effect', effect, retain=True) 55 | 56 | def set_brightness(self, brightness): 57 | if brightness < 1 or brightness > 10: 58 | print('Brightness out of range:', brightness) 59 | return 60 | self.brightness = brightness 61 | self.serial.write(b'b%d' % (self.brightness - 1)) 62 | self.client.publish('cloud/light/brightness', str(brightness), retain=True) 63 | 64 | def update_cloud(self): 65 | if not self.on: 66 | self.serial.write(b'D') 67 | elif self.effect == 'white': 68 | self.serial.write(b'W') 69 | elif self.effect == 'party': 70 | self.serial.write(b'P') 71 | elif self.effect == 'forest': 72 | self.serial.write(b'F') 73 | elif self.effect == 'ocean': 74 | self.serial.write(b'O') 75 | elif self.effect == 'lightning': 76 | self.serial.write(b'L') 77 | else: 78 | print('Unknown state/effect:', self.on, self.effect) 79 | 80 | def on_connect(self): 81 | print('Connected to MQTT broker.') 82 | self.client.subscribe('cloud/light/switch') 83 | self.client.subscribe('cloud/light/set_effect') 84 | self.client.subscribe('cloud/light/set_brightness') 85 | self.set_state(self.on) 86 | self.set_effect(self.effect) 87 | self.set_brightness(self.brightness) 88 | self.client.publish('cloud/light/online', 'online', retain=True) 89 | 90 | def on_message(self, userdata, msg): 91 | if msg.topic == 'cloud/light/switch': 92 | self.set_state(msg.payload.decode()) 93 | return 94 | if msg.topic == 'cloud/light/set_effect': 95 | self.set_effect(msg.payload.decode()) 96 | return 97 | if msg.topic == 'cloud/light/set_brightness': 98 | self.set_brightness(int(msg.payload.decode())) 99 | return 100 | print('Unknown message:', msg.topic, msg.payload) 101 | 102 | 103 | def main(): 104 | while True: 105 | try: 106 | # Open serial port. 107 | port = get_port() 108 | if not port: 109 | print('Looking for port...') 110 | while not port: 111 | time.sleep(5) 112 | port = get_port() 113 | print('Opening port:', port) 114 | ser = serial.Serial(port) 115 | 116 | # Connect to MQTT broker. 117 | client = mqtt.Client() 118 | cloud = Cloud(ser, client) 119 | client.on_connect = lambda client, userdata, flags, rc: cloud.on_connect() 120 | client.on_message = lambda client, userdata, msg: cloud.on_message(userdata, msg) 121 | client.will_set('cloud/light/online', 'offline', retain=True) 122 | client.connect('localhost') 123 | client.loop_forever() 124 | except serial.serialutil.SerialException: 125 | print('Lost serial connection.') 126 | client.publish('cloud/light/online', 'offline', retain=True) 127 | client.disconnect() 128 | client.loop_stop() 129 | 130 | if __name__ == '__main__': 131 | main() 132 | -------------------------------------------------------------------------------- /cloud/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aykevl/things/cloud 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 7 | tinygo.org/x/drivers v0.26.1-0.20231117195053-21637eaead8c 8 | ) 9 | -------------------------------------------------------------------------------- /cloud/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da h1:u4fP3+V+W/547R/P7ezR5gH2zucphUZfQLc1ypNaXOg= 2 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da/go.mod h1:Bn4qAbkSY0k0/wrXdqQ8bWPTv/Uww3uIJuQv/knzDdQ= 3 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 h1:NcmN0bjs7hmLKSARz88oxXF6OU50/d4Xeo5QWEE5y7w= 4 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2/go.mod h1:GDZBEY7gPGjvz/G3zLFirDcgbaN3nF4nvJ157+vnEro= 5 | github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE= 6 | github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= 7 | github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk= 8 | github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= 9 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 10 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 11 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 12 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 13 | github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as= 14 | github.com/kettek/apng v0.0.0-20191108220231-414630eed80f/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q= 15 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 16 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 17 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 18 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 19 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 20 | github.com/sago35/go-bdf v0.0.0-20200313142241-6c17821c91c4/go.mod h1:rOebXGuMLsXhZAC6mF/TjxONsm45498ZyzVhel++6KM= 21 | github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= 22 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 23 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 24 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 25 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 26 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 27 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 28 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 29 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 30 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 31 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 32 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 33 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 35 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 36 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 37 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 39 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 42 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 43 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 44 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 45 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 46 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 47 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 48 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 49 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 50 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 51 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 52 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 53 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 54 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 55 | tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 56 | tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 57 | tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 58 | tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI= 59 | tinygo.org/x/drivers v0.26.1-0.20231117195053-21637eaead8c h1:P0NHE8ZtL4l/18xO8Kk5//5O+PXoqacx8qfOQHoPXxg= 60 | tinygo.org/x/drivers v0.26.1-0.20231117195053-21637eaead8c/go.mod h1:X7utcg3yfFUFuKLOMTZD56eztXMjpkcf8OHldfTBsjw= 61 | tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= 62 | tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk= 63 | tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20= 64 | tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4= 65 | -------------------------------------------------------------------------------- /cloud/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "machine" 6 | "time" 7 | 8 | "github.com/aykevl/ledsgo" 9 | "tinygo.org/x/drivers/scd4x" 10 | "tinygo.org/x/drivers/sgp30" 11 | "tinygo.org/x/drivers/ws2812" 12 | ) 13 | 14 | const NUM_LEDS = 50 15 | 16 | var leds = make([]color.RGBA, NUM_LEDS) 17 | 18 | const animationSpeed = 2 // higher means faster 19 | var brightness uint8 = 127 20 | var palette = ledsgo.PartyColors 21 | 22 | const measurementInterval = time.Second * 5 23 | 24 | var ( 25 | co2Sensor *scd4x.Device 26 | vocSensor *sgp30.Device 27 | ) 28 | 29 | type ledPosition struct { 30 | X uint8 31 | Y uint8 32 | } 33 | 34 | var brightnessMap = [10]uint8{2, 7, 17, 32, 52, 77, 108, 146, 189, 238} 35 | 36 | var positions = [...]ledPosition{ 37 | {43, 16}, {44, 16}, {44, 19}, {37, 19}, {39, 9}, {44, 19}, {50, 13}, {41, 12}, {39, 9}, // ball 1 38 | {57, 20}, {66, 21}, {75, 19}, {64, 15}, {63, 24}, // ball 2 39 | {65, 28}, {75, 30}, {70, 33}, {80, 40}, {78, 36}, {75, 39}, // ball 3 40 | {70, 41}, {70, 39}, {62, 37}, {52, 43}, {62, 52}, {66, 40}, {61, 36}, {52, 40}, // ball 4 41 | {43, 39}, {38, 37}, {45, 36}, {42, 34}, {34, 40}, {42, 44}, {46, 39}, {40, 36}, // ball 5 42 | {29, 36}, {26, 43}, {29, 36}, // ball 6 43 | {21, 35}, {15, 37}, {20, 39}, // ball 7 44 | {29, 29}, {33, 20}, {28, 12}, {23, 20}, {20, 19}, {22, 21}, {28, 27}, {33, 25}, // ball 8 45 | } 46 | 47 | func main() { 48 | led := machine.LED 49 | led.Configure(machine.PinConfig{Mode: machine.PinOutput}) 50 | 51 | LED_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput}) 52 | strip := ws2812.New(LED_PIN) 53 | 54 | time.Sleep(time.Second * 1) 55 | println("start") 56 | go runSensors() 57 | 58 | var command byte 59 | animation := noise 60 | for { 61 | if command == 0 { 62 | // Read command. 63 | if machine.Serial.Buffered() != 0 { 64 | command, _ = machine.Serial.ReadByte() 65 | } 66 | } 67 | if command != 0 { 68 | switch command { 69 | case 'D': // disable 70 | animation = poweroff 71 | command = 0 72 | case 'W': // white 73 | animation = white 74 | command = 0 75 | case 'P': 76 | animation = noise 77 | palette = ledsgo.PartyColors 78 | command = 0 79 | case 'F': 80 | animation = noise 81 | palette = ledsgo.ForestColors 82 | command = 0 83 | case 'O': 84 | animation = noise 85 | palette = ledsgo.OceanColors 86 | command = 0 87 | case 'L': // lightning 88 | animation = lightning 89 | command = 0 90 | case 'b': // brightness 91 | if machine.Serial.Buffered() != 0 { 92 | b, _ := machine.Serial.ReadByte() 93 | if b >= '0' && b <= '9' { 94 | brightness = brightnessMap[b-'0'] 95 | } 96 | command = 0 97 | } 98 | } 99 | } 100 | 101 | // Update colors. 102 | var t uint64 103 | if animationSpeed != 0 { 104 | t = uint64(time.Now().UnixNano() >> (26 - animationSpeed)) 105 | } 106 | animation(t, leds) 107 | 108 | // Send new colors to LEDs. 109 | for _, c := range leds { 110 | strip.WriteByte(c.G) // G 111 | strip.WriteByte(c.R) // R 112 | strip.WriteByte(c.B) // B 113 | strip.WriteByte(c.A) // W (alpha channel, used as white channel) 114 | } 115 | time.Sleep(1 * time.Millisecond) 116 | } 117 | } 118 | 119 | func noise(t uint64, leds []color.RGBA) { 120 | for i := range leds { 121 | pos := positions[i] 122 | const spread = 24 // higher means more detail 123 | val := ledsgo.Noise3(uint32(t), uint32(pos.X)*spread, uint32(pos.Y)*spread) 124 | c := palette.ColorAt(val) 125 | c.A = 0 // the alpha channel is used as white channel, so don't use it 126 | leds[i] = ledsgo.ApplyAlpha(c, brightness) 127 | } 128 | } 129 | 130 | func lightning(t uint64, leds []color.RGBA) { 131 | const interval = 1 << 8 132 | elapsed := interval - t%interval 133 | for i := range leds[:10] { 134 | leds[i] = color.RGBA{0, 0, 0, uint8(elapsed / (interval / 100))} 135 | } 136 | } 137 | 138 | func white(t uint64, leds []color.RGBA) { 139 | for i := range leds { 140 | leds[i] = color.RGBA{A: brightness} 141 | } 142 | } 143 | 144 | func poweroff(t uint64, leds []color.RGBA) { 145 | for i := range leds { 146 | leds[i] = color.RGBA{} 147 | } 148 | } 149 | 150 | func xorshift64(x uint64) uint64 { 151 | // https://en.wikipedia.org/wiki/Xorshift 152 | x ^= x << 13 153 | x ^= x >> 7 154 | x ^= x << 17 155 | return x 156 | } 157 | 158 | func runSensors() { 159 | configureSensors() 160 | 161 | for n := uint64(0); ; n++ { 162 | // Sample CO₂ every 10 seconds. 163 | if n%10 == 0 && co2Sensor != nil { 164 | sampleCO2Sensor(co2Sensor) 165 | } 166 | // Sample VOC every second. 167 | if vocSensor != nil { 168 | sampleVOCSensor(vocSensor) 169 | } 170 | time.Sleep(time.Second) 171 | } 172 | } 173 | 174 | func configureSensors() { 175 | bus := machine.I2C1 176 | err := bus.Configure(machine.I2CConfig{ 177 | SDA: machine.GP26, 178 | SCL: machine.GP27, 179 | Frequency: 400 * machine.KHz, 180 | }) 181 | if err != nil { 182 | println("could not configure I2C:", bus) 183 | return 184 | } 185 | { 186 | // Configure SCD40 CO₂ sensor. 187 | sensor := scd4x.New(bus) 188 | if !sensor.Connected() { 189 | println("CO2 sensor is not connected!") 190 | return 191 | } 192 | if err := sensor.Configure(); err != nil { 193 | println("could not configure CO2 sensor:", err.Error()) 194 | return 195 | } 196 | println("configured!") 197 | 198 | if err := sensor.StartPeriodicMeasurement(); err != nil { 199 | println("could not start peridic measurement:", err) 200 | return 201 | } 202 | co2Sensor = sensor 203 | } 204 | { 205 | sensor := sgp30.New(bus) 206 | if !sensor.Connected() { 207 | println("VOC sensor not connected") 208 | return 209 | } 210 | err := sensor.Configure(sgp30.Config{}) 211 | if err != nil { 212 | println("VOC sensor could not be configured:", err.Error()) 213 | return 214 | } 215 | vocSensor = sensor 216 | } 217 | } 218 | 219 | func sampleCO2Sensor(sensor *scd4x.Device) { 220 | co2, err := sensor.ReadCO2() 221 | if err != nil { 222 | println("failed to read CO2:", err.Error()) 223 | return 224 | } 225 | 226 | temperature, err := sensor.ReadTemperature() 227 | if err != nil { 228 | println("failed to read temperature:", err.Error()) 229 | return 230 | } 231 | 232 | humidity, err := sensor.ReadHumidity() 233 | if err != nil { 234 | println("failed to read humidity:", err.Error()) 235 | return 236 | } 237 | println("CO2: ", co2) 238 | println("temperature:", temperature) 239 | println("humidity: ", humidity) 240 | } 241 | 242 | func sampleVOCSensor(sensor *sgp30.Device) { 243 | err := sensor.Update(0) 244 | if err != nil { 245 | println("could not read VOC sensor:", err.Error()) 246 | return 247 | } 248 | println("CO2eq: ", sensor.CO2()) 249 | println("TVOC ", sensor.TVOC()) 250 | } 251 | -------------------------------------------------------------------------------- /cloud/pins_arduino.go: -------------------------------------------------------------------------------- 1 | //go:build arduino 2 | // +build arduino 3 | 4 | package main 5 | 6 | import "machine" 7 | 8 | const ( 9 | LED_PIN = machine.D2 10 | ) 11 | -------------------------------------------------------------------------------- /cloud/pins_esp32.go: -------------------------------------------------------------------------------- 1 | //go:build esp32 2 | // +build esp32 3 | 4 | package main 5 | 6 | const ( 7 | LED_PIN = 13 8 | ) 9 | -------------------------------------------------------------------------------- /cloud/pins_feather-stm32f405.go: -------------------------------------------------------------------------------- 1 | //go:build feather_stm32f405 2 | // +build feather_stm32f405 3 | 4 | package main 5 | 6 | import "machine" 7 | 8 | const ( 9 | LED_PIN = machine.A0 10 | ) 11 | -------------------------------------------------------------------------------- /cloud/pins_pico.go: -------------------------------------------------------------------------------- 1 | //go:build pico 2 | // +build pico 3 | 4 | package main 5 | 6 | import "machine" 7 | 8 | const ( 9 | LED_PIN = machine.GPIO29 10 | ) 11 | -------------------------------------------------------------------------------- /cloud/pins_pybadge.go: -------------------------------------------------------------------------------- 1 | //go:build pybadge || arduino_nano33 2 | // +build pybadge arduino_nano33 3 | 4 | package main 5 | 6 | import "machine" 7 | 8 | const ( 9 | LED_PIN = machine.A0 10 | ) 11 | -------------------------------------------------------------------------------- /dress/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aykevl/things/dress 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/aykevl/ledsgo v0.0.0-20230808203851-4c9b90563294 7 | tinygo.org/x/drivers v0.23.0 8 | ) 9 | -------------------------------------------------------------------------------- /dress/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da h1:u4fP3+V+W/547R/P7ezR5gH2zucphUZfQLc1ypNaXOg= 2 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da/go.mod h1:Bn4qAbkSY0k0/wrXdqQ8bWPTv/Uww3uIJuQv/knzDdQ= 3 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 h1:NcmN0bjs7hmLKSARz88oxXF6OU50/d4Xeo5QWEE5y7w= 4 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2/go.mod h1:GDZBEY7gPGjvz/G3zLFirDcgbaN3nF4nvJ157+vnEro= 5 | github.com/aykevl/ledsgo v0.0.0-20230808203851-4c9b90563294 h1:vQgq4d9Epm9FGk1DT9GBwRpRQ2QXgPcY3hGqN+psGRk= 6 | github.com/aykevl/ledsgo v0.0.0-20230808203851-4c9b90563294/go.mod h1:GDZBEY7gPGjvz/G3zLFirDcgbaN3nF4nvJ157+vnEro= 7 | github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE= 8 | github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= 9 | github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= 10 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 11 | github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as= 12 | github.com/kettek/apng v0.0.0-20191108220231-414630eed80f/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q= 13 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 14 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 16 | github.com/sago35/go-bdf v0.0.0-20200313142241-6c17821c91c4/go.mod h1:rOebXGuMLsXhZAC6mF/TjxONsm45498ZyzVhel++6KM= 17 | github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= 18 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 19 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 20 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 22 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 23 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 24 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 25 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 26 | tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 27 | tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 28 | tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 29 | tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI= 30 | tinygo.org/x/drivers v0.23.0 h1:fUy4OmLOWWYCOzDp/83Qewej1Q+YgUpwkm11e7gxUc0= 31 | tinygo.org/x/drivers v0.23.0/go.mod h1:J4+51Li1kcfL5F93kmnDWEEzQF3bLGz0Am3Q7E2a8/E= 32 | tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= 33 | tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk= 34 | tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20= 35 | tinygo.org/x/tinyfs v0.2.0/go.mod h1:6ZHYdvB3sFYeMB3ypmXZCNEnFwceKc61ADYTYHpep1E= 36 | tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4= 37 | -------------------------------------------------------------------------------- /dress/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "machine" 6 | "time" 7 | 8 | "github.com/aykevl/ledsgo" 9 | "tinygo.org/x/drivers/ws2812" 10 | ) 11 | 12 | const NumLEDs = 50 13 | 14 | const ( 15 | //ledPin = machine.ADC5 // arduino 16 | ledPin = machine.GPIO29 17 | ) 18 | 19 | var leds = make([]color.RGBA, NumLEDs) 20 | 21 | var brightness uint8 = 32 22 | 23 | // var palette = ledsgo.PartyColors 24 | var palette = PurpleShades 25 | 26 | var PurpleShades = ledsgo.Palette16{ 27 | color.RGBA{0xFF, 0x00, 0x00, 0xFF}, 28 | color.RGBA{0xFF, 0x00, 0x00, 0xFF}, 29 | color.RGBA{0xEE, 0x00, 0x11, 0xFF}, 30 | color.RGBA{0xDD, 0x00, 0x22, 0xFF}, 31 | color.RGBA{0xBB, 0x00, 0x44, 0xFF}, 32 | color.RGBA{0x88, 0x00, 0x66, 0xFF}, 33 | color.RGBA{0x77, 0x00, 0x77, 0xFF}, 34 | color.RGBA{0x77, 0x00, 0x66, 0xFF}, 35 | color.RGBA{0x66, 0x00, 0x66, 0xFF}, 36 | color.RGBA{0x66, 0x00, 0x77, 0xFF}, 37 | color.RGBA{0x55, 0x00, 0x88, 0xFF}, 38 | color.RGBA{0x55, 0x00, 0x88, 0xFF}, 39 | color.RGBA{0x44, 0x00, 0x99, 0xFF}, 40 | color.RGBA{0x33, 0x00, 0xAA, 0xFF}, 41 | color.RGBA{0x22, 0x00, 0xBB, 0xFF}, 42 | color.RGBA{0x11, 0x00, 0xEE, 0xFF}, 43 | } 44 | 45 | func main() { 46 | ledPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) 47 | strip := ws2812.New(ledPin) 48 | 49 | for { 50 | // Update colors. 51 | t := uint64(time.Now().UnixNano() >> 20) 52 | noise(t, leds) 53 | //showPalette(t, leds) 54 | 55 | // Send new colors to LEDs. 56 | strip.WriteColors(leds) 57 | time.Sleep(1 * time.Millisecond) 58 | } 59 | } 60 | 61 | func position(i int) (x, y int) { 62 | x = i / 10 * 2 63 | loopPos := i - (x * 5) 64 | y = loopPos 65 | if y >= 5 { 66 | y = 9 - y 67 | x += 1 68 | } 69 | return 70 | } 71 | 72 | func noise(t uint64, leds []color.RGBA) { 73 | for i := range leds { 74 | const spread = 7 // higher means more detail 75 | x, y := position(i) 76 | val := ledsgo.Noise3(uint32(x)< 2 | #include 3 | 4 | // Control the number of light level bits to use for the LEDs. 5 | // A higher number means a better resolution (noticeable at low light levels) 6 | // but also increases the amount of flickering. 7 | #define MAX_BRIGHTNESS 128 8 | 9 | // For each LED, the PORTB and DDRB registers. 10 | // DDRB is the lower 4 bits while PORTB is the higher 4 bits. 11 | static const uint8_t states[6] = { 12 | 0b00100110, // 5 13 | 0b00010101, // 3 14 | 0b00010011, // 1 15 | 0b01000110, // 6 16 | 0b01000101, // 4 17 | 0b00100011, // 2 18 | }; 19 | 20 | static const uint8_t sinewave[64] = { 21 | 0, 0, 2, 4, 7, 11, 17, 23, 31, 39, 49, 59, 70, 82, 94, 106, 119, 132, 145, 157, 170, 182, 193, 204, 214, 223, 231, 238, 244, 249, 252, 254, 255, 254, 252, 249, 244, 238, 231, 223, 214, 204, 193, 182, 170, 157, 145, 132, 119, 106, 94, 82, 70, 59, 49, 39, 31, 23, 17, 11, 7, 4, 2, 0 22 | }; 23 | 24 | // Define modes that can be cycled through. 25 | enum { 26 | mode_sparkling, // also the power on mode 27 | mode_wave, 28 | mode_off, 29 | num_modes, 30 | }; 31 | 32 | // Source: https://www.avrfreaks.net/forum/tiny-fast-prng 33 | __attribute__((section(".noinit"))) 34 | static uint8_t rnd_s, rnd_a; 35 | static uint8_t rnd(void) { 36 | rnd_s ^= rnd_s<<3; 37 | rnd_s ^= rnd_s>>5; 38 | rnd_s ^= rnd_a++>>2; 39 | return rnd_s; 40 | } 41 | 42 | __attribute__((section(".noinit"))) 43 | static uint8_t brightness[6]; 44 | 45 | __attribute__((section(".noinit"))) 46 | static uint8_t mode; 47 | 48 | int main(void) { 49 | // Reduce clock speed to 128kHz. 50 | //CCP = 0xD8; 51 | //CLKMSR = 0b01; 52 | //CCP = 0xD8; 53 | //CLKPSR = 0; 54 | 55 | // Reduce clock speed to 250kHz. 56 | CCP = 0xD8; // unlock protected registers 57 | CLKPSR = 0b0101; // division factor 32 (8 / 32 = 0.25) 58 | 59 | if ((RSTFLR & EXTRF) == 0) { 60 | // Not an external reset, so probably a power on reset. 61 | // Initialize global variables. 62 | mode = 0; 63 | rnd_s = 0xaa; 64 | rnd_a = 0; 65 | } else { 66 | // Reset pin triggered, so go to the next mode. 67 | mode++; 68 | if (mode >= num_modes) { 69 | mode = 0; 70 | } 71 | } 72 | 73 | if (mode == mode_off) { 74 | // Power down the chip entirely. It will only awake when the reset pin 75 | // is pressed. 76 | // This reduces power consumption to around 1µA, or as low as my 77 | // multimeter will measure. 78 | SMCR = 0b0101; // SM = 0b010 (power down), SE = 1 (sleep enabled) 79 | while (1) { 80 | sleep_cpu(); 81 | } 82 | } 83 | 84 | uint8_t cycle = 0; 85 | while (1) { 86 | cycle++; 87 | 88 | // Run next step in the animation. 89 | if (mode == mode_wave) { 90 | // Do a sine wave in a circle. 91 | for (int8_t i=5; i >= 0; i--) { 92 | uint8_t index = cycle - (uint8_t)(i * 42); 93 | brightness[i] = 0; 94 | if (index < 128) { 95 | brightness[i] = sinewave[index / 2]; 96 | } 97 | } 98 | } else { 99 | // Turn LEDs randomly on, and let them fade out. 100 | for (int8_t i=5; i >= 0; i--) { 101 | // Reduce brightness. Quicly at first, but slower at lower 102 | // brightnesses. 103 | // This is supposed to look like a power law, but I'm not sure it 104 | // does. 105 | uint8_t b = brightness[i]; 106 | if (b & 128) { 107 | b -= 2; 108 | } 109 | if (b & 64) { 110 | b -= 1; 111 | } 112 | if ((b & 32) && ((cycle % 2) == 0)) { 113 | b--; 114 | } 115 | if (b > (256 / MAX_BRIGHTNESS)) { 116 | if ((cycle % 4) == 0) { 117 | b--; 118 | } 119 | } 120 | brightness[i] = b; 121 | } 122 | if (cycle % 8 == 0) { 123 | uint8_t r = rnd(); 124 | if ((r & 0b11000000) == 0b11000000) { 125 | // Pick one LED at random and turn it on. 126 | uint8_t index = r % 6; 127 | if (brightness[index] < r) { 128 | brightness[index] = r; 129 | } 130 | } 131 | } 132 | } 133 | 134 | // Update LEDs using charlieplexing. 135 | for (uint8_t delay = 0; delay < (768 / MAX_BRIGHTNESS); delay++) { 136 | // Turn each LED on for just the right amount of time. 137 | for (uint8_t i=0; i<6; i++) { 138 | uint8_t state = states[i]; 139 | 140 | // Configure port to only turn a particular LED on. 141 | PORTB = state >> 4; 142 | 143 | // The number of clock cycles the output should be active for. 144 | uint8_t numCycles = brightness[i]; 145 | 146 | // Use custom assembly to turn the LEDs on for exactly the given number 147 | // of clock cycles, while keeping the code constant-time. 148 | // By lowering the resolution to 1 cycle, we can reduce the chip speed a 149 | // lot, which is a significant reduction in power consumption. 150 | __asm__ volatile( 151 | // 1 cycle: output bit 0 (least-significant bit) 152 | "lsr %[num]\n\t" 153 | "brcc 1f\n\t" 154 | "out %[port], %[state]\n\t" 155 | "1:\n\t" 156 | "out %[port], __zero_reg__\n\t" 157 | 158 | // 2 cycles: output bit 1 159 | "lsr %[num]\n\t" 160 | "brcc 1f\n\t" 161 | "out %[port], %[state]\n\t" 162 | "1:\n\t" 163 | "nop\n\t" 164 | "out %[port], __zero_reg__\n\t" 165 | 166 | // 4 cycles: output bit 2 167 | "lsr %[num]\n\t" 168 | "brcc 1f\n\t" 169 | "out %[port], %[state]\n\t" 170 | "1:\n\t" 171 | "nop\n\t" 172 | "nop\n\t" 173 | "nop\n\t" 174 | "out %[port], __zero_reg__\n\t" 175 | 176 | // 8 cycle loop: output bit 3-7 177 | "cpse %[num], __zero_reg__\n\t" // if num != 0: 178 | "out %[port], %[state]\n\t" // LEDs on 179 | "ldi __tmp_reg__, %[iterations]\n\t" // i = 32 180 | "1:\n\t" // loop: 181 | "nop\n\t" // wait 2 cycles 182 | "nop\n\t" // 183 | "subi %[num], 1\n\t" // num-- 184 | "brpl 2f\n\t" // if num == -1: 185 | "out %[port], __zero_reg__\n\t" // LEDs off 186 | "2:\n\t" // 187 | "subi __tmp_reg__, 1\n\t" // i-- 188 | "brne 1b\n\t" // if i == 0: goto loop 189 | : [num]"+r"(numCycles) 190 | : [port]"I"(_SFR_IO_ADDR(DDRB)), 191 | [state]"r"(state), 192 | [iterations]"I"(MAX_BRIGHTNESS / 8) 193 | ); 194 | } 195 | } 196 | } 197 | 198 | // unreachable 199 | } 200 | -------------------------------------------------------------------------------- /earring-leaf/schematic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-leaf/schematic.pdf -------------------------------------------------------------------------------- /earring-ring-rgb36/.gitignore: -------------------------------------------------------------------------------- 1 | earring/production 2 | earring/earring-backups 3 | earring/fp-info-cache 4 | -------------------------------------------------------------------------------- /earring-ring-rgb36/earring/Custom.pretty/MY-1220-03.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "MY-1220-03" 2 | (version 20240108) 3 | (generator "pcbnew") 4 | (generator_version "8.0") 5 | (layer "F.Cu") 6 | (property "Reference" "REF**" 7 | (at 0 -10 0) 8 | (unlocked yes) 9 | (layer "F.SilkS") 10 | (uuid "c3c3b544-305e-461b-bf98-c2ba8c14bfb7") 11 | (effects 12 | (font 13 | (size 1 1) 14 | (thickness 0.1) 15 | ) 16 | ) 17 | ) 18 | (property "Value" "MY-1220-03" 19 | (at 0 -8.5 0) 20 | (unlocked yes) 21 | (layer "F.Fab") 22 | (uuid "23edab16-3f82-4bea-a8e3-d8c711f71685") 23 | (effects 24 | (font 25 | (size 1 1) 26 | (thickness 0.15) 27 | ) 28 | ) 29 | ) 30 | (property "Footprint" "" 31 | (at 0 -9.5 0) 32 | (unlocked yes) 33 | (layer "F.Fab") 34 | (hide yes) 35 | (uuid "35e035bd-fb60-4b1a-8d6d-31db2f6d4dc9") 36 | (effects 37 | (font 38 | (size 1 1) 39 | (thickness 0.15) 40 | ) 41 | ) 42 | ) 43 | (property "Datasheet" "" 44 | (at 0 -9.5 0) 45 | (unlocked yes) 46 | (layer "F.Fab") 47 | (hide yes) 48 | (uuid "aaadf6c9-aecb-4871-bea8-8e37d28b2e03") 49 | (effects 50 | (font 51 | (size 1 1) 52 | (thickness 0.15) 53 | ) 54 | ) 55 | ) 56 | (property "Description" "" 57 | (at 0 -9.5 0) 58 | (unlocked yes) 59 | (layer "F.Fab") 60 | (hide yes) 61 | (uuid "af2e49cb-1a29-41c0-8034-c84a532dbfd4") 62 | (effects 63 | (font 64 | (size 1 1) 65 | (thickness 0.15) 66 | ) 67 | ) 68 | ) 69 | (attr smd) 70 | (fp_line 71 | (start -6.6 -5.1) 72 | (end -6.6 -2.3) 73 | (stroke 74 | (width 0.1) 75 | (type default) 76 | ) 77 | (layer "F.SilkS") 78 | (uuid "603fa197-2f42-4ee8-9861-d3e86628ebc6") 79 | ) 80 | (fp_line 81 | (start -6.6 -5.1) 82 | (end 6.6 -5.1) 83 | (stroke 84 | (width 0.1) 85 | (type default) 86 | ) 87 | (layer "F.SilkS") 88 | (uuid "00733716-9e9d-4163-96d7-e162bf616cd2") 89 | ) 90 | (fp_line 91 | (start -6.6 3.6) 92 | (end -6.6 2.3) 93 | (stroke 94 | (width 0.1) 95 | (type default) 96 | ) 97 | (layer "F.SilkS") 98 | (uuid "43b6ab9d-12b4-455f-9195-68f657d72e83") 99 | ) 100 | (fp_line 101 | (start -3.6 6.9) 102 | (end -6.6 3.6) 103 | (stroke 104 | (width 0.1) 105 | (type default) 106 | ) 107 | (layer "F.SilkS") 108 | (uuid "c66d10a0-fbe3-4b4f-b5ed-88ee761ce2ce") 109 | ) 110 | (fp_line 111 | (start -3.6 6.9) 112 | (end 3.6 6.9) 113 | (stroke 114 | (width 0.1) 115 | (type default) 116 | ) 117 | (layer "F.SilkS") 118 | (uuid "c6eb0c1a-afbc-4b0b-ab46-0d89067e7381") 119 | ) 120 | (fp_line 121 | (start 3.6 6.9) 122 | (end 6.6 3.6) 123 | (stroke 124 | (width 0.1) 125 | (type default) 126 | ) 127 | (layer "F.SilkS") 128 | (uuid "0ad5622a-dadb-42ec-b552-2b8dbf797f0f") 129 | ) 130 | (fp_line 131 | (start 6.6 -5.1) 132 | (end 6.6 -2.3) 133 | (stroke 134 | (width 0.1) 135 | (type default) 136 | ) 137 | (layer "F.SilkS") 138 | (uuid "4280776c-814c-4806-8c74-c25c83c0c1b7") 139 | ) 140 | (fp_line 141 | (start 6.6 3.6) 142 | (end 6.6 2.3) 143 | (stroke 144 | (width 0.1) 145 | (type default) 146 | ) 147 | (layer "F.SilkS") 148 | (uuid "641f89e1-2a1e-42cc-bcc6-38ea9d095dae") 149 | ) 150 | (fp_poly 151 | (pts 152 | (xy 6.8 -5.3) (xy 6.8 -2.3) (xy 9.7 -2.3) (xy 9.7 2.3) (xy 6.8 2.3) (xy 6.8 3.7) (xy 3.6 7.2) (xy -3.6 7.2) 153 | (xy -6.8 3.7) (xy -6.8 2.3) (xy -9.7 2.3) (xy -9.7 -2.3) (xy -6.8 -2.3) (xy -6.8 -5.3) 154 | ) 155 | (stroke 156 | (width 0.05) 157 | (type solid) 158 | ) 159 | (fill none) 160 | (layer "F.CrtYd") 161 | (uuid "69e46665-7e45-41bd-9230-637383f910af") 162 | ) 163 | (fp_text user "${REFERENCE}" 164 | (at 0 -7 0) 165 | (unlocked yes) 166 | (layer "F.Fab") 167 | (uuid "8e1ea3f3-d78c-4dc0-9c0c-5bea35bc9363") 168 | (effects 169 | (font 170 | (size 1 1) 171 | (thickness 0.15) 172 | ) 173 | ) 174 | ) 175 | (pad "1" smd rect 176 | (at -7.55 0) 177 | (size 3.9 4.18) 178 | (layers "F.Cu" "F.Paste" "F.Mask") 179 | (thermal_bridge_angle 45) 180 | (uuid "56d562cb-1a50-4a64-be60-149f3e14d385") 181 | ) 182 | (pad "1" smd rect 183 | (at 7.55 0) 184 | (size 3.9 4.18) 185 | (layers "F.Cu" "F.Paste" "F.Mask") 186 | (thermal_bridge_angle 45) 187 | (uuid "88c18f5c-9f80-4468-8f9f-ee196948a786") 188 | ) 189 | (pad "2" smd circle 190 | (at 0 0) 191 | (size 7 7) 192 | (layers "F.Cu" "F.Paste" "F.Mask") 193 | (uuid "a53ce961-e05f-4beb-a075-70aaed44c0b5") 194 | ) 195 | ) 196 | -------------------------------------------------------------------------------- /earring-ring-rgb36/earring/Custom.pretty/NH-B1212RGBA-HF.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "NH-B1212RGBA-HF" 2 | (version 20240108) 3 | (generator "pcbnew") 4 | (generator_version "8.0") 5 | (layer "F.Cu") 6 | (property "Reference" "REF**" 7 | (at 0 -4.6 0) 8 | (unlocked yes) 9 | (layer "F.SilkS") 10 | (uuid "dfc297e8-6ab0-4b06-9edb-82dc1fabbb57") 11 | (effects 12 | (font 13 | (size 1 1) 14 | (thickness 0.1) 15 | ) 16 | ) 17 | ) 18 | (property "Value" "NH-B1212RGBA-HF" 19 | (at 0 -3.1 0) 20 | (unlocked yes) 21 | (layer "F.Fab") 22 | (uuid "56d8e704-36b6-48e0-b9ad-ad84a3c0fa5c") 23 | (effects 24 | (font 25 | (size 1 1) 26 | (thickness 0.15) 27 | ) 28 | ) 29 | ) 30 | (property "Footprint" "" 31 | (at 0 -4.1 0) 32 | (unlocked yes) 33 | (layer "F.Fab") 34 | (hide yes) 35 | (uuid "56c4ff6c-a341-48e8-9583-3de25d5be260") 36 | (effects 37 | (font 38 | (size 1 1) 39 | (thickness 0.15) 40 | ) 41 | ) 42 | ) 43 | (property "Datasheet" "" 44 | (at 0 -4.1 0) 45 | (unlocked yes) 46 | (layer "F.Fab") 47 | (hide yes) 48 | (uuid "8d74f3ec-87e5-4945-8c70-5fb76ca406bf") 49 | (effects 50 | (font 51 | (size 1 1) 52 | (thickness 0.15) 53 | ) 54 | ) 55 | ) 56 | (property "Description" "" 57 | (at 0 -4.1 0) 58 | (unlocked yes) 59 | (layer "F.Fab") 60 | (hide yes) 61 | (uuid "06a0132f-3fd8-4b6f-91ab-9be3371da309") 62 | (effects 63 | (font 64 | (size 1 1) 65 | (thickness 0.15) 66 | ) 67 | ) 68 | ) 69 | (attr smd) 70 | (fp_line 71 | (start 0 -0.425) 72 | (end 0 0) 73 | (stroke 74 | (width 0.1) 75 | (type default) 76 | ) 77 | (layer "F.SilkS") 78 | (uuid "fe5a489c-3a59-4788-83d8-decf2e33f0f2") 79 | ) 80 | (fp_line 81 | (start 0 0) 82 | (end 0.7 0) 83 | (stroke 84 | (width 0.1) 85 | (type default) 86 | ) 87 | (layer "F.SilkS") 88 | (uuid "b050b1ca-f5d4-4a26-b129-f568354501d0") 89 | ) 90 | (fp_rect 91 | (start -0.8 -0.6) 92 | (end 0.8 0.6) 93 | (stroke 94 | (width 0.05) 95 | (type default) 96 | ) 97 | (fill none) 98 | (layer "F.CrtYd") 99 | (uuid "fafc5fa6-3f3d-465e-9e39-3f3b15e58f01") 100 | ) 101 | (fp_text user "${REFERENCE}" 102 | (at 0 -1.6 0) 103 | (unlocked yes) 104 | (layer "F.Fab") 105 | (uuid "8a28b09a-6839-449d-ae02-c775287ce8a5") 106 | (effects 107 | (font 108 | (size 1 1) 109 | (thickness 0.15) 110 | ) 111 | ) 112 | ) 113 | (pad "1" smd rect 114 | (at 0.475 -0.3) 115 | (size 0.55 0.35) 116 | (layers "F.Cu" "F.Paste" "F.Mask") 117 | (thermal_bridge_angle 45) 118 | (uuid "4c77f8e6-275b-4693-bb8b-69554b198f5c") 119 | ) 120 | (pad "2" smd rect 121 | (at 0.475 0.3) 122 | (size 0.55 0.35) 123 | (layers "F.Cu" "F.Paste" "F.Mask") 124 | (thermal_bridge_angle 45) 125 | (uuid "848ee3e8-f947-498e-b102-c62737a94e81") 126 | ) 127 | (pad "3" smd rect 128 | (at -0.475 0.3) 129 | (size 0.55 0.35) 130 | (layers "F.Cu" "F.Paste" "F.Mask") 131 | (thermal_bridge_angle 45) 132 | (uuid "c63fd986-4a1f-4d1b-a52a-21ed80ad19e2") 133 | ) 134 | (pad "4" smd rect 135 | (at -0.475 -0.3) 136 | (size 0.55 0.35) 137 | (layers "F.Cu" "F.Paste" "F.Mask") 138 | (thermal_bridge_angle 45) 139 | (uuid "e44cedf6-eb64-46d0-8623-00ffd344f67e") 140 | ) 141 | ) 142 | -------------------------------------------------------------------------------- /earring-ring-rgb36/earring/Custom.pretty/NH-B1515RGBA-HF.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "NH-B1515RGBA-HF" 2 | (version 20240108) 3 | (generator "pcbnew") 4 | (generator_version "8.0") 5 | (layer "F.Cu") 6 | (property "Reference" "REF**" 7 | (at 0 -9.6 0) 8 | (unlocked yes) 9 | (layer "F.SilkS") 10 | (uuid "c3235b3a-2c95-40f8-95db-c1ec17f3f023") 11 | (effects 12 | (font 13 | (size 1 1) 14 | (thickness 0.1) 15 | ) 16 | ) 17 | ) 18 | (property "Value" "NH-B1515RGBA-HF" 19 | (at 0 -8.1 0) 20 | (unlocked yes) 21 | (layer "F.Fab") 22 | (uuid "b0198e2d-eeb4-4db7-bffb-6b696f4cc2d2") 23 | (effects 24 | (font 25 | (size 1 1) 26 | (thickness 0.15) 27 | ) 28 | ) 29 | ) 30 | (property "Footprint" "" 31 | (at 0 -9.1 0) 32 | (unlocked yes) 33 | (layer "F.Fab") 34 | (hide yes) 35 | (uuid "d873082d-f9eb-4bb1-9829-75def92e0b3d") 36 | (effects 37 | (font 38 | (size 1 1) 39 | (thickness 0.15) 40 | ) 41 | ) 42 | ) 43 | (property "Datasheet" "" 44 | (at 0 -9.1 0) 45 | (unlocked yes) 46 | (layer "F.Fab") 47 | (hide yes) 48 | (uuid "92cec690-8e01-48fa-83a4-a37ec116322b") 49 | (effects 50 | (font 51 | (size 1 1) 52 | (thickness 0.15) 53 | ) 54 | ) 55 | ) 56 | (property "Description" "" 57 | (at 0 -9.1 0) 58 | (unlocked yes) 59 | (layer "F.Fab") 60 | (hide yes) 61 | (uuid "dee58e19-dea1-4bd8-aadc-1fb302a9af57") 62 | (effects 63 | (font 64 | (size 1 1) 65 | (thickness 0.15) 66 | ) 67 | ) 68 | ) 69 | (attr smd) 70 | (fp_rect 71 | (start -0.8 -0.8) 72 | (end 0.8 0.8) 73 | (stroke 74 | (width 0.05) 75 | (type default) 76 | ) 77 | (fill none) 78 | (layer "F.CrtYd") 79 | (uuid "8a97b0b7-f3a0-4308-9c66-df0a91fdf0bf") 80 | ) 81 | (fp_text user "${REFERENCE}" 82 | (at 0 -6.6 0) 83 | (unlocked yes) 84 | (layer "F.Fab") 85 | (uuid "6583c5cf-24d0-4513-aebd-28483800876a") 86 | (effects 87 | (font 88 | (size 1 1) 89 | (thickness 0.15) 90 | ) 91 | ) 92 | ) 93 | (fp_text user "+" 94 | (at -0.6 0.35 0) 95 | (unlocked yes) 96 | (layer "F.Fab") 97 | (uuid "fe045b79-09e5-4d29-81b8-c4113cfdcdb3") 98 | (effects 99 | (font 100 | (size 0.5 0.5) 101 | (thickness 0.125) 102 | ) 103 | (justify left bottom) 104 | ) 105 | ) 106 | (pad "1" smd rect 107 | (at -0.7 0.4) 108 | (size 0.6 0.4) 109 | (layers "F.Cu" "F.Paste" "F.Mask") 110 | (thermal_bridge_angle 45) 111 | (uuid "ca8c1c98-d05e-41b5-8e94-f41be4e3c6ba") 112 | ) 113 | (pad "2" smd rect 114 | (at 0.7 0.4) 115 | (size 0.6 0.4) 116 | (layers "F.Cu" "F.Paste" "F.Mask") 117 | (thermal_bridge_angle 45) 118 | (uuid "376c919d-5b8e-4903-b2cf-63ac61cc031a") 119 | ) 120 | (pad "3" smd rect 121 | (at 0.7 -0.4) 122 | (size 0.6 0.4) 123 | (layers "F.Cu" "F.Paste" "F.Mask") 124 | (thermal_bridge_angle 45) 125 | (uuid "b16ea57e-56f6-42f9-9fed-728396b10c6f") 126 | ) 127 | (pad "4" smd rect 128 | (at -0.7 -0.4) 129 | (size 0.6 0.4) 130 | (layers "F.Cu" "F.Paste" "F.Mask") 131 | (thermal_bridge_angle 45) 132 | (uuid "0f385e89-8d2c-4021-b0c6-c8c5ccc1d207") 133 | ) 134 | ) 135 | -------------------------------------------------------------------------------- /earring-ring-rgb36/earring/Custom.pretty/NH-Z1415RGBA-SG.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "NH-Z1415RGBA-SG" 2 | (version 20240108) 3 | (generator "pcbnew") 4 | (generator_version "8.0") 5 | (layer "F.Cu") 6 | (property "Reference" "REF**" 7 | (at 0 -4.6 0) 8 | (unlocked yes) 9 | (layer "F.SilkS") 10 | (uuid "998dcea6-3158-4d12-a7f9-f3d247a939d6") 11 | (effects 12 | (font 13 | (size 1 1) 14 | (thickness 0.1) 15 | ) 16 | ) 17 | ) 18 | (property "Value" "NH-Z1415RGBA-SG" 19 | (at 0 -3.1 0) 20 | (unlocked yes) 21 | (layer "F.Fab") 22 | (uuid "f50d0dc0-7d3c-469d-bf5a-7fb2d0ca5e25") 23 | (effects 24 | (font 25 | (size 1 1) 26 | (thickness 0.15) 27 | ) 28 | ) 29 | ) 30 | (property "Footprint" "" 31 | (at 0 -4.1 0) 32 | (unlocked yes) 33 | (layer "F.Fab") 34 | (hide yes) 35 | (uuid "42ffac10-2122-4acf-9821-c222876e0a6d") 36 | (effects 37 | (font 38 | (size 1 1) 39 | (thickness 0.15) 40 | ) 41 | ) 42 | ) 43 | (property "Datasheet" "" 44 | (at 0 -4.1 0) 45 | (unlocked yes) 46 | (layer "F.Fab") 47 | (hide yes) 48 | (uuid "3927fb67-fa2e-4954-bf34-9cd4d547dc98") 49 | (effects 50 | (font 51 | (size 1 1) 52 | (thickness 0.15) 53 | ) 54 | ) 55 | ) 56 | (property "Description" "" 57 | (at 0 -4.1 0) 58 | (unlocked yes) 59 | (layer "F.Fab") 60 | (hide yes) 61 | (uuid "d6170dfd-8aa2-40c9-a911-1915f6a06fc5") 62 | (effects 63 | (font 64 | (size 1 1) 65 | (thickness 0.15) 66 | ) 67 | ) 68 | ) 69 | (attr smd) 70 | (fp_poly 71 | (pts 72 | (xy 0.05 -0.6) (xy 0.05 -0.2) (xy 0.25 -0.4) 73 | ) 74 | (stroke 75 | (width 0.1) 76 | (type solid) 77 | ) 78 | (fill solid) 79 | (layer "F.SilkS") 80 | (uuid "a54fef26-e022-4c5b-806a-379cc91a9c78") 81 | ) 82 | (fp_rect 83 | (start -0.8 -0.75) 84 | (end 0.8 0.75) 85 | (stroke 86 | (width 0.05) 87 | (type default) 88 | ) 89 | (fill none) 90 | (layer "F.CrtYd") 91 | (uuid "c112f07a-4b07-49ba-add9-b320d3ae374e") 92 | ) 93 | (fp_text user "${REFERENCE}" 94 | (at 0 -1.6 0) 95 | (unlocked yes) 96 | (layer "F.Fab") 97 | (uuid "d98a0803-ec16-4c19-be1f-e72673d68688") 98 | (effects 99 | (font 100 | (size 1 1) 101 | (thickness 0.15) 102 | ) 103 | ) 104 | ) 105 | (pad "1" smd rect 106 | (at 0.65 -0.4) 107 | (size 0.5 0.4) 108 | (layers "F.Cu" "F.Paste" "F.Mask") 109 | (thermal_bridge_angle 45) 110 | (uuid "ea2b0178-e3ce-49e7-a11d-0bc565fc91eb") 111 | ) 112 | (pad "2" smd rect 113 | (at 0.65 0.4) 114 | (size 0.5 0.4) 115 | (layers "F.Cu" "F.Paste" "F.Mask") 116 | (thermal_bridge_angle 45) 117 | (uuid "15c81043-dfbf-494b-8174-c1ccd00321ac") 118 | ) 119 | (pad "3" smd rect 120 | (at -0.65 0.4) 121 | (size 0.5 0.4) 122 | (layers "F.Cu" "F.Paste" "F.Mask") 123 | (thermal_bridge_angle 45) 124 | (uuid "663cb5fa-9493-4417-a350-d07ff7b9468d") 125 | ) 126 | (pad "4" smd rect 127 | (at -0.65 -0.4) 128 | (size 0.5 0.4) 129 | (layers "F.Cu" "F.Paste" "F.Mask") 130 | (thermal_bridge_angle 45) 131 | (uuid "0708dba6-fcc9-49e7-bc37-ed9364de9ead") 132 | ) 133 | ) 134 | -------------------------------------------------------------------------------- /earring-ring-rgb36/earring/Custom.pretty/ProgramHeader.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "ProgramHeader" 2 | (version 20240108) 3 | (generator "pcbnew") 4 | (generator_version "8.0") 5 | (layer "F.Cu") 6 | (property "Reference" "REF**" 7 | (at 0 -2.28 0) 8 | (unlocked yes) 9 | (layer "F.SilkS") 10 | (hide yes) 11 | (uuid "6c584c2c-eb20-4a2c-a3e0-0720e5aef85b") 12 | (effects 13 | (font 14 | (size 1 1) 15 | (thickness 0.1) 16 | ) 17 | ) 18 | ) 19 | (property "Value" "ProgramHeader" 20 | (at 0 -5.08 0) 21 | (unlocked yes) 22 | (layer "F.Fab") 23 | (uuid "d89dd48d-1c93-4ec4-a19f-b10fc7d1e9c5") 24 | (effects 25 | (font 26 | (size 1 1) 27 | (thickness 0.15) 28 | ) 29 | ) 30 | ) 31 | (property "Footprint" "" 32 | (at 0 -4.98 0) 33 | (unlocked yes) 34 | (layer "F.Fab") 35 | (hide yes) 36 | (uuid "10e7830a-5cb6-49a5-9bde-03b676a57f91") 37 | (effects 38 | (font 39 | (size 1 1) 40 | (thickness 0.15) 41 | ) 42 | ) 43 | ) 44 | (property "Datasheet" "" 45 | (at 0 -4.98 0) 46 | (unlocked yes) 47 | (layer "F.Fab") 48 | (hide yes) 49 | (uuid "ca429b47-b579-4cc7-92ab-9f513c6e8f46") 50 | (effects 51 | (font 52 | (size 1 1) 53 | (thickness 0.15) 54 | ) 55 | ) 56 | ) 57 | (property "Description" "" 58 | (at 0 -4.98 0) 59 | (unlocked yes) 60 | (layer "F.Fab") 61 | (hide yes) 62 | (uuid "79d630f8-4fc3-4155-a2db-6fcb6ae3bbd0") 63 | (effects 64 | (font 65 | (size 1 1) 66 | (thickness 0.15) 67 | ) 68 | ) 69 | ) 70 | (attr smd) 71 | (fp_text user "${REFERENCE}" 72 | (at 0 -3.58 0) 73 | (unlocked yes) 74 | (layer "F.Fab") 75 | (uuid "ac61653a-2779-46fe-88db-8749689c25c5") 76 | (effects 77 | (font 78 | (size 1 1) 79 | (thickness 0.15) 80 | ) 81 | ) 82 | ) 83 | (pad "1" smd circle 84 | (at -3.81 0) 85 | (size 1.5 1.5) 86 | (layers "F.Cu" "F.Paste" "F.Mask") 87 | (uuid "e8dca4df-5f81-4373-905b-f2be97b6deb4") 88 | ) 89 | (pad "2" smd rect 90 | (at -1.27 0) 91 | (size 1.5 1.5) 92 | (layers "F.Cu" "F.Paste" "F.Mask") 93 | (uuid "62a7a1e1-e6c9-4cd7-a6b2-bf43f9f15a4c") 94 | ) 95 | (pad "3" smd circle 96 | (at 1.27 0) 97 | (size 1.5 1.5) 98 | (layers "F.Cu" "F.Paste" "F.Mask") 99 | (uuid "c8a7cdce-5903-46ad-92ed-14951836228c") 100 | ) 101 | (pad "4" smd circle 102 | (at 3.81 0) 103 | (size 1.5 1.5) 104 | (layers "F.Cu" "F.Paste" "F.Mask") 105 | (uuid "16053347-2a6e-4921-9833-2b455789ed60") 106 | ) 107 | ) 108 | -------------------------------------------------------------------------------- /earring-ring-rgb36/earring/Custom.pretty/ToolingHoleWithMouseBytes.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "ToolingHoleWithMouseBytes" 2 | (version 20240108) 3 | (generator "pcbnew") 4 | (generator_version "8.0") 5 | (layer "F.Cu") 6 | (property "Reference" "REF**" 7 | (at 0 -5.1 0) 8 | (unlocked yes) 9 | (layer "F.SilkS") 10 | (hide yes) 11 | (uuid "03bf97db-7ff9-4457-857e-811ed57af9a8") 12 | (effects 13 | (font 14 | (size 1 1) 15 | (thickness 0.1) 16 | ) 17 | ) 18 | ) 19 | (property "Value" "ToolingHoleWithMouseBytes" 20 | (at 0 -3.6 0) 21 | (unlocked yes) 22 | (layer "F.Fab") 23 | (hide yes) 24 | (uuid "953fb660-4a4b-4852-a867-666c4ad394cb") 25 | (effects 26 | (font 27 | (size 1 1) 28 | (thickness 0.15) 29 | ) 30 | ) 31 | ) 32 | (property "Footprint" "" 33 | (at 0 0 0) 34 | (unlocked yes) 35 | (layer "F.Fab") 36 | (hide yes) 37 | (uuid "bf0997a2-7588-43ca-9b38-ab2eabc60721") 38 | (effects 39 | (font 40 | (size 1 1) 41 | (thickness 0.15) 42 | ) 43 | ) 44 | ) 45 | (property "Datasheet" "" 46 | (at 0 0 0) 47 | (unlocked yes) 48 | (layer "F.Fab") 49 | (hide yes) 50 | (uuid "d53f30fc-74c5-4294-a81e-ef114d76fff7") 51 | (effects 52 | (font 53 | (size 1 1) 54 | (thickness 0.15) 55 | ) 56 | ) 57 | ) 58 | (property "Description" "" 59 | (at 0 0 0) 60 | (unlocked yes) 61 | (layer "F.Fab") 62 | (hide yes) 63 | (uuid "b131c8b9-c0bf-489b-b629-c6546c3cb58e") 64 | (effects 65 | (font 66 | (size 1 1) 67 | (thickness 0.15) 68 | ) 69 | ) 70 | ) 71 | (attr through_hole) 72 | (fp_line 73 | (start -1.7 0) 74 | (end -1.7 -1.83) 75 | (stroke 76 | (width 0.05) 77 | (type default) 78 | ) 79 | (layer "Edge.Cuts") 80 | (uuid "7aca3c31-6ce5-41eb-8380-2c70875d3b9d") 81 | ) 82 | (fp_line 83 | (start 1.7 0) 84 | (end 1.7 -1.83) 85 | (stroke 86 | (width 0.05) 87 | (type default) 88 | ) 89 | (layer "Edge.Cuts") 90 | (uuid "36c951dd-90ec-4e1e-a20b-5eda680d0e42") 91 | ) 92 | (fp_arc 93 | (start 1.7 0) 94 | (mid 0 1.7) 95 | (end -1.7 0) 96 | (stroke 97 | (width 0.05) 98 | (type default) 99 | ) 100 | (layer "Edge.Cuts") 101 | (uuid "96131d89-80b8-4ca0-8f84-2850f7e6d8e5") 102 | ) 103 | (pad "" np_thru_hole circle 104 | (at -1.143 -1.778) 105 | (size 0.5 0.5) 106 | (drill 0.5) 107 | (layers "*.Mask") 108 | (uuid "ef598eec-5afa-436f-bbdf-1e81efba62d4") 109 | ) 110 | (pad "" np_thru_hole circle 111 | (at -0.381 -1.778) 112 | (size 0.5 0.5) 113 | (drill 0.5) 114 | (layers "*.Mask") 115 | (uuid "a7d3f0fb-a692-42c7-a5bd-d5e6e7741c71") 116 | ) 117 | (pad "" np_thru_hole circle 118 | (at 0 0) 119 | (size 1.152 1.152) 120 | (drill 1.152) 121 | (layers "*.Mask") 122 | (solder_mask_margin 0.148) 123 | (clearance 0.248) 124 | (uuid "d2ab0100-f9b0-4db2-93de-083142b782fe") 125 | ) 126 | (pad "" np_thru_hole circle 127 | (at 0.381 -1.778) 128 | (size 0.5 0.5) 129 | (drill 0.5) 130 | (layers "*.Mask") 131 | (uuid "16860b4c-b137-4745-b1e1-2ce99dc0d541") 132 | ) 133 | (pad "" np_thru_hole circle 134 | (at 1.143 -1.778) 135 | (size 0.5 0.5) 136 | (drill 0.5) 137 | (layers "*.Mask") 138 | (uuid "5cf9b32e-4cbf-4fd6-93eb-538dd30ab255") 139 | ) 140 | ) 141 | -------------------------------------------------------------------------------- /earring-ring-rgb36/earring/Custom.pretty/ToolingHole_JLCPCB.kicad_mod: -------------------------------------------------------------------------------- 1 | (footprint "ToolingHole_JLCPCB" 2 | (version 20240108) 3 | (generator "pcbnew") 4 | (generator_version "8.0") 5 | (layer "F.Cu") 6 | (property "Reference" "REF**" 7 | (at 0 -0.5 0) 8 | (unlocked yes) 9 | (layer "F.SilkS") 10 | (hide yes) 11 | (uuid "53d78b11-4fff-4f0c-b7d1-4f99c1a89510") 12 | (effects 13 | (font 14 | (size 1 1) 15 | (thickness 0.1) 16 | ) 17 | ) 18 | ) 19 | (property "Value" "ToolingHole_JLCPCB" 20 | (at 0 1 0) 21 | (unlocked yes) 22 | (layer "F.Fab") 23 | (hide yes) 24 | (uuid "12136128-17f6-4f18-bf5d-ee2ff891c131") 25 | (effects 26 | (font 27 | (size 1 1) 28 | (thickness 0.15) 29 | ) 30 | ) 31 | ) 32 | (property "Footprint" "" 33 | (at 0 0 0) 34 | (unlocked yes) 35 | (layer "F.Fab") 36 | (hide yes) 37 | (uuid "a1420132-2125-4ef6-8283-fc0eee3a70de") 38 | (effects 39 | (font 40 | (size 1 1) 41 | (thickness 0.15) 42 | ) 43 | ) 44 | ) 45 | (property "Datasheet" "" 46 | (at 0 0 0) 47 | (unlocked yes) 48 | (layer "F.Fab") 49 | (hide yes) 50 | (uuid "fe16b6bc-1054-415f-a26e-85189e630e74") 51 | (effects 52 | (font 53 | (size 1 1) 54 | (thickness 0.15) 55 | ) 56 | ) 57 | ) 58 | (property "Description" "" 59 | (at 0 0 0) 60 | (unlocked yes) 61 | (layer "F.Fab") 62 | (hide yes) 63 | (uuid "805221d0-1c9d-4a97-b5a3-ca801b91b877") 64 | (effects 65 | (font 66 | (size 1 1) 67 | (thickness 0.15) 68 | ) 69 | ) 70 | ) 71 | (attr through_hole) 72 | (pad "" np_thru_hole circle 73 | (at 0 0) 74 | (size 1.152 1.152) 75 | (drill 1.152) 76 | (layers "*.Mask") 77 | (solder_mask_margin 0.148) 78 | (clearance 0.248) 79 | (uuid "43525128-76ef-49e0-a99e-4c0085d5d3f1") 80 | ) 81 | ) 82 | -------------------------------------------------------------------------------- /earring-ring-rgb36/earring/earring.kicad_prl: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "active_layer": 36, 4 | "active_layer_preset": "All Layers", 5 | "auto_track_width": true, 6 | "hidden_netclasses": [], 7 | "hidden_nets": [], 8 | "high_contrast_mode": 0, 9 | "net_color_mode": 1, 10 | "opacity": { 11 | "images": 0.6, 12 | "pads": 1.0, 13 | "tracks": 1.0, 14 | "vias": 1.0, 15 | "zones": 0.6 16 | }, 17 | "selection_filter": { 18 | "dimensions": true, 19 | "footprints": true, 20 | "graphics": true, 21 | "keepouts": true, 22 | "lockedItems": false, 23 | "otherItems": true, 24 | "pads": true, 25 | "text": true, 26 | "tracks": true, 27 | "vias": true, 28 | "zones": true 29 | }, 30 | "visible_items": [ 31 | 0, 32 | 1, 33 | 2, 34 | 3, 35 | 4, 36 | 5, 37 | 8, 38 | 9, 39 | 10, 40 | 11, 41 | 12, 42 | 13, 43 | 15, 44 | 16, 45 | 17, 46 | 18, 47 | 19, 48 | 20, 49 | 21, 50 | 22, 51 | 23, 52 | 24, 53 | 25, 54 | 26, 55 | 27, 56 | 28, 57 | 29, 58 | 30, 59 | 32, 60 | 33, 61 | 34, 62 | 35, 63 | 36, 64 | 39, 65 | 40 66 | ], 67 | "visible_layers": "fffffff_ffffffff", 68 | "zone_display_mode": 0 69 | }, 70 | "git": { 71 | "repo_password": "", 72 | "repo_type": "", 73 | "repo_username": "", 74 | "ssh_key": "" 75 | }, 76 | "meta": { 77 | "filename": "earring.kicad_prl", 78 | "version": 3 79 | }, 80 | "project": { 81 | "files": [] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /earring-ring-rgb36/earring/fabrication-toolkit-options.json: -------------------------------------------------------------------------------- 1 | {"EXTRA_LAYERS": "", "ALL_ACTIVE_LAYERS": false, "EXTEND_EDGE_CUT": false, "ALTERNATIVE_EDGE_CUT": false, "AUTO TRANSLATE": true, "AUTO FILL": true, "EXCLUDE DNP": false} -------------------------------------------------------------------------------- /earring-ring-rgb36/earring/fp-lib-table: -------------------------------------------------------------------------------- 1 | (fp_lib_table 2 | (version 7) 3 | (lib (name "Custom")(type "KiCad")(uri "${KIPRJMOD}/Custom.pretty")(options "")(descr "")) 4 | ) 5 | -------------------------------------------------------------------------------- /earring-ring-rgb36/place-components.py: -------------------------------------------------------------------------------- 1 | import math 2 | import pcbnew 3 | 4 | pcb = pcbnew.GetBoard() 5 | 6 | # Place the LEDs. 7 | radius = 10.5 8 | for n in range(36): 9 | fp = pcb.FindFootprintByReference('D%d' % (n+1)) 10 | fp.SetPos(pcbnew.VECTOR2I_MM(math.sin(math.pi*2/36*n)*radius, -math.cos(math.pi*2/36*n)*radius)) 11 | fp.SetOrientationDegrees(n*-10-90+360) 12 | 13 | # Refresh editor (so it doesn't work on an old version of the PCB). 14 | pcbnew.Refresh() 15 | -------------------------------------------------------------------------------- /earring-ring/README.md: -------------------------------------------------------------------------------- 1 | # Go powered LED earring 2 | 3 | This is a simple project for LED earrings. The earrings have 18 individually addressable RGB LEDs. 4 | 5 | ![Photo of the LED earrings](earring.jpg) 6 | 7 | The logic and the animations are all written in Go. The LEDs are controlled using bitbanging which is done in handcrafted inline assembly in C (through CGo). 8 | 9 | In this directory you'll find: 10 | 11 | * The source files. 12 | * The schematic as a PDF file. 13 | * The gerber, BOM, and Pick-n-place files in case you want to fabricate this earring yourself. 14 | * An Altium project file, downloaded from EasyEDA (untested, might not work). 15 | 16 | You can also find the design on [oshwlab.com](https://oshwlab.com/aykevanlaethem/led-earring-attiny1616_copy_copy_copy_copy) to easily produce them using JLCPCB. But also see [the project page on my website](https://aykevl.nl/projects/earrings), I might start selling them soon. 17 | 18 | ## Programming 19 | 20 | The PCB exposes a few pins: 21 | 22 | - `+`, the plus side of the battery 23 | - `R`, the programming pin (originally "reset" but actually just the UPDI programming pin) 24 | - `-`, the battery minus side 25 | 26 | **Be careful** with connecting the `+`! Probably, don't do it at all unless you put in some resistor. The LEDs are connected without any resistors and expect some internal resistance from the button cell battery. 27 | 28 | You can make a programmer quite easily using a USB to UART converter and a 1kΩ resistor. You can find details on the [pymcuprog](https://pypi.org/project/pymcuprog/) project page (see "Serial port UPDI (pyupdi)"). 29 | 30 | To program, insert a CR1220/CR1225 coin cell battery and connect the `-` to the UART ground and `R` to the UPDI programming pin. Then run the following command: 31 | 32 | tinygo flash -target=attiny1616 -opt=2 33 | 34 | That's all! It'll take a few seconds and the new code is running on the chip. 35 | 36 | 37 | ## Simulating 38 | 39 | You can also simulate the code for easier debugging. Just run it as a Go program: 40 | 41 | ``` 42 | go run . 43 | ``` 44 | 45 | ![Screenshot of earring code running under simulation](simulator.png) 46 | 47 | In the future, I hope to improve the simulator so that it will draw the LEDs in a circle like on the real earring. 48 | 49 | ## Revision history 50 | 51 | I've made a few revisions of these earrings: 52 | 53 | * **v1** is the initial version I'm still using for myself and is the one I sold on GopherCon. I had to solder the button and battery holder on the back myself. 54 | * **v2** was a bit of a failure. It used a smaller LED but sadly most of those were not assembled correctly by JLCPCB (often, one of the LEDs didn't work). Therefore, I haven't published it. 55 | * **v3** is the one that actually works, and is easily mass-produced. The button and battery holder can be assembled by JLCPCB, meaning I only have to break them apart, program them, and attach earring hooks and such. 56 | 57 | ## Credits 58 | 59 | I got a lot of inspiration from the earrings made by [California STEAM](https://www.tindie.com/stores/californiasteam/). Unfortunately they're not shipping to Europe so I had to make my own 🙂 60 | 61 | Another cool project I found was [this earring on Hackaday](https://hackaday.io/project/186402-ws2812b-neopixel-earring). It's built quite differently however, even using WS2812 LEDs! 62 | 63 | ## License 64 | 65 | Public domain. Feel free to use however you wish. Attribution is appreciated though. 66 | -------------------------------------------------------------------------------- /earring-ring/bitbang.go: -------------------------------------------------------------------------------- 1 | //go:build attiny1616 2 | 3 | package main 4 | 5 | import ( 6 | "device/avr" 7 | "machine" 8 | "runtime/interrupt" 9 | "unsafe" 10 | ) 11 | 12 | // #include "bitbang.h" 13 | import "C" 14 | 15 | func init() { 16 | // Use 20MHz/32 = 625kHz 17 | // This results in a current consumption of around 0.41mA with all LEDs off. 18 | // It *should* result in a current consumption of 187.5µA, but I think the 19 | // 20MHz oscillator uses a fair bit of current too. 20 | avr.CPU.CCP.Set(0xD8) // unlock protected registers 21 | avr.CLKCTRL.MCLKCTRLB.Set(0x4<<1 | 1) // prescaler of 32 22 | } 23 | 24 | const button = machine.PC3 25 | 26 | func initHardware() { 27 | avr.PORTA.DIR.Set(0b0111_1110) // Configure PA1-PA6 as output. 28 | avr.PORTB.DIR.Set(0b0011_1111) // Configure PB0-PB5 as output (R2, G2, B2, R3, G3, B3) 29 | avr.PORTB.OUT.Set(0b0011_1111) // Set PB0-PB5 low 30 | avr.PORTC.DIR.Set(0b0000_0111) // Configure PC0-PC2 as output (R1, G1, B1) 31 | avr.PORTC.OUT.Set(0b0000_0111) // Set PC0-PC2 low 32 | 33 | button.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) 34 | 35 | // This is the only remaining pin. Configure it as output just in case that 36 | // helps with standby current consumption. 37 | machine.PA7.Configure(machine.PinConfig{Mode: machine.PinOutput}) 38 | } 39 | 40 | func disableLEDs() { 41 | avr.PORTA.OUTCLR.Set(0b0111_1110) // Set PA1-PA6 high 42 | avr.PORTB.OUTCLR.Set(0b0011_1111) // Set PB0-PB5 high 43 | avr.PORTC.OUTCLR.Set(0b0000_0111) // Set PC0-PC2 high 44 | } 45 | 46 | func isButtonPressed() bool { 47 | return !button.Get() 48 | } 49 | 50 | func updateLEDs() { 51 | // Pinout for the anodes: 52 | // A1: PA2 53 | // A2: PA1 54 | // A3: PA6 55 | // A4: PA5 56 | // A5: PA4 57 | // A6: PA3 58 | // Pinout for the cathodes: 59 | // R1: PC0 60 | // G1: PC1 61 | // B1: PC2 62 | // R2: PB0 63 | // G2: PB1 64 | // B2: PB2 65 | // R3: PB3 66 | // G3: PB4 67 | // B3: PB5 68 | 69 | state := interrupt.Disable() 70 | 71 | // R1 72 | avr.PORTC.OUTTGL.Set(1 << 0) 73 | showLEDs( 74 | leds[0+2].R, 75 | leds[0+3].R, 76 | leds[0+4].R, 77 | leds[0+5].R, 78 | leds[0+0].R, 79 | leds[0+1].R, 80 | ) 81 | avr.PORTC.OUTTGL.Set(1 << 0) 82 | 83 | // G1 84 | avr.PORTC.OUTTGL.Set(1 << 1) 85 | showLEDs( 86 | leds[0+2].G, 87 | leds[0+3].G, 88 | leds[0+4].G, 89 | leds[0+5].G, 90 | leds[0+0].G, 91 | leds[0+1].G, 92 | ) 93 | avr.PORTC.OUTTGL.Set(1 << 1) 94 | 95 | // B1 96 | avr.PORTC.OUTTGL.Set(1 << 2) 97 | showLEDs( 98 | leds[0+2].B, 99 | leds[0+3].B, 100 | leds[0+4].B, 101 | leds[0+5].B, 102 | leds[0+0].B, 103 | leds[0+1].B, 104 | ) 105 | avr.PORTC.OUTTGL.Set(1 << 2) 106 | 107 | // R2 (LEDs reversed) 108 | avr.PORTB.OUTTGL.Set(1 << 0) 109 | showLEDs( 110 | leds[6+3].R, 111 | leds[6+2].R, 112 | leds[6+1].R, 113 | leds[6+0].R, 114 | leds[6+5].R, 115 | leds[6+4].R, 116 | ) 117 | avr.PORTB.OUTTGL.Set(1 << 0) 118 | 119 | // G2 (LEDs reversed) 120 | avr.PORTB.OUTTGL.Set(1 << 1) 121 | showLEDs( 122 | leds[6+3].G, 123 | leds[6+2].G, 124 | leds[6+1].G, 125 | leds[6+0].G, 126 | leds[6+5].G, 127 | leds[6+4].G, 128 | ) 129 | avr.PORTB.OUTTGL.Set(1 << 1) 130 | 131 | // B2 (LEDs reversed) 132 | avr.PORTB.OUTTGL.Set(1 << 2) 133 | showLEDs( 134 | leds[6+3].B, 135 | leds[6+2].B, 136 | leds[6+1].B, 137 | leds[6+0].B, 138 | leds[6+5].B, 139 | leds[6+4].B, 140 | ) 141 | avr.PORTB.OUTTGL.Set(1 << 2) 142 | 143 | // R3 144 | avr.PORTB.OUTTGL.Set(1 << 3) 145 | showLEDs( 146 | leds[12+2].R, 147 | leds[12+3].R, 148 | leds[12+4].R, 149 | leds[12+5].R, 150 | leds[12+0].R, 151 | leds[12+1].R, 152 | ) 153 | avr.PORTB.OUTTGL.Set(1 << 3) 154 | 155 | // G3 156 | avr.PORTB.OUTTGL.Set(1 << 4) 157 | showLEDs( 158 | leds[12+2].G, 159 | leds[12+3].G, 160 | leds[12+4].G, 161 | leds[12+5].G, 162 | leds[12+0].G, 163 | leds[12+1].G, 164 | ) 165 | avr.PORTB.OUTTGL.Set(1 << 4) 166 | 167 | // B3 168 | avr.PORTB.OUTTGL.Set(1 << 5) 169 | showLEDs( 170 | leds[12+2].B, 171 | leds[12+3].B, 172 | leds[12+4].B, 173 | leds[12+5].B, 174 | leds[12+0].B, 175 | leds[12+1].B, 176 | ) 177 | avr.PORTB.OUTTGL.Set(1 << 5) 178 | 179 | interrupt.Restore(state) 180 | } 181 | 182 | // The bitbang function is very large, but without the //go:noinline it would be 183 | // inlined. I guess LLVM doesn't recognize the inline assembly is very large. 184 | // 185 | //go:noinline 186 | func showLEDs(c1, c2, c3, c4, c5, c6 uint8) { 187 | port := (*uint8)(unsafe.Pointer(uintptr(0x0001))) // VPORTA.OUT (alias of PORTA.OUT in I/O space) 188 | C.bitbang_show_leds(c1, c2, c3, c4, c5, c6, port) 189 | } 190 | -------------------------------------------------------------------------------- /earring-ring/bitbang.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static void bitbang_show_leds(uint8_t c1, uint8_t c2, uint8_t c3, uint8_t c4, uint8_t c5, uint8_t c6, volatile uint8_t *port) { 4 | uint8_t savedOut = *port; 5 | uint8_t tmp1 = savedOut; 6 | uint8_t tmp2 = savedOut; 7 | uint8_t tmp3 = savedOut; 8 | __asm__ __volatile__( 9 | // precalculate 32-cycle bitplane 10 | "bst %[c1], 7\n\t" 11 | "bld %[tmp1], 6\n\t" 12 | "bst %[c2], 7\n\t" 13 | "bld %[tmp1], 5\n\t" 14 | "bst %[c3], 7\n\t" 15 | "bld %[tmp1], 4\n\t" 16 | "bst %[c4], 7\n\t" 17 | "bld %[tmp1], 3\n\t" 18 | "bst %[c5], 7\n\t" 19 | "bld %[tmp1], 2\n\t" 20 | "bst %[c6], 7\n\t" 21 | "bld %[tmp1], 1\n\t" 22 | // precalculate 16-cycle bitplane 23 | "bst %[c1], 6\n\t" 24 | "bld %[tmp2], 6\n\t" 25 | "bst %[c2], 6\n\t" 26 | "out %[port], %[tmp1]\n\t" // start 32 cycles 27 | "bld %[tmp2], 5\n\t" 28 | "bst %[c3], 6\n\t" 29 | "bld %[tmp2], 4\n\t" 30 | "bst %[c4], 6\n\t" 31 | "bld %[tmp2], 3\n\t" 32 | "bst %[c5], 6\n\t" 33 | "bld %[tmp2], 2\n\t" 34 | "bst %[c6], 6\n\t" 35 | "bld %[tmp2], 1\n\t" 36 | // precalculate 8-cycle bitplane 37 | "bst %[c1], 5\n\t" 38 | "bld %[tmp1], 6\n\t" 39 | "bst %[c2], 5\n\t" 40 | "bld %[tmp1], 5\n\t" 41 | "bst %[c3], 5\n\t" 42 | "bld %[tmp1], 4\n\t" 43 | "bst %[c4], 5\n\t" 44 | "bld %[tmp1], 3\n\t" 45 | "bst %[c5], 5\n\t" 46 | "bld %[tmp1], 2\n\t" 47 | "bst %[c6], 5\n\t" 48 | "bld %[tmp1], 1\n\t" 49 | // precalculate 4-cycle bitplane 50 | "bst %[c1], 4\n\t" 51 | "bld %[tmp3], 6\n\t" 52 | "bst %[c2], 4\n\t" 53 | "bld %[tmp3], 5\n\t" 54 | "bst %[c3], 4\n\t" 55 | "bld %[tmp3], 4\n\t" 56 | "bst %[c4], 4\n\t" 57 | "bld %[tmp3], 3\n\t" 58 | "bst %[c5], 4\n\t" 59 | "bld %[tmp3], 2\n\t" 60 | "out %[port], %[tmp2]\n\t" // end 32 cycles, start 16 cycles 61 | "bst %[c6], 4\n\t" 62 | "bld %[tmp3], 1\n\t" 63 | // precalculate 2-cycle bitplane 64 | "bst %[c1], 3\n\t" 65 | "bld %[tmp2], 6\n\t" 66 | "bst %[c2], 3\n\t" 67 | "bld %[tmp2], 5\n\t" 68 | "bst %[c3], 3\n\t" 69 | "bld %[tmp2], 4\n\t" 70 | "bst %[c4], 3\n\t" 71 | "bld %[tmp2], 3\n\t" 72 | "bst %[c5], 3\n\t" 73 | "bld %[tmp2], 2\n\t" 74 | "bst %[c6], 3\n\t" 75 | "bld %[tmp2], 1\n\t" 76 | // precalculate 1-cycle bitplane 77 | "bst %[c1], 2\n\t" 78 | "out %[port], %[tmp1]\n\t" // end 16 cycles, start 8 cycles 79 | "bld %[tmp1], 6\n\t" 80 | "bst %[c2], 2\n\t" 81 | "bld %[tmp1], 5\n\t" 82 | "bst %[c3], 2\n\t" 83 | "bld %[tmp1], 4\n\t" 84 | "bst %[c4], 2\n\t" 85 | "bld %[tmp1], 3\n\t" 86 | "out %[port], %[tmp3]\n\t" // end 8 cycles, start 4 cycles 87 | "bst %[c5], 2\n\t" 88 | "bld %[tmp1], 2\n\t" 89 | "bst %[c6], 2\n\t" 90 | "out %[port], %[tmp2]\n\t" // end 4 cycles, start 2 cycles 91 | "bld %[tmp1], 1\n\t" 92 | "out %[port], %[tmp1]\n\t" // end 2 cycles, start 1 cycle 93 | "out %[port], %[savedOut]\n\t" // end 1 cycle, restore old port state 94 | : [tmp1]"+r"(tmp1), 95 | [tmp2]"+r"(tmp2), 96 | [tmp3]"+r"(tmp3) 97 | : [c1]"r"(c1), 98 | [c2]"r"(c2), 99 | [c3]"r"(c3), 100 | [c4]"r"(c4), 101 | [c5]"r"(c5), 102 | [c6]"r"(c6), 103 | [port]"I"(port), 104 | [savedOut]"r"(savedOut) 105 | ); 106 | *port = savedOut; 107 | } 108 | -------------------------------------------------------------------------------- /earring-ring/earring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/earring.jpg -------------------------------------------------------------------------------- /earring-ring/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aykevl/things/earring-ring 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/aykevl/board v0.0.0-20240106144210-80ca76f77def 7 | github.com/aykevl/ledsgo v0.0.0-20240320160706-1580ae804c3f 8 | ) 9 | 10 | require ( 11 | fyne.io/fyne/v2 v2.3.4 // indirect 12 | fyne.io/systray v1.10.1-0.20230403195833-7dc3c09283d6 // indirect 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/fredbi/uri v1.0.0 // indirect 15 | github.com/fsnotify/fsnotify v1.6.0 // indirect 16 | github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934 // indirect 17 | github.com/fyne-io/glfw-js v0.0.0-20220517201726-bebc2019cd33 // indirect 18 | github.com/fyne-io/image v0.0.0-20221020213044-f609c6a24345 // indirect 19 | github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect 20 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect 21 | github.com/go-text/typesetting v0.0.0-20230504123538-7c21ee424d89 // indirect 22 | github.com/godbus/dbus/v5 v5.1.0 // indirect 23 | github.com/goki/freetype v1.0.1 // indirect 24 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 25 | github.com/gopherjs/gopherjs v1.17.2 // indirect 26 | github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect 27 | github.com/pmezard/go-difflib v1.0.0 // indirect 28 | github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect 29 | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect 30 | github.com/stretchr/testify v1.8.2 // indirect 31 | github.com/tevino/abool v1.2.0 // indirect 32 | github.com/yuin/goldmark v1.5.4 // indirect 33 | golang.org/x/image v0.7.0 // indirect 34 | golang.org/x/mobile v0.0.0-20230427221453-e8d11dd0ba41 // indirect 35 | golang.org/x/net v0.10.0 // indirect 36 | golang.org/x/sys v0.8.0 // indirect 37 | golang.org/x/text v0.9.0 // indirect 38 | gopkg.in/yaml.v3 v3.0.1 // indirect 39 | honnef.co/go/js/dom v0.0.0-20221001195520-26252dedbe70 // indirect 40 | tinygo.org/x/drivers v0.26.1-0.20231124130000-fef6564044f9 // indirect 41 | ) 42 | -------------------------------------------------------------------------------- /earring-ring/pcb-v1-BOM.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/pcb-v1-BOM.csv -------------------------------------------------------------------------------- /earring-ring/pcb-v1-PickAndPlace.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/pcb-v1-PickAndPlace.csv -------------------------------------------------------------------------------- /earring-ring/pcb-v1-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/pcb-v1-back.png -------------------------------------------------------------------------------- /earring-ring/pcb-v1-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/pcb-v1-front.png -------------------------------------------------------------------------------- /earring-ring/pcb-v1-gerber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/pcb-v1-gerber.zip -------------------------------------------------------------------------------- /earring-ring/pcb-v3-BOM.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/pcb-v3-BOM.csv -------------------------------------------------------------------------------- /earring-ring/pcb-v3-PickAndPlace.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/pcb-v3-PickAndPlace.csv -------------------------------------------------------------------------------- /earring-ring/pcb-v3-gerber.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/pcb-v3-gerber.zip -------------------------------------------------------------------------------- /earring-ring/schematic-v1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/schematic-v1.pdf -------------------------------------------------------------------------------- /earring-ring/schematic-v3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/schematic-v3.pdf -------------------------------------------------------------------------------- /earring-ring/simulation.go: -------------------------------------------------------------------------------- 1 | //go:build !baremetal 2 | 3 | package main 4 | 5 | import ( 6 | "time" 7 | 8 | "github.com/aykevl/board" 9 | ) 10 | 11 | func initHardware() { 12 | board.Simulator.AddressableLEDs = 18 13 | board.AddressableLEDs.Configure() 14 | 15 | board.Buttons.Configure() 16 | } 17 | 18 | func disableLEDs() { 19 | // Assume LEDs have been shut down already. 20 | // TODO: the board package should have a way to shut down LEDs when not in 21 | // use (this is possible on the MCH2022 badge, for example). 22 | updateLEDs() 23 | } 24 | 25 | var simulatorButtonPressed bool 26 | 27 | func isButtonPressed() bool { 28 | for { 29 | event := board.Buttons.NextEvent() 30 | if event == board.NoKeyEvent { 31 | break 32 | } 33 | // We assume only one button is used (otherwise this code wouldn't work 34 | // correctly). 35 | simulatorButtonPressed = event.Pressed() 36 | } 37 | return simulatorButtonPressed 38 | } 39 | 40 | func updateLEDs() { 41 | for i, c := range leds { 42 | board.AddressableLEDs.SetRGB(i, c.R, c.G, c.B) 43 | } 44 | board.AddressableLEDs.Update() 45 | time.Sleep(time.Second / 500) 46 | } 47 | -------------------------------------------------------------------------------- /earring-ring/simulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/earring-ring/simulator.png -------------------------------------------------------------------------------- /festival-camp/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aykevl/things/festival-camp 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/aykevl/ledsgo v0.0.0-20230808203851-4c9b90563294 7 | tinygo.org/x/drivers v0.23.0 8 | ) 9 | -------------------------------------------------------------------------------- /festival-camp/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da h1:u4fP3+V+W/547R/P7ezR5gH2zucphUZfQLc1ypNaXOg= 2 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da/go.mod h1:Bn4qAbkSY0k0/wrXdqQ8bWPTv/Uww3uIJuQv/knzDdQ= 3 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 h1:NcmN0bjs7hmLKSARz88oxXF6OU50/d4Xeo5QWEE5y7w= 4 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2/go.mod h1:GDZBEY7gPGjvz/G3zLFirDcgbaN3nF4nvJ157+vnEro= 5 | github.com/aykevl/ledsgo v0.0.0-20230808203851-4c9b90563294 h1:vQgq4d9Epm9FGk1DT9GBwRpRQ2QXgPcY3hGqN+psGRk= 6 | github.com/aykevl/ledsgo v0.0.0-20230808203851-4c9b90563294/go.mod h1:GDZBEY7gPGjvz/G3zLFirDcgbaN3nF4nvJ157+vnEro= 7 | github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE= 8 | github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= 9 | github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= 10 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 11 | github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as= 12 | github.com/kettek/apng v0.0.0-20191108220231-414630eed80f/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q= 13 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 14 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 16 | github.com/sago35/go-bdf v0.0.0-20200313142241-6c17821c91c4/go.mod h1:rOebXGuMLsXhZAC6mF/TjxONsm45498ZyzVhel++6KM= 17 | github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= 18 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 19 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 20 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 22 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 23 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 24 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 25 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 26 | tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 27 | tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 28 | tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 29 | tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI= 30 | tinygo.org/x/drivers v0.23.0 h1:fUy4OmLOWWYCOzDp/83Qewej1Q+YgUpwkm11e7gxUc0= 31 | tinygo.org/x/drivers v0.23.0/go.mod h1:J4+51Li1kcfL5F93kmnDWEEzQF3bLGz0Am3Q7E2a8/E= 32 | tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= 33 | tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk= 34 | tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20= 35 | tinygo.org/x/tinyfs v0.2.0/go.mod h1:6ZHYdvB3sFYeMB3ypmXZCNEnFwceKc61ADYTYHpep1E= 36 | tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4= 37 | -------------------------------------------------------------------------------- /festival-camp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "machine" 6 | "time" 7 | 8 | "github.com/aykevl/ledsgo" 9 | "tinygo.org/x/drivers/ws2812" 10 | ) 11 | 12 | const NumLEDs = 30 13 | 14 | const ( 15 | ledPin = machine.GPIO29 16 | ) 17 | 18 | var leds = make([]color.RGBA, NumLEDs) 19 | 20 | var brightness uint8 = 64 21 | 22 | var palette = ledsgo.PartyColors 23 | 24 | func main() { 25 | ledPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) 26 | strip := ws2812.New(ledPin) 27 | 28 | for { 29 | // Update colors. 30 | t := uint64(time.Now().UnixNano() >> 24) 31 | noise(t, leds) 32 | 33 | // Send new colors to LEDs. 34 | strip.WriteColors(leds) 35 | time.Sleep(1 * time.Millisecond) 36 | } 37 | } 38 | 39 | func noise(t uint64, leds []color.RGBA) { 40 | for i := range leds { 41 | const spread = 8 // higher means more detail 42 | val := ledsgo.Noise2(uint32(i)<>20) 97 | y := vectors[i].Y.Int32Scaled(255) 98 | z := vectors[i].Z.Int32Scaled(255) 99 | hue := uint16(ledsgo.Noise3(x, y, z)) 100 | colors[i] = ledsgo.Color{hue, 0xff, 0x22}.Spectrum() 101 | } 102 | } 103 | 104 | // Map positive acceleration to R, G, B colors. 105 | func mpuTest1(colors ledsgo.Strip, strip drivers.LEDArray, mpu mpu6050.Device) { 106 | configureMPU(colors, strip, mpu) 107 | 108 | for { 109 | x, y, z := mpu.ReadAcceleration() 110 | var r, g, b uint8 111 | if x > 0 { 112 | r = uint8(x >> 8) 113 | } 114 | if y > 0 { 115 | g = uint8(y >> 8) 116 | } 117 | if z > 0 { 118 | b = uint8(z >> 8) 119 | } 120 | colors.FillSolid(color.RGBA{r, g, b, 0}) 121 | strip.WriteColors(colors) 122 | time.Sleep(100 * time.Millisecond) 123 | } 124 | } 125 | 126 | // Gradient sets all LEDs to a red-blue gradient across the globe. 127 | func gradient(colors ledsgo.Strip, strip drivers.LEDArray) { 128 | for { 129 | for i := 0; i < len(vectors); i++ { 130 | vec := vectors[i] 131 | const brightness = 32 132 | val := vec.X.Int32Scaled(brightness) 133 | r := uint8(brightness + val) 134 | b := uint8(brightness - val) 135 | colors[i] = color.RGBA{r, 0, b, 0} 136 | } 137 | strip.WriteColors(colors) 138 | time.Sleep(10 * time.Millisecond) 139 | } 140 | } 141 | 142 | // Rotate sets a rotating gradient across all LEDs. 143 | func rotate(colors ledsgo.Strip, strip drivers.LEDArray) { 144 | // Set up rotation 145 | inc := fixpoint.QuatQ24{fixpoint.Q24FromInt32(0), fixpoint.Vec3Q24FromFloat(0, 0, 0.005)} 146 | inc.W = fixpoint.Q24FromInt32(1).Sub(fixpoint.Q24FromFloat(0.5).Mul(inc.X().Mul(inc.X()).Add(inc.Y().Mul(inc.Y())).Add(inc.Z().Mul(inc.Z())))) 147 | 148 | // How far we've rotated so far. 149 | rot := fixpoint.QuatIdent() 150 | for cycle := 0; ; cycle++ { 151 | rot = rot.Mul(inc) 152 | for i := 0; i < len(vectors); i++ { 153 | vec := rot.Rotate(vectors[i]) 154 | const brightness = 32 155 | val := vec.X.Int32Scaled(brightness) 156 | r := uint8(brightness + val) 157 | b := uint8(brightness - val) 158 | colors[i] = color.RGBA{r, 0, b, 0} 159 | } 160 | strip.WriteColors(colors) 161 | time.Sleep(10 * time.Millisecond) 162 | } 163 | } 164 | 165 | // mpuTest2 sets a linear gradient across the globe that should be stable when 166 | // it gets rotated. 167 | func mpuTest2(colors ledsgo.Strip, strip drivers.LEDArray, mpu mpu6050.Device) { 168 | configureMPU(colors, strip, mpu) 169 | 170 | // How far we've rotated so far. 171 | orientation := fixpoint.QuatIdent() 172 | for { 173 | x, y, z := mpu.ReadRotation() 174 | 175 | // Create a rotation quaternion from the rotation we've just read. 176 | const mul = 15 177 | inc := fixpoint.QuatQ24{ 178 | fixpoint.Q24FromInt32(0), // W 179 | fixpoint.Vec3Q24{ // V 180 | fixpoint.Q24{int32(z) * mul}, // V.X 181 | fixpoint.Q24{int32(y) * mul}, // V.Y 182 | fixpoint.Q24{int32(x) * mul}, // V.Z 183 | }, 184 | } 185 | inc.W = fixpoint.Q24FromInt32(1).Sub(fixpoint.Q24FromFloat(0.5).Mul(inc.X().Mul(inc.X()).Add(inc.Y().Mul(inc.Y())).Add(inc.Z().Mul(inc.Z())))) 186 | 187 | // Update estimated orientation. 188 | orientation = orientation.Mul(inc) 189 | 190 | // Update LEDs with. 191 | for i := 0; i < len(vectors); i++ { 192 | vec := orientation.Rotate(vectors[i]) 193 | const brightness = 32 194 | val := vec.X.Int32Scaled(brightness) 195 | r := uint8(brightness + val) 196 | b := uint8(brightness - val) 197 | colors[i] = color.RGBA{r, 0, b, 0} 198 | } 199 | strip.WriteColors(colors) 200 | 201 | time.Sleep(10 * time.Millisecond) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /goggles-8ring/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aykevl/things/goggles-8ring 2 | 3 | go 1.23.1 4 | 5 | require ( 6 | github.com/aykevl/ledsgo v0.0.0-20240320160706-1580ae804c3f 7 | tinygo.org/x/drivers v0.28.0 8 | ) 9 | -------------------------------------------------------------------------------- /goggles-8ring/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da h1:u4fP3+V+W/547R/P7ezR5gH2zucphUZfQLc1ypNaXOg= 2 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da/go.mod h1:Bn4qAbkSY0k0/wrXdqQ8bWPTv/Uww3uIJuQv/knzDdQ= 3 | github.com/aykevl/ledsgo v0.0.0-20240320160706-1580ae804c3f h1:z5NoPaZqyJi/NessdnPo70a/rT8lvbt44PlsLgUBabw= 4 | github.com/aykevl/ledsgo v0.0.0-20240320160706-1580ae804c3f/go.mod h1:GDZBEY7gPGjvz/G3zLFirDcgbaN3nF4nvJ157+vnEro= 5 | github.com/kettek/apng v0.0.0-20191108220231-414630eed80f/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q= 6 | tinygo.org/x/drivers v0.28.0 h1:ROVrGGXddmpn2+oV/Bu3LceYbtPCJckmgIqvPcN/L0k= 7 | tinygo.org/x/drivers v0.28.0/go.mod h1:T6snsUqS0RAxOANxiV81fQwLxDDNmprxTAYzmxoA7J0= 8 | -------------------------------------------------------------------------------- /goggles-8ring/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "machine" 6 | "time" 7 | 8 | "github.com/aykevl/ledsgo" 9 | "tinygo.org/x/drivers/ws2812" 10 | ) 11 | 12 | const NumLEDs = 8 + 8 13 | 14 | const ( 15 | ledPin = machine.GPIO28 16 | ) 17 | 18 | var leds = make([]color.RGBA, NumLEDs) 19 | 20 | var brightness uint8 = 24 21 | 22 | var palette ledsgo.Palette16 = ledsgo.RainbowColors 23 | 24 | type Point struct { 25 | x, y int8 26 | } 27 | 28 | // Map 8 LEDs in a circle to (x, y) coordinates. 29 | var mapping = [...]Point{ 30 | {127, 0}, 31 | {90, 90}, 32 | {0, 127}, 33 | {-90, 90}, 34 | {-127, 0}, 35 | {-90, -90}, 36 | {0, -127}, 37 | {90, -90}, 38 | } 39 | 40 | func main() { 41 | ledPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) 42 | strip := ws2812.New(ledPin) 43 | 44 | for { 45 | // Update colors. 46 | now := time.Now() 47 | t := uint64(now.UnixNano() >> 22) 48 | noise(t, leds) 49 | 50 | // Send new colors to LEDs. 51 | strip.WriteColors(leds) 52 | time.Sleep(10 * time.Millisecond) 53 | } 54 | } 55 | 56 | func noise(t uint64, leds []color.RGBA) { 57 | for i := range leds { 58 | x := (int(mapping[i%8].x) + 127) * 4 59 | y := (int(mapping[i%8].y) + 127) * 4 60 | val := ledsgo.Noise3(uint32(x)+(uint32(i)/8)<<16, uint32(y), uint32(t)) 61 | c := palette.ColorAt(val) 62 | c.A = 0 // the alpha channel is used as white channel, so don't use it 63 | leds[i] = ledsgo.ApplyAlpha(c, brightness) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /hub75/driver_nrf52.go: -------------------------------------------------------------------------------- 1 | // +build nrf52 2 | 3 | package hub75 4 | 5 | import ( 6 | "device/arm" 7 | "device/nrf" 8 | "machine" 9 | "unsafe" 10 | ) 11 | 12 | type chipSpecificSettings struct { 13 | bus *nrf.SPIM_Type 14 | timer *nrf.TIMER_Type 15 | gpioteChannel uint8 // GPIOTE channel used for the Output Enable pin. 16 | ppiChannel0 uint8 // PPI channel used to turn the screen on. 17 | ppiChannel1 uint8 // PPI channel used to turn the screen off. 18 | } 19 | 20 | func (d *Device) configureChip(dataPin, clockPin machine.Pin) { 21 | // Set some properties that are chip-specific. 22 | d.bus = nrf.SPIM0 23 | d.timer = nrf.TIMER0 24 | d.gpioteChannel = 0 25 | d.ppiChannel0 = 0 26 | d.ppiChannel1 = 1 27 | 28 | // Configure the SPI bus. 29 | d.bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Disabled) 30 | d.bus.FREQUENCY.Set(nrf.SPIM_FREQUENCY_FREQUENCY_M8) 31 | d.bus.CONFIG.Set(0) // default config 32 | d.bus.PSEL.MISO.Set(0xffffffff) 33 | d.bus.PSEL.MOSI.Set(uint32(dataPin)) 34 | d.bus.PSEL.SCK.Set(uint32(clockPin)) 35 | d.bus.INTENSET.Set(nrf.SPIM_INTENSET_ENDTX_Msk) 36 | d.bus.ENABLE.Set(nrf.SPIM_ENABLE_ENABLE_Enabled) 37 | 38 | // Configure the SPI interrupt handler to the highest priority. 39 | // It doesn't need to be the highest, but it should be the same priority as 40 | // the timer. 41 | arm.SetPriority(nrf.IRQ_SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0, 0x00) 42 | arm.EnableIRQ(nrf.IRQ_SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0) 43 | d.bus.TXD.LIST.Set(nrf.SPIM_TXD_LIST_LIST_ArrayList) 44 | d.bus.RXD.MAXCNT.Set(0) 45 | 46 | // Configure the timer interrupt handler at the highest priority. 47 | // Also, make sure that when the timer fires, it automatically stops itself. 48 | arm.SetPriority(nrf.IRQ_TIMER0, 0x00) 49 | arm.EnableIRQ(nrf.IRQ_TIMER0) 50 | d.timer.PRESCALER.Set(0) 51 | d.timer.SHORTS.SetBits(nrf.TIMER_SHORTS_COMPARE1_CLEAR | nrf.TIMER_SHORTS_COMPARE1_STOP) 52 | d.timer.CC[0].Set(1) 53 | d.timer.INTENSET.Set(nrf.TIMER_INTENSET_COMPARE1) 54 | 55 | // Configure a GPIOTE channel. 56 | nrf.GPIOTE.CONFIG[d.gpioteChannel].Set( 57 | (nrf.GPIOTE_CONFIG_MODE_Task << nrf.GPIOTE_CONFIG_MODE_Pos) | 58 | (uint32(d.oe) << nrf.GPIOTE_CONFIG_PSEL_Pos) | 59 | (nrf.GPIOTE_CONFIG_POLARITY_None << nrf.GPIOTE_CONFIG_POLARITY_Pos) | 60 | (nrf.GPIOTE_CONFIG_OUTINIT_High << nrf.GPIOTE_CONFIG_OUTINIT_Pos)) 61 | 62 | // Set up the PPI channels. 63 | // We use one channel (ppiChannel0 with CC[0]) to turn the screen on, and 64 | // another (ppiChannel1 with CC[1]) to turn it off again. 65 | nrf.PPI.CHENSET.Set(1 << d.ppiChannel0) 66 | nrf.PPI.CH[d.ppiChannel0].EEP.Set(uint32(uintptr(unsafe.Pointer(&d.timer.EVENTS_COMPARE[0])))) 67 | nrf.PPI.CH[d.ppiChannel0].TEP.Set(uint32(uintptr(unsafe.Pointer(&nrf.GPIOTE.TASKS_CLR[d.gpioteChannel])))) 68 | nrf.PPI.CHENSET.Set(1 << d.ppiChannel1) 69 | nrf.PPI.CH[d.ppiChannel1].EEP.Set(uint32(uintptr(unsafe.Pointer(&d.timer.EVENTS_COMPARE[1])))) 70 | nrf.PPI.CH[d.ppiChannel1].TEP.Set(uint32(uintptr(unsafe.Pointer(&nrf.GPIOTE.TASKS_SET[d.gpioteChannel])))) 71 | } 72 | 73 | // startTransfer starts the SPI transaction to send the next row to the screen. 74 | func (d *Device) startTransfer() { 75 | bitstring := d.displayBitstrings[d.row][d.colorBit] 76 | d.bus.TXD.MAXCNT.Set(uint32(len(bitstring))) 77 | d.bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&bitstring[0])))) 78 | d.bus.TASKS_START.Set(1) 79 | } 80 | 81 | // startOutputEnableTimer will enable and disable the screen for a very short 82 | // time, depending on which bit is currently enabled. 83 | func (d *Device) startOutputEnableTimer() { 84 | // Turn the screen on for the specified time. 85 | // Note that it is actually turned on at T+1 (by a PPI on CC[0]) so we also 86 | // have to add one to the time to get the correct turn-on time. 87 | d.timer.CC[1].Set(d.brightness<>speed), int32(x<>20)*speed), int32(x*detail))/256 + 128 53 | heat -= int16(y) * cooling 54 | if heat < 0 { 55 | heat = 0 56 | } 57 | display.SetPixel(x, y, heatMap(uint8(heat))) 58 | } 59 | } 60 | } 61 | 62 | func heatMap(index uint8) color.RGBA { 63 | if index < 128 { 64 | return color.RGBA{index * 2, 0, 0, 255} 65 | } 66 | if index < 224 { 67 | return color.RGBA{255, uint8(uint32(index-128) * 8 / 3), 0, 255} 68 | } 69 | return color.RGBA{255, 255, (index - 224) * 8, 255} 70 | } 71 | -------------------------------------------------------------------------------- /hub75/examples/patterns/pca10040.go: -------------------------------------------------------------------------------- 1 | // +build pca10040 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/aykevl/things/hub75" 7 | ) 8 | 9 | var display = hub75.New(hub75.Config{ 10 | Data: 22, 11 | Clock: 23, 12 | Latch: 24, 13 | OutputEnable: 25, 14 | A: 20, 15 | B: 19, 16 | C: 18, 17 | D: 17, 18 | }) 19 | -------------------------------------------------------------------------------- /ledcube/README.md: -------------------------------------------------------------------------------- 1 | # LED cube 2 | 3 | This is code for a LED cube, inspired by a LED cube I've seen at 4 | [CCC](https://en.wikipedia.org/wiki/Chaos_Communication_Congress) and the 5 | [SquareWave Dot](https://squarewave.io/) cube. 6 | 7 | Hardware: 8 | 9 | * 6 32x32 hub75 panels, for example [these](https://www.aliexpress.com/item/33023473536.html). 10 | * [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800). 11 | * A power source, for example a USB power bank. 12 | * Something to make it a cube, for example wood (you can remove the plastic 13 | frame and use the small screws that remain). 14 | * Some wires and stuff to connect things internally. 15 | -------------------------------------------------------------------------------- /ledcube/baremetal.go: -------------------------------------------------------------------------------- 1 | // +build baremetal 2 | 3 | package main 4 | 5 | func getFullRefreshes() uint { 6 | return display.FullRefreshes() 7 | } 8 | -------------------------------------------------------------------------------- /ledcube/itsybitsy-m4.go: -------------------------------------------------------------------------------- 1 | // +build itsybitsy_m4 2 | 3 | package main 4 | 5 | import ( 6 | "machine" 7 | 8 | "github.com/aykevl/things/hub75" 9 | ) 10 | 11 | var display = hub75.New(hub75.Config{ 12 | Data: machine.NoPin, 13 | Clock: machine.NoPin, 14 | Latch: machine.D5, 15 | OutputEnable: machine.D7, 16 | A: machine.D9, 17 | B: machine.D10, 18 | C: machine.D11, 19 | D: machine.D12, 20 | NumScreens: 6, 21 | Brightness: 1, 22 | }) 23 | -------------------------------------------------------------------------------- /ledcube/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "time" 6 | 7 | "github.com/aykevl/ledsgo" 8 | ) 9 | 10 | const ( 11 | size = 32 12 | ) 13 | 14 | func main() { 15 | fullRefreshes := uint(0) 16 | previousSecond := int64(0) 17 | //demo := colorCoordinateAt 18 | demo := noiseAt 19 | //demo := fireAt 20 | //demo := radiance 21 | //demo := hyperspace 22 | for { 23 | start := time.Now() 24 | drawPixels(start, demo) 25 | display.Display() 26 | 27 | second := (start.UnixNano() / int64(time.Second)) 28 | if second != previousSecond { 29 | previousSecond = second 30 | newFullRefreshes := getFullRefreshes() 31 | animationTime := time.Since(start) 32 | animationFPS := int64(10 * time.Second / animationTime) 33 | print("#", second, " screen=", newFullRefreshes-fullRefreshes, "fps animation=", animationTime.String(), "/", (animationFPS / 10), ".", animationFPS%10, "fps\r\n") 34 | fullRefreshes = newFullRefreshes 35 | } 36 | } 37 | } 38 | 39 | // drawPixels updates every pixel on the cube by calling getColor for each pixel 40 | // and drawing it to the screen. It maps virtual (3D) pixels to physical (2D) 41 | // pixels in the process. 42 | func drawPixels(t time.Time, getColor func(x, y, z int, t time.Time) color.RGBA) { 43 | // Somewhat arbitrarily picking the top left of the topmost panel as the (0, 44 | // 0, 31) of the 3D cube. 45 | for x := 0; x < size; x++ { 46 | for y := 0; y < size; y++ { 47 | display.SetPixel(int16(x+size*5), int16(y), getColor(x+1, y+1, 0, t)) 48 | display.SetPixel(int16(x+size*4), int16(y), getColor(0, x+1, y+1, t)) 49 | display.SetPixel(int16(x+size*3), int16(y), getColor(size-x, 0, y+1, t)) 50 | display.SetPixel(int16(x+size*2), int16(y), getColor(size+1, size-x, y+1, t)) 51 | display.SetPixel(int16(x+size*1), int16(y), getColor(x+1, size+1, y+1, t)) 52 | display.SetPixel(int16(x+size*0), int16(y), getColor(x+1, size-y, size+1, t)) 53 | } 54 | } 55 | } 56 | 57 | // noiseAt returns noise at the specified location. 58 | func noiseAt(x, y, z int, t time.Time) color.RGBA { 59 | const ( 60 | spread = 4096 / size // higher means the noise gets more detailed 61 | speed = 20 // higher means slower 62 | ) 63 | hue := uint16(ledsgo.Noise4(int32(t.UnixNano()>>speed), int32(x*spread), int32(y*spread), int32(z*spread))) * 2 64 | return ledsgo.Color{hue, 0xff, 0xff}.Spectrum() 65 | } 66 | 67 | // fireAt returns fire at the specified location. 68 | func fireAt(x, y, z int, t time.Time) color.RGBA { 69 | const pointsPerCircle = 12 // how many LEDs there are per turn of the torch 70 | const cooling = 1792 / size // higher means faster cooling 71 | const detail = 12800 / size // higher means more detailed flames 72 | const speed = 12 // higher means faster 73 | const screenHeight = size + 1 74 | if z == 0 { 75 | return color.RGBA{} 76 | } 77 | heat := ledsgo.Noise3(int32((31-z)*detail)-int32((t.UnixNano()>>20)*speed), int32(x*detail), int32(y*detail))/32 + (128 * 8) 78 | heat -= int16(screenHeight-z) * cooling 79 | if heat < 0 { 80 | heat = 0 81 | } 82 | return heatMap(heat) 83 | } 84 | 85 | // heatMap maps a color in the range 0..2047 to a color in a heat index. Useful 86 | // for making flames. 87 | func heatMap(index int16) color.RGBA { 88 | if index < 128*8 { 89 | // red only 90 | return color.RGBA{uint8(index / 4), 0, 0, 255} 91 | } 92 | if index < 224*8 { 93 | // red-yellow 94 | return color.RGBA{255, uint8(uint32(index-128*8) / 3), 0, 255} 95 | } 96 | // yellow-white 97 | return color.RGBA{255, 255, (uint8(index - 224*8)), 255} 98 | } 99 | 100 | // colorCoordinateAt returns a color based on the 3 coordinates given. Useful 101 | // for getting the virtual->physical pixel mapping right. 102 | func colorCoordinateAt(x, y, z int, t time.Time) color.RGBA { 103 | // X represents red (more red to the right) 104 | // Y represents green (more green to the bottom) 105 | // Z represents blue (more blue to the bottom) 106 | return color.RGBA{uint8(x * 255 / (size + 1)), uint8(y * 255 / (size + 1)), uint8(z * 255 / (size + 1)), 0xff} 107 | } 108 | 109 | // radiance shows colors radiating out of the center. 110 | func radiance(x, y, z int, now time.Time) color.RGBA { 111 | const circleX = 33 / 2 * 256 112 | const circleY = 33 / 2 * 256 113 | px := (x * (8192 / size)) - 4224 // .8 114 | py := (y * (8192 / size)) - 4224 // .8 115 | distance := ledsgo.Sqrt((px*px + py*py)) // .8 116 | hue := uint16(ledsgo.Noise1(int32(distance>>0)-int32(now.UnixNano()>>18))) + 0x8000 117 | return ledsgo.Color{hue, 0xff, 0xff}.Spectrum() 118 | } 119 | 120 | // hyperspace is a demo that should look a little bit like a hyperspace scene in 121 | // a sci-fi movie. 122 | func hyperspace(x, y, z int, now time.Time) color.RGBA { 123 | const circleX = (size/2 + 0.5) * 256 124 | const circleY = (size/2 + 0.5) * 256 125 | const cylinderRadius = 50 * 256 // higher number means more complexity 126 | // Calculate distance from the circle center. 127 | px := (x << 8) - circleX // .8 128 | py := (y << 8) - circleY // .8 129 | distance := ledsgo.Sqrt((px*px + py*py)) // .8 130 | 131 | // Normalize this distance. 132 | px = px * cylinderRadius / distance 133 | py = py * cylinderRadius / distance 134 | 135 | // Now the tricky part. Imagine this is a cyliner with px and py on 136 | // the outer circle of the cylinder. The cylinder moves through 3D 137 | // space in the direction of one of the flat sides of the cylinder 138 | // (the third coordinate). 139 | alpha := int(ledsgo.Noise3(int32(px), int32(py), int32(distance/4)-int32(now.UnixNano()>>16))) 140 | alpha -= 10000 141 | if alpha < 0 { 142 | alpha = 0 143 | } 144 | c := color.RGBA{0xaa, 0xaa, 0xff, 0xff} 145 | c = ledsgo.ApplyAlpha(c, uint8(alpha/256)) 146 | return c 147 | } 148 | -------------------------------------------------------------------------------- /ledcube/unix.go: -------------------------------------------------------------------------------- 1 | // +build !baremetal 2 | 3 | package main 4 | 5 | import ( 6 | "log" 7 | 8 | "github.com/aykevl/tilegraphics/sdlscreen" 9 | ) 10 | 11 | var display *sdlscreen.Screen 12 | 13 | func init() { 14 | var err error 15 | const scale = 192 / size 16 | display, err = sdlscreen.NewScreen("LED cube", size*6*scale, size*scale) 17 | if err != nil { 18 | log.Fatalln("could not instantiate screen:", err) 19 | } 20 | display.Scale = scale 21 | } 22 | 23 | func getFullRefreshes() uint { 24 | return 0 25 | } 26 | -------------------------------------------------------------------------------- /mch2022-leds/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Ayke van Laethem. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of the copyright holder nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /mch2022-leds/README.md: -------------------------------------------------------------------------------- 1 | # TinyGo example for the MCH2022 badge 2 | 3 | This is a very simple example how you can write a program for the MCH2022 badge using TinyGo. 4 | 5 | Compile it using the following command: 6 | 7 | tinygo build -o main.bin -target=mch2022 8 | 9 | This creates a `main.bin` file, which is ready to be sent to the badge. You can do it like this: 10 | 11 | python3 mch2022-template-app/tools/webusb_push.py "TinyGo LED example" main.bin --run 12 | 13 | You can find the file `webusb_push.py` [here](https://github.com/badgeteam/mch2022-tools/blob/bef7edfe709f89d9d601de7dde61b31fe5317854/webusb_push.py). 14 | -------------------------------------------------------------------------------- /mch2022-leds/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aykevl/things/mch2022-leds 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 7 | tinygo.org/x/drivers v0.21.0 8 | ) 9 | -------------------------------------------------------------------------------- /mch2022-leds/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da h1:u4fP3+V+W/547R/P7ezR5gH2zucphUZfQLc1ypNaXOg= 2 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da/go.mod h1:Bn4qAbkSY0k0/wrXdqQ8bWPTv/Uww3uIJuQv/knzDdQ= 3 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 h1:NcmN0bjs7hmLKSARz88oxXF6OU50/d4Xeo5QWEE5y7w= 4 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2/go.mod h1:GDZBEY7gPGjvz/G3zLFirDcgbaN3nF4nvJ157+vnEro= 5 | github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE= 6 | github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= 7 | github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= 8 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 9 | github.com/kettek/apng v0.0.0-20191108220231-414630eed80f/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q= 10 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 11 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 12 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 13 | github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= 14 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 15 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 16 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 17 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 18 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 19 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 20 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 21 | tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 22 | tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 23 | tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 24 | tinygo.org/x/drivers v0.21.0 h1:4jasPGeHEo+clHqU2bothSYbmncc+u8nN0VMAmJ7IRg= 25 | tinygo.org/x/drivers v0.21.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI= 26 | tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= 27 | tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20= 28 | tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4= 29 | -------------------------------------------------------------------------------- /mch2022-leds/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "machine" 6 | "time" 7 | 8 | "github.com/aykevl/ledsgo" 9 | "tinygo.org/x/drivers/ws2812" 10 | ) 11 | 12 | func main() { 13 | // Enable power to the LEDs 14 | power := machine.PowerOn 15 | power.Configure(machine.PinConfig{Mode: machine.PinOutput}) 16 | power.High() 17 | 18 | // Initialize the data pin. 19 | led := machine.WS2812 20 | led.Configure(machine.PinConfig{Mode: machine.PinOutput}) 21 | 22 | // Prepare the LED data slice to send. 23 | ws := ws2812.New(led) 24 | leds := make([]color.RGBA, 5) 25 | 26 | // Update the data each cycle. 27 | for { 28 | now := time.Now() 29 | for i := range leds { 30 | leds[i] = ledsgo.Color{ 31 | H: uint16(i)*4096 + uint16(now.UnixNano()>>17), 32 | S: 255, 33 | V: 64, 34 | }.Rainbow() 35 | } 36 | ws.WriteColors(leds) 37 | time.Sleep(time.Second / 60) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mch2022-noise/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aykevl/things/mch2022-example 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/aykevl/board v0.0.0-20230406201915-c56eb14f8f81 7 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 8 | github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 9 | github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20150408091349-4742878d9c90 10 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e 11 | ) 12 | 13 | require ( 14 | github.com/aykevl/tinygl v0.0.0-20230401131404-081aaf7cd876 // indirect 15 | github.com/veandco/go-sdl2 v0.4.34 // indirect 16 | golang.org/x/text v0.3.6 // indirect 17 | tinygo.org/x/drivers v0.24.1-0.20230329221936-55e100c4fe08 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /mch2022-noise/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da h1:u4fP3+V+W/547R/P7ezR5gH2zucphUZfQLc1ypNaXOg= 2 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da/go.mod h1:Bn4qAbkSY0k0/wrXdqQ8bWPTv/Uww3uIJuQv/knzDdQ= 3 | github.com/aykevl/board v0.0.0-20230406201915-c56eb14f8f81 h1:BEdZ/T19J90L61hUIL2O7BqageoWzdpcBttF0BtDYk8= 4 | github.com/aykevl/board v0.0.0-20230406201915-c56eb14f8f81/go.mod h1:ppTJfl3SyndM5Blrz6sdueY7gulVGG5tBi+FR1c26Lw= 5 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 h1:NcmN0bjs7hmLKSARz88oxXF6OU50/d4Xeo5QWEE5y7w= 6 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2/go.mod h1:GDZBEY7gPGjvz/G3zLFirDcgbaN3nF4nvJ157+vnEro= 7 | github.com/aykevl/tinygl v0.0.0-20230401131404-081aaf7cd876 h1:JnwnDpTkwFkiLFuN1RwRCIb61PRY619HB1RmvrsQT00= 8 | github.com/aykevl/tinygl v0.0.0-20230401131404-081aaf7cd876/go.mod h1:LUcIGll0cn5VSybiZ2nIXRNpl7IJ0lcnxOnCbzGxVcU= 9 | github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE= 10 | github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746 h1:wAIE/kN63Oig1DdOzN7O+k4AbFh2cCJoKMFXrwRJtzk= 11 | github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= 12 | github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= 13 | github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= 14 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 15 | github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as= 16 | github.com/kettek/apng v0.0.0-20191108220231-414630eed80f/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q= 17 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 18 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 19 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 20 | github.com/sago35/go-bdf v0.0.0-20200313142241-6c17821c91c4/go.mod h1:rOebXGuMLsXhZAC6mF/TjxONsm45498ZyzVhel++6KM= 21 | github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20150408091349-4742878d9c90 h1:ccVm9C6f5YMcVv6t9MXahIDkqVvzD6vklkJTIE4D2nY= 22 | github.com/tatsuhiro-t/go-nghttp2 v0.0.0-20150408091349-4742878d9c90/go.mod h1:YZhsh86DfZgAShPKeg1eBLVrmuQxWcR9H4TdpgNvSnw= 23 | github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= 24 | github.com/veandco/go-sdl2 v0.4.34 h1:dqbUhV/SSJ35grdYTLv3iVxtO1NzNmgzMV//hyCyypY= 25 | github.com/veandco/go-sdl2 v0.4.34/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= 26 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 27 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= 28 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 29 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 31 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 32 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 33 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 34 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 35 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 36 | tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 37 | tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 38 | tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 39 | tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI= 40 | tinygo.org/x/drivers v0.24.1-0.20230329221936-55e100c4fe08 h1:bId84LAT/LdQSpw2C91og4qNmN5VAPz4scB17badxaA= 41 | tinygo.org/x/drivers v0.24.1-0.20230329221936-55e100c4fe08/go.mod h1:J4+51Li1kcfL5F93kmnDWEEzQF3bLGz0Am3Q7E2a8/E= 42 | tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= 43 | tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk= 44 | tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20= 45 | tinygo.org/x/tinyfs v0.2.0/go.mod h1:6ZHYdvB3sFYeMB3ypmXZCNEnFwceKc61ADYTYHpep1E= 46 | tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4= 47 | -------------------------------------------------------------------------------- /mch2022-noise/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/mch2022-noise/icon.png -------------------------------------------------------------------------------- /mch2022-noise/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aykevl/board" 7 | "github.com/aykevl/ledsgo" 8 | ) 9 | 10 | const ( 11 | width = 320 12 | height = 240 13 | ) 14 | 15 | func main() { 16 | println("starting...") 17 | 18 | display := board.Display.Configure() 19 | 20 | const size = 4 21 | buffer := make([]uint8, width*height*2) 22 | for cycle := 0; ; cycle++ { 23 | start := time.Now() 24 | for y := 0; y < height; y += size { 25 | for x := 0; x < width; x += size { 26 | value := ledsgo.Noise3( 27 | uint32(x)<<4, 28 | uint32(y)<<4, 29 | uint32(start.UnixNano()>>21)) 30 | c := ledsgo.PartyColors.ColorAt(value * 2) 31 | raw1, raw2 := makeColor(c.R, c.G, c.B) 32 | for x2 := x; x2 < x+size; x2++ { 33 | for y2 := y; y2 < y+size; y2++ { 34 | buffer[(y2*width+x2)*2+0] = raw1 35 | buffer[(y2*width+x2)*2+1] = raw2 36 | } 37 | } 38 | } 39 | } 40 | draw := time.Now() 41 | display.DrawRGBBitmap8(0, 0, buffer, width, height) 42 | if cycle%16 == 0 { 43 | end := time.Now() 44 | println("frame update:", draw.Sub(start).String()) 45 | println("frame draw: ", end.Sub(draw).String()) 46 | println("frame: ", end.Sub(start).String(), time.Second/end.Sub(start)) 47 | } 48 | } 49 | } 50 | 51 | func handleError(err error) { 52 | if err != nil { 53 | for { 54 | println("error:", err) 55 | time.Sleep(time.Second) 56 | } 57 | } 58 | } 59 | 60 | func makeColor(r, g, b uint8) (uint8, uint8) { 61 | r = gamma8[r] 62 | g = gamma8[g] 63 | b = gamma8[b] 64 | c := uint16(r&0xF8)<<8 + 65 | uint16(g&0xFC)<<3 + 66 | uint16(b&0xF8)>>3 67 | return uint8(c >> 8), uint8(c) 68 | } 69 | 70 | // Gamma brightness lookup table 71 | // gamma = 0.40 steps = 256 range = 0-255 72 | var gamma8 = [...]uint8{ 73 | 0, 28, 37, 43, 48, 53, 57, 61, 64, 67, 70, 73, 75, 78, 80, 82, 74 | 84, 86, 88, 90, 92, 94, 96, 97, 99, 101, 102, 104, 105, 107, 108, 110, 75 | 111, 113, 114, 115, 117, 118, 119, 120, 122, 123, 124, 125, 126, 127, 129, 130, 76 | 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 77 | 147, 148, 149, 149, 150, 151, 152, 153, 154, 155, 155, 156, 157, 158, 159, 160, 78 | 160, 161, 162, 163, 164, 164, 165, 166, 167, 167, 168, 169, 170, 170, 171, 172, 79 | 173, 173, 174, 175, 175, 176, 177, 177, 178, 179, 179, 180, 181, 182, 182, 183, 80 | 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 190, 191, 192, 192, 193, 81 | 194, 194, 195, 195, 196, 197, 197, 198, 198, 199, 199, 200, 201, 201, 202, 202, 82 | 203, 203, 204, 205, 205, 206, 206, 207, 207, 208, 208, 209, 209, 210, 211, 211, 83 | 212, 212, 213, 213, 214, 214, 215, 215, 216, 216, 217, 217, 218, 218, 219, 219, 84 | 220, 220, 221, 221, 222, 222, 223, 223, 224, 224, 225, 225, 226, 226, 227, 227, 85 | 228, 228, 229, 229, 230, 230, 230, 231, 231, 232, 232, 233, 233, 234, 234, 235, 86 | 235, 235, 236, 236, 237, 237, 238, 238, 239, 239, 240, 240, 240, 241, 241, 242, 87 | 242, 243, 243, 243, 244, 244, 245, 245, 246, 246, 246, 247, 247, 248, 248, 248, 88 | 249, 249, 250, 250, 251, 251, 251, 252, 252, 253, 253, 253, 254, 254, 255, 255, 89 | } 90 | -------------------------------------------------------------------------------- /poi/Makefile: -------------------------------------------------------------------------------- 1 | FLAGS = -target=pca10040-s132v6 -size=short -opt=2 -serial=none -programmer=cmsis-dap 2 | 3 | v2-red: 4 | tinygo flash -tags='v2 red' $(FLAGS) . 5 | 6 | v2-blue: 7 | tinygo flash -tags='v2 blue' $(FLAGS) . 8 | -------------------------------------------------------------------------------- /poi/README.md: -------------------------------------------------------------------------------- 1 | # LED poi 2 | 3 | This is some code for a [poi](https://en.wikipedia.org/wiki/Poi_(performance_art)). You can see a video in [this tweet](https://twitter.com/aykevl/status/1280135326913232896). [This](https://www.youtube.com/watch?v=LGy0neDXxAE0) is a professionally made video of a commercially available variant. 4 | 5 | As of this writing, I've built three types of poi, all with Nordic chips inside (loosely based on [this build](http://orchardelica.com/wp/?page_id=597)): 6 | 7 | * The first one with a GT82C_02 board inside with a nRF51822 chip. 8 | * The second one with a GT832E_01 board inside with a nRF52832 chip. 9 | * The third one with a GT832C_01 board inside. It is similar to the GT832E_01 except that it also contains a BMI160 accelerometer/gyroscope and is slightly smaller. 10 | 11 | I quite like these boards. They're easy to solder with 2.54mm or 2.00mm distance between outside connections, are very small, and only require an external voltage regulator to use them on a lithium ion battery. 12 | 13 | The electronics of the third type (which I might stick with, it works well) are the following: 14 | 15 | * [GT832C_01](https://www.aliexpress.com/item/4000123520442.html) module (nRF52832 with BMI160). 16 | * Microchip MPC1702 3.3V regulator, see note 1 below. 17 | * Fairchild FQP30N06L MOSFET to turn the LED strips on and off. Bought from AliExpress so no idea whether it was genuine. 18 | * 1m 144 LED SK9822 LED strip, see note 2 below. Each side uses a quarter (25cm or 36 LEDs) of the strip. Both sides are connected in parallel. 19 | * Protected 14500 Li-ion battery: [this one](https://eu.nkon.nl/keeppower-16588.html). Protected so that an accidental short won't blow up the battery. 20 | * AA battery holder. Be warned that not all AA battery holders can fit protected Li-ion batteries because they are slightly longer than regular AA batteries. Battery holders that are 57mm long on the outside seem to be long enough usually. 21 | 22 | Note 1: you can in principle use any 3.3V voltage regulator, but to be usable with a lithium battery (li-ion or li-poly), it has to be one with a low drop out rate. I tried an L78L33 from STMicroelectronics but the dropout voltage was too big to be usable: the LED strips started misbehaving when the battery was still at 3.85V (nearly full). Be [especially careful](https://hackaday.com/2018/11/10/what-good-are-counterfeit-parts-believe-it-or-not-maybe-a-refund/) with 3.3V voltage regulators from AliExpress. They might work well to drop from 5V to 3.3V, but may have a too large dropout rate for a clean signal between the MCU and the LED strip. 23 | 24 | Note 2: use [SK9822 LED strips](https://cpldcpu.wordpress.com/2016/12/13/sk9822-a-clone-of-the-apa102/). Not APA102 because it [does not support flicker-free brightness control](https://cpldcpu.wordpress.com/2014/08/27/apa102/) and not WS2812 because it is a different protocol and it is not suitable for persistence of vision applications (it will cause tons of flickering). SK9822 is a near-clone of APA102 but the brightness control improvement is actually very important for this electronics project. 25 | 26 | Note 3: the GT832C_01 board seems to be memory protected from the factory. Perhaps it has some firmware installed on it by default, I haven't checked whether it does anything. See [this post](https://devzone.nordicsemi.com/f/nordic-q-a/17015/how-do-i-disable-control-access-port-protection-on-nrf52-using-openocd) for how to remove the protection to make it programmable. 27 | 28 | You can control the poi from [here](https://aykevl.nl/apps/poi/), for example. It's just Bluetooth Low Energy, and it's also possible to control it using nRF Connect ([Android version](https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp)). 29 | -------------------------------------------------------------------------------- /poi/blue.go: -------------------------------------------------------------------------------- 1 | // +build v2,blue 2 | 3 | package main 4 | 5 | import "image/color" 6 | 7 | // Appearance configuration. 8 | var ( 9 | baseColor = color.RGBA{0, 0, 0xff, 0x08} 10 | bluetoothName = "blue poi" 11 | ) 12 | -------------------------------------------------------------------------------- /poi/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aykevl/things/poi 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 7 | github.com/spaolacci/murmur3 v1.1.0 8 | tinygo.org/x/bluetooth v0.5.0 9 | tinygo.org/x/drivers v0.20.0 10 | ) 11 | 12 | require ( 13 | github.com/JuulLabs-OSS/cbgo v0.0.2 // indirect 14 | github.com/fatih/structs v1.1.0 // indirect 15 | github.com/go-ole/go-ole v1.2.6 // indirect 16 | github.com/godbus/dbus/v5 v5.1.0 // indirect 17 | github.com/muka/go-bluetooth v0.0.0-20220604035144-0b043d86dc03 // indirect 18 | github.com/sirupsen/logrus v1.8.1 // indirect 19 | golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /poi/nrf.go: -------------------------------------------------------------------------------- 1 | // +build nrf 2 | 3 | package main 4 | 5 | import ( 6 | "time" 7 | 8 | "tinygo.org/x/bluetooth" 9 | ) 10 | 11 | var ( 12 | serviceUUID = bluetooth.NewUUID([16]byte{0xd8, 0x65, 0x00, 0x01, 0x90, 0xc8, 0x46, 0x7a, 0xb3, 0xd2, 0xe4, 0xc1, 0x8a, 0x61, 0x10, 0x7f}) 13 | animationUUID = bluetooth.NewUUID([16]byte{0xd8, 0x65, 0x00, 0x02, 0x90, 0xc8, 0x46, 0x7a, 0xb3, 0xd2, 0xe4, 0xc1, 0x8a, 0x61, 0x10, 0x7f}) 14 | speedUUID = bluetooth.NewUUID([16]byte{0xd8, 0x65, 0x00, 0x03, 0x90, 0xc8, 0x46, 0x7a, 0xb3, 0xd2, 0xe4, 0xc1, 0x8a, 0x61, 0x10, 0x7f}) 15 | brightnessUUID = bluetooth.NewUUID([16]byte{0xd8, 0x65, 0x00, 0x04, 0x90, 0xc8, 0x46, 0x7a, 0xb3, 0xd2, 0xe4, 0xc1, 0x8a, 0x61, 0x10, 0x7f}) 16 | colorUUID = bluetooth.NewUUID([16]byte{0xd8, 0x65, 0x00, 0x05, 0x90, 0xc8, 0x46, 0x7a, 0xb3, 0xd2, 0xe4, 0xc1, 0x8a, 0x61, 0x10, 0x7f}) 17 | ) 18 | 19 | func initHardware() { 20 | if debug { 21 | println("init hardware") 22 | } 23 | // Initialize the adapter. 24 | adapter := bluetooth.DefaultAdapter 25 | err := adapter.Enable() 26 | if err != nil { 27 | println("could not enable:", err.Error()) 28 | return 29 | } 30 | 31 | // Start advertising. 32 | adv := adapter.DefaultAdvertisement() 33 | err = adv.Configure(bluetooth.AdvertisementOptions{ 34 | LocalName: bluetoothName, 35 | ServiceUUIDs: []bluetooth.UUID{serviceUUID}, 36 | Interval: bluetooth.NewDuration(760 * time.Millisecond), 37 | }) 38 | if err != nil { 39 | println("could not configure advertisement:", err.Error()) 40 | return 41 | } 42 | err = adv.Start() 43 | if err != nil { 44 | println("could not start advertisement:", err.Error()) 45 | return 46 | } 47 | 48 | err = adapter.AddService(&bluetooth.Service{ 49 | UUID: serviceUUID, 50 | Characteristics: []bluetooth.CharacteristicConfig{ 51 | { 52 | UUID: animationUUID, 53 | Value: []byte{animationIndex}, 54 | Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission, 55 | WriteEvent: func(client bluetooth.Connection, offset int, value []byte) { 56 | if offset != 0 || len(value) < 1 { 57 | return 58 | } 59 | if int(value[0]) < len(animations) { 60 | animationIndex = value[0] 61 | if debug { 62 | println("animation is now:", animationIndex) 63 | } 64 | } 65 | }, 66 | }, 67 | { 68 | UUID: speedUUID, 69 | Value: []byte{speed}, 70 | Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission, 71 | WriteEvent: func(client bluetooth.Connection, offset int, value []byte) { 72 | if offset != 0 || len(value) < 1 { 73 | return 74 | } 75 | speed = value[0] 76 | if debug { 77 | println("speed is now:", speed) 78 | } 79 | }, 80 | }, 81 | { 82 | UUID: brightnessUUID, 83 | Value: []byte{baseColor.A}, 84 | Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission, 85 | WriteEvent: func(client bluetooth.Connection, offset int, value []byte) { 86 | if offset != 0 || len(value) < 1 { 87 | return 88 | } 89 | baseColor.A = value[0] 90 | if debug { 91 | println("brightness is now:", baseColor.A) 92 | } 93 | }, 94 | }, 95 | { 96 | UUID: colorUUID, 97 | Value: []byte{baseColor.R, baseColor.G, baseColor.B}, 98 | Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission, 99 | WriteEvent: func(client bluetooth.Connection, offset int, value []byte) { 100 | if offset != 0 || len(value) < 3 { 101 | return 102 | } 103 | baseColor.R = value[0] 104 | baseColor.G = value[1] 105 | baseColor.B = value[2] 106 | if debug { 107 | println("updated color") 108 | } 109 | }, 110 | }, 111 | }, 112 | }) 113 | if err != nil { 114 | println("could not add poi service:", err.Error()) 115 | return 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /poi/red.go: -------------------------------------------------------------------------------- 1 | // +build v2,red 2 | 3 | package main 4 | 5 | import "image/color" 6 | 7 | // Appearance configuration. 8 | var ( 9 | baseColor = color.RGBA{0xff, 0, 0, 0x08} 10 | bluetoothName = "red poi" 11 | ) 12 | -------------------------------------------------------------------------------- /poi/v2.go: -------------------------------------------------------------------------------- 1 | // +build pca10040,v2 2 | 3 | // Actually targetting a GT832C_01, which is a small nrf52840 board that's easy 4 | // to solder. The GT832C_01 (as opposed to the GT832E_01) also includes an 5 | // onboard gyroscope/accelerometer (BMI160) which is quite useful. 6 | 7 | package main 8 | 9 | import ( 10 | "image/color" 11 | "machine" 12 | ) 13 | 14 | // Hardware configuration. 15 | const ( 16 | spiClockPin machine.Pin = 25 17 | spiDataPin machine.Pin = 26 18 | spiFrequency = 8000000 19 | 20 | mosfetPin machine.Pin = 27 21 | 22 | serialTxPin machine.Pin = 18 23 | 24 | hasBMI160 = true 25 | 26 | numLeds = 24 // number of LEDs in the strip 27 | height = 24 // number of LEDs to be animated 28 | ) 29 | 30 | //go:inline 31 | func setLED(y int16, c color.RGBA) { 32 | leds[height-y-1] = applyBrightness(c) 33 | } 34 | -------------------------------------------------------------------------------- /spirit-level/README.md: -------------------------------------------------------------------------------- 1 | # Spirit level using the Circuit Play Express 2 | 3 | This is a small ring that implements a spirit level tool, to measure whether 4 | something is horizontal. It depends, of course, on the accuracy of the LIS3DH 5 | accelerometer on the board. 6 | 7 | The WS2812 ring on the board is used to indicate which part of the Circuit Play 8 | Express is lower. 9 | -------------------------------------------------------------------------------- /spirit-level/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "device/arm" 5 | "image/color" 6 | "machine" 7 | "time" 8 | 9 | "github.com/go-gl/mathgl/mgl32" 10 | "github.com/tinygo-org/drivers/lis3dh" 11 | "github.com/tinygo-org/drivers/ws2812" 12 | ) 13 | 14 | var ( 15 | i2c = machine.I2C1 16 | ledPin = machine.GPIO{machine.NEOPIXELS} 17 | ws = ws2812.New(ledPin) 18 | leds = make([]color.RGBA, 10) 19 | ) 20 | 21 | // Coordinates of the LEDs in (x, y) format 22 | // (assuming they're on a circle with 12 points). 23 | var coords = [10][2]float32{ 24 | // first half 25 | {0.500, -0.866}, 26 | {0.866, -0.500}, 27 | {1.000, 0.000}, 28 | {0.866, 0.500}, 29 | {0.500, 0.866}, 30 | {-0.500, 0.866}, 31 | 32 | // second half 33 | {-0.866, 0.500}, 34 | {-1.000, 0.000}, 35 | {-0.866, -0.500}, 36 | {-0.500, -0.866}, 37 | } 38 | 39 | func main() { 40 | ledPin.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT}) 41 | 42 | i2c.Configure(machine.I2CConfig{}) 43 | accel := lis3dh.New(i2c) 44 | accel.Address = lis3dh.Address1 // address on the Circuit Playground Express 45 | accel.Configure() 46 | accel.SetRange(lis3dh.RANGE_2_G) 47 | 48 | println("connected:", accel.Connected()) 49 | 50 | for { 51 | time.Sleep(time.Second / 20) 52 | 53 | xi, yi, zi, _ := accel.ReadAcceleration() 54 | println("accel:", xi/1000, yi/1000, zi/1000) 55 | 56 | vec := mgl32.Vec3{ 57 | float32(xi) / 1000000, 58 | float32(yi) / 1000000, 59 | float32(zi) / 1000000, 60 | } 61 | vec = vec.Normalize() 62 | println(" x:", int32(vec[0]*1000)) 63 | println(" y:", int32(vec[1]*1000)) 64 | 65 | for i := range leds { 66 | ledX := coords[i][0] 67 | ledY := coords[i][1] 68 | height := (vec[0] * ledX) + (vec[1] * ledY) 69 | red := (height * 10) + 1 70 | if red < 0 { 71 | red = 0 72 | } 73 | leds[i] = color.RGBA{uint8(red), 0, 0, 0} 74 | } 75 | 76 | mask := arm.DisableInterrupts() 77 | ws.WriteColors(leds) 78 | arm.EnableInterrupts(mask) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test-ws2812/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aykevl/things/test-ws2812 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 7 | tinygo.org/x/drivers v0.24.1-0.20230520223205-95f0ca8c3ee0 8 | ) 9 | -------------------------------------------------------------------------------- /test-ws2812/go.sum: -------------------------------------------------------------------------------- 1 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da h1:u4fP3+V+W/547R/P7ezR5gH2zucphUZfQLc1ypNaXOg= 2 | github.com/aykevl/Go-simplex-noise v0.0.0-20191228011045-32260ebd32da/go.mod h1:Bn4qAbkSY0k0/wrXdqQ8bWPTv/Uww3uIJuQv/knzDdQ= 3 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2 h1:NcmN0bjs7hmLKSARz88oxXF6OU50/d4Xeo5QWEE5y7w= 4 | github.com/aykevl/ledsgo v0.0.0-20220227114919-bd2e91bb77f2/go.mod h1:GDZBEY7gPGjvz/G3zLFirDcgbaN3nF4nvJ157+vnEro= 5 | github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE= 6 | github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= 7 | github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= 8 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 9 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 10 | github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as= 11 | github.com/kettek/apng v0.0.0-20191108220231-414630eed80f/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q= 12 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 13 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 14 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 15 | github.com/sago35/go-bdf v0.0.0-20200313142241-6c17821c91c4/go.mod h1:rOebXGuMLsXhZAC6mF/TjxONsm45498ZyzVhel++6KM= 16 | github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= 17 | golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= 18 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 19 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 20 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 21 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 22 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 23 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 24 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 25 | tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 26 | tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 27 | tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI= 28 | tinygo.org/x/drivers v0.19.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI= 29 | tinygo.org/x/drivers v0.24.1-0.20230520223205-95f0ca8c3ee0 h1:zDzbTUAG311K+/z7ZbWk7uD3xlo5K6O9XZ0Q0FIM0nw= 30 | tinygo.org/x/drivers v0.24.1-0.20230520223205-95f0ca8c3ee0/go.mod h1:v+mXaA4cgpz/YZJ3ZPm/86bYQJAXTaYtMkHlVwbodbw= 31 | tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM= 32 | tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk= 33 | tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20= 34 | tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4= 35 | -------------------------------------------------------------------------------- /test-ws2812/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "machine" 6 | "runtime/interrupt" 7 | "time" 8 | 9 | "github.com/aykevl/ledsgo" 10 | "tinygo.org/x/drivers/ws2812" 11 | ) 12 | 13 | const NUM_LEDS = 10 14 | 15 | var leds = make([]color.RGBA, NUM_LEDS) 16 | 17 | func main() { 18 | led := machine.LED 19 | led.Configure(machine.PinConfig{Mode: machine.PinOutput}) 20 | 21 | LED_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput}) 22 | strip := ws2812.New(LED_PIN) 23 | 24 | for { 25 | // Update colors. 26 | t := uint64(time.Now().UnixNano() >> 20) 27 | noise(t, leds) 28 | 29 | // Send new colors to LEDs. 30 | mask := interrupt.Disable() 31 | for _, c := range leds { 32 | strip.WriteByte(c.G) // G 33 | strip.WriteByte(c.R) // R 34 | strip.WriteByte(c.B) // B 35 | strip.WriteByte(c.A) // W (alpha channel, used as white channel) 36 | } 37 | interrupt.Restore(mask) 38 | time.Sleep(20 * time.Millisecond) 39 | } 40 | } 41 | 42 | func noise(t uint64, leds []color.RGBA) { 43 | for i := range leds { 44 | const spread = 48 // higher means more detail 45 | val := ledsgo.Noise2(uint32(t), uint32(i)*spread) 46 | c := ledsgo.PartyColors.ColorAt(val) 47 | c.A = 0 // the alpha channel is used as white channel, so don't use it 48 | leds[i] = ledsgo.ApplyAlpha(c, 64) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test-ws2812/pins_arduino.go: -------------------------------------------------------------------------------- 1 | //go:build arduino 2 | 3 | package main 4 | 5 | import "machine" 6 | 7 | const ( 8 | LED_PIN = machine.D2 9 | ) 10 | -------------------------------------------------------------------------------- /test-ws2812/pins_esp32.go: -------------------------------------------------------------------------------- 1 | //go:build esp32 2 | 3 | package main 4 | 5 | const ( 6 | LED_PIN = 13 7 | ) 8 | -------------------------------------------------------------------------------- /test-ws2812/pins_feather-stm32f405.go: -------------------------------------------------------------------------------- 1 | //go:build feather_stm32f405 2 | 3 | package main 4 | 5 | import "machine" 6 | 7 | const ( 8 | LED_PIN = machine.A0 9 | ) 10 | -------------------------------------------------------------------------------- /test-ws2812/pins_pico.go: -------------------------------------------------------------------------------- 1 | //go:build pico 2 | 3 | package main 4 | 5 | import "machine" 6 | 7 | const ( 8 | LED_PIN = machine.GPIO29 9 | ) 10 | -------------------------------------------------------------------------------- /test-ws2812/pins_pybadge.go: -------------------------------------------------------------------------------- 1 | //go:build pybadge || arduino_nano33 2 | 3 | package main 4 | 5 | import "machine" 6 | 7 | const ( 8 | LED_PIN = machine.D13 9 | ) 10 | -------------------------------------------------------------------------------- /test-ws2812/pins_ws2812.go: -------------------------------------------------------------------------------- 1 | //go:build circuitplay_express 2 | 3 | package main 4 | 5 | import "machine" 6 | 7 | const ( 8 | LED_PIN = machine.WS2812 9 | ) 10 | -------------------------------------------------------------------------------- /watch/README.md: -------------------------------------------------------------------------------- 1 | # GopherWatch - PineTime firmware written in Go 2 | 3 | Work in progress. No guarantee that this firmware will ever be usable. 4 | 5 | Features/goals: 6 | 7 | * Long standby time, possibly up to a few months. 8 | * Fast and responsive display. 9 | * Easy to work on, if you know the Go programming language. 10 | * Full-featured testing in simulation: use `go run .` to test the firmware directly on your computer instead of having to flash it to the PineTime. 11 | 12 | **Warning**: installing this firmware comes at your own risk. While it looks reliable to me, there is always the risk that it bricks a sealed PineTime. 13 | 14 | To use this bootloader: 15 | 16 | 1. Install the [Wasp-OS bootloader](https://wiki.pine64.org/wiki/Switching_your_PineTime_between_InfiniTime_and_Wasp-os). 17 | I have found the [OTA tool from InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime/tree/main/bootloader/ota-dfu-python) to be more reliable in this step (it appears that the Wasp-OS ota.py doesn't work with InfiniTime). 18 | 19 | ./dfu.py -a --legacy -z path/to/reloader-mcuboot.zip 20 | 21 | Wait until you see a small blue pine icon that slowly turns white, and then the Wasp-OS bootloader screen. 22 | 23 | 2. Create a firmware package. You can use the following command: 24 | ``` 25 | tinygo build -o watch.zip -target=./pinetime-wasp-bootloader.json -opt=2 26 | ``` 27 | 3. Flash the firmware package using ota.py (from either InfiniTime or Wasp-OS). 28 | 29 | ./dfu.py -a --legacy -z watch.zip 30 | 4. Enjoy the new firmware! 31 | 32 | Once you have done the first OTA using the Wasp-OS bootloader and you have access to the SWD pins, you can flash the firmware using SWD: 33 | 34 | tinygo flash -target=./pinetime-wasp-bootloader.json 35 | 36 | If you want to go back, you can follow the steps [on the wiki (Wasp-os => InfiniTime)](https://wiki.pine64.org/wiki/Switching_your_PineTime_between_InfiniTime_and_Wasp-os). 37 | 38 | ## Development 39 | 40 | Check out the repository and run the following command to start the simulator: 41 | 42 | go run . 43 | 44 | You may need to install a few dependencies first: 45 | 46 | * [Go](https://go.dev/dl/) (at least version 1.20) 47 | * [Fyne dependencies](https://docs.fyne.io/started/) 48 | 49 | This is what it might look like: 50 | 51 | Screenshot of the watch simulator 52 | 53 | You can also flash the firmware directly to the PineTime, using the following command (use the `-programmer=` flag if you don't use a J-Link programmer): 54 | 55 | tinygo flash -target=./pinetime-wasp-bootloader.json -opt=2 56 | -------------------------------------------------------------------------------- /watch/assets/watchface-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-0.png -------------------------------------------------------------------------------- /watch/assets/watchface-0.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-0.raw -------------------------------------------------------------------------------- /watch/assets/watchface-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-1.png -------------------------------------------------------------------------------- /watch/assets/watchface-1.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-1.raw -------------------------------------------------------------------------------- /watch/assets/watchface-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-2.png -------------------------------------------------------------------------------- /watch/assets/watchface-2.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-2.raw -------------------------------------------------------------------------------- /watch/assets/watchface-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-3.png -------------------------------------------------------------------------------- /watch/assets/watchface-3.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-3.raw -------------------------------------------------------------------------------- /watch/assets/watchface-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-4.png -------------------------------------------------------------------------------- /watch/assets/watchface-4.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-4.raw -------------------------------------------------------------------------------- /watch/assets/watchface-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-5.png -------------------------------------------------------------------------------- /watch/assets/watchface-5.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-5.raw -------------------------------------------------------------------------------- /watch/assets/watchface-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-6.png -------------------------------------------------------------------------------- /watch/assets/watchface-6.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-6.raw -------------------------------------------------------------------------------- /watch/assets/watchface-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-7.png -------------------------------------------------------------------------------- /watch/assets/watchface-7.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-7.raw -------------------------------------------------------------------------------- /watch/assets/watchface-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-8.png -------------------------------------------------------------------------------- /watch/assets/watchface-8.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-8.raw -------------------------------------------------------------------------------- /watch/assets/watchface-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-9.png -------------------------------------------------------------------------------- /watch/assets/watchface-9.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-9.raw -------------------------------------------------------------------------------- /watch/assets/watchface-colon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-colon.png -------------------------------------------------------------------------------- /watch/assets/watchface-colon.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/assets/watchface-colon.raw -------------------------------------------------------------------------------- /watch/ble-none.go: -------------------------------------------------------------------------------- 1 | //go:build !(softdevice || !baremetal) 2 | 3 | // Dummy Bluetooth implementation, to be able to run this watch on systems that 4 | // don't support Bluetooth. 5 | 6 | package main 7 | 8 | func InitBluetooth() error { 9 | // nothing to do 10 | return nil 11 | } 12 | 13 | func updateBatteryLevel(level uint8) { 14 | } 15 | 16 | func updateStepCountValue(stepCount uint32) { 17 | } 18 | -------------------------------------------------------------------------------- /watch/ble.go: -------------------------------------------------------------------------------- 1 | //go:build softdevice || !baremetal 2 | 3 | package main 4 | 5 | // Bluetooth support for the watch. 6 | // This should be kept reasonably portable, so that at least testing on Linux 7 | // will continue to work. 8 | 9 | import ( 10 | "time" 11 | 12 | "tinygo.org/x/bluetooth" 13 | ) 14 | 15 | var adapter = bluetooth.DefaultAdapter 16 | 17 | var ( 18 | batteryLevel bluetooth.Characteristic 19 | stepCountChar bluetooth.Characteristic 20 | ) 21 | 22 | var connectedDevice chan bluetooth.Device 23 | 24 | func InitBluetooth() error { 25 | err := adapter.Enable() 26 | if err != nil { 27 | return err 28 | } 29 | 30 | adapter.SetConnectHandler(handleBLEConnection) 31 | connectedDevice = make(chan bluetooth.Device, 1) 32 | go connectionHandler() 33 | 34 | // TODO: use a shorter advertisement interval after start and after losing 35 | // connection. For example, a 20ms interval for 30 seconds as stated in the 36 | // Apple guidelines: 37 | // https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf 38 | // An interval of 1285 uses around 11µA according to the online power profiler: 39 | // https://devzone.nordicsemi.com/power/w/opp/2/online-power-profiler-for-bluetooth-le 40 | adv := adapter.DefaultAdvertisement() 41 | err = adv.Configure(bluetooth.AdvertisementOptions{ 42 | LocalName: "InfiniTime", 43 | Interval: bluetooth.NewDuration(1285 * time.Millisecond), 44 | }) 45 | if err != nil { 46 | return err 47 | } 48 | err = adv.Start() 49 | if err != nil { 50 | return err 51 | } 52 | 53 | // Add Device Information Service. This is necessary for Gadgetbridge, 54 | // otherwise it keeps showing an error ("the bind value at index 2 is 55 | // null"). 56 | err = adapter.AddService(&bluetooth.Service{ 57 | UUID: bluetooth.ServiceUUIDDeviceInformation, 58 | Characteristics: []bluetooth.CharacteristicConfig{ 59 | { 60 | UUID: bluetooth.CharacteristicUUIDManufacturerNameString, 61 | Value: []byte("Pine64"), 62 | Flags: bluetooth.CharacteristicReadPermission, 63 | }, 64 | { 65 | UUID: bluetooth.CharacteristicUUIDFirmwareRevisionString, 66 | Value: []byte("GopherWatch-dev"), // unspecified version 67 | Flags: bluetooth.CharacteristicReadPermission, 68 | }, 69 | }, 70 | }) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | // Add battery service. 76 | err = adapter.AddService(&bluetooth.Service{ 77 | UUID: bluetooth.ServiceUUIDBattery, 78 | Characteristics: []bluetooth.CharacteristicConfig{ 79 | { 80 | Handle: &batteryLevel, 81 | UUID: bluetooth.CharacteristicUUIDBatteryLevel, 82 | Value: []byte{0}, 83 | Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicNotifyPermission, 84 | }, 85 | }, 86 | }) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | // Current Time Service. This enables Gadgetbridge to sync the time. 92 | err = adapter.AddService(&bluetooth.Service{ 93 | UUID: bluetooth.ServiceUUIDCurrentTime, 94 | Characteristics: []bluetooth.CharacteristicConfig{ 95 | { 96 | UUID: bluetooth.CharacteristicUUIDCurrentTime, 97 | Flags: bluetooth.CharacteristicWriteWithoutResponsePermission | bluetooth.CharacteristicWritePermission, 98 | WriteEvent: func(client bluetooth.Connection, offset int, value []byte) { 99 | if offset != 0 || len(value) != 10 { 100 | return // unexpected value 101 | } 102 | year := int(value[0]) | int(value[1])<<8 103 | month := time.Month(value[2]) 104 | day := int(value[3]) 105 | hour := int(value[4]) 106 | minute := int(value[5]) 107 | second := int(value[6]) 108 | nanosecond := int(value[7]) * (1e9 / 256) 109 | newTime := time.Date(year, month, day, hour, minute, second, nanosecond, time.UTC) 110 | oldTime := time.Now() 111 | diff := newTime.Sub(oldTime) 112 | adjustTime(diff) 113 | }, 114 | }, 115 | }, 116 | }) 117 | if err != nil { 118 | return err 119 | } 120 | 121 | // InfiniTime Motion Service. 122 | err = adapter.AddService(&bluetooth.Service{ 123 | UUID: makeInfiniTimeUUID(0x0003_0000), 124 | Characteristics: []bluetooth.CharacteristicConfig{ 125 | // Step count characteristic. 126 | { 127 | Handle: &stepCountChar, 128 | UUID: makeInfiniTimeUUID(0x0003_0001), 129 | Flags: bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicNotifyPermission, 130 | Value: make([]byte, 4), 131 | }, 132 | }, 133 | }) 134 | if err != nil { 135 | return err 136 | } 137 | 138 | return nil 139 | } 140 | 141 | func makeInfiniTimeUUID(firstPart uint32) bluetooth.UUID { 142 | // SSSSCCCC-78fc-48fe-8e23-433b3a1942d0 143 | return bluetooth.NewUUID([16]byte{ 144 | uint8(firstPart >> 24), uint8(firstPart >> 16), uint8(firstPart >> 8), uint8(firstPart >> 0), 145 | 0x78, 0xfc, 146 | 0x48, 0xfe, 147 | 0x8e, 0x23, 148 | 0x43, 0x3b, 0x3a, 0x19, 0x42, 0xd0}) 149 | } 150 | 151 | func handleBLEConnection(device bluetooth.Device, connected bool) { 152 | if connected { 153 | select { 154 | case connectedDevice <- device: 155 | default: 156 | } 157 | } 158 | } 159 | 160 | // Background goroutine that updates connection parameters as needed. 161 | func connectionHandler() { 162 | for device := range connectedDevice { 163 | // Wait a bit after connecting so that initial negotiating can be 164 | // faster. 165 | time.Sleep(time.Second * 5) 166 | 167 | // Following the Apple accessory design guidelines, picking a connection 168 | // latency of around 500ms that is a multiple of 15ms (and giving the 169 | // device 15ms of space). My Android 13 phone picks 510ms as the 170 | // connection interval with these parameters. 171 | // For comparison, the Mi Band 3 negotiates 517.5ms as the connection 172 | // interval after a sync. 173 | device.RequestConnectionParams(bluetooth.ConnectionParams{ 174 | MinInterval: bluetooth.NewDuration(495 * time.Millisecond), 175 | MaxInterval: bluetooth.NewDuration(510 * time.Millisecond), 176 | Timeout: bluetooth.NewDuration(5 * time.Second), 177 | }) 178 | } 179 | } 180 | 181 | var updateCharacteristicBuf [4]byte 182 | var batteryLevelValue uint8 183 | 184 | func updateBatteryLevel(level uint8) { 185 | if level == batteryLevelValue { 186 | return 187 | } 188 | updateCharacteristicBuf[0] = level 189 | _, err := batteryLevel.Write(updateCharacteristicBuf[:1]) 190 | if err != nil { 191 | return 192 | } 193 | batteryLevelValue = level 194 | } 195 | 196 | var stepCountValue uint32 197 | 198 | func updateStepCountValue(stepCount uint32) { 199 | if stepCount == stepCountValue { 200 | return 201 | } 202 | updateCharacteristicBuf[0] = byte(stepCount >> 0) 203 | updateCharacteristicBuf[1] = byte(stepCount >> 8) 204 | updateCharacteristicBuf[2] = byte(stepCount >> 16) 205 | updateCharacteristicBuf[3] = byte(stepCount >> 24) 206 | _, err := stepCountChar.Write(updateCharacteristicBuf[:4]) 207 | if err != nil { 208 | return 209 | } 210 | stepCountValue = stepCount 211 | } 212 | -------------------------------------------------------------------------------- /watch/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aykevl/things/watch 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/aykevl/board v0.0.0-20240106144210-80ca76f77def 7 | github.com/aykevl/tinygl v0.0.0-20240131130748-3033a2fd9182 8 | tinygo.org/x/bluetooth v0.8.1-0.20240127051609-b6fde65fd674 9 | tinygo.org/x/drivers v0.26.1-0.20231124130000-fef6564044f9 10 | ) 11 | 12 | require ( 13 | fyne.io/fyne/v2 v2.4.1 // indirect 14 | fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/fredbi/uri v1.1.0 // indirect 17 | github.com/fsnotify/fsnotify v1.7.0 // indirect 18 | github.com/fyne-io/gl-js v0.0.0-20230506162202-1fdaa286a934 // indirect 19 | github.com/fyne-io/glfw-js v0.0.0-20231117203605-bc7c6f97d52f // indirect 20 | github.com/fyne-io/image v0.0.0-20230811065323-ed435dc8bca6 // indirect 21 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect 22 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20231124074035-2de0cf0c80af // indirect 23 | github.com/go-ole/go-ole v1.3.0 // indirect 24 | github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 // indirect 25 | github.com/go-text/typesetting v0.0.0-20231120180320-af78120ccb13 // indirect 26 | github.com/godbus/dbus/v5 v5.1.0 // indirect 27 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 28 | github.com/gopherjs/gopherjs v1.17.2 // indirect 29 | github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect 30 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 31 | github.com/pmezard/go-difflib v1.0.0 // indirect 32 | github.com/saltosystems/winrt-go v0.0.0-20231011131235-9071442c0c84 // indirect 33 | github.com/sirupsen/logrus v1.9.3 // indirect 34 | github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect 35 | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect 36 | github.com/stretchr/testify v1.8.4 // indirect 37 | github.com/tevino/abool v1.2.0 // indirect 38 | github.com/tinygo-org/cbgo v0.0.4 // indirect 39 | github.com/yuin/goldmark v1.6.0 // indirect 40 | golang.org/x/image v0.14.0 // indirect 41 | golang.org/x/mobile v0.0.0-20231108233038-35478a0c49da // indirect 42 | golang.org/x/net v0.18.0 // indirect 43 | golang.org/x/sys v0.16.0 // indirect 44 | golang.org/x/text v0.14.0 // indirect 45 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 46 | gopkg.in/yaml.v3 v3.0.1 // indirect 47 | honnef.co/go/js/dom v0.0.0-20231030024858-cb489e859d05 // indirect 48 | ) 49 | -------------------------------------------------------------------------------- /watch/img/simulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aykevl/things/b7000ae9e1e40aa2425a2562e4990e1f44dec909/watch/img/simulator.png -------------------------------------------------------------------------------- /watch/notinygo.go: -------------------------------------------------------------------------------- 1 | //go:build !tinygo 2 | 3 | package main 4 | 5 | import "time" 6 | 7 | var timeOffset time.Duration 8 | 9 | func adjustTime(offset time.Duration) { 10 | timeOffset += offset 11 | } 12 | 13 | func watchTime() time.Time { 14 | return time.Now().Add(timeOffset) 15 | } 16 | -------------------------------------------------------------------------------- /watch/pinetime-wasp-bootloader.json: -------------------------------------------------------------------------------- 1 | { 2 | "inherits": ["pinetime", "nrf52-s132v6"], 3 | "linkerscript": "pinetime-wasp-bootloader.ld", 4 | "serial": "none", 5 | "binary-format": "nrf-dfu" 6 | } 7 | -------------------------------------------------------------------------------- /watch/pinetime-wasp-bootloader.ld: -------------------------------------------------------------------------------- 1 | /* Slightly modified version of targets/nrf52-s132v6.ld in the TinyGo source 2 | * code. It changes the RAM start address to 0x200039e0 to avoid PNVRAM area. 3 | * For details, see: 4 | * https://wasp-os.readthedocs.io/en/latest/wasp.html#pnvram-protocol 5 | */ 6 | 7 | /* TODO: exclude bootloader area from FLASH_TEXT */ 8 | 9 | MEMORY 10 | { 11 | FLASH_TEXT (rw) : ORIGIN = 0x00000000 + 0x00026000 , LENGTH = 512K - 0x00026000 /* .text */ 12 | RAM (xrw) : ORIGIN = 0x20000000 + 0x000039e0, LENGTH = 64K - 0x000039e0 13 | } 14 | 15 | _stack_size = 4K; 16 | 17 | /* This value is needed by the Nordic SoftDevice. */ 18 | __app_ram_base = ORIGIN(RAM); 19 | 20 | INCLUDE "targets/arm.ld" 21 | -------------------------------------------------------------------------------- /watch/tinygo.go: -------------------------------------------------------------------------------- 1 | //go:build tinygo 2 | 3 | package main 4 | 5 | import ( 6 | "runtime" 7 | "time" 8 | ) 9 | 10 | func adjustTime(offset time.Duration) { 11 | runtime.AdjustTimeOffset(int64(offset)) 12 | } 13 | 14 | func watchTime() time.Time { 15 | return time.Now() 16 | } 17 | -------------------------------------------------------------------------------- /watch/ui.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/aykevl/tinygl" 7 | "github.com/aykevl/tinygl/style/basic" 8 | "tinygo.org/x/drivers/pixel" 9 | ) 10 | 11 | // ViewManager is a kind of window manager for the watch. 12 | type ViewManager[T pixel.Color] struct { 13 | screen *tinygl.Screen[T] 14 | *basic.Basic[T] 15 | 16 | // This is a stack of views that can be added on top and popped from when 17 | // going back to the previous view. 18 | views []View[T] 19 | } 20 | 21 | // Len returns the number of views. 22 | func (v *ViewManager[T]) Len() int { 23 | return len(v.views) 24 | } 25 | 26 | // Push adds the view to the stack of views, displaying it on top of the screen. 27 | func (v *ViewManager[T]) Push(view View[T]) { 28 | v.views = append(v.views, view) 29 | v.screen.SetChild(view.Object) 30 | } 31 | 32 | // Pop removes the topmost view, revealing the view underneath. 33 | func (v *ViewManager[T]) Pop() { 34 | v.views[len(v.views)-1] = View[T]{} // allow this view to be GC'd 35 | v.views = v.views[:len(v.views)-1] 36 | v.screen.SetChild(v.views[len(v.views)-1].Object) 37 | } 38 | 39 | // Replace all views in the stack, and replace it with the given view. 40 | // This is used to set a new homescreen for example. 41 | func (v *ViewManager[T]) ReplaceAll(view View[T]) { 42 | v.views = v.views[:0] 43 | v.views = append(v.views, view) 44 | v.screen.SetChild(view.Object) 45 | } 46 | 47 | // Update runs the Update callback attached to this view. 48 | func (v *ViewManager[T]) Update(now time.Time) { 49 | callback := v.views[len(v.views)-1].Update 50 | if callback != nil { 51 | callback(now) 52 | } 53 | } 54 | 55 | // A view is a single full-screen UI that is active at a time. 56 | // It is comparable to an Android activity. 57 | type View[T pixel.Color] struct { 58 | tinygl.Object[T] 59 | Update func(now time.Time) 60 | } 61 | 62 | // NewView creates a new view with the given values. 63 | func NewView[T pixel.Color](object tinygl.Object[T], update func(now time.Time)) View[T] { 64 | return View[T]{ 65 | Object: object, 66 | Update: update, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /watch/watchface.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | _ "embed" 5 | "time" 6 | 7 | "github.com/aykevl/tinygl" 8 | "github.com/aykevl/tinygl/gfx" 9 | "github.com/aykevl/tinygl/image" 10 | "tinygo.org/x/drivers/pixel" 11 | ) 12 | 13 | //go:embed assets/watchface-0.raw 14 | var char0 string 15 | 16 | //go:embed assets/watchface-1.raw 17 | var char1 string 18 | 19 | //go:embed assets/watchface-2.raw 20 | var char2 string 21 | 22 | //go:embed assets/watchface-3.raw 23 | var char3 string 24 | 25 | //go:embed assets/watchface-4.raw 26 | var char4 string 27 | 28 | //go:embed assets/watchface-5.raw 29 | var char5 string 30 | 31 | //go:embed assets/watchface-6.raw 32 | var char6 string 33 | 34 | //go:embed assets/watchface-7.raw 35 | var char7 string 36 | 37 | //go:embed assets/watchface-8.raw 38 | var char8 string 39 | 40 | //go:embed assets/watchface-9.raw 41 | var char9 string 42 | 43 | //go:embed assets/watchface-colon.raw 44 | var charColon string 45 | 46 | // Current watchface. 47 | var watchFaceIndex uint8 48 | 49 | // List of all available watch faces. 50 | var watchFaces = []string{ 51 | "Digital", 52 | "Analog", 53 | } 54 | 55 | // Create a simple digital watch face as the homescreen. 56 | func (w *Watch[T]) createWatchFace(views *ViewManager[T]) View[T] { 57 | switch watchFaceIndex { 58 | case 0: 59 | return w.createDigitalWatchface(views) 60 | case 1: 61 | return w.createAnalogWatchface(views) 62 | default: 63 | // should be unreachable 64 | return w.createDigitalWatchface(views) 65 | } 66 | } 67 | 68 | func (w *Watch[T]) createDigitalWatchface(views *ViewManager[T]) View[T] { 69 | const charWidth, charHeight = 48, 107 70 | var ( 71 | black = pixel.NewColor[T](0, 0, 0) 72 | white = pixel.NewColor[T](255, 255, 255) 73 | 74 | // TODO: compress the images in some way (for example, using run-length 75 | // encoding). Right now the images together consume 7062 bytes of flash, 76 | // which is quite a lot. 77 | numbers = [10]image.Mono[T]{ 78 | image.MakeMono(white, black, charWidth, charHeight, char0), 79 | image.MakeMono(white, black, charWidth, charHeight, char1), 80 | image.MakeMono(white, black, charWidth, charHeight, char2), 81 | image.MakeMono(white, black, charWidth, charHeight, char3), 82 | image.MakeMono(white, black, charWidth, charHeight, char4), 83 | image.MakeMono(white, black, charWidth, charHeight, char5), 84 | image.MakeMono(white, black, charWidth, charHeight, char6), 85 | image.MakeMono(white, black, charWidth, charHeight, char7), 86 | image.MakeMono(white, black, charWidth, charHeight, char8), 87 | image.MakeMono(white, black, charWidth, charHeight, char9), 88 | } 89 | ) 90 | 91 | _, displayHeight := w.display.Size() 92 | canvas := gfx.NewCanvas(black, 96, 96) 93 | colonImage := image.MakeMono(white, black, charWidth, charHeight, charColon) 94 | 95 | // Create hh:mm images. 96 | h0 := gfx.NewImage[T](numbers[0], charWidth*0, int(displayHeight)/2-charHeight/2) 97 | h1 := gfx.NewImage[T](numbers[0], charWidth*1, int(displayHeight)/2-charHeight/2) 98 | colon := gfx.NewImage[T](colonImage, charWidth*2, int(displayHeight)/2-charHeight/2) 99 | m0 := gfx.NewImage[T](numbers[0], charWidth*3, int(displayHeight)/2-charHeight/2) 100 | m1 := gfx.NewImage[T](numbers[0], charWidth*4, int(displayHeight)/2-charHeight/2) 101 | canvas.Add(h0) 102 | canvas.Add(h1) 103 | canvas.Add(colon) 104 | canvas.Add(m0) 105 | canvas.Add(m1) 106 | 107 | eventWrapper := tinygl.NewEventBox[T](canvas) 108 | eventWrapper.SetEventHandler(func(event tinygl.Event, x, y int) { 109 | if event == tinygl.TouchTap { 110 | if backlight == 0 { 111 | // Tapped on a sleeping watch. 112 | // Awake the screen. 113 | w.exitSleep() 114 | } else { 115 | // Regular tap on the clock. 116 | // TODO: detect gesture (for example, swipe upwards) to make it 117 | // harder to accidentally get in the settings menu. 118 | views.Push(w.createAppsView(views)) 119 | } 120 | } 121 | }) 122 | 123 | updateTime := func(hour, minute int) { 124 | h0.SetImage(numbers[hour/10]) 125 | h1.SetImage(numbers[hour%10]) 126 | m0.SetImage(numbers[minute/10]) 127 | m1.SetImage(numbers[minute%10]) 128 | } 129 | now := watchTime() 130 | hour := now.Hour() 131 | minute := now.Minute() 132 | updateTime(hour, minute) 133 | 134 | return NewView[T](eventWrapper, func(now time.Time) { 135 | // Update the watchface. 136 | if backlight > 0 { 137 | // Watch face is visible. 138 | newHour := now.Hour() 139 | newMinute := now.Minute() 140 | if hour != newHour || minute != newMinute { 141 | hour = newHour 142 | minute = newMinute 143 | updateTime(hour, minute) 144 | } 145 | } 146 | }) 147 | } 148 | 149 | func (w *Watch[T]) createAnalogWatchface(views *ViewManager[T]) View[T] { 150 | var ( 151 | black = pixel.NewColor[T](0, 0, 0) 152 | hourMark = pixel.NewColor[T](100, 100, 100) 153 | hourHandleColor = pixel.NewColor[T](255, 255, 255) 154 | minuteHandleColor = pixel.NewColor[T](255, 255, 255) 155 | centerDotColor = pixel.NewColor[T](255, 255, 255) 156 | ) 157 | 158 | canvas := gfx.NewCanvas(black, 96, 96) 159 | displayWidth, displayHeight := w.display.Size() 160 | centerX, centerY := int(displayWidth)/2, int(displayHeight)/2 161 | r := int(displayHeight) / 2 162 | 163 | // Hour markers. 164 | for i := 0; i < 360; i += 360 / 12 { 165 | markX, markY := getAnalogWatchCoord(i) 166 | canvas.Add(gfx.NewLine(hourMark, 167 | centerX+markX*r/255, 168 | centerY+markY*r/255, 169 | centerX+markX*r/290, 170 | centerY+markY*r/290, 171 | r/16)) 172 | } 173 | 174 | // Dot in the center. 175 | canvas.Add(gfx.NewCircle(centerDotColor, centerX, centerY, 6)) 176 | 177 | // Handles, at an arbitrary position. 178 | hourHandle := gfx.NewLine(hourHandleColor, centerX, centerY, centerX, centerY, 10) 179 | canvas.Add(hourHandle) 180 | minuteHandle := gfx.NewLine(minuteHandleColor, centerX, centerY, centerX, centerY, 6) 181 | canvas.Add(minuteHandle) 182 | 183 | updateTime := func(hour, minute, second int) { 184 | hour = hour % 12 185 | hourX, hourY := getAnalogWatchCoord(hour*30 + minute/2) 186 | hourHandle.SetPosition(centerX+hourX*10/255, centerY+hourY*10/255, centerX+hourX*r/500, centerY+hourY*r/500) 187 | minuteX, minuteY := getAnalogWatchCoord(minute*6 + second/10) 188 | minuteHandle.SetPosition(centerX+minuteX*10/255, centerY+minuteY*10/255, centerX+minuteX*r/300, centerY+minuteY*r/300) 189 | } 190 | now := watchTime() 191 | hour := now.Hour() 192 | minute := now.Minute() 193 | second := now.Second() 194 | updateTime(hour, minute, second) 195 | 196 | eventWrapper := tinygl.NewEventBox[T](canvas) 197 | eventWrapper.SetEventHandler(func(event tinygl.Event, x, y int) { 198 | if event == tinygl.TouchTap { 199 | if backlight == 0 { 200 | // Tapped on a sleeping watch. 201 | // Awake the screen. 202 | w.exitSleep() 203 | } else { 204 | // Regular tap on the clock. 205 | // TODO: detect gesture (for example, swipe upwards) to make it 206 | // harder to accidentally get in the settings menu. 207 | views.Push(w.createAppsView(views)) 208 | } 209 | } 210 | }) 211 | return NewView[T](eventWrapper, func(now time.Time) { 212 | // Update the watchface. 213 | if backlight > 0 { 214 | // Watch face is visible. 215 | newHour := now.Hour() 216 | newMinute := now.Minute() 217 | newSecond := now.Second() 218 | if hour != newHour || minute != newMinute || second != newSecond { 219 | hour = newHour 220 | minute = newMinute 221 | second = newSecond 222 | updateTime(hour, minute, second) 223 | } 224 | } 225 | }) 226 | } 227 | 228 | // Return the -255..255 X and Y coordinates on a circle, starting at 12 o'clock 229 | // going right around. The index is the number of degrees (0..359). 230 | func getAnalogWatchCoord(index int) (x, y int) { 231 | switch { 232 | case index < 90: 233 | return int(watchCoord[index]), -int(watchCoord[89-index]) 234 | case index < 180: 235 | return int(watchCoord[179-index]), int(watchCoord[index-90]) 236 | case index < 270: 237 | return -int(watchCoord[index-180]), int(watchCoord[269-index]) 238 | default: // index < 360 239 | return -int(watchCoord[359-index]), -int(watchCoord[index-270]) 240 | } 241 | } 242 | 243 | // Table with precalculated sin/cos values. 244 | // Python oneliner: 245 | // 246 | // r=255; [round(math.sin(i/180*math.pi)*r) for i in range(90)] 247 | var watchCoord = [...]uint8{ 248 | 0, 4, 9, 13, 18, 22, 27, 31, 35, 40, 44, 49, 53, 57, 62, 66, 70, 75, 79, 83, 87, 91, 96, 100, 104, 108, 112, 116, 120, 124, 127, 131, 135, 139, 143, 146, 150, 153, 157, 160, 164, 167, 171, 174, 177, 180, 183, 186, 190, 192, 195, 198, 201, 204, 206, 209, 211, 214, 216, 219, 221, 223, 225, 227, 229, 231, 233, 235, 236, 238, 240, 241, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 253, 254, 254, 254, 255, 255, 255, 249 | } 250 | --------------------------------------------------------------------------------