Compare commits

...

1 Commits

Author SHA1 Message Date
Ayke van Laethem f367eabcf4 ws2812: add SPI based implementation
This is useful because it allows using the WS2812 driver on platforms
without bitbanged WS2812 support and it may be the only option if
disabling interrupts is impossible or impractical.
2020-12-27 00:37:09 +01:00
+76
View File
@@ -5,6 +5,8 @@ import (
"errors"
"image/color"
"machine"
"tinygo.org/x/drivers"
)
var errUnknownClockSpeed = errors.New("ws2812: unknown CPU clock speed")
@@ -38,3 +40,77 @@ func (d Device) WriteColors(buf []color.RGBA) error {
}
return nil
}
// DeviceSPI wraps a SPI object for driving a string of WS2812 LEDs.
type DeviceSPI struct {
Bus drivers.SPI
// Use a buffer embedded in the device struct so that at most one allocation
// happens at NewSPI and no allocation during transmission.
buf []byte
}
// NewSPI returns a WS2812 driver using a SPI bus. This SPI bus must already be
// configured at exactly 4MHz otherwise WS2812 won't work properly with it.
//
// The advantage of using a SPI bus over bitbanging is that it doesn't require
// custom assembly for each new platform and that it may avoid needing to
// disable interrupts while sending color data if the SPI peripheral uses DMA.
// The disadvantage is of course that it is limited in which pins can be used
// for WS2812 output.
func NewSPI(bus drivers.SPI) *DeviceSPI {
return &DeviceSPI{
Bus: bus,
}
}
// WriteColors wries the given color slice out using the WS2812 protocol.
// Colors are sent out in the usual GRB format.
func (d *DeviceSPI) WriteColors(buf []color.RGBA) error {
// Each color needs 15 bytes: 5 SPI bits per WS2812 bit with 3*8 WS2812 bits
// per color means 120 SPI bits. In addition to that, an extra 0 byte seems
// to be necessary on nRF5x chips to avoid having the SDO line pulled high
// at the end of the transfer.
if len(d.buf) < len(buf)*15+1 {
d.buf = make([]byte, len(buf)*15+1)
}
for i, color := range buf {
bitBuf := makeSPIBits(color.G)
copy(d.buf[i*15+0:], bitBuf[:])
bitBuf = makeSPIBits(color.R)
copy(d.buf[i*15+5:], bitBuf[:])
bitBuf = makeSPIBits(color.B)
copy(d.buf[i*15+10:], bitBuf[:])
}
return d.Bus.Tx(d.buf, nil)
}
func makeSPIBits(b byte) [5]byte {
// Create a 40 bit bitstring from this one byte.
var bitstring uint64
for i := 0; i < 8; i++ {
bitstring <<= 5
if b&0x80 != 0 {
// 0b11100 means the output is high for 750ns (three high bits at
// 4MHz) and low for 500ns (two low bits). This outputs a 1 bit in
// the custom WS2812 protocol.
bitstring |= 0b11100 // T1H (0b111) + TLD (0b00)
} else {
// 0b10000 means the output is high for 250ns (one high bit at 4MHz)
// and low for 1000ns (four low bits at 4MHz). This outputs a 0 bit
// in the custom WS2812 protocol.
bitstring |= 0b10000 // T0H (0b100) + TLD (0b00)
}
b <<= 1
}
// Create a 5 byte array from this bitstring.
bitstring <<= 7
var buf [5]byte
for i := 0; i < 5; i++ {
buf[i] = byte(bitstring >> 40)
bitstring <<= 8
}
return buf
}