mirror of https://github.com/periph/devices
nrzled: Add SPI driver for Neopixel LEDs (#291)
Add support and tests for WS2812B Neopixel LEDs. This commit has been tested with a strip of 50 LEDs.pull/1/head
parent
b2ab8ea899
commit
7f2de9c07f
@ -0,0 +1,453 @@
|
||||
// Copyright 2016 The Periph Authors. All rights reserved.
|
||||
// Use of this source code is governed under the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package nrzled
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
|
||||
"periph.io/x/periph/conn/display"
|
||||
"periph.io/x/periph/conn/physic"
|
||||
"periph.io/x/periph/conn/spi"
|
||||
)
|
||||
|
||||
// SPIDev represents a strip of WS2812b LEDs as a strip connected over a SPI port.
|
||||
// It accepts a stream of raw RGB pixels and converts it to a bit pattern consistent
|
||||
// with the WS812b protocol.
|
||||
// Includes intensity and temperature correction.
|
||||
type SPIDev struct {
|
||||
s spi.Conn //
|
||||
numPixels int //
|
||||
rawBuf []byte // Raw buffer sent over SPI. Cached to reduce heap fragmentation.
|
||||
pixels []byte // Double buffer of pixels, to enable partial painting via Draw(). Effectively points inside rawBuf.
|
||||
rect image.Rectangle // Device bounds
|
||||
pixelMap map[image.Point]int
|
||||
}
|
||||
|
||||
// NewSPI returns a strip that communicates over SPI to NRZ encoded LEDs.
|
||||
//
|
||||
// Due to the tight timing demands of these LEDs,
|
||||
// the SPI port speed must be a reliable 2.5MHz
|
||||
//
|
||||
// Note that your SPI buffer should be at least 12*num_pixels+2 bytes long
|
||||
func NewSPI(p spi.Port, o *Opts) (*SPIDev, error) {
|
||||
c, err := p.Connect(2500*physic.KiloHertz, spi.Mode3|spi.NoCS, 8)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawBCt := 4 * (3 * o.NumPixels) //3 bytes per pixel, 4 symbol bytes per byte
|
||||
buf := make([]byte, rawBCt+3) //3 bytes for latch. 24*400ns = 9600ns.
|
||||
tail := buf[rawBCt:]
|
||||
for i := range tail {
|
||||
tail[i] = 0x00
|
||||
}
|
||||
|
||||
return &SPIDev{
|
||||
s: c,
|
||||
numPixels: o.NumPixels,
|
||||
rawBuf: buf,
|
||||
pixels: buf[:rawBCt],
|
||||
rect: image.Rect(0, 0, o.NumPixels, 1),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *SPIDev) String() string {
|
||||
return fmt.Sprintf("nrzled: {%d, %s}", d.numPixels, d.s)
|
||||
}
|
||||
|
||||
// ColorModel implements display.Drawer. There's no surprise, it is
|
||||
// color.NRGBAModel.
|
||||
func (d *SPIDev) ColorModel() color.Model {
|
||||
return color.NRGBAModel
|
||||
}
|
||||
|
||||
// Bounds implements display.Drawer. Min is guaranteed to be {0, 0}.
|
||||
func (d *SPIDev) Bounds() image.Rectangle {
|
||||
return d.rect
|
||||
}
|
||||
|
||||
// Draw implements display.Drawer.
|
||||
//
|
||||
// Using something else than image.NRGBA is 10x slower. When using image.NRGBA,
|
||||
// the alpha channel is ignored.
|
||||
func (d *SPIDev) Draw(r image.Rectangle, src image.Image, sp image.Point) error {
|
||||
if r = r.Intersect(d.rect); r.Empty() {
|
||||
return nil
|
||||
}
|
||||
srcR := src.Bounds()
|
||||
srcR.Min = srcR.Min.Add(sp)
|
||||
if dX := r.Dx(); dX < srcR.Dx() {
|
||||
srcR.Max.X = srcR.Min.X + dX
|
||||
}
|
||||
if dY := r.Dy(); dY < srcR.Dy() {
|
||||
srcR.Max.Y = srcR.Min.Y + dY
|
||||
}
|
||||
if srcR.Empty() {
|
||||
return nil
|
||||
}
|
||||
d.rasterImg(d.pixels, r, src, srcR)
|
||||
return d.s.Tx(d.rawBuf, nil)
|
||||
}
|
||||
|
||||
// Write accepts a stream of raw RGB pixels and sends it as WS2812b encoded
|
||||
// stream.
|
||||
func (d *SPIDev) Write(pixels []byte) (int, error) {
|
||||
if len(pixels)%3 != 0 || len(pixels)/3 > d.numPixels {
|
||||
return 0, errors.New("ws2812b: invalid RGB stream length")
|
||||
}
|
||||
// Do not touch footer.
|
||||
d.raster(d.pixels, pixels, false)
|
||||
err := d.s.Tx(d.rawBuf, nil)
|
||||
return len(pixels), err
|
||||
}
|
||||
|
||||
// Halt turns off all the lights.
|
||||
func (d *SPIDev) Halt() error {
|
||||
// Zap out the buffer.
|
||||
for i := range d.pixels {
|
||||
d.pixels[i] = 0x88
|
||||
}
|
||||
return d.s.Tx(d.rawBuf, nil)
|
||||
}
|
||||
|
||||
// raster serializes a buffer of RGB bytes to the WS2812b SPI format.
|
||||
//
|
||||
// It is expected to be given the part where pixels are, not the header nor
|
||||
// footer.
|
||||
//
|
||||
// dst is in WS2812b SPI 32 bits word format. src is in RGB 24 bits, or 32 bits
|
||||
// word format when srcHasAlpha is true. The src alpha channel is ignored in
|
||||
// this case.
|
||||
//
|
||||
// src cannot be longer in pixel count than dst.
|
||||
func (d *SPIDev) raster(dst []byte, src []byte, srcHasAlpha bool) {
|
||||
pBytes := 3
|
||||
if srcHasAlpha {
|
||||
pBytes = 4
|
||||
}
|
||||
length := len(src) / pBytes
|
||||
if l := len(dst) / 4; l < length {
|
||||
length = l
|
||||
}
|
||||
if length == 0 {
|
||||
// Save ourself some unneeded processing.
|
||||
return
|
||||
}
|
||||
stride := 4 //number of spi-bytes in color-byte
|
||||
for i := 0; i < length; i++ {
|
||||
sOff := pBytes * i
|
||||
dOff := 3 * stride * i //3 channels * stride
|
||||
r, g, b := src[sOff], src[sOff+1], src[sOff+2]
|
||||
//grb color order, msb first
|
||||
copy(dst[dOff+stride*0:dOff+stride*1], bitlut[r])
|
||||
copy(dst[dOff+stride*1:dOff+stride*2], bitlut[g])
|
||||
copy(dst[dOff+stride*2:dOff+stride*3], bitlut[b])
|
||||
}
|
||||
}
|
||||
|
||||
// rasterImg is the generic version of raster that converts an image instead of raw RGB values.
|
||||
//
|
||||
// It has 'fast paths' for image.RGBA and image.NRGBA that extract and convert the RGB values
|
||||
// directly. For other image types, it converts to image.RGBA and then does the same. In all
|
||||
// cases, alpha values are ignored.
|
||||
//
|
||||
// rect specifies where into the output buffer to draw.
|
||||
//
|
||||
// srcR specifies what portion of the source image to use.
|
||||
func (d *SPIDev) rasterImg(dst []byte, rect image.Rectangle, src image.Image, srcR image.Rectangle) {
|
||||
// Render directly into the buffer for maximum performance and to keep
|
||||
// untouched sections intact.
|
||||
switch im := src.(type) {
|
||||
case *image.RGBA:
|
||||
start := im.PixOffset(srcR.Min.X, srcR.Min.Y)
|
||||
// srcR.Min.Y since the output display has only a single column
|
||||
end := im.PixOffset(srcR.Max.X, srcR.Min.Y)
|
||||
// Offset into the output buffer using rect
|
||||
d.raster(dst[4*rect.Min.X:], im.Pix[start:end], true)
|
||||
case *image.NRGBA:
|
||||
// Ignores alpha
|
||||
start := im.PixOffset(srcR.Min.X, srcR.Min.Y)
|
||||
// srcR.Min.Y since the output display has only a single column
|
||||
end := im.PixOffset(srcR.Max.X, srcR.Min.Y)
|
||||
// Offset into the output buffer using rect
|
||||
d.raster(dst[4*rect.Min.X:], im.Pix[start:end], true)
|
||||
default:
|
||||
// Slow path. Convert to RGBA
|
||||
b := im.Bounds()
|
||||
m := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()))
|
||||
draw.Draw(m, m.Bounds(), src, b.Min, draw.Src)
|
||||
start := m.PixOffset(srcR.Min.X, srcR.Min.Y)
|
||||
// srcR.Min.Y since the output display has only a single column
|
||||
end := m.PixOffset(srcR.Max.X, srcR.Min.Y)
|
||||
// Offset into the output buffer using rect
|
||||
d.raster(dst[4*rect.Min.X:], m.Pix[start:end], true)
|
||||
}
|
||||
}
|
||||
|
||||
var _ display.Drawer = &Dev{}
|
||||
|
||||
// The bit lookup table converts a single byte into its 4 byte SPI symbol
|
||||
// 0 => 1000 and 1 => 1110
|
||||
var bitlut = [][]byte{
|
||||
{0x88, 0x88, 0x88, 0x88}, // 0 | 0x00 | 10001000100010001000100010001000
|
||||
{0x88, 0x88, 0x88, 0x8E}, // 1 | 0x01 | 10001000100010001000100010001110
|
||||
{0x88, 0x88, 0x88, 0xE8}, // 2 | 0x02 | 10001000100010001000100011101000
|
||||
{0x88, 0x88, 0x88, 0xEE}, // 3 | 0x03 | 10001000100010001000100011101110
|
||||
{0x88, 0x88, 0x8E, 0x88}, // 4 | 0x04 | 10001000100010001000111010001000
|
||||
{0x88, 0x88, 0x8E, 0x8E}, // 5 | 0x05 | 10001000100010001000111010001110
|
||||
{0x88, 0x88, 0x8E, 0xE8}, // 6 | 0x06 | 10001000100010001000111011101000
|
||||
{0x88, 0x88, 0x8E, 0xEE}, // 7 | 0x07 | 10001000100010001000111011101110
|
||||
{0x88, 0x88, 0xE8, 0x88}, // 8 | 0x08 | 10001000100010001110100010001000
|
||||
{0x88, 0x88, 0xE8, 0x8E}, // 9 | 0x09 | 10001000100010001110100010001110
|
||||
{0x88, 0x88, 0xE8, 0xE8}, // 10 | 0x0A | 10001000100010001110100011101000
|
||||
{0x88, 0x88, 0xE8, 0xEE}, // 11 | 0x0B | 10001000100010001110100011101110
|
||||
{0x88, 0x88, 0xEE, 0x88}, // 12 | 0x0C | 10001000100010001110111010001000
|
||||
{0x88, 0x88, 0xEE, 0x8E}, // 13 | 0x0D | 10001000100010001110111010001110
|
||||
{0x88, 0x88, 0xEE, 0xE8}, // 14 | 0x0E | 10001000100010001110111011101000
|
||||
{0x88, 0x88, 0xEE, 0xEE}, // 15 | 0x0F | 10001000100010001110111011101110
|
||||
{0x88, 0x8E, 0x88, 0x88}, // 16 | 0x10 | 10001000100011101000100010001000
|
||||
{0x88, 0x8E, 0x88, 0x8E}, // 17 | 0x11 | 10001000100011101000100010001110
|
||||
{0x88, 0x8E, 0x88, 0xE8}, // 18 | 0x12 | 10001000100011101000100011101000
|
||||
{0x88, 0x8E, 0x88, 0xEE}, // 19 | 0x13 | 10001000100011101000100011101110
|
||||
{0x88, 0x8E, 0x8E, 0x88}, // 20 | 0x14 | 10001000100011101000111010001000
|
||||
{0x88, 0x8E, 0x8E, 0x8E}, // 21 | 0x15 | 10001000100011101000111010001110
|
||||
{0x88, 0x8E, 0x8E, 0xE8}, // 22 | 0x16 | 10001000100011101000111011101000
|
||||
{0x88, 0x8E, 0x8E, 0xEE}, // 23 | 0x17 | 10001000100011101000111011101110
|
||||
{0x88, 0x8E, 0xE8, 0x88}, // 24 | 0x18 | 10001000100011101110100010001000
|
||||
{0x88, 0x8E, 0xE8, 0x8E}, // 25 | 0x19 | 10001000100011101110100010001110
|
||||
{0x88, 0x8E, 0xE8, 0xE8}, // 26 | 0x1A | 10001000100011101110100011101000
|
||||
{0x88, 0x8E, 0xE8, 0xEE}, // 27 | 0x1B | 10001000100011101110100011101110
|
||||
{0x88, 0x8E, 0xEE, 0x88}, // 28 | 0x1C | 10001000100011101110111010001000
|
||||
{0x88, 0x8E, 0xEE, 0x8E}, // 29 | 0x1D | 10001000100011101110111010001110
|
||||
{0x88, 0x8E, 0xEE, 0xE8}, // 30 | 0x1E | 10001000100011101110111011101000
|
||||
{0x88, 0x8E, 0xEE, 0xEE}, // 31 | 0x1F | 10001000100011101110111011101110
|
||||
{0x88, 0xE8, 0x88, 0x88}, // 32 | 0x20 | 10001000111010001000100010001000
|
||||
{0x88, 0xE8, 0x88, 0x8E}, // 33 | 0x21 | 10001000111010001000100010001110
|
||||
{0x88, 0xE8, 0x88, 0xE8}, // 34 | 0x22 | 10001000111010001000100011101000
|
||||
{0x88, 0xE8, 0x88, 0xEE}, // 35 | 0x23 | 10001000111010001000100011101110
|
||||
{0x88, 0xE8, 0x8E, 0x88}, // 36 | 0x24 | 10001000111010001000111010001000
|
||||
{0x88, 0xE8, 0x8E, 0x8E}, // 37 | 0x25 | 10001000111010001000111010001110
|
||||
{0x88, 0xE8, 0x8E, 0xE8}, // 38 | 0x26 | 10001000111010001000111011101000
|
||||
{0x88, 0xE8, 0x8E, 0xEE}, // 39 | 0x27 | 10001000111010001000111011101110
|
||||
{0x88, 0xE8, 0xE8, 0x88}, // 40 | 0x28 | 10001000111010001110100010001000
|
||||
{0x88, 0xE8, 0xE8, 0x8E}, // 41 | 0x29 | 10001000111010001110100010001110
|
||||
{0x88, 0xE8, 0xE8, 0xE8}, // 42 | 0x2A | 10001000111010001110100011101000
|
||||
{0x88, 0xE8, 0xE8, 0xEE}, // 43 | 0x2B | 10001000111010001110100011101110
|
||||
{0x88, 0xE8, 0xEE, 0x88}, // 44 | 0x2C | 10001000111010001110111010001000
|
||||
{0x88, 0xE8, 0xEE, 0x8E}, // 45 | 0x2D | 10001000111010001110111010001110
|
||||
{0x88, 0xE8, 0xEE, 0xE8}, // 46 | 0x2E | 10001000111010001110111011101000
|
||||
{0x88, 0xE8, 0xEE, 0xEE}, // 47 | 0x2F | 10001000111010001110111011101110
|
||||
{0x88, 0xEE, 0x88, 0x88}, // 48 | 0x30 | 10001000111011101000100010001000
|
||||
{0x88, 0xEE, 0x88, 0x8E}, // 49 | 0x31 | 10001000111011101000100010001110
|
||||
{0x88, 0xEE, 0x88, 0xE8}, // 50 | 0x32 | 10001000111011101000100011101000
|
||||
{0x88, 0xEE, 0x88, 0xEE}, // 51 | 0x33 | 10001000111011101000100011101110
|
||||
{0x88, 0xEE, 0x8E, 0x88}, // 52 | 0x34 | 10001000111011101000111010001000
|
||||
{0x88, 0xEE, 0x8E, 0x8E}, // 53 | 0x35 | 10001000111011101000111010001110
|
||||
{0x88, 0xEE, 0x8E, 0xE8}, // 54 | 0x36 | 10001000111011101000111011101000
|
||||
{0x88, 0xEE, 0x8E, 0xEE}, // 55 | 0x37 | 10001000111011101000111011101110
|
||||
{0x88, 0xEE, 0xE8, 0x88}, // 56 | 0x38 | 10001000111011101110100010001000
|
||||
{0x88, 0xEE, 0xE8, 0x8E}, // 57 | 0x39 | 10001000111011101110100010001110
|
||||
{0x88, 0xEE, 0xE8, 0xE8}, // 58 | 0x3A | 10001000111011101110100011101000
|
||||
{0x88, 0xEE, 0xE8, 0xEE}, // 59 | 0x3B | 10001000111011101110100011101110
|
||||
{0x88, 0xEE, 0xEE, 0x88}, // 60 | 0x3C | 10001000111011101110111010001000
|
||||
{0x88, 0xEE, 0xEE, 0x8E}, // 61 | 0x3D | 10001000111011101110111010001110
|
||||
{0x88, 0xEE, 0xEE, 0xE8}, // 62 | 0x3E | 10001000111011101110111011101000
|
||||
{0x88, 0xEE, 0xEE, 0xEE}, // 63 | 0x3F | 10001000111011101110111011101110
|
||||
{0x8E, 0x88, 0x88, 0x88}, // 64 | 0x40 | 10001110100010001000100010001000
|
||||
{0x8E, 0x88, 0x88, 0x8E}, // 65 | 0x41 | 10001110100010001000100010001110
|
||||
{0x8E, 0x88, 0x88, 0xE8}, // 66 | 0x42 | 10001110100010001000100011101000
|
||||
{0x8E, 0x88, 0x88, 0xEE}, // 67 | 0x43 | 10001110100010001000100011101110
|
||||
{0x8E, 0x88, 0x8E, 0x88}, // 68 | 0x44 | 10001110100010001000111010001000
|
||||
{0x8E, 0x88, 0x8E, 0x8E}, // 69 | 0x45 | 10001110100010001000111010001110
|
||||
{0x8E, 0x88, 0x8E, 0xE8}, // 70 | 0x46 | 10001110100010001000111011101000
|
||||
{0x8E, 0x88, 0x8E, 0xEE}, // 71 | 0x47 | 10001110100010001000111011101110
|
||||
{0x8E, 0x88, 0xE8, 0x88}, // 72 | 0x48 | 10001110100010001110100010001000
|
||||
{0x8E, 0x88, 0xE8, 0x8E}, // 73 | 0x49 | 10001110100010001110100010001110
|
||||
{0x8E, 0x88, 0xE8, 0xE8}, // 74 | 0x4A | 10001110100010001110100011101000
|
||||
{0x8E, 0x88, 0xE8, 0xEE}, // 75 | 0x4B | 10001110100010001110100011101110
|
||||
{0x8E, 0x88, 0xEE, 0x88}, // 76 | 0x4C | 10001110100010001110111010001000
|
||||
{0x8E, 0x88, 0xEE, 0x8E}, // 77 | 0x4D | 10001110100010001110111010001110
|
||||
{0x8E, 0x88, 0xEE, 0xE8}, // 78 | 0x4E | 10001110100010001110111011101000
|
||||
{0x8E, 0x88, 0xEE, 0xEE}, // 79 | 0x4F | 10001110100010001110111011101110
|
||||
{0x8E, 0x8E, 0x88, 0x88}, // 80 | 0x50 | 10001110100011101000100010001000
|
||||
{0x8E, 0x8E, 0x88, 0x8E}, // 81 | 0x51 | 10001110100011101000100010001110
|
||||
{0x8E, 0x8E, 0x88, 0xE8}, // 82 | 0x52 | 10001110100011101000100011101000
|
||||
{0x8E, 0x8E, 0x88, 0xEE}, // 83 | 0x53 | 10001110100011101000100011101110
|
||||
{0x8E, 0x8E, 0x8E, 0x88}, // 84 | 0x54 | 10001110100011101000111010001000
|
||||
{0x8E, 0x8E, 0x8E, 0x8E}, // 85 | 0x55 | 10001110100011101000111010001110
|
||||
{0x8E, 0x8E, 0x8E, 0xE8}, // 86 | 0x56 | 10001110100011101000111011101000
|
||||
{0x8E, 0x8E, 0x8E, 0xEE}, // 87 | 0x57 | 10001110100011101000111011101110
|
||||
{0x8E, 0x8E, 0xE8, 0x88}, // 88 | 0x58 | 10001110100011101110100010001000
|
||||
{0x8E, 0x8E, 0xE8, 0x8E}, // 89 | 0x59 | 10001110100011101110100010001110
|
||||
{0x8E, 0x8E, 0xE8, 0xE8}, // 90 | 0x5A | 10001110100011101110100011101000
|
||||
{0x8E, 0x8E, 0xE8, 0xEE}, // 91 | 0x5B | 10001110100011101110100011101110
|
||||
{0x8E, 0x8E, 0xEE, 0x88}, // 92 | 0x5C | 10001110100011101110111010001000
|
||||
{0x8E, 0x8E, 0xEE, 0x8E}, // 93 | 0x5D | 10001110100011101110111010001110
|
||||
{0x8E, 0x8E, 0xEE, 0xE8}, // 94 | 0x5E | 10001110100011101110111011101000
|
||||
{0x8E, 0x8E, 0xEE, 0xEE}, // 95 | 0x5F | 10001110100011101110111011101110
|
||||
{0x8E, 0xE8, 0x88, 0x88}, // 96 | 0x60 | 10001110111010001000100010001000
|
||||
{0x8E, 0xE8, 0x88, 0x8E}, // 97 | 0x61 | 10001110111010001000100010001110
|
||||
{0x8E, 0xE8, 0x88, 0xE8}, // 98 | 0x62 | 10001110111010001000100011101000
|
||||
{0x8E, 0xE8, 0x88, 0xEE}, // 99 | 0x63 | 10001110111010001000100011101110
|
||||
{0x8E, 0xE8, 0x8E, 0x88}, //100 | 0x64 | 10001110111010001000111010001000
|
||||
{0x8E, 0xE8, 0x8E, 0x8E}, //101 | 0x65 | 10001110111010001000111010001110
|
||||
{0x8E, 0xE8, 0x8E, 0xE8}, //102 | 0x66 | 10001110111010001000111011101000
|
||||
{0x8E, 0xE8, 0x8E, 0xEE}, //103 | 0x67 | 10001110111010001000111011101110
|
||||
{0x8E, 0xE8, 0xE8, 0x88}, //104 | 0x68 | 10001110111010001110100010001000
|
||||
{0x8E, 0xE8, 0xE8, 0x8E}, //105 | 0x69 | 10001110111010001110100010001110
|
||||
{0x8E, 0xE8, 0xE8, 0xE8}, //106 | 0x6A | 10001110111010001110100011101000
|
||||
{0x8E, 0xE8, 0xE8, 0xEE}, //107 | 0x6B | 10001110111010001110100011101110
|
||||
{0x8E, 0xE8, 0xEE, 0x88}, //108 | 0x6C | 10001110111010001110111010001000
|
||||
{0x8E, 0xE8, 0xEE, 0x8E}, //109 | 0x6D | 10001110111010001110111010001110
|
||||
{0x8E, 0xE8, 0xEE, 0xE8}, //110 | 0x6E | 10001110111010001110111011101000
|
||||
{0x8E, 0xE8, 0xEE, 0xEE}, //111 | 0x6F | 10001110111010001110111011101110
|
||||
{0x8E, 0xEE, 0x88, 0x88}, //112 | 0x70 | 10001110111011101000100010001000
|
||||
{0x8E, 0xEE, 0x88, 0x8E}, //113 | 0x71 | 10001110111011101000100010001110
|
||||
{0x8E, 0xEE, 0x88, 0xE8}, //114 | 0x72 | 10001110111011101000100011101000
|
||||
{0x8E, 0xEE, 0x88, 0xEE}, //115 | 0x73 | 10001110111011101000100011101110
|
||||
{0x8E, 0xEE, 0x8E, 0x88}, //116 | 0x74 | 10001110111011101000111010001000
|
||||
{0x8E, 0xEE, 0x8E, 0x8E}, //117 | 0x75 | 10001110111011101000111010001110
|
||||
{0x8E, 0xEE, 0x8E, 0xE8}, //118 | 0x76 | 10001110111011101000111011101000
|
||||
{0x8E, 0xEE, 0x8E, 0xEE}, //119 | 0x77 | 10001110111011101000111011101110
|
||||
{0x8E, 0xEE, 0xE8, 0x88}, //120 | 0x78 | 10001110111011101110100010001000
|
||||
{0x8E, 0xEE, 0xE8, 0x8E}, //121 | 0x79 | 10001110111011101110100010001110
|
||||
{0x8E, 0xEE, 0xE8, 0xE8}, //122 | 0x7A | 10001110111011101110100011101000
|
||||
{0x8E, 0xEE, 0xE8, 0xEE}, //123 | 0x7B | 10001110111011101110100011101110
|
||||
{0x8E, 0xEE, 0xEE, 0x88}, //124 | 0x7C | 10001110111011101110111010001000
|
||||
{0x8E, 0xEE, 0xEE, 0x8E}, //125 | 0x7D | 10001110111011101110111010001110
|
||||
{0x8E, 0xEE, 0xEE, 0xE8}, //126 | 0x7E | 10001110111011101110111011101000
|
||||
{0x8E, 0xEE, 0xEE, 0xEE}, //127 | 0x7F | 10001110111011101110111011101110
|
||||
{0xE8, 0x88, 0x88, 0x88}, //128 | 0x80 | 11101000100010001000100010001000
|
||||
{0xE8, 0x88, 0x88, 0x8E}, //129 | 0x81 | 11101000100010001000100010001110
|
||||
{0xE8, 0x88, 0x88, 0xE8}, //130 | 0x82 | 11101000100010001000100011101000
|
||||
{0xE8, 0x88, 0x88, 0xEE}, //131 | 0x83 | 11101000100010001000100011101110
|
||||
{0xE8, 0x88, 0x8E, 0x88}, //132 | 0x84 | 11101000100010001000111010001000
|
||||
{0xE8, 0x88, 0x8E, 0x8E}, //133 | 0x85 | 11101000100010001000111010001110
|
||||
{0xE8, 0x88, 0x8E, 0xE8}, //134 | 0x86 | 11101000100010001000111011101000
|
||||
{0xE8, 0x88, 0x8E, 0xEE}, //135 | 0x87 | 11101000100010001000111011101110
|
||||
{0xE8, 0x88, 0xE8, 0x88}, //136 | 0x88 | 11101000100010001110100010001000
|
||||
{0xE8, 0x88, 0xE8, 0x8E}, //137 | 0x89 | 11101000100010001110100010001110
|
||||
{0xE8, 0x88, 0xE8, 0xE8}, //138 | 0x8A | 11101000100010001110100011101000
|
||||
{0xE8, 0x88, 0xE8, 0xEE}, //139 | 0x8B | 11101000100010001110100011101110
|
||||
{0xE8, 0x88, 0xEE, 0x88}, //140 | 0x8C | 11101000100010001110111010001000
|
||||
{0xE8, 0x88, 0xEE, 0x8E}, //141 | 0x8D | 11101000100010001110111010001110
|
||||
{0xE8, 0x88, 0xEE, 0xE8}, //142 | 0x8E | 11101000100010001110111011101000
|
||||
{0xE8, 0x88, 0xEE, 0xEE}, //143 | 0x8F | 11101000100010001110111011101110
|
||||
{0xE8, 0x8E, 0x88, 0x88}, //144 | 0x90 | 11101000100011101000100010001000
|
||||
{0xE8, 0x8E, 0x88, 0x8E}, //145 | 0x91 | 11101000100011101000100010001110
|
||||
{0xE8, 0x8E, 0x88, 0xE8}, //146 | 0x92 | 11101000100011101000100011101000
|
||||
{0xE8, 0x8E, 0x88, 0xEE}, //147 | 0x93 | 11101000100011101000100011101110
|
||||
{0xE8, 0x8E, 0x8E, 0x88}, //148 | 0x94 | 11101000100011101000111010001000
|
||||
{0xE8, 0x8E, 0x8E, 0x8E}, //149 | 0x95 | 11101000100011101000111010001110
|
||||
{0xE8, 0x8E, 0x8E, 0xE8}, //150 | 0x96 | 11101000100011101000111011101000
|
||||
{0xE8, 0x8E, 0x8E, 0xEE}, //151 | 0x97 | 11101000100011101000111011101110
|
||||
{0xE8, 0x8E, 0xE8, 0x88}, //152 | 0x98 | 11101000100011101110100010001000
|
||||
{0xE8, 0x8E, 0xE8, 0x8E}, //153 | 0x99 | 11101000100011101110100010001110
|
||||
{0xE8, 0x8E, 0xE8, 0xE8}, //154 | 0x9A | 11101000100011101110100011101000
|
||||
{0xE8, 0x8E, 0xE8, 0xEE}, //155 | 0x9B | 11101000100011101110100011101110
|
||||
{0xE8, 0x8E, 0xEE, 0x88}, //156 | 0x9C | 11101000100011101110111010001000
|
||||
{0xE8, 0x8E, 0xEE, 0x8E}, //157 | 0x9D | 11101000100011101110111010001110
|
||||
{0xE8, 0x8E, 0xEE, 0xE8}, //158 | 0x9E | 11101000100011101110111011101000
|
||||
{0xE8, 0x8E, 0xEE, 0xEE}, //159 | 0x9F | 11101000100011101110111011101110
|
||||
{0xE8, 0xE8, 0x88, 0x88}, //160 | 0xA0 | 11101000111010001000100010001000
|
||||
{0xE8, 0xE8, 0x88, 0x8E}, //161 | 0xA1 | 11101000111010001000100010001110
|
||||
{0xE8, 0xE8, 0x88, 0xE8}, //162 | 0xA2 | 11101000111010001000100011101000
|
||||
{0xE8, 0xE8, 0x88, 0xEE}, //163 | 0xA3 | 11101000111010001000100011101110
|
||||
{0xE8, 0xE8, 0x8E, 0x88}, //164 | 0xA4 | 11101000111010001000111010001000
|
||||
{0xE8, 0xE8, 0x8E, 0x8E}, //165 | 0xA5 | 11101000111010001000111010001110
|
||||
{0xE8, 0xE8, 0x8E, 0xE8}, //166 | 0xA6 | 11101000111010001000111011101000
|
||||
{0xE8, 0xE8, 0x8E, 0xEE}, //167 | 0xA7 | 11101000111010001000111011101110
|
||||
{0xE8, 0xE8, 0xE8, 0x88}, //168 | 0xA8 | 11101000111010001110100010001000
|
||||
{0xE8, 0xE8, 0xE8, 0x8E}, //169 | 0xA9 | 11101000111010001110100010001110
|
||||
{0xE8, 0xE8, 0xE8, 0xE8}, //170 | 0xAA | 11101000111010001110100011101000
|
||||
{0xE8, 0xE8, 0xE8, 0xEE}, //171 | 0xAB | 11101000111010001110100011101110
|
||||
{0xE8, 0xE8, 0xEE, 0x88}, //172 | 0xAC | 11101000111010001110111010001000
|
||||
{0xE8, 0xE8, 0xEE, 0x8E}, //173 | 0xAD | 11101000111010001110111010001110
|
||||
{0xE8, 0xE8, 0xEE, 0xE8}, //174 | 0xAE | 11101000111010001110111011101000
|
||||
{0xE8, 0xE8, 0xEE, 0xEE}, //175 | 0xAF | 11101000111010001110111011101110
|
||||
{0xE8, 0xEE, 0x88, 0x88}, //176 | 0xB0 | 11101000111011101000100010001000
|
||||
{0xE8, 0xEE, 0x88, 0x8E}, //177 | 0xB1 | 11101000111011101000100010001110
|
||||
{0xE8, 0xEE, 0x88, 0xE8}, //178 | 0xB2 | 11101000111011101000100011101000
|
||||
{0xE8, 0xEE, 0x88, 0xEE}, //179 | 0xB3 | 11101000111011101000100011101110
|
||||
{0xE8, 0xEE, 0x8E, 0x88}, //180 | 0xB4 | 11101000111011101000111010001000
|
||||
{0xE8, 0xEE, 0x8E, 0x8E}, //181 | 0xB5 | 11101000111011101000111010001110
|
||||
{0xE8, 0xEE, 0x8E, 0xE8}, //182 | 0xB6 | 11101000111011101000111011101000
|
||||
{0xE8, 0xEE, 0x8E, 0xEE}, //183 | 0xB7 | 11101000111011101000111011101110
|
||||
{0xE8, 0xEE, 0xE8, 0x88}, //184 | 0xB8 | 11101000111011101110100010001000
|
||||
{0xE8, 0xEE, 0xE8, 0x8E}, //185 | 0xB9 | 11101000111011101110100010001110
|
||||
{0xE8, 0xEE, 0xE8, 0xE8}, //186 | 0xBA | 11101000111011101110100011101000
|
||||
{0xE8, 0xEE, 0xE8, 0xEE}, //187 | 0xBB | 11101000111011101110100011101110
|
||||
{0xE8, 0xEE, 0xEE, 0x88}, //188 | 0xBC | 11101000111011101110111010001000
|
||||
{0xE8, 0xEE, 0xEE, 0x8E}, //189 | 0xBD | 11101000111011101110111010001110
|
||||
{0xE8, 0xEE, 0xEE, 0xE8}, //190 | 0xBE | 11101000111011101110111011101000
|
||||
{0xE8, 0xEE, 0xEE, 0xEE}, //191 | 0xBF | 11101000111011101110111011101110
|
||||
{0xEE, 0x88, 0x88, 0x88}, //192 | 0xC0 | 11101110100010001000100010001000
|
||||
{0xEE, 0x88, 0x88, 0x8E}, //193 | 0xC1 | 11101110100010001000100010001110
|
||||
{0xEE, 0x88, 0x88, 0xE8}, //194 | 0xC2 | 11101110100010001000100011101000
|
||||
{0xEE, 0x88, 0x88, 0xEE}, //195 | 0xC3 | 11101110100010001000100011101110
|
||||
{0xEE, 0x88, 0x8E, 0x88}, //196 | 0xC4 | 11101110100010001000111010001000
|
||||
{0xEE, 0x88, 0x8E, 0x8E}, //197 | 0xC5 | 11101110100010001000111010001110
|
||||
{0xEE, 0x88, 0x8E, 0xE8}, //198 | 0xC6 | 11101110100010001000111011101000
|
||||
{0xEE, 0x88, 0x8E, 0xEE}, //199 | 0xC7 | 11101110100010001000111011101110
|
||||
{0xEE, 0x88, 0xE8, 0x88}, //200 | 0xC8 | 11101110100010001110100010001000
|
||||
{0xEE, 0x88, 0xE8, 0x8E}, //201 | 0xC9 | 11101110100010001110100010001110
|
||||
{0xEE, 0x88, 0xE8, 0xE8}, //202 | 0xCA | 11101110100010001110100011101000
|
||||
{0xEE, 0x88, 0xE8, 0xEE}, //203 | 0xCB | 11101110100010001110100011101110
|
||||
{0xEE, 0x88, 0xEE, 0x88}, //204 | 0xCC | 11101110100010001110111010001000
|
||||
{0xEE, 0x88, 0xEE, 0x8E}, //205 | 0xCD | 11101110100010001110111010001110
|
||||
{0xEE, 0x88, 0xEE, 0xE8}, //206 | 0xCE | 11101110100010001110111011101000
|
||||
{0xEE, 0x88, 0xEE, 0xEE}, //207 | 0xCF | 11101110100010001110111011101110
|
||||
{0xEE, 0x8E, 0x88, 0x88}, //208 | 0xD0 | 11101110100011101000100010001000
|
||||
{0xEE, 0x8E, 0x88, 0x8E}, //209 | 0xD1 | 11101110100011101000100010001110
|
||||
{0xEE, 0x8E, 0x88, 0xE8}, //210 | 0xD2 | 11101110100011101000100011101000
|
||||
{0xEE, 0x8E, 0x88, 0xEE}, //211 | 0xD3 | 11101110100011101000100011101110
|
||||
{0xEE, 0x8E, 0x8E, 0x88}, //212 | 0xD4 | 11101110100011101000111010001000
|
||||
{0xEE, 0x8E, 0x8E, 0x8E}, //213 | 0xD5 | 11101110100011101000111010001110
|
||||
{0xEE, 0x8E, 0x8E, 0xE8}, //214 | 0xD6 | 11101110100011101000111011101000
|
||||
{0xEE, 0x8E, 0x8E, 0xEE}, //215 | 0xD7 | 11101110100011101000111011101110
|
||||
{0xEE, 0x8E, 0xE8, 0x88}, //216 | 0xD8 | 11101110100011101110100010001000
|
||||
{0xEE, 0x8E, 0xE8, 0x8E}, //217 | 0xD9 | 11101110100011101110100010001110
|
||||
{0xEE, 0x8E, 0xE8, 0xE8}, //218 | 0xDA | 11101110100011101110100011101000
|
||||
{0xEE, 0x8E, 0xE8, 0xEE}, //219 | 0xDB | 11101110100011101110100011101110
|
||||
{0xEE, 0x8E, 0xEE, 0x88}, //220 | 0xDC | 11101110100011101110111010001000
|
||||
{0xEE, 0x8E, 0xEE, 0x8E}, //221 | 0xDD | 11101110100011101110111010001110
|
||||
{0xEE, 0x8E, 0xEE, 0xE8}, //222 | 0xDE | 11101110100011101110111011101000
|
||||
{0xEE, 0x8E, 0xEE, 0xEE}, //223 | 0xDF | 11101110100011101110111011101110
|
||||
{0xEE, 0xE8, 0x88, 0x88}, //224 | 0xE0 | 11101110111010001000100010001000
|
||||
{0xEE, 0xE8, 0x88, 0x8E}, //225 | 0xE1 | 11101110111010001000100010001110
|
||||
{0xEE, 0xE8, 0x88, 0xE8}, //226 | 0xE2 | 11101110111010001000100011101000
|
||||
{0xEE, 0xE8, 0x88, 0xEE}, //227 | 0xE3 | 11101110111010001000100011101110
|
||||
{0xEE, 0xE8, 0x8E, 0x88}, //228 | 0xE4 | 11101110111010001000111010001000
|
||||
{0xEE, 0xE8, 0x8E, 0x8E}, //229 | 0xE5 | 11101110111010001000111010001110
|
||||
{0xEE, 0xE8, 0x8E, 0xE8}, //230 | 0xE6 | 11101110111010001000111011101000
|
||||
{0xEE, 0xE8, 0x8E, 0xEE}, //231 | 0xE7 | 11101110111010001000111011101110
|
||||
{0xEE, 0xE8, 0xE8, 0x88}, //232 | 0xE8 | 11101110111010001110100010001000
|
||||
{0xEE, 0xE8, 0xE8, 0x8E}, //233 | 0xE9 | 11101110111010001110100010001110
|
||||
{0xEE, 0xE8, 0xE8, 0xE8}, //234 | 0xEA | 11101110111010001110100011101000
|
||||
{0xEE, 0xE8, 0xE8, 0xEE}, //235 | 0xEB | 11101110111010001110100011101110
|
||||
{0xEE, 0xE8, 0xEE, 0x88}, //236 | 0xEC | 11101110111010001110111010001000
|
||||
{0xEE, 0xE8, 0xEE, 0x8E}, //237 | 0xED | 11101110111010001110111010001110
|
||||
{0xEE, 0xE8, 0xEE, 0xE8}, //238 | 0xEE | 11101110111010001110111011101000
|
||||
{0xEE, 0xE8, 0xEE, 0xEE}, //239 | 0xEF | 11101110111010001110111011101110
|
||||
{0xEE, 0xEE, 0x88, 0x88}, //240 | 0xF0 | 11101110111011101000100010001000
|
||||
{0xEE, 0xEE, 0x88, 0x8E}, //241 | 0xF1 | 11101110111011101000100010001110
|
||||
{0xEE, 0xEE, 0x88, 0xE8}, //242 | 0xF2 | 11101110111011101000100011101000
|
||||
{0xEE, 0xEE, 0x88, 0xEE}, //243 | 0xF3 | 11101110111011101000100011101110
|
||||
{0xEE, 0xEE, 0x8E, 0x88}, //244 | 0xF4 | 11101110111011101000111010001000
|
||||
{0xEE, 0xEE, 0x8E, 0x8E}, //245 | 0xF5 | 11101110111011101000111010001110
|
||||
{0xEE, 0xEE, 0x8E, 0xE8}, //246 | 0xF6 | 11101110111011101000111011101000
|
||||
{0xEE, 0xEE, 0x8E, 0xEE}, //247 | 0xF7 | 11101110111011101000111011101110
|
||||
{0xEE, 0xEE, 0xE8, 0x88}, //248 | 0xF8 | 11101110111011101110100010001000
|
||||
{0xEE, 0xEE, 0xE8, 0x8E}, //249 | 0xF9 | 11101110111011101110100010001110
|
||||
{0xEE, 0xEE, 0xE8, 0xE8}, //250 | 0xFA | 11101110111011101110100011101000
|
||||
{0xEE, 0xEE, 0xE8, 0xEE}, //251 | 0xFB | 11101110111011101110100011101110
|
||||
{0xEE, 0xEE, 0xEE, 0x88}, //252 | 0xFC | 11101110111011101110111010001000
|
||||
{0xEE, 0xEE, 0xEE, 0x8E}, //253 | 0xFD | 11101110111011101110111010001110
|
||||
{0xEE, 0xEE, 0xEE, 0xE8}, //254 | 0xFE | 11101110111011101110111011101000
|
||||
{0xEE, 0xEE, 0xEE, 0xEE}, //255 | 0xFF | 11101110111011101110111011101110
|
||||
}
|
||||
@ -0,0 +1,633 @@
|
||||
// Copyright 2016 The Periph Authors. All rights reserved.
|
||||
// Use of this source code is governed under the Apache License, Version 2.0
|
||||
// that can be found in the LICENSE file.
|
||||
|
||||
package nrzled
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"periph.io/x/periph/conn/conntest"
|
||||
"periph.io/x/periph/conn/physic"
|
||||
"periph.io/x/periph/conn/spi"
|
||||
"periph.io/x/periph/conn/spi/spitest"
|
||||
)
|
||||
|
||||
// ToRGB converts a slice of color.NRGBA to a byte stream of RGB pixels.
|
||||
//
|
||||
// Ignores alpha.
|
||||
func ToRGB(p []color.NRGBA) []byte {
|
||||
b := make([]byte, 0, len(p)*3)
|
||||
for _, c := range p {
|
||||
b = append(b, c.R, c.G, c.B)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestDevEmpty(t *testing.T) {
|
||||
buf := bytes.Buffer{}
|
||||
o := Opts{NumPixels: 150}
|
||||
o.NumPixels = 0
|
||||
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &o)
|
||||
if n, err := d.Write([]byte{}); n != 0 || err != nil {
|
||||
t.Fatalf("%d %v", n, err)
|
||||
}
|
||||
if expected := []byte{0x0, 0x0, 0x0}; !bytes.Equal(expected, buf.Bytes()) {
|
||||
t.Fatalf("\nGot: %#02v\nWant: %#02v\n", buf.Bytes(), expected)
|
||||
}
|
||||
if got, expected := d.String(), "nrzled: {0, recordraw}"; got != expected {
|
||||
t.Fatalf("\nGot: %s\nWant: %s\n", got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectFail(t *testing.T) {
|
||||
if d, err := NewSPI(&configFail{}, &Opts{NumPixels: 150}); d != nil || err == nil {
|
||||
t.Fatal("Connect() call have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevLen(t *testing.T) {
|
||||
buf := bytes.Buffer{}
|
||||
o := Opts{NumPixels: 150}
|
||||
o.NumPixels = 1
|
||||
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &o)
|
||||
if n, err := d.Write([]byte{0}); n != 0 || err == nil {
|
||||
t.Fatalf("%d %v", n, err)
|
||||
}
|
||||
if expected := []byte{}; !bytes.Equal(expected, buf.Bytes()) {
|
||||
t.Fatalf("\nGot: %#02v\nWant: %#02v\n", buf.Bytes(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
var writeTests = []struct {
|
||||
name string
|
||||
pixels []byte
|
||||
want []byte
|
||||
opts Opts
|
||||
}{
|
||||
{
|
||||
name: "1 pixel to #FFFFFF",
|
||||
pixels: ToRGB([]color.NRGBA{
|
||||
{0xFF, 0xFF, 0xFF, 0x00},
|
||||
}),
|
||||
want: []byte{
|
||||
/*FF*/ 0xEE, 0xEE, 0xEE, 0xEE /*FF*/, 0xEE, 0xEE, 0xEE, 0xEE /*FF*/, 0xEE, 0xEE, 0xEE, 0xEE,
|
||||
/*EOF*/ 0x00, 0x00, 0x00,
|
||||
},
|
||||
opts: Opts{
|
||||
NumPixels: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 pixel to #FEFEFE",
|
||||
pixels: ToRGB([]color.NRGBA{
|
||||
{0xFE, 0xFE, 0xFE, 0x00},
|
||||
}),
|
||||
want: []byte{
|
||||
/*FE*/ 0xEE, 0xEE, 0xEE, 0xE8 /*FE*/, 0xEE, 0xEE, 0xEE, 0xE8 /*FE*/, 0xEE, 0xEE, 0xEE, 0xE8,
|
||||
/*EOF*/ 0x00, 0x00, 0x00,
|
||||
},
|
||||
opts: Opts{
|
||||
NumPixels: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 pixel to #F0F0F0",
|
||||
pixels: ToRGB([]color.NRGBA{
|
||||
{0xF0, 0xF0, 0xF0, 0x00},
|
||||
}),
|
||||
want: []byte{
|
||||
/*F0*/ 0xEE, 0xEE, 0x88, 0x88 /*F0*/, 0xEE, 0xEE, 0x88, 0x88 /*F0*/, 0xEE, 0xEE, 0x88, 0x88,
|
||||
/*EOF*/ 0x00, 0x00, 0x00,
|
||||
},
|
||||
opts: Opts{
|
||||
NumPixels: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 pixel to #808080",
|
||||
pixels: ToRGB([]color.NRGBA{
|
||||
{0x80, 0x80, 0x80, 0x00},
|
||||
}),
|
||||
want: []byte{
|
||||
/*80*/ 0xE8, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88,
|
||||
/*EOF*/ 0x00, 0x00, 0x00,
|
||||
},
|
||||
opts: Opts{
|
||||
NumPixels: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 pixel to #80FF00",
|
||||
pixels: ToRGB([]color.NRGBA{
|
||||
{0x80, 0xFF, 0x00, 0x00},
|
||||
}),
|
||||
want: []byte{
|
||||
/*80*/ 0xE8, 0x88, 0x88, 0x88 /*FF*/, 0xEE, 0xEE, 0xEE, 0xEE /*00*/, 0x88, 0x88, 0x88, 0x88,
|
||||
/*EOF*/ 0x00, 0x00, 0x00,
|
||||
},
|
||||
opts: Opts{
|
||||
NumPixels: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 pixel to #800000",
|
||||
pixels: ToRGB([]color.NRGBA{
|
||||
{0x80, 0x00, 0x00, 0x00},
|
||||
}),
|
||||
want: []byte{
|
||||
/*80*/ 0xE8, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88,
|
||||
/*EOF*/ 0x00, 0x00, 0x00,
|
||||
},
|
||||
opts: Opts{
|
||||
NumPixels: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 pixel to #008000",
|
||||
pixels: ToRGB([]color.NRGBA{
|
||||
{0x00, 0x80, 0x00, 0x00},
|
||||
}),
|
||||
want: []byte{
|
||||
/*00*/ 0x88, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88,
|
||||
/*EOF*/ 0x00, 0x00, 0x00,
|
||||
},
|
||||
opts: Opts{
|
||||
NumPixels: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 pixel to #000080",
|
||||
pixels: ToRGB([]color.NRGBA{
|
||||
{0x00, 0x00, 0x80, 0x00},
|
||||
}),
|
||||
want: []byte{
|
||||
/*00*/ 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88,
|
||||
/*EOF*/ 0x00, 0x00, 0x00,
|
||||
},
|
||||
opts: Opts{
|
||||
NumPixels: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "All at once",
|
||||
pixels: ToRGB([]color.NRGBA{
|
||||
{0xFF, 0xFF, 0xFF, 0x00},
|
||||
{0xFE, 0xFE, 0xFE, 0x00},
|
||||
{0xF0, 0xF0, 0xF0, 0x00},
|
||||
{0x80, 0x80, 0x80, 0x00},
|
||||
|
||||
{0x80, 0x00, 0x00, 0x00},
|
||||
{0x00, 0x80, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x80, 0x00},
|
||||
|
||||
{0x00, 0x00, 0x10, 0x00},
|
||||
{0x00, 0x00, 0x01, 0x00},
|
||||
{0x00, 0x00, 0x00, 0x00},
|
||||
}),
|
||||
want: []byte{
|
||||
/*FF*/ 0xEE, 0xEE, 0xEE, 0xEE /*FF*/, 0xEE, 0xEE, 0xEE, 0xEE /*FF*/, 0xEE, 0xEE, 0xEE, 0xEE,
|
||||
/*FE*/ 0xEE, 0xEE, 0xEE, 0xE8 /*FE*/, 0xEE, 0xEE, 0xEE, 0xE8 /*FE*/, 0xEE, 0xEE, 0xEE, 0xE8,
|
||||
/*F0*/ 0xEE, 0xEE, 0x88, 0x88 /*F0*/, 0xEE, 0xEE, 0x88, 0x88 /*F0*/, 0xEE, 0xEE, 0x88, 0x88,
|
||||
/*80*/ 0xE8, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88,
|
||||
|
||||
/*80*/ 0xE8, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88,
|
||||
/*00*/ 0x88, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88,
|
||||
/*00*/ 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*80*/, 0xE8, 0x88, 0x88, 0x88,
|
||||
|
||||
/*00*/ 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*10*/, 0x88, 0x8E, 0x88, 0x88,
|
||||
/*00*/ 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*01*/, 0x88, 0x88, 0x88, 0x8E,
|
||||
/*00*/ 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88 /*00*/, 0x88, 0x88, 0x88, 0x88,
|
||||
/*EOF*/ 0x00, 0x00, 0x00,
|
||||
},
|
||||
opts: Opts{
|
||||
NumPixels: 10,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestWrites(t *testing.T) {
|
||||
for _, tt := range writeTests {
|
||||
buf := bytes.Buffer{}
|
||||
tt.opts.NumPixels = len(tt.pixels) / 3
|
||||
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &tt.opts)
|
||||
n, err := d.Write(tt.pixels)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != len(tt.pixels) {
|
||||
t.Fatalf("%s: Got %d bytes result, want %d", tt.name, n, len(tt.pixels)*3)
|
||||
}
|
||||
if got := buf.Bytes(); !bytes.Equal(got, tt.want) {
|
||||
t.Logf("%s:\nGot: (%d)%#02v\nWant: (%d)%#02v\n", tt.name, len(got), got, len(tt.want), tt.want)
|
||||
for i := range tt.want {
|
||||
if got[i] != tt.want[i] {
|
||||
t.Logf("(%d) Got: %#02v\tWant: %#02v\n", i, got[i], tt.want[i])
|
||||
}
|
||||
}
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevColor(t *testing.T) {
|
||||
if (&Dev{}).ColorModel() != color.NRGBAModel {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevLong(t *testing.T) {
|
||||
buf := bytes.Buffer{}
|
||||
colors := make([]color.NRGBA, 256)
|
||||
o := Opts{NumPixels: 150}
|
||||
o.NumPixels = len(colors)
|
||||
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &o)
|
||||
if n, err := d.Write(ToRGB(colors)); n != len(colors)*3 || err != nil {
|
||||
t.Fatalf("%d %v", n, err)
|
||||
}
|
||||
expected := make([]byte, 12*o.NumPixels+3)
|
||||
for i := 0; i < 12*o.NumPixels; i += 12 {
|
||||
//Each channel should be 0x00
|
||||
for j := 0; j < 12; j++ {
|
||||
expected[i+j] = 0x88
|
||||
}
|
||||
}
|
||||
trailer := expected[12*o.NumPixels:]
|
||||
for i := range trailer {
|
||||
trailer[i] = 0x00
|
||||
}
|
||||
if !bytes.Equal(expected, buf.Bytes()) {
|
||||
t.Fatalf("\nGot: %#02v\nWant: %#02v\n", buf.Bytes(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDevWrite_Long(t *testing.T) {
|
||||
buf := bytes.Buffer{}
|
||||
o := Opts{NumPixels: 150}
|
||||
o.NumPixels = 1
|
||||
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &o)
|
||||
if n, err := d.Write([]byte{0, 0, 0, 1, 1, 1}); n != 0 || err == nil {
|
||||
t.Fatal(n, err)
|
||||
}
|
||||
}
|
||||
|
||||
var drawTests = []struct {
|
||||
name string
|
||||
img image.Image
|
||||
want []byte
|
||||
opts Opts
|
||||
}{
|
||||
{
|
||||
name: "Draw NRGBA",
|
||||
img: func() image.Image {
|
||||
im := image.NewNRGBA(image.Rect(0, 0, 4, 1))
|
||||
for i := 0; i < 4; i++ {
|
||||
im.Pix[4*i+0] = 0x00
|
||||
im.Pix[4*i+1] = 0x80
|
||||
im.Pix[4*i+2] = 0xFF
|
||||
im.Pix[4*i+3] = 0
|
||||
}
|
||||
return im
|
||||
}(),
|
||||
want: func() []byte {
|
||||
var b []byte
|
||||
for i := 0; i < 4; i++ {
|
||||
b = append(b, 0x88, 0x88, 0x88, 0x88) //0x00
|
||||
b = append(b, 0xE8, 0x88, 0x88, 0x88) //0x80
|
||||
b = append(b, 0xEE, 0xEE, 0xEE, 0xEE) //0xFF
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
b = append(b, 0x00)
|
||||
}
|
||||
return b
|
||||
}(),
|
||||
opts: Opts{
|
||||
NumPixels: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Draw Empty",
|
||||
img: func() image.Image {
|
||||
im := image.NewNRGBA(image.Rect(0, 0, 0, 0))
|
||||
return im
|
||||
}(),
|
||||
want: func() []byte {
|
||||
var b []byte
|
||||
return b
|
||||
}(),
|
||||
opts: Opts{
|
||||
NumPixels: 4,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestDraws(t *testing.T) {
|
||||
for _, tt := range drawTests {
|
||||
buf := bytes.Buffer{}
|
||||
d, _ := NewSPI(spitest.NewRecordRaw(&buf), &tt.opts)
|
||||
if err := d.Draw(d.Bounds(), tt.img, image.Point{}); err != nil {
|
||||
t.Fatalf("%s: %v", tt.name, err)
|
||||
}
|
||||
got := buf.Bytes()
|
||||
if !bytes.Equal(got, tt.want) {
|
||||
t.Logf("%s:\nGot: (%d)%#02v\nWant: (%d)%#02v\n", tt.name, len(got), got, len(tt.want), tt.want)
|
||||
for i := range tt.want {
|
||||
if got[i] != tt.want[i] {
|
||||
t.Logf("(%d) Got: %#02v\tWant: %#02v\n", i, got[i], tt.want[i])
|
||||
}
|
||||
}
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var offsetDrawWant = []byte{
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0xE1, 0x89, 0x79, 0x6B,
|
||||
0xE1, 0x9A, 0x88, 0x75,
|
||||
0xE1, 0xAD, 0x98, 0x82,
|
||||
0xE1, 0xC2, 0xAB, 0x92,
|
||||
0xE1, 0xDA, 0xC0, 0xA4,
|
||||
0xE1, 0xF5, 0xD9, 0xB9,
|
||||
0xE2, 0x89, 0x7A, 0x69,
|
||||
0xE2, 0x9A, 0x8A, 0x76,
|
||||
0xE2, 0xAC, 0x9B, 0x86,
|
||||
0xE2, 0xC0, 0xAE, 0x98,
|
||||
0xE2, 0xD5, 0xC3, 0xAC,
|
||||
0xE2, 0xED, 0xDA, 0xC2,
|
||||
0xE4, 0x83, 0x7A, 0x6E,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0xFF,
|
||||
}
|
||||
|
||||
var offsetDrawTests = []struct {
|
||||
name string
|
||||
img image.Image
|
||||
point image.Point
|
||||
offset image.Rectangle
|
||||
want []byte
|
||||
opts Opts
|
||||
}{
|
||||
{
|
||||
name: "Offset Draw NRGBA",
|
||||
img: func() image.Image {
|
||||
im := image.NewNRGBA(image.Rect(0, 0, 16, 4))
|
||||
for x := 0; x < 16; x++ {
|
||||
for y := 0; y < 4; y++ {
|
||||
i := (y*16 + x) * 3
|
||||
im.Set(x, y, color.RGBA{R: uint8(i + 1), G: uint8(i + 2), B: uint8(i + 3), A: 0xFF})
|
||||
}
|
||||
}
|
||||
return im
|
||||
}(),
|
||||
point: image.Point{X: 3, Y: 2},
|
||||
offset: image.Rect(0, 0, 16, 1),
|
||||
want: offsetDrawWant,
|
||||
opts: Opts{
|
||||
NumPixels: 15,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Both Offset Draw NRGBA",
|
||||
img: func() image.Image {
|
||||
im := image.NewNRGBA(image.Rect(0, 0, 16, 4))
|
||||
for x := 0; x < 16; x++ {
|
||||
for y := 0; y < 4; y++ {
|
||||
i := (y*16 + x) * 3
|
||||
im.Set(x, y, color.RGBA{R: uint8(i + 1), G: uint8(i + 2), B: uint8(i + 3), A: 0xFF})
|
||||
}
|
||||
}
|
||||
return im
|
||||
}(),
|
||||
point: image.Point{X: 3, Y: 2},
|
||||
offset: image.Rect(2, 0, 16, 1),
|
||||
want: []byte{
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0xE1, 0x89, 0x79, 0x6B,
|
||||
0xE1, 0x9A, 0x88, 0x75,
|
||||
0xE1, 0xAD, 0x98, 0x82,
|
||||
0xE1, 0xC2, 0xAB, 0x92,
|
||||
0xE1, 0xDA, 0xC0, 0xA4,
|
||||
0xE1, 0xF5, 0xD9, 0xB9,
|
||||
0xE2, 0x89, 0x7A, 0x69,
|
||||
0xE2, 0x9A, 0x8A, 0x76,
|
||||
0xE2, 0xAC, 0x9B, 0x86,
|
||||
0xE2, 0xC0, 0xAE, 0x98,
|
||||
0xE2, 0xD5, 0xC3, 0xAC,
|
||||
0xE2, 0xED, 0xDA, 0xC2,
|
||||
0xE4, 0x83, 0x7A, 0x6E,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0xFF, 0xFF,
|
||||
},
|
||||
opts: Opts{
|
||||
NumPixels: 17,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestHalt(t *testing.T) {
|
||||
s := spitest.Playback{
|
||||
Playback: conntest.Playback{
|
||||
DontPanic: false,
|
||||
Count: 1,
|
||||
Ops: []conntest.IO{
|
||||
{},
|
||||
{W: []byte{
|
||||
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
|
||||
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
|
||||
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
|
||||
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,
|
||||
//End of frame
|
||||
0x00, 0x00, 0x00,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
o := Opts{NumPixels: 150}
|
||||
o.NumPixels = 4
|
||||
d, _ := NewSPI(&s, &o)
|
||||
if err := d.Halt(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type genColor func(int) [3]byte
|
||||
|
||||
func benchmarkWrite(b *testing.B, o Opts, length int, f genColor) {
|
||||
var pixels []byte
|
||||
for i := 0; i < length; i++ {
|
||||
c := f(i)
|
||||
pixels = append(pixels, c[:]...)
|
||||
}
|
||||
o.NumPixels = length
|
||||
b.ReportAllocs()
|
||||
d, _ := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o)
|
||||
_, _ = d.Write(pixels[:])
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = d.Write(pixels[:])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWriteWhite(b *testing.B) {
|
||||
o := Opts{NumPixels: 150}
|
||||
benchmarkWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0xFF, 0xFF, 0xFF} })
|
||||
}
|
||||
|
||||
func BenchmarkWriteDim(b *testing.B) {
|
||||
o := Opts{NumPixels: 150}
|
||||
benchmarkWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0x01, 0x01, 0x01} })
|
||||
}
|
||||
|
||||
func BenchmarkWriteBlack(b *testing.B) {
|
||||
o := Opts{NumPixels: 150}
|
||||
benchmarkWrite(b, o, 150, func(i int) [3]byte { return [3]byte{0x0, 0x0, 0x0} })
|
||||
}
|
||||
|
||||
func genColorfulPixel(x int) [3]byte {
|
||||
i := x * 3
|
||||
return [3]byte{uint8(i) + uint8(i>>8),
|
||||
uint8(i+1) + uint8(i+1>>8),
|
||||
uint8(i+2) + uint8(i+2>>8),
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWriteColorful(b *testing.B) {
|
||||
o := Opts{NumPixels: 150}
|
||||
benchmarkWrite(b, o, 150, genColorfulPixel)
|
||||
}
|
||||
|
||||
func BenchmarkWriteColorfulPassThru(b *testing.B) {
|
||||
o := Opts{
|
||||
NumPixels: 150,
|
||||
}
|
||||
benchmarkWrite(b, o, 150, genColorfulPixel)
|
||||
}
|
||||
|
||||
func BenchmarkWriteColorfulVariation(b *testing.B) {
|
||||
// Continuously vary the lookup tables.
|
||||
b.ReportAllocs()
|
||||
pixels := [256 * 3]byte{}
|
||||
for i := range pixels {
|
||||
pixels[i] = uint8(i) + uint8(i>>8)
|
||||
}
|
||||
o := Opts{NumPixels: 150}
|
||||
o.NumPixels = len(pixels) / 3
|
||||
d, _ := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o)
|
||||
_, _ = d.Write(pixels[:])
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = d.Write(pixels[:])
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkDraw(b *testing.B, o Opts, img draw.Image, f genColor) {
|
||||
for x := 0; x < img.Bounds().Dx(); x++ {
|
||||
for y := 0; y < img.Bounds().Dy(); y++ {
|
||||
pix := f(x)
|
||||
c := color.NRGBA{R: pix[0], G: pix[1], B: pix[2], A: 255}
|
||||
img.Set(x, y, c)
|
||||
}
|
||||
}
|
||||
o.NumPixels = img.Bounds().Max.X
|
||||
b.ReportAllocs()
|
||||
d, _ := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o)
|
||||
r := d.Bounds()
|
||||
p := image.Point{}
|
||||
if err := d.Draw(r, img, p); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := d.Draw(r, img, p); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDrawNRGBAColorful(b *testing.B) {
|
||||
o := Opts{NumPixels: 150}
|
||||
benchmarkDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), genColorfulPixel)
|
||||
}
|
||||
|
||||
func BenchmarkDrawNRGBAWhite(b *testing.B) {
|
||||
o := Opts{NumPixels: 150}
|
||||
benchmarkDraw(b, o, image.NewNRGBA(image.Rect(0, 0, 150, 1)), func(i int) [3]byte { return [3]byte{0xFF, 0xFF, 0xFF} })
|
||||
}
|
||||
|
||||
func BenchmarkDrawRGBAColorful(b *testing.B) {
|
||||
o := Opts{NumPixels: 150}
|
||||
benchmarkDraw(b, o, image.NewRGBA(image.Rect(0, 0, 256, 1)), genColorfulPixel)
|
||||
}
|
||||
|
||||
func BenchmarkDrawSlowpath(b *testing.B) {
|
||||
// Should be an image type that doesn't have a fast path
|
||||
img := image.NewGray(image.Rect(0, 0, 150, 1))
|
||||
for x := 0; x < img.Bounds().Dx(); x++ {
|
||||
for y := 0; y < img.Bounds().Dy(); y++ {
|
||||
pix := genColorfulPixel(x)
|
||||
img.Set(x, y, color.Gray{pix[0]})
|
||||
}
|
||||
}
|
||||
o := Opts{NumPixels: 150}
|
||||
o.NumPixels = img.Bounds().Max.X
|
||||
b.ReportAllocs()
|
||||
d, _ := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &o)
|
||||
r := d.Bounds()
|
||||
p := image.Point{}
|
||||
if err := d.Draw(r, img, p); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := d.Draw(r, img, p); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHalt(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
d, _ := NewSPI(spitest.NewRecordRaw(ioutil.Discard), &Opts{NumPixels: 150})
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := d.Halt(); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
type configFail struct {
|
||||
spitest.Record
|
||||
}
|
||||
|
||||
func (c *configFail) Connect(f physic.Frequency, mode spi.Mode, bits int) (spi.Conn, error) {
|
||||
return nil, errors.New("injected error")
|
||||
}
|
||||
|
||||
func equalUint16(a, b []uint16) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Loading…
Reference in New Issue