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

     1  // Package st7735 implements a driver for the ST7735 TFT displays, it comes in various screen sizes.
     2  //
     3  // Datasheet: https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/
     4  package st7735 // import "tinygo.org/x/drivers/st7735"
     5  
     6  import (
     7  	"image/color"
     8  	"machine"
     9  	"time"
    10  
    11  	"errors"
    12  
    13  	"tinygo.org/x/drivers"
    14  	"tinygo.org/x/drivers/pixel"
    15  )
    16  
    17  type Model uint8
    18  
    19  // Rotation controls the rotation used by the display.
    20  //
    21  // Deprecated: use drivers.Rotation instead.
    22  type Rotation = drivers.Rotation
    23  
    24  // Pixel formats supported by the st7735 driver.
    25  type Color interface {
    26  	pixel.RGB444BE | pixel.RGB565BE
    27  
    28  	pixel.BaseColor
    29  }
    30  
    31  var (
    32  	errOutOfBounds = errors.New("rectangle coordinates outside display area")
    33  )
    34  
    35  // Device wraps an SPI connection.
    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 {
    41  	bus          drivers.SPI
    42  	dcPin        machine.Pin
    43  	resetPin     machine.Pin
    44  	csPin        machine.Pin
    45  	blPin        machine.Pin
    46  	width        int16
    47  	height       int16
    48  	columnOffset int16
    49  	rowOffset    int16
    50  	rotation     drivers.Rotation
    51  	batchLength  int16
    52  	model        Model
    53  	isBGR        bool
    54  	batchData    pixel.Image[T] // "image" with width, height of (batchLength, 1)
    55  }
    56  
    57  // Config is the configuration for the display
    58  type Config struct {
    59  	Width        int16
    60  	Height       int16
    61  	Rotation     drivers.Rotation
    62  	Model        Model
    63  	RowOffset    int16
    64  	ColumnOffset int16
    65  }
    66  
    67  // New creates a new ST7735 connection. The SPI wire must already be configured.
    68  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] {
    75  	dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    76  	resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    77  	csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    78  	blPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    79  	return DeviceOf[T]{
    80  		bus:      bus,
    81  		dcPin:    dcPin,
    82  		resetPin: resetPin,
    83  		csPin:    csPin,
    84  		blPin:    blPin,
    85  	}
    86  }
    87  
    88  // Configure initializes the display with default configuration
    89  func (d *DeviceOf[T]) Configure(cfg Config) {
    90  	d.model = cfg.Model
    91  	if cfg.Width != 0 {
    92  		d.width = cfg.Width
    93  	} else {
    94  		if d.model == MINI80x160 {
    95  			d.width = 80
    96  		} else {
    97  			d.width = 128
    98  		}
    99  	}
   100  	if cfg.Height != 0 {
   101  		d.height = cfg.Height
   102  	} else {
   103  		d.height = 160
   104  	}
   105  	d.rotation = cfg.Rotation
   106  	d.rowOffset = cfg.RowOffset
   107  	d.columnOffset = cfg.ColumnOffset
   108  
   109  	d.batchLength = d.width
   110  	if d.height > d.width {
   111  		d.batchLength = d.height
   112  	}
   113  	d.batchLength += d.batchLength & 1
   114  	d.batchData = pixel.NewImage[T](int(d.batchLength), 1)
   115  
   116  	// reset the device
   117  	d.resetPin.High()
   118  	time.Sleep(5 * time.Millisecond)
   119  	d.resetPin.Low()
   120  	time.Sleep(20 * time.Millisecond)
   121  	d.resetPin.High()
   122  	time.Sleep(150 * time.Millisecond)
   123  
   124  	// Common initialization
   125  	d.Command(SWRESET)
   126  	time.Sleep(150 * time.Millisecond)
   127  	d.Command(SLPOUT)
   128  	time.Sleep(500 * time.Millisecond)
   129  	d.Command(FRMCTR1)
   130  	d.Data(0x01)
   131  	d.Data(0x2C)
   132  	d.Data(0x2D)
   133  	d.Command(FRMCTR2)
   134  	d.Data(0x01)
   135  	d.Data(0x2C)
   136  	d.Data(0x2D)
   137  	d.Command(FRMCTR3)
   138  	d.Data(0x01)
   139  	d.Data(0x2C)
   140  	d.Data(0x2D)
   141  	d.Data(0x01)
   142  	d.Data(0x2C)
   143  	d.Data(0x2D)
   144  	d.Command(INVCTR)
   145  	d.Data(0x07)
   146  	d.Command(PWCTR1)
   147  	d.Data(0xA2)
   148  	d.Data(0x02)
   149  	d.Data(0x84)
   150  	d.Command(PWCTR2)
   151  	d.Data(0xC5)
   152  	d.Command(PWCTR3)
   153  	d.Data(0x0A)
   154  	d.Data(0x00)
   155  	d.Command(PWCTR4)
   156  	d.Data(0x8A)
   157  	d.Data(0x2A)
   158  	d.Command(PWCTR5)
   159  	d.Data(0x8A)
   160  	d.Data(0xEE)
   161  	d.Command(VMCTR1)
   162  	d.Data(0x0E)
   163  
   164  	// Set the color format depending on the generic type.
   165  	d.Command(COLMOD)
   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  	}
   173  
   174  	if d.model == GREENTAB {
   175  		d.InvertColors(false)
   176  	} else if d.model == MINI80x160 {
   177  		d.isBGR = true
   178  		d.InvertColors(true)
   179  	}
   180  
   181  	// common color adjustment
   182  	d.Command(GMCTRP1)
   183  	d.Data(0x02)
   184  	d.Data(0x1C)
   185  	d.Data(0x07)
   186  	d.Data(0x12)
   187  	d.Data(0x37)
   188  	d.Data(0x32)
   189  	d.Data(0x29)
   190  	d.Data(0x2D)
   191  	d.Data(0x29)
   192  	d.Data(0x25)
   193  	d.Data(0x2B)
   194  	d.Data(0x39)
   195  	d.Data(0x00)
   196  	d.Data(0x01)
   197  	d.Data(0x03)
   198  	d.Data(0x10)
   199  	d.Command(GMCTRN1)
   200  	d.Data(0x03)
   201  	d.Data(0x1D)
   202  	d.Data(0x07)
   203  	d.Data(0x06)
   204  	d.Data(0x2E)
   205  	d.Data(0x2C)
   206  	d.Data(0x29)
   207  	d.Data(0x2D)
   208  	d.Data(0x2E)
   209  	d.Data(0x2E)
   210  	d.Data(0x37)
   211  	d.Data(0x3F)
   212  	d.Data(0x00)
   213  	d.Data(0x00)
   214  	d.Data(0x02)
   215  	d.Data(0x10)
   216  
   217  	d.Command(NORON)
   218  	time.Sleep(10 * time.Millisecond)
   219  	d.Command(DISPON)
   220  	time.Sleep(500 * time.Millisecond)
   221  
   222  	if cfg.Model == MINI80x160 {
   223  		d.Command(MADCTL)
   224  		d.Data(0xC0)
   225  	}
   226  
   227  	d.SetRotation(d.rotation)
   228  
   229  	d.blPin.High()
   230  }
   231  
   232  // Display does nothing, there's no buffer as it might be too big for some boards
   233  func (d *DeviceOf[T]) Display() error {
   234  	return nil
   235  }
   236  
   237  // SetPixel sets a pixel in the screen
   238  func (d *DeviceOf[T]) SetPixel(x int16, y int16, c color.RGBA) {
   239  	w, h := d.Size()
   240  	if x < 0 || y < 0 || x >= w || y >= h {
   241  		return
   242  	}
   243  	d.FillRectangle(x, y, 1, 1, c)
   244  }
   245  
   246  // setWindow prepares the screen to be modified at a given rectangle
   247  func (d *DeviceOf[T]) setWindow(x, y, w, h int16) {
   248  	if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
   249  		x += d.columnOffset
   250  		y += d.rowOffset
   251  	} else {
   252  		x += d.rowOffset
   253  		y += d.columnOffset
   254  	}
   255  	d.Tx([]uint8{CASET}, true)
   256  	d.Tx([]uint8{uint8(x >> 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)}, false)
   257  	d.Tx([]uint8{RASET}, true)
   258  	d.Tx([]uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)}, false)
   259  	d.Command(RAMWR)
   260  }
   261  
   262  // SetScrollWindow sets an area to scroll with fixed top and bottom parts of the display
   263  func (d *DeviceOf[T]) SetScrollArea(topFixedArea, bottomFixedArea int16) {
   264  	// TODO: this code is broken, see the st7789 and ili9341 implementations for
   265  	// how to do this correctly.
   266  	d.Command(VSCRDEF)
   267  	d.Tx([]uint8{
   268  		uint8(topFixedArea >> 8), uint8(topFixedArea),
   269  		uint8(d.height - topFixedArea - bottomFixedArea>>8), uint8(d.height - topFixedArea - bottomFixedArea),
   270  		uint8(bottomFixedArea >> 8), uint8(bottomFixedArea)},
   271  		false)
   272  }
   273  
   274  // SetScroll sets the vertical scroll address of the display.
   275  func (d *DeviceOf[T]) SetScroll(line int16) {
   276  	d.Command(VSCRSADD)
   277  	d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false)
   278  }
   279  
   280  // SpotScroll returns the display to its normal state
   281  func (d *DeviceOf[T]) StopScroll() {
   282  	d.Command(NORON)
   283  }
   284  
   285  // FillRectangle fills a rectangle at a given coordinates with a color
   286  func (d *DeviceOf[T]) FillRectangle(x, y, width, height int16, c color.RGBA) error {
   287  	k, i := d.Size()
   288  	if x < 0 || y < 0 || width <= 0 || height <= 0 ||
   289  		x >= k || (x+width) > k || y >= i || (y+height) > i {
   290  		return errors.New("rectangle coordinates outside display area")
   291  	}
   292  	d.setWindow(x, y, width, height)
   293  
   294  	d.batchData.FillSolidColor(pixel.NewColor[T](c.R, c.G, c.B))
   295  	i = width * height
   296  	for i > 0 {
   297  		if i >= d.batchLength {
   298  			d.Tx(d.batchData.RawBuffer(), false)
   299  		} else {
   300  			d.Tx(d.batchData.Rescale(int(i), 1).RawBuffer(), false)
   301  		}
   302  		i -= d.batchLength
   303  	}
   304  	return nil
   305  }
   306  
   307  // DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
   308  //
   309  // Deprecated: use DrawBitmap instead.
   310  func (d *DeviceOf[T]) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
   311  	k, i := d.Size()
   312  	if x < 0 || y < 0 || w <= 0 || h <= 0 ||
   313  		x >= k || (x+w) > k || y >= i || (y+h) > i {
   314  		return errOutOfBounds
   315  	}
   316  	d.setWindow(x, y, w, h)
   317  	d.Tx(data, false)
   318  	return nil
   319  }
   320  
   321  // DrawBitmap copies the bitmap to the internal buffer on the screen at the
   322  // given coordinates. It returns once the image data has been sent completely.
   323  func (d *DeviceOf[T]) DrawBitmap(x, y int16, bitmap pixel.Image[T]) error {
   324  	width, height := bitmap.Size()
   325  	return d.DrawRGBBitmap8(x, y, bitmap.RawBuffer(), int16(width), int16(height))
   326  }
   327  
   328  // FillRectangle fills a rectangle at a given coordinates with a buffer
   329  func (d *DeviceOf[T]) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
   330  	k, l := d.Size()
   331  	if x < 0 || y < 0 || width <= 0 || height <= 0 ||
   332  		x >= k || (x+width) > k || y >= l || (y+height) > l {
   333  		return errors.New("rectangle coordinates outside display area")
   334  	}
   335  	k = width * height
   336  	l = int16(len(buffer))
   337  	if k != l {
   338  		return errors.New("buffer length does not match with rectangle size")
   339  	}
   340  
   341  	d.setWindow(x, y, width, height)
   342  
   343  	offset := int16(0)
   344  	for k > 0 {
   345  		for i := int16(0); i < d.batchLength; i++ {
   346  			if offset+i < l {
   347  				c := buffer[offset+i]
   348  				d.batchData.Set(int(i), 0, pixel.NewColor[T](c.R, c.G, c.B))
   349  			}
   350  		}
   351  		if k >= d.batchLength {
   352  			d.Tx(d.batchData.RawBuffer(), false)
   353  		} else {
   354  			d.Tx(d.batchData.Rescale(int(k), 1).RawBuffer(), false)
   355  		}
   356  		k -= d.batchLength
   357  		offset += d.batchLength
   358  	}
   359  	return nil
   360  }
   361  
   362  // DrawFastVLine draws a vertical line faster than using SetPixel
   363  func (d *DeviceOf[T]) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
   364  	if y0 > y1 {
   365  		y0, y1 = y1, y0
   366  	}
   367  	d.FillRectangle(x, y0, 1, y1-y0+1, c)
   368  }
   369  
   370  // DrawFastHLine draws a horizontal line faster than using SetPixel
   371  func (d *DeviceOf[T]) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
   372  	if x0 > x1 {
   373  		x0, x1 = x1, x0
   374  	}
   375  	d.FillRectangle(x0, y, x1-x0+1, 1, c)
   376  }
   377  
   378  // FillScreen fills the screen with a given color
   379  func (d *DeviceOf[T]) FillScreen(c color.RGBA) {
   380  	if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
   381  		d.FillRectangle(0, 0, d.width, d.height, c)
   382  	} else {
   383  		d.FillRectangle(0, 0, d.height, d.width, c)
   384  	}
   385  }
   386  
   387  // Rotation returns the currently configured rotation.
   388  func (d *DeviceOf[T]) Rotation() drivers.Rotation {
   389  	return d.rotation
   390  }
   391  
   392  // SetRotation changes the rotation of the device (clock-wise)
   393  func (d *DeviceOf[T]) SetRotation(rotation drivers.Rotation) error {
   394  	d.rotation = rotation
   395  	madctl := uint8(0)
   396  	switch rotation % 4 {
   397  	case drivers.Rotation0:
   398  		madctl = MADCTL_MX | MADCTL_MY
   399  	case drivers.Rotation90:
   400  		madctl = MADCTL_MY | MADCTL_MV
   401  	case drivers.Rotation180:
   402  		// nothing to do
   403  	case drivers.Rotation270:
   404  		madctl = MADCTL_MX | MADCTL_MV
   405  	}
   406  	if d.isBGR {
   407  		madctl |= MADCTL_BGR
   408  	}
   409  	d.Command(MADCTL)
   410  	d.Data(madctl)
   411  	return nil
   412  }
   413  
   414  // Command sends a command to the display
   415  func (d *DeviceOf[T]) Command(command uint8) {
   416  	d.Tx([]byte{command}, true)
   417  }
   418  
   419  // Command sends a data to the display
   420  func (d *DeviceOf[T]) Data(data uint8) {
   421  	d.Tx([]byte{data}, false)
   422  }
   423  
   424  // Tx sends data to the display
   425  func (d *DeviceOf[T]) Tx(data []byte, isCommand bool) {
   426  	d.dcPin.Set(!isCommand)
   427  	d.bus.Tx(data, nil)
   428  }
   429  
   430  // Size returns the current size of the display.
   431  func (d *DeviceOf[T]) Size() (w, h int16) {
   432  	if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 {
   433  		return d.width, d.height
   434  	}
   435  	return d.height, d.width
   436  }
   437  
   438  // EnableBacklight enables or disables the backlight
   439  func (d *DeviceOf[T]) EnableBacklight(enable bool) {
   440  	if enable {
   441  		d.blPin.High()
   442  	} else {
   443  		d.blPin.Low()
   444  	}
   445  }
   446  
   447  // Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot
   448  // less power. The LCD won't display an image anymore, but the memory contents
   449  // will be kept.
   450  func (d *DeviceOf[T]) Sleep(sleepEnabled bool) error {
   451  	if sleepEnabled {
   452  		// Shut down LCD panel.
   453  		d.Command(SLPIN)
   454  		time.Sleep(5 * time.Millisecond) // 5ms required by the datasheet
   455  	} else {
   456  		// Turn the LCD panel back on.
   457  		d.Command(SLPOUT)
   458  		// The st7735 datasheet says it is necessary to wait 120ms before
   459  		// sending another command.
   460  		time.Sleep(120 * time.Millisecond)
   461  	}
   462  	return nil
   463  }
   464  
   465  // InverColors inverts the colors of the screen
   466  func (d *DeviceOf[T]) InvertColors(invert bool) {
   467  	if invert {
   468  		d.Command(INVON)
   469  	} else {
   470  		d.Command(INVOFF)
   471  	}
   472  }
   473  
   474  // IsBGR changes the color mode (RGB/BGR)
   475  func (d *DeviceOf[T]) IsBGR(bgr bool) {
   476  	d.isBGR = bgr
   477  }