tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/ili9341/ili9341.go (about) 1 package ili9341 2 3 import ( 4 "errors" 5 "image/color" 6 "machine" 7 "time" 8 9 "tinygo.org/x/drivers" 10 "tinygo.org/x/drivers/pixel" 11 ) 12 13 type Config struct { 14 Width int16 15 Height int16 16 Rotation drivers.Rotation 17 DisplayInversion bool 18 } 19 20 type Device struct { 21 width int16 22 height int16 23 rotation drivers.Rotation 24 driver driver 25 26 x0, x1 int16 // cached address window; prevents useless/expensive 27 y0, y1 int16 // syscalls to PASET and CASET 28 29 dc machine.Pin 30 cs machine.Pin 31 rst machine.Pin 32 rd machine.Pin 33 } 34 35 // Image buffer type used in the ili9341. 36 type Image = pixel.Image[pixel.RGB565BE] 37 38 var cmdBuf [6]byte 39 40 var initCmd = []byte{ 41 0xEF, 3, 0x03, 0x80, 0x02, 42 0xCF, 3, 0x00, 0xC1, 0x30, 43 0xED, 4, 0x64, 0x03, 0x12, 0x81, 44 0xE8, 3, 0x85, 0x00, 0x78, 45 0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02, 46 0xF7, 1, 0x20, 47 0xEA, 2, 0x00, 0x00, 48 PWCTR1, 1, 0x23, // Power control VRH[5:0] 49 PWCTR2, 1, 0x10, // Power control SAP[2:0];BT[3:0] 50 VMCTR1, 2, 0x3e, 0x28, // VCM control 51 VMCTR2, 1, 0x86, // VCM control2 52 MADCTL, 1, 0x48, // Memory Access Control 53 VSCRSADD, 1, 0x00, // Vertical scroll zero 54 PIXFMT, 1, 0x55, 55 FRMCTR1, 2, 0x00, 0x18, 56 DFUNCTR, 3, 0x08, 0x82, 0x27, // Display Function Control 57 0xF2, 1, 0x00, // 3Gamma Function Disable 58 GAMMASET, 1, 0x01, // Gamma curve selected 59 GMCTRP1, 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma 60 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, 61 GMCTRN1, 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma 62 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, 63 } 64 65 // Configure prepares display for use 66 func (d *Device) Configure(config Config) { 67 68 if config.Width == 0 { 69 config.Width = TFTWIDTH 70 } 71 if config.Height == 0 { 72 config.Height = TFTHEIGHT 73 } 74 d.width = config.Width 75 d.height = config.Height 76 d.rotation = config.Rotation 77 78 // try to pick an initial cache miss for one of the points 79 d.x0, d.x1 = -(d.width + 1), d.x0 80 d.y0, d.y1 = -(d.height + 1), d.y0 81 82 output := machine.PinConfig{machine.PinOutput} 83 84 // configure chip select if there is one 85 if d.cs != machine.NoPin { 86 d.cs.Configure(output) 87 d.cs.High() // deselect 88 } 89 90 d.dc.Configure(output) 91 d.dc.High() // data mode 92 93 // driver-specific configuration 94 d.driver.configure(&config) 95 96 if d.rd != machine.NoPin { 97 d.rd.Configure(output) 98 d.rd.High() 99 } 100 101 // reset the display 102 if d.rst != machine.NoPin { 103 // configure hardware reset if there is one 104 d.rst.Configure(output) 105 d.rst.High() 106 delay(100) 107 d.rst.Low() 108 delay(100) 109 d.rst.High() 110 delay(200) 111 } else { 112 // if no hardware reset, send software reset 113 d.sendCommand(SWRESET, nil) 114 delay(150) 115 } 116 117 if config.DisplayInversion { 118 initCmd = append(initCmd, INVON, 0x80) 119 } 120 121 initCmd = append(initCmd, 122 SLPOUT, 0x80, // Exit Sleep 123 DISPON, 0x80, // Display on 124 0x00, // End of list 125 ) 126 for i, c := 0, len(initCmd); i < c; { 127 cmd := initCmd[i] 128 if cmd == 0x00 { 129 break 130 } 131 x := initCmd[i+1] 132 numArgs := int(x & 0x7F) 133 d.sendCommand(cmd, initCmd[i+2:i+2+numArgs]) 134 if x&0x80 > 0 { 135 delay(150) 136 } 137 i += numArgs + 2 138 } 139 140 d.SetRotation(d.rotation) 141 } 142 143 // Size returns the current size of the display. 144 func (d *Device) Size() (x, y int16) { 145 switch d.rotation { 146 case Rotation90, Rotation270, Rotation90Mirror, Rotation270Mirror: 147 return d.height, d.width 148 default: // Rotation0, Rotation180, etc 149 return d.width, d.height 150 } 151 } 152 153 // SetPixel modifies the internal buffer. 154 func (d *Device) SetPixel(x, y int16, c color.RGBA) { 155 d.setWindow(x, y, 1, 1) 156 c565 := RGBATo565(c) 157 d.startWrite() 158 d.driver.write16(c565) 159 d.endWrite() 160 } 161 162 // Display sends the buffer (if any) to the screen. 163 func (d *Device) Display() error { 164 return nil 165 } 166 167 // EnableTEOutput enables the TE ("tearing effect") line. 168 // The TE line goes high when the screen is not currently being updated and can 169 // be used to start drawing. When used correctly, it can avoid tearing entirely. 170 func (d *Device) EnableTEOutput(on bool) { 171 if on { 172 cmdBuf[0] = 0 173 d.sendCommand(TEON, cmdBuf[:1]) // M=0 (V-blanking only, no H-blanking) 174 } else { 175 d.sendCommand(TEOFF, nil) // TEOFF 176 } 177 } 178 179 // DrawRGBBitmap copies an RGB bitmap to the internal buffer at given coordinates 180 // 181 // Deprecated: use DrawBitmap instead. 182 func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error { 183 k, i := d.Size() 184 if x < 0 || y < 0 || w <= 0 || h <= 0 || 185 x >= k || (x+w) > k || y >= i || (y+h) > i { 186 return errors.New("rectangle coordinates outside display area") 187 } 188 d.setWindow(x, y, w, h) 189 d.startWrite() 190 d.driver.write16sl(data) 191 d.endWrite() 192 return nil 193 } 194 195 // DrawRGBBitmap8 copies an RGB bitmap to the internal buffer at given coordinates 196 // 197 // Deprecated: use DrawBitmap instead. 198 func (d *Device) DrawRGBBitmap8(x, y int16, data []uint8, w, h int16) error { 199 k, i := d.Size() 200 if x < 0 || y < 0 || w <= 0 || h <= 0 || 201 x >= k || (x+w) > k || y >= i || (y+h) > i { 202 return errors.New("rectangle coordinates outside display area") 203 } 204 d.setWindow(x, y, w, h) 205 d.startWrite() 206 d.driver.write8sl(data) 207 d.endWrite() 208 return nil 209 } 210 211 // DrawBitmap copies the bitmap to the internal buffer on the screen at the 212 // given coordinates. It returns once the image data has been sent completely. 213 func (d *Device) DrawBitmap(x, y int16, bitmap Image) error { 214 width, height := bitmap.Size() 215 return d.DrawRGBBitmap8(x, y, bitmap.RawBuffer(), int16(width), int16(height)) 216 } 217 218 // FillRectangle fills a rectangle at given coordinates with a color 219 func (d *Device) FillRectangle(x, y, width, height int16, c color.RGBA) error { 220 k, i := d.Size() 221 if x < 0 || y < 0 || width <= 0 || height <= 0 || 222 x >= k || (x+width) > k || y >= i || (y+height) > i { 223 return errors.New("rectangle coordinates outside display area") 224 } 225 d.setWindow(x, y, width, height) 226 c565 := RGBATo565(c) 227 d.startWrite() 228 d.driver.write16n(c565, int(width)*int(height)) 229 d.endWrite() 230 return nil 231 } 232 233 // DrawRectangle draws a rectangle at given coordinates with a color 234 func (d *Device) DrawRectangle(x, y, w, h int16, c color.RGBA) error { 235 if err := d.DrawFastHLine(x, x+w-1, y, c); err != nil { 236 return err 237 } 238 if err := d.DrawFastHLine(x, x+w-1, y+h-1, c); err != nil { 239 return err 240 } 241 if err := d.DrawFastVLine(x, y, y+h-1, c); err != nil { 242 return err 243 } 244 if err := d.DrawFastVLine(x+w-1, y, y+h-1, c); err != nil { 245 return err 246 } 247 return nil 248 } 249 250 // DrawFastVLine draws a vertical line faster than using SetPixel 251 func (d *Device) DrawFastVLine(x, y0, y1 int16, c color.RGBA) error { 252 if y0 > y1 { 253 y0, y1 = y1, y0 254 } 255 return d.FillRectangle(x, y0, 1, y1-y0+1, c) 256 } 257 258 // DrawFastHLine draws a horizontal line faster than using SetPixel 259 func (d *Device) DrawFastHLine(x0, x1, y int16, c color.RGBA) error { 260 if x0 > x1 { 261 x0, x1 = x1, x0 262 } 263 return d.FillRectangle(x0, y, x1-x0+1, 1, c) 264 } 265 266 // FillScreen fills the screen with a given color 267 func (d *Device) FillScreen(c color.RGBA) { 268 if d.rotation == Rotation0 || d.rotation == Rotation180 { 269 d.FillRectangle(0, 0, d.width, d.height, c) 270 } else { 271 d.FillRectangle(0, 0, d.height, d.width, c) 272 } 273 } 274 275 // Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot 276 // less power. The LCD won't display an image anymore, but the memory contents 277 // will be kept. 278 func (d *Device) Sleep(sleepEnabled bool) error { 279 if sleepEnabled { 280 // Shut down LCD panel. 281 d.sendCommand(SLPIN, nil) 282 time.Sleep(5 * time.Millisecond) // 5ms required by the datasheet 283 } else { 284 // Turn the LCD panel back on. 285 d.sendCommand(SLPOUT, nil) 286 // Note: the ili9341 documentation says that it is needed to wait at 287 // least 120ms before going to sleep again. Sleeping here would not be 288 // practical (delays turning on the screen too much), so just hope the 289 // screen won't need to sleep again for at least 120ms. 290 // In practice, it's unlikely the user will set the display to sleep 291 // again within 120ms. 292 } 293 return nil 294 } 295 296 // Rotation returns the current rotation of the device. 297 func (d *Device) Rotation() drivers.Rotation { 298 return d.rotation 299 } 300 301 // GetRotation returns the current rotation of the device. 302 // 303 // Deprecated: use Rotation instead. 304 func (d *Device) GetRotation() drivers.Rotation { 305 return d.rotation 306 } 307 308 // SetRotation changes the rotation of the device (clock-wise). 309 func (d *Device) SetRotation(rotation drivers.Rotation) error { 310 madctl := uint8(0) 311 switch rotation % 8 { 312 case Rotation0: 313 madctl = MADCTL_MX | MADCTL_BGR 314 case Rotation90: 315 madctl = MADCTL_MV | MADCTL_BGR 316 case Rotation180: 317 madctl = MADCTL_MY | MADCTL_BGR | MADCTL_ML 318 case Rotation270: 319 madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML 320 case Rotation0Mirror: 321 madctl = MADCTL_BGR 322 case Rotation90Mirror: 323 madctl = MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML 324 case Rotation180Mirror: 325 madctl = MADCTL_MX | MADCTL_MY | MADCTL_BGR | MADCTL_ML 326 case Rotation270Mirror: 327 madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML 328 } 329 cmdBuf[0] = madctl 330 d.sendCommand(MADCTL, cmdBuf[:1]) 331 d.rotation = rotation 332 return nil 333 } 334 335 // SetScrollArea sets an area to scroll with fixed top/bottom or left/right parts of the display 336 // Rotation affects scroll direction 337 func (d *Device) SetScrollArea(topFixedArea, bottomFixedArea int16) { 338 if d.height < 320 { 339 // The screen doesn't use the full 320 pixel height. 340 // Enlarge the bottom fixed area to fill the 320 pixel height, so that 341 // bottomFixedArea starts from the visible bottom of the screen. 342 bottomFixedArea += 320 - d.height 343 } 344 cmdBuf[0] = uint8(topFixedArea >> 8) 345 cmdBuf[1] = uint8(topFixedArea) 346 cmdBuf[2] = uint8((320 - topFixedArea - bottomFixedArea) >> 8) 347 cmdBuf[3] = uint8(320 - topFixedArea - bottomFixedArea) 348 cmdBuf[4] = uint8(bottomFixedArea >> 8) 349 cmdBuf[5] = uint8(bottomFixedArea) 350 d.sendCommand(VSCRDEF, cmdBuf[:6]) 351 } 352 353 // SetScroll sets the vertical scroll address of the display. 354 func (d *Device) SetScroll(line int16) { 355 cmdBuf[0] = uint8(line >> 8) 356 cmdBuf[1] = uint8(line) 357 d.sendCommand(VSCRSADD, cmdBuf[:2]) 358 } 359 360 // StopScroll returns the display to its normal state 361 func (d *Device) StopScroll() { 362 d.sendCommand(NORON, nil) 363 } 364 365 // setWindow prepares the screen to be modified at a given rectangle 366 func (d *Device) setWindow(x, y, w, h int16) { 367 //x += d.columnOffset 368 //y += d.rowOffset 369 x1 := x + w - 1 370 if x != d.x0 || x1 != d.x1 { 371 cmdBuf[0] = uint8(x >> 8) 372 cmdBuf[1] = uint8(x) 373 cmdBuf[2] = uint8(x1 >> 8) 374 cmdBuf[3] = uint8(x1) 375 d.sendCommand(CASET, cmdBuf[:4]) 376 d.x0, d.x1 = x, x1 377 } 378 y1 := y + h - 1 379 if y != d.y0 || y1 != d.y1 { 380 cmdBuf[0] = uint8(y >> 8) 381 cmdBuf[1] = uint8(y) 382 cmdBuf[2] = uint8(y1 >> 8) 383 cmdBuf[3] = uint8(y1) 384 d.sendCommand(PASET, cmdBuf[:4]) 385 d.y0, d.y1 = y, y1 386 } 387 d.sendCommand(RAMWR, nil) 388 } 389 390 //go:inline 391 func (d *Device) startWrite() { 392 if d.cs != machine.NoPin { 393 d.cs.Low() 394 } 395 } 396 397 //go:inline 398 func (d *Device) endWrite() { 399 if d.cs != machine.NoPin { 400 d.cs.High() 401 } 402 } 403 404 func (d *Device) sendCommand(cmd byte, data []byte) { 405 d.startWrite() 406 d.dc.Low() 407 d.driver.write8(cmd) 408 d.dc.High() 409 if data != nil { 410 d.driver.write8sl(data) 411 } 412 d.endWrite() 413 } 414 415 type driver interface { 416 configure(config *Config) 417 write8(b byte) 418 write8n(b byte, n int) 419 write8sl(b []byte) 420 write16(data uint16) 421 write16n(data uint16, n int) 422 write16sl(data []uint16) 423 } 424 425 func delay(m int) { 426 t := time.Now().UnixNano() + int64(time.Duration(m*1000)*time.Microsecond) 427 for time.Now().UnixNano() < t { 428 } 429 } 430 431 // RGBATo565 converts a color.RGBA to uint16 used in the display 432 func RGBATo565(c color.RGBA) uint16 { 433 r, g, b, _ := c.RGBA() 434 return uint16((r & 0xF800) + 435 ((g & 0xFC00) >> 5) + 436 ((b & 0xF800) >> 11)) 437 }