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

     1  // Package ssd1351 implements a driver for the SSD1351 OLED color displays.
     2  //
     3  // Datasheet: https://download.mikroe.com/documents/datasheets/ssd1351-revision-1.3.pdf
     4  package ssd1351 // import "tinygo.org/x/drivers/ssd1351"
     5  
     6  import (
     7  	"errors"
     8  	"image/color"
     9  	"machine"
    10  	"time"
    11  
    12  	"tinygo.org/x/drivers"
    13  )
    14  
    15  var (
    16  	errDrawingOutOfBounds = errors.New("rectangle coordinates outside display area")
    17  	errBufferSizeMismatch = errors.New("buffer length does not match with rectangle size")
    18  )
    19  
    20  // Device wraps an SPI connection.
    21  type Device struct {
    22  	bus          drivers.SPI
    23  	dcPin        machine.Pin
    24  	resetPin     machine.Pin
    25  	csPin        machine.Pin
    26  	enPin        machine.Pin
    27  	rwPin        machine.Pin
    28  	width        int16
    29  	height       int16
    30  	rowOffset    int16
    31  	columnOffset int16
    32  	bufferLength int16
    33  }
    34  
    35  // Config is the configuration for the display
    36  type Config struct {
    37  	Width        int16
    38  	Height       int16
    39  	RowOffset    int16
    40  	ColumnOffset int16
    41  }
    42  
    43  // New creates a new SSD1351 connection. The SPI wire must already be configured.
    44  func New(bus drivers.SPI, resetPin, dcPin, csPin, enPin, rwPin machine.Pin) Device {
    45  	return Device{
    46  		bus:      bus,
    47  		dcPin:    dcPin,
    48  		resetPin: resetPin,
    49  		csPin:    csPin,
    50  		enPin:    enPin,
    51  		rwPin:    rwPin,
    52  	}
    53  }
    54  
    55  // Configure initializes the display with default configuration
    56  func (d *Device) Configure(cfg Config) {
    57  	if cfg.Width == 0 {
    58  		cfg.Width = 128
    59  	}
    60  
    61  	if cfg.Height == 0 {
    62  		cfg.Height = 128
    63  	}
    64  
    65  	d.width = cfg.Width
    66  	d.height = cfg.Height
    67  	d.rowOffset = cfg.RowOffset
    68  	d.columnOffset = cfg.ColumnOffset
    69  
    70  	d.bufferLength = d.width
    71  	if d.height > d.width {
    72  		d.bufferLength = d.height
    73  	}
    74  
    75  	// configure GPIO pins
    76  	d.dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    77  	d.resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    78  	d.csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    79  	d.enPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    80  	d.rwPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
    81  
    82  	// reset the device
    83  	d.resetPin.High()
    84  	time.Sleep(100 * time.Millisecond)
    85  	d.resetPin.Low()
    86  	time.Sleep(100 * time.Millisecond)
    87  	d.resetPin.High()
    88  	time.Sleep(200 * time.Millisecond)
    89  
    90  	d.rwPin.Low()
    91  	d.dcPin.Low()
    92  	d.enPin.High()
    93  
    94  	// Initialization
    95  	d.Command(SET_COMMAND_LOCK)
    96  	d.Data(0x12)
    97  	d.Command(SET_COMMAND_LOCK)
    98  	d.Data(0xB1)
    99  	d.Command(SLEEP_MODE_DISPLAY_OFF)
   100  	d.Command(SET_FRONT_CLOCK_DIV)
   101  	d.Data(0xF1)
   102  	d.Command(SET_MUX_RATIO)
   103  	d.Data(0x7F)
   104  	d.Command(SET_REMAP_COLORDEPTH)
   105  	d.Data(0x62)
   106  	d.Command(SET_COLUMN_ADDRESS)
   107  	d.Data(0x00)
   108  	d.Data(0x7F)
   109  	d.Command(SET_ROW_ADDRESS)
   110  	d.Data(0x00)
   111  	d.Data(0x7F)
   112  	d.Command(SET_DISPLAY_START_LINE)
   113  	d.Data(0x00)
   114  	d.Command(SET_DISPLAY_OFFSET)
   115  	d.Data(0x00)
   116  	d.Command(SET_GPIO)
   117  	d.Data(0x00)
   118  	d.Command(FUNCTION_SELECTION)
   119  	d.Data(0x01)
   120  	d.Command(SET_PHASE_PERIOD)
   121  	d.Data(0x32)
   122  	d.Command(SET_SEGMENT_LOW_VOLTAGE)
   123  	d.Data(0xA0)
   124  	d.Data(0xB5)
   125  	d.Data(0x55)
   126  	d.Command(SET_PRECHARGE_VOLTAGE)
   127  	d.Data(0x17)
   128  	d.Command(SET_VCOMH_VOLTAGE)
   129  	d.Data(0x05)
   130  	d.Command(SET_CONTRAST)
   131  	d.Data(0xC8)
   132  	d.Data(0x80)
   133  	d.Data(0xC8)
   134  	d.Command(MASTER_CONTRAST)
   135  	d.Data(0x0F)
   136  	d.Command(SET_SECOND_PRECHARGE_PERIOD)
   137  	d.Data(0x01)
   138  	d.Command(SET_DISPLAY_MODE_RESET)
   139  	d.Command(SLEEP_MODE_DISPLAY_ON)
   140  
   141  }
   142  
   143  // Display does nothing, there's no buffer as it might be too big for some boards
   144  func (d *Device) Display() error {
   145  	return nil
   146  }
   147  
   148  // SetPixel sets a pixel in the buffer
   149  func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
   150  	if x < 0 || y < 0 || x >= d.width || y >= d.height {
   151  		return
   152  	}
   153  	d.FillRectangle(x, y, 1, 1, c)
   154  }
   155  
   156  // setWindow prepares the screen memory to be modified at given coordinates
   157  func (d *Device) setWindow(x, y, w, h int16) {
   158  	x += d.columnOffset
   159  	y += d.rowOffset
   160  	d.Command(SET_COLUMN_ADDRESS)
   161  	d.Tx([]byte{uint8(x), uint8(x + w - 1)}, false)
   162  	d.Command(SET_ROW_ADDRESS)
   163  	d.Tx([]byte{uint8(y), uint8(y + h - 1)}, false)
   164  	d.Command(WRITE_RAM)
   165  }
   166  
   167  // FillRectangle fills a rectangle at given coordinates with a color
   168  func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error {
   169  	if x < 0 || y < 0 || width <= 0 || height <= 0 ||
   170  		x >= d.width || (x+width) > d.width || y >= d.height || (y+height) > d.height {
   171  		return errDrawingOutOfBounds
   172  	}
   173  	d.setWindow(x, y, width, height)
   174  	c565 := RGBATo565(c)
   175  	c1 := uint8(c565 >> 8)
   176  	c2 := uint8(c565)
   177  
   178  	dim := int16(width * height)
   179  	if d.bufferLength < dim {
   180  		dim = d.bufferLength
   181  	}
   182  	data := make([]uint8, dim*2)
   183  
   184  	for i := int16(0); i < dim; i++ {
   185  		data[i*2] = c1
   186  		data[i*2+1] = c2
   187  	}
   188  	dim = int16(width * height)
   189  	for dim > 0 {
   190  		if dim >= d.bufferLength {
   191  			d.Tx(data, false)
   192  		} else {
   193  			d.Tx(data[:dim*2], false)
   194  		}
   195  		dim -= d.bufferLength
   196  	}
   197  	return nil
   198  }
   199  
   200  // FillRectangleWithBuffer fills a rectangle at given coordinates with a buffer
   201  func (d *Device) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error {
   202  	if x < 0 || y < 0 || width <= 0 || height <= 0 ||
   203  		x >= d.width || (x+width) > d.width || y >= d.height || (y+height) > d.height {
   204  		return errDrawingOutOfBounds
   205  	}
   206  	dim := int16(width * height)
   207  	l := int16(len(buffer))
   208  	if dim != l {
   209  		return errBufferSizeMismatch
   210  	}
   211  
   212  	d.setWindow(x, y, width, height)
   213  
   214  	bl := dim
   215  	if d.bufferLength < dim {
   216  		bl = d.bufferLength
   217  	}
   218  	data := make([]uint8, bl*2)
   219  
   220  	offset := int16(0)
   221  	for dim > 0 {
   222  		for i := int16(0); i < bl; i++ {
   223  			if offset+i < l {
   224  				c565 := RGBATo565(buffer[offset+i])
   225  				c1 := uint8(c565 >> 8)
   226  				c2 := uint8(c565)
   227  				data[i*2] = c1
   228  				data[i*2+1] = c2
   229  			}
   230  		}
   231  		if dim >= d.bufferLength {
   232  			d.Tx(data, false)
   233  		} else {
   234  			d.Tx(data[:dim*2], false)
   235  		}
   236  		dim -= d.bufferLength
   237  		offset += d.bufferLength
   238  	}
   239  	return nil
   240  }
   241  
   242  // DrawFastVLine draws a vertical line faster than using SetPixel
   243  func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) {
   244  	if y0 > y1 {
   245  		y0, y1 = y1, y0
   246  	}
   247  	d.FillRectangle(x, y0, 1, y1-y0+1, c)
   248  }
   249  
   250  // DrawFastHLine draws a horizontal line faster than using SetPixel
   251  func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) {
   252  	if x0 > x1 {
   253  		x0, x1 = x1, x0
   254  	}
   255  	d.FillRectangle(x0, y, x1-x0+1, 1, c)
   256  }
   257  
   258  // FillScreen fills the screen with a given color
   259  func (d *Device) FillScreen(c color.RGBA) {
   260  	d.FillRectangle(0, 0, d.width, d.height, c)
   261  }
   262  
   263  // SetContrast sets the three contrast values (A, B & C)
   264  func (d *Device) SetContrast(contrastA, contrastB, contrastC uint8) {
   265  	d.Command(SET_CONTRAST)
   266  	d.Tx([]byte{contrastA, contrastB, contrastC}, false)
   267  }
   268  
   269  // Command sends a command byte to the display
   270  func (d *Device) Command(command uint8) {
   271  	d.Tx([]byte{command}, true)
   272  }
   273  
   274  // Data sends a data byte to the display
   275  func (d *Device) Data(data uint8) {
   276  	d.Tx([]byte{data}, false)
   277  }
   278  
   279  // Tx sends data to the display
   280  func (d *Device) Tx(data []byte, isCommand bool) {
   281  	d.dcPin.Set(!isCommand)
   282  	d.csPin.Low()
   283  	d.bus.Tx(data, nil)
   284  	d.csPin.High()
   285  }
   286  
   287  // Size returns the current size of the display
   288  func (d *Device) Size() (w, h int16) {
   289  	return d.width, d.height
   290  }
   291  
   292  // RGBATo565 converts a color.RGBA to uint16 used in the display
   293  func RGBATo565(c color.RGBA) uint16 {
   294  	r, g, b, _ := c.RGBA()
   295  	return uint16((r & 0xF800) +
   296  		((g & 0xFC00) >> 5) +
   297  		((b & 0xF800) >> 11))
   298  }