Skip to content

Commit

Permalink
apa102: avoid creating garbage
Browse files Browse the repository at this point in the history
Avoid creating unnecessary garbage in the following ways:

  * Use the Transfer method instead of the Tx method for single byte
    transfers.
  * Use a statically allocated buffer for the fixed start-of-frame
    sequence.

Running this for a few minutes did not cause the garbage collector to
run. Previously, it would run every second or so in a
persistence-of-vision application.
  • Loading branch information
aykevl authored and deadprogram committed May 26, 2020
1 parent c3f3af5 commit 91539d9
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 15 deletions.
27 changes: 15 additions & 12 deletions apa102/apa102.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const (
GRB
)

var startFrame = []byte{0x00, 0x00, 0x00, 0x00}

// Device wraps APA102 SPI LEDs.
type Device struct {
bus SPI
Expand All @@ -30,6 +32,7 @@ type Device struct {
// SPI from the TinyGo "machine" package implements this already.
type SPI interface {
Tx(w, r []byte) error
Transfer(b byte) (byte, error)
}

// New returns a new APA102 driver. Pass in a fully configured SPI bus.
Expand All @@ -51,22 +54,22 @@ func (d Device) WriteColors(cs []color.RGBA) (n int, err error) {
// write data
for _, c := range cs {
// brightness is scaled to 5 bit value
d.bus.Tx([]byte{0xe0 | (c.A >> 3)}, nil)
d.bus.Transfer(0xe0 | (c.A >> 3))

// set the colors
switch d.Order {
case BRG:
d.bus.Tx([]byte{c.B}, nil)
d.bus.Tx([]byte{c.R}, nil)
d.bus.Tx([]byte{c.G}, nil)
d.bus.Transfer(c.B)
d.bus.Transfer(c.R)
d.bus.Transfer(c.G)
case GRB:
d.bus.Tx([]byte{c.G}, nil)
d.bus.Tx([]byte{c.R}, nil)
d.bus.Tx([]byte{c.B}, nil)
d.bus.Transfer(c.G)
d.bus.Transfer(c.R)
d.bus.Transfer(c.B)
case BGR:
d.bus.Tx([]byte{c.B}, nil)
d.bus.Tx([]byte{c.G}, nil)
d.bus.Tx([]byte{c.R}, nil)
d.bus.Transfer(c.B)
d.bus.Transfer(c.G)
d.bus.Transfer(c.R)
}
}

Expand All @@ -86,14 +89,14 @@ func (d Device) Write(buf []byte) (n int, err error) {

// startFrame sends the start bytes for a strand of LEDs.
func (d Device) startFrame() {
d.bus.Tx([]byte{0x00, 0x00, 0x00, 0x00}, nil)
d.bus.Tx(startFrame, nil)
}

// endFrame sends the end frame marker with one extra bit per LED so
// long strands of LEDs receive the necessary termination for updates.
// See https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/
func (d Device) endFrame(count int) {
for i := 0; i < count/16; i++ {
d.bus.Tx([]byte{0xff}, nil)
d.bus.Transfer(0xff)
}
}
8 changes: 5 additions & 3 deletions apa102/softspi.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ func (s *bbSPI) delay() {
}
}

// Transfer is used to send a single byte.
func (s *bbSPI) Transfer(b byte) {
// Transfer matches signature of machine.SPI.Transfer() and is used to send a
// single byte. The received data is ignored and no error will ever be returned.
func (s *bbSPI) Transfer(b byte) (byte, error) {
for i := uint8(0); i < 8; i++ {

// half clock cycle high to start
Expand All @@ -63,6 +64,7 @@ func (s *bbSPI) Transfer(b byte) {

// for actual SPI would try to read the MISO value here
s.delay()

}

return 0, nil
}

0 comments on commit 91539d9

Please sign in to comment.