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 }