Skip to content

Commit

Permalink
Added initial support for AIP1640 led driver, used in Wemos D1 mini's
Browse files Browse the repository at this point in the history
matrix LED shield
  • Loading branch information
conejoninja authored and deadprogram committed Apr 12, 2018
1 parent 9a0f401 commit 8e63911
Show file tree
Hide file tree
Showing 3 changed files with 307 additions and 0 deletions.
159 changes: 159 additions & 0 deletions drivers/gpio/aip1640_driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package gpio

import (
"time"

"gobot.io/x/gobot"
)

const (
AIP1640DataCmd = 0x40
AIP1640DispCtrl = 0x88
AIP1640AddrCmd = 0xC0

AIP1640FixedAddr = 0x04
)

// AIP1640Driver is the gobot driver for the AIP1640 LED driver used in the WEMOS D1 mini Matrix LED Shield.
// It has some similarities with the TM16xx LED drivers
//
// Datasheet CN: https://datasheet.lcsc.com/szlcsc/AiP1640_C82650.pdf
//
// Library ported from: https://github.com/wemos/WEMOS_Matrix_LED_Shield_Arduino_Library
type AIP1640Driver struct {
pintClock *DirectPinDriver
pinData *DirectPinDriver
name string
intensity byte
buffer [8]byte
connection gobot.Connection
gobot.Commander
}

// NewAIP1640Driver return a new AIP1640Driver given a gobot.Connection and the clock, data and strobe pins
func NewAIP1640Driver(a gobot.Connection, clockPin string, dataPin string) *AIP1640Driver {
t := &AIP1640Driver{
name: gobot.DefaultName("AIP1640Driver"),
pintClock: NewDirectPinDriver(a, clockPin),
pinData: NewDirectPinDriver(a, dataPin),
intensity: 7,
connection: a,
Commander: gobot.NewCommander(),
}

/* TODO : Add commands */

return t
}

// Start initializes the tm1638, it uses a SPI-like communication protocol
func (a *AIP1640Driver) Start() (err error) {
a.pinData.On()
a.pintClock.On()

return
}

// Halt implements the Driver interface
func (a *AIP1640Driver) Halt() (err error) { return }

// Name returns the AIP1640Drivers name
func (a *AIP1640Driver) Name() string { return a.name }

// SetName sets the AIP1640Drivers name
func (a *AIP1640Driver) SetName(n string) { a.name = n }

// Connection returns the AIP1640Driver Connection
func (a *AIP1640Driver) Connection() gobot.Connection {
return a.connection
}

// SetIntensity changes the intensity (from 1 to 7) of the display
func (a *AIP1640Driver) SetIntensity(level byte) {
if level >= 7 {
level = 7
}
a.intensity = level
}

// Display sends the buffer to the display (ie. turns on/off the corresponding LEDs)
func (a *AIP1640Driver) Display() {
for i := 0; i < 8; i++ {
a.sendData(byte(i), a.buffer[i])

a.pinData.Off()
a.pintClock.Off()
time.Sleep(1 * time.Millisecond)
a.pintClock.On()
a.pinData.On()
}

a.sendCommand(AIP1640DispCtrl | a.intensity)
}

// Clear empties the buffer (turns off all the LEDs)
func (a *AIP1640Driver) Clear() {
for i := 0; i < 8; i++ {
a.buffer[i] = 0x00
}
}

// DrawPixel turns on or off a specific in the buffer
func (a *AIP1640Driver) DrawPixel(x, y byte, enabled bool) {
if x >= 8 || y >= 8 {
return
}
y = 7 - y
if enabled {
a.buffer[y] |= 1 << x
} else {
a.buffer[y] &^= 1 << x
}
}

// DrawRow sets any given row of LEDs in the buffer
func (a *AIP1640Driver) DrawRow(row, data byte) {
if row >= 8 {
return
}
a.buffer[7-row] = data
}

// DrawMatrix sets the whole buffer
func (a *AIP1640Driver) DrawMatrix(data [8]byte) {
for i := 0; i < 8; i++ {
a.buffer[7-i] = data[i]
}
}

// sendCommand is an auxiliary function to send commands to the AIP1640Driver module
func (a *AIP1640Driver) sendCommand(cmd byte) {
a.pinData.Off()
a.send(cmd)
a.pinData.On()
}

// sendData is an auxiliary function to send data to the AIP1640Driver module
func (a *AIP1640Driver) sendData(address byte, data byte) {
a.sendCommand(AIP1640DataCmd | AIP1640FixedAddr)
a.pinData.Off()
a.send(AIP1640AddrCmd | address)
a.send(data)
a.pinData.On()
}

// send writes data on the module
func (a *AIP1640Driver) send(data byte) {
for i := 0; i < 8; i++ {
a.pintClock.Off()

if (data & 1) > 0 {
a.pinData.On()
} else {
a.pinData.Off()
}
data >>= 1

a.pintClock.On()
}
}
93 changes: 93 additions & 0 deletions drivers/gpio/aip1640_driver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package gpio

import (
"strings"
"testing"

"gobot.io/x/gobot"
"gobot.io/x/gobot/gobottest"
)

var _ gobot.Driver = (*AIP1640Driver)(nil)

// --------- HELPERS
func initTestAIP1640Driver() (driver *AIP1640Driver) {
driver, _ = initTestAIP1640DriverWithStubbedAdaptor()
return
}

func initTestAIP1640DriverWithStubbedAdaptor() (*AIP1640Driver, *gpioTestAdaptor) {
adaptor := newGpioTestAdaptor()
return NewAIP1640Driver(adaptor, "1", "2"), adaptor
}

// --------- TESTS
func TestAIP1640Driver(t *testing.T) {
var a interface{} = initTestAIP1640Driver()
_, ok := a.(*AIP1640Driver)
if !ok {
t.Errorf("NewAIP1640Driver() should have returned a *AIP1640Driver")
}
}

func TestAIP1640DriverStart(t *testing.T) {
d := initTestAIP1640Driver()
gobottest.Assert(t, d.Start(), nil)
}

func TestAIP1640DriverHalt(t *testing.T) {
d := initTestAIP1640Driver()
gobottest.Assert(t, d.Halt(), nil)
}

func TestAIP1640DriverDefaultName(t *testing.T) {
d := initTestAIP1640Driver()
gobottest.Assert(t, strings.HasPrefix(d.Name(), "AIP1640Driver"), true)
}

func TestAIP1640DriverSetName(t *testing.T) {
d := initTestAIP1640Driver()
d.SetName("mybot")
gobottest.Assert(t, d.Name(), "mybot")
}

func TestAIP1640DriveDrawPixel(t *testing.T) {
d := initTestAIP1640Driver()
d.DrawPixel(2, 3, true)
d.DrawPixel(0, 3, true)
gobottest.Assert(t, uint8(5), d.buffer[7-3])
}

func TestAIP1640DriverDrawRow(t *testing.T) {
d := initTestAIP1640Driver()
d.DrawRow(4, 0x3C)
gobottest.Assert(t, uint8(0x3C), d.buffer[7-4])
}

func TestAIP1640DriverDrawMatrix(t *testing.T) {
d := initTestAIP1640Driver()
drawing := [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}
d.DrawMatrix(drawing)
gobottest.Assert(t, [8]byte{0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01}, d.buffer)
}

func TestAIP1640DriverClear(t *testing.T) {
d := initTestAIP1640Driver()
drawing := [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}
d.DrawMatrix(drawing)
gobottest.Assert(t, [8]byte{0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01}, d.buffer)
d.Clear()
gobottest.Assert(t, [8]byte{}, d.buffer)
}

func TestAIP1640DriverSetIntensity(t *testing.T) {
d := initTestAIP1640Driver()
d.SetIntensity(3)
gobottest.Assert(t, uint8(3), d.intensity)
}

func TestAIP1640DriverSetIntensityHigherThan7(t *testing.T) {
d := initTestAIP1640Driver()
d.SetIntensity(19)
gobottest.Assert(t, uint8(7), d.intensity)
}
55 changes: 55 additions & 0 deletions examples/firmata_aip1640.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// +build example
//
// Do not build by default.

/*
How to run
Pass serial port to use as the first param:
go run examples/firmata_aip1640.go /dev/ttyACM0
*/

package main

import (
"os"
"time"

"gobot.io/x/gobot"
"gobot.io/x/gobot/drivers/gpio"
"gobot.io/x/gobot/platforms/firmata"
)

func main() {

firmataAdaptor := firmata.NewAdaptor(os.Args[1])
// In the WEMOS D1 Mini LED Matrix Shield clockPin = 14, dataPin = 13
aip1640 := gpio.NewAIP1640Driver(firmataAdaptor, "14", "13")

smiles := [3][8]byte{
{0x3C, 0x42, 0xA5, 0x81, 0xA5, 0x99, 0x42, 0x3C}, // happy :)
{0x3C, 0x42, 0xA5, 0x81, 0xBD, 0x81, 0x42, 0x3C}, // normal :|
{0x3C, 0x42, 0xA5, 0x81, 0x99, 0xA5, 0x42, 0x3C}, // sad :(
}

s := 0
work := func() {
aip1640.Clear()
gobot.Every(600*time.Millisecond, func() {
aip1640.DrawMatrix(smiles[s])
aip1640.Display()
s++
if s > 2 {
s = 0
}
})
}

robot := gobot.NewRobot("aip1640Bot",
[]gobot.Connection{firmataAdaptor},
[]gobot.Device{aip1640},
work,
)

robot.Start()
}

0 comments on commit 8e63911

Please sign in to comment.