# The MIT License (MIT) # # Copyright (c) 2023 Kevin G. Schlosser # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # This code is based on the work by Robert Hammelrath # https://github.com/robert-hh/SSD1963-TFT-Library-for-PyBoard-and-RP2040 # ************* IMPORTANT *************** # The pins must be connected in this manner no exceptions # d0 = GP2 # d1 = GP3 # d2 = GP4 # d3 = GP5 # d4 = GP6 # d5 = GP7 # d6 = GP8 # d7 = GP9 # dc = GP10 # rd = GP11 # wr = GP12 # cs = tied to ground # reset = any other pin # backlight = any other pin # power = any other pin # *************************************** import lvgl as lv import rp2 # NOQA import time import array import micropython from machine import Pin from micropython import const import lv_utils # NOQA TYPING = const(0) if TYPING: null = None pins = 0 pindirs = 0 x_dec = 0 x = 0 osr = 0 class Side(object): @staticmethod def side(*_) -> list: return [] def jmp(*_) -> Side: return Side def in_(*_) -> Side: return Side def label(*_) -> Side: return Side def nop() -> Side: return Side def pull(*_) -> Side: return Side def mov(*_) -> Side: return Side def out(*_) -> Side: return Side class ptr8(bytearray): pass class uint(int): pass class ptr16(list): pass class ptr32(list): pass # CS is not used and must be hard tied to GND _BASE_PIN = const(2) _DMA_BASE = const(0x50000000) _READ_ADDR = const(0) _WRITE_ADDR = const(1) _TRANS_COUNT = const(2) _CTRL_TRIG = const(3) _CTRL_ALIAS = const(4) _TRANS_COUNT_ALIAS = const(9) _CHAN_ABORT = const(0x111) # Address offset / 4 _BUSY = const(1 << 24) _PIO0_BASE = const(0x50200000) _PIO0_BASE_TXF0 = const(_PIO0_BASE + 0x10) _PIO0_BASE_TXF1 = const(_PIO0_BASE + 0x14) _PIO0_BASE_TXF2 = const(_PIO0_BASE + 0x18) _PIO0_BASE_TXF3 = const(_PIO0_BASE + 0x1C) _PIO0_BASE_RXF0 = const(_PIO0_BASE + 0x20) _PIO0_BASE_RXF1 = const(_PIO0_BASE + 0x24) _PIO0_BASE_RXF2 = const(_PIO0_BASE + 0x28) _PIO0_BASE_RXF3 = const(_PIO0_BASE + 0x2C) _PIO0_INSTR_MEM = const(_PIO0_BASE + 0x48) PORTRAIT = const(-1) LANDSCAPE = const(-2) REVERSE_PORTRAIT = const(-3) REVERSE_LANDSCAPE = const(-4) class _IOBase(object): width = 0 height = 0 def __init__( self, reset, power=-1, backlight=-1, backlight_on=0, power_on=0, rot=PORTRAIT, factor=4, start_x=0, start_y=0, double_buffer=True, initialize=True, color_format=None, asynchronous=False ): if not lv.is_initialized(): lv.init() if lv.color_t.__SIZE__ != 4: # NOQA raise RuntimeError( 'You must set "LV_COLOR_DEPTH=32" at compile time' ) buf_size = (self.width * self.height * lv.color_t.__SIZE__) // factor # NOQA self.buf1 = bytearray(buf_size) self.buf2 = bytearray(buf_size) if double_buffer else None if self.buf1 and self.buf2: print("Double buffer") elif self.buf1: print("Single buffer") else: raise RuntimeError( "Not enough memory to allocate display buffer" ) self.disp_buf = lv.disp_draw_buf_t() self.disp_drv = lv.disp_drv_t() self.disp_buf.init( # NOQA self.buf1, self.buf2, buf_size // lv.color_t.__SIZE__ # NOQA ) self.disp_drv.init() # NOQA self.disp_drv.user_data = { 'start_x': start_x, 'start_y': start_y } self.disp_drv.draw_buf = self.disp_buf self.disp_drv.flush_cb = self._flush self.disp_drv.hor_res = self.width self.disp_drv.ver_res = self.height if color_format: self.disp_drv.color_format = color_format if rot == PORTRAIT: orientation = 1 v_flip = 0 h_flip = 0 elif rot == LANDSCAPE: orientation = 0 v_flip = 0 h_flip = 0 elif rot == REVERSE_PORTRAIT: orientation = 1 v_flip = 1 h_flip = 1 elif rot == REVERSE_LANDSCAPE: orientation = 0 v_flip = 1 h_flip = 1 else: raise RuntimeError('rotation not supported') # # For convenience, define X1..X1 and Y9..Y12 as output port using the # python functions. # X1..X8 will be redefind on the fly as Input by accessing the MODER # control registers # when needed. Y9 is treate seperately, since it is used for Reset, # which is done at python level # since it need long delays anyhow, 5 and 15 ms vs. 10 µs. # # Set TFT general defaults self._orientation = orientation self._v_flip = v_flip # flip vertical self._h_flip = h_flip # flip horizontal self._c_flip = 0 # flip blue/red self._rc_flip = 0 # flip row/column self._power_on = power_on self._backlight_on = backlight_on if backlight != -1: self._backlight = Pin(backlight, Pin.OUT) else: self._backlight = None if power != -1: self._power = Pin(power, Pin.OUT) else: self._power = None if double_buffer: self._factor = factor * 2 else: self._factor = factor self._start_x = start_x self._start_y = start_y # cache a few function calls self._reset = Pin(reset, Pin.OUT, value=1) # Reset the device time.sleep_ms(10) self._reset.value(0) # Low time.sleep_ms(20) self._reset.value(1) # set high again time.sleep_ms(20) # create the array for the Cursor settings and pre-set the commands self._ar_setxy = array.array("H", bytearray(22)) if orientation == LANDSCAPE: self._ar_setxy[0] = 0x2A self._ar_setxy[5] = 0x2B else: self._ar_setxy[0] = 0x2B self._ar_setxy[5] = 0x2A self._ar_setxy[10] = 0x2C # set frequencies and mwait time factors self._tx_limit = max((20000 * 480 * 800 * 3) // 25000000, 1) self._rx_limit = max((30000 * 100 * 100 * 3) // 25000000, 1) self._DMA_chan_abort(0) # cancel any actions # create the state machines self._sm_data_write_triple = rp2.StateMachine( 0, self._pio_data_write_triple, freq=25000000, sideset_base=Pin(_BASE_PIN + 8), out_base=Pin(_BASE_PIN) ) self._sm_data_write_byte = rp2.StateMachine( 1, self._pio_data_write_byte, freq=25000000, sideset_base=Pin(_BASE_PIN + 8), out_base=Pin(_BASE_PIN) ) self._sm_cmd_write = rp2.StateMachine( 2, self._pio_cmd_write, freq=25000000, sideset_base=Pin(_BASE_PIN + 9), out_base=Pin(_BASE_PIN) ) self._sm_cmd_data_read = rp2.StateMachine( 3, self._pio_cmd_data_read, freq=25000000, sideset_base=Pin(_BASE_PIN + 8), out_base=Pin(_BASE_PIN), in_base=Pin(_BASE_PIN) ) # Set up the DMA control patterns self._DMA_fill_control = ( (0x1 << 21) | (0x00 << 15) | (0 << 11) | (0 << 10) | (0 << 6) | (0 << 5) | (0 << 4) | (2 << 2) | (1 << 1) | (1 << 0) ) self._DMA_data_write_control = ( (0x1 << 21) | (0x01 << 15) | (0 << 11) | (0 << 10) | (0 << 6) | (0 << 5) | (0 << 4) | (0 << 2) | (1 << 1) | (1 << 0) ) self._DMA_data_read_control = ( (0x1 << 21) | (0x07 << 15) | (0 << 11) | (0 << 10) | (0 << 6) | (1 << 5) | (0 << 4) | (0 << 2) | (1 << 1) | (1 << 0) ) time.sleep_ms(10) if initialize: self.init() if not lv_utils.event_loop.is_running(): self.event_loop = lv_utils.event_loop(asynchronous=asynchronous) # define the PIO codes # writing command or data. D/C# is coded in the 9th bit # fmt: off @staticmethod @rp2.asm_pio( sideset_init=(rp2.PIO.OUT_HIGH,) * 2, out_init=(rp2.PIO.OUT_HIGH,) * 9, out_shiftdir=rp2.PIO.SHIFT_RIGHT, autopull=True, pull_thresh=16 ) def _pio_cmd_write(): out(pins, 9).side(0b10) # WR low, output 9 bit out(null, 7).side(0b11) # WR high, discard 7 bit # just write byte data bytes. Used with and w/o DMA @staticmethod @rp2.asm_pio( sideset_init=(rp2.PIO.OUT_HIGH,) * 3, out_init=(rp2.PIO.OUT_HIGH,) * 8, out_shiftdir=rp2.PIO.SHIFT_RIGHT, autopull=True, pull_thresh=8 ) def _pio_data_write_byte(): out(pins, 8).side(0b101) # WR low, output data nop().side(0b111) # WR high # just write data byte triples # used for fill commands with DMA @staticmethod @rp2.asm_pio( sideset_init=(rp2.PIO.OUT_HIGH,) * 3, out_init=(rp2.PIO.OUT_HIGH,) * 8, out_shiftdir=rp2.PIO.SHIFT_RIGHT, autopull=True, pull_thresh=24 ) def _pio_data_write_triple(): out(pins, 8).side(0b101) # WR low, output data nop().side(0b111) # WR high # Write a command and read back data # Switching the bus direction as needed @staticmethod @rp2.asm_pio( sideset_init=(rp2.PIO.OUT_HIGH,) * 3, out_init=(rp2.PIO.OUT_HIGH,) * 8, out_shiftdir=rp2.PIO.SHIFT_RIGHT, autopull=False, autopush=True, pull_thresh=16, push_thresh=8 ) def _pio_cmd_data_read(): pull().side(0b111) # get number of bytes to read mov(x, osr).side(0b111) # save it to the counter pull().side(0b100) # WR low out(pins, 8).side(0b100) # send the command nop().side(0b110) # WR high out(pindirs, 8).side(0b011)[3] # and switch to input mode, RD Low # NOQA nop().side(0b011)[3] # First read needs a delay, RD low # NOQA label("again") nop().side(0b111) # RD high in_(pins, 8).side(0b111) # Get data jmp(x_dec, "again").side(0b011) # Loop, RD low pull().side(0b111) # get the new pindir value out(pindirs, 8).side(0b111) # and switch back to output mode # fmt: on # set up DMA0. Parameters: # source address, destination address, # DMA tranfers, control word @staticmethod @micropython.viper def _DMA0_setup(src: ptr32, dst: ptr32, nword: uint, control: uint): dma = ptr32(uint(_DMA_BASE)) dma[_READ_ADDR] = uint(src) dma[_WRITE_ADDR] = uint(dst) dma[_TRANS_COUNT] = nword dma[_CTRL_TRIG] = control # Abort an transfer @staticmethod @micropython.viper def _DMA_chan_abort(chan: uint): dma = ptr32(uint(_DMA_BASE)) dma[_CHAN_ABORT] = 1 << chan while dma[_CHAN_ABORT]: time.sleep_us(10) # wait until the counter reaches zero @staticmethod @micropython.viper def _DMA0_wait(limit: int): dma = ptr32(uint(_DMA_BASE)) wait = 5 while dma[_TRANS_COUNT] > 0 < limit: time.sleep_us(wait) limit -= 1 wait += 1 # encode 565 type data @staticmethod @micropython.viper def _encode565(data: ptr8, pixels: int, buffer: ptr8): to = 0 for i in range(0, pixels * 2, 2): buffer[to] = data[i + 1] & 0xF8 buffer[to + 1] = ( ((data[i + 1] & 0x07) << 5) | ((data[i] >> 3) & 0x1C) ) buffer[to + 2] = data[i] << 3 to += 3 # Set the address range for various draw commands and set the # TFT for expecting data # PIO version of # SetXY: takes net about 50 µs including the call. Pretty slow # set the adress range @micropython.viper def _setXY(self, x1: int, y1: int, x2: int, y2: int): ar_setxy = ptr16(self._ar_setxy) ar_setxy[1] = (x1 >> 8) | 0x100 ar_setxy[2] = x1 | 0x100 ar_setxy[3] = (x2 >> 8) | 0x100 ar_setxy[4] = x2 | 0x100 ar_setxy[6] = (y1 >> 8) | 0x100 ar_setxy[7] = y1 | 0x100 ar_setxy[8] = (y2 >> 8) | 0x100 ar_setxy[9] = y2 | 0x100 self._sm_cmd_write.active(1) self._sm_cmd_write.put(ar_setxy, 0) self._sm_cmd_write.active(0) # PIO version of # Fill screen by writing size pixels with the color given in data # data must be 3 bytes of red, green, blue # The area to be filled has to be set in advance by setXY # The speed is 60 ns/pixel at 100MHz pio clock. Pretty fast @micropython.viper def fillSCR(self, data: ptr8, pixels: int): DMA_fill_control = int(ptr32(self._DMA_fill_control)) tx_limit = int(ptr32(self._tx_limit)) self._sm_data_write_triple.active(1) self._DMA0_setup(data, _PIO0_BASE_TXF0, pixels, DMA_fill_control) self._DMA0_wait(tx_limit) # Wait for the transfer to finish self._sm_data_write_triple.active(0) # Send data to the tft controller @micropython.viper def _tft_data(self, data: ptr8): self._sm_data_write_byte.active(1) self._sm_data_write_byte.put(data, 0) self._sm_data_write_byte.active(0) @micropython.viper def _tft_data_DMA(self, data: ptr8, size: int): self._sm_data_write_byte.active(1) DMA_data_write_control = int(ptr32(self._DMA_data_write_control)) tx_limit = int(ptr32(self._tx_limit)) self._DMA0_setup( data, _PIO0_BASE_TXF1, size, DMA_data_write_control ) self._DMA0_wait(tx_limit) # Wait for the transfer to finish self._sm_data_write_byte.active(0) # Send a command to the TFT controller @micropython.viper def _tft_cmd(self, cmd: int): self._sm_cmd_write.active(1) self._sm_cmd_write.put(cmd, 0) self._sm_cmd_write.active(0) # Send a command and data to the TFT controller # cmd is the command byte, data must be a bytearray # object with the command payload, # int is the size of the data # For the startup-phase use this function. @micropython.viper def _tft_cmd_data(self, cmd: int, data: ptr8, _): self._tft_cmd(cmd) self._tft_data(data) # PIO version of send a command byte and read data # from the TFT controller by DMA # data must be a bytearray object, int is the size of the data. # The speed is about 120 ns/byte. PIO speed 25 MHz. No luck # at faster rates @micropython.viper def _tft_read_cmd_data(self, cmd: int, data: ptr8, size: int): rx_limit = int(ptr32(self._rx_limit)) self._sm_cmd_data_read.active(1) self._sm_cmd_data_read.put(size - 1) # send the size self._sm_cmd_data_read.put(cmd, 0) # send the command self._DMA0_setup( _PIO0_BASE_RXF3, data, size, self._DMA_data_read_control ) self._DMA0_wait(rx_limit) # Wait for the transfer to finish self._sm_cmd_data_read.put(0xff, 0) # reset direction self._sm_cmd_data_read.active(0) # PIO version of send a command byte and read data from # the TFT controller py polling # data must be a bytearray object, int is the size of the data. # The speed is about 14 µs/byte. Pretty slow. @micropython.viper def _tft_read_cmd_data_poll(self, cmd: int, data: ptr8, size: int): self._sm_cmd_data_read.active(1) self._sm_cmd_data_read.put(size - 1) # send the size self._sm_cmd_data_read.put(cmd, 0) # send the command for i in range(size): # get the data data[i] = int(self._sm_cmd_data_read.get()) self._sm_cmd_data_read.put(0xff, 0) # reset direction self._sm_cmd_data_read.active(0) # swap byte pairs in a buffer # sometimes needed for picture data @micropython.viper def _swapbytes(self, data: ptr8, ln: int): # bytearray, len(bytearray) for i in range(0, ln, 2): data[i], data[i + 1] = data[i + 1], data[i] # swap colors red/blue in the buffer @micropython.viper def _swapcolors(self, data: ptr8, ln: int): # bytearray, len(bytearray) for i in range(0, ln, 3): data[i], data[i + 2] = data[i + 2], data[i] def _init(self): raise NotImplementedError def init(self): # PLL multiplier, set PLL clock to 100M self._tft_cmd_data(0xE2, bytearray(b'\x1D\x02\x54'), 3) # N=0x2D for 6.5MHz, 0x1D for 10MHz crystal # PLLClock = Crystal * (Mult + 1) / (Div + 1) # The intermediate value Crystal * (Mult + 1) # must be between 250MHz and 750 MHz self._tft_cmd_data(0xE0, bytearray(b'\x01'), 1) # PLL Enable time.sleep_ms(10) self._tft_cmd_data(0xE0, bytearray(b'\x03'), 1) time.sleep_ms(10) self._tft_cmd(0x01) # software reset time.sleep_ms(10) self._init() # GPIO[3:0] out 1 self._tft_cmd_data(0xBA, bytearray(b'\x0F'), 1) # GPIO3=input, GPIO[2:0]=output self._tft_cmd_data(0xB8, bytearray(b'\x07\x01'), 1) # Pixel data Interface 8 Bit self._tft_cmd_data(0xF0, bytearray(b'\x00'), 1) # Display on self._tft_cmd(0x29) # Set PWM for B/L self._tft_cmd_data( 0xBE, bytearray(b'\x06\xF0\x01\xF0\x00\x00'), 6 ) # Set DBC: enable, agressive self._tft_cmd_data(0xD0, bytearray(b'\x0D'), 1) # Init done. clear Screen and switch BG LED on self.backlight(100) # switch BG LED on def backlight(self, percent): # set backlight brightness if self._backlight is not None: self._backlight.value(1 if percent >= 50 else 0) def power(self, onoff): if self._power is not None: self._power.value(int(onoff)) def set_rotation(self, rot, c_flip=False): # set the tft flip modes if rot == PORTRAIT: orientation = 1 v_flip = 0 h_flip = 0 elif rot == LANDSCAPE: orientation = 0 v_flip = 0 h_flip = 0 elif rot == REVERSE_PORTRAIT: orientation = 1 v_flip = 1 h_flip = 1 elif rot == REVERSE_LANDSCAPE: orientation = 0 v_flip = 1 h_flip = 1 else: raise RuntimeError('rotation not supported') self._v_flip = v_flip # flip vertical self._h_flip = h_flip # flip horizontal self._c_flip = c_flip # flip blue/red self._orientation = orientation # LANDSCAPE/PORTRAIT # rotation/ flip, etc., t.b.d. self._tft_cmd_data( 0x36, bytearray([ (orientation << 5) | (c_flip << 3) | ((h_flip & 1) << 1) | (v_flip & 1) ]), 1 ) @micropython.viper def _flush(self, disp_drv, area, color_p: ptr32): # NOQA start_x = uint(ptr16(self._start_x)) start_y = uint(ptr16(self._start_y)) x1 = uint(ptr16(area.x1)) + start_x x2 = uint(ptr16(area.x2)) + start_x y1 = uint(ptr16(area.y1)) + start_y y2 = uint(ptr16(area.y2)) + start_y size = (x2 - x1 + 1) * (y2 - y1 + 1) data = bytearray(size * 3) for i in range(size): data[i * 3] = color_p[i] >> 24 data[i * 3 + 1] = color_p[i] >> 16 & 0xFF data[i * 3 + 2] = color_p[i] >> 8 & 0xFF self._setXY(x1, y1, x2 + 1, y2 + 1) self._tft_data_DMA(data, size) class LB04301(_IOBase): width = 479 height = 271 def _init(self): # # Value Min Typical Max # DotClock 5 MHZ 9 MHz 12 MHz # HT (Hor. Total 490 531 612 # HDP (Hor. Disp) 480 # HBP (back porch) 8 43 # HFP (Fr. porch) 2 8 # HPW (Hor. sync) 1 # VT (Vert. Total) 275 288 335 # VDP (Vert. Disp) 272 # VBP (back porch) 2 12 # VFP (fr. porch) 1 4 # VPW (vert. sync) 1 10 # # This table in combination with the relation # above leads to the settings: # HPS = 43, HPW = 8, LPS = 0, HT = 531 # VPS = 14, VPW = 10, FPS = 0, VT = 288 # # PLL setting for PCLK self._tft_cmd_data(0xe6, bytearray(b'\x01\x70\xA3'), 3) # (9MHz * 1048576 / 100MHz) - 1 = 94371 = 0x170a3 self._tft_cmd_data( 0xB0, bytearray([ 0x20, # 24 Color bits, HSync/VSync low, No Dithering 0x00, # TFT mode self.width >> 8, self.width & 0xFF, # physical Width of TFT self.height >> 8, self.height & 0xFF, # physical Height of TFT 0x00 ]), 7 ) # Last byte only required for a serial TFT # HSYNC, Set HT 531 HPS 43 HPW=Sync pulse 8 LPS 0 self._tft_cmd_data( 0xB4, bytearray(b'\x02\x13\x00\x2B\x08\x00\x00\x00'), 8 ) # VSYNC, Set VT 288 VPS 14 VPW 10 FPS 0 self._tft_cmd_data( 0xB6, bytearray(b'\x01\x20\x00\x0E\x0A\x00\x00'), 7 ) # rotation/ flip, etc., t.b.d. self._tft_cmd_data( 0x36, bytearray([ (self._orientation & 1) << 5 | (self._h_flip & 1) << 1 | (self._v_flip & 1) ]), 1 ) class AT070TN92(_IOBase): width = 799 height = 479 def _init(self): # # Value Min Typical Max # DotClock 26.4 MHz 33.3 MHz 46.8 MHz # HT (Hor. Total 862 1056 1200 # HDP (Hor. Disp) 800 # HBP (back porch) 46 46 46 # HFP (Fr. porch) 16 210 254 # HPW (Hor. sync) 1 40 # VT (Vert. Total) 510 525 650 # VDP (Vert. Disp) 480 # VBP (back porch) 23 23 23 # VFP (fr. porch) 7 22 147 # VPW (vert. sync) 1 20 # # This table in combination with the relation # above leads to the settings: # HPS = 46, HPW = 8, LPS = 0, HT = 1056 # VPS = 23, VPW = 10, VPS = 0, VT = 525 # # PLL setting for PCLK # (33.3MHz * 1048576 / 100MHz) - 1 = 349174 = 0x553f6 self._tft_cmd_data(0xE6, bytearray(b'\x05\x53\xF6'), 3) # LCD SPECIFICATION self._tft_cmd_data( 0xB0, bytearray([ 0x00, # 18 Color bits, HSync/VSync low, No Dithering/FRC 0x00, # TFT mode self.width >> 8, self.width & 0xFF, # physical Width of TFT self.height >> 8, self.height & 0xFF, # physical Height of TFT 0x00 ]), 7 ) # Last byte only required for a serial TFT # HSYNC, Set HT 1056 HPS 46 HPW 8 LPS 0 self._tft_cmd_data( 0xB4, bytearray(b'\x04\x1F\x00\x2E\x08\x00\x00\x00'), 8 ) # VSYNC, Set VT 525 VPS 23 VPW 08 FPS 0 self._tft_cmd_data( 0xB6, bytearray(b'\x02\x0C\x00\x17\x08\x00\x00'), 7 ) # rotation/ flip, etc., t.b.d. self._tft_cmd_data( 0x36, bytearray([ ((self._orientation & 1) << 5) | ((self._h_flip & 1) << 1) | (self._v_flip & 1) ]), 1 ) class AT090TN10(_IOBase): width = 799 height = 479 def _init(self): # # Value Min Typical Max # DotClock 26.4 MHz 33.3 MHz 46.8 MHz # HT (Hor. Total 862 1056 1200 # HDP (Hor. Disp) 800 # HBP (back porch) 46 46 46 # HFP (Fr. porch) 16 210 354 # HPW (Hor. sync) 1 40 # VT (Vert. Total) 510 525 650 # VDP (Vert. Disp) 480 # VBP (back porch) 23 23 23 # VFP (fr. porch) 7 22 147 # VPW (vert. sync) 1 20 # # This table in combination with the relation # above leads to the settings: # HPS = 46, HPW = 8, LPS = 0, HT = 1056 # VPS = 23, VPW = 10, VPS = 0, VT = 525 # # PLL setting for PCLK # (33.3MHz * 1048576 / 100MHz) - 1 = 349174 = 0x553f6 self._tft_cmd_data(0xE6, bytearray(b'\x05\x53\xF6'), 3) # LCD SPECIFICATION self._tft_cmd_data( 0xB0, bytearray([ 0x20, # 24 Color bits, HSync/VSync low, No Dithering/FRC 0x00, # TFT mode self.width >> 8, self.width & 0xFF, # physical Width of TFT self.height >> 8, self.height & 0xFF, # physical Height of TFT 0x00 ]), 7 ) # Last byte only required for a serial TFT # HSYNC, Set HT 1056 HPS 46 HPW 8 LPS 0 self._tft_cmd_data( 0xB4, bytearray(b'\x04\x1F\x00\x2E\x08\x00\x00\x00'), 8 ) # VSYNC, Set VT 525 VPS 23 VPW 08 FPS 0 self._tft_cmd_data( 0xB6, bytearray(b'\x02\x0C\x00\x17\x08\x00\x00'), 7 ) # rotation/ flip, etc., t.b.d. self._tft_cmd_data( 0x36, bytearray([ ((self._orientation & 1) << 5) | ((self._h_flip & 1) << 1) | (self._v_flip & 1) ]), 1 )