ESP32S3/MicroPython display bmp on ili9341 LCD

Last post introduced setup and basic of ESP32-S3-DevKitC-1 running MicroPython v1.24.1 using 3.2" 320x240 IPS LCD (ILI9341 SPI) with Cap. Touch (FT6336U) and Micro SD Slot. This post further exercise to load 240x240 RGB888/RGB565 bmp from device/SD, display on ILI9341 LCD.




Remark for ESP32-S3-DevKitC-1:

For ESP32-S3-DevKitC-1 with Octal SPI flash/PSRAM memory, use the "spiram-oct" variant such as ESP32_GENERIC_S3-SPIRAM_OCT-20241129-v1.24.1.bin (ref: https://micropython.org/download/ESP32_GENERIC_S3/). If use a in-correct firmware such as ESP32_GENERIC_S3-20241129-v1.24.1.bin, "MemoryError: memory allocation failed" will be raised.

For boards with Octal SPI flash/PSRAM memory embedded ESP32-S3-WROOM-1/1U modules, and boards with ESP32-S3-WROOM-2 modules, the pins GPIO35, GPIO36 and GPIO37 are used for the internal communication between ESP32-S3 and SPI flash/PSRAM memory, thus not available for external use.
(ref: https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-devkitc-1/user_guide_v1.0.html)

Connection and library setup, read the last post, ILI9341/FT6336U/SD on ESP32S3/MicroPython.


All test images were generated using "Image Creatoe in Bing". Converted to 240x240 RGB888/RGB565 bmp using Python/GIMP. To resize and convert jpg to bmp, read "Resize jpg and convert to bmp in RGB888 and RGB565 mode, using Python/GIMP".

Exercise Code:

mpy_s3_ili9341_image.py
"""
ESP32-S3-DevKitC-1 running MicroPython v1.24.1
3.2" 320x240 IPS LCD (ILI9341 SPI) with Cap. Touch (FT6336) and Micro SD Slot
Display 240x240 RGB888/RGB565 bmp on ILI9341 LCD

For ESP32-S3-DevKitC-1 has Octal SPIRAM,
makesure using MicroPython firmware of "spiram-oct" variant,
such as ESP32_GENERIC_S3-SPIRAM_OCT-20241129-v1.24.1.bin.

Libraries needed, install to /lib:
- ili9341.py
- sdcard (remark: NOT machine.SDCard)

ili9341.py download from:
rdagger/micropython-ili9341
https://github.com/rdagger/micropython-ili9341

micropython-lib/sdcard.py can be download from
https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/storage/sdcard/sdcard.py
or using Thonny > Tools > Manager Package...

All test images were generated using "Image Creator in Bing",
converted to 240x240 RGB888/RGB565 bmp using GIMP or Python.
To resize using GIMP/Python, read:
https://coxxect.blogspot.com/2024/11/resize-jpg-and-convert-to-bmp-in-rgb888.html
"""
import os, sys
from time import ticks_ms, ticks_diff
from machine import Pin, SPI
import sdcard
import gc

from ili9341 import Display, color565

print("====================================")
print(sys.implementation[0], os.uname()[3],
      "\nrun on", os.uname()[4])
print("====================================")

LCD_CS=14  # low to select
LCD_RST=13
LCD_RS=12
LCD_SDI=11 #MOSI
LCD_SCK=10
LCD_LED=9
LCD_SDO=8  #MISO

SD_CS=18   # low to select

# un-select both LCD and SD in power-up,
# to prevent un-used cs unstable and false selected.
pin_cs_lcd = Pin(LCD_CS, Pin.OUT)
pin_cs_lcd.value(1)
pin_cs_sd = Pin(SD_CS, Pin.OUT)
pin_cs_sd.value(1)

pin_backlight=Pin(LCD_LED, Pin.OUT)
pin_backlight.value(0)

# rot and rot_set
# are used to better manager rotation/width/height of display
rot=0   # 0 rotation = 0
        # 1 rotation = 90
        # 2 rotation = 180
        # 3 rotation = 270
# rot_set[x][0] : ili9341 Display rotation
# rot_set[x][1] : ili9341 Display width
# rot_set[x][2] : ili9341 Display height
rot_set = [[0, 240, 320],
           [90, 320, 240],
           [180, 240, 320],
           [270, 320, 240]]

# spi share by LCD and SD
spi = SPI(1, baudrate=40000000, sck=LCD_SCK, mosi=LCD_SDI, miso=LCD_SDO)

display = Display(spi,
                  rotation=rot_set[rot][0],
                  width=rot_set[rot][1], height=rot_set[rot][2],
                  dc=Pin(LCD_RS), cs=Pin(LCD_CS), rst=Pin(LCD_RST))
display.invert(enable=True)

# remark:
# It's sdcard.SDCard
# NOT machine.SDCard
sd = sdcard.SDCard(spi, Pin(SD_CS))

vfs = os.VfsFat(sd)
os.mount(vfs, "/sd")

pin_backlight.value(1)
print("Display:", display.width, "x", display.height)
display.clear()

# read SD and draw on display
def listImages(path):
    image_files = os.listdir(path)
    print(len(image_files), "files in", path)
    for f in image_files:
        print(f)

# Read bmp
def read_bmp(filename):
    print("Read:", filename)
    gc.collect()
    print(gc.mem_free())
    tick_ms_read_bmp_start = ticks_ms()
    with open(filename, 'rb') as f:
        bmp_data = f.read()
    tick_ms_read_bmp_end = ticks_ms()
    
    msg_read_bmp = "read bmp: " + str(ticks_diff(tick_ms_read_bmp_end, tick_ms_read_bmp_start)) + " ms"
    display.draw_text8x8(0, 290,
                         msg_read_bmp,
                         color565(255, 255, 255))
    print(msg_read_bmp)
    return bmp_data

def get_compression_method(num):
    switcher = {
        0: "BI_RGB",
        1: "BI_RLE8",
        2: "BI_RLE4",
        3: "BI_BITFIELDS",
        4: "BI_JPEG",
        5: "BI_PNG",
        6: "BI_ALPHABITFIELDS",
        11: "BI_CMYK",
        12: "BI_CMYKRLE8",
        13: "BI_CMYKRLE4",
        }
    
    return switcher.get(num, "unknown")

def display_bmp(src_path, src_file):
    bmp_file = src_path + '/' + src_file
    display.clear()
    display.draw_text8x8(0, 250, src_path, color565(255, 255, 255))
    display.draw_text8x8(0, 260, src_file, color565(255, 255, 255))

    bmp_data = read_bmp(bmp_file)
    print("bmp_data", type(bmp_data), len(bmp_data))
    
    offset = int.from_bytes(bmp_data[10:14], 'little')
    image_width = int.from_bytes(bmp_data[18:22], 'little')
    image_height = int.from_bytes(bmp_data[22:26], 'little')
    num_of_bits_per_pixel = int.from_bytes(bmp_data[28:30], 'little')
    compression_method = int.from_bytes(bmp_data[30:34], 'little')
    
    print("=============================================")
    print("What concerned are:")
    print("Signature:",chr(bmp_data[0]), chr(bmp_data[1]))
    print("offset:", offset)
    print("image_width:", image_width)
    print("image_height:", image_height)
    print("num_of_bits_per_pixel:", num_of_bits_per_pixel)
    print("compression_method:", compression_method, get_compression_method(compression_method))

    print("=============================================")


    #==========================
    # Verify the bmp imag
    if chr(bmp_data[0]) != 'B' or chr(bmp_data[1]) != 'M':
        print("Must be bmp")
    elif image_width != IMG_WIDTH or image_height != IMG_HEIGHT:
        print("Must be 240 x 240")
    else:
        if num_of_bits_per_pixel == 24:
            print("Assume " + bmp_file + " is 240x240 RGB888 bmp, continuous.")
        
            buffer = bytearray(IMG_WIDTH*IMG_HEIGHT*2)  # for RGB565, each pixel two bytes

            byte_per_pixel_src = 3
            byte_per_pixel_buffer = 2
            byte_per_line_src = IMG_WIDTH*byte_per_pixel_src
            byte_per_line_buffer = IMG_WIDTH*byte_per_pixel_buffer
        
            tick_ms_formbuffer_start = ticks_ms()
            for y in range(IMG_HEIGHT):
                src_y_offset = (y*byte_per_line_src) + offset
            
                # Because bmp start from bottom-left,
                # so we have to invert the y order (IMG_HEIGHT-1-y).
                buffer_y_offset_0 = (IMG_HEIGHT-1-y)*byte_per_line_buffer
                buffer_y_offset_1 = (IMG_HEIGHT-1-y)*byte_per_line_buffer + 1

                for x in range(IMG_WIDTH):
                    i = (x*byte_per_pixel_src) + src_y_offset
                
                    r_565 = (bmp_data[i+2] >> 3) & 0b00011111
                    g_565 = (bmp_data[i+1] >> 2) & 0b00111111
                    b_565 = (bmp_data[i] >> 3)   & 0b00011111
                    rgb565 = (r_565 << 11) | (g_565 << 5) | b_565
                
                    buffer[x*2 + buffer_y_offset_0] = (rgb565 >> 8) & 0xFF
                    buffer[x*2 + buffer_y_offset_1] = rgb565 & 0xFF

            tick_ms_formbuffer_end = ticks_ms()
            display.block(0, 0, 239, 239, buffer)
            tick_ms_block_end = ticks_ms()
        
            msg_formbuffer = "form buffer:     " +\
                             str(ticks_diff(tick_ms_formbuffer_end, tick_ms_formbuffer_start)) +\
                             " ms"
            msg_displayblock = "display.block(): " +\
                             str(ticks_diff(tick_ms_block_end, tick_ms_formbuffer_end)) +\
                             " ms"
            print(msg_formbuffer)
            print(msg_displayblock)
            display.draw_text8x8(0, 300,
                         msg_formbuffer,
                         color565(255, 255, 255))
            display.draw_text8x8(0, 310,
                         msg_displayblock,
                         color565(255, 255, 255))

        elif num_of_bits_per_pixel == 16:
            print("Assume " + bmp_file + " is 240x240 RGB565 bmp, continuous.")

            buffer = bytearray(IMG_WIDTH*IMG_HEIGHT*2)  # for RGB565, each pixel two bytes
        
            byte_per_pixel_src = 2
            byte_per_pixel_buffer = 2
            byte_per_line_src = IMG_WIDTH*byte_per_pixel_src
            byte_per_line_buffer = IMG_WIDTH*byte_per_pixel_buffer

            tick_ms_formbuffer_start = ticks_ms()
            for y in range(IMG_HEIGHT):
            
                src_y_offset_0 = (y*byte_per_line_src) + offset
                src_y_offset_1 = (y*byte_per_line_src) + offset + 1
                # Because bmp start from bottom-left,
                # so we have to invert the y order (IMG_HEIGHT-1-y).
                buffer_y_offset_0 = (IMG_HEIGHT-1-y)*byte_per_line_buffer
                buffer_y_offset_1 = (IMG_HEIGHT-1-y)*byte_per_line_buffer + 1
            
                for x in range(IMG_WIDTH):
                    #notice: HIGH/LOW byte reversed
                    buffer[x*2 + buffer_y_offset_0] = bmp_data[x*2 + src_y_offset_1]
                    buffer[x*2 + buffer_y_offset_1] = bmp_data[x*2 + src_y_offset_0]
                
            tick_ms_formbuffer_end = ticks_ms()
            display.block(0, 0, 239, 239, buffer)
            tick_ms_block_end = ticks_ms()
        
            msg_formbuffer = "form buffer:     " +\
                             str(ticks_diff(tick_ms_formbuffer_end, tick_ms_formbuffer_start)) +\
                             " ms"
            msg_displayblock = "display.block(): " +\
                             str(ticks_diff(tick_ms_block_end, tick_ms_formbuffer_end)) +\
                             " ms"
            print(msg_formbuffer)
            print(msg_displayblock)
            display.draw_text8x8(0, 300,
                         msg_formbuffer,
                         color565(255, 255, 255))
            display.draw_text8x8(0, 310,
                         msg_displayblock,
                         color565(255, 255, 255))
                
        else:
            print("Must be 16 or 24 bit per pixel")
#==========================
img_path = '/sd/images'   #load from SD
#img_path = '/images'      #load from MicroPython device
#img_file = 'img_001_gimp_240_rgb888.bmp'
#img_file = 'img_002_gimp_240_rgb565.bmp'
img_file = 'img_003_240x240_rgb888.bmp'
#img_file = 'img_004_240x240_rgb565.bmp'
IMG_WIDTH = 240
IMG_HEIGHT = 240

listImages(img_path)
print()

display_bmp(img_path, img_file)

print("~ bye ~")


mpy_s3_ili9341_images_loop.py
"""
ESP32-S3-DevKitC-1 running MicroPython v1.24.1
3.2" 320x240 IPS LCD (ILI9341 SPI) with Cap. Touch (FT6336) and Micro SD Slot
Loop to display 240x240 RGB888/RGB565 bmp images on ILI9341 LCD

For ESP32-S3-DevKitC-1 has Octal SPIRAM,
makesure using MicroPython firmware of "spiram-oct" variant,
such as ESP32_GENERIC_S3-SPIRAM_OCT-20241129-v1.24.1.bin.

Libraries needed, install to /lib:
- ili9341.py
- sdcard (remark: NOT machine.SDCard)

ili9341.py download from:
rdagger/micropython-ili9341
https://github.com/rdagger/micropython-ili9341

micropython-lib/sdcard.py can be download from
https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/storage/sdcard/sdcard.py
or using Thonny > Tools > Manager Package...

All test images were generated using "Image Creator in Bing",
converted to 240x240 RGB888/RGB565 bmp using GIMP or Python.
To resize using GIMP/Python, read:
https://coxxect.blogspot.com/2024/11/resize-jpg-and-convert-to-bmp-in-rgb888.html
"""
import os, sys
from time import sleep, ticks_ms, ticks_diff
from machine import Pin, SPI
import sdcard
import gc

from ili9341 import Display, color565

print("====================================")
print(sys.implementation[0], os.uname()[3],
      "\nrun on", os.uname()[4])
print("====================================")

LCD_CS=14  # low to select
LCD_RST=13
LCD_RS=12
LCD_SDI=11 #MOSI
LCD_SCK=10
LCD_LED=9
LCD_SDO=8  #MISO

SD_CS=18   # low to select

# un-select both LCD and SD in power-up,
# to prevent un-used cs unstable and false selected.
pin_cs_lcd = Pin(LCD_CS, Pin.OUT)
pin_cs_lcd.value(1)
pin_cs_sd = Pin(SD_CS, Pin.OUT)
pin_cs_sd.value(1)

pin_backlight=Pin(LCD_LED, Pin.OUT)
pin_backlight.value(0)

# rot and rot_set
# are used to better manager rotation/width/height of display
rot=0   # 0 rotation = 0
        # 1 rotation = 90
        # 2 rotation = 180
        # 3 rotation = 270
# rot_set[x][0] : ili9341 Display rotation
# rot_set[x][1] : ili9341 Display width
# rot_set[x][2] : ili9341 Display height
rot_set = [[0, 240, 320],
           [90, 320, 240],
           [180, 240, 320],
           [270, 320, 240]]

# spi share by LCD and SD
spi = SPI(1, baudrate=40000000, sck=LCD_SCK, mosi=LCD_SDI, miso=LCD_SDO)

display = Display(spi,
                  rotation=rot_set[rot][0],
                  width=rot_set[rot][1], height=rot_set[rot][2],
                  dc=Pin(LCD_RS), cs=Pin(LCD_CS), rst=Pin(LCD_RST))
display.invert(enable=True)

# remark:
# It's sdcard.SDCard
# NOT machine.SDCard
sd = sdcard.SDCard(spi, Pin(SD_CS))

vfs = os.VfsFat(sd)
os.mount(vfs, "/sd")

pin_backlight.value(1)
print("Display:", display.width, "x", display.height)
display.clear()

# Read bmp
def read_bmp(filename):
    print("Read:", filename)
    gc.collect()
    print(gc.mem_free())
    tick_ms_read_bmp_start = ticks_ms()
    with open(filename, 'rb') as f:
        bmp_data = f.read()
    tick_ms_read_bmp_end = ticks_ms()
    
    msg_read_bmp = "read bmp: " + str(ticks_diff(tick_ms_read_bmp_end, tick_ms_read_bmp_start)) + " ms"
    display.draw_text8x8(0, 290,
                         msg_read_bmp,
                         color565(255, 255, 255))
    print(msg_read_bmp)
    return bmp_data

def display_bmp(src_path, src_file):
    bmp_file = src_path + '/' + src_file
    display.clear()
    display.draw_text8x8(0, 250, src_path+'/', color565(255, 255, 255))
    display.draw_text8x8(0, 260, src_file, color565(255, 255, 255))
    bmp_data = read_bmp(bmp_file)
    print("bmp_data", type(bmp_data), len(bmp_data))
    
    offset = int.from_bytes(bmp_data[10:14], 'little')
    image_width = int.from_bytes(bmp_data[18:22], 'little')
    image_height = int.from_bytes(bmp_data[22:26], 'little')
    num_of_bits_per_pixel = int.from_bytes(bmp_data[28:30], 'little')

    #==========================
    # Verify the bmp imag
    if chr(bmp_data[0]) != 'B' or chr(bmp_data[1]) != 'M':
        print("Must be bmp")
    elif image_width != IMG_WIDTH or image_height != IMG_HEIGHT:
        print("Must be 240 x 240")
    else:
        if num_of_bits_per_pixel == 24:
            print("Assume " + bmp_file + " is 240x240 RGB888 bmp, continuous.")
        
            buffer = bytearray(IMG_WIDTH*IMG_HEIGHT*2)  # for RGB565, each pixel two bytes

            byte_per_pixel_src = 3
            byte_per_pixel_buffer = 2
            byte_per_line_src = IMG_WIDTH*byte_per_pixel_src
            byte_per_line_buffer = IMG_WIDTH*byte_per_pixel_buffer
        
            tick_ms_formbuffer_start = ticks_ms()
            for y in range(IMG_HEIGHT):
                src_y_offset = (y*byte_per_line_src) + offset
            
                # Because bmp start from bottom-left,
                # so we have to invert the y order (IMG_HEIGHT-1-y).
                buffer_y_offset_0 = (IMG_HEIGHT-1-y)*byte_per_line_buffer
                buffer_y_offset_1 = (IMG_HEIGHT-1-y)*byte_per_line_buffer + 1

                for x in range(IMG_WIDTH):
                    i = (x*byte_per_pixel_src) + src_y_offset
                
                    r_565 = (bmp_data[i+2] >> 3) & 0b00011111
                    g_565 = (bmp_data[i+1] >> 2) & 0b00111111
                    b_565 = (bmp_data[i] >> 3)   & 0b00011111
                    rgb565 = (r_565 << 11) | (g_565 << 5) | b_565
                
                    buffer[x*2 + buffer_y_offset_0] = (rgb565 >> 8) & 0xFF
                    buffer[x*2 + buffer_y_offset_1] = rgb565 & 0xFF

            tick_ms_formbuffer_end = ticks_ms()
            display.block(0, 0, 239, 239, buffer)
            tick_ms_block_end = ticks_ms()
        
            msg_formbuffer = "form buffer:     " +\
                             str(ticks_diff(tick_ms_formbuffer_end, tick_ms_formbuffer_start)) +\
                             " ms"
            msg_displayblock = "display.block(): " +\
                             str(ticks_diff(tick_ms_block_end, tick_ms_formbuffer_end)) +\
                             " ms"
            print(msg_formbuffer)
            print(msg_displayblock)
            display.draw_text8x8(0, 300,
                         msg_formbuffer,
                         color565(255, 255, 255))
            display.draw_text8x8(0, 310,
                         msg_displayblock,
                         color565(255, 255, 255))

        elif num_of_bits_per_pixel == 16:
            print("Assume " + bmp_file + " is 240x240 RGB565 bmp, continuous.")

            buffer = bytearray(IMG_WIDTH*IMG_HEIGHT*2)  # for RGB565, each pixel two bytes
        
            byte_per_pixel_src = 2
            byte_per_pixel_buffer = 2
            byte_per_line_src = IMG_WIDTH*byte_per_pixel_src
            byte_per_line_buffer = IMG_WIDTH*byte_per_pixel_buffer

            tick_ms_formbuffer_start = ticks_ms()
            for y in range(IMG_HEIGHT):
            
                src_y_offset_0 = (y*byte_per_line_src) + offset
                src_y_offset_1 = (y*byte_per_line_src) + offset + 1
                # Because bmp start from bottom-left,
                # so we have to invert the y order (IMG_HEIGHT-1-y).
                buffer_y_offset_0 = (IMG_HEIGHT-1-y)*byte_per_line_buffer
                buffer_y_offset_1 = (IMG_HEIGHT-1-y)*byte_per_line_buffer + 1
            
                for x in range(IMG_WIDTH):
                    #notice: HIGH/LOW byte reversed
                    buffer[x*2 + buffer_y_offset_0] = bmp_data[x*2 + src_y_offset_1]
                    buffer[x*2 + buffer_y_offset_1] = bmp_data[x*2 + src_y_offset_0]
                
            tick_ms_formbuffer_end = ticks_ms()
            display.block(0, 0, 239, 239, buffer)
            tick_ms_block_end = ticks_ms()
        
            msg_formbuffer = "form buffer:     " +\
                             str(ticks_diff(tick_ms_formbuffer_end, tick_ms_formbuffer_start)) +\
                             " ms"
            msg_displayblock = "display.block(): " +\
                             str(ticks_diff(tick_ms_block_end, tick_ms_formbuffer_end)) +\
                             " ms"
            print(msg_formbuffer)
            print(msg_displayblock)
            display.draw_text8x8(0, 300,
                         msg_formbuffer,
                         color565(255, 255, 255))
            display.draw_text8x8(0, 310,
                         msg_displayblock,
                         color565(255, 255, 255))
                
        else:
            print("Must be 16 or 24 bit per pixel")
#==========================
#img_path = '/sd/images'   #load from SD
img_path = '/images'      #load from MicroPython device
IMG_WIDTH = 240
IMG_HEIGHT = 240

while True:
    
    files = os.listdir(img_path)
    bmp_files = [file for file in files if file.endswith('.bmp')]
    bmp_file_list = sorted(bmp_files)

    for f in bmp_file_list:
        print()
        display_bmp(img_path, f)
        sleep(2)


mpy_s3_ili9341_images_touch.py
"""
ESP32-S3-DevKitC-1 running MicroPython v1.24.1
3.2" 320x240 IPS LCD (ILI9341 SPI) with Cap. Touch (FT6336) and Micro SD Slot
Display 240x240 RGB888/RGB565 bmp images on ILI9341 LCD.
Touch left 1/3 of LCD to show previous image.
Touch right 1/3 of LCD to show next image.

For ESP32-S3-DevKitC-1 has Octal SPIRAM,
makesure using MicroPython firmware of "spiram-oct" variant,
such as ESP32_GENERIC_S3-SPIRAM_OCT-20241129-v1.24.1.bin.

Libraries needed, install to /lib:
- ili9341.py
- sdcard (remark: NOT machine.SDCard)
- ft6x36.py

ili9341.py download from:
rdagger/micropython-ili9341
https://github.com/rdagger/micropython-ili9341

micropython-lib/sdcard.py can be download from
https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/storage/sdcard/sdcard.py
or using Thonny > Tools > Manager Package...

lbuque/micropython-ft6x36
https://github.com/lbuque/micropython-ft6x36

All test images were generated using "Image Creator in Bing",
converted to 240x240 RGB888/RGB565 bmp using GIMP or Python.
To resize using GIMP/Python, read:
https://coxxect.blogspot.com/2024/11/resize-jpg-and-convert-to-bmp-in-rgb888.html
"""
import os, sys
from time import sleep, ticks_ms, ticks_diff
from machine import Pin, SPI, I2C
import sdcard
import gc

from ili9341 import Display, color565

from ft6x36 import FT6x36

print("====================================")
print(sys.implementation[0], os.uname()[3],
      "\nrun on", os.uname()[4])
print("====================================")

LCD_CS=14  # low to select
LCD_RST=13
LCD_RS=12
LCD_SDI=11 #MOSI
LCD_SCK=10
LCD_LED=9
LCD_SDO=8  #MISO

CTP_SCL=7
CTP_RST=6
CTP_SDA=5
CTP_INT=4  #Not used

SD_CS=18   # low to select

# un-select both LCD and SD in power-up,
# to prevent un-used cs unstable and false selected.
pin_cs_lcd = Pin(LCD_CS, Pin.OUT)
pin_cs_lcd.value(1)
pin_cs_sd = Pin(SD_CS, Pin.OUT)
pin_cs_sd.value(1)

pin_backlight=Pin(LCD_LED, Pin.OUT)
pin_backlight.value(0)

# output a negative pulse to CTP_RST pin, to reset FT6336U
pin_CTP_RST=Pin(CTP_RST, Pin.OUT)
pin_CTP_RST.value(1)
sleep(0.1)
pin_CTP_RST.value(0)
sleep(0.1)
pin_CTP_RST.value(1)
sleep(0.1)

# rot and rot_set
# are used to better manager rotation/width/height of display and touch
rot=0   # 0 rotation = 0
        # 1 rotation = 90
        # 2 rotation = 180
        # 3 rotation = 270
# rot_set[x][0] : ili9341 Display rotation
# rot_set[x][1] : ili9341 Display width
# rot_set[x][2] : ili9341 Display height
# rot_set[x][3] : ft6336U Touch rotation
# rot_set[x][4] : ft6336U Touch width
# rot_set[x][5] : ft6336U Touch height
rot_set = [[0, 240, 320, FT6x36.PORTRAIT_INVERTED, 240, 320],
           [90, 320, 240, FT6x36.LANDSCAPE_INVERTED, 240, 320],
           [180, 240, 320, FT6x36.PORTRAIT, 240, 320],
           [270, 320, 240, FT6x36.LANDSCAPE, 240, 320]]

# spi share by LCD and SD
spi = SPI(1, baudrate=40000000, sck=LCD_SCK, mosi=LCD_SDI, miso=LCD_SDO)

display = Display(spi,
                  rotation=rot_set[rot][0],
                  width=rot_set[rot][1], height=rot_set[rot][2],
                  dc=Pin(LCD_RS), cs=Pin(LCD_CS), rst=Pin(LCD_RST))
display.invert(enable=True)

# i2c
i2c = I2C(0, scl=Pin(CTP_SCL), sda=Pin(CTP_SDA))
print("i2c", i2c)
print("I2C Scan:", i2c.scan())
touch = FT6x36(i2c,
               width=rot_set[rot][4],
               height=rot_set[rot][5],
               rotation=rot_set[rot][3],)
print("Touch:", touch._width, "x", touch._height)

# remark:
# It's sdcard.SDCard
# NOT machine.SDCard
sd = sdcard.SDCard(spi, Pin(SD_CS))

vfs = os.VfsFat(sd)
os.mount(vfs, "/sd")

pin_backlight.value(1)
print("Display:", display.width, "x", display.height)
display.clear()

# Read bmp
def read_bmp(filename):
    print("Read:", filename)
    gc.collect()
    print(gc.mem_free())
    tick_ms_read_bmp_start = ticks_ms()
    with open(filename, 'rb') as f:
        bmp_data = f.read()
    tick_ms_read_bmp_end = ticks_ms()
    
    msg_read_bmp = "read bmp: " + str(ticks_diff(tick_ms_read_bmp_end, tick_ms_read_bmp_start)) + " ms"
    display.draw_text8x8(0, 290,
                         msg_read_bmp,
                         color565(255, 255, 255))
    print(msg_read_bmp)
    return bmp_data

def display_bmp(src_path, src_file):
    bmp_file = src_path + '/' + src_file
    display.clear()
    display.draw_text8x8(0, 250, src_path+'/', color565(255, 255, 255))
    display.draw_text8x8(0, 260, src_file, color565(255, 255, 255))
    bmp_data = read_bmp(bmp_file)
    print("bmp_data", type(bmp_data), len(bmp_data))
    
    offset = int.from_bytes(bmp_data[10:14], 'little')
    image_width = int.from_bytes(bmp_data[18:22], 'little')
    image_height = int.from_bytes(bmp_data[22:26], 'little')
    num_of_bits_per_pixel = int.from_bytes(bmp_data[28:30], 'little')

    #==========================
    # Verify the bmp imag
    if chr(bmp_data[0]) != 'B' or chr(bmp_data[1]) != 'M':
        print("Must be bmp")
    elif image_width != IMG_WIDTH or image_height != IMG_HEIGHT:
        print("Must be 240 x 240")
    else:
        if num_of_bits_per_pixel == 24:
            print("Assume " + bmp_file + " is 240x240 RGB888 bmp, continuous.")
        
            buffer = bytearray(IMG_WIDTH*IMG_HEIGHT*2)  # for RGB565, each pixel two bytes

            byte_per_pixel_src = 3
            byte_per_pixel_buffer = 2
            byte_per_line_src = IMG_WIDTH*byte_per_pixel_src
            byte_per_line_buffer = IMG_WIDTH*byte_per_pixel_buffer
        
            tick_ms_formbuffer_start = ticks_ms()
            for y in range(IMG_HEIGHT):
                src_y_offset = (y*byte_per_line_src) + offset
            
                # Because bmp start from bottom-left,
                # so we have to invert the y order (IMG_HEIGHT-1-y).
                buffer_y_offset_0 = (IMG_HEIGHT-1-y)*byte_per_line_buffer
                buffer_y_offset_1 = (IMG_HEIGHT-1-y)*byte_per_line_buffer + 1

                for x in range(IMG_WIDTH):
                    i = (x*byte_per_pixel_src) + src_y_offset
                
                    r_565 = (bmp_data[i+2] >> 3) & 0b00011111
                    g_565 = (bmp_data[i+1] >> 2) & 0b00111111
                    b_565 = (bmp_data[i] >> 3)   & 0b00011111
                    rgb565 = (r_565 << 11) | (g_565 << 5) | b_565
                
                    buffer[x*2 + buffer_y_offset_0] = (rgb565 >> 8) & 0xFF
                    buffer[x*2 + buffer_y_offset_1] = rgb565 & 0xFF

            tick_ms_formbuffer_end = ticks_ms()
            display.block(0, 0, 239, 239, buffer)
            tick_ms_block_end = ticks_ms()
        
            msg_formbuffer = "form buffer:     " +\
                             str(ticks_diff(tick_ms_formbuffer_end, tick_ms_formbuffer_start)) +\
                             " ms"
            msg_displayblock = "display.block(): " +\
                             str(ticks_diff(tick_ms_block_end, tick_ms_formbuffer_end)) +\
                             " ms"
            print(msg_formbuffer)
            print(msg_displayblock)
            display.draw_text8x8(0, 300,
                         msg_formbuffer,
                         color565(255, 255, 255))
            display.draw_text8x8(0, 310,
                         msg_displayblock,
                         color565(255, 255, 255))

        elif num_of_bits_per_pixel == 16:
            print("Assume " + bmp_file + " is 240x240 RGB565 bmp, continuous.")

            buffer = bytearray(IMG_WIDTH*IMG_HEIGHT*2)  # for RGB565, each pixel two bytes
        
            byte_per_pixel_src = 2
            byte_per_pixel_buffer = 2
            byte_per_line_src = IMG_WIDTH*byte_per_pixel_src
            byte_per_line_buffer = IMG_WIDTH*byte_per_pixel_buffer

            tick_ms_formbuffer_start = ticks_ms()
            for y in range(IMG_HEIGHT):
            
                src_y_offset_0 = (y*byte_per_line_src) + offset
                src_y_offset_1 = (y*byte_per_line_src) + offset + 1
                # Because bmp start from bottom-left,
                # so we have to invert the y order (IMG_HEIGHT-1-y).
                buffer_y_offset_0 = (IMG_HEIGHT-1-y)*byte_per_line_buffer
                buffer_y_offset_1 = (IMG_HEIGHT-1-y)*byte_per_line_buffer + 1
            
                for x in range(IMG_WIDTH):
                    #notice: HIGH/LOW byte reversed
                    buffer[x*2 + buffer_y_offset_0] = bmp_data[x*2 + src_y_offset_1]
                    buffer[x*2 + buffer_y_offset_1] = bmp_data[x*2 + src_y_offset_0]
                
            tick_ms_formbuffer_end = ticks_ms()
            display.block(0, 0, 239, 239, buffer)
            tick_ms_block_end = ticks_ms()
        
            msg_formbuffer = "form buffer:     " +\
                             str(ticks_diff(tick_ms_formbuffer_end, tick_ms_formbuffer_start)) +\
                             " ms"
            msg_displayblock = "display.block(): " +\
                             str(ticks_diff(tick_ms_block_end, tick_ms_formbuffer_end)) +\
                             " ms"
            print(msg_formbuffer)
            print(msg_displayblock)
            display.draw_text8x8(0, 300,
                         msg_formbuffer,
                         color565(255, 255, 255))
            display.draw_text8x8(0, 310,
                         msg_displayblock,
                         color565(255, 255, 255))
                
        else:
            print("Must be 16 or 24 bit per pixel")
#==========================
#img_path = '/sd/images'   #load from SD
img_path = '/images'      #load from MicroPython device
IMG_WIDTH = 240
IMG_HEIGHT = 240

files = os.listdir(img_path)
bmp_files = [file for file in files if file.endswith('.bmp')]
bmp_file_list = sorted(bmp_files)

index = 0

while True:
    print()
    f = bmp_file_list[index]
    display_bmp(img_path, f)
    
    while True:
        p = touch.get_positions()
        
        if len(p) > 0:
            print(p)
            x = p[0][0]
            
            if x < display.width//3:
                print(p, ": Prev")
                index = index-1
                if index < 0:
                    index = len(bmp_file_list)-1
                break
            
            elif x > display.width*2//3:
                print(p, ": Next")
                index = index + 1
                if index == len(bmp_file_list):
                    index = 0
                break
            
            else:
                print(p, ": No op")

        sleep(0.1)


Related:
Raspberry Pi Pico 2/MicroPython display bmp images on 240x240 ST7789 SPI Display.

Comments

Popular posts from this blog

480x320 TFT/ILI9488 SPI wih EP32C3 (arduino-esp32) using Arduino_GFX Library

Drive 320x240 ILI9341 SPI TFT using ESP32-S3 (NodeMCU ESP-S3-12K-Kit) using TFT_eSPI library, in Arduino Framework.