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

     1  package ili9341
     2  
     3  import (
     4  	"errors"
     5  	"image/color"
     6  	"machine"
     7  	"time"
     8  
     9  	"tinygo.org/x/drivers"
    10  	"tinygo.org/x/drivers/pixel"
    11  )
    12  
    13  type Config struct {
    14  	Width            int16
    15  	Height           int16
    16  	Rotation         drivers.Rotation
    17  	DisplayInversion bool
    18  }
    19  
    20  type Device struct {
    21  	width    int16
    22  	height   int16
    23  	rotation drivers.Rotation
    24  	driver   driver
    25  
    26  	x0, x1 int16 // cached address window; prevents useless/expensive
    27  	y0, y1 int16 // syscalls to PASET and CASET
    28  
    29  	dc  machine.Pin
    30  	cs  machine.Pin
    31  	rst machine.Pin
    32  	rd  machine.Pin
    33  }
    34  
    35  // Image buffer type used in the ili9341.
    36  type Image = pixel.Image[pixel.RGB565BE]
    37  
    38  var cmdBuf [6]byte
    39  
    40  var initCmd = []byte{
    41  	0xEF, 3, 0x03, 0x80, 0x02,
    42  	0xCF, 3, 0x00, 0xC1, 0x30,
    43  	0xED, 4, 0x64, 0x03, 0x12, 0x81,
    44  	0xE8, 3, 0x85, 0x00, 0x78,
    45  	0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02,
    46  	0xF7, 1, 0x20,
    47  	0xEA, 2, 0x00, 0x00,
    48  	PWCTR1, 1, 0x23, // Power control VRH[5:0]
    49  	PWCTR2, 1, 0x10, // Power control SAP[2:0];BT[3:0]
    50  	VMCTR1, 2, 0x3e, 0x28, // VCM control
    51  	VMCTR2, 1, 0x86, // VCM control2
    52  	MADCTL, 1, 0x48, // Memory Access Control
    53  	VSCRSADD, 1, 0x00, // Vertical scroll zero
    54  	PIXFMT, 1, 0x55,
    55  	FRMCTR1, 2, 0x00, 0x18,
    56  	DFUNCTR, 3, 0x08, 0x82, 0x27, // Display Function Control
    57  	0xF2, 1, 0x00, // 3Gamma Function Disable
    58  	GAMMASET, 1, 0x01, // Gamma curve selected
    59  	GMCTRP1, 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma
    60  	0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00,
    61  	GMCTRN1, 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma
    62  	0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F,
    63  }
    64  
    65  // Configure prepares display for use
    66  func (d *Device) Configure(config Config) {
    67  
    68  	if config.Width == 0 {
    69  		config.Width = TFTWIDTH
    70  	}
    71  	if config.Height == 0 {
    72  		config.Height = TFTHEIGHT
    73  	}
    74  	d.width = config.Width
    75  	d.height = config.Height
    76  	d.rotation = config.Rotation
    77  
    78  	// try to pick an initial cache miss for one of the points
    79  	d.x0, d.x1 = -(d.width + 1), d.x0
    80  	d.y0, d.y1 = -(d.height + 1), d.y0
    81  
    82  	output := machine.PinConfig{machine.PinOutput}
    83  
    84  	// configure chip select if there is one
    85  	if d.cs != machine.NoPin {
    86  		d.cs.Configure(output)
    87  		d.cs.High() // deselect
    88  	}
    89  
    90  	d.dc.Configure(output)
    91  	d.dc.High() // data mode
    92  
    93  	// driver-specific configuration
    94  	d.driver.configure(&config)
    95  
    96  	if d.rd != machine.NoPin {
    97  		d.rd.Configure(output)
    98  		d.rd.High()
    99  	}
   100  
   101  	// reset the display
   102  	if d.rst != machine.NoPin {
   103  		// configure hardware reset if there is one
   104  		d.rst.Configure(output)
   105  		d.rst.High()
   106  		delay(100)
   107  		d.rst.Low()
   108  		delay(100)
   109  		d.rst.High()
   110  		delay(200)
   111  	} else {
   112  		// if no hardware reset, send software reset
   113  		d.sendCommand(SWRESET, nil)
   114  		delay(150)
   115  	}
   116  
   117  	if config.DisplayInversion {
   118  		initCmd = append(initCmd, INVON, 0x80)
   119  	}
   120  
   121  	initCmd = append(initCmd,
   122  		SLPOUT, 0x80, // Exit Sleep
   123  		DISPON, 0x80, // Display on
   124  		0x00, // End of list
   125  	)
   126  	for i, c := 0, len(initCmd); i < c; {
   127  		cmd := initCmd[i]
   128  		if cmd == 0x00 {
   129  			break
   130  		}
   131  		x := initCmd[i+1]
   132  		numArgs := int(x & 0x7F)
   133  		d.sendCommand(cmd, initCmd[i+2:i+2+numArgs])
   134  		if x&0x80 > 0 {
   135  			delay(150)
   136  		}
   137  		i += numArgs + 2
   138  	}
   139  
   140  	d.SetRotation(d.rotation)
   141  }
   142  
   143  // Size returns the current size of the display.
   144  func (d *Device) Size() (x, y int16) {
   145  	switch d.rotation {
   146  	case Rotation90, Rotation270, Rotation90Mirror, Rotation270Mirror:
   147  		return d.height, d.width
   148  	default: // Rotation0, Rotation180, etc
   149  		return d.width, d.height
   150  	}
   151  }
   152  
   153  // SetPixel modifies the internal buffer.
   154  func (d *Device) SetPixel(x, y int16, c color.RGBA) {
   155  	d.setWindow(x, y, 1, 1)
   156  	c565 := RGBATo565(c)
   157  	d.startWrite()
   158  	d.driver.write16(c565)
   159  	d.endWrite()
   160  }
   161  
   162  // Display sends the buffer (if any) to the screen.
   163  func (d *Device) Display() error {
   164  	return nil
   165  }
   166  
   167  // EnableTEOutput enables the TE ("tearing effect") line.
   168  // The TE line goes high when the screen is not currently being updated and can
   169  // be used to start drawing. When used correctly, it can avoid tearing entirely.
   170  func (d *Device) EnableTEOutput(on bool) {
   171  	if on {
   172  		cmdBuf[0] = 0
   173  		d.sendCommand(TEON, cmdBuf[:1]) // M=0 (V-blanking only, no H-blanking)
   174  	} else {
   175  		d.sendCommand(TEOFF, nil) // TEOFF
   176  	}
   177  }
   178  
   179  // DrawRGBBitmap copies an RGB bitmap to the internal buffer at given coordinates
   180  //
   181  // Deprecated: use DrawBitmap instead.
   182  func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
   183  	k, i := d.Size()
   184  	if x < 0 || y < 0 || w <= 0 || h <= 0 ||
   185  		x >= k || (x+w) > k || y >= i || (y+h) > i {
   186  		return errors.New("rectangle coordinates outside display area")
   187  	}
   188  	d.setWindow(x, y, w, h)
   189  	d.startWrite()
   190  	d.driver.write16sl(data)
   191  	d.endWrite()
   192  	return nil
   193  }
   194  
   195  // DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates
   196  //
   197  // Deprecated: use DrawBitmap instead.
   198  func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error {
   199  	k, i := d.Size()
   200  	if x < 0 || y < 0 || w <= 0 || h <= 0 ||
   201  		x >= k || (x+w) > k || y >= i || (y+h) > i {
   202  		return errors.New("rectangle coordinates outside display area")
   203  	}
   204  	d.setWindow(x, y, w, h)
   205  	d.startWrite()
   206  	d.driver.write8sl(data)
   207  	d.endWrite()
   208  	return nil
   209  }
   210  
   211  // DrawBitmap copies the bitmap to the internal buffer on the screen at the
   212  // given coordinates. It returns once the image data has been sent completely.
   213  func (d *Device) DrawBitmap(x, y int16, bitmap Image) error {
   214  	width, height := bitmap.Size()
   215  	return d.DrawRGBBitmap8(x, y, bitmap.RawBuffer(), int16(width), int16(height))
   216  }
   217  
   218  // FillRectangle fills a rectangle at given coordinates with a color
   219  func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
   220  	k, i := d.Size()
   221  	if x < 0 || y < 0 || width <= 0 || height <= 0 ||
   222  		x >= k || (x+width) > k || y >= i || (y+height) > i {
   223  		return errors.New("rectangle coordinates outside display area")
   224  	}
   225  	d.setWindow(x, y, width, height)
   226  	c565 := RGBATo565(c)
   227  	d.startWrite()
   228  	d.driver.write16n(c565, int(width)*int(height))
   229  	d.endWrite()
   230  	return nil
   231  }
   232  
   233  // DrawRectangle draws a rectangle at given coordinates with a color
   234  func (d *Device) DrawRectangle(x, y, w, h int16, c color.RGBA) error {
   235  	if err := d.DrawFastHLine(x, x+w-1, y, c); err != nil {
   236  		return err
   237  	}
   238  	if err := d.DrawFastHLine(x, x+w-1, y+h-1, c); err != nil {
   239  		return err
   240  	}
   241  	if err := d.DrawFastVLine(x, y, y+h-1, c); err != nil {
   242  		return err
   243  	}
   244  	if err := d.DrawFastVLine(x+w-1, y, y+h-1, c); err != nil {
   245  		return err
   246  	}
   247  	return nil
   248  }
   249  
   250  // DrawFastVLine draws a vertical line faster than using SetPixel
   251  func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) error {
   252  	if y0 > y1 {
   253  		y0, y1 = y1, y0
   254  	}
   255  	return d.FillRectangle(x, y0, 1, y1-y0+1, c)
   256  }
   257  
   258  // DrawFastHLine draws a horizontal line faster than using SetPixel
   259  func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) error {
   260  	if x0 > x1 {
   261  		x0, x1 = x1, x0
   262  	}
   263  	return d.FillRectangle(x0, y, x1-x0+1, 1, c)
   264  }
   265  
   266  // FillScreen fills the screen with a given color
   267  func (d *Device) FillScreen(c color.RGBA) {
   268  	if d.rotation == Rotation0 || d.rotation == Rotation180 {
   269  		d.FillRectangle(0, 0, d.width, d.height, c)
   270  	} else {
   271  		d.FillRectangle(0, 0, d.height, d.width, c)
   272  	}
   273  }
   274  
   275  // Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot
   276  // less power. The LCD won't display an image anymore, but the memory contents
   277  // will be kept.
   278  func (d *Device) Sleep(sleepEnabled bool) error {
   279  	if sleepEnabled {
   280  		// Shut down LCD panel.
   281  		d.sendCommand(SLPIN, nil)
   282  		time.Sleep(5 * time.Millisecond) // 5ms required by the datasheet
   283  	} else {
   284  		// Turn the LCD panel back on.
   285  		d.sendCommand(SLPOUT, nil)
   286  		// Note: the ili9341 documentation says that it is needed to wait at
   287  		// least 120ms before going to sleep again. Sleeping here would not be
   288  		// practical (delays turning on the screen too much), so just hope the
   289  		// screen won't need to sleep again for at least 120ms.
   290  		// In practice, it's unlikely the user will set the display to sleep
   291  		// again within 120ms.
   292  	}
   293  	return nil
   294  }
   295  
   296  // Rotation returns the current rotation of the device.
   297  func (d *Device) Rotation() drivers.Rotation {
   298  	return d.rotation
   299  }
   300  
   301  // GetRotation returns the current rotation of the device.
   302  //
   303  // Deprecated: use Rotation instead.
   304  func (d *Device) GetRotation() drivers.Rotation {
   305  	return d.rotation
   306  }
   307  
   308  // SetRotation changes the rotation of the device (clock-wise).
   309  func (d *Device) SetRotation(rotation drivers.Rotation) error {
   310  	madctl := uint8(0)
   311  	switch rotation % 8 {
   312  	case Rotation0:
   313  		madctl = MADCTL_MX | MADCTL_BGR
   314  	case Rotation90:
   315  		madctl = MADCTL_MV | MADCTL_BGR
   316  	case Rotation180:
   317  		madctl = MADCTL_MY | MADCTL_BGR | MADCTL_ML
   318  	case Rotation270:
   319  		madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML
   320  	case Rotation0Mirror:
   321  		madctl = MADCTL_BGR
   322  	case Rotation90Mirror:
   323  		madctl = MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML
   324  	case Rotation180Mirror:
   325  		madctl = MADCTL_MX | MADCTL_MY | MADCTL_BGR | MADCTL_ML
   326  	case Rotation270Mirror:
   327  		madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML
   328  	}
   329  	cmdBuf[0] = madctl
   330  	d.sendCommand(MADCTL, cmdBuf[:1])
   331  	d.rotation = rotation
   332  	return nil
   333  }
   334  
   335  // SetScrollArea sets an area to scroll with fixed top/bottom or left/right parts of the display
   336  // Rotation affects scroll direction
   337  func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) {
   338  	if d.height < 320 {
   339  		// The screen doesn't use the full 320 pixel height.
   340  		// Enlarge the bottom fixed area to fill the 320 pixel height, so that
   341  		// bottomFixedArea starts from the visible bottom of the screen.
   342  		bottomFixedArea += 320 - d.height
   343  	}
   344  	cmdBuf[0] = uint8(topFixedArea >> 8)
   345  	cmdBuf[1] = uint8(topFixedArea)
   346  	cmdBuf[2] = uint8((320 - topFixedArea - bottomFixedArea) >> 8)
   347  	cmdBuf[3] = uint8(320 - topFixedArea - bottomFixedArea)
   348  	cmdBuf[4] = uint8(bottomFixedArea >> 8)
   349  	cmdBuf[5] = uint8(bottomFixedArea)
   350  	d.sendCommand(VSCRDEF, cmdBuf[:6])
   351  }
   352  
   353  // SetScroll sets the vertical scroll address of the display.
   354  func (d *Device) SetScroll(line int16) {
   355  	cmdBuf[0] = uint8(line >> 8)
   356  	cmdBuf[1] = uint8(line)
   357  	d.sendCommand(VSCRSADD, cmdBuf[:2])
   358  }
   359  
   360  // StopScroll returns the display to its normal state
   361  func (d *Device) StopScroll() {
   362  	d.sendCommand(NORON, nil)
   363  }
   364  
   365  // setWindow prepares the screen to be modified at a given rectangle
   366  func (d *Device) setWindow(x, y, w, h int16) {
   367  	//x += d.columnOffset
   368  	//y += d.rowOffset
   369  	x1 := x + w - 1
   370  	if x != d.x0 || x1 != d.x1 {
   371  		cmdBuf[0] = uint8(x >> 8)
   372  		cmdBuf[1] = uint8(x)
   373  		cmdBuf[2] = uint8(x1 >> 8)
   374  		cmdBuf[3] = uint8(x1)
   375  		d.sendCommand(CASET, cmdBuf[:4])
   376  		d.x0, d.x1 = x, x1
   377  	}
   378  	y1 := y + h - 1
   379  	if y != d.y0 || y1 != d.y1 {
   380  		cmdBuf[0] = uint8(y >> 8)
   381  		cmdBuf[1] = uint8(y)
   382  		cmdBuf[2] = uint8(y1 >> 8)
   383  		cmdBuf[3] = uint8(y1)
   384  		d.sendCommand(PASET, cmdBuf[:4])
   385  		d.y0, d.y1 = y, y1
   386  	}
   387  	d.sendCommand(RAMWR, nil)
   388  }
   389  
   390  //go:inline
   391  func (d *Device) startWrite() {
   392  	if d.cs != machine.NoPin {
   393  		d.cs.Low()
   394  	}
   395  }
   396  
   397  //go:inline
   398  func (d *Device) endWrite() {
   399  	if d.cs != machine.NoPin {
   400  		d.cs.High()
   401  	}
   402  }
   403  
   404  func (d *Device) sendCommand(cmd byte, data []byte) {
   405  	d.startWrite()
   406  	d.dc.Low()
   407  	d.driver.write8(cmd)
   408  	d.dc.High()
   409  	if data != nil {
   410  		d.driver.write8sl(data)
   411  	}
   412  	d.endWrite()
   413  }
   414  
   415  type driver interface {
   416  	configure(config *Config)
   417  	write8(b byte)
   418  	write8n(b byte, n int)
   419  	write8sl(b []byte)
   420  	write16(data uint16)
   421  	write16n(data uint16, n int)
   422  	write16sl(data []uint16)
   423  }
   424  
   425  func delay(m int) {
   426  	t := time.Now().UnixNano() + int64(time.Duration(m*1000)*time.Microsecond)
   427  	for time.Now().UnixNano() < t {
   428  	}
   429  }
   430  
   431  // RGBATo565 converts a color.RGBA to uint16 used in the display
   432  func RGBATo565(c color.RGBA) uint16 {
   433  	r, g, b, _ := c.RGBA()
   434  	return uint16((r & 0xF800) +
   435  		((g & 0xFC00) >> 5) +
   436  		((b & 0xF800) >> 11))
   437  }