tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/st7735/st7735.go (about) 1 // Package st7735 implements a driver for the ST7735 TFT displays, it comes in various screen sizes. 2 // 3 // Datasheet: https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/ 4 package st7735 // import "tinygo.org/x/drivers/st7735" 5 6 import ( 7 "image/color" 8 "machine" 9 "time" 10 11 "errors" 12 13 "tinygo.org/x/drivers" 14 "tinygo.org/x/drivers/pixel" 15 ) 16 17 type Model uint8 18 19 // Rotation controls the rotation used by the display. 20 // 21 // Deprecated: use drivers.Rotation instead. 22 type Rotation = drivers.Rotation 23 24 // Pixel formats supported by the st7735 driver. 25 type Color interface { 26 pixel.RGB444BE | pixel.RGB565BE 27 28 pixel.BaseColor 29 } 30 31 var ( 32 errOutOfBounds = errors.New("rectangle coordinates outside display area") 33 ) 34 35 // Device wraps an SPI connection. 36 type Device = DeviceOf[pixel.RGB565BE] 37 38 // DeviceOf is a generic version of Device, which supports different pixel 39 // formats. 40 type DeviceOf[T Color] struct { 41 bus drivers.SPI 42 dcPin machine.Pin 43 resetPin machine.Pin 44 csPin machine.Pin 45 blPin machine.Pin 46 width int16 47 height int16 48 columnOffset int16 49 rowOffset int16 50 rotation drivers.Rotation 51 batchLength int16 52 model Model 53 isBGR bool 54 batchData pixel.Image[T] // "image" with width, height of (batchLength, 1) 55 } 56 57 // Config is the configuration for the display 58 type Config struct { 59 Width int16 60 Height int16 61 Rotation drivers.Rotation 62 Model Model 63 RowOffset int16 64 ColumnOffset int16 65 } 66 67 // New creates a new ST7735 connection. The SPI wire must already be configured. 68 func New(bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) Device { 69 return NewOf[pixel.RGB565BE](bus, resetPin, dcPin, csPin, blPin) 70 } 71 72 // NewOf creates a new ST7735 connection with a particular pixel format. The SPI 73 // wire must already be configured. 74 func NewOf[T Color](bus drivers.SPI, resetPin, dcPin, csPin, blPin machine.Pin) DeviceOf[T] { 75 dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) 76 resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) 77 csPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) 78 blPin.Configure(machine.PinConfig{Mode: machine.PinOutput}) 79 return DeviceOf[T]{ 80 bus: bus, 81 dcPin: dcPin, 82 resetPin: resetPin, 83 csPin: csPin, 84 blPin: blPin, 85 } 86 } 87 88 // Configure initializes the display with default configuration 89 func (d *DeviceOf[T]) Configure(cfg Config) { 90 d.model = cfg.Model 91 if cfg.Width != 0 { 92 d.width = cfg.Width 93 } else { 94 if d.model == MINI80x160 { 95 d.width = 80 96 } else { 97 d.width = 128 98 } 99 } 100 if cfg.Height != 0 { 101 d.height = cfg.Height 102 } else { 103 d.height = 160 104 } 105 d.rotation = cfg.Rotation 106 d.rowOffset = cfg.RowOffset 107 d.columnOffset = cfg.ColumnOffset 108 109 d.batchLength = d.width 110 if d.height > d.width { 111 d.batchLength = d.height 112 } 113 d.batchLength += d.batchLength & 1 114 d.batchData = pixel.NewImage[T](int(d.batchLength), 1) 115 116 // reset the device 117 d.resetPin.High() 118 time.Sleep(5 * time.Millisecond) 119 d.resetPin.Low() 120 time.Sleep(20 * time.Millisecond) 121 d.resetPin.High() 122 time.Sleep(150 * time.Millisecond) 123 124 // Common initialization 125 d.Command(SWRESET) 126 time.Sleep(150 * time.Millisecond) 127 d.Command(SLPOUT) 128 time.Sleep(500 * time.Millisecond) 129 d.Command(FRMCTR1) 130 d.Data(0x01) 131 d.Data(0x2C) 132 d.Data(0x2D) 133 d.Command(FRMCTR2) 134 d.Data(0x01) 135 d.Data(0x2C) 136 d.Data(0x2D) 137 d.Command(FRMCTR3) 138 d.Data(0x01) 139 d.Data(0x2C) 140 d.Data(0x2D) 141 d.Data(0x01) 142 d.Data(0x2C) 143 d.Data(0x2D) 144 d.Command(INVCTR) 145 d.Data(0x07) 146 d.Command(PWCTR1) 147 d.Data(0xA2) 148 d.Data(0x02) 149 d.Data(0x84) 150 d.Command(PWCTR2) 151 d.Data(0xC5) 152 d.Command(PWCTR3) 153 d.Data(0x0A) 154 d.Data(0x00) 155 d.Command(PWCTR4) 156 d.Data(0x8A) 157 d.Data(0x2A) 158 d.Command(PWCTR5) 159 d.Data(0x8A) 160 d.Data(0xEE) 161 d.Command(VMCTR1) 162 d.Data(0x0E) 163 164 // Set the color format depending on the generic type. 165 d.Command(COLMOD) 166 var zeroColor T 167 switch any(zeroColor).(type) { 168 case pixel.RGB444BE: 169 d.Data(0x03) // 12 bits per pixel 170 default: 171 d.Data(0x05) // 16 bits per pixel 172 } 173 174 if d.model == GREENTAB { 175 d.InvertColors(false) 176 } else if d.model == MINI80x160 { 177 d.isBGR = true 178 d.InvertColors(true) 179 } 180 181 // common color adjustment 182 d.Command(GMCTRP1) 183 d.Data(0x02) 184 d.Data(0x1C) 185 d.Data(0x07) 186 d.Data(0x12) 187 d.Data(0x37) 188 d.Data(0x32) 189 d.Data(0x29) 190 d.Data(0x2D) 191 d.Data(0x29) 192 d.Data(0x25) 193 d.Data(0x2B) 194 d.Data(0x39) 195 d.Data(0x00) 196 d.Data(0x01) 197 d.Data(0x03) 198 d.Data(0x10) 199 d.Command(GMCTRN1) 200 d.Data(0x03) 201 d.Data(0x1D) 202 d.Data(0x07) 203 d.Data(0x06) 204 d.Data(0x2E) 205 d.Data(0x2C) 206 d.Data(0x29) 207 d.Data(0x2D) 208 d.Data(0x2E) 209 d.Data(0x2E) 210 d.Data(0x37) 211 d.Data(0x3F) 212 d.Data(0x00) 213 d.Data(0x00) 214 d.Data(0x02) 215 d.Data(0x10) 216 217 d.Command(NORON) 218 time.Sleep(10 * time.Millisecond) 219 d.Command(DISPON) 220 time.Sleep(500 * time.Millisecond) 221 222 if cfg.Model == MINI80x160 { 223 d.Command(MADCTL) 224 d.Data(0xC0) 225 } 226 227 d.SetRotation(d.rotation) 228 229 d.blPin.High() 230 } 231 232 // Display does nothing, there's no buffer as it might be too big for some boards 233 func (d *DeviceOf[T]) Display() error { 234 return nil 235 } 236 237 // SetPixel sets a pixel in the screen 238 func (d *DeviceOf[T]) SetPixel(x int16, y int16, c color.RGBA) { 239 w, h := d.Size() 240 if x < 0 || y < 0 || x >= w || y >= h { 241 return 242 } 243 d.FillRectangle(x, y, 1, 1, c) 244 } 245 246 // setWindow prepares the screen to be modified at a given rectangle 247 func (d *DeviceOf[T]) setWindow(x, y, w, h int16) { 248 if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 { 249 x += d.columnOffset 250 y += d.rowOffset 251 } else { 252 x += d.rowOffset 253 y += d.columnOffset 254 } 255 d.Tx([]uint8{CASET}, true) 256 d.Tx([]uint8{uint8(x >> 8), uint8(x), uint8((x + w - 1) >> 8), uint8(x + w - 1)}, false) 257 d.Tx([]uint8{RASET}, true) 258 d.Tx([]uint8{uint8(y >> 8), uint8(y), uint8((y + h - 1) >> 8), uint8(y + h - 1)}, false) 259 d.Command(RAMWR) 260 } 261 262 // SetScrollWindow sets an area to scroll with fixed top and bottom parts of the display 263 func (d *DeviceOf[T]) SetScrollArea(topFixedArea, bottomFixedArea int16) { 264 // TODO: this code is broken, see the st7789 and ili9341 implementations for 265 // how to do this correctly. 266 d.Command(VSCRDEF) 267 d.Tx([]uint8{ 268 uint8(topFixedArea >> 8), uint8(topFixedArea), 269 uint8(d.height - topFixedArea - bottomFixedArea>>8), uint8(d.height - topFixedArea - bottomFixedArea), 270 uint8(bottomFixedArea >> 8), uint8(bottomFixedArea)}, 271 false) 272 } 273 274 // SetScroll sets the vertical scroll address of the display. 275 func (d *DeviceOf[T]) SetScroll(line int16) { 276 d.Command(VSCRSADD) 277 d.Tx([]uint8{uint8(line >> 8), uint8(line)}, false) 278 } 279 280 // SpotScroll returns the display to its normal state 281 func (d *DeviceOf[T]) StopScroll() { 282 d.Command(NORON) 283 } 284 285 // FillRectangle fills a rectangle at a given coordinates with a color 286 func (d *DeviceOf[T]) FillRectangle(x, y, width, height int16, c color.RGBA) error { 287 k, i := d.Size() 288 if x < 0 || y < 0 || width <= 0 || height <= 0 || 289 x >= k || (x+width) > k || y >= i || (y+height) > i { 290 return errors.New("rectangle coordinates outside display area") 291 } 292 d.setWindow(x, y, width, height) 293 294 d.batchData.FillSolidColor(pixel.NewColor[T](c.R, c.G, c.B)) 295 i = width * height 296 for i > 0 { 297 if i >= d.batchLength { 298 d.Tx(d.batchData.RawBuffer(), false) 299 } else { 300 d.Tx(d.batchData.Rescale(int(i), 1).RawBuffer(), false) 301 } 302 i -= d.batchLength 303 } 304 return nil 305 } 306 307 // DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates 308 // 309 // Deprecated: use DrawBitmap instead. 310 func (d *DeviceOf[T]) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error { 311 k, i := d.Size() 312 if x < 0 || y < 0 || w <= 0 || h <= 0 || 313 x >= k || (x+w) > k || y >= i || (y+h) > i { 314 return errOutOfBounds 315 } 316 d.setWindow(x, y, w, h) 317 d.Tx(data, false) 318 return nil 319 } 320 321 // DrawBitmap copies the bitmap to the internal buffer on the screen at the 322 // given coordinates. It returns once the image data has been sent completely. 323 func (d *DeviceOf[T]) DrawBitmap(x, y int16, bitmap pixel.Image[T]) error { 324 width, height := bitmap.Size() 325 return d.DrawRGBBitmap8(x, y, bitmap.RawBuffer(), int16(width), int16(height)) 326 } 327 328 // FillRectangle fills a rectangle at a given coordinates with a buffer 329 func (d *DeviceOf[T]) FillRectangleWithBuffer(x, y, width, height int16, buffer []color.RGBA) error { 330 k, l := d.Size() 331 if x < 0 || y < 0 || width <= 0 || height <= 0 || 332 x >= k || (x+width) > k || y >= l || (y+height) > l { 333 return errors.New("rectangle coordinates outside display area") 334 } 335 k = width * height 336 l = int16(len(buffer)) 337 if k != l { 338 return errors.New("buffer length does not match with rectangle size") 339 } 340 341 d.setWindow(x, y, width, height) 342 343 offset := int16(0) 344 for k > 0 { 345 for i := int16(0); i < d.batchLength; i++ { 346 if offset+i < l { 347 c := buffer[offset+i] 348 d.batchData.Set(int(i), 0, pixel.NewColor[T](c.R, c.G, c.B)) 349 } 350 } 351 if k >= d.batchLength { 352 d.Tx(d.batchData.RawBuffer(), false) 353 } else { 354 d.Tx(d.batchData.Rescale(int(k), 1).RawBuffer(), false) 355 } 356 k -= d.batchLength 357 offset += d.batchLength 358 } 359 return nil 360 } 361 362 // DrawFastVLine draws a vertical line faster than using SetPixel 363 func (d *DeviceOf[T]) DrawFastVLine(x, y0, y1 int16, c color.RGBA) { 364 if y0 > y1 { 365 y0, y1 = y1, y0 366 } 367 d.FillRectangle(x, y0, 1, y1-y0+1, c) 368 } 369 370 // DrawFastHLine draws a horizontal line faster than using SetPixel 371 func (d *DeviceOf[T]) DrawFastHLine(x0, x1, y int16, c color.RGBA) { 372 if x0 > x1 { 373 x0, x1 = x1, x0 374 } 375 d.FillRectangle(x0, y, x1-x0+1, 1, c) 376 } 377 378 // FillScreen fills the screen with a given color 379 func (d *DeviceOf[T]) FillScreen(c color.RGBA) { 380 if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 { 381 d.FillRectangle(0, 0, d.width, d.height, c) 382 } else { 383 d.FillRectangle(0, 0, d.height, d.width, c) 384 } 385 } 386 387 // Rotation returns the currently configured rotation. 388 func (d *DeviceOf[T]) Rotation() drivers.Rotation { 389 return d.rotation 390 } 391 392 // SetRotation changes the rotation of the device (clock-wise) 393 func (d *DeviceOf[T]) SetRotation(rotation drivers.Rotation) error { 394 d.rotation = rotation 395 madctl := uint8(0) 396 switch rotation % 4 { 397 case drivers.Rotation0: 398 madctl = MADCTL_MX | MADCTL_MY 399 case drivers.Rotation90: 400 madctl = MADCTL_MY | MADCTL_MV 401 case drivers.Rotation180: 402 // nothing to do 403 case drivers.Rotation270: 404 madctl = MADCTL_MX | MADCTL_MV 405 } 406 if d.isBGR { 407 madctl |= MADCTL_BGR 408 } 409 d.Command(MADCTL) 410 d.Data(madctl) 411 return nil 412 } 413 414 // Command sends a command to the display 415 func (d *DeviceOf[T]) Command(command uint8) { 416 d.Tx([]byte{command}, true) 417 } 418 419 // Command sends a data to the display 420 func (d *DeviceOf[T]) Data(data uint8) { 421 d.Tx([]byte{data}, false) 422 } 423 424 // Tx sends data to the display 425 func (d *DeviceOf[T]) Tx(data []byte, isCommand bool) { 426 d.dcPin.Set(!isCommand) 427 d.bus.Tx(data, nil) 428 } 429 430 // Size returns the current size of the display. 431 func (d *DeviceOf[T]) Size() (w, h int16) { 432 if d.rotation == drivers.Rotation0 || d.rotation == drivers.Rotation180 { 433 return d.width, d.height 434 } 435 return d.height, d.width 436 } 437 438 // EnableBacklight enables or disables the backlight 439 func (d *DeviceOf[T]) EnableBacklight(enable bool) { 440 if enable { 441 d.blPin.High() 442 } else { 443 d.blPin.Low() 444 } 445 } 446 447 // Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot 448 // less power. The LCD won't display an image anymore, but the memory contents 449 // will be kept. 450 func (d *DeviceOf[T]) Sleep(sleepEnabled bool) error { 451 if sleepEnabled { 452 // Shut down LCD panel. 453 d.Command(SLPIN) 454 time.Sleep(5 * time.Millisecond) // 5ms required by the datasheet 455 } else { 456 // Turn the LCD panel back on. 457 d.Command(SLPOUT) 458 // The st7735 datasheet says it is necessary to wait 120ms before 459 // sending another command. 460 time.Sleep(120 * time.Millisecond) 461 } 462 return nil 463 } 464 465 // InverColors inverts the colors of the screen 466 func (d *DeviceOf[T]) InvertColors(invert bool) { 467 if invert { 468 d.Command(INVON) 469 } else { 470 d.Command(INVOFF) 471 } 472 } 473 474 // IsBGR changes the color mode (RGB/BGR) 475 func (d *DeviceOf[T]) IsBGR(bgr bool) { 476 d.isBGR = bgr 477 }