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 }