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

     1  // Package ssd1331 implements a driver for the SSD1331 TFT color displays.
     2  //
     3  // Datasheet: https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/
     4  package ssd1331 // import "tinygo.org/x/drivers/ssd1331"
     5  
     6  import (
     7  	"image/color"
     8  	"machine"
     9  
    10  	"errors"
    11  	"time"
    12  
    13  	"tinygo.org/x/drivers"
    14  )
    15  
    16  type Model uint8
    17  type Rotation uint8
    18  
    19  // Device wraps an SPI connection.
    20  type Device struct {
    21  	bus         drivers.SPI
    22  	dcPin       machine.Pin
    23  	resetPin    machine.Pin
    24  	csPin       machine.Pin
    25  	width       int16
    26  	height      int16
    27  	batchLength int16
    28  	isBGR       bool
    29  	batchData   []uint8
    30  }
    31  
    32  // Config is the configuration for the display
    33  type Config struct {
    34  	Width  int16
    35  	Height int16
    36  }
    37  
    38  // New creates a new SSD1331 connection. The SPI wire must already be configured.
    39  func New(bus drivers.SPI, resetPin, dcPin, csPin machine.Pin) Device {
    40  	dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    41  	resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    42  	csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    43  	return Device{
    44  		bus:      bus,
    45  		dcPin:    dcPin,
    46  		resetPin: resetPin,
    47  		csPin:    csPin,
    48  	}
    49  }
    50  
    51  // Configure initializes the display with default configuration
    52  func (d *Device) Configure(cfg Config) {
    53  	if cfg.Width != 0 {
    54  		d.width = cfg.Width
    55  	} else {
    56  		d.width = 96
    57  	}
    58  	if cfg.Height != 0 {
    59  		d.height = cfg.Height
    60  	} else {
    61  		d.height = 64
    62  	}
    63  
    64  	d.batchLength = d.width
    65  	if d.height > d.width {
    66  		d.batchLength = d.height
    67  	}
    68  	d.batchLength += d.batchLength & 1
    69  	d.batchData = make([]uint8, d.batchLength*2)
    70  
    71  	// reset the device
    72  	d.resetPin.High()
    73  	time.Sleep(100 * time.Millisecond)
    74  	d.resetPin.Low()
    75  	time.Sleep(100 * time.Millisecond)
    76  	d.resetPin.High()
    77  	time.Sleep(200 * time.Millisecond)
    78  
    79  	// Initialization
    80  	d.Command(DISPLAYOFF)
    81  	d.Command(SETREMAP)
    82  	d.Command(0x72) // RGB
    83  	//d.Command(0x76) // BGR
    84  	d.Command(STARTLINE)
    85  	d.Command(0x0)
    86  	d.Command(DISPLAYOFFSET)
    87  	d.Command(0x0)
    88  	d.Command(NORMALDISPLAY)
    89  	d.Command(SETMULTIPLEX)
    90  	d.Command(0x3F)
    91  	d.Command(SETMASTER)
    92  	d.Command(0x8E)
    93  	d.Command(POWERMODE)
    94  	d.Command(0x0B)
    95  	d.Command(PRECHARGE)
    96  	d.Command(0x31)
    97  	d.Command(CLOCKDIV)
    98  	d.Command(0xF0)
    99  	d.Command(PRECHARGEA)
   100  	d.Command(0x64)
   101  	d.Command(PRECHARGEB)
   102  	d.Command(0x78)
   103  	d.Command(PRECHARGEC)
   104  	d.Command(0x64)
   105  	d.Command(PRECHARGELEVEL)
   106  	d.Command(0x3A)
   107  	d.Command(VCOMH)
   108  	d.Command(0x3E)
   109  	d.Command(MASTERCURRENT)
   110  	d.Command(0x06)
   111  	d.Command(CONTRASTA)
   112  	d.Command(0x91)
   113  	d.Command(CONTRASTB)
   114  	d.Command(0x50)
   115  	d.Command(CONTRASTC)
   116  	d.Command(0x7D)
   117  	d.Command(DISPLAYON)
   118  }
   119  
   120  // Display does nothing, there's no buffer as it might be too big for some boards
   121  func (d *Device) Display() error {
   122  	return nil
   123  }
   124  
   125  // SetPixel sets a pixel in the screen
   126  func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
   127  	if x < 0 || y < 0 || x >= d.width || y >= d.height {
   128  		return
   129  	}
   130  	d.FillRectangle(x, y, 1, 1, c)
   131  }
   132  
   133  // setWindow prepares the screen to be modified at a given rectangle
   134  func (d *Device) setWindow(x, y, w, h int16) {
   135  	/*d.Tx([]uint8{SETCOLUMN}, true)
   136  	d.Tx([]uint8{uint8(x), uint8(x + w - 1)}, false)
   137  	d.Tx([]uint8{SETROW}, true)
   138  	d.Tx([]uint8{uint8(y), uint8(y + h - 1)}, false)*/
   139  	d.Command(SETCOLUMN)
   140  	d.Command(uint8(x))
   141  	d.Command(uint8(x + w - 1))
   142  	d.Command(SETROW)
   143  	d.Command(uint8(y))
   144  	d.Command(uint8(y + h - 1))
   145  }
   146  
   147  // FillRectangle fills a rectangle at a given coordinates with a color
   148  func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
   149  	if x < 0 || y < 0 || width <= 0 || height <= 0 ||
   150  		x >= d.width || (x+width) > d.width || y >= d.height || (y+height) > d.height {
   151  		return errors.New("rectangle coordinates outside display area")
   152  	}
   153  	d.setWindow(x, y, width, height)
   154  	c565 := RGBATo565(c)
   155  	c1 := uint8(c565 >> 8)
   156  	c2 := uint8(c565)
   157  
   158  	var i int16
   159  	for i = 0; i < d.batchLength; i++ {
   160  		d.batchData[i*2] = c1
   161  		d.batchData[i*2+1] = c2
   162  	}
   163  	i = width * height
   164  	for i > 0 {
   165  		if i >= d.batchLength {
   166  			d.Tx(d.batchData, false)
   167  		} else {
   168  			d.Tx(d.batchData[:i*2], false)
   169  		}
   170  		i -= d.batchLength
   171  	}
   172  	return nil
   173  }
   174  
   175  // FillRectangle fills a rectangle at a given coordinates with a buffer
   176  func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
   177  	if x < 0 || y < 0 || width <= 0 || height <= 0 ||
   178  		x >= d.width || (x+width) > d.width || y >= d.height || (y+height) > d.height {
   179  		return errors.New("rectangle coordinates outside display area")
   180  	}
   181  	k := width * height
   182  	l := int16(len(buffer))
   183  	if k != l {
   184  		return errors.New("buffer length does not match with rectangle size")
   185  	}
   186  
   187  	d.setWindow(x, y, width, height)
   188  
   189  	offset := int16(0)
   190  	for k > 0 {
   191  		for i := int16(0); i < d.batchLength; i++ {
   192  			if offset+i < l {
   193  				c565 := RGBATo565(buffer[offset+i])
   194  				c1 := uint8(c565 >> 8)
   195  				c2 := uint8(c565)
   196  				d.batchData[i*2] = c1
   197  				d.batchData[i*2+1] = c2
   198  			}
   199  		}
   200  		if k >= d.batchLength {
   201  			d.Tx(d.batchData, false)
   202  		} else {
   203  			d.Tx(d.batchData[:k*2], false)
   204  		}
   205  		k -= d.batchLength
   206  		offset += d.batchLength
   207  	}
   208  	return nil
   209  }
   210  
   211  // DrawFastVLine draws a vertical line faster than using SetPixel
   212  func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
   213  	if y0 > y1 {
   214  		y0, y1 = y1, y0
   215  	}
   216  	d.FillRectangle(x, y0, 1, y1-y0+1, c)
   217  }
   218  
   219  // DrawFastHLine draws a horizontal line faster than using SetPixel
   220  func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
   221  	if x0 > x1 {
   222  		x0, x1 = x1, x0
   223  	}
   224  	d.FillRectangle(x0, y, x1-x0+1, 1, c)
   225  }
   226  
   227  // FillScreen fills the screen with a given color
   228  func (d *Device) FillScreen(c color.RGBA) {
   229  	d.FillRectangle(0, 0, d.width, d.height, c)
   230  }
   231  
   232  // SetContrast sets the three contrast values (A, B & C)
   233  func (d *Device) SetContrast(contrastA, contrastB, contrastC uint8) {
   234  	d.Command(CONTRASTA)
   235  	d.Command(contrastA)
   236  	d.Command(CONTRASTB)
   237  	d.Command(contrastB)
   238  	d.Command(CONTRASTC)
   239  	d.Command(contrastC)
   240  }
   241  
   242  // Command sends a command to the display
   243  func (d *Device) Command(command uint8) {
   244  	d.Tx([]byte{command}, true)
   245  }
   246  
   247  // Command sends a data to the display
   248  func (d *Device) Data(data uint8) {
   249  	d.Tx([]byte{data}, false)
   250  }
   251  
   252  // Tx sends data to the display
   253  func (d *Device) Tx(data []byte, isCommand bool) {
   254  	d.dcPin.Set(!isCommand)
   255  	d.bus.Tx(data, nil)
   256  }
   257  
   258  // Size returns the current size of the display.
   259  func (d *Device) Size() (w, h int16) {
   260  	return d.width, d.height
   261  }
   262  
   263  // IsBGR changes the color mode (RGB/BGR)
   264  func (d *Device) IsBGR(bgr bool) {
   265  	d.isBGR = bgr
   266  }
   267  
   268  // RGBATo565 converts a color.RGBA to uint16 used in the display
   269  func RGBATo565(c color.RGBA) uint16 {
   270  	r, g, b, _ := c.RGBA()
   271  	return uint16((r & 0xF800) +
   272  		((g & 0xFC00) >> 5) +
   273  		((b & 0xF800) >> 11))
   274  }