diff --git a/experimental/devices/sn3218/doc.go b/experimental/devices/sn3218/doc.go new file mode 100644 index 0000000..0dd99bb --- /dev/null +++ b/experimental/devices/sn3218/doc.go @@ -0,0 +1,10 @@ +// Copyright 2018 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 sn3218 controls a SN3218 LED driver with 18 LEDs over an i2c bus. +// +// Datasheet +// +// http://www.si-en.com/uploadpdf/s2011517171720.pdf +package sn3218 diff --git a/experimental/devices/sn3218/example_test.go b/experimental/devices/sn3218/example_test.go new file mode 100644 index 0000000..f5a8c96 --- /dev/null +++ b/experimental/devices/sn3218/example_test.go @@ -0,0 +1,45 @@ +// Copyright 2018 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 sn3218_test + +import ( + "log" + "time" + + "periph.io/x/periph/conn/i2c/i2creg" + "periph.io/x/periph/experimental/devices/sn3218" + "periph.io/x/periph/host" +) + +func Example() { + if _, err := host.Init(); err != nil { + log.Fatal(err) + } + + b, err := i2creg.Open("") + if err != nil { + log.Fatal(err) + } + defer b.Close() + + d, err := sn3218.New(b) + if err != nil { + log.Fatal(err) + } + defer d.Halt() + + // By default, the device is disabled and brightness is 0 for all LEDs + // So let's set the brightness to a low value and enable the device to + // get started + d.BrightnessAll(1) + d.Sleep() + + // Switch LED 7 on + if err := d.Switch(7, true); err != nil { + log.Fatal("Error while switching LED", err) + } + + time.Sleep(1000 * time.Millisecond) +} diff --git a/experimental/devices/sn3218/sn3218.go b/experimental/devices/sn3218/sn3218.go new file mode 100644 index 0000000..1621e26 --- /dev/null +++ b/experimental/devices/sn3218/sn3218.go @@ -0,0 +1,141 @@ +// Copyright 2018 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 sn3218 + +import ( + "errors" + + "periph.io/x/periph/conn/i2c" +) + +const ( + i2cAddress = 0x54 + cmdEnableOutput = 0x00 + cmdSetBrightnessValues = 0x01 + cmdEnableLeds = 0x13 + cmdUpdate = 0x16 + cmdReset = 0x17 +) + +// Dev is a handler to sn3218 controller. +type Dev struct { + i2c i2c.Dev + on [18]bool + brightness [18]byte +} + +// New returns a handle to a SN3218 LED driver. +func New(bus i2c.Bus) (*Dev, error) { + d := &Dev{ + i2c: i2c.Dev{Bus: bus, Addr: i2cAddress}, + } + if err := d.reset(); err != nil { + return nil, err + } + return d, nil +} + +// Halt resets the registers and switches the driver off. +func (d *Dev) Halt() error { + return d.reset() +} + +// WakeUp returns from sleep mode and switches the channels according to the states in the register of SN3218. +func (d *Dev) WakeUp() error { + _, err := d.i2c.Write([]byte{cmdEnableOutput, 0x01}) + return err +} + +// Sleep sends SN3218 to sleep mode while keeping the states in the registers. +func (d *Dev) Sleep() error { + _, err := d.i2c.Write([]byte{cmdEnableOutput, 0x00}) + return err +} + +// GetState returns the state (on/off) and the brightness (0..255) of the +// Channel 0..17. +func (d *Dev) GetState(channel int) (bool, byte, error) { + if channel < 0 || channel >= 18 { + return false, 0, errors.New("channel number out of range 0..17") + } + return d.on[channel], d.brightness[channel], nil +} + +// Switch switched the channel (0..18) to state (on/off). +func (d *Dev) Switch(channel int, state bool) error { + if channel < 0 || channel >= 18 { + return errors.New("channel number out of range 0..17") + } + d.on[channel] = state + return d.updateStates() +} + +// SwitchAll switches all channels accoring to the state (on/off). +func (d *Dev) SwitchAll(state bool) error { + for i := 0; i < 18; i++ { + d.on[i] = state + } + return d.updateStates() +} + +// Brightness sets the brightness of led (0..17) to value (0..255). +func (d *Dev) Brightness(channel int, value byte) error { + if channel < 0 || channel >= 18 { + return errors.New("channel number out of range 0..17") + } + d.brightness[channel] = value + return d.updateBrightness() +} + +// BrightnessAll sets the brightness of all channels to the value (0..255). +func (d *Dev) BrightnessAll(value byte) error { + for i := 0; i < 18; i++ { + d.brightness[i] = value + } + return d.updateBrightness() +} + +// Reset resets the registers to the default values. +func (d *Dev) reset() error { + _, err := d.i2c.Write([]byte{cmdReset, 0xFF}) + d.on = [18]bool{} + d.brightness = [18]byte{} + return err +} + +func (d *Dev) stateArrayToInt() uint { + var result uint = 0 + for i := uint(0); i < uint(18); i++ { + state := uint(1) + if !d.on[i] { + state = uint(0) + } + result |= (state << i) + } + return result +} + +func (d *Dev) update() error { + _, err := d.i2c.Write([]byte{cmdUpdate, 0xFF}) + return err +} + +func (d *Dev) updateStates() error { + mask := d.stateArrayToInt() + cmd := [...]byte{cmdEnableLeds, byte(mask & 0x3F), byte((mask >> 6) & 0x3F), byte((mask >> 12) & 0X3F)} + if _, err := d.i2c.Write(cmd[:]); err != nil { + return err + } + return d.update() +} + +func (d *Dev) updateBrightness() error { + cmd := [19]byte{cmdSetBrightnessValues} + copy(cmd[1:], d.brightness[:]) + if _, err := d.i2c.Write(cmd[:]); err != nil { + return err + } + return d.update() +} diff --git a/experimental/devices/sn3218/sn3218_test.go b/experimental/devices/sn3218/sn3218_test.go new file mode 100644 index 0000000..3af90ae --- /dev/null +++ b/experimental/devices/sn3218/sn3218_test.go @@ -0,0 +1,230 @@ +// Copyright 2018 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 sn3218 + +import ( + "bytes" + "testing" + + "periph.io/x/periph/conn/i2c/i2ctest" +) + +func setup() *i2ctest.Record { + return &i2ctest.Record{ + Bus: nil, + Ops: []i2ctest.IO{}, + } +} + +func TestNew(t *testing.T) { + bus := setup() + _, err := New(bus) + if err != nil { + t.Fatal("New should not return error", err) + } + if len(bus.Ops) > 1 { + t.Fatal("Expected 0 operation to I2CBus, got ", len(bus.Ops)) + } + + if !bytes.Equal(bus.Ops[0].W, []byte{0x17, 0xFF}) { + t.Fatal("Expected: 0x17, 0xFF (reset), got: ", bus.Ops[0].W) + } +} + +func TestHalt(t *testing.T) { + bus := setup() + dev, _ := New(bus) + err := dev.Halt() + + if err != nil { + t.Fatal("Halt should not return error, but did", err) + } + if len(bus.Ops) != 2 { + t.Fatal("Expected 2 operations, got", len(bus.Ops)) + } + if !bytes.Equal(bus.Ops[1].W, []byte{0x17, 0xFF}) { + t.Fatal("I2C write different than expected (reset)") + } +} + +func TestWakeUp(t *testing.T) { + bus := setup() + dev, _ := New(bus) + dev.WakeUp() + if len(bus.Ops) != 2 { + t.Fatal("Expected 2 operations, got", len(bus.Ops)) + } + if bus.Ops[1].Addr != 0x54 { + t.Fatal("Expected: Write to address 0x54, got: ", bus.Ops[1].Addr) + } + if !bytes.Equal(bus.Ops[1].W, []byte{0x00, 0x01}) { + t.Fatal("Expected: 0x00, 0x01, got: ", bus.Ops[1].W) + } +} + +func TestSleep(t *testing.T) { + bus := setup() + dev, _ := New(bus) + dev.Sleep() + if !bytes.Equal(bus.Ops[1].W, []byte{0x00, 0x00}) { + t.Fatal("Expected: 0x00, 0x00, got: ", bus.Ops[1].W) + } +} + +func TestGetState(t *testing.T) { + bus := setup() + dev, _ := New(bus) + state, brightness, err := dev.GetState(0) + if state != false || brightness != 0 || err != nil { + t.Fatal("Expected: false, 0, nil, got: ", state, brightness, err) + } + if _, _, err := dev.GetState(-1); err == nil { + t.Fatal("Expected error, but error is nil") + } + if _, _, err := dev.GetState(18); err == nil { + t.Fatal("Expected error, but error is nil") + } +} + +func TestSwitch(t *testing.T) { + bus := setup() + dev, _ := New(bus) + err := dev.Switch(7, true) + if err != nil { + t.Fatal("Expected: err == nil, got:", err) + } + if state, _, _ := dev.GetState(7); !state { + t.Fatal("Expected: LED on, but was off") + } + dev.Switch(7, false) + if state, _, _ := dev.GetState(7); state { + t.Fatal("Expected: LED off, but was on") + } + if len(bus.Ops) != 5 { + t.Fatal("Expected 5 i2c writes, got: ", len(bus.Ops)) + } + if !bytes.Equal(bus.Ops[1].W, []byte{0x13, 0x00, 0x02, 0x00}) { + t.Fatal("Expected 0x13, 0x00, 0x02, 0x00, got:", bus.Ops[1].W) + } + if !bytes.Equal(bus.Ops[2].W, []byte{0x16, 0xFF}) { + t.Fatal("Expected 0x16, 0xFF got:", bus.Ops[2].W) + } + if !bytes.Equal(bus.Ops[3].W, []byte{0x13, 0x00, 0x00, 0x00}) { + t.Fatal("Expected 0x13, 0x00, 0x00, 0x00, got: ", bus.Ops[3].W) + } + if !bytes.Equal(bus.Ops[4].W, []byte{0x16, 0xFF}) { + t.Fatal("Expected 0x16, 0xFF got:", bus.Ops[4].W) + } + if err = dev.Switch(19, true); err == nil { + t.Fatal("Tried to switch LED out of range and expected error, but error is nil...") + } + +} + +func TestSetGlobalBrightness(t *testing.T) { + bus := setup() + dev, _ := New(bus) + dev.BrightnessAll(100) + for i := 0; i < 17; i++ { + if dev.brightness[i] != 100 { + t.Fatal("Brightness of LED", i, " should be 100, but is", dev.brightness[i]) + } + } + + if len(bus.Ops) != 3 { + t.Fatal("Expected 3 operations on I2C, got", len(bus.Ops)) + } + + if !bytes.Equal(bus.Ops[1].W, []byte{0x01, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64}) { + t.Fatal("Write operation to I2C different than expected") + } + + if !bytes.Equal(bus.Ops[2].W, []byte{0x16, 0xFF}) { + t.Fatal("Expected update command, but got something else") + } +} + +func TestSetBrightness(t *testing.T) { + bus := setup() + dev, _ := New(bus) + if _, brightness, _ := dev.GetState(9); brightness != 0 { + t.Fatal("Brightness should be 0, but it's not") + } + if err := dev.Brightness(9, 8); err != nil { + t.Fatal("There should be no error, but it is", err) + } + if _, brightness, _ := dev.GetState(9); brightness != 8 { + t.Fatal("Brightness should be 8, but it's not") + } + if len(bus.Ops) != 3 { + t.Fatal("Expected 3 i2c operations, got", len(bus.Ops)) + } + if !bytes.Equal(bus.Ops[1].W, []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0}) { + t.Fatal("Write operation to I2C different than expected") + } + if err := dev.Brightness(42, 100); err == nil { + t.Fatal("Expected error because channel out of range, but error was nil") + } +} + +func TestSwitchAll(t *testing.T) { + bus := setup() + dev, _ := New(bus) + dev.SwitchAll(true) + for i := 0; i < 17; i++ { + if state, _, _ := dev.GetState(i); !state { + t.Fatal("LED should be on, but is off: ", i) + } + } + if len(bus.Ops) != 3 { + t.Fatal("Expected 3 operations on I2C, got", len(bus.Ops)) + } + if !bytes.Equal(bus.Ops[1].W, []byte{19, 63, 63, 63}) { + t.Fatal("Data written to bus different than expected") + } + + dev.SwitchAll(false) + for i := 0; i < 17; i++ { + if state, _, _ := dev.GetState(i); state { + t.Fatal("LED should be off, but is on: ", i) + } + } + if len(bus.Ops) != 5 { + t.Fatal("Expected 4 operations on I2C, got", len(bus.Ops)) + } + if !bytes.Equal(bus.Ops[3].W, []byte{19, 0, 0, 0}) { + t.Fatal("Data written to bus different than expected") + } +} + +func TestBoolArrayToInt(t *testing.T) { + bus := setup() + dev, _ := New(bus) + + result := dev.stateArrayToInt() + if result != 0 { + t.Error("Expected: 0, got: ", result) + } + + dev.on[0] = true + result = dev.stateArrayToInt() + if result != 1 { + t.Error("Expected: 1, got: ", result) + } + + dev.on[1] = true + result = dev.stateArrayToInt() + if result != 3 { + t.Error("Expected: 3, got ", result) + } + + for i := 0; i < 18; i++ { + dev.on[i] = true + } + result = dev.stateArrayToInt() + if result != 262143 { + t.Error("Expected: 262143, got ", result) + } +}