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

     1  // Package hub75 implements a driver for the HUB75 LED matrix.
     2  //
     3  // Guide: https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf
     4  // This driver was inspired by https://github.com/2dom/PxMatrix
     5  package hub75 // import "tinygo.org/x/drivers/hub75"
     6  
     7  import (
     8  	"image/color"
     9  	"machine"
    10  	"time"
    11  
    12  	"tinygo.org/x/drivers"
    13  )
    14  
    15  type Config struct {
    16  	Width      int16
    17  	Height     int16
    18  	ColorDepth uint16
    19  	RowPattern int16
    20  	Brightness uint8
    21  	FastUpdate bool
    22  }
    23  
    24  type Device struct {
    25  	bus               drivers.SPI
    26  	a                 machine.Pin
    27  	b                 machine.Pin
    28  	c                 machine.Pin
    29  	d                 machine.Pin
    30  	oe                machine.Pin
    31  	lat               machine.Pin
    32  	width             int16
    33  	height            int16
    34  	brightness        uint8
    35  	fastUpdate        bool
    36  	colorDepth        uint16
    37  	colorStep         uint16
    38  	colorHalfStep     uint16
    39  	colorThirdStep    uint16
    40  	colorTwoThirdStep uint16
    41  	rowPattern        int16
    42  	rowsPerBuffer     int16
    43  	panelWidth        int16
    44  	panelWidthBytes   int16
    45  	pixelCounter      uint32
    46  	lineCounter       uint32
    47  	patternColorBytes uint8
    48  	rowSetsPerBuffer  uint8
    49  	sendBufferSize    uint16
    50  	rowOffset         []uint32
    51  	buffer            [][]uint8 // [ColorDepth][(width * height * 3(rgb)) / 8]uint8
    52  	displayColor      uint16
    53  }
    54  
    55  // New returns a new HUB75 driver. Pass in a fully configured SPI bus.
    56  func New(b drivers.SPI, latPin, oePin, aPin, bPin, cPin, dPin machine.Pin) Device {
    57  	aPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    58  	bPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    59  	cPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    60  	dPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    61  	oePin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    62  	latPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    63  
    64  	return Device{
    65  		bus: b,
    66  		a:   aPin,
    67  		b:   bPin,
    68  		c:   cPin,
    69  		d:   dPin,
    70  		oe:  oePin,
    71  		lat: latPin,
    72  	}
    73  }
    74  
    75  // Configure sets up the device.
    76  func (d *Device) Configure(cfg Config) {
    77  	if cfg.Width != 0 {
    78  		d.width = cfg.Width
    79  	} else {
    80  		d.width = 64
    81  	}
    82  	if cfg.Height != 0 {
    83  		d.height = cfg.Height
    84  	} else {
    85  		d.height = 32
    86  	}
    87  	if cfg.ColorDepth != 0 {
    88  		d.colorDepth = cfg.ColorDepth
    89  	} else {
    90  		d.colorDepth = 8
    91  	}
    92  	if cfg.RowPattern != 0 {
    93  		d.rowPattern = cfg.RowPattern
    94  	} else {
    95  		d.rowPattern = 16
    96  	}
    97  	if cfg.Brightness != 0 {
    98  		d.brightness = cfg.Brightness
    99  	} else {
   100  		d.brightness = 255
   101  	}
   102  
   103  	d.fastUpdate = cfg.FastUpdate
   104  	d.rowsPerBuffer = d.height / 2
   105  	d.panelWidth = 1
   106  	d.panelWidthBytes = (d.width / d.panelWidth) / 8
   107  	d.rowOffset = make([]uint32, d.height)
   108  	d.patternColorBytes = uint8((d.height / d.rowPattern) * (d.width / 8))
   109  	d.rowSetsPerBuffer = uint8(d.rowsPerBuffer / d.rowPattern)
   110  	d.sendBufferSize = uint16(d.patternColorBytes) * 3
   111  	d.colorStep = 256 / d.colorDepth
   112  	d.colorHalfStep = d.colorStep / 2
   113  	d.colorThirdStep = d.colorStep / 3
   114  	d.colorTwoThirdStep = 2 * d.colorThirdStep
   115  	d.buffer = make([][]uint8, d.colorDepth)
   116  	for i := range d.buffer {
   117  		d.buffer[i] = make([]uint8, (d.width*d.height*3)/8)
   118  	}
   119  
   120  	d.colorHalfStep = d.colorStep / 2
   121  	d.colorThirdStep = d.colorStep / 3
   122  	d.colorTwoThirdStep = 2 * d.colorThirdStep
   123  
   124  	d.a.Low()
   125  	d.b.Low()
   126  	d.c.Low()
   127  	d.d.Low()
   128  	d.oe.High()
   129  
   130  	var i uint32
   131  	for i = 0; i < uint32(d.height); i++ {
   132  		d.rowOffset[i] = (i%uint32(d.rowPattern))*uint32(d.sendBufferSize) + uint32(d.sendBufferSize) - 1
   133  	}
   134  }
   135  
   136  // SetPixel modifies the internal buffer in a single pixel.
   137  func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
   138  	d.fillMatrixBuffer(x, y, c.R, c.G, c.B)
   139  }
   140  
   141  // fillMatrixBuffer modifies a pixel in the internal buffer given position and RGB values
   142  func (d *Device) fillMatrixBuffer(x int16, y int16, r uint8, g uint8, b uint8) {
   143  	if x < 0 || x >= d.width || y < 0 || y >= d.height {
   144  		return
   145  	}
   146  	x = d.width - 1 - x
   147  
   148  	var offsetR uint32
   149  	var offsetG uint32
   150  	var offsetB uint32
   151  
   152  	vertIndexInBuffer := uint8((int32(y) % int32(d.rowsPerBuffer)) / int32(d.rowPattern))
   153  	whichBuffer := uint8(y / d.rowsPerBuffer)
   154  	xByte := x / 8
   155  	whichPanel := uint8(xByte / d.panelWidthBytes)
   156  	inRowByteOffset := uint8(xByte % d.panelWidthBytes)
   157  
   158  	offsetR = d.rowOffset[y] - uint32(inRowByteOffset) - uint32(d.panelWidthBytes)*
   159  		(uint32(d.rowSetsPerBuffer)*(uint32(d.panelWidth)*uint32(whichBuffer)+uint32(whichPanel))+uint32(vertIndexInBuffer))
   160  	offsetG = offsetR - uint32(d.patternColorBytes)
   161  	offsetB = offsetG - uint32(d.patternColorBytes)
   162  
   163  	bitSelect := uint8(x % 8)
   164  
   165  	for c := uint16(0); c < d.colorDepth; c++ {
   166  		colorTresh := uint8(c*d.colorStep + d.colorHalfStep)
   167  		if r > colorTresh {
   168  			d.buffer[c][offsetR] |= 1 << bitSelect
   169  		} else {
   170  			d.buffer[c][offsetR] = d.buffer[c][offsetR] &^ 1 << bitSelect
   171  		}
   172  		if g > colorTresh {
   173  			d.buffer[(c+d.colorThirdStep)%d.colorDepth][offsetG] |= 1 << bitSelect
   174  		} else {
   175  			d.buffer[(c+d.colorThirdStep)%d.colorDepth][offsetG] &^= 1 << bitSelect
   176  		}
   177  		if b > colorTresh {
   178  			d.buffer[(c+d.colorTwoThirdStep)%d.colorDepth][offsetB] |= 1 << bitSelect
   179  		} else {
   180  			d.buffer[(c+d.colorTwoThirdStep)%d.colorDepth][offsetB] &^= 1 << bitSelect
   181  		}
   182  	}
   183  }
   184  
   185  // Display sends the buffer (if any) to the screen.
   186  func (d *Device) Display() error {
   187  	rp := uint16(d.rowPattern)
   188  	for i := uint16(0); i < rp; i++ {
   189  		// FAST UPDATES (only if brightness = 255)
   190  		if d.fastUpdate && d.brightness == 255 {
   191  			d.setMux((i + rp - 1) % rp)
   192  			d.lat.High()
   193  			d.oe.Low()
   194  			d.lat.Low()
   195  			time.Sleep(1 * time.Microsecond)
   196  			d.bus.Tx(d.buffer[d.displayColor][i*d.sendBufferSize:(i+1)*d.sendBufferSize], nil)
   197  			time.Sleep(10 * time.Microsecond)
   198  			d.oe.High()
   199  
   200  		} else { // NO FAST UPDATES
   201  			d.setMux(i)
   202  			d.bus.Tx(d.buffer[d.displayColor][i*d.sendBufferSize:(i+1)*d.sendBufferSize], nil)
   203  			d.latch((255 * uint16(d.brightness)) / 255)
   204  		}
   205  	}
   206  	d.displayColor++
   207  	if d.displayColor >= d.colorDepth {
   208  		d.displayColor = 0
   209  	}
   210  	return nil
   211  }
   212  
   213  func (d *Device) latch(showTime uint16) {
   214  	d.lat.High()
   215  	d.lat.Low()
   216  	d.oe.Low()
   217  	time.Sleep(time.Duration(showTime) * time.Microsecond)
   218  	d.oe.High()
   219  }
   220  
   221  func (d *Device) setMux(value uint16) {
   222  	if (value & 0x01) == 0x01 {
   223  		d.a.High()
   224  	} else {
   225  		d.a.Low()
   226  	}
   227  	if (value & 0x02) == 0x02 {
   228  		d.b.High()
   229  	} else {
   230  		d.b.Low()
   231  	}
   232  	if (value & 0x04) == 0x04 {
   233  		d.c.High()
   234  	} else {
   235  		d.c.Low()
   236  	}
   237  	if (value & 0x08) == 0x08 {
   238  		d.d.High()
   239  	} else {
   240  		d.d.Low()
   241  	}
   242  }
   243  
   244  // FlushDisplay flushes the display
   245  func (d *Device) FlushDisplay() {
   246  	var i uint16
   247  	for i = 0; i < d.sendBufferSize; i++ {
   248  		d.bus.Tx([]byte{0x00}, nil)
   249  	}
   250  }
   251  
   252  // SetBrightness changes the brightness of the display
   253  func (d *Device) SetBrightness(brightness uint8) {
   254  	d.brightness = brightness
   255  }
   256  
   257  // ClearDisplay erases the internal buffer
   258  func (d *Device) ClearDisplay() {
   259  	bufferSize := (d.width * d.height * 3) / 8
   260  	for c := uint16(0); c < d.colorDepth; c++ {
   261  		for j := int16(0); j < bufferSize; j++ {
   262  			d.buffer[c][j] = 0
   263  		}
   264  	}
   265  }
   266  
   267  // Size returns the current size of the display.
   268  func (d *Device) Size() (w, h int16) {
   269  	return d.width, d.height
   270  }