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

Previous exercise "Raspberry Pi Pico 2/MicroPython read bmp images and draw on ST7789 display pixel-by-pixel" is a slow approach. Because it draw in pixel-by-pixel, each involve a write sequency. But it help me understand bmp much more.

Here is another approach, form a bytearray passing to st7789pt blit_buffer() method, such that the driver write the whole buffer in one sequency to improve the speed much more.


For connection and installing st7789py_mpy on Raspberry Pi Pico 2/MicroPython, read:
https://coxxect.blogspot.com/2024/10/raspberry-pi-pico-2micropython-display.html

To prepare 240x240 RGB565/RGB888 bmp using Python or GIMP, read:
https://coxxect.blogspot.com/2024/11/resize-jpg-and-convert-to-bmp-in-rgb888.html


Exercise code:

mpy_pico2_bmp_buffer.py
"""
Raspberry Pi Pico/MicroPython exercise
to display on 1.54" IPS 240x240 with SPI ST7789 driver

Read, decode bmp in /images/ directory, and disply on 240x240 SPI ST7789 display.
Target:
- 240x240 RGB565 .bmp
- 240x240 RGB888 .bmp

Calling blit_buffer() with bmp data in buffer, to make it more efficiency.
Actually, I don't know the format of buffer or bitmap for st7789py_mpy, or any method to
generate buffer or bitmap.
I find out my approach to form the buffer for blit_buffer() method by guessing and trying.

ref:

Using library: russhughes/st7789py_mpy
https://github.com/russhughes/st7789py_mpy

to install st7789py_mpy on Raspberry Pi Pico 2/MicroPython,
https://coxxect.blogspot.com/2024/10/raspberry-pi-pico-2micropython-display.html

to know more about bmp structure:
https://coxxect.blogspot.com/2024/10/python-code-to-read-bmp-info-from.html

To prepare the 240x240 bmp RGB565/RGB888 using Python or GIMP, read te post:
https://coxxect.blogspot.com/2024/11/resize-jpg-and-convert-to-bmp-in-rgb888.html

Connection:
-----------
GND   GND
VCC   3V3
SCL   GP18
SDA   GP19
RES   GP20
DC    GP21
CS    GP17
BLK   GP22
      GP16 (dummy, not used)

"""

import os, sys
from machine import Pin, SPI
import time
import st7789py as st7789

disp_sck  = 18 # default SCK of SPI(0)
disp_mosi = 19 # default MOSI of SPI(0)
disp_miso = 16  # not use
disp_res = 20
disp_dc  = 21
disp_cs  = 17
disp_blk = 22

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

DISP_WIDTH = 240
DISP_HEIGHT = 240

# it's found that:
# - cannot set baudrate
# - even I set miso=None, miso will be assigned deault GP16 or previous assigned miso
disp_spi = SPI(0, baudrate=60000000, sck=Pin(disp_sck), mosi=Pin(disp_mosi), miso=None)
print(disp_spi)

display = st7789.ST7789(disp_spi,
                        DISP_WIDTH, DISP_HEIGHT,
                        reset=Pin(disp_res, Pin.OUT),
                        cs=Pin(disp_cs, Pin.OUT),
                        dc=Pin(disp_dc, Pin.OUT),
                        backlight=Pin(disp_blk, Pin.OUT))
print(st7789.__name__, display.width, "x", display.height)
display.fill(st7789.WHITE)
time.sleep(0.5)
display.fill(st7789.BLACK)

img_path = '/images/'
#img_file = 'img_a_240_rgb565.bmp'
img_file = 'img_b_240_rgb888.bmp'


# Read bmp
def read_bmp(filename):
    print("Read:", filename)
    with open(filename, 'rb') as f:
        bmp_data = f.read()
    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")

IMG_WIDTH = 240
IMG_HEIGHT = 240
def displat_bmp(bmp_file):
    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 = time.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 = time.ticks_ms()
            display.blit_buffer(buffer, 0, 0, image_width, image_height)
            tick_ms_blitbuffer_end = time.ticks_ms()
        
            print("time to form buffer:",
                  time.ticks_diff(tick_ms_formbuffer_end, tick_ms_formbuffer_start),
                  "ms")
            print("time to blit_buffer():",
                  time.ticks_diff(tick_ms_blitbuffer_end, tick_ms_formbuffer_end),
                  "ms")

        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 = time.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 = time.ticks_ms()
            display.blit_buffer(buffer, 0, 0, image_width, image_height)
            tick_ms_blitbuffer_end = time.ticks_ms()
        
            print("time to form buffer:",
                  time.ticks_diff(tick_ms_formbuffer_end, tick_ms_formbuffer_start),
                  "ms")
            print("time to blit_buffer():",
                  time.ticks_diff(tick_ms_blitbuffer_end, tick_ms_formbuffer_end),
                  "ms")
                
        else:
            print("Must be 16 or 24 bit per pixel")
#==========================
displat_bmp(img_path + img_file)



mpy_pico2_bmp_buffer_slideshow.py
"""
Raspberry Pi Pico/MicroPython exercise
to display on 1.54" IPS 240x240 with SPI ST7789 driver

Read, decode bmp in /images/ directory, and disply on 240x240 SPI ST7789 display.
In SlideShow.

Target:
- 240x240 RGB565 .bmp
- 240x240 RGB888 .bmp

Calling blit_buffer() with bmp data in buffer, to make it more efficiency.
Actually, I don't know the format of buffer or bitmap for st7789py_mpy, or any method to
generate buffer or bitmap.
I find out my approach to form the buffer for blit_buffer() method by guessing and trying.

ref:

Using library: russhughes/st7789py_mpy
https://github.com/russhughes/st7789py_mpy

to install st7789py_mpy on Raspberry Pi Pico 2/MicroPython,
https://coxxect.blogspot.com/2024/10/raspberry-pi-pico-2micropython-display.html

to know more about bmp structure:
https://coxxect.blogspot.com/2024/10/python-code-to-read-bmp-info-from.html

To prepare the 240x240 bmp RGB565/RGB888 using Python or GIMP, read te post:
https://coxxect.blogspot.com/2024/11/resize-jpg-and-convert-to-bmp-in-rgb888.html

Connection:
-----------
GND   GND
VCC   3V3
SCL   GP18
SDA   GP19
RES   GP20
DC    GP21
CS    GP17
BLK   GP22
      GP16 (dummy, not used)

"""

import os, sys
from machine import Pin, SPI
import time
import st7789py as st7789

disp_sck  = 18 # default SCK of SPI(0)
disp_mosi = 19 # default MOSI of SPI(0)
disp_miso = 16  # not use
disp_res = 20
disp_dc  = 21
disp_cs  = 17
disp_blk = 22

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

DISP_WIDTH = 240
DISP_HEIGHT = 240

# it's found that:
# - cannot set baudrate
# - even I set miso=None, miso will be assigned deault GP16 or previous assigned miso
disp_spi = SPI(0, baudrate=60000000, sck=Pin(disp_sck), mosi=Pin(disp_mosi), miso=None)
print(disp_spi)

display = st7789.ST7789(disp_spi,
                        DISP_WIDTH, DISP_HEIGHT,
                        reset=Pin(disp_res, Pin.OUT),
                        cs=Pin(disp_cs, Pin.OUT),
                        dc=Pin(disp_dc, Pin.OUT),
                        backlight=Pin(disp_blk, Pin.OUT))
print(st7789.__name__, display.width, "x", display.height)
display.fill(st7789.WHITE)
time.sleep(0.5)
display.fill(st7789.BLACK)

img_path = '/images/'
img_file = 'img_a_240_rgb565.bmp'
#img_file = 'img_b_240_rgb888.bmp'

# Read bmp
def read_bmp(filename):
    print("Read:", filename)
    with open(filename, 'rb') as f:
        bmp_data = f.read()
    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")

IMG_WIDTH = 240
IMG_HEIGHT = 240
def displat_bmp(bmp_file):
    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 = time.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 = time.ticks_ms()
            display.blit_buffer(buffer, 0, 0, image_width, image_height)
            tick_ms_blitbuffer_end = time.ticks_ms()
        
            print("time to form buffer:",
                  time.ticks_diff(tick_ms_formbuffer_end, tick_ms_formbuffer_start),
                  "ms")
            print("time to blit_buffer():",
                  time.ticks_diff(tick_ms_blitbuffer_end, tick_ms_formbuffer_end),
                  "ms")

        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 = time.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 = time.ticks_ms()
            display.blit_buffer(buffer, 0, 0, image_width, image_height)
            tick_ms_blitbuffer_end = time.ticks_ms()
        
            print("time to form buffer:",
                  time.ticks_diff(tick_ms_formbuffer_end, tick_ms_formbuffer_start),
                  "ms")
            
            print("time to blit_buffer():",
                  time.ticks_diff(tick_ms_blitbuffer_end, tick_ms_formbuffer_end),
                  "ms")
                
        else:
            print("Must be 16 or 24 bit per pixel")
#==========================
while True:
    for b_file in os.listdir(img_path):
        if b_file.endswith('.bmp'):
            print()
            bmp_file_full_path = img_path + b_file
            displat_bmp(bmp_file_full_path)
            time.sleep(4)



Comments

Popular posts from this blog

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

my dev.tools - FNIRSI 2C23T 3-in-1 Dual Channel Oscilloscope/Multimeter/Signal Generator