class ili9341: # Constants COLOR_MODE_RGB = const(0x00) COLOR_MODE_BGR = const(0x08) MADCTL_MH = const(0x04) MADCTL_ML = const(0x10) MADCTL_MV = const(0x20) MADCTL_MX = const(0x40) MADCTL_MY = const(0x80) PORTRAIT = MADCTL_MX LANDSCAPE = MADCTL_MV TRANS_BUFFER_LEN = const(16) # Default values of "power" and "backlight" are reversed logic! 0 means ON. # You can change this by setting backlight_on and power_on arguments. def __init__(self, miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, backlight_on=0, power_on=0, spihost=esp.HSPI_HOST, mhz=40, factor=4, hybrid=True, width=240, height=320, colormode=COLOR_MODE_BGR, rot=PORTRAIT, invert=False, double_buffer=True): # Make sure Micropython was built such that color won't require processing before DMA if lv.color_t.SIZE != 2: raise RuntimeError( 'ili9341 micropython driver requires defining LV_COLOR_DEPTH=16' ) if colormode == COLOR_MODE_BGR and not hasattr(lv.color_t().ch, 'green_l'): raise RuntimeError( 'ili9341 BGR color mode requires defining LV_COLOR_16_SWAP=1') # Initializations self.width = width self.height = height self.miso = miso self.mosi = mosi self.clk = clk self.cs = cs self.dc = dc self.rst = rst self.power = power self.backlight = backlight self.backlight_on = backlight_on self.power_on = power_on self.spihost = spihost self.mhz = mhz self.factor = factor self.hybrid = hybrid self.buf_size = (self.width * self.height * lv.color_t.SIZE) // factor self.init_cmds = [ { 'cmd': 0xCF, 'data': bytes([0x00, 0x83, 0X30]) }, { 'cmd': 0xED, 'data': bytes([0x64, 0x03, 0X12, 0X81]) }, { 'cmd': 0xE8, 'data': bytes([0x85, 0x01, 0x79]) }, { 'cmd': 0xCB, 'data': bytes([0x39, 0x2C, 0x00, 0x34, 0x02]) }, { 'cmd': 0xF7, 'data': bytes([0x20]) }, { 'cmd': 0xEA, 'data': bytes([0x00, 0x00]) }, { 'cmd': 0xC0, 'data': bytes([0x26]) }, # Power control { 'cmd': 0xC1, 'data': bytes([0x11]) }, # Power control { 'cmd': 0xC5, 'data': bytes([0x35, 0x3E]) }, # VCOM control { 'cmd': 0xC7, 'data': bytes([0xBE]) }, # VCOM control { 'cmd': 0x36, 'data': bytes([rot | colormode]) }, # Memory Access Control { 'cmd': 0x3A, 'data': bytes([0x55]) }, # Pixel Format Set { 'cmd': 0xB1, 'data': bytes([0x00, 0x1B]) }, { 'cmd': 0xF2, 'data': bytes([0x08]) }, { 'cmd': 0x26, 'data': bytes([0x01]) }, { 'cmd': 0xE0, 'data': bytes([ 0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00 ]) }, { 'cmd': 0XE1, 'data': bytes([ 0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F ]) }, { 'cmd': 0x2A, 'data': bytes([0x00, 0x00, 0x00, 0xEF]) }, { 'cmd': 0x2B, 'data': bytes([0x00, 0x00, 0x01, 0x3f]) }, { 'cmd': 0x2C, 'data': bytes([0]) }, { 'cmd': 0xB7, 'data': bytes([0x07]) }, { 'cmd': 0xB6, 'data': bytes([0x0A, 0x82, 0x27, 0x00]) }, { 'cmd': 0x11, 'data': bytes([0]), 'delay': 100 }, { 'cmd': 0x29, 'data': bytes([0]), 'delay': 100 } ] if invert: self.init_cmds.append({'cmd': 0x21}) self.init() # Register display driver self.buf1 = esp.heap_caps_malloc(self.buf_size, esp.MALLOC_CAP.DMA) self.buf2 = esp.heap_caps_malloc( self.buf_size, esp.MALLOC_CAP.DMA) 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 DMA-able memory to allocate display buffer") self.disp_buf = lv.disp_buf_t() self.disp_drv = lv.disp_drv_t() lv.disp_buf_init(self.disp_buf, self.buf1, self.buf2, self.buf_size // lv.color_t.SIZE) lv.disp_drv_init(self.disp_drv) self.disp_drv.user_data = {'dc': self.dc, 'spi': self.spi} self.disp_drv.buffer = self.disp_buf self.disp_drv.flush_cb = esp.ili9341_flush if self.hybrid and hasattr( esp, 'ili9341_flush') else self.flush self.disp_drv.monitor_cb = self.monitor self.disp_drv.hor_res = self.width self.disp_drv.ver_res = self.height lv.disp_drv_register(self.disp_drv) ###################################################### def disp_spi_init(self): # Register finalizer callback to deinit SPI. # This would get called on soft reset. self.finalizer = lvesp32.cb_finalizer(self.deinit) lvesp32.init() buscfg = esp.spi_bus_config_t({ "miso_io_num": self.miso, "mosi_io_num": self.mosi, "sclk_io_num": self.clk, "quadwp_io_num": -1, "quadhd_io_num": -1, "max_transfer_sz": self.buf_size, }) devcfg = esp.spi_device_interface_config_t({ "clock_speed_hz": self.mhz * 1000 * 1000, # Clock out at DISP_SPI_MHZ MHz "mode": 0, # SPI mode 0 "spics_io_num": self.cs, # CS pin "queue_size": 2, #"flags": esp.SPI_DEVICE.HALFDUPLEX, "duty_cycle_pos": 128, }) if self.hybrid and hasattr(esp, 'ili9341_post_cb_isr'): devcfg.pre_cb = None devcfg.post_cb = esp.ili9341_post_cb_isr else: devcfg.pre_cb = esp.spi_pre_cb_isr devcfg.post_cb = esp.spi_post_cb_isr esp.gpio_pad_select_gpio(self.cs) # Initialize the SPI bus, if needed. if buscfg.miso_io_num >= 0 and \ buscfg.mosi_io_num >= 0 and \ buscfg.sclk_io_num >= 0: esp.gpio_pad_select_gpio(self.miso) esp.gpio_pad_select_gpio(self.mosi) esp.gpio_pad_select_gpio(self.clk) esp.gpio_set_direction(self.miso, esp.GPIO_MODE.INPUT) esp.gpio_set_pull_mode(self.miso, esp.GPIO.PULLUP_ONLY) esp.gpio_set_direction(self.mosi, esp.GPIO_MODE.OUTPUT) esp.gpio_set_direction(self.clk, esp.GPIO_MODE.OUTPUT) ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) if ret != 0: raise RuntimeError("Failed initializing SPI bus") self.trans_buffer = esp.heap_caps_malloc(TRANS_BUFFER_LEN, esp.MALLOC_CAP.DMA) self.cmd_trans_data = self.trans_buffer.__dereference__(1) self.word_trans_data = self.trans_buffer.__dereference__(4) # Attach the LCD to the SPI bus ptr_to_spi = esp.C_Pointer() ret = esp.spi_bus_add_device(self.spihost, devcfg, ptr_to_spi) if ret != 0: raise RuntimeError("Failed adding SPI device") self.spi = ptr_to_spi.ptr_val self.bytes_transmitted = 0 completed_spi_transaction = esp.spi_transaction_t() cast_spi_transaction_instance = esp.spi_transaction_t.cast_instance def post_isr(arg): reported_transmitted = self.bytes_transmitted if reported_transmitted > 0: print('- Completed DMA of %d bytes (mem_free=0x%X)' % (reported_transmitted, gc.mem_free())) self.bytes_transmitted -= reported_transmitted # Called in ISR context! def flush_isr(spi_transaction_ptr): lv.disp_flush_ready(self.disp_drv) # esp.spi_device_release_bus(self.spi) esp.get_ccount(self.end_time_ptr) # cast_spi_transaction_instance(completed_spi_transaction, spi_transaction_ptr) # self.bytes_transmitted += completed_spi_transaction.length # try: # micropython.schedule(post_isr, None) # except RuntimeError: # pass self.spi_callbacks = esp.spi_transaction_set_cb(None, flush_isr) # # Deinitialize SPI device and bus, and free memory # This function is called from finilizer during gc sweep - therefore must not allocate memory! # trans_result_ptr = esp.C_Pointer() def deinit(self): print('Deinitializing ILI9341..') # Prevent callbacks to lvgl, which refer to the buffers we are about to delete lvesp32.deinit() if self.spi: # Pop all pending transaction results ret = 0 while ret == 0: ret = esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr, 1) # Remove device esp.spi_bus_remove_device(self.spi) self.spi = None # Free SPI bus esp.spi_bus_free(self.spihost) self.spihost = None # Free RAM if self.buf1: esp.heap_caps_free(self.buf1) self.buf1 = None if self.buf2: esp.heap_caps_free(self.buf2) self.buf2 = None if self.trans_buffer: esp.heap_caps_free(self.trans_buffer) self.trans_buffer = None ###################################################### trans = esp.spi_transaction_t() # .cast( # esp.heap_caps_malloc( # esp.spi_transaction_t.SIZE, esp.MALLOC_CAP.DMA)) def spi_send(self, data): self.trans.length = len( data) * 8 # Length is in bytes, transaction length is in bits. self.trans.tx_buffer = data # data should be allocated as DMA-able memory self.trans.user = None esp.spi_device_polling_transmit(self.spi, self.trans) def spi_send_dma(self, data): self.trans.length = len( data) * 8 # Length is in bytes, transaction length is in bits. self.trans.tx_buffer = data # data should be allocated as DMA-able memory self.trans.user = self.spi_callbacks #esp.spi_device_transmit(self.spi, self.trans) esp.spi_device_queue_trans(self.spi, self.trans, -1) ###################################################### ###################################################### def send_cmd(self, cmd): esp.gpio_set_level(self.dc, 0) # Command mode self.cmd_trans_data[0] = cmd self.spi_send(self.cmd_trans_data) def send_data(self, data): esp.gpio_set_level(self.dc, 1) # Data mode if len(data) > TRANS_BUFFER_LEN: raise RuntimeError('Data too long, please use DMA!') trans_data = self.trans_buffer.__dereference__(len(data)) trans_data[:] = data[:] self.spi_send(trans_data) def send_trans_word(self): esp.gpio_set_level(self.dc, 1) # Data mode self.spi_send(self.word_trans_data) def send_data_dma(self, data): # data should be allocated as DMA-able memory esp.gpio_set_level(self.dc, 1) # Data mode self.spi_send_dma(data) ###################################################### def init(self): self.disp_spi_init() # Initialize non-SPI GPIOs esp.gpio_pad_select_gpio(self.dc) esp.gpio_pad_select_gpio(self.rst) if self.backlight != -1: esp.gpio_pad_select_gpio(self.backlight) if self.power != -1: esp.gpio_pad_select_gpio(self.power) esp.gpio_set_direction(self.dc, esp.GPIO_MODE.OUTPUT) esp.gpio_set_direction(self.rst, esp.GPIO_MODE.OUTPUT) if self.backlight != -1: esp.gpio_set_direction(self.backlight, esp.GPIO_MODE.OUTPUT) if self.power != -1: esp.gpio_set_direction(self.power, esp.GPIO_MODE.OUTPUT) # Power the display if self.power != -1: esp.gpio_set_level(self.power, self.power_on) sleep_ms(100) # Reset the display esp.gpio_set_level(self.rst, 0) sleep_ms(100) esp.gpio_set_level(self.rst, 1) sleep_ms(100) # Send all the commands for cmd in self.init_cmds: self.send_cmd(cmd['cmd']) if 'data' in cmd: self.send_data(cmd['data']) if 'delay' in cmd: sleep_ms(cmd['delay']) print("ILI9341 initialization completed") # Enable backlight if self.backlight != -1: print("Enable backlight") esp.gpio_set_level(self.backlight, self.backlight_on) ###################################################### start_time_ptr = esp.C_Pointer() end_time_ptr = esp.C_Pointer() flush_acc_setup_cycles = 0 flush_acc_dma_cycles = 0 def flush(self, disp_drv, area, color_p): if self.end_time_ptr.int_val and self.end_time_ptr.int_val > self.start_time_ptr.int_val: self.flush_acc_dma_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val esp.get_ccount(self.start_time_ptr) # esp.spi_device_acquire_bus(self.spi, esp.ESP.MAX_DELAY) # Column addresses self.send_cmd(0x2A) self.word_trans_data[0] = (area.x1 >> 8) & 0xFF self.word_trans_data[1] = area.x1 & 0xFF self.word_trans_data[2] = (area.x2 >> 8) & 0xFF self.word_trans_data[3] = area.x2 & 0xFF self.send_trans_word() # Page addresses self.send_cmd(0x2B) self.word_trans_data[0] = (area.y1 >> 8) & 0xFF self.word_trans_data[1] = area.y1 & 0xFF self.word_trans_data[2] = (area.y2 >> 8) & 0xFF self.word_trans_data[3] = area.y2 & 0xFF self.send_trans_word() # Memory write by DMA, disp_flush_ready when finished self.send_cmd(0x2C) size = (area.x2 - area.x1 + 1) * (area.y2 - area.y1 + 1) data_view = color_p.__dereference__(size * lv.color_t.SIZE) esp.get_ccount(self.end_time_ptr) if self.end_time_ptr.int_val > self.start_time_ptr.int_val: self.flush_acc_setup_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val esp.get_ccount(self.start_time_ptr) self.send_data_dma(data_view) ###################################################### monitor_acc_time = 0 monitor_acc_px = 0 monitor_count = 0 cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 def monitor(self, disp_drv, time, px): self.monitor_acc_time += time self.monitor_acc_px += px self.monitor_count += 1 def stat(self): if self.monitor_count == 0: return None time = self.monitor_acc_time // self.monitor_count setup = self.flush_acc_setup_cycles // (self.monitor_count * self.cycles_in_ms) dma = self.flush_acc_dma_cycles // (self.monitor_count * self.cycles_in_ms) px = self.monitor_acc_px // self.monitor_count self.monitor_acc_time = 0 self.monitor_acc_px = 0 self.monitor_count = 0 self.flush_acc_setup_cycles = 0 self.flush_acc_dma_cycles = 0 return time, setup, dma, px
class xpt2046: # Command is 8 bit, but we add another bit as a space before xpt2046 stats sending the response, See Figure 12 on the datasheet CMD_X_READ = const(0b100100000) CMD_Y_READ = const(0b110100000) CMD_Z1_READ = const(0b101100000) CMD_Z2_READ = const(0b110000000) MAX_RAW_COORD = const((1 << 12) - 1) def __init__(self, miso=-1, mosi=-1, clk=-1, cs=25, spihost=esp.HSPI_HOST, mhz=5, max_cmds=16, cal_x0=3783, cal_y0=3948, cal_x1=242, cal_y1=423, transpose=True, samples=3): # Initializations self.screen_width = lv.disp_get_hor_res(lv.disp_t.cast(None)) self.screen_height = lv.disp_get_ver_res(lv.disp_t.cast(None)) self.miso = miso self.mosi = mosi self.clk = clk self.cs = cs self.spihost = spihost self.mhz = mhz self.max_cmds = max_cmds self.cal_x0 = cal_x0 self.cal_y0 = cal_y0 self.cal_x1 = cal_x1 self.cal_y1 = cal_y1 self.transpose = transpose self.samples = samples self.touch_count = 0 self.touch_cycles = 0 self.spi_init() indev_drv = lv.indev_drv_t() lv.indev_drv_init(indev_drv) indev_drv.type = lv.INDEV_TYPE.POINTER indev_drv.read_cb = self.read lv.indev_drv_register(indev_drv) def calibrate(self, x0, y0, x1, y1): self.cal_x0 = x0 self.cal_y0 = y0 self.cal_x1 = x1 self.cal_y1 = y1 def spi_init(self): buscfg = esp.spi_bus_config_t({ "miso_io_num": self.miso, "mosi_io_num": self.mosi, "sclk_io_num": self.clk, "quadwp_io_num": -1, "quadhd_io_num": -1, "max_transfer_sz": 4, }) devcfg = esp.spi_device_interface_config_t({ "command_bits": 9, # Actually 8, but need another cycle before xpt starts transmitting response, see Figure 12 on the datasheet. "clock_speed_hz": self.mhz * 1000 * 1000, "mode": 0, # SPI mode 0 "spics_io_num": self.cs, # CS pin "queue_size": self.max_cmds, "flags": esp.SPI_DEVICE.HALFDUPLEX, "duty_cycle_pos": 128, }) esp.gpio_pad_select_gpio(self.cs) # Initialize the SPI bus, if needed if buscfg.miso_io_num >= 0 and \ buscfg.mosi_io_num >= 0 and \ buscfg.sclk_io_num >= 0: esp.gpio_pad_select_gpio(self.miso) esp.gpio_pad_select_gpio(self.mosi) esp.gpio_pad_select_gpio(self.clk) esp.gpio_set_direction(self.miso, esp.GPIO_MODE.INPUT) esp.gpio_set_pull_mode(self.miso, esp.GPIO.PULLUP_ONLY) esp.gpio_set_direction(self.mosi, esp.GPIO_MODE.OUTPUT) esp.gpio_set_direction(self.clk, esp.GPIO_MODE.OUTPUT) ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) if ret != 0: raise RuntimeError("Failed initializing SPI bus") # Register finalizer callback to deinit SPI. # This would get called on soft reset. self.finalizer = lvesp32.cb_finalizer(self.deinit) # Attach the xpt2046 to the SPI bus ptr_to_spi = esp.C_Pointer() ret = esp.spi_bus_add_device(self.spihost, devcfg, ptr_to_spi) if ret != 0: raise RuntimeError("Failed adding SPI device") self.spi = ptr_to_spi.ptr_val # Prepare transactions. Each response is 16bit long self.trans = [ esp.spi_transaction_t({ 'rx_buffer': bytearray(2), 'rxlength': 16 }) for i in range(0, self.max_cmds) ] trans_result_ptr = esp.C_Pointer() # # Deinitalize SPI device and bus # def deinit(self): print('Deinitializing XPT2046...') if self.spi: # Pop all pending transaction results ret = 0 while ret == 0: ret = esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr, 1) # Remove device esp.spi_bus_remove_device(self.spi) # Free SPI bus esp.spi_bus_free(self.spihost) # @micropython.viper def xpt_cmds(self, cmds): cmd_count = int(len(cmds)) for i in range(0, cmd_count): self.trans[i].cmd = cmds[i] esp.spi_device_queue_trans(self.spi, self.trans[i], -1) result = [] for i in range(0, cmd_count): esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr, -1) buf = self.trans[i].rx_buffer.__dereference__(2) value = (int(buf[0]) << 4) + (int( buf[1]) >> 4) # value is in the 12 higher bits, network order if value == int(self.MAX_RAW_COORD): value = 0 result.append(value) return tuple(result) # @micropython.viper def get_med_coords(self, count: int): mid = count // 2 values = [] for i in range(0, count): values.append(self.xpt_cmds([self.CMD_X_READ, self.CMD_Y_READ])) # values = self.xpt_cmds([self.CMD_X_READ]*count + [self.CMD_Y_READ]*count) # x_values = sorted(values[:count]) # y_values = sorted(values[count:]) x_values = sorted([x for x, y in values]) y_values = sorted([y for x, y in values]) if int(x_values[0]) == 0 or int(y_values[0]) == 0: return None return x_values[mid], y_values[mid] # @micropython.viper def get_coords(self): med_coords = self.get_med_coords(int(self.samples)) if not med_coords: return None if self.transpose: raw_y, raw_x = med_coords else: raw_x, raw_y = med_coords if int(raw_x) != 0 and int(raw_y) != 0: x = ((int(raw_x) - int(self.cal_x0)) * int(self.screen_width)) // ( int(self.cal_x1) - int(self.cal_x0)) y = ((int(raw_y) - int(self.cal_y0)) * int( self.screen_height)) // (int(self.cal_y1) - int(self.cal_y0)) # print('(%d, %d) ==> (%d, %d)' % (raw_x, raw_y, x, y)) return x, y else: return None # @micropython.native def get_pressure(self, factor: int) -> int: z1, z2, x = self.xpt_cmds( [self.CMD_Z1_READ, self.CMD_Z2_READ, self.CMD_X_READ]) if int(z1) == 0: return -1 return ((int(x) * factor) / 4096) * (int(z2) / int(z1) - 1) start_time_ptr = esp.C_Pointer() end_time_ptr = esp.C_Pointer() cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 # @micropython.native def read(self, indev_drv, data) -> int: esp.get_ccount(self.start_time_ptr) coords = self.get_coords() esp.get_ccount(self.end_time_ptr) if self.end_time_ptr.int_val > self.start_time_ptr.int_val: self.touch_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val self.touch_count += 1 if coords: data.point.x, data.point.y = coords data.state = lv.INDEV_STATE.PR return False data.state = lv.INDEV_STATE.REL return False def stat(self): return self.touch_cycles / (self.touch_count * self.cycles_in_ms)
class ILI9XXX: display_name = "ili9XXX" display_type = 0 init_cmds = [] # Default values of "power" and "backlight" are reversed logic! 0 means ON. # You can change this by setting backlight_on and power_on arguments. def __init__( self, m5stack, miso=5, mosi=18, clk=19, cs=13, dc=12, backlight_on=0, power_on=0, spihost=esp.HSPI_HOST, mhz=40, factor=4, hybrid=True, width=320, height=240, colormode=COLOR_MODE_BGR, rot=LANDSCAPE, invert=False, double_buffer=True, half_duplex=True, display_type=0, debug=False, ): # Initializations self.debug = debug self.width = width self.height = height self.m5stack = m5stack self.miso = miso self.mosi = mosi self.clk = clk self.cs = cs self.dc = dc self.backlight_on = backlight_on self.power_on = power_on self.spihost = spihost self.mhz = mhz self.factor = factor self.hybrid = hybrid self.half_duplex = half_duplex self.buf_size = (self.width * self.height * lv.color_t.SIZE) // factor if invert: self.init_cmds.append({"cmd": 0x21}) self.init() # Register display driver self.buf1 = esp.heap_caps_malloc(self.buf_size, esp.MALLOC_CAP.DMA) self.buf2 = (esp.heap_caps_malloc(self.buf_size, esp.MALLOC_CAP.DMA) if double_buffer else None) if self.buf1 and self.buf2: if self.debug: print("Double buffer") elif self.buf1: if self.debug: print("Single buffer") else: raise RuntimeError( "Not enough DMA-able memory to allocate display buffer") self.disp_buf = lv.disp_buf_t() self.disp_drv = lv.disp_drv_t() self.disp_buf.init(self.buf1, self.buf2, self.buf_size // lv.color_t.SIZE) self.disp_drv.init() self.disp_drv.user_data = { "dc": self.dc, "spi": self.spi, "dt": self.display_type } self.disp_drv.buffer = self.disp_buf self.disp_drv.flush_cb = (esp.ili9xxx_flush if hybrid and hasattr(esp, "ili9xxx_flush") else self.flush) self.disp_drv.monitor_cb = self.monitor self.disp_drv.hor_res = self.width self.disp_drv.ver_res = self.height self.disp_drv.register() ###################################################### def disp_spi_init(self): # Register finalizer callback to deinit SPI. # This would get called on soft reset. self.finalizer = lvesp32.cb_finalizer(self.deinit) lvesp32.init() buscfg = esp.spi_bus_config_t({ "miso_io_num": self.miso, "mosi_io_num": self.mosi, "sclk_io_num": self.clk, "quadwp_io_num": -1, "quadhd_io_num": -1, "max_transfer_sz": self.buf_size, }) devcfg_flags = esp.SPI_DEVICE.NO_DUMMY if self.half_duplex: devcfg_flags |= esp.SPI_DEVICE.HALFDUPLEX devcfg = esp.spi_device_interface_config_t({ "clock_speed_hz": self.mhz * 1000 * 1000, # Clock out at DISP_SPI_MHZ MHz "mode": 0, # SPI mode 0 "spics_io_num": self.cs, # CS pin "queue_size": 2, "flags": devcfg_flags, "duty_cycle_pos": 128, }) if self.hybrid and hasattr(esp, "ili9xxx_post_cb_isr"): devcfg.pre_cb = None devcfg.post_cb = esp.ili9xxx_post_cb_isr else: devcfg.pre_cb = esp.spi_pre_cb_isr devcfg.post_cb = esp.spi_post_cb_isr esp.gpio_pad_select_gpio(self.cs) # Initialize the SPI bus, if needed. if buscfg.miso_io_num >= 0 and buscfg.mosi_io_num >= 0 and buscfg.sclk_io_num >= 0: esp.gpio_pad_select_gpio(self.miso) esp.gpio_pad_select_gpio(self.mosi) esp.gpio_pad_select_gpio(self.clk) esp.gpio_set_direction(self.miso, esp.GPIO_MODE.INPUT) esp.gpio_set_pull_mode(self.miso, esp.GPIO.PULLUP_ONLY) esp.gpio_set_direction(self.mosi, esp.GPIO_MODE.OUTPUT) esp.gpio_set_direction(self.clk, esp.GPIO_MODE.OUTPUT) ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) if ret != 0: raise RuntimeError("Failed initializing SPI bus") self.trans_buffer = esp.heap_caps_malloc(TRANS_BUFFER_LEN, esp.MALLOC_CAP.DMA) self.cmd_trans_data = self.trans_buffer.__dereference__(1) self.word_trans_data = self.trans_buffer.__dereference__(4) # Attach the LCD to the SPI bus ptr_to_spi = esp.C_Pointer() ret = esp.spi_bus_add_device(self.spihost, devcfg, ptr_to_spi) if ret != 0: raise RuntimeError("Failed adding SPI device") self.spi = ptr_to_spi.ptr_val self.bytes_transmitted = 0 completed_spi_transaction = esp.spi_transaction_t() cast_spi_transaction_instance = esp.spi_transaction_t.cast_instance def post_isr(arg): reported_transmitted = self.bytes_transmitted if reported_transmitted > 0: print("- Completed DMA of %d bytes (mem_free=0x%X)" % (reported_transmitted, gc.mem_free())) self.bytes_transmitted -= reported_transmitted # Called in ISR context! def flush_isr(spi_transaction_ptr): lv.disp_flush_ready(self.disp_drv) # esp.spi_device_release_bus(self.spi) esp.get_ccount(self.end_time_ptr) # cast_spi_transaction_instance(completed_spi_transaction, spi_transaction_ptr) # self.bytes_transmitted += completed_spi_transaction.length # try: # micropython.schedule(post_isr, None) # except RuntimeError: # pass self.spi_callbacks = esp.spi_transaction_set_cb(None, flush_isr) # # Deinitialize SPI device and bus, and free memory # This function is called from finilizer during gc sweep - therefore must not allocate memory! # trans_result_ptr = esp.C_Pointer() def deinit(self): if self.debug: print("Deinitializing {}..".format(self.display_name)) # Prevent callbacks to lvgl, which refer to the buffers we are about to delete lvesp32.deinit() if self.spi: # Pop all pending transaction results ret = 0 while ret == 0: ret = esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr, 1) # Remove device esp.spi_bus_remove_device(self.spi) self.spi = None # Free SPI bus esp.spi_bus_free(self.spihost) self.spihost = None # Free RAM if self.buf1: esp.heap_caps_free(self.buf1) self.buf1 = None if self.buf2: esp.heap_caps_free(self.buf2) self.buf2 = None if self.trans_buffer: esp.heap_caps_free(self.trans_buffer) self.trans_buffer = None ###################################################### trans = esp.spi_transaction_t() # .cast( # esp.heap_caps_malloc( # esp.spi_transaction_t.SIZE, esp.MALLOC_CAP.DMA)) def spi_send(self, data): # Length is in bytes, transaction length is in bits. self.trans.length = len(data) * 8 self.trans.tx_buffer = data # data should be allocated as DMA-able memory self.trans.user = None esp.spi_device_polling_transmit(self.spi, self.trans) def spi_send_dma(self, data): # Length is in bytes, transaction length is in bits. self.trans.length = len(data) * 8 self.trans.tx_buffer = data # data should be allocated as DMA-able memory self.trans.user = self.spi_callbacks esp.spi_device_queue_trans(self.spi, self.trans, -1) ###################################################### ###################################################### def send_cmd(self, cmd): esp.gpio_set_level(self.dc, 0) # Command mode self.cmd_trans_data[0] = cmd self.spi_send(self.cmd_trans_data) def send_data(self, data): esp.gpio_set_level(self.dc, 1) # Data mode if len(data) > TRANS_BUFFER_LEN: raise RuntimeError("Data too long, please use DMA!") trans_data = self.trans_buffer.__dereference__(len(data)) trans_data[:] = data[:] self.spi_send(trans_data) def send_trans_word(self): esp.gpio_set_level(self.dc, 1) # Data mode self.spi_send(self.word_trans_data) def send_data_dma(self, data): # data should be allocated as DMA-able memory esp.gpio_set_level(self.dc, 1) # Data mode self.spi_send_dma(data) ###################################################### def init(self): self.disp_spi_init() # Initialize non-SPI GPIOs esp.gpio_pad_select_gpio(self.dc) esp.gpio_set_direction(self.dc, esp.GPIO_MODE.OUTPUT) # Power the display # Gets powered on M5Stack initialization # Reset the display self.m5stack.lcd_rst(0) sleep_ms(100) self.m5stack.lcd_rst(1) sleep_ms(100) # Send all the commands for cmd in self.init_cmds: self.send_cmd(cmd["cmd"]) if "data" in cmd: self.send_data(cmd["data"]) if "delay" in cmd: sleep_ms(cmd["delay"]) if self.debug: print("{} initialization completed".format(self.display_name)) # Enable backlight self.m5stack.lcd_brightness(100) ###################################################### start_time_ptr = esp.C_Pointer() end_time_ptr = esp.C_Pointer() flush_acc_setup_cycles = 0 flush_acc_dma_cycles = 0 def flush(self, disp_drv, area, color_p): if self.end_time_ptr.int_val and self.end_time_ptr.int_val > self.start_time_ptr.int_val: self.flush_acc_dma_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val esp.get_ccount(self.start_time_ptr) # esp.spi_device_acquire_bus(self.spi, esp.ESP.MAX_DELAY) # Column addresses self.send_cmd(0x2A) self.word_trans_data[0] = (area.x1 >> 8) & 0xFF self.word_trans_data[1] = area.x1 & 0xFF self.word_trans_data[2] = (area.x2 >> 8) & 0xFF self.word_trans_data[3] = area.x2 & 0xFF self.send_trans_word() # Page addresses self.send_cmd(0x2B) self.word_trans_data[0] = (area.y1 >> 8) & 0xFF self.word_trans_data[1] = area.y1 & 0xFF self.word_trans_data[2] = (area.y2 >> 8) & 0xFF self.word_trans_data[3] = area.y2 & 0xFF self.send_trans_word() # Memory write by DMA, disp_flush_ready when finished self.send_cmd(0x2C) size = (area.x2 - area.x1 + 1) * (area.y2 - area.y1 + 1) data_view = color_p.__dereference__(size * lv.color_t.SIZE) esp.get_ccount(self.end_time_ptr) if self.end_time_ptr.int_val > self.start_time_ptr.int_val: self.flush_acc_setup_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val esp.get_ccount(self.start_time_ptr) self.send_data_dma(data_view) ###################################################### monitor_acc_time = 0 monitor_acc_px = 0 monitor_count = 0 cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 def monitor(self, disp_drv, time, px): self.monitor_acc_time += time self.monitor_acc_px += px self.monitor_count += 1 def stat(self): if self.monitor_count == 0: return None time = self.monitor_acc_time // self.monitor_count setup = self.flush_acc_setup_cycles // (self.monitor_count * self.cycles_in_ms) dma = self.flush_acc_dma_cycles // (self.monitor_count * self.cycles_in_ms) px = self.monitor_acc_px // self.monitor_count self.monitor_acc_time = 0 self.monitor_acc_px = 0 self.monitor_count = 0 self.flush_acc_setup_cycles = 0 self.flush_acc_dma_cycles = 0 return time, setup, dma, px
class ili9341: width = const(240) height = const(320) ###################################################### disp_buf = lv.disp_buf_t() disp_drv = lv.disp_drv_t() def __init__(self, miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, backlight=2, spihost=esp.enum.HSPI_HOST, mhz=40, factor=4, hybrid=True): # Make sure Micropython was built such that color won't require processing before DMA if lv.color_t.SIZE != 2: raise RuntimeError('ili9341 micropython driver requires defining LV_COLOR_DEPTH=16') if not hasattr(lv.color_t().ch, 'green_l'): raise RuntimeError('ili9341 micropython driver requires defining LV_COLOR_16_SWAP=1') # Initializations self.miso = miso self.mosi = mosi self.clk = clk self.cs = cs self.dc = dc self.rst = rst self.backlight = backlight self.spihost = spihost self.mhz = mhz self.factor = factor self.hybrid = hybrid self.buf_size = (self.width * self.height * lv.color_t.SIZE) // factor self.init() # Register display driver self.buf1 = esp.heap_caps_malloc(self.buf_size, esp.CAP.DMA) self.buf2 = esp.heap_caps_malloc(self.buf_size, esp.CAP.DMA) if self.buf1 and self.buf2: print("Double buffer") elif self.buf1: print("Single buffer") else: raise RuntimeError("Not enough DMA-able memory to allocate display buffer") lv.disp_buf_init(self.disp_buf, self.buf1, self.buf2, self.buf_size // lv.color_t.SIZE) lv.disp_drv_init(self.disp_drv) self.disp_drv.user_data = {'dc': self.dc, 'spi': self.spi} self.disp_drv.buffer = self.disp_buf self.disp_drv.flush_cb = esp.ili9341_flush if self.hybrid and hasattr(esp, 'ili9341_flush') else self.flush self.disp_drv.monitor_cb = self.monitor self.disp_drv.hor_res = self.width self.disp_drv.ver_res = self.height lv.disp_drv_register(self.disp_drv) ###################################################### init_cmds = [ {'cmd': 0xCF, 'data': bytes([0x00, 0x83, 0X30])}, {'cmd': 0xED, 'data': bytes([0x64, 0x03, 0X12, 0X81])}, {'cmd': 0xE8, 'data': bytes([0x85, 0x01, 0x79])}, {'cmd': 0xCB, 'data': bytes([0x39, 0x2C, 0x00, 0x34, 0x02])}, {'cmd': 0xF7, 'data': bytes([0x20])}, {'cmd': 0xEA, 'data': bytes([0x00, 0x00])}, {'cmd': 0xC0, 'data': bytes([0x26])}, # Power control {'cmd': 0xC1, 'data': bytes([0x11])}, # Power control {'cmd': 0xC5, 'data': bytes([0x35, 0x3E])}, # VCOM control {'cmd': 0xC7, 'data': bytes([0xBE])}, # VCOM control {'cmd': 0x36, 'data': bytes([0x48])}, # Memory Access Control {'cmd': 0x3A, 'data': bytes([0x55])}, # Pixel Format Set {'cmd': 0xB1, 'data': bytes([0x00, 0x1B])}, {'cmd': 0xF2, 'data': bytes([0x08])}, {'cmd': 0x26, 'data': bytes([0x01])}, {'cmd': 0xE0, 'data': bytes([0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00])}, {'cmd': 0XE1, 'data': bytes([0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F])}, {'cmd': 0x2A, 'data': bytes([0x00, 0x00, 0x00, 0xEF])}, {'cmd': 0x2B, 'data': bytes([0x00, 0x00, 0x01, 0x3f])}, {'cmd': 0x2C, 'data': bytes([0])}, {'cmd': 0xB7, 'data': bytes([0x07])}, {'cmd': 0xB6, 'data': bytes([0x0A, 0x82, 0x27, 0x00])}, {'cmd': 0x11, 'data': bytes([0]), 'delay':100}, {'cmd': 0x29, 'data': bytes([0]), 'delay':100}] ###################################################### def disp_spi_init(self): buscfg = esp.spi_bus_config_t({ "miso_io_num": self.miso, "mosi_io_num": self.mosi, "sclk_io_num": self.clk, "quadwp_io_num": -1, "quadhd_io_num": -1, "max_transfer_sz": self.buf_size, }) devcfg = esp.spi_device_interface_config_t({ "clock_speed_hz": self.mhz*1000*1000, # Clock out at DISP_SPI_MHZ MHz "mode": 0, # SPI mode 0 "spics_io_num": self.cs, # CS pin "queue_size": 2, "flags": esp.ESP.HALF_DUPLEX, "duty_cycle_pos": 128, }) if self.hybrid and hasattr(esp, 'ili9341_post_cb_isr'): devcfg.pre_cb = None devcfg.post_cb = esp.ili9341_post_cb_isr else: devcfg.pre_cb = esp.spi_pre_cb_isr devcfg.post_cb = esp.spi_post_cb_isr esp.gpio_pad_select_gpio(self.miso) esp.gpio_pad_select_gpio(self.mosi) esp.gpio_pad_select_gpio(self.clk) esp.gpio_set_direction(self.miso, esp.GPIO_MODE.INPUT) esp.gpio_set_pull_mode(self.miso, esp.GPIO.PULLUP_ONLY) esp.gpio_set_direction(self.mosi, esp.GPIO_MODE.OUTPUT) esp.gpio_set_direction(self.clk, esp.GPIO_MODE.OUTPUT) esp.gpio_pad_select_gpio(self.cs) # Initialize the SPI bus ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) if ret != 0: raise RuntimeError("Failed initializing SPI bus") # Attach the LCD to the SPI bus ptr_to_spi = esp.C_Pointer() ret = esp.spi_bus_add_device(self.spihost, devcfg, ptr_to_spi) if ret != 0: raise RuntimeError("Failed adding SPI device") self.spi = ptr_to_spi.ptr_val self.bytes_transmitted = 0 completed_spi_transaction = esp.spi_transaction_t() cast_spi_transaction_instance = esp.spi_transaction_t.cast_instance def post_isr(arg): reported_transmitted = self.bytes_transmitted if reported_transmitted > 0: print('- Completed DMA of %d bytes (mem_free=0x%X)' % (reported_transmitted , gc.mem_free())) self.bytes_transmitted -= reported_transmitted # Called in ISR context! def flush_isr(spi_transaction_ptr): lv.disp_flush_ready(self.disp_drv) # esp.spi_device_release_bus(self.spi) esp.get_ccount(self.end_time_ptr) # cast_spi_transaction_instance(completed_spi_transaction, spi_transaction_ptr) # self.bytes_transmitted += completed_spi_transaction.length # try: # micropython.schedule(post_isr, None) # except RuntimeError: # pass self.spi_callbacks = esp.spi_transaction_set_cb(None, flush_isr) ###################################################### trans = esp.spi_transaction_t() # .cast( # esp.heap_caps_malloc( # esp.spi_transaction_t.SIZE, esp.CAP.DMA)) def spi_send(self, data): self.trans.length = len(data) * 8 # Length is in bytes, transaction length is in bits. self.trans.tx_buffer = data # data should be allocated as DMA-able memory self.trans.user = None esp.spi_device_polling_transmit(self.spi, self.trans) def spi_send_dma(self, data): self.trans.length = len(data) * 8 # Length is in bytes, transaction length is in bits. self.trans.tx_buffer = data # data should be allocated as DMA-able memory self.trans.user = self.spi_callbacks esp.spi_device_queue_trans(self.spi, self.trans, -1) ###################################################### ###################################################### trans_buffer_len = const(16) trans_buffer = esp.heap_caps_malloc(trans_buffer_len, esp.CAP.DMA) cmd_trans_data = trans_buffer.__dereference__(1) word_trans_data = trans_buffer.__dereference__(4) def send_cmd(self, cmd): esp.gpio_set_level(self.dc, 0) # Command mode self.cmd_trans_data[0] = cmd self.spi_send(self.cmd_trans_data) def send_data(self, data): esp.gpio_set_level(self.dc, 1) # Data mode if len(data) > self.trans_buffer_len: raise RuntimeError('Data too long, please use DMA!') trans_data = self.trans_buffer.__dereference__(len(data)) trans_data[:] = data[:] self.spi_send(trans_data) def send_trans_word(self): esp.gpio_set_level(self.dc, 1) # Data mode self.spi_send(self.word_trans_data) def send_data_dma(self, data): # data should be allocated as DMA-able memory esp.gpio_set_level(self.dc, 1) # Data mode self.spi_send_dma(data) ###################################################### def init(self): self.disp_spi_init() esp.gpio_pad_select_gpio(self.dc) # Initialize non-SPI GPIOs esp.gpio_set_direction(self.dc, esp.GPIO_MODE.OUTPUT) esp.gpio_set_direction(self.rst, esp.GPIO_MODE.OUTPUT) if self.backlight != -1: esp.gpio_set_direction(self.backlight, esp.GPIO_MODE.OUTPUT) # Reset the display esp.gpio_set_level(self.rst, 0) esp.task_delay_ms(100) esp.gpio_set_level(self.rst, 1) esp.task_delay_ms(100) # Send all the commands for cmd in self.init_cmds: self.send_cmd(cmd['cmd']) self.send_data(cmd['data']) if 'delay' in cmd: esp.task_delay_ms(cmd['delay']) print("ILI9341 initialization completed") # Enable backlight if self.backlight != -1: print("Enable backlight") esp.gpio_set_level(self.backlight, 1) ###################################################### start_time_ptr = esp.C_Pointer() end_time_ptr = esp.C_Pointer() flush_acc_setup_cycles = 0 flush_acc_dma_cycles = 0 def flush(self, disp_drv, area, color_p): if self.end_time_ptr.int_val and self.end_time_ptr.int_val > self.start_time_ptr.int_val: self.flush_acc_dma_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val esp.get_ccount(self.start_time_ptr) # esp.spi_device_acquire_bus(self.spi, esp.ESP.MAX_DELAY) # Column addresses self.send_cmd(0x2A); self.word_trans_data[0] = (area.x1 >> 8) & 0xFF self.word_trans_data[1] = area.x1 & 0xFF self.word_trans_data[2] = (area.x2 >> 8) & 0xFF self.word_trans_data[3] = area.x2 & 0xFF self.send_trans_word() # Page addresses self.send_cmd(0x2B); self.word_trans_data[0] = (area.y1 >> 8) & 0xFF self.word_trans_data[1] = area.y1 & 0xFF self.word_trans_data[2] = (area.y2 >> 8) & 0xFF self.word_trans_data[3] = area.y2 & 0xFF self.send_trans_word() # Memory write by DMA, disp_flush_ready when finished self.send_cmd(0x2C) size = (area.x2 - area.x1 + 1) * (area.y2 - area.y1 + 1) data_view = color_p.__dereference__(size * lv.color_t.SIZE) esp.get_ccount(self.end_time_ptr) if self.end_time_ptr.int_val > self.start_time_ptr.int_val: self.flush_acc_setup_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val esp.get_ccount(self.start_time_ptr) self.send_data_dma(data_view) ###################################################### monitor_acc_time = 0 monitor_acc_px = 0 monitor_count = 0 cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 def monitor(self, disp_drv, time, px): self.monitor_acc_time += time self.monitor_acc_px += px self.monitor_count += 1 def stat(self): if self.monitor_count == 0: return None time = self.monitor_acc_time // self.monitor_count setup = self.flush_acc_setup_cycles // (self.monitor_count * self.cycles_in_ms) dma = self.flush_acc_dma_cycles // (self.monitor_count * self.cycles_in_ms) px = self.monitor_acc_px // self.monitor_count self.monitor_acc_time = 0 self.monitor_acc_px = 0 self.monitor_count = 0 self.flush_acc_setup_cycles = 0 self.flush_acc_dma_cycles = 0 return time, setup, dma, px
class xpt2046: CMDS = (CMD_X, CMD_Y, CMD_Z1, CMD_Z2) def __init__(self, miso=-1, mosi=-1, clk=-1, cs=25, spihost=esp.HSPI_HOST, mhz=5, transpose=False, touch_margin=100): # Initializations self.screen_width = lv.disp_get_hor_res(lv.disp_t.cast(None)) self.screen_height = lv.disp_get_ver_res(lv.disp_t.cast(None)) self.miso = miso self.mosi = mosi self.clk = clk self.cs = cs self.spihost = spihost self.mhz = mhz self.cal_x0 = None self.transpose = transpose self.touch_margin = touch_margin self.touch_count = 0 self.touch_cycles = 0 self.spi_init() # Prepare buffer with commands to send to device nc = len(self.CMDS) self.buflen = 2 * nc * REP + 1 # +1 for final CMD_OFF self.txbuf = esp.heap_caps_malloc(self.buflen, esp.MALLOC_CAP.DMA) txbuf = self.txbuf.__dereference__(self.buflen) for c in range(nc): for r in range(REP): txbuf[(c * REP + r) * 2 + 0] = self.CMDS[c] # first byte is command, second is 0 txbuf[(c * REP + r) * 2 + 1] = 0 txbuf[2 * nc * REP] = CMD_OFF # print("xpt2046 txbuf:", ["%02x" % v for v in txbuf]) self.rxbuf = esp.heap_caps_malloc(self.buflen, esp.MALLOC_CAP.DMA) indev_drv = lv.indev_drv_t() lv.indev_drv_init(indev_drv) indev_drv.type = lv.INDEV_TYPE.POINTER indev_drv.read_cb = self.read lv.indev_drv_register(indev_drv) print("xpt2046 touch initialized") def calibrate(self, x0, y0, x1, y1): self.cal_x0 = x0 self.cal_y0 = y0 self.cal_x1 = x1 self.cal_y1 = y1 def spi_init(self): esp.gpio_pad_select_gpio(self.cs) # Initialize the SPI bus, if needed if self.miso >= 0 and self.mosi >= 0 and self.clk >= 0: esp.gpio_pad_select_gpio(self.miso) esp.gpio_pad_select_gpio(self.mosi) esp.gpio_pad_select_gpio(self.clk) esp.gpio_set_direction(self.miso, esp.GPIO_MODE.INPUT) esp.gpio_set_pull_mode(self.miso, esp.GPIO.PULLUP_ONLY) esp.gpio_set_direction(self.mosi, esp.GPIO_MODE.OUTPUT) esp.gpio_set_direction(self.clk, esp.GPIO_MODE.OUTPUT) buscfg = esp.spi_bus_config_t({ "miso_io_num": self.miso, "mosi_io_num": self.mosi, "sclk_io_num": self.clk, "quadwp_io_num": -1, "quadhd_io_num": -1, "max_transfer_sz": 128, }) ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) if ret != 0: raise RuntimeError("Failed initializing SPI bus") # Register finalizer callback to deinit SPI. This gets called on soft reset. self.finalizer = lvesp32.cb_finalizer(self.deinit) # Attach the xpt2046 to the SPI bus devcfg = esp.spi_device_interface_config_t({ "clock_speed_hz": self.mhz * 1000 * 1000, "mode": 0, # SPI mode 0 "spics_io_num": self.cs, # CS pin "queue_size": 2, "duty_cycle_pos": 128, }) ptr_to_spi = esp.C_Pointer() ret = esp.spi_bus_add_device(self.spihost, devcfg, ptr_to_spi) if ret != 0: raise RuntimeError("Failed adding SPI device") self.spi = ptr_to_spi.ptr_val trans_result_ptr = esp.C_Pointer() # Deinitalize SPI device and bus def deinit(self): print("Deinitializing XPT2046...") if self.spi: # Pop all pending transaction results ret = 0 while ret == 0: ret = esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr, 1) # Remove device esp.spi_bus_remove_device(self.spi) # Free SPI bus esp.spi_bus_free(self.spihost) # do_cmds performs the commands in [cmds] in a single transaction. Each command is run in # 16 cycles overlapping the reading of one command with the writing of the next, per datasheet. # Each command is repeated rep times and the last two results are averaged. def do_cmds(self): trans = esp.spi_transaction_t({ "length": self.buflen * 8, "tx_buffer": self.txbuf, "rx_buffer": self.rxbuf }) rxbuf = self.rxbuf.__dereference__(self.buflen) res = esp.spi_device_polling_transmit(self.spi, trans) # print("got:", res, ["%02x"%v for v in rxbuf]) vals = [(rxbuf[1 + 2 * i] << 5) | (rxbuf[2 + 2 * i] >> 3) for i in range(self.buflen // 2)] # print("res:", res, "vals:", vals) res = [(vals[c * REP + REP - 2] + vals[c * REP + REP - 1]) // 2 for c in range(len(self.CMDS))] return res # update reads the device and scales the result according to the calibration def update(self): raw_x, raw_y, z1, z2 = self.do_cmds() if z1 < self.touch_margin or z2 > 4192 - self.touch_margin: return None # print("X:", raw_x, "Y:", raw_y, "Z1:", z1, "Z2:", z2) if self.transpose: raw_y, raw_x = (raw_x, raw_y) if self.cal_x0 is None: return raw_x, raw_y # scale X to screen dimensions x = (raw_x - self.cal_x0) * self.screen_width // (self.cal_x1 - self.cal_x0) if x < 0: x = 0 elif x >= self.screen_width: x = self.screen_width - 1 # scale Y to screen dimensions y = (raw_y - self.cal_y0) * self.screen_height // (self.cal_y1 - self.cal_y0) if y < 0: y = 0 elif y >= self.screen_height: y = self.screen_height - 1 return x, y start_time_ptr = esp.C_Pointer() end_time_ptr = esp.C_Pointer() cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 def read(self, indev_drv, data) -> int: esp.get_ccount(self.start_time_ptr) coords = self.update() esp.get_ccount(self.end_time_ptr) if self.end_time_ptr.int_val > self.start_time_ptr.int_val: self.touch_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val self.touch_count += 1 # print("touch", coords) if coords: data.point.x, data.point.y = coords data.state = lv.INDEV_STATE.PR return False data.state = lv.INDEV_STATE.REL return False def stat(self): return self.touch_cycles / (self.touch_count * self.cycles_in_ms)
class ili9XXX: TRANS_BUFFER_LEN = const(16) display_name = 'ili9XXX' init_cmds = [] # Default values of "power" and "backlight" are reversed logic! 0 means ON. # You can change this by setting backlight_on and power_on arguments. def __init__(self, m5stack=M5Stack(), miso=5, mosi=18, clk=19, cs=13, dc=12, backlight=100, backlight_on=1, power_on=1, spihost=esp.HSPI_HOST, mhz=40, factor=4, hybrid=True, width=240, height=320, start_x=0, start_y=0, invert=False, double_buffer=True, half_duplex=True, display_type=0, asynchronous=False, initialize=True): # Initializations if not lv.is_initialized(): lv.init() self.asynchronous = asynchronous self.initialize = initialize self.m5stack = m5stack self.width = width self.height = height self.start_x = start_x self.start_y = start_y self.miso = miso self.mosi = mosi self.clk = clk self.cs = cs self.dc = dc self.backlight = backlight self.backlight_on = backlight_on self.power_on = power_on self.spihost = spihost self.mhz = mhz self.factor = factor self.hybrid = hybrid self.half_duplex = half_duplex self.display_type = display_type self.buf_size = (self.width * self.height * lv.color_t.__SIZE__) // factor if invert: self.init_cmds.append({'cmd': 0x21}) # Register display driver self.buf1 = esp.heap_caps_malloc(self.buf_size, esp.MALLOC_CAP.DMA) self.buf2 = esp.heap_caps_malloc( self.buf_size, esp.MALLOC_CAP.DMA) 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 DMA-able memory to allocate display buffer") self.disp_buf = lv.disp_draw_buf_t() self.disp_drv = lv.disp_drv_t() self.disp_buf.init(self.buf1, self.buf2, self.buf_size // lv.color_t.__SIZE__) self.disp_drv.init() self.disp_spi_init() self.disp_drv.user_data = { 'dc': self.dc, 'spi': self.spi, 'dt': self.display_type, 'start_x': self.start_x, 'start_y': self.start_y } self.disp_drv.draw_buf = self.disp_buf self.disp_drv.flush_cb = esp.ili9xxx_flush if hybrid and hasattr( esp, 'ili9xxx_flush') else self.flush self.disp_drv.monitor_cb = self.monitor self.disp_drv.hor_res = self.width self.disp_drv.ver_res = self.height if self.initialize: self.init() if not lv_utils.event_loop.is_running(): self.event_loop = lv_utils.event_loop( asynchronous=self.asynchronous) ###################################################### def disp_spi_init(self): # TODO: Register finalizer callback to deinit SPI. # That would get called on soft reset. buscfg = esp.spi_bus_config_t({ "miso_io_num": self.miso, "mosi_io_num": self.mosi, "sclk_io_num": self.clk, "quadwp_io_num": -1, "quadhd_io_num": -1, "max_transfer_sz": self.buf_size, }) devcfg_flags = esp.SPI_DEVICE.NO_DUMMY if self.half_duplex: devcfg_flags |= esp.SPI_DEVICE.HALFDUPLEX devcfg = esp.spi_device_interface_config_t({ "clock_speed_hz": self.mhz * 1000 * 1000, # Clock out at DISP_SPI_MHZ MHz "mode": 0, # SPI mode 0 "spics_io_num": self.cs, # CS pin "queue_size": 2, "flags": devcfg_flags, "duty_cycle_pos": 128, }) if self.hybrid and hasattr(esp, 'ili9xxx_post_cb_isr'): devcfg.pre_cb = None devcfg.post_cb = esp.ili9xxx_post_cb_isr else: devcfg.pre_cb = esp.ex_spi_pre_cb_isr devcfg.post_cb = esp.ex_spi_post_cb_isr esp.gpio_pad_select_gpio(self.cs) # Initialize the SPI bus, if needed. if buscfg.mosi_io_num >= 0 and \ buscfg.sclk_io_num >= 0: if buscfg.miso_io_num >= 0: esp.gpio_pad_select_gpio(self.miso) esp.gpio_set_direction(self.miso, esp.GPIO_MODE.INPUT) esp.gpio_set_pull_mode(self.miso, esp.GPIO.PULLUP_ONLY) esp.gpio_pad_select_gpio(self.mosi) esp.gpio_pad_select_gpio(self.clk) esp.gpio_set_direction(self.mosi, esp.GPIO_MODE.OUTPUT) esp.gpio_set_direction(self.clk, esp.GPIO_MODE.OUTPUT) ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) if ret != 0: raise RuntimeError("Failed initializing SPI bus") self.trans_buffer = esp.heap_caps_malloc(TRANS_BUFFER_LEN, esp.MALLOC_CAP.DMA) self.cmd_trans_data = self.trans_buffer.__dereference__(1) self.word_trans_data = self.trans_buffer.__dereference__(4) # Attach the LCD to the SPI bus ptr_to_spi = esp.C_Pointer() ret = esp.spi_bus_add_device(self.spihost, devcfg, ptr_to_spi) if ret != 0: raise RuntimeError("Failed adding SPI device") self.spi = ptr_to_spi.ptr_val self.bytes_transmitted = 0 completed_spi_transaction = esp.spi_transaction_t() cast_spi_transaction_instance = esp.spi_transaction_t.__cast_instance__ def post_isr(arg): reported_transmitted = self.bytes_transmitted if reported_transmitted > 0: print('- Completed DMA of %d bytes (mem_free=0x%X)' % (reported_transmitted, gc.mem_free())) self.bytes_transmitted -= reported_transmitted # Called in ISR context! def flush_isr(spi_transaction_ptr): self.disp_drv.flush_ready() # esp.spi_device_release_bus(self.spi) esp.get_ccount(self.end_time_ptr) # cast_spi_transaction_instance(completed_spi_transaction, spi_transaction_ptr) # self.bytes_transmitted += completed_spi_transaction.length # try: # micropython.schedule(post_isr, None) # except RuntimeError: # pass self.spi_callbacks = esp.spi_transaction_set_cb(None, flush_isr) # # Deinitialize SPI device and bus, and free memory # This function is called from finilizer during gc sweep - therefore must not allocate memory! # trans_result_ptr = esp.C_Pointer() def deinit(self): print('Deinitializing {}..'.format(self.display_name)) # Prevent callbacks to lvgl, which refer to the buffers we are about to delete if lv_utils.event_loop.is_running(): self.event_loop.deinit() self.disp_drv.remove() if self.spi: # Pop all pending transaction results ret = 0 while ret == 0: ret = esp.spi_device_get_trans_result(self.spi, self.trans_result_ptr, 1) # Remove device esp.spi_bus_remove_device(self.spi) self.spi = None # Free SPI bus esp.spi_bus_free(self.spihost) self.spihost = None # Free RAM if self.buf1: esp.heap_caps_free(self.buf1) self.buf1 = None if self.buf2: esp.heap_caps_free(self.buf2) self.buf2 = None if self.trans_buffer: esp.heap_caps_free(self.trans_buffer) self.trans_buffer = None ###################################################### trans = esp.spi_transaction_t() # .__cast__( # esp.heap_caps_malloc( # esp.spi_transaction_t.__SIZE__, esp.MALLOC_CAP.DMA)) def spi_send(self, data): self.trans.length = len( data) * 8 # Length is in bytes, transaction length is in bits. self.trans.tx_buffer = data # data should be allocated as DMA-able memory self.trans.user = None esp.spi_device_polling_transmit(self.spi, self.trans) def spi_send_dma(self, data): self.trans.length = len( data) * 8 # Length is in bytes, transaction length is in bits. self.trans.tx_buffer = data # data should be allocated as DMA-able memory self.trans.user = self.spi_callbacks esp.spi_device_queue_trans(self.spi, self.trans, -1) ###################################################### ###################################################### def send_cmd(self, cmd): esp.gpio_set_level(self.dc, 0) # Command mode self.cmd_trans_data[0] = cmd self.spi_send(self.cmd_trans_data) def send_data(self, data): esp.gpio_set_level(self.dc, 1) # Data mode if len(data) > TRANS_BUFFER_LEN: raise RuntimeError('Data too long, please use DMA!') trans_data = self.trans_buffer.__dereference__(len(data)) trans_data[:] = data[:] self.spi_send(trans_data) def send_trans_word(self): esp.gpio_set_level(self.dc, 1) # Data mode self.spi_send(self.word_trans_data) def send_data_dma(self, data): # data should be allocated as DMA-able memory esp.gpio_set_level(self.dc, 1) # Data mode self.spi_send_dma(data) ###################################################### async def _init(self, sleep_func): # Initialize non-SPI GPIOs esp.gpio_pad_select_gpio(self.dc) esp.gpio_set_direction(self.dc, esp.GPIO_MODE.OUTPUT) # Power the display # Reset the display self.m5stack.lcd_rst(0) await sleep_func(100) self.m5stack.lcd_rst(1) await sleep_func(100) # Send all the commands for cmd in self.init_cmds: self.send_cmd(cmd['cmd']) if 'data' in cmd: self.send_data(cmd['data']) if 'delay' in cmd: await sleep_func(cmd['delay']) print("{} initialization completed".format(self.display_name)) # Enable backlight if self.backlight != -1: print("Enable backlight") self.m5stack.lcd_backlight(self.backlight_on) self.m5stack.lcd_brightness(self.backlight) # Register the driver self.disp_drv.register() def init(self): import utime generator = self._init(lambda ms: (yield ms)) try: while True: ms = next(generator) utime.sleep_ms(ms) except StopIteration: pass async def init_async(self): import uasyncio await self._init(uasyncio.sleep_ms) # def power_down(self): ###################################################### start_time_ptr = esp.C_Pointer() end_time_ptr = esp.C_Pointer() flush_acc_setup_cycles = 0 flush_acc_dma_cycles = 0 def flush(self, disp_drv, area, color_p): if self.end_time_ptr.int_val and self.end_time_ptr.int_val > self.start_time_ptr.int_val: self.flush_acc_dma_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val esp.get_ccount(self.start_time_ptr) # esp.spi_device_acquire_bus(self.spi, esp.ESP.MAX_DELAY) # Column addresses self.send_cmd(0x2A) x1 = area.x1 + self.start_x x2 = area.x2 + self.start_x self.word_trans_data[0] = (x1 >> 8) & 0xFF self.word_trans_data[1] = x1 & 0xFF self.word_trans_data[2] = (x2 >> 8) & 0xFF self.word_trans_data[3] = x2 & 0xFF self.send_trans_word() # Page addresses self.send_cmd(0x2B) y1 = area.y1 + self.start_y y2 = area.y2 + self.start_y self.word_trans_data[0] = (y1 >> 8) & 0xFF self.word_trans_data[1] = y1 & 0xFF self.word_trans_data[2] = (y2 >> 8) & 0xFF self.word_trans_data[3] = y2 & 0xFF self.send_trans_word() # Memory write by DMA, disp_flush_ready when finished self.send_cmd(0x2C) size = (x2 - x1 + 1) * (y2 - y1 + 1) data_view = color_p.__dereference__(size * lv.color_t.__SIZE__) esp.get_ccount(self.end_time_ptr) if self.end_time_ptr.int_val > self.start_time_ptr.int_val: self.flush_acc_setup_cycles += self.end_time_ptr.int_val - self.start_time_ptr.int_val esp.get_ccount(self.start_time_ptr) self.send_data_dma(data_view) ###################################################### monitor_acc_time = 0 monitor_acc_px = 0 monitor_count = 0 cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 def monitor(self, disp_drv, time, px): self.monitor_acc_time += time self.monitor_acc_px += px self.monitor_count += 1 def stat(self): if self.monitor_count == 0: return None time = self.monitor_acc_time // self.monitor_count setup = self.flush_acc_setup_cycles // (self.monitor_count * self.cycles_in_ms) dma = self.flush_acc_dma_cycles // (self.monitor_count * self.cycles_in_ms) px = self.monitor_acc_px // self.monitor_count self.monitor_acc_time = 0 self.monitor_acc_px = 0 self.monitor_count = 0 self.flush_acc_setup_cycles = 0 self.flush_acc_dma_cycles = 0 return time, setup, dma, px def madctl(self, colormode, rotation, rotations): # if rotation is 0 or positive use the value as is. if rotation >= 0: return rotation | colormode # otherwise use abs(rotation)-1 as index to retreive value from rotations set index = abs(rotation) - 1 if index > len(rotations): RuntimeError('Invalid display rot value specified during init.') return rotations[index] | colormode