From c5f8394d1d8efe3bd2517b525e2e244d96b11908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Henrique=20Guard=C3=A3o=20Gandarez?= Date: Fri, 27 Mar 2026 17:05:22 -0300 Subject: [PATCH] neopixel: add PIO-based WS2812B driver for RP2040/RP2350 The existing ws2812 bit-bang driver fails on RP2350 (Cortex-M33) due to timing differences from M0+. This adds a neopixel package that uses PIO via tinygo-org/pio for hardware-timed LED control on RP2040/RP2350, with a fallback to the ws2812 bit-bang driver on other platforms. Co-Authored-By: Claude Opus 4.6 (1M context) --- examples/neopixel/main.go | 36 +++++++++++++++++ go.mod | 3 +- go.sum | 2 + neopixel/neopixel.go | 80 ++++++++++++++++++++++++++++++++++++++ neopixel/neopixel_other.go | 33 ++++++++++++++++ neopixel/neopixel_rp2.go | 40 +++++++++++++++++++ 6 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 examples/neopixel/main.go create mode 100644 neopixel/neopixel.go create mode 100644 neopixel/neopixel_other.go create mode 100644 neopixel/neopixel_rp2.go diff --git a/examples/neopixel/main.go b/examples/neopixel/main.go new file mode 100644 index 000000000..26d7443fb --- /dev/null +++ b/examples/neopixel/main.go @@ -0,0 +1,36 @@ +// Demonstrates NeoPixel (WS2812B) RGB LED control. +// +// Tested on Waveshare RP2350-LCD-1.47 with onboard RGB LED on GP22. +// +// To flash: +// +// tinygo flash -target=pico2 ./examples/neopixel/ +package main + +import ( + "image/color" + "machine" + "time" + + "tinygo.org/x/drivers/neopixel" +) + +func main() { + pixel, err := neopixel.New(machine.GP22, 1) + if err != nil { + panic(err) + } + pixel.SetBrightness(50) + + colors := []color.RGBA{ + {R: 255, G: 0, B: 0}, + {R: 0, G: 255, B: 0}, + {R: 0, G: 0, B: 255}, + } + + for i := 0; ; i = (i + 1) % 3 { + pixel.SetPixel(0, colors[i]) + pixel.Show() + time.Sleep(time.Second) + } +} diff --git a/go.mod b/go.mod index 10c3e98a7..432c77ae0 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,16 @@ module tinygo.org/x/drivers - go 1.22.1 toolchain go1.23.1 - require ( github.com/eclipse/paho.mqtt.golang v1.2.0 github.com/frankban/quicktest v1.10.2 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/orsinium-labs/tinymath v1.1.0 github.com/soypat/natiu-mqtt v0.5.1 + github.com/tinygo-org/pio v0.3.0 golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d golang.org/x/net v0.33.0 tinygo.org/x/tinyfont v0.3.0 diff --git a/go.sum b/go.sum index 6bb35574d..673d984d8 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/orsinium-labs/tinymath v1.1.0 h1:KomdsyLHB7vE3f1nRAJF2dyf1m/gnM2HxfTe github.com/orsinium-labs/tinymath v1.1.0/go.mod h1:WPXX6ei3KSXG7JfA03a+ekCYaY9SWN4I+JRl2p6ck+A= github.com/soypat/natiu-mqtt v0.5.1 h1:rwaDmlvjzD2+3MCOjMZc4QEkDkNwDzbct2TJbpz+TPc= github.com/soypat/natiu-mqtt v0.5.1/go.mod h1:xEta+cwop9izVCW7xOx2W+ct9PRMqr0gNVkvBPnQTc4= +github.com/tinygo-org/pio v0.3.0 h1:opEnOtw58KGB4RJD3/n/Rd0/djYGX3DeJiXLI6y/yDI= +github.com/tinygo-org/pio v0.3.0/go.mod h1:wf6c6lKZp+pQOzKKcpzchmRuhiMc27ABRuo7KVnaMFU= github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= diff --git a/neopixel/neopixel.go b/neopixel/neopixel.go new file mode 100644 index 000000000..c127dacfb --- /dev/null +++ b/neopixel/neopixel.go @@ -0,0 +1,80 @@ +// Package neopixel implements a driver for WS2812B/NeoPixel RGB LED strips. +// +// On RP2040/RP2350, it uses PIO for hardware-timed control. +// On other platforms, it falls back to the ws2812 bit-bang driver. +package neopixel // import "tinygo.org/x/drivers/neopixel" + +import ( + "image/color" + "machine" +) + +// Device controls a strip of WS2812B/NeoPixel LEDs. +type Device struct { + pin machine.Pin + pixels []color.RGBA + brightness uint8 + backend backend +} + +// backend is the platform-specific implementation for sending pixel data. +type backend interface { + init(pin machine.Pin) error + writePixels(pixels []color.RGBA, brightness uint8) error +} + +// New creates a new NeoPixel device on the given pin with the specified number of pixels. +func New(pin machine.Pin, numPixels int) (*Device, error) { + d := &Device{ + pin: pin, + pixels: make([]color.RGBA, numPixels), + brightness: 255, + backend: newBackend(), + } + if err := d.backend.init(pin); err != nil { + return nil, err + } + return d, nil +} + +// SetPixel sets the color of pixel at index i. +func (d *Device) SetPixel(i int, c color.RGBA) { + if i >= 0 && i < len(d.pixels) { + d.pixels[i] = c + } +} + +// Show sends the current pixel buffer to the LED strip. +func (d *Device) Show() error { + return d.backend.writePixels(d.pixels, d.brightness) +} + +// SetBrightness sets the global brightness (0-255). +func (d *Device) SetBrightness(b uint8) { + d.brightness = b +} + +// Fill sets all pixels to the given color. +func (d *Device) Fill(c color.RGBA) { + for i := range d.pixels { + d.pixels[i] = c + } +} + +// Clear turns off all pixels. +func (d *Device) Clear() { + d.Fill(color.RGBA{}) +} + +// NumPixels returns the number of pixels in the strip. +func (d *Device) NumPixels() int { + return len(d.pixels) +} + +// applyBrightness scales a color by the brightness value. +func applyBrightness(c color.RGBA, brightness uint8) (r, g, b uint8) { + r = uint8((uint16(c.R) * uint16(brightness)) >> 8) + g = uint8((uint16(c.G) * uint16(brightness)) >> 8) + b = uint8((uint16(c.B) * uint16(brightness)) >> 8) + return +} diff --git a/neopixel/neopixel_other.go b/neopixel/neopixel_other.go new file mode 100644 index 000000000..5016a7a7c --- /dev/null +++ b/neopixel/neopixel_other.go @@ -0,0 +1,33 @@ +//go:build !rp2040 && !rp2350 + +package neopixel + +import ( + "image/color" + "machine" + + "tinygo.org/x/drivers/ws2812" +) + +type otherBackend struct { + ws ws2812.Device +} + +func newBackend() backend { + return &otherBackend{} +} + +func (b *otherBackend) init(pin machine.Pin) error { + pin.Configure(machine.PinConfig{Mode: machine.PinOutput}) + b.ws = ws2812.NewWS2812(pin) + return nil +} + +func (b *otherBackend) writePixels(pixels []color.RGBA, brightness uint8) error { + adjusted := make([]color.RGBA, len(pixels)) + for i, c := range pixels { + r, g, bl := applyBrightness(c, brightness) + adjusted[i] = color.RGBA{R: r, G: g, B: bl} + } + return b.ws.WriteColors(adjusted) +} diff --git a/neopixel/neopixel_rp2.go b/neopixel/neopixel_rp2.go new file mode 100644 index 000000000..489517932 --- /dev/null +++ b/neopixel/neopixel_rp2.go @@ -0,0 +1,40 @@ +//go:build rp2040 || rp2350 + +package neopixel + +import ( + "image/color" + "machine" + + pio "github.com/tinygo-org/pio/rp2-pio" + "github.com/tinygo-org/pio/rp2-pio/piolib" +) + +type rp2Backend struct { + ws *piolib.WS2812B +} + +func newBackend() backend { + return &rp2Backend{} +} + +func (b *rp2Backend) init(pin machine.Pin) error { + sm, err := pio.PIO0.ClaimStateMachine() + if err != nil { + return err + } + ws, err := piolib.NewWS2812B(sm, pin) + if err != nil { + return err + } + b.ws = ws + return nil +} + +func (b *rp2Backend) writePixels(pixels []color.RGBA, brightness uint8) error { + for _, c := range pixels { + r, g, bl := applyBrightness(c, brightness) + b.ws.PutRGB(r, g, bl) + } + return nil +}