tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/is31fl3731/is31fl3731.go (about)

     1  // Package is31fl3731 provides a driver for the Lumissil IS31FL3731 matrix LED
     2  // driver.
     3  //
     4  // Driver supports following layouts:
     5  //   - any custom LED matrix layout
     6  //   - Adafruit 15x7 CharliePlex LED Matrix FeatherWing (CharlieWing)
     7  //     https://www.adafruit.com/product/3163
     8  //
     9  // Datasheet:
    10  //
    11  //	https://www.lumissil.com/assets/pdf/core/IS31FL3731_DS.pdf
    12  //
    13  // This driver inspired by Adafruit Python driver:
    14  //
    15  //	https://github.com/adafruit/Adafruit_CircuitPython_IS31FL3731
    16  package is31fl3731
    17  
    18  import (
    19  	"fmt"
    20  	"time"
    21  
    22  	"tinygo.org/x/drivers"
    23  	"tinygo.org/x/drivers/internal/legacy"
    24  )
    25  
    26  // Device implements TinyGo driver for Lumissil IS31FL3731 matrix LED driver
    27  type Device struct {
    28  	Address uint8
    29  	bus     drivers.I2C
    30  
    31  	// Currently selected command register (one of the frame registers or the
    32  	// function register)
    33  	selectedCommand uint8
    34  }
    35  
    36  // Configure chip for operating as a LED matrix display
    37  func (d *Device) Configure() (err error) {
    38  	// Shutdown software
    39  	err = d.writeFunctionRegister(SET_SHUTDOWN, []byte{SOFTWARE_OFF})
    40  	if err != nil {
    41  		return fmt.Errorf("failed to shutdown: %w", err)
    42  	}
    43  
    44  	time.Sleep(time.Millisecond * 10)
    45  
    46  	// Wake up software
    47  	err = d.writeFunctionRegister(SET_SHUTDOWN, []byte{SOFTWARE_ON})
    48  	if err != nil {
    49  		return fmt.Errorf("failed to wake up: %w", err)
    50  	}
    51  
    52  	// Set display to a picture mode ("auto frame play mode" and "audio frame play
    53  	// mode" are not supported in this version of the driver)
    54  	err = d.writeFunctionRegister(SET_DISPLAY_MODE, []byte{DISPLAY_MODE_PICTURE})
    55  	if err != nil {
    56  		return fmt.Errorf("failed to switch to a picture move: %w", err)
    57  	}
    58  
    59  	// Enable LEDs that are present (soldered) on the board. From the datasheet:
    60  	// LEDs which are no connected must be off by LED Control Register (Frame
    61  	// Registers) or it will affect other LEDs
    62  	err = d.enableLEDs()
    63  	if err != nil {
    64  		return fmt.Errorf("failed to enable LEDs: %w", err)
    65  	}
    66  
    67  	// Disable audiosync
    68  	err = d.writeFunctionRegister(SET_AUDIOSYNC, []byte{AUDIOSYNC_OFF})
    69  	if err != nil {
    70  		return fmt.Errorf("failed to disable audiosync: %w", err)
    71  	}
    72  
    73  	// Clear all frames
    74  	for frame := FRAME_0; frame <= FRAME_7; frame++ {
    75  		err = d.Clear(frame)
    76  		if err != nil {
    77  			return fmt.Errorf("failed to clear frame %d: %w", frame, err)
    78  		}
    79  	}
    80  
    81  	// 1st frame is displayed by default
    82  	err = d.SetActiveFrame(FRAME_0)
    83  	if err != nil {
    84  		return fmt.Errorf("failed to set active frame: %w", err)
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  // selectCommand selects command register, can be:
    91  // - frame registers 0-7
    92  // - function register
    93  func (d *Device) selectCommand(command uint8) (err error) {
    94  	if command != d.selectedCommand {
    95  		d.selectedCommand = command
    96  		return legacy.WriteRegister(d.bus, d.Address, COMMAND, []byte{command})
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  // writeFunctionRegister selects the function register and writes data into it
   103  func (d *Device) writeFunctionRegister(operation uint8, data []byte) (err error) {
   104  	err = d.selectCommand(FUNCTION)
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	return legacy.WriteRegister(d.bus, d.Address, operation, data)
   110  }
   111  
   112  // enableLEDs enables only LEDs that are soldered on the set board. Enabled
   113  // all 16x9 LEDs by default
   114  func (d *Device) enableLEDs() (err error) {
   115  	for frame := FRAME_0; frame <= FRAME_7; frame++ {
   116  		err = d.selectCommand(frame)
   117  		if err != nil {
   118  			return err
   119  		}
   120  
   121  		// Enable every LED (16 columns x 9 rows)
   122  		for i := uint8(0); i < 16; i++ {
   123  			err = legacy.WriteRegister(d.bus, d.Address, i, []byte{0xFF})
   124  			if err != nil {
   125  				return err
   126  			}
   127  		}
   128  	}
   129  
   130  	return nil
   131  }
   132  
   133  // setPixelPWD sets individual pixel's PWM value [0-255] on the selected frame
   134  func (d *Device) setPixelPWD(frame, n, value uint8) (err error) {
   135  	err = d.selectCommand(frame)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	return legacy.WriteRegister(d.bus, d.Address, LED_PWM_OFFSET+n, []byte{value})
   141  }
   142  
   143  // SetActiveFrame sets frame to display with LEDs
   144  func (d *Device) SetActiveFrame(frame uint8) (err error) {
   145  	if frame > FRAME_7 {
   146  		return fmt.Errorf("frame %d is out of valid range [0-7]", frame)
   147  	}
   148  
   149  	return d.writeFunctionRegister(SET_ACTIVE_FRAME, []byte{frame})
   150  }
   151  
   152  // Fill the whole frame with provided PWM value [0-255]
   153  func (d *Device) Fill(frame, value uint8) (err error) {
   154  	if frame > FRAME_7 {
   155  		return fmt.Errorf("frame %d is out of valid range [0-7]", frame)
   156  	}
   157  
   158  	err = d.selectCommand(frame)
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	data := make([]byte, 24)
   164  	for i := range data {
   165  		data[i] = value
   166  	}
   167  
   168  	for i := uint8(0); i < 6; i++ {
   169  		err = legacy.WriteRegister(d.bus, d.Address, LED_PWM_OFFSET+i*24, data)
   170  		if err != nil {
   171  			return err
   172  		}
   173  	}
   174  
   175  	return nil
   176  }
   177  
   178  // Clear the whole frame
   179  func (d *Device) Clear(frame uint8) (err error) {
   180  	return d.Fill(frame, 0x00)
   181  }
   182  
   183  // DrawPixelIndex draws a single pixel on the selected frame by its index with
   184  // provided PWM value [0-255]
   185  func (d *Device) DrawPixelIndex(frame, index, value uint8) (err error) {
   186  	if frame > FRAME_7 {
   187  		return fmt.Errorf("frame %d is out of valid range [0-7]", frame)
   188  	}
   189  
   190  	return d.setPixelPWD(frame, index, value)
   191  }
   192  
   193  // DrawPixelXY draws a single pixel on the selected frame by its XY coordinates
   194  // with provided PWM value [0-255]. Raw LEDs layout assumed to be a 16x9 matrix,
   195  // and can be used with any custom board that has IS31FL3731 driver.
   196  func (d *Device) DrawPixelXY(frame, x, y, value uint8) (err error) {
   197  	return d.setPixelPWD(frame, 16*x+y, value)
   198  }
   199  
   200  // New creates a raw driver w/o any preset board layout.
   201  // Addresses:
   202  // - 0x74 (AD pin connected to GND)
   203  // - 0x75 (AD pin connected to SCL)
   204  // - 0x76 (AD pin connected to SDA)
   205  // - 0x77 (AD pin connected to VCC)
   206  func New(bus drivers.I2C, address uint8) Device {
   207  	return Device{
   208  		Address: address,
   209  		bus:     bus,
   210  	}
   211  }