diff --git a/piglow/README.md b/piglow/README.md new file mode 100644 index 0000000..413f938 --- /dev/null +++ b/piglow/README.md @@ -0,0 +1,15 @@ +#PiGlow + +[![GoDoc](http://godoc.org/github.com/goiot/devices/piglow?status.svg)](http://godoc.org/github.com/goiot/devices/piglow) + +[Manufacturer info](https://shop.pimoroni.com/products/piglow) + +![PiGlow](https://cdn.shopify.com/s/files/1/0174/1800/products/PiGlow-3_1024x1024.gif?v=1424952533) + +The PiGlow is a small add on board for the Raspberry Pi that provides 18 individually controllable LEDs. This board uses the +SN3218 8-bit 18-channel PWM chip to drive 18 surface mount LEDs. Communication is done via I2C over the GPIO header with a bus address of 0x54. +Each LED can be set to a PWM value of between 0 and 255. + +##Datasheet: + +* [SN3218 Datasheet](https://github.com/pimoroni/piglow/raw/master/sn3218-datasheet.pdf) diff --git a/piglow/examples/strobe.go b/piglow/examples/strobe.go new file mode 100644 index 0000000..c6a261c --- /dev/null +++ b/piglow/examples/strobe.go @@ -0,0 +1,57 @@ +package main + +import ( + "os" + "os/signal" + "syscall" + "time" + + "github.com/goiot/devices/piglow" + "golang.org/x/exp/io/i2c" +) + +func main() { + p, err := piglow.Open(&i2c.Devfs{Dev: "/dev/i2c-1", Addr: piglow.Address}) + if err != nil { + panic(err) + } + + // catch signals and terminate the app + sigc := make(chan os.Signal, 1) + signal.Notify(sigc, + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT) + + if err := p.Setup(); err != nil { + panic(err) + } + + for { + select { + case <-sigc: + p.Shutdown() + p.Close() + return + default: + time.Sleep(50 * time.Millisecond) + + for i := 1; i <= 18; i++ { + if err := p.SetLEDBrightness(i, 1); err != nil { + panic(err) + } + time.Sleep(10 * time.Millisecond) + } + + time.Sleep(50 * time.Millisecond) + + for i := 18; i > 0; i-- { + if err := p.SetLEDBrightness(i, 0); err != nil { + panic(err) + } + time.Sleep(10 * time.Millisecond) + } + } + } +} diff --git a/piglow/examples_test.go b/piglow/examples_test.go new file mode 100644 index 0000000..6f80a7f --- /dev/null +++ b/piglow/examples_test.go @@ -0,0 +1,21 @@ +package piglow_test + +import ( + "time" + + "github.com/goiot/devices/piglow" + "golang.org/x/exp/io/i2c" +) + +func Example() { + p, _ := piglow.Open(&i2c.Devfs{Dev: "/dev/i2c-1", Addr: piglow.Address}) + + p.Setup() + + brightness := 0 + for i := 0; i < 10; i++ { + brightness ^= 1 + p.SetBrightness(brightness) + time.Sleep(300 * time.Millisecond) + } +} diff --git a/piglow/piglow.go b/piglow/piglow.go new file mode 100644 index 0000000..8110ffd --- /dev/null +++ b/piglow/piglow.go @@ -0,0 +1,265 @@ +// Package piglow implements a driver for the Pimoroni PiGlow. +package piglow + +import ( + "fmt" + + "golang.org/x/exp/io/i2c" + "golang.org/x/exp/io/i2c/driver" +) + +const Address = 0x54 // Address is the I2C address of the device. + +// PiGlow represents a PiGlow device +type PiGlow struct { + conn *i2c.Device +} + +// Reset resets the internal registers +func (p *PiGlow) Reset() error { + if err := p.conn.Write([]byte{0x17, 0xFF}); err != nil { + return err + } + + return nil +} + +// Shutdown sets the software shutdown mode of the PiGlow +func (p *PiGlow) Shutdown() error { + return p.conn.Write([]byte{0x00, 0x00}) +} + +// Enable enables the PiGlow for normal operations +func (p *PiGlow) Enable() error { + return p.conn.Write([]byte{0x00, 0x01}) +} + +// Setup enables normal operations, resets the internal registers, and enables +// all LED control registers +func (p *PiGlow) Setup() error { + if err := p.Reset(); err != nil { + return err + } + + if err := p.Enable(); err != nil { + return err + } + + if err := p.SetLEDControlRegister(1, 0xFF); err != nil { + return err + } + + if err := p.SetLEDControlRegister(2, 0xFF); err != nil { + return err + } + + if err := p.SetLEDControlRegister(3, 0xFF); err != nil { + return err + } + + return nil +} + +// Open opens a new PiGlow. A PiGlow must be closed if no longer in use. +// If the PiGlow has not been powered down since last use, it will be opened +// with it's last programmed state. +func Open(o driver.Opener) (*PiGlow, error) { + conn, err := i2c.Open(o) + if err != nil { + return nil, err + } + + return &PiGlow{conn: conn}, nil +} + +// Close frees the underlying resources. It must be called once +// the PiGlow is no longer in use. +func (p *PiGlow) Close() error { + return p.conn.Close() +} + +// Green sets all the green LEDs to the level of 0-255. +func (p *PiGlow) Green(level int) error { + if err := p.conn.Write([]byte{0x04, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x06, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x0E, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { + return err + } + + return nil +} + +// Blue sets all the blue LEDs to the level of 0-255. +func (p *PiGlow) Blue(level int) error { + if err := p.conn.Write([]byte{0x05, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x0C, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x0F, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { + return err + } + + return nil +} + +// Yellow sets all the yellow LEDs to the level of 0-255. +func (p *PiGlow) Yellow(level int) error { + if err := p.conn.Write([]byte{0x03, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x09, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x10, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { + return err + } + + return nil +} + +// Orange sets all the orange LEDs to the level of 0-255. +func (p *PiGlow) Orange(level int) error { + if err := p.conn.Write([]byte{0x02, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x08, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x11, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { + return err + } + + return nil +} + +// White sets all the white LEDs to the level of 0-255. +func (p *PiGlow) White(level int) error { + if err := p.conn.Write([]byte{0x0A, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x0B, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x0D, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { + return err + } + + return nil +} + +// Red sets all the red LEDs to the level of 0-255. +func (p *PiGlow) Red(level int) error { + if err := p.conn.Write([]byte{0x01, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x07, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x12, byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { + return err + } + + return nil +} + +// SetLEDControlRegister sets the control register 1-3 to the bitmask enables. +// bitmask definition: +// 0 - LED disabled +// 1 - LED enabled +// LED Control Register 1 - LED channel 1 to 6 bits 0-5 +// LED Control Register 2 - LED channel 7 to 12 bits 0-5 +// LED Control Register 3 - LED channel 13 to 18 bits 0-5 +func (p *PiGlow) SetLEDControlRegister(register, enables int) error { + var address byte + + switch register { + case 1: + address = 0x13 + case 2: + address = 0x14 + case 3: + address = 0x15 + default: + return fmt.Errorf("%d is an unknown register", register) + } + + if err := p.conn.Write([]byte{address, byte(enables)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { + return err + } + + return nil +} + +// SetLEDBrightness sets the led 1-18 to the level 0-255. +func (p *PiGlow) SetLEDBrightness(led, level int) error { + if err := p.conn.Write([]byte{byte(led), byte(level)}); err != nil { + return err + } + + if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { + return err + } + + return nil +} + +// SetBrightness sets all the LEDs to the level 0-255. +func (p *PiGlow) SetBrightness(level int) error { + for i := 1; i <= 18; i++ { + if err := p.conn.Write([]byte{byte(i), byte(level)}); err != nil { + return err + } + } + + if err := p.conn.Write([]byte{0x16, 0xFF}); err != nil { + return err + } + + return nil +} diff --git a/piglow/piglow_test.go b/piglow/piglow_test.go new file mode 100644 index 0000000..4921b9c --- /dev/null +++ b/piglow/piglow_test.go @@ -0,0 +1,320 @@ +package piglow + +import ( + "bytes" + "errors" + "reflect" + "testing" + + "golang.org/x/exp/io/i2c/driver" +) + +type opener struct { + buf *bytes.Buffer +} + +func (o opener) Open() (driver.Conn, error) { + return &conn{buf: o.buf}, nil +} + +type conn struct { + buf *bytes.Buffer +} + +func (c conn) Tx(w, r []byte) error { + if w != nil { + if _, err := c.buf.Write(w); err != nil { + return err + } + } + + if r != nil { + if _, err := c.buf.Read(r); err != nil { + return err + } + } + + return nil +} + +func (conn) Close() error { + return nil +} + +func openPiGlow(t *testing.T) (*PiGlow, *bytes.Buffer) { + o := opener{ + buf: bytes.NewBuffer([]byte{}), + } + device, err := Open(o) + if err != nil { + t.Fatal(err) + } + + return device, o.buf +} + +func assert(t *testing.T, want, got interface{}) { + if !reflect.DeepEqual(want, got) { + t.Fatalf("got = %v, want = %v", got, want) + } +} + +func TestGreen(t *testing.T) { + device, buf := openPiGlow(t) + + if err := device.Green(1); err != nil { + t.Fatal(err) + } + + expected := []byte{ + 0x04, 0x01, + 0x06, 0x01, + 0x0E, 0x01, + 0x16, 0xFF, + } + + assert(t, expected, buf.Bytes()) +} + +func TestBlue(t *testing.T) { + device, buf := openPiGlow(t) + + if err := device.Blue(1); err != nil { + t.Fatal(err) + } + + expected := []byte{ + 0x05, 0x01, + 0x0C, 0x01, + 0x0F, 0x01, + 0x16, 0xFF, + } + + assert(t, expected, buf.Bytes()) +} + +func TestYellow(t *testing.T) { + device, buf := openPiGlow(t) + + if err := device.Yellow(1); err != nil { + t.Fatal(err) + } + + expected := []byte{ + 0x03, 0x01, + 0x09, 0x01, + 0x010, 0x01, + 0x16, 0xFF, + } + + assert(t, expected, buf.Bytes()) +} + +func TestOrange(t *testing.T) { + device, buf := openPiGlow(t) + + if err := device.Orange(1); err != nil { + t.Fatal(err) + } + + expected := []byte{ + 0x02, 0x01, + 0x08, 0x01, + 0x011, 0x01, + 0x16, 0xFF, + } + + assert(t, expected, buf.Bytes()) +} + +func TestWhite(t *testing.T) { + device, buf := openPiGlow(t) + + if err := device.White(1); err != nil { + t.Fatal(err) + } + + expected := []byte{ + 0x0A, 0x01, + 0x0B, 0x01, + 0x0D, 0x01, + 0x16, 0xFF, + } + + assert(t, expected, buf.Bytes()) +} + +func TestRed(t *testing.T) { + device, buf := openPiGlow(t) + + if err := device.Red(1); err != nil { + t.Fatal(err) + } + + expected := []byte{ + 0x01, 0x01, + 0x07, 0x01, + 0x012, 0x01, + 0x16, 0xFF, + } + + assert(t, expected, buf.Bytes()) +} + +func TestSetBrightness(t *testing.T) { + device, buf := openPiGlow(t) + + if err := device.SetBrightness(10); err != nil { + t.Fatal(err) + } + + expected := []byte{ + 0x01, 0x0A, + 0x02, 0x0A, + 0x03, 0x0A, + 0x04, 0x0A, + 0x05, 0x0A, + 0x06, 0x0A, + 0x07, 0x0A, + 0x08, 0x0A, + 0x09, 0x0A, + 0x0a, 0x0A, + 0x0b, 0x0A, + 0x0c, 0x0A, + 0x0d, 0x0A, + 0x0e, 0x0A, + 0x0f, 0x0A, + 0x10, 0x0A, + 0x11, 0x0A, + 0x012, 0x0A, + 0x16, 0xFF, + } + + assert(t, expected, buf.Bytes()) +} + +func TestSetLEDBrightness(t *testing.T) { + device, buf := openPiGlow(t) + + var states = []struct { + led int + level int + expected []byte + }{ + {1, 5, []byte{0x1, 5, 0x16, 0xFF}}, + {2, 10, []byte{0x2, 10, 0x16, 0xFF}}, + {3, 15, []byte{0x3, 15, 0x16, 0xFF}}, + {4, 20, []byte{0x4, 20, 0x16, 0xFF}}, + {5, 25, []byte{0x5, 25, 0x16, 0xFF}}, + {6, 30, []byte{0x6, 30, 0x16, 0xFF}}, + {7, 35, []byte{0x7, 35, 0x16, 0xFF}}, + {8, 40, []byte{0x8, 40, 0x16, 0xFF}}, + {9, 45, []byte{0x9, 45, 0x16, 0xFF}}, + {10, 50, []byte{0x0A, 50, 0x16, 0xFF}}, + {11, 55, []byte{0x0B, 55, 0x16, 0xFF}}, + {12, 60, []byte{0x0C, 60, 0x16, 0xFF}}, + {13, 65, []byte{0x0D, 65, 0x16, 0xFF}}, + {14, 70, []byte{0x0E, 70, 0x16, 0xFF}}, + {15, 75, []byte{0x0F, 75, 0x16, 0xFF}}, + {16, 80, []byte{0x10, 80, 0x16, 0xFF}}, + {17, 85, []byte{0x11, 85, 0x16, 0xFF}}, + {18, 90, []byte{0x12, 90, 0x16, 0xFF}}, + } + + for _, state := range states { + buf.Reset() + + if err := device.SetLEDBrightness(state.led, state.level); err != nil { + t.Fatal(err) + } + + assert(t, state.expected, buf.Bytes()) + } +} + +func TestReset(t *testing.T) { + device, buf := openPiGlow(t) + + if err := device.Reset(); err != nil { + t.Fatal(err) + } + + expected := []byte{ + 0x17, 0xFF, + } + + assert(t, expected, buf.Bytes()) +} + +func TestShutdown(t *testing.T) { + device, buf := openPiGlow(t) + + if err := device.Shutdown(); err != nil { + t.Fatal(err) + } + + expected := []byte{ + 0x00, 0x00, + } + + assert(t, expected, buf.Bytes()) +} + +func TestEnable(t *testing.T) { + device, buf := openPiGlow(t) + + if err := device.Enable(); err != nil { + t.Fatal(err) + } + + expected := []byte{ + 0x00, 0x01, + } + + assert(t, expected, buf.Bytes()) +} + +func TestSetup(t *testing.T) { + device, buf := openPiGlow(t) + + if err := device.Setup(); err != nil { + t.Fatal(err) + } + + expected := []byte{ + 0x17, 0xFF, + 0x00, 0x01, + 0x13, 0xFF, + 0x16, 0xFF, + 0x14, 0xFF, + 0x16, 0xFF, + 0x15, 0xFF, + 0x16, 0xFF, + } + + assert(t, expected, buf.Bytes()) +} + +func TestSetLEDControlRegister(t *testing.T) { + device, buf := openPiGlow(t) + + var states = []struct { + register int + enables int + expected []byte + err error + }{ + {1, 0xFF, []byte{0x13, 0xFF, 0x16, 0xFF}, nil}, + {2, 0xFF, []byte{0x14, 0xFF, 0x16, 0xFF}, nil}, + {3, 0xFF, []byte{0x15, 0xFF, 0x16, 0xFF}, nil}, + {0, 0xFF, []byte{}, errors.New("0 is an unknown register")}, + } + + for _, state := range states { + buf.Reset() + + err := device.SetLEDControlRegister(state.register, state.enables) + assert(t, state.expected, buf.Bytes()) + assert(t, state.err, err) + } +}