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 }