Example #1
0
 def getCount(self):
     cnt_ptr = espidf.C_Pointer()
     espidf.pcnt_get_counter_value(self._unit, cnt_ptr)
     cnt = cnt_ptr.int_val
     if cnt > MAXCOUNT or cnt < -MAXCOUNT:
         self.clearCount()
     return cnt
    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": 128 * 1024,
        })

        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":
            1,
            "pre_cb":
            None,
            "post_cb":
            None,
            "flags":
            1 << 4,  # SPI_DEVICE_HALFDUPLEX
            "duty_cycle_pos":
            128,
        })

        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
Example #3
0
    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
Example #4
0
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
Example #5
0
    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)
Example #6
0
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)
Example #7
0
    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)
        ]
Example #8
0
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
    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": 128 * 1024,
        })

        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,
            "pre_cb":
            esp.spi_pre_cb_isr,
            "post_cb":
            esp.spi_post_cb_isr,
            "flags":
            1 << 4,  # SPI_DEVICE_HALFDUPLEX
            "duty_cycle_pos":
            128,
        })

        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)
            # 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)
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
Example #11
0
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)
Example #12
0
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