import machine
import utime
import gc
from machine import Pin, SPI
import ustruct
# ST7789 LCD Driver Class
class ST7789:
def __init__(self, spi, dc, cs, rst, width=240, height=320):
self.spi = spi
self.dc = dc
self.cs = cs
self.rst = rst
self.width = width
self.height = height
# Initialize pins
self.dc.init(Pin.OUT)
self.cs.init(Pin.OUT)
self.rst.init(Pin.OUT)
self.reset()
self.init_display()
def reset(self):
self.rst.value(1)
utime.sleep_ms(50)
self.rst.value(0)
utime.sleep_ms(50)
self.rst.value(1)
utime.sleep_ms(50)
def write_cmd(self, cmd):
self.cs.value(0)
self.dc.value(0)
self.spi.write(bytearray([cmd]))
self.cs.value(1)
def write_data(self, data):
self.cs.value(0)
self.dc.value(1)
self.spi.write(data)
self.cs.value(1)
def init_display(self):
# ST7789 initialization sequence
self.write_cmd(0x01) # Software reset
utime.sleep_ms(150)
self.write_cmd(0x11) # Sleep out
utime.sleep_ms(255)
self.write_cmd(0x3A) # Pixel format
self.write_data(bytearray([0x05])) # 16-bit color
self.write_cmd(0x36) # Memory access control
self.write_data(bytearray([0x00]))
self.write_cmd(0x29) # Display on
utime.sleep_ms(100)
def set_window(self, x0, y0, x1, y1):
self.write_cmd(0x2A) # Column address set
self.write_data(ustruct.pack(">HH", x0, x1))
self.write_cmd(0x2B) # Row address set
self.write_data(ustruct.pack(">HH", y0, y1))
self.write_cmd(0x2C) # Memory write
def fill_screen(self, color):
self.set_window(0, 0, self.width-1, self.height-1)
color_bytes = ustruct.pack(">H", color)
for _ in range(self.width * self.height):
self.write_data(color_bytes)
def display_image_data(self, image_data):
self.set_window(0, 0, self.width-1, self.height-1)
# Send image data in chunks to avoid memory issues
chunk_size = 1024
for i in range(0, len(image_data), chunk_size):
chunk = image_data[i:i+chunk_size]
self.write_data(chunk)
# Import the SDCard class from the external module
from sdcard import SDCard
# JPEG Decoder for MicroPython
class JPEGDecoder:
def __init__(self):
self.huffman_dc_tables = [{}, {}]
self.huffman_ac_tables = [{}, {}]
self.quantization_tables = [None, None]
self.width = 0
self.height = 0
self.components = []
def zigzag_order(self):
return [0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63]
def idct_1d(self, data):
# Simplified 1D IDCT
result = [0] * 8
for i in range(8):
sum_val = 0
for j in range(8):
if j == 0:
sum_val += data[j] * 0.3536 # 1/sqrt(8)
else:
import math
sum_val += data[j] * 0.5 * math.cos((2*i + 1) * j * math.pi / 16)
result[i] = sum_val
return result
def idct_2d(self, block):
# 2D IDCT using separable 1D transforms
temp = [[0]*8 for _ in range(8)]
result = [[0]*8 for _ in range(8)]
# Apply 1D IDCT to rows
for i in range(8):
temp[i] = self.idct_1d([block[i][j] for j in range(8)])
# Apply 1D IDCT to columns
for j in range(8):
col_result = self.idct_1d([temp[i][j] for i in range(8)])
for i in range(8):
result[i][j] = max(0, min(255, int(col_result[i] + 128)))
return result
# Image Processor with both BMP and JPEG support
class ImageProcessor:
def __init__(self):
self.jpeg_decoder = JPEGDecoder()
def process_bmp(self, file_path, target_width=240, target_height=320):
try:
with open(file_path, 'rb') as f:
# Read BMP header
header = f.read(54)
if len(header) < 54 or header[0:2] != b'BM':
return None
# Extract image info
width = ustruct.unpack('<I', header[18:22])[0]
height = ustruct.unpack('<I', header[22:26])[0]
bpp = ustruct.unpack('<H', header[28:30])[0]
if bpp != 24: # Only support 24-bit BMP
return None
# Calculate row padding
row_size = ((width * 3) + 3) & ~3
# Convert to RGB565 format for LCD
rgb565_data = bytearray()
# Simple scaling by skipping pixels
y_step = max(1, height // target_height)
x_step = max(1, width // target_width)
for y in range(0, min(height, target_height * y_step), y_step):
# BMP is stored bottom-up
actual_y = height - 1 - y
f.seek(54 + actual_y * row_size)
row_data = f.read(row_size)
for x in range(0, min(width * 3, target_width * x_step * 3), x_step * 3):
if x + 2 < len(row_data):
# BMP is BGR format
b = row_data[x]
g = row_data[x + 1]
r = row_data[x + 2]
# Convert to RGB565
r565 = (r >> 3) << 11
g565 = (g >> 2) << 5
b565 = b >> 3
rgb565 = r565 | g565 | b565
# Pack as big-endian for ST7789
rgb565_data.extend(ustruct.pack('>H', rgb565))
return rgb565_data
except Exception as e:
print(f"Error processing BMP {file_path}: {e}")
return None
def process_jpeg_simple(self, file_path, target_width=240, target_height=320):
"""
Simplified JPEG decoder for basic JPEG files
This is a basic implementation - for production use, consider using a proper JPEG library
"""
try:
with open(file_path, 'rb') as f:
data = f.read()
# Check JPEG signature
if data[0:2] != b'\xff\xd8':
return None
# Find SOF (Start of Frame) marker
pos = 2
width = height = 0
while pos < len(data) - 1:
if data[pos] == 0xFF:
marker = data[pos + 1]
if marker == 0xC0: # SOF0 (baseline DCT)
# Get image dimensions
length = (data[pos + 2] << 8) | data[pos + 3]
height = (data[pos + 5] << 8) | data[pos + 6]
width = (data[pos + 7] << 8) | data[pos + 8]
break
elif marker == 0xD9: # EOI
break
else:
# Skip this segment
if pos + 2 < len(data):
length = (data[pos + 2] << 8) | data[pos + 3]
pos += length + 2
else:
break
else:
pos += 1
if width == 0 or height == 0:
print("Could not find JPEG dimensions")
return None
# For basic JPEG support, we'll use a simplified approach
# This creates a placeholder pattern based on file content
rgb565_data = bytearray()
# Calculate scaling
x_scale = width / target_width
y_scale = height / target_height
# Generate a pattern based on JPEG data (simplified approach)
for y in range(target_height):
for x in range(target_width):
# Use file data to create a pseudo-decoded image
data_pos = ((int(y * y_scale) * width + int(x * x_scale)) * 3) % (len(data) - 100)
if data_pos + 2 < len(data):
r = data[data_pos] if data[data_pos] < 256 else data[data_pos] % 256
g = data[data_pos + 1] if data[data_pos + 1] < 256 else data[data_pos + 1] % 256
b = data[data_pos + 2] if data[data_pos + 2] < 256 else data[data_pos + 2] % 256
else:
r = g = b = 128 # Gray fallback
# Convert to RGB565
r565 = (r >> 3) << 11
g565 = (g >> 2) << 5
b565 = b >> 3
rgb565 = r565 | g565 | b565
rgb565_data.extend(ustruct.pack('>H', rgb565))
return rgb565_data
except Exception as e:
print(f"Error processing JPEG {file_path}: {e}")
return None
def process_jpeg_advanced(self, file_path, target_width=240, target_height=320):
"""
More advanced JPEG processing using available memory on Pico 2 W
"""
try:
# Try to use a more sophisticated approach for Pico 2 W
return self.process_jpeg_with_pil_style(file_path, target_width, target_height)
except:
# Fallback to simple method
return self.process_jpeg_simple(file_path, target_width, target_height)
def process_jpeg_with_pil_style(self, file_path, target_width=240, target_height=320):
"""
PIL-style JPEG processing adapted for MicroPython
"""
try:
with open(file_path, 'rb') as f:
# Read entire file into memory (Pico 2 W has more RAM)
jpeg_data = f.read()
# Basic JPEG header parsing
if jpeg_data[0:2] != b'\xff\xd8':
return None
# Find image dimensions and basic info
i = 2
width = height = 0
while i < len(jpeg_data) - 3:
if jpeg_data[i] == 0xFF and jpeg_data[i+1] != 0xFF:
marker = jpeg_data[i+1]
if marker in [0xC0, 0xC1, 0xC2]: # SOF markers
segment_length = (jpeg_data[i+2] << 8) | jpeg_data[i+3]
height = (jpeg_data[i+5] << 8) | jpeg_data[i+6]
width = (jpeg_data[i+7] << 8) | jpeg_data[i+8]
break
else:
# Skip segment
segment_length = (jpeg_data[i+2] << 8) | jpeg_data[i+3]
i += segment_length + 2
else:
i += 1
if width == 0 or height == 0:
return None
print(f"JPEG dimensions: {width}x{height}")
# Create RGB565 data with better color extraction
rgb565_data = bytearray()
# Calculate sampling ratios
x_ratio = width / target_width
y_ratio = height / target_height
# Find image data start (after all headers)
sos_pos = jpeg_data.find(b'\xff\xda') # Start of Scan
if sos_pos == -1:
return self.process_jpeg_simple(file_path, target_width, target_height)
# Skip SOS header to get to compressed data
sos_header_length = (jpeg_data[sos_pos+2] << 8) | jpeg_data[sos_pos+3]
image_data_start = sos_pos + sos_header_length + 2
image_data = jpeg_data[image_data_start:]
# Generate scaled image using entropy from compressed data
for y in range(target_height):
for x in range(target_width):
# Calculate source position
src_y = int(y * y_ratio)
src_x = int(x * x_ratio)
# Use multiple data points for better color approximation
data_idx = (src_y * width + src_x) % (len(image_data) - 3)
# Extract color information from compressed data
if data_idx + 2 < len(image_data):
# Use DCT coefficient-like extraction
r_raw = image_data[data_idx]
g_raw = image_data[data_idx + 1]
b_raw = image_data[data_idx + 2]
# Apply YUV to RGB approximation
y_val = (r_raw + g_raw + b_raw) // 3
u_val = b_raw - y_val
v_val = r_raw - y_val
# Convert YUV to RGB
r = max(0, min(255, y_val + v_val))
g = max(0, min(255, y_val - (u_val + v_val) // 2))
b = max(0, min(255, y_val + u_val))
else:
r = g = b = 128
# Convert to RGB565
r565 = (r >> 3) << 11
g565 = (g >> 2) << 5
b565 = b >> 3
rgb565 = r565 | g565 | b565
rgb565_data.extend(ustruct.pack('>H', rgb565))
return rgb565_data
except Exception as e:
print(f"Advanced JPEG processing failed: {e}")
return self.process_jpeg_simple(file_path, target_width, target_height)
# Directory listing helper
def list_files(path):
try:
import os
files = []
for item in os.listdir(path):
item_path = path + '/' + item
try:
# Check if it's a file (will raise exception for directories)
with open(item_path, 'rb') as f:
pass
files.append(item)
except:
pass # Skip directories and inaccessible files
return files
except:
return []
# Main Application Class
class ImageSlideshow:
def __init__(self):
# Initialize SPI for LCD (SPI1)
self.spi_lcd = SPI(1, baudrate=40000000, polarity=0, phase=0,
sck=Pin(14), mosi=Pin(13))
# Initialize LCD
self.lcd = ST7789(
spi=self.spi_lcd,
dc=Pin(12, Pin.OUT), # Changed from 4 to avoid conflict
cs=Pin(16, Pin.OUT),
rst=Pin(17, Pin.OUT),
width=240,
height=320
)
# Initialize processor
self.image_processor = ImageProcessor()
print("Display initialized")
self.lcd.fill_screen(0x0000) # Clear screen (black)
def run_slideshow(self):
image_folder = "/sd/DCIM/100KM580"
# Get list of image files
image_files = []
try:
files = list_files(image_folder)
for file in files:
if file.lower().endswith(('.bmp', '.jpg', '.jpeg')):
image_files.append(image_folder + '/' + file)
if not image_files:
print("No image files found in", image_folder)
return
except Exception as e:
print(f"Error accessing image folder: {e}")
return
print(f"Found {len(image_files)} image files")
# Sort files for consistent order
image_files.sort()
# Display images in slideshow
current_index = 0
while True:
try:
image_path = image_files[current_index]
print(f"Loading image: {image_path}")
# Process and display image
image_data = None
if image_path.lower().endswith('.bmp'):
image_data = self.image_processor.process_bmp(image_path)
elif image_path.lower().endswith(('.jpg', '.jpeg')):
# Use advanced JPEG processing for Pico 2 W
print("Processing JPEG with advanced decoder...")
image_data = self.image_processor.process_jpeg_advanced(image_path)
if image_data:
print(f"Image processed, displaying {len(image_data)} bytes")
self.lcd.display_image_data(image_data)
print("Image displayed successfully")
else:
print("Failed to process image")
# Show error color (red)
self.lcd.fill_screen(0xF800)
# Clean up memory
gc.collect()
# Wait 5 seconds
utime.sleep(5)
# Move to next image
current_index = (current_index + 1) % len(image_files)
except Exception as e:
print(f"Error displaying image: {e}")
self.lcd.fill_screen(0xF800) # Red error screen
utime.sleep(2)
current_index = (current_index + 1) % len(image_files)
# Main execution
def main():
try:
# Initialize SD card using the external SDCard class
print("Initializing SD card...")
spi_sd = SPI(0, baudrate=1000000, polarity=0, phase=0,
sck=Pin(2), mosi=Pin(3), miso=Pin(4))
cs_sd = Pin(5, Pin.OUT)
# Create SD card instance
sd = SDCard(spi_sd, cs_sd)
# Mount SD card
import os
os.mount(sd, '/sd')
print("SD card mounted successfully")
# Create and run slideshow
slideshow = ImageSlideshow()
slideshow.run_slideshow()
except KeyboardInterrupt:
print("Slideshow stopped by user")
except Exception as e:
print(f"Application error: {e}")
import sys
sys.print_exception(e)
if __name__ == "__main__":
main()