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

     1  // Package st7789 implements a driver for the ST7789 TFT displays, it comes in various screen sizes.
     2  //
     3  // Datasheets: https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf
     4  //
     5  //	http://www.newhavendisplay.com/appnotes/datasheets/LCDs/ST7789V.pdf
     6  package st7789 // import "tinygo.org/x/drivers/st7789"
     7  
     8  import (
     9  	"image/color"
    10  	"machine"
    11  	"math"
    12  	"time"
    13  
    14  	"errors"
    15  
    16  	"tinygo.org/x/drivers"
    17  	"tinygo.org/x/drivers/pixel"
    18  )
    19  
    20  // Rotation controls the rotation used by the display.
    21  //
    22  // Deprecated: use drivers.Rotation instead.
    23  type Rotation = drivers.Rotation
    24  
    25  // The color format used on the display, like RGB565, RGB666, and RGB444.
    26  type ColorFormat uint8
    27  
    28  // Pixel formats supported by the st7789 driver.
    29  type Color interface {
    30  	pixel.RGB444BE | pixel.RGB565BE
    31  
    32  	pixel.BaseColor
    33  }
    34  
    35  // FrameRate controls the frame rate used by the display.
    36  type FrameRate uint8
    37  
    38  var (
    39  	errOutOfBounds = errors.New("rectangle coordinates outside display area")
    40  )
    41  
    42  // Device wraps an SPI connection.
    43  type Device = DeviceOf[pixel.RGB565BE]
    44  
    45  // DeviceOf is a generic version of Device. It supports multiple different pixel
    46  // formats.
    47  type DeviceOf[T Color] struct {
    48  	bus             drivers.SPI
    49  	dcPin           machine.Pin
    50  	resetPin        machine.Pin
    51  	csPin           machine.Pin
    52  	blPin           machine.Pin
    53  	width           int16
    54  	height          int16
    55  	columnOffsetCfg int16
    56  	rowOffsetCfg    int16
    57  	columnOffset    int16
    58  	rowOffset       int16
    59  	rotation        drivers.Rotation
    60  	frameRate       FrameRate
    61  	batchLength     int32
    62  	batchData       pixel.Image[T] // "image" with (width, height) of (batchLength, 1)
    63  	isBGR           bool
    64  	vSyncLines      int16
    65  	cmdBuf          [1]byte
    66  	buf             [6]byte
    67  }
    68  
    69  // Config is the configuration for the display
    70  type Config struct {
    71  	Width        int16
    72  	Height       int16
    73  	Rotation     drivers.Rotation
    74  	RowOffset    int16
    75  	ColumnOffset int16
    76  	FrameRate    FrameRate
    77  	VSyncLines   int16
    78  
    79  	// Gamma control. Look in the LCD panel datasheet or provided example code
    80  	// to find these values. If not set, the defaults will be used.
    81  	PVGAMCTRL []uint8 // Positive voltage gamma control (14 bytes)
    82  	NVGAMCTRL []uint8 // Negative voltage gamma control (14 bytes)
    83  }
    84  
    85  // New creates a new ST7789 connection. The SPI wire must already be configured.
    86  func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device {
    87  	return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin)
    88  }
    89  
    90  // NewOf creates a new ST7789 connection with a particular pixel format. The SPI
    91  // wire must already be configured.
    92  func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) DeviceOf[T] {
    93  	dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    94  	resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    95  	csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    96  	blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    97  	return DeviceOf[T]{
    98  		bus:      bus,
    99  		dcPin:    dcPin,
   100  		resetPin: resetPin,
   101  		csPin:    csPin,
   102  		blPin:    blPin,
   103  	}
   104  }
   105  
   106  // Configure initializes the display with default configuration
   107  func (d *DeviceOf[T]) Configure(cfg Config) {
   108  	if cfg.Width != 0 {
   109  		d.width = cfg.Width
   110  	} else {
   111  		d.width = 240
   112  	}
   113  	if cfg.Height != 0 {
   114  		d.height = cfg.Height
   115  	} else {
   116  		d.height = 240
   117  	}
   118  
   119  	d.rotation = cfg.Rotation
   120  	d.rowOffsetCfg = cfg.RowOffset
   121  	d.columnOffsetCfg = cfg.ColumnOffset
   122  
   123  	if cfg.FrameRate != 0 {
   124  		d.frameRate = cfg.FrameRate
   125  	} else {
   126  		d.frameRate = FRAMERATE_60
   127  	}
   128  
   129  	if cfg.VSyncLines >= 2 && cfg.VSyncLines <= 254 {
   130  		d.vSyncLines = cfg.VSyncLines
   131  	} else {
   132  		d.vSyncLines = 16
   133  	}
   134  
   135  	d.batchLength = int32(d.width)
   136  	if d.height > d.width {
   137  		d.batchLength = int32(d.height)
   138  	}
   139  	d.batchLength += d.batchLength & 1
   140  
   141  	// Reset the device
   142  	d.resetPin.High()
   143  	time.Sleep(50 * time.Millisecond)
   144  	d.resetPin.Low()
   145  	time.Sleep(50 * time.Millisecond)
   146  	d.resetPin.High()
   147  	time.Sleep(50 * time.Millisecond)
   148  
   149  	// Common initialization
   150  	d.startWrite()
   151  	d.sendCommand(SWRESET, nil) // Soft reset
   152  	d.endWrite()
   153  	time.Sleep(150 * time.Millisecond) //
   154  	d.startWrite()
   155  
   156  	d.sendCommand(SLPOUT, nil) // Exit sleep mode
   157  
   158  	// Memory initialization
   159  	var zeroColor T
   160  	switch any(zeroColor).(type) {
   161  	case pixel.RGB444BE:
   162  		d.setColorFormat(ColorRGB444) // 12 bits per pixel
   163  	default:
   164  		// Use default RGB565 color format.
   165  		d.setColorFormat(ColorRGB565) // 16 bits per pixel
   166  	}
   167  	time.Sleep(10 * time.Millisecond)
   168  
   169  	d.setRotation(d.rotation) // Memory orientation
   170  
   171  	d.setWindow(0, 0, d.width, d.height)   // Full draw window
   172  	d.fillScreen(color.RGBA{0, 0, 0, 255}) // Clear screen
   173  
   174  	// Framerate
   175  	d.sendCommand(FRCTRL2, []byte{byte(d.frameRate)}) // Frame rate for normal mode (default 60Hz)
   176  
   177  	// Frame vertical sync and "porch"
   178  	//
   179  	// Front and back porch controls vertical scanline sync time before and after
   180  	// a frame, where memory can be safely written without tearing.
   181  	//
   182  	fp := uint8(d.vSyncLines / 2)         // Split the desired pause half and half
   183  	bp := uint8(d.vSyncLines - int16(fp)) // between front and back porch.
   184  
   185  	d.sendCommand(PORCTRL, []byte{
   186  		bp,   // Back porch 5bit     (0x7F max 0x08 default)
   187  		fp,   // Front porch 5bit    (0x7F max 0x08 default)
   188  		0x00, // Seprarate porch     (TODO: what is this?)
   189  		0x22, // Idle mode porch     (4bit-back 4bit-front 0x22 default)
   190  		0x22, // Partial mode porch  (4bit-back 4bit-front 0x22 default)
   191  	})
   192  
   193  	// Ready to display
   194  	d.sendCommand(INVON, nil)         // Inversion ON
   195  	time.Sleep(10 * time.Millisecond) //
   196  
   197  	// Set gamma tables, if configured.
   198  	if len(cfg.PVGAMCTRL) == 14 {
   199  		d.sendCommand(GMCTRP1, cfg.PVGAMCTRL) // PVGAMCTRL: Positive Voltage Gamma Control
   200  	}
   201  	if len(cfg.NVGAMCTRL) == 14 {
   202  		d.sendCommand(GMCTRN1, cfg.NVGAMCTRL) // NVGAMCTRL: Negative Voltage Gamma Control
   203  	}
   204  
   205  	d.sendCommand(NORON, nil)         // Normal mode ON
   206  	time.Sleep(10 * time.Millisecond) //
   207  
   208  	d.sendCommand(DISPON, nil)        // Screen ON
   209  	time.Sleep(10 * time.Millisecond) //
   210  
   211  	d.endWrite()
   212  	d.blPin.High() // Backlight ON
   213  }
   214  
   215  // Send a command with data to the display. It does not change the chip select
   216  // pin (it must be low when calling). The DC pin is left high after return,
   217  // meaning that data can be sent right away.
   218  func (d *DeviceOf[T]) sendCommand(command uint8, data []byte) error {
   219  	d.cmdBuf[0] = command
   220  	d.dcPin.Low()
   221  	err := d.bus.Tx(d.cmdBuf[:1], nil)
   222  	d.dcPin.High()
   223  	if len(data) != 0 {
   224  		err = d.bus.Tx(data, nil)
   225  	}
   226  	return err
   227  }
   228  
   229  // startWrite must be called at the beginning of all exported methods to set the
   230  // chip select pin low.
   231  func (d *DeviceOf[T]) startWrite() {
   232  	if d.csPin != machine.NoPin {
   233  		d.csPin.Low()
   234  	}
   235  }
   236  
   237  // endWrite must be called at the end of all exported methods to set the chip
   238  // select pin high.
   239  func (d *DeviceOf[T]) endWrite() {
   240  	if d.csPin != machine.NoPin {
   241  		d.csPin.High()
   242  	}
   243  }
   244  
   245  // getBuffer returns the image buffer, that's always d.batchLength wide and 1
   246  // pixel high. It can be used as a temporary buffer to transmit image data.
   247  func (d *DeviceOf[T]) getBuffer() pixel.Image[T] {
   248  	if d.batchData.Len() == 0 {
   249  		d.batchData = pixel.NewImage[T](int(d.batchLength), 1)
   250  	}
   251  	return d.batchData
   252  }
   253  
   254  // Sync waits for the display to hit the next VSYNC pause
   255  func (d *DeviceOf[T]) Sync() {
   256  	d.SyncToScanLine(0)
   257  }
   258  
   259  // SyncToScanLine waits for the display to hit a specific scanline
   260  //
   261  // A scanline value of 0 will forward to the beginning of the next VSYNC,
   262  // even if the display is currently in a VSYNC pause.
   263  //
   264  // Syncline values appear to increment once for every two vertical
   265  // lines on the display.
   266  //
   267  // NOTE: Use GetHighestScanLine and GetLowestScanLine to obtain the highest
   268  // and lowest useful values. Values are affected by front and back porch
   269  // vsync settings (derived from VSyncLines configuration option).
   270  func (d *DeviceOf[T]) SyncToScanLine(scanline uint16) {
   271  	scan := d.GetScanLine()
   272  
   273  	// Sometimes GetScanLine returns erroneous 0 on first call after draw, so double check
   274  	if scan == 0 {
   275  		scan = d.GetScanLine()
   276  	}
   277  
   278  	if scanline == 0 {
   279  		// we dont know where we are in an ongoing vsync so go around
   280  		for scan < 1 {
   281  			time.Sleep(1 * time.Millisecond)
   282  			scan = d.GetScanLine()
   283  		}
   284  		for scan > 0 {
   285  			scan = d.GetScanLine()
   286  		}
   287  	} else {
   288  		// go around unless we're very close to the target
   289  		for scan > scanline+4 {
   290  			time.Sleep(1 * time.Millisecond)
   291  			scan = d.GetScanLine()
   292  		}
   293  		for scan < scanline {
   294  			scan = d.GetScanLine()
   295  		}
   296  	}
   297  }
   298  
   299  // GetScanLine reads the current scanline value from the display
   300  func (d *DeviceOf[T]) GetScanLine() uint16 {
   301  	d.startWrite()
   302  	data := []uint8{0x00, 0x00}
   303  	d.dcPin.Low()
   304  	d.bus.Transfer(GSCAN)
   305  	d.dcPin.High()
   306  	for i := range data {
   307  		data[i], _ = d.bus.Transfer(0xFF)
   308  	}
   309  	scanline := uint16(data[0])<<8 + uint16(data[1])
   310  	d.endWrite()
   311  	return scanline
   312  }
   313  
   314  // GetHighestScanLine calculates the last scanline id in the frame before VSYNC pause
   315  func (d *DeviceOf[T]) GetHighestScanLine() uint16 {
   316  	// Last scanline id appears to be backporch/2 + 320/2
   317  	return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 160
   318  }
   319  
   320  // GetLowestScanLine calculate the first scanline id to appear after VSYNC pause
   321  func (d *DeviceOf[T]) GetLowestScanLine() uint16 {
   322  	// First scanline id appears to be backporch/2 + 1
   323  	return uint16(math.Ceil(float64(d.vSyncLines)/2)/2) + 1
   324  }
   325  
   326  // Display does nothing, there's no buffer as it might be too big for some boards
   327  func (d *DeviceOf[T]) Display() error {
   328  	return nil
   329  }
   330  
   331  // SetPixel sets a pixel in the screen
   332  func (d *DeviceOf[T]) SetPixel(x int16, y int16, c color.RGBA) {
   333  	if x < 0 || y < 0 ||
   334  		(((d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180) && (x >= d.width || y >= d.height)) ||
   335  			((d.rotation == drivers.Rotation90 || d.rotation == drivers.Rotation270) && (x >= d.height || y >= d.width))) {
   336  		return
   337  	}
   338  	d.FillRectangle(x, y, 1, 1, c)
   339  }
   340  
   341  // setWindow prepares the screen to be modified at a given rectangle
   342  func (d *DeviceOf[T]) setWindow(x, y, w, h int16) {
   343  	x += d.columnOffset
   344  	y += d.rowOffset
   345  	copy(d.buf[:4], []uint8{uint8(x >> 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)})
   346  	d.sendCommand(CASET, d.buf[:4])
   347  	copy(d.buf[:4], []uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)})
   348  	d.sendCommand(RASET, d.buf[:4])
   349  	d.sendCommand(RAMWR, nil)
   350  }
   351  
   352  // FillRectangle fills a rectangle at a given coordinates with a color
   353  func (d *DeviceOf[T]) FillRectangle(x, y, width, height int16, c color.RGBA) error {
   354  	d.startWrite()
   355  	err := d.fillRectangle(x, y, width, height, c)
   356  	d.endWrite()
   357  	return err
   358  }
   359  
   360  func (d *DeviceOf[T]) fillRectangle(x, y, width, height int16, c color.RGBA) error {
   361  	k, i := d.Size()
   362  	if x < 0 || y < 0 || width <= 0 || height <= 0 ||
   363  		x >= k || (x+width) > k || y >= i || (y+height) > i {
   364  		return errors.New("rectangle coordinates outside display area")
   365  	}
   366  	d.setWindow(x, y, width, height)
   367  
   368  	image := d.getBuffer()
   369  	image.FillSolidColor(pixel.NewColor[T](c.R, c.G, c.B))
   370  	j := int(width) * int(height)
   371  	for j > 0 {
   372  		// The DC pin is already set to data in the setWindow call, so we can
   373  		// just write bytes on the SPI bus.
   374  		if j >= image.Len() {
   375  			d.bus.Tx(image.RawBuffer(), nil)
   376  		} else {
   377  			d.bus.Tx(image.Rescale(j, 1).RawBuffer(), nil)
   378  		}
   379  		j -= image.Len()
   380  	}
   381  	return nil
   382  }
   383  
   384  // DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
   385  //
   386  // Deprecated: use DrawBitmap instead.
   387  func (d *DeviceOf[T]) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
   388  	k, i := d.Size()
   389  	if x < 0 || y < 0 || w <= 0 || h <= 0 ||
   390  		x >= k || (x+w) > k || y >= i || (y+h) > i {
   391  		return errOutOfBounds
   392  	}
   393  	d.startWrite()
   394  	d.setWindow(x, y, w, h)
   395  	d.bus.Tx(data, nil)
   396  	d.endWrite()
   397  	return nil
   398  }
   399  
   400  // DrawBitmap copies the bitmap to the internal buffer on the screen at the
   401  // given coordinates. It returns once the image data has been sent completely.
   402  func (d *DeviceOf[T]) DrawBitmap(x, y int16, bitmap pixel.Image[T]) error {
   403  	width, height := bitmap.Size()
   404  	return d.DrawRGBBitmap8(x, y, bitmap.RawBuffer(), int16(width), int16(height))
   405  }
   406  
   407  // FillRectangleWithBuffer fills buffer with a rectangle at a given coordinates.
   408  func (d *DeviceOf[T]) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
   409  	i, j := d.Size()
   410  	if x < 0 || y < 0 || width <= 0 || height <= 0 ||
   411  		x >= i || (x+width) > i || y >= j || (y+height) > j {
   412  		return errors.New("rectangle coordinates outside display area")
   413  	}
   414  	if int32(width)*int32(height) != int32(len(buffer)) {
   415  		return errors.New("buffer length does not match with rectangle size")
   416  	}
   417  	d.startWrite()
   418  	d.setWindow(x, y, width, height)
   419  
   420  	k := int(width) * int(height)
   421  	image := d.getBuffer()
   422  	offset := 0
   423  	for k > 0 {
   424  		for i := 0; i < image.Len(); i++ {
   425  			if offset+i < len(buffer) {
   426  				c := buffer[offset+i]
   427  				image.Set(i, 0, pixel.NewColor[T](c.R, c.G, c.B))
   428  			}
   429  		}
   430  		// The DC pin is already set to data in the setWindow call, so we don't
   431  		// have to set it here.
   432  		if k >= image.Len() {
   433  			d.bus.Tx(image.RawBuffer(), nil)
   434  		} else {
   435  			d.bus.Tx(image.Rescale(k, 1).RawBuffer(), nil)
   436  		}
   437  		k -= image.Len()
   438  		offset += image.Len()
   439  	}
   440  	d.endWrite()
   441  	return nil
   442  }
   443  
   444  // DrawFastVLine draws a vertical line faster than using SetPixel
   445  func (d *DeviceOf[T]) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
   446  	if y0 > y1 {
   447  		y0, y1 = y1, y0
   448  	}
   449  	d.FillRectangle(x, y0, 1, y1-y0+1, c)
   450  }
   451  
   452  // DrawFastHLine draws a horizontal line faster than using SetPixel
   453  func (d *DeviceOf[T]) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
   454  	if x0 > x1 {
   455  		x0, x1 = x1, x0
   456  	}
   457  	d.FillRectangle(x0, y, x1-x0+1, 1, c)
   458  }
   459  
   460  // FillScreen fills the screen with a given color
   461  func (d *DeviceOf[T]) FillScreen(c color.RGBA) {
   462  	d.startWrite()
   463  	d.fillScreen(c)
   464  	d.endWrite()
   465  }
   466  
   467  func (d *DeviceOf[T]) fillScreen(c color.RGBA) {
   468  	if d.rotation == NO_ROTATION || d.rotation == ROTATION_180 {
   469  		d.fillRectangle(0, 0, d.width, d.height, c)
   470  	} else {
   471  		d.fillRectangle(0, 0, d.height, d.width, c)
   472  	}
   473  }
   474  
   475  // Control the color format that is used when writing to the screen.
   476  // The default is RGB565, setting it to any other value will break functions
   477  // like SetPixel, FillRectangle, etc. Instead, you can write color data in the
   478  // specified color format using DrawRGBBitmap8.
   479  func (d *DeviceOf[T]) SetColorFormat(format ColorFormat) {
   480  	d.startWrite()
   481  	d.setColorFormat(format)
   482  	d.endWrite()
   483  }
   484  
   485  func (d *DeviceOf[T]) setColorFormat(format ColorFormat) {
   486  	// Lower 4 bits set the color format used in SPI.
   487  	// Upper 4 bits set the color format used in the direct RGB interface.
   488  	// The RGB interface is not currently supported, so it is left at a
   489  	// reasonable default. Also, the RGB interface doesn't support RGB444.
   490  	colmod := byte(format) | 0x50
   491  	d.sendCommand(COLMOD, []byte{colmod})
   492  }
   493  
   494  // Rotation returns the current rotation of the device.
   495  func (d *DeviceOf[T]) Rotation() drivers.Rotation {
   496  	return d.rotation
   497  }
   498  
   499  // SetRotation changes the rotation of the device (clock-wise)
   500  func (d *DeviceOf[T]) SetRotation(rotation Rotation) error {
   501  	d.rotation = rotation
   502  	d.startWrite()
   503  	err := d.setRotation(rotation)
   504  	d.endWrite()
   505  	return err
   506  }
   507  
   508  func (d *DeviceOf[T]) setRotation(rotation Rotation) error {
   509  	madctl := uint8(0)
   510  	switch rotation % 4 {
   511  	case drivers.Rotation0:
   512  		d.rowOffset = 0
   513  		d.columnOffset = 0
   514  	case drivers.Rotation90:
   515  		madctl = MADCTL_MX | MADCTL_MV
   516  		d.rowOffset = 0
   517  		d.columnOffset = 0
   518  	case drivers.Rotation180:
   519  		madctl = MADCTL_MX | MADCTL_MY
   520  		d.rowOffset = d.rowOffsetCfg
   521  		d.columnOffset = d.columnOffsetCfg
   522  	case drivers.Rotation270:
   523  		madctl = MADCTL_MY | MADCTL_MV
   524  		d.rowOffset = d.columnOffsetCfg
   525  		d.columnOffset = d.rowOffsetCfg
   526  	}
   527  	if d.isBGR {
   528  		madctl |= MADCTL_BGR
   529  	}
   530  	return d.sendCommand(MADCTL, []byte{madctl})
   531  }
   532  
   533  // Size returns the current size of the display.
   534  func (d *DeviceOf[T]) Size() (w, h int16) {
   535  	if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
   536  		return d.width, d.height
   537  	}
   538  	return d.height, d.width
   539  }
   540  
   541  // EnableBacklight enables or disables the backlight
   542  func (d *DeviceOf[T]) EnableBacklight(enable bool) {
   543  	if enable {
   544  		d.blPin.High()
   545  	} else {
   546  		d.blPin.Low()
   547  	}
   548  }
   549  
   550  // Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot
   551  // less power. The LCD won't display an image anymore, but the memory contents
   552  // will be kept.
   553  func (d *DeviceOf[T]) Sleep(sleepEnabled bool) error {
   554  	if sleepEnabled {
   555  		d.startWrite()
   556  		d.sendCommand(SLPIN, nil)
   557  		d.endWrite()
   558  		time.Sleep(5 * time.Millisecond) // 5ms required by the datasheet
   559  	} else {
   560  		// Turn the LCD panel back on.
   561  		d.startWrite()
   562  		d.sendCommand(SLPOUT, nil)
   563  		d.endWrite()
   564  		// Note: the st7789 documentation says that it is needed to wait at
   565  		// least 120ms before going to sleep again. Sleeping here would not be
   566  		// practical (delays turning on the screen too much), so just hope the
   567  		// screen won't need to sleep again for at least 120ms.
   568  		// In practice, it's unlikely the user will set the display to sleep
   569  		// again within 120ms.
   570  	}
   571  	return nil
   572  }
   573  
   574  // InvertColors inverts the colors of the screen
   575  func (d *DeviceOf[T]) InvertColors(invert bool) {
   576  	d.startWrite()
   577  	if invert {
   578  		d.sendCommand(INVON, nil)
   579  	} else {
   580  		d.sendCommand(INVOFF, nil)
   581  	}
   582  	d.endWrite()
   583  }
   584  
   585  // IsBGR changes the color mode (RGB/BGR)
   586  func (d *DeviceOf[T]) IsBGR(bgr bool) {
   587  	d.isBGR = bgr
   588  }
   589  
   590  // SetScrollArea sets an area to scroll with fixed top and bottom parts of the display.
   591  func (d *DeviceOf[T]) SetScrollArea(topFixedArea, bottomFixedArea int16) {
   592  	if d.height < 320 {
   593  		// The screen doesn't use the full 320 pixel height.
   594  		// Enlarge the bottom fixed area to fill the 320 pixel height, so that
   595  		// bottomFixedArea starts from the visible bottom of the screen.
   596  		topFixedArea += d.rowOffset
   597  		bottomFixedArea += (320 - d.height) - d.rowOffset
   598  	}
   599  	if d.rotation == drivers.Rotation180 {
   600  		// The screen is rotated by 180°, so we have to switch the top and
   601  		// bottom fixed area.
   602  		topFixedArea, bottomFixedArea = bottomFixedArea, topFixedArea
   603  	}
   604  	verticalScrollArea := 320 - topFixedArea - bottomFixedArea
   605  	copy(d.buf[:6], []uint8{
   606  		uint8(topFixedArea >> 8), uint8(topFixedArea),
   607  		uint8(verticalScrollArea >> 8), uint8(verticalScrollArea),
   608  		uint8(bottomFixedArea >> 8), uint8(bottomFixedArea)})
   609  	d.startWrite()
   610  	d.sendCommand(VSCRDEF, d.buf[:6])
   611  	d.endWrite()
   612  }
   613  
   614  // SetScroll sets the vertical scroll address of the display.
   615  func (d *DeviceOf[T]) SetScroll(line int16) {
   616  	if d.rotation == drivers.Rotation180 {
   617  		// The screen is rotated by 180°, so we have to invert the scroll line
   618  		// (taking care of the RowOffset).
   619  		line = (319 - d.rowOffset) - line
   620  	}
   621  	d.buf[0] = uint8(line >> 8)
   622  	d.buf[1] = uint8(line)
   623  	d.startWrite()
   624  	d.sendCommand(VSCRSADD, d.buf[:2])
   625  	d.endWrite()
   626  }
   627  
   628  // StopScroll returns the display to its normal state.
   629  func (d *DeviceOf[T]) StopScroll() {
   630  	d.startWrite()
   631  	d.sendCommand(NORON, nil)
   632  	d.endWrite()
   633  }