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.
This commit is contained in:
Ayke van Laethem
2023-11-09 18:35:28 +01:00
committed by Ron Evans
parent f96a70915e
commit 4a9667ffef
+61 -52
View File
@@ -11,6 +11,7 @@ import (
"errors" "errors"
"tinygo.org/x/drivers" "tinygo.org/x/drivers"
"tinygo.org/x/drivers/pixel"
) )
type Model uint8 type Model uint8
@@ -20,12 +21,23 @@ type Model uint8
// Deprecated: use drivers.Rotation instead. // Deprecated: use drivers.Rotation instead.
type Rotation = drivers.Rotation type Rotation = drivers.Rotation
// Pixel formats supported by the st7735 driver.
type Color interface {
pixel.RGB444BE | pixel.RGB565BE
pixel.BaseColor
}
var ( var (
errOutOfBounds = errors.New("rectangle coordinates outside display area") errOutOfBounds = errors.New("rectangle coordinates outside display area")
) )
// Device wraps an SPI connection. // Device wraps an SPI connection.
type Device struct { type Device = DeviceOf[pixel.RGB565BE]
// DeviceOf is a generic version of Device, which supports different pixel
// formats.
type DeviceOf[T Color] struct {
bus drivers.SPI bus drivers.SPI
dcPin machine.Pin dcPin machine.Pin
resetPin machine.Pin resetPin machine.Pin
@@ -39,7 +51,7 @@ type Device struct {
batchLength int16 batchLength int16
model Model model Model
isBGR bool isBGR bool
batchData []uint8 batchData pixel.Image[T] // "image" with width, height of (batchLength, 1)
} }
// Config is the configuration for the display // Config is the configuration for the display
@@ -54,11 +66,17 @@ type Config struct {
// New creates a new ST7735 connection. The SPI wire must already be configured. // New creates a new ST7735 connection. The SPI wire must already be configured.
func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device { func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin)
}
// NewOf creates a new ST7735 connection with a particular pixel format. The SPI
// wire must already be configured.
func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) DeviceOf[T] {
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
blPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
return Device{ return DeviceOf[T]{
bus: bus, bus: bus,
dcPin: dcPin, dcPin: dcPin,
resetPin: resetPin, resetPin: resetPin,
@@ -68,7 +86,7 @@ func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
} }
// Configure initializes the display with default configuration // Configure initializes the display with default configuration
func (d *Device) Configure(cfg Config) { func (d *DeviceOf[T]) Configure(cfg Config) {
d.model = cfg.Model d.model = cfg.Model
if cfg.Width != 0 { if cfg.Width != 0 {
d.width = cfg.Width d.width = cfg.Width
@@ -93,7 +111,7 @@ func (d *Device) Configure(cfg Config) {
d.batchLength = d.height d.batchLength = d.height
} }
d.batchLength += d.batchLength & 1 d.batchLength += d.batchLength & 1
d.batchData = make([]uint8, d.batchLength*2) d.batchData = pixel.NewImage[T](int(d.batchLength), 1)
// reset the device // reset the device
d.resetPin.High() d.resetPin.High()
@@ -142,8 +160,16 @@ func (d *Device) Configure(cfg Config) {
d.Data(0xEE) d.Data(0xEE)
d.Command(VMCTR1) d.Command(VMCTR1)
d.Data(0x0E) d.Data(0x0E)
// Set the color format depending on the generic type.
d.Command(COLMOD) d.Command(COLMOD)
d.Data(0x05) var zeroColor T
switch any(zeroColor).(type) {
case pixel.RGB444BE:
d.Data(0x03) // 12 bits per pixel
default:
d.Data(0x05) // 16 bits per pixel
}
if d.model == GREENTAB { if d.model == GREENTAB {
d.InvertColors(false) d.InvertColors(false)
@@ -204,12 +230,12 @@ func (d *Device) Configure(cfg Config) {
} }
// Display does nothing, there's no buffer as it might be too big for some boards // Display does nothing, there's no buffer as it might be too big for some boards
func (d *Device) Display() error { func (d *DeviceOf[T]) Display() error {
return nil return nil
} }
// SetPixel sets a pixel in the screen // SetPixel sets a pixel in the screen
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) { func (d *DeviceOf[T]) SetPixel(x int16, y int16, c color.RGBA) {
w, h := d.Size() w, h := d.Size()
if x < 0 || y < 0 || x >= w || y >= h { if x < 0 || y < 0 || x >= w || y >= h {
return return
@@ -218,7 +244,7 @@ func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
} }
// setWindow prepares the screen to be modified at a given rectangle // setWindow prepares the screen to be modified at a given rectangle
func (d *Device) setWindow(x, y, w, h int16) { func (d *DeviceOf[T]) setWindow(x, y, w, h int16) {
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 { if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
x += d.columnOffset x += d.columnOffset
y += d.rowOffset y += d.rowOffset
@@ -234,7 +260,7 @@ func (d *Device) setWindow(x, y, w, h int16) {
} }
// SetScrollWindow sets an area to scroll with fixed top and bottom parts of the display // SetScrollWindow sets an area to scroll with fixed top and bottom parts of the display
func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) { func (d *DeviceOf[T]) SetScrollArea(topFixedArea, bottomFixedArea int16) {
// TODO: this code is broken, see the st7789 and ili9341 implementations for // TODO: this code is broken, see the st7789 and ili9341 implementations for
// how to do this correctly. // how to do this correctly.
d.Command(VSCRDEF) d.Command(VSCRDEF)
@@ -246,38 +272,32 @@ func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
} }
// SetScroll sets the vertical scroll address of the display. // SetScroll sets the vertical scroll address of the display.
func (d *Device) SetScroll(line int16) { func (d *DeviceOf[T]) SetScroll(line int16) {
d.Command(VSCRSADD) d.Command(VSCRSADD)
d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false) d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false)
} }
// SpotScroll returns the display to its normal state // SpotScroll returns the display to its normal state
func (d *Device) StopScroll() { func (d *DeviceOf[T]) StopScroll() {
d.Command(NORON) d.Command(NORON)
} }
// FillRectangle fills a rectangle at a given coordinates with a color // FillRectangle fills a rectangle at a given coordinates with a color
func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error { func (d *DeviceOf[T]) FillRectangle(x, y, width, height int16, c color.RGBA) error {
k, i := d.Size() k, i := d.Size()
if x < 0 || y < 0 || width <= 0 || height <= 0 || if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= k || (x+width) > k || y >= i || (y+height) > i { x >= k || (x+width) > k || y >= i || (y+height) > i {
return errors.New("rectangle coordinates outside display area") return errors.New("rectangle coordinates outside display area")
} }
d.setWindow(x, y, width, height) d.setWindow(x, y, width, height)
c565 := RGBATo565(c)
c1 := uint8(c565 >> 8)
c2 := uint8(c565)
for i = 0; i < d.batchLength; i++ { d.batchData.FillSolidColor(pixel.NewColor[T](c.R, c.G, c.B))
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
}
i = width * height i = width * height
for i > 0 { for i > 0 {
if i >= d.batchLength { if i >= d.batchLength {
d.Tx(d.batchData, false) d.Tx(d.batchData.RawBuffer(), false)
} else { } else {
d.Tx(d.batchData[:i*2], false) d.Tx(d.batchData.Rescale(int(i), 1).RawBuffer(), false)
} }
i -= d.batchLength i -= d.batchLength
} }
@@ -285,7 +305,7 @@ func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
} }
// DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates // DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error { func (d *DeviceOf[T]) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
k, i := d.Size() k, i := d.Size()
if x < 0 || y < 0 || w <= 0 || h <= 0 || if x < 0 || y < 0 || w <= 0 || h <= 0 ||
x >= k || (x+w) > k || y >= i || (y+h) > i { 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 {
} }
// FillRectangle fills a rectangle at a given coordinates with a buffer // FillRectangle fills a rectangle at a given coordinates with a buffer
func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error { func (d *DeviceOf[T]) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
k, l := d.Size() k, l := d.Size()
if x < 0 || y < 0 || width <= 0 || height <= 0 || if x < 0 || y < 0 || width <= 0 || height <= 0 ||
x >= k || (x+width) > k || y >= l || (y+height) > l { 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
for k > 0 { for k > 0 {
for i := int16(0); i < d.batchLength; i++ { for i := int16(0); i < d.batchLength; i++ {
if offset+i < l { if offset+i < l {
c565 := RGBATo565(buffer[offset+i]) c := buffer[offset+i]
c1 := uint8(c565 >> 8) d.batchData.Set(int(i), 0, pixel.NewColor[T](c.R, c.G, c.B))
c2 := uint8(c565)
d.batchData[i*2] = c1
d.batchData[i*2+1] = c2
} }
} }
if k >= d.batchLength { if k >= d.batchLength {
d.Tx(d.batchData, false) d.Tx(d.batchData.RawBuffer(), false)
} else { } else {
d.Tx(d.batchData[:k*2], false) d.Tx(d.batchData.Rescale(int(k), 1).RawBuffer(), false)
} }
k -= d.batchLength k -= d.batchLength
offset += d.batchLength offset += d.batchLength
@@ -334,7 +351,7 @@ func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []col
} }
// DrawFastVLine draws a vertical line faster than using SetPixel // DrawFastVLine draws a vertical line faster than using SetPixel
func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) { func (d *DeviceOf[T]) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
if y0 > y1 { if y0 > y1 {
y0, y1 = y1, y0 y0, y1 = y1, y0
} }
@@ -342,7 +359,7 @@ func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
} }
// DrawFastHLine draws a horizontal line faster than using SetPixel // DrawFastHLine draws a horizontal line faster than using SetPixel
func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) { func (d *DeviceOf[T]) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
if x0 > x1 { if x0 > x1 {
x0, x1 = x1, x0 x0, x1 = x1, x0
} }
@@ -350,7 +367,7 @@ func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
} }
// FillScreen fills the screen with a given color // FillScreen fills the screen with a given color
func (d *Device) FillScreen(c color.RGBA) { func (d *DeviceOf[T]) FillScreen(c color.RGBA) {
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 { if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
d.FillRectangle(0, 0, d.width, d.height, c) d.FillRectangle(0, 0, d.width, d.height, c)
} else { } else {
@@ -359,12 +376,12 @@ func (d *Device) FillScreen(c color.RGBA) {
} }
// Rotation returns the currently configured rotation. // Rotation returns the currently configured rotation.
func (d *Device) Rotation() drivers.Rotation { func (d *DeviceOf[T]) Rotation() drivers.Rotation {
return d.rotation return d.rotation
} }
// SetRotation changes the rotation of the device (clock-wise) // SetRotation changes the rotation of the device (clock-wise)
func (d *Device) SetRotation(rotation drivers.Rotation) error { func (d *DeviceOf[T]) SetRotation(rotation drivers.Rotation) error {
d.rotation = rotation d.rotation = rotation
madctl := uint8(0) madctl := uint8(0)
switch rotation % 4 { switch rotation % 4 {
@@ -386,23 +403,23 @@ func (d *Device) SetRotation(rotation drivers.Rotation) error {
} }
// Command sends a command to the display // Command sends a command to the display
func (d *Device) Command(command uint8) { func (d *DeviceOf[T]) Command(command uint8) {
d.Tx([]byte{command}, true) d.Tx([]byte{command}, true)
} }
// Command sends a data to the display // Command sends a data to the display
func (d *Device) Data(data uint8) { func (d *DeviceOf[T]) Data(data uint8) {
d.Tx([]byte{data}, false) d.Tx([]byte{data}, false)
} }
// Tx sends data to the display // Tx sends data to the display
func (d *Device) Tx(data []byte, isCommand bool) { func (d *DeviceOf[T]) Tx(data []byte, isCommand bool) {
d.dcPin.Set(!isCommand) d.dcPin.Set(!isCommand)
d.bus.Tx(data, nil) d.bus.Tx(data, nil)
} }
// Size returns the current size of the display. // Size returns the current size of the display.
func (d *Device) Size() (w, h int16) { func (d *DeviceOf[T]) Size() (w, h int16) {
if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 { if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
return d.width, d.height return d.width, d.height
} }
@@ -410,7 +427,7 @@ func (d *Device) Size() (w, h int16) {
} }
// EnableBacklight enables or disables the backlight // EnableBacklight enables or disables the backlight
func (d *Device) EnableBacklight(enable bool) { func (d *DeviceOf[T]) EnableBacklight(enable bool) {
if enable { if enable {
d.blPin.High() d.blPin.High()
} else { } else {
@@ -421,7 +438,7 @@ func (d *Device) EnableBacklight(enable bool) {
// Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot // Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot
// less power. The LCD won't display an image anymore, but the memory contents // less power. The LCD won't display an image anymore, but the memory contents
// will be kept. // will be kept.
func (d *Device) Sleep(sleepEnabled bool) error { func (d *DeviceOf[T]) Sleep(sleepEnabled bool) error {
if sleepEnabled { if sleepEnabled {
// Shut down LCD panel. // Shut down LCD panel.
d.Command(SLPIN) d.Command(SLPIN)
@@ -437,7 +454,7 @@ func (d *Device) Sleep(sleepEnabled bool) error {
} }
// InverColors inverts the colors of the screen // InverColors inverts the colors of the screen
func (d *Device) InvertColors(invert bool) { func (d *DeviceOf[T]) InvertColors(invert bool) {
if invert { if invert {
d.Command(INVON) d.Command(INVON)
} else { } else {
@@ -446,14 +463,6 @@ func (d *Device) InvertColors(invert bool) {
} }
// IsBGR changes the color mode (RGB/BGR) // IsBGR changes the color mode (RGB/BGR)
func (d *Device) IsBGR(bgr bool) { func (d *DeviceOf[T]) IsBGR(bgr bool) {
d.isBGR = bgr d.isBGR = bgr
} }
// RGBATo565 converts a color.RGBA to uint16 used in the display
func RGBATo565(c color.RGBA) uint16 {
r, g, b, _ := c.RGBA()
return uint16((r & 0xF800) +
((g & 0xFC00) >> 5) +
((b & 0xF800) >> 11))
}