mirror of https://github.com/periph/devices
nrzled: add support for ws281x and clones (#165)
- Includes driver and CLI. Completely untested.pull/1/head
parent
7dc1026c65
commit
0b05c62a9e
@ -0,0 +1,22 @@
|
||||
// Copyright 2017 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 is a driver for LEDs ws2811/ws2812/ws2812b and compatible
|
||||
// devices like sk6812 and ucs1903 that uses a single wire NRZ encoded
|
||||
// communication protocol.
|
||||
//
|
||||
// Note that some ICs are 7 bits with the least significant bit ignored, others
|
||||
// are using a real 8 bits PWM. The PWM frequency varies across ICs.
|
||||
//
|
||||
// Datasheet
|
||||
//
|
||||
// This directory contains datasheets for ws2812, ws2812b, ucs190x and various
|
||||
// sk6812.
|
||||
//
|
||||
// https://github.com/cpldcpu/light_ws2812/tree/master/Datasheets
|
||||
//
|
||||
// UCS1903 datasheet
|
||||
//
|
||||
// http://www.bestlightingbuy.com/pdf/UCS1903%20datasheet.pdf
|
||||
package nrzled
|
||||
@ -0,0 +1,223 @@
|
||||
// Copyright 2017 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"
|
||||
"time"
|
||||
|
||||
"periph.io/x/periph/conn"
|
||||
"periph.io/x/periph/conn/gpio/gpiostream"
|
||||
"periph.io/x/periph/devices"
|
||||
)
|
||||
|
||||
// NRZ converts a byte into the Non-Return-to-Zero encoded 24 bits.
|
||||
//
|
||||
// The upper 8 bits are zeros.
|
||||
//
|
||||
// The Non-return-to-zero protocol is a self-clocking signal that enables
|
||||
// one-way communication without the need of a dedicated clock signal, unlike
|
||||
// SPI driven LEDs like the apa102.
|
||||
//
|
||||
// See https://en.wikipedia.org/wiki/Non-return-to-zero for more technical
|
||||
// details.
|
||||
func NRZ(b byte) uint32 {
|
||||
// The stream is 1x01x01x01x01x01x01x01x0 with the x bits being the bits from
|
||||
// `b` in reverse order.
|
||||
out := uint32(0x924924)
|
||||
out |= uint32(b&0x80) << (3*7 + 1 - 7)
|
||||
out |= uint32(b&0x40) << (3*6 + 1 - 6)
|
||||
out |= uint32(b&0x20) << (3*5 + 1 - 5)
|
||||
out |= uint32(b&0x10) << (3*4 + 1 - 4)
|
||||
out |= uint32(b&0x08) << (3*3 + 1 - 3)
|
||||
out |= uint32(b&0x04) << (3*2 + 1 - 2)
|
||||
out |= uint32(b&0x02) << (3*1 + 1 - 1)
|
||||
out |= uint32(b&0x01) << (3*0 + 1 - 0)
|
||||
return out
|
||||
}
|
||||
|
||||
// Dev is a handle to the LED strip.
|
||||
type Dev struct {
|
||||
p gpiostream.PinOut
|
||||
numPixels int
|
||||
channels int // Number of channels per pixel
|
||||
b gpiostream.BitStream // NRZ encoded bits; cached to reduce heap fragmentation
|
||||
buf []byte // Double buffer of RGB/RGBW pixels; enables partial Draw()
|
||||
}
|
||||
|
||||
func (d *Dev) String() string {
|
||||
return fmt.Sprintf("nrzled{%s}", d.p)
|
||||
}
|
||||
|
||||
// Halt turns the lights off.
|
||||
//
|
||||
// It doesn't affect the back buffer.
|
||||
func (d *Dev) Halt() error {
|
||||
zero := NRZ(0)
|
||||
a := byte(zero >> 16)
|
||||
b := byte(zero >> 8)
|
||||
c := byte(zero)
|
||||
for i := 0; i < d.channels*d.numPixels; i++ {
|
||||
d.b.Bits[3*i+0] = a
|
||||
d.b.Bits[3*i+1] = b
|
||||
d.b.Bits[3*i+2] = c
|
||||
}
|
||||
if err := d.p.StreamOut(&d.b); err != nil {
|
||||
return fmt.Errorf("nrzled: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ColorModel implements devices.Display.
|
||||
//
|
||||
// It is color.NRGBAModel.
|
||||
func (d *Dev) ColorModel() color.Model {
|
||||
return color.NRGBAModel
|
||||
}
|
||||
|
||||
// Bounds implements devices.Display. Min is guaranteed to be {0, 0}.
|
||||
func (d *Dev) Bounds() image.Rectangle {
|
||||
return image.Rectangle{Max: image.Point{X: d.numPixels, Y: 1}}
|
||||
}
|
||||
|
||||
// Draw implements devices.Display.
|
||||
//
|
||||
// Using something else than image.NRGBA is 10x slower and is not recommended.
|
||||
// When using image.NRGBA, the alpha channel is ignored in RGB mode and used as
|
||||
// White channel in RGBW mode.
|
||||
//
|
||||
// A back buffer is kept so that partial updates are supported, albeit the full
|
||||
// LED strip is updated synchronously.
|
||||
func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) {
|
||||
r = r.Intersect(d.Bounds())
|
||||
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 d.buf == nil {
|
||||
// Allocate d.buf on first Draw() call, in case the user only wants to use
|
||||
// .Write().
|
||||
d.buf = make([]byte, d.numPixels*d.channels)
|
||||
}
|
||||
if img, ok := src.(*image.NRGBA); ok {
|
||||
// Fast path for image.NRGBA.
|
||||
base := srcR.Min.Y * img.Stride
|
||||
raster(d.b.Bits, img.Pix[base+4*srcR.Min.X:base+4*srcR.Max.X], d.channels, 4)
|
||||
} else {
|
||||
// Generic version.
|
||||
m := srcR.Max.X - srcR.Min.X
|
||||
if d.channels == 3 {
|
||||
for i := 0; i < m; i++ {
|
||||
c := color.NRGBAModel.Convert(src.At(srcR.Min.X+i, srcR.Min.Y)).(color.NRGBA)
|
||||
j := 3 * i
|
||||
put(d.b.Bits[3*(j+0):], c.G)
|
||||
put(d.b.Bits[3*(j+1):], c.R)
|
||||
put(d.b.Bits[3*(j+2):], c.B)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < m; i++ {
|
||||
c := color.NRGBAModel.Convert(src.At(srcR.Min.X+i, srcR.Min.Y)).(color.NRGBA)
|
||||
j := 4 * i
|
||||
put(d.b.Bits[3*(j+0):], c.G)
|
||||
put(d.b.Bits[3*(j+1):], c.R)
|
||||
put(d.b.Bits[3*(j+2):], c.B)
|
||||
put(d.b.Bits[3*(j+3):], c.A)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = d.p.StreamOut(&d.b)
|
||||
}
|
||||
|
||||
// Write accepts a stream of raw RGB/RGBW pixels and sends it as NRZ encoded
|
||||
// stream.
|
||||
//
|
||||
// This bypasses the back buffer.
|
||||
func (d *Dev) Write(pixels []byte) (int, error) {
|
||||
if len(pixels)%d.channels != 0 || len(pixels) > d.numPixels*d.channels {
|
||||
return 0, errors.New("nrzled: invalid RGB stream length")
|
||||
}
|
||||
raster(d.b.Bits, pixels, d.channels, d.channels)
|
||||
if err := d.p.StreamOut(&d.b); err != nil {
|
||||
return 0, fmt.Errorf("nrzled: %v", err)
|
||||
}
|
||||
return len(pixels), nil
|
||||
}
|
||||
|
||||
// New opens a handle to a compatible LED strip.
|
||||
//
|
||||
// The speed (hz) should either be 800000 for fast ICs and 400000 for the slow
|
||||
// ones.
|
||||
//
|
||||
// channels should be either 1 (White only), 3 (RGB) or 4 (RGBW). For RGB and
|
||||
// RGBW, the encoding is respectively GRB and GRBW.
|
||||
func New(p gpiostream.PinOut, numPixels, hz int, channels int) (*Dev, error) {
|
||||
if hz <= 0 || hz > 1000000000 {
|
||||
return nil, errors.New("nrzled: specify valid speed in hz")
|
||||
}
|
||||
if channels != 3 && channels != 4 {
|
||||
return nil, errors.New("nrzled: specify valid number of channels (3 or 4)")
|
||||
}
|
||||
// It is more space effective to use gpiostream.Bits than
|
||||
// gpiostream.EdgeStream.
|
||||
return &Dev{
|
||||
p: p,
|
||||
numPixels: numPixels,
|
||||
channels: channels,
|
||||
b: gpiostream.BitStream{
|
||||
Res: time.Second / time.Duration(hz),
|
||||
// Each bit is encoded on 3 bits.
|
||||
Bits: make(gpiostream.Bits, numPixels*3*channels),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// raster converts a RGB/RGBW input stream into a binary output stream as it
|
||||
// must be sent over the GPIO pin.
|
||||
//
|
||||
// `in` is RGB 24 bits or RGBW 32 bits. Each bit is encoded over 3 bits so the
|
||||
// length of `out` must be 3x as large as `in`.
|
||||
//
|
||||
// Encoded output format is GRB as 72 bits (24 * 3) or 96 bits (32 * 3).
|
||||
func raster(out, in []byte, outChannels, inChannels int) {
|
||||
pixels := len(in) / inChannels
|
||||
if outChannels == 3 {
|
||||
for i := 0; i < pixels; i++ {
|
||||
j := i * inChannels
|
||||
k := 3 * i
|
||||
put(out[3*(k+0):], in[j+1])
|
||||
put(out[3*(k+1):], in[j+0])
|
||||
put(out[3*(k+2):], in[j+2])
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < pixels; i++ {
|
||||
j := i * inChannels
|
||||
k := 4 * i
|
||||
put(out[3*(k+0):], in[j+1])
|
||||
put(out[3*(k+1):], in[j+0])
|
||||
put(out[3*(k+2):], in[j+2])
|
||||
put(out[3*(k+3):], in[j+3])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func put(out []byte, v byte) {
|
||||
w := NRZ(v)
|
||||
out[0] = byte(w >> 16)
|
||||
out[1] = byte(w >> 8)
|
||||
out[2] = byte(w)
|
||||
}
|
||||
|
||||
var _ conn.Resource = &Dev{}
|
||||
var _ devices.Display = &Dev{}
|
||||
var _ fmt.Stringer = &Dev{}
|
||||
@ -0,0 +1,322 @@
|
||||
// Copyright 2017 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"
|
||||
"image"
|
||||
"image/color"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"periph.io/x/periph/conn/gpio/gpiostream"
|
||||
"periph.io/x/periph/conn/gpio/gpiostream/gpiostreamtest"
|
||||
)
|
||||
|
||||
func TestNRZ(t *testing.T) {
|
||||
data := []struct {
|
||||
in byte
|
||||
expected uint32
|
||||
}{
|
||||
{0x00, 0x924924},
|
||||
{0x01, 0x924926},
|
||||
{0x02, 0x924934},
|
||||
{0x04, 0x9249A4},
|
||||
{0x08, 0x924D24},
|
||||
{0x10, 0x926924},
|
||||
{0x20, 0x934924},
|
||||
{0x40, 0x9A4924},
|
||||
{0x80, 0xD24924},
|
||||
{0xFD, 0xDB6DA6},
|
||||
{0xFE, 0xDB6DB4},
|
||||
{0xFF, 0xDB6DB6},
|
||||
}
|
||||
for i, line := range data {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
if actual := NRZ(line.in); line.expected != actual {
|
||||
t.Fatalf("NRZ(%X): 0x%X != 0x%X", line.in, line.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew_3(t *testing.T) {
|
||||
g := gpiostreamtest.PinOutPlayback{
|
||||
N: "Yo",
|
||||
Ops: []gpiostream.Stream{
|
||||
&gpiostream.BitStream{
|
||||
Bits: gpiostream.Bits{
|
||||
0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92,
|
||||
0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49,
|
||||
0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24,
|
||||
0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92,
|
||||
0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49,
|
||||
0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24,
|
||||
},
|
||||
Res: 2500 * time.Nanosecond,
|
||||
},
|
||||
},
|
||||
}
|
||||
d, err := New(&g, 10, 400000, 3)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s := d.String(); s != "nrzled{Yo}" {
|
||||
t.Fatal(s)
|
||||
}
|
||||
if c := d.ColorModel(); c != color.NRGBAModel {
|
||||
t.Fatal(c)
|
||||
}
|
||||
if r := d.Bounds(); r != image.Rect(0, 0, 10, 1) {
|
||||
t.Fatal(r)
|
||||
}
|
||||
if err = d.Halt(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = g.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew_fail(t *testing.T) {
|
||||
g := gpiostreamtest.PinOutPlayback{}
|
||||
if _, err := New(&g, 1, 0, 3); err == nil {
|
||||
t.Fatal("hz == 0")
|
||||
}
|
||||
if _, err := New(&g, 1, 400000, 2); err == nil {
|
||||
t.Fatal("channels == 2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDraw_NRGBA_3(t *testing.T) {
|
||||
g := gpiostreamtest.PinOutPlayback{
|
||||
Ops: []gpiostream.Stream{
|
||||
&gpiostream.BitStream{
|
||||
Bits: gpiostream.Bits{
|
||||
0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb,
|
||||
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49,
|
||||
0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24,
|
||||
0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb,
|
||||
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d,
|
||||
0xb6, 0x92, 0x49, 0xb6, 0x92, 0x49, 0xb4, 0x92, 0x4d, 0x24,
|
||||
},
|
||||
Res: 2500 * time.Nanosecond,
|
||||
},
|
||||
},
|
||||
}
|
||||
d, _ := New(&g, 10, 400000, 3)
|
||||
img := image.NewNRGBA(d.Bounds())
|
||||
copy(img.Pix, getRGBW())
|
||||
d.Draw(d.Bounds(), img, image.Point{})
|
||||
if err := g.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDraw_RGBA_3(t *testing.T) {
|
||||
g := gpiostreamtest.PinOutPlayback{
|
||||
Ops: []gpiostream.Stream{
|
||||
&gpiostream.BitStream{
|
||||
Bits: gpiostream.Bits{
|
||||
0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb,
|
||||
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49,
|
||||
0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24,
|
||||
0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb,
|
||||
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d,
|
||||
0xa6, 0xda, 0x49, 0xb6, 0xd3, 0x4d, 0x34, 0xdb, 0x49, 0x36,
|
||||
},
|
||||
Res: 2500 * time.Nanosecond,
|
||||
},
|
||||
},
|
||||
}
|
||||
d, _ := New(&g, 10, 400000, 3)
|
||||
img := image.NewRGBA(d.Bounds())
|
||||
copy(img.Pix, getRGBW())
|
||||
d.Draw(d.Bounds(), img, image.Point{})
|
||||
if err := g.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDraw_RGBA_4(t *testing.T) {
|
||||
g := gpiostreamtest.PinOutPlayback{
|
||||
Ops: []gpiostream.Stream{
|
||||
&gpiostream.BitStream{
|
||||
Bits: gpiostream.Bits{
|
||||
0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92,
|
||||
0x49, 0x24, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49,
|
||||
0x24, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6,
|
||||
0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb,
|
||||
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49,
|
||||
0x24, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6,
|
||||
0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xd2, 0x49, 0x24, 0xda, 0x49, 0xb6, 0xd3,
|
||||
0x4d, 0x34, 0xdb, 0x49, 0x36, 0x92, 0x4d, 0x26,
|
||||
},
|
||||
Res: 2500 * time.Nanosecond,
|
||||
},
|
||||
},
|
||||
}
|
||||
d, _ := New(&g, 10, 400000, 4)
|
||||
img := image.NewRGBA(d.Bounds())
|
||||
copy(img.Pix, getRGBW())
|
||||
d.Draw(d.Bounds(), img, image.Point{})
|
||||
if err := g.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDraw_Limits(t *testing.T) {
|
||||
g := gpiostreamtest.PinOutPlayback{
|
||||
Ops: []gpiostream.Stream{
|
||||
&gpiostream.BitStream{
|
||||
Bits: gpiostream.Bits{
|
||||
0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb,
|
||||
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49,
|
||||
0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24,
|
||||
0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb,
|
||||
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d,
|
||||
0xa6, 0xda, 0x49, 0xb6, 0xd3, 0x4d, 0x34, 0xdb, 0x49, 0x36,
|
||||
},
|
||||
Res: 2500 * time.Nanosecond,
|
||||
},
|
||||
},
|
||||
}
|
||||
d, _ := New(&g, 10, 400000, 3)
|
||||
img := image.NewRGBA(image.Rect(-1, -1, 20, 20))
|
||||
copy(img.Pix, getRGBW())
|
||||
d.Draw(d.Bounds(), img, image.Point{})
|
||||
if err := g.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_3(t *testing.T) {
|
||||
g := gpiostreamtest.PinOutPlayback{
|
||||
Ops: []gpiostream.Stream{
|
||||
&gpiostream.BitStream{
|
||||
Bits: gpiostream.Bits{
|
||||
0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb,
|
||||
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49,
|
||||
0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0x92, 0x49, 0x24,
|
||||
0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0x24, 0xdb,
|
||||
0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0xdb, 0x6d, 0xb6, 0x92, 0x49, 0xa4, 0x92, 0x49, 0x36, 0x92, 0x49,
|
||||
0xa6, 0x92, 0x49, 0xb6, 0x92, 0x49, 0xb4, 0x92, 0x4d, 0x24,
|
||||
},
|
||||
Res: 2500 * time.Nanosecond,
|
||||
},
|
||||
},
|
||||
}
|
||||
d, _ := New(&g, 10, 400000, 3)
|
||||
if n, err := d.Write(getRGB()); n != 30 || err != nil {
|
||||
t.Fatal(n, err)
|
||||
}
|
||||
if err := g.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_fail(t *testing.T) {
|
||||
g := gpiostreamtest.PinOutPlayback{DontPanic: true}
|
||||
d, _ := New(&g, 10, 400000, 3)
|
||||
if n, err := d.Write([]byte{1}); n != 0 || err == nil {
|
||||
t.Fatal(n, err)
|
||||
}
|
||||
if n, err := d.Write([]byte{1, 2, 3}); n != 0 || err == nil {
|
||||
t.Fatal(n, err)
|
||||
}
|
||||
if err := g.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHalt_fail(t *testing.T) {
|
||||
g := gpiostreamtest.PinOutPlayback{DontPanic: true}
|
||||
d, _ := New(&g, 10, 400000, 3)
|
||||
if d.Halt() == nil {
|
||||
t.Fatal("expected failure")
|
||||
}
|
||||
if err := g.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRaster_3_3(t *testing.T) {
|
||||
data := []byte{
|
||||
// 24 bits per pixel in RGB
|
||||
0, 1, 2,
|
||||
0xFD, 0xFE, 0xFF,
|
||||
}
|
||||
expected := []byte{
|
||||
// 72 bits per pixel in GRB
|
||||
0x92, 0x49, 0x26, 0x92, 0x49, 0x24, 0x92, 0x49, 0x34,
|
||||
0xdb, 0x6d, 0xb4, 0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xb6,
|
||||
}
|
||||
actual := make([]byte, len(expected))
|
||||
raster(actual, data, 3, 3)
|
||||
if !bytes.Equal(expected, actual) {
|
||||
t.Fatalf("\nexpected %#v\n actual %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRaster_4_4(t *testing.T) {
|
||||
data := []byte{
|
||||
// 32 bits per pixel in RGBW
|
||||
0, 1, 2, 3,
|
||||
0xFC, 0xFD, 0xFE, 0xFF,
|
||||
}
|
||||
expected := []byte{
|
||||
// 96 bits per pixel in GRBW
|
||||
0x92, 0x49, 0x26, 0x92, 0x49, 0x24, 0x92, 0x49, 0x34, 0x92, 0x49, 0x36,
|
||||
0xdb, 0x6d, 0xa6, 0xdb, 0x6d, 0xa4, 0xdb, 0x6d, 0xb4, 0xdb, 0x6d, 0xb6,
|
||||
}
|
||||
actual := make([]byte, len(expected))
|
||||
raster(actual, data, 4, 4)
|
||||
if !bytes.Equal(expected, actual) {
|
||||
t.Fatalf("\nexpected %#v\n actual %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
func BenchmarkNRZ(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
NRZ(23)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// getRGB returns a buffer of 10 RGB pixels.
|
||||
func getRGB() []byte {
|
||||
return []byte{
|
||||
0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFF,
|
||||
0x00, 0xFF, 0x00,
|
||||
0x00, 0xFF, 0xFF,
|
||||
0xFF, 0x00, 0x00,
|
||||
0xFF, 0x00, 0xFF,
|
||||
0xFF, 0xFF, 0x00,
|
||||
0xFF, 0xFF, 0xFF,
|
||||
3, 4, 5,
|
||||
6, 7, 8,
|
||||
}
|
||||
}
|
||||
|
||||
// getRGBW returns a buffer of 10 RGB pixels.
|
||||
func getRGBW() []byte {
|
||||
return []byte{
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFF, 0xFF,
|
||||
0x00, 0xFF, 0x00, 0xFF,
|
||||
0x00, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0x00, 0x00, 0xFF,
|
||||
0xFF, 0x00, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0x00, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0x80,
|
||||
6, 7, 8, 9,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue