drivers: add driver for IS31FL3731 matrix LED driver (#370)

IS31FL3731: add driver for IS31FL3731 matrix LED driver
This commit is contained in:
Anton Fisher
2022-03-09 04:54:07 -08:00
committed by GitHub
parent 02e4548966
commit 9a98d1be55
6 changed files with 421 additions and 0 deletions
+2
View File
@@ -157,6 +157,8 @@ smoke-test:
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
@md5sum ./build/test.hex
ifneq ($(AVR), 0)
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
@md5sum ./build/test.hex
+1
View File
@@ -87,6 +87,7 @@ The following 78 devices are supported.
| [software I2C driver](https://www.ti.com/lit/an/slva704/slva704.pdf) | GPIO |
| [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI |
| [INA260 Volt/Amp/Power meter](https://www.ti.com/lit/ds/symlink/ina260.pdf) | I2C |
| [IS31FL3731 matrix LED driver](https://www.lumissil.com/assets/pdf/core/IS31FL3731_DS.pdf) | I2C |
| [4x4 Membrane Keypad](https://cdn.sparkfun.com/assets/f/f/a/5/0/DS-16038.pdf) | GPIO |
| [L293x motor driver](https://www.ti.com/lit/ds/symlink/l293d.pdf) | GPIO/PWM |
| [L9110x motor driver](https://www.elecrow.com/download/datasheet-l9110.pdf) | GPIO/PWM |
+51
View File
@@ -0,0 +1,51 @@
package main
import (
"time"
"machine"
"tinygo.org/x/drivers/is31fl3731"
)
// I2CAddress -- address of led matrix
var I2CAddress uint8 = is31fl3731.I2C_ADDRESS_74
func main() {
bus := machine.I2C0
err := bus.Configure(machine.I2CConfig{})
if err != nil {
println("could not configure I2C:", err)
return
}
// Create driver for Adafruit 15x7 CharliePlex LED Matrix FeatherWing
// (CharlieWing): https://www.adafruit.com/product/3163
ledMatrix := is31fl3731.NewAdafruitCharlieWing15x7(bus, I2CAddress)
err = ledMatrix.Configure()
if err != nil {
println("could not configure is31fl3731 driver:", err)
return
}
// Fill the whole matrix on the frame #0 (visible by default)
ledMatrix.Fill(is31fl3731.FRAME_0, uint8(3))
// Draw couple pixels on the frame #1 (not visible yet)
ledMatrix.DrawPixelXY(is31fl3731.FRAME_1, uint8(0), uint8(0), uint8(10))
ledMatrix.DrawPixelXY(is31fl3731.FRAME_1, uint8(14), uint8(6), uint8(10))
// There are 8 frames available, it's a good idea to draw on an invisible
// frame and then switch to that frame to reduce flickering. Switch between
// frame #0 and #1 in a loop to show animation:
for {
println("show frame #0...")
ledMatrix.SetActiveFrame(is31fl3731.FRAME_0)
time.Sleep(time.Second * 3)
println("show frame #1...")
ledMatrix.SetActiveFrame(is31fl3731.FRAME_1)
time.Sleep(time.Second * 3)
}
}
+209
View File
@@ -0,0 +1,209 @@
// Package is31fl3731 provides a driver for the Lumissil IS31FL3731 matrix LED
// driver.
//
// Driver supports following layouts:
// - any custom LED matrix layout
// - Adafruit 15x7 CharliePlex LED Matrix FeatherWing (CharlieWing)
// https://www.adafruit.com/product/3163
//
// Datasheet:
// https://www.lumissil.com/assets/pdf/core/IS31FL3731_DS.pdf
//
// This driver inspired by Adafruit Python driver:
// https://github.com/adafruit/Adafruit_CircuitPython_IS31FL3731
//
package is31fl3731
import (
"fmt"
"time"
"tinygo.org/x/drivers"
)
// Device implements TinyGo driver for Lumissil IS31FL3731 matrix LED driver
type Device struct {
Address uint8
bus drivers.I2C
// Currently selected command register (one of the frame registers or the
// function register)
selectedCommand uint8
}
// Configure chip for operating as a LED matrix display
func (d *Device) Configure() (err error) {
// Shutdown software
err = d.writeFunctionRegister(SET_SHUTDOWN, []byte{SOFTWARE_OFF})
if err != nil {
return fmt.Errorf("failed to shutdown: %w", err)
}
time.Sleep(time.Millisecond * 10)
// Wake up software
err = d.writeFunctionRegister(SET_SHUTDOWN, []byte{SOFTWARE_ON})
if err != nil {
return fmt.Errorf("failed to wake up: %w", err)
}
// Set display to a picture mode ("auto frame play mode" and "audio frame play
// mode" are not supported in this version of the driver)
err = d.writeFunctionRegister(SET_DISPLAY_MODE, []byte{DISPLAY_MODE_PICTURE})
if err != nil {
return fmt.Errorf("failed to switch to a picture move: %w", err)
}
// Enable LEDs that are present (soldered) on the board. From the datasheet:
// LEDs which are no connected must be off by LED Control Register (Frame
// Registers) or it will affect other LEDs
err = d.enableLEDs()
if err != nil {
return fmt.Errorf("failed to enable LEDs: %w", err)
}
// Disable audiosync
err = d.writeFunctionRegister(SET_AUDIOSYNC, []byte{AUDIOSYNC_OFF})
if err != nil {
return fmt.Errorf("failed to disable audiosync: %w", err)
}
// Clear all frames
for frame := FRAME_0; frame <= FRAME_7; frame++ {
err = d.Clear(frame)
if err != nil {
return fmt.Errorf("failed to clear frame %d: %w", frame, err)
}
}
// 1st frame is displayed by default
err = d.SetActiveFrame(FRAME_0)
if err != nil {
return fmt.Errorf("failed to set active frame: %w", err)
}
return nil
}
// selectCommand selects command register, can be:
// - frame registers 0-7
// - function register
func (d *Device) selectCommand(command uint8) (err error) {
if command != d.selectedCommand {
d.selectedCommand = command
return d.bus.WriteRegister(d.Address, COMMAND, []byte{command})
}
return nil
}
// writeFunctionRegister selects the function register and writes data into it
func (d *Device) writeFunctionRegister(operation uint8, data []byte) (err error) {
err = d.selectCommand(FUNCTION)
if err != nil {
return err
}
return d.bus.WriteRegister(d.Address, operation, data)
}
// enableLEDs enables only LEDs that are soldered on the set board. Enabled
// all 16x9 LEDs by default
func (d *Device) enableLEDs() (err error) {
for frame := FRAME_0; frame <= FRAME_7; frame++ {
err = d.selectCommand(frame)
if err != nil {
return err
}
// Enable every LED (16 columns x 9 rows)
for i := uint8(0); i < 16; i++ {
err = d.bus.WriteRegister(d.Address, i, []byte{0xFF})
if err != nil {
return err
}
}
}
return nil
}
// setPixelPWD sets individual pixel's PWM value [0-255] on the selected frame
func (d *Device) setPixelPWD(frame, n, value uint8) (err error) {
err = d.selectCommand(frame)
if err != nil {
return err
}
return d.bus.WriteRegister(d.Address, LED_PWM_OFFSET+n, []byte{value})
}
// SetActiveFrame sets frame to display with LEDs
func (d *Device) SetActiveFrame(frame uint8) (err error) {
if frame > FRAME_7 {
return fmt.Errorf("frame %d is out of valid range [0-7]", frame)
}
return d.writeFunctionRegister(SET_ACTIVE_FRAME, []byte{frame})
}
// Fill the whole frame with provided PWM value [0-255]
func (d *Device) Fill(frame, value uint8) (err error) {
if frame > FRAME_7 {
return fmt.Errorf("frame %d is out of valid range [0-7]", frame)
}
err = d.selectCommand(frame)
if err != nil {
return err
}
data := make([]byte, 24)
for i := range data {
data[i] = value
}
for i := uint8(0); i < 6; i++ {
err = d.bus.WriteRegister(d.Address, LED_PWM_OFFSET+i*24, data)
if err != nil {
return err
}
}
return nil
}
// Clear the whole frame
func (d *Device) Clear(frame uint8) (err error) {
return d.Fill(frame, 0x00)
}
// DrawPixelIndex draws a single pixel on the selected frame by its index with
// provided PWM value [0-255]
func (d *Device) DrawPixelIndex(frame, index, value uint8) (err error) {
if frame > FRAME_7 {
return fmt.Errorf("frame %d is out of valid range [0-7]", frame)
}
return d.setPixelPWD(frame, index, value)
}
// DrawPixelXY draws a single pixel on the selected frame by its XY coordinates
// with provided PWM value [0-255]. Raw LEDs layout assumed to be a 16x9 matrix,
// and can be used with any custom board that has IS31FL3731 driver.
func (d *Device) DrawPixelXY(frame, x, y, value uint8) (err error) {
return d.setPixelPWD(frame, 16*x+y, value)
}
// New creates a raw driver w/o any preset board layout.
// Addresses:
// - 0x74 (AD pin connected to GND)
// - 0x75 (AD pin connected to SCL)
// - 0x76 (AD pin connected to SDA)
// - 0x77 (AD pin connected to VCC)
func New(bus drivers.I2C, address uint8) Device {
return Device{
Address: address,
bus: bus,
}
}
+106
View File
@@ -0,0 +1,106 @@
package is31fl3731
import (
"fmt"
"tinygo.org/x/drivers"
)
// DeviceAdafruitCharlieWing15x7 implements TinyGo driver for Lumissil
// IS31FL3731 matrix LED driver on Adafruit 15x7 CharliePlex LED Matrix
// FeatherWing (CharlieWing) board: https://www.adafruit.com/product/3163
type DeviceAdafruitCharlieWing15x7 struct {
Device
}
// enableLEDs enables only LEDs that are soldered on the Adafruit CharlieWing
// board. The board has following LEDs matrix layout:
//
// "o" - connected (soldered) LEDs
// "x" - not connected LEDs
//
// + - - - - - - - - - - - - - - +
// | + - - - - - - - - - - - - + |
// | | | |
// | | v v
// +---------------------------------+
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | o o o o o o o o o o o o o o o x |
// | x x x x x x x x x x x x x x x x |
// +---------------------------------+
// ^ ^ | |
// | | ... - - + |
// | + - - - - - - - - - - - - - +
// |
// start (address 0x00)
//
func (d *DeviceAdafruitCharlieWing15x7) enableLEDs() (err error) {
for frame := FRAME_0; frame <= FRAME_7; frame++ {
err = d.selectCommand(frame)
if err != nil {
return err
}
// Enable left half
for i := uint8(0); i < 16; i += 2 {
err = d.bus.WriteRegister(d.Address, i, []byte{0b11111110})
if err != nil {
return err
}
}
// Enable right half
for i := uint8(3); i < 16; i += 2 {
err = d.bus.WriteRegister(d.Address, i, []byte{0b01111111})
if err != nil {
return err
}
}
// Disable invisible column on the right side
err = d.bus.WriteRegister(d.Address, 1, []byte{0b00000000})
if err != nil {
return err
}
}
return nil
}
// DrawPixelXY draws a single pixel on the selected frame by its XY coordinates
// with provided PWM value [0-255]
func (d *DeviceAdafruitCharlieWing15x7) DrawPixelXY(frame, x, y, value uint8) (err error) {
var index uint8
if x >= 15 {
return fmt.Errorf("invalid value: X is out of range [0, 15]")
} else if y >= 7 {
return fmt.Errorf("invalid value: Y is out of range [0, 7]")
}
// Board is one pixel shorter (7 vs 8 supported pixels)
if x < 8 {
index = 16*x + y + 1
} else {
index = 16*(16-x) - y - 1 - 1
}
return d.setPixelPWD(frame, index, value)
}
// NewAdafruitCharlieWing15x7 creates a new driver with Adafruit 15x7
// CharliePlex LED Matrix FeatherWing (CharlieWing) layout.
// Available addresses:
// - 0x74 (default)
// - 0x77 (when the address jumper soldered)
func NewAdafruitCharlieWing15x7(bus drivers.I2C, address uint8) DeviceAdafruitCharlieWing15x7 {
return DeviceAdafruitCharlieWing15x7{
Device: Device{
Address: address,
bus: bus,
},
}
}
+52
View File
@@ -0,0 +1,52 @@
package is31fl3731
// Registers. Names taken from the datasheet:
// https://www.lumissil.com/assets/pdf/core/IS31FL3731_DS.pdf
const (
// AD pin connected to GND
I2C_ADDRESS_74 uint8 = 0x74
// AD pin connected to SCL
I2C_ADDRESS_75 uint8 = 0x75
// AD pin connected to SDA
I2C_ADDRESS_76 uint8 = 0x76
// AD pin connected to VCC
I2C_ADDRESS_77 uint8 = 0x77
// Main command register
COMMAND uint8 = 0xFD
// Commands for each of 8 frames
FRAME_0 uint8 = 0x00
FRAME_1 uint8 = 0x01
FRAME_2 uint8 = 0x02
FRAME_3 uint8 = 0x03
FRAME_4 uint8 = 0x04
FRAME_5 uint8 = 0x05
FRAME_6 uint8 = 0x06
FRAME_7 uint8 = 0x07
// Command to set configuration
FUNCTION uint8 = 0x0B
// Configuration:
SET_DISPLAY_MODE uint8 = 0x00
SET_ACTIVE_FRAME uint8 = 0x01
SET_AUDIOSYNC uint8 = 0x06
SET_SHUTDOWN uint8 = 0x0A
// Configuration: display mode
DISPLAY_MODE_PICTURE uint8 = 0x00
// Configuration: audiosync (enable audio signal to modulate the intensity of
// the matrix)
AUDIOSYNC_OFF uint8 = 0x00
AUDIOSYNC_ON uint8 = 0x01
// Configuration: software shutdown
SOFTWARE_OFF uint8 = 0x00
SOFTWARE_ON uint8 = 0x01
// Frame LEDs
LED_CONTROL_OFFSET uint8 = 0x00 // to on/off each LED
LED_PWM_OFFSET uint8 = 0x24 // to set PWM (0-255) for each LED
)