Skip to content

Commit 91eb8cb

Browse files
committed
st7735: make the display generic over RGB565 and RGB444
Using RGB444 instead of RGB565 can speed up graphics operations by up to 25%, especially on slow screens. But for full support, all parts of the driver need to be aware of the color format. It's possible to do this using a regular configuration variable, but it's unlikely to be very efficient. Hence the usage of generics.
1 parent 95246a7 commit 91eb8cb

File tree

1 file changed

+61
-52
lines changed

1 file changed

+61
-52
lines changed

st7735/st7735.go

Lines changed: 61 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"errors"
1212

1313
"tinygo.org/x/drivers"
14+
"tinygo.org/x/drivers/pixel"
1415
)
1516

1617
type Model uint8
@@ -20,12 +21,23 @@ type Model uint8
2021
// Deprecated: use drivers.Rotation instead.
2122
type Rotation = drivers.Rotation
2223

24+
// Pixel formats supported by the st7735 driver.
25+
type Color interface {
26+
pixel.RGB444BE | pixel.RGB565BE
27+
28+
pixel.BaseColor
29+
}
30+
2331
var (
2432
errOutOfBounds = errors.New("rectangle coordinates outside display area")
2533
)
2634

2735
// Device wraps an SPI connection.
28-
type Device struct {
36+
type Device = DeviceOf[pixel.RGB565BE]
37+
38+
// DeviceOf is a generic version of Device, which supports different pixel
39+
// formats.
40+
type DeviceOf[T Color] struct {
2941
bus drivers.SPI
3042
dcPin machine.Pin
3143
resetPin machine.Pin
@@ -39,7 +51,7 @@ type Device struct {
3951
batchLength int16
4052
model Model
4153
isBGR bool
42-
batchData []uint8
54+
batchData pixel.Image[T] // "image" with width, height of (batchLength, 1)
4355
}
4456

4557
// Config is the configuration for the display
@@ -54,11 +66,17 @@ type Config struct {
5466

5567
// New creates a new ST7735 connection. The SPI wire must already be configured.
5668
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
69+
return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin)
70+
}
71+
72+
// NewOf creates a new ST7735 connection with a particular pixel format. The SPI
73+
// wire must already be configured.
74+
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) DeviceOf[T] {
5775
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
5876
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
5977
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
6078
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
61-
return Device{
79+
return DeviceOf[T]{
6280
bus: bus,
6381
dcPin: dcPin,
6482
resetPin: resetPin,
@@ -68,7 +86,7 @@ func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
6886
}
6987

7088
// Configure initializes the display with default configuration
71-
func (d *Device) Configure(cfg Config) {
89+
func (d *DeviceOf[T]) Configure(cfg Config) {
7290
d.model = cfg.Model
7391
if cfg.Width != 0 {
7492
d.width = cfg.Width
@@ -93,7 +111,7 @@ func (d *Device) Configure(cfg Config) {
93111
d.batchLength = d.height
94112
}
95113
d.batchLength += d.batchLength & 1
96-
d.batchData = make([]uint8, d.batchLength*2)
114+
d.batchData = pixel.NewImage[T](int(d.batchLength), 1)
97115

98116
// reset the device
99117
d.resetPin.High()
@@ -142,8 +160,16 @@ func (d *Device) Configure(cfg Config) {
142160
d.Data(0xEE)
143161
d.Command(VMCTR1)
144162
d.Data(0x0E)
163+
164+
// Set the color format depending on the generic type.
145165
d.Command(COLMOD)
146-
d.Data(0x05)
166+
var zeroColor T
167+
switch any(zeroColor).(type) {
168+
case pixel.RGB444BE:
169+
d.Data(0x03) // 12 bits per pixel
170+
default:
171+
d.Data(0x05) // 16 bits per pixel
172+
}
147173

148174
if d.model == GREENTAB {
149175
d.InvertColors(false)
@@ -204,12 +230,12 @@ func (d *Device) Configure(cfg Config) {
204230
}
205231

206232
// Display does nothing, there's no buffer as it might be too big for some boards
207-
func (d *Device) Display() error {
233+
func (d *DeviceOf[T]) Display() error {
208234
return nil
209235
}
210236

211237
// SetPixel sets a pixel in the screen
212-
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
238+
func (d *DeviceOf[T]) SetPixel(x int16, y int16, c color.RGBA) {
213239
w, h := d.Size()
214240
if x < 0 || y < 0 || x >= w || y >= h {
215241
return
@@ -218,7 +244,7 @@ func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
218244
}
219245

220246
// setWindow prepares the screen to be modified at a given rectangle
221-
func (d *Device) setWindow(x, y, w, h int16) {
247+
func (d *DeviceOf[T]) setWindow(x, y, w, h int16) {
222248
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
223249
x += d.columnOffset
224250
y += d.rowOffset
@@ -234,7 +260,7 @@ func (d *Device) setWindow(x, y, w, h int16) {
234260
}
235261

236262
// SetScrollWindow sets an area to scroll with fixed top and bottom parts of the display
237-
func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
263+
func (d *DeviceOf[T]) SetScrollArea(topFixedArea, bottomFixedArea int16) {
238264
// TODO: this code is broken, see the st7789 and ili9341 implementations for
239265
// how to do this correctly.
240266
d.Command(VSCRDEF)
@@ -246,46 +272,40 @@ func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
246272
}
247273

248274
// SetScroll sets the vertical scroll address of the display.
249-
func (d *Device) SetScroll(line int16) {
275+
func (d *DeviceOf[T]) SetScroll(line int16) {
250276
d.Command(VSCRSADD)
251277
d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false)
252278
}
253279

254280
// SpotScroll returns the display to its normal state
255-
func (d *Device) StopScroll() {
281+
func (d *DeviceOf[T]) StopScroll() {
256282
d.Command(NORON)
257283
}
258284

259285
// FillRectangle fills a rectangle at a given coordinates with a color
260-
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
286+
func (d *DeviceOf[T]) FillRectangle(x, y, width, height int16, c color.RGBA) error {
261287
k, i := d.Size()
262288
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
263289
x >= k || (x+width) > k || y >= i || (y+height) > i {
264290
return errors.New("rectangle coordinates outside display area")
265291
}
266292
d.setWindow(x, y, width, height)
267-
c565 := RGBATo565(c)
268-
c1 := uint8(c565 >> 8)
269-
c2 := uint8(c565)
270293

271-
for i = 0; i < d.batchLength; i++ {
272-
d.batchData[i*2] = c1
273-
d.batchData[i*2+1] = c2
274-
}
294+
d.batchData.FillSolidColor(pixel.NewColor[T](c.R, c.G, c.B))
275295
i = width * height
276296
for i > 0 {
277297
if i >= d.batchLength {
278-
d.Tx(d.batchData, false)
298+
d.Tx(d.batchData.RawBuffer(), false)
279299
} else {
280-
d.Tx(d.batchData[:i*2], false)
300+
d.Tx(d.batchData.Rescale(int(i), 1).RawBuffer(), false)
281301
}
282302
i -= d.batchLength
283303
}
284304
return nil
285305
}
286306

287307
// DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
288-
func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
308+
func (d *DeviceOf[T]) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
289309
k, i := d.Size()
290310
if x < 0 || y < 0 || w <= 0 || h <= 0 ||
291311
x >= k || (x+w) > k || y >= i || (y+h) > i {
@@ -297,7 +317,7 @@ func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
297317
}
298318

299319
// FillRectangle fills a rectangle at a given coordinates with a buffer
300-
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
320+
func (d *DeviceOf[T]) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
301321
k, l := d.Size()
302322
if x < 0 || y < 0 || width <= 0 || height <= 0 ||
303323
x >= k || (x+width) > k || y >= l || (y+height) > l {
@@ -315,17 +335,14 @@ func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []col
315335
for k > 0 {
316336
for i := int16(0); i < d.batchLength; i++ {
317337
if offset+i < l {
318-
c565 := RGBATo565(buffer[offset+i])
319-
c1 := uint8(c565 >> 8)
320-
c2 := uint8(c565)
321-
d.batchData[i*2] = c1
322-
d.batchData[i*2+1] = c2
338+
c := buffer[offset+i]
339+
d.batchData.Set(int(i), 0, pixel.NewColor[T](c.R, c.G, c.B))
323340
}
324341
}
325342
if k >= d.batchLength {
326-
d.Tx(d.batchData, false)
343+
d.Tx(d.batchData.RawBuffer(), false)
327344
} else {
328-
d.Tx(d.batchData[:k*2], false)
345+
d.Tx(d.batchData.Rescale(int(k), 1).RawBuffer(), false)
329346
}
330347
k -= d.batchLength
331348
offset += d.batchLength
@@ -334,23 +351,23 @@ func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []col
334351
}
335352

336353
// DrawFastVLine draws a vertical line faster than using SetPixel
337-
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
354+
func (d *DeviceOf[T]) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
338355
if y0 > y1 {
339356
y0, y1 = y1, y0
340357
}
341358
d.FillRectangle(x, y0, 1, y1-y0+1, c)
342359
}
343360

344361
// DrawFastHLine draws a horizontal line faster than using SetPixel
345-
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
362+
func (d *DeviceOf[T]) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
346363
if x0 > x1 {
347364
x0, x1 = x1, x0
348365
}
349366
d.FillRectangle(x0, y, x1-x0+1, 1, c)
350367
}
351368

352369
// FillScreen fills the screen with a given color
353-
func (d *Device) FillScreen(c color.RGBA) {
370+
func (d *DeviceOf[T]) FillScreen(c color.RGBA) {
354371
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
355372
d.FillRectangle(0, 0, d.width, d.height, c)
356373
} else {
@@ -359,12 +376,12 @@ func (d *Device) FillScreen(c color.RGBA) {
359376
}
360377

361378
// Rotation returns the currently configured rotation.
362-
func (d *Device) Rotation() drivers.Rotation {
379+
func (d *DeviceOf[T]) Rotation() drivers.Rotation {
363380
return d.rotation
364381
}
365382

366383
// SetRotation changes the rotation of the device (clock-wise)
367-
func (d *Device) SetRotation(rotation drivers.Rotation) error {
384+
func (d *DeviceOf[T]) SetRotation(rotation drivers.Rotation) error {
368385
d.rotation = rotation
369386
madctl := uint8(0)
370387
switch rotation % 4 {
@@ -386,31 +403,31 @@ func (d *Device) SetRotation(rotation drivers.Rotation) error {
386403
}
387404

388405
// Command sends a command to the display
389-
func (d *Device) Command(command uint8) {
406+
func (d *DeviceOf[T]) Command(command uint8) {
390407
d.Tx([]byte{command}, true)
391408
}
392409

393410
// Command sends a data to the display
394-
func (d *Device) Data(data uint8) {
411+
func (d *DeviceOf[T]) Data(data uint8) {
395412
d.Tx([]byte{data}, false)
396413
}
397414

398415
// Tx sends data to the display
399-
func (d *Device) Tx(data []byte, isCommand bool) {
416+
func (d *DeviceOf[T]) Tx(data []byte, isCommand bool) {
400417
d.dcPin.Set(!isCommand)
401418
d.bus.Tx(data, nil)
402419
}
403420

404421
// Size returns the current size of the display.
405-
func (d *Device) Size() (w, h int16) {
422+
func (d *DeviceOf[T]) Size() (w, h int16) {
406423
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
407424
return d.width, d.height
408425
}
409426
return d.height, d.width
410427
}
411428

412429
// EnableBacklight enables or disables the backlight
413-
func (d *Device) EnableBacklight(enable bool) {
430+
func (d *DeviceOf[T]) EnableBacklight(enable bool) {
414431
if enable {
415432
d.blPin.High()
416433
} else {
@@ -421,7 +438,7 @@ func (d *Device) EnableBacklight(enable bool) {
421438
// Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot
422439
// less power. The LCD won't display an image anymore, but the memory contents
423440
// will be kept.
424-
func (d *Device) Sleep(sleepEnabled bool) error {
441+
func (d *DeviceOf[T]) Sleep(sleepEnabled bool) error {
425442
if sleepEnabled {
426443
// Shut down LCD panel.
427444
d.Command(SLPIN)
@@ -437,7 +454,7 @@ func (d *Device) Sleep(sleepEnabled bool) error {
437454
}
438455

439456
// InverColors inverts the colors of the screen
440-
func (d *Device) InvertColors(invert bool) {
457+
func (d *DeviceOf[T]) InvertColors(invert bool) {
441458
if invert {
442459
d.Command(INVON)
443460
} else {
@@ -446,14 +463,6 @@ func (d *Device) InvertColors(invert bool) {
446463
}
447464

448465
// IsBGR changes the color mode (RGB/BGR)
449-
func (d *Device) IsBGR(bgr bool) {
466+
func (d *DeviceOf[T]) IsBGR(bgr bool) {
450467
d.isBGR = bgr
451468
}
452-
453-
// RGBATo565 converts a color.RGBA to uint16 used in the display
454-
func RGBATo565(c color.RGBA) uint16 {
455-
r, g, b, _ := c.RGBA()
456-
return uint16((r & 0xF800) +
457-
((g & 0xFC00) >> 5) +
458-
((b & 0xF800) >> 11))
459-
}

0 commit comments

Comments
 (0)