Skip to content

ST7789 240x320 screen

Titouan Fréquelin edited this page May 5, 2025 · 7 revisions

Introduction

The project

This project aims to implement a screen to the keycube in order to run a game, basic software and menus.

One part is dedictaed to implement the basic menus with a specific flowchart, while the other focuses on adding games.

Hardware used

In order to complete this project, we choosed to use a Raspberry Pico 2 W 2350 paired with a Pico Breadboard.

IMG_20250326_222747

We then chosed an Adafruit 2.0" 320x240 Color IPS TFT Display - ST7789. We chosed this screen because it is featured in a Doom WAD project that we will try to implement later on.

IMG_20250326_222701

We also had to used 10 male/female jumper cables and a usb to micro usb cable to connect everything together

IMG_20250326_222641

Hardware setup

First, we have to make sure that the Pico 2 W is well plugged in to the Breadboard. Then, we need to plug the screen to the "e" row of pins, facing up. It should look like something like that :

IMG20250326184729

Then, to connect the wires, from the screen to the Pico :

  • VIN to VBUS

  • GND to GND

  • SCK to GP18

  • MOSI to GP19

  • CS to GP27

  • RST to GP20

  • D/C to GP21

  • BL to GP22

We are also gonna use two buttons on the Breadboard, connecting them to the pins GP14 and GP15.

That is the setup we used, but we can also relate to this following image for documentation :

image

The finished Breadboard should look like that :

IMG_20250326_230304

We can now plug it in to a computer using the micro usb to usb cable :

IMG_20250326_222718

Software used

To implement code, we used the langage Micropython that will allow everyone, including beginners in coding, to add code to the project and to understand how the code works. We used the IDE Mu Editor which is free and easy to use.

The code.py file contains a code example allowing to display a background on the screen with a text, changing colors every few seconds. We use various librairies that can be found on the circuitpython library bundle, and the .uf2 file to implement circuitpython can also be found on the circuit python website, specific for this Pico 2 W.

Here is the final version of the example code, when pressing a button, a new background color is added to the list and displayed with the others. Green for button 1 and purple for button 2.

import board
import busio
import displayio
import digitalio
import terminalio
import time
from adafruit_display_text import label
from fourwire import FourWire

from adafruit_st7789 import ST7789

# Release any resources currently in use for the displays
displayio.release_displays()

# Initialisation SPI
spi = busio.SPI(clock=board.GP18, MOSI=board.GP19)

# Utilisation de board.Pin au lieu de DigitalInOut
tft_cs = board.GP27  # Pin pour le chip select
tft_dc = board.GP21  # Pin pour le Data/Command

# Connexion SPI avec l'écran
display_bus = FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=board.GP20)

# Initialisation de l'écran ST7789
display = ST7789(display_bus, width=320, height=240, rotation=90)

# Créer le groupe pour l'affichage
splash = displayio.Group()
display.root_group = splash

# Fond bleu sombre
color_bitmap = displayio.Bitmap(320, 240, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0x06024b  # Bleu sombre

bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
splash.append(bg_sprite)

# Dessiner un rectangle intérieur bleu
inner_bitmap = displayio.Bitmap(280, 200, 1)
inner_palette = displayio.Palette(1)
inner_palette[0] = 0x0B029C  # Bleu
inner_sprite = displayio.TileGrid(inner_bitmap, pixel_shader=inner_palette, x=20, y=20)
splash.append(inner_sprite)

# Ajouter du texte jaune
text_group = displayio.Group(scale=3, x=57, y=120)
text = "-Keycube-"
text_area = label.Label(terminalio.FONT, text=text, color=0xFFFFFF)
text_group.append(text_area)  # Sous-groupe pour le texte et l'échelle
splash.append(text_group)

# Configuration des boutons
button1 = digitalio.DigitalInOut(board.GP15)
button1.direction = digitalio.Direction.INPUT
button1.pull = digitalio.Pull.UP  # Bouton en pull-up

button2 = digitalio.DigitalInOut(board.GP14)
button2.direction = digitalio.Direction.INPUT
button2.pull = digitalio.Pull.UP  # Bouton en pull-up

# Boucle pour faire clignoter toutes les 3 secondes
while True:
    # 1. Changer les couleurs (fond et rectangle en rouge, texte en noir)
    color_palette[0] = 0x410000  # Fond rouge foncé
    inner_palette[0] = 0x8B0000  # Rectangle rouge foncé
    text_area.color = 0x000000    # Texte en noir
    display.refresh()
    time.sleep(3)
    
    # 2. Revenir aux couleurs d'origine (fond bleu, rectangle bleu, texte jaune)
    color_palette[0] = 0x06024b  # Bleu sombre
    inner_palette[0] = 0x0B029C  # Bleu
    text_area.color = 0xFFFFFF   # Texte jaune
    display.refresh()
    time.sleep(3)
    
    if button2.value:  # Appui sur bouton 2 et mode vert
        color_palette[0] = 0x01370a  # Vert sombre
        inner_palette[0] = 0x00da25  # Vert
        text_area.color = 0x000000   # Texte noir
        
        time.sleep(3)  # Débounce
        
    if button1.value:  # Appui sur bouton 2 et mode vert
        color_palette[0] = 0x62006a  # Violet sombre
        inner_palette[0] = 0xd700e8  # Violet
        text_area.color = 0xFFFFFF   # Texte jaune
        
        time.sleep(3)  # Débounce

The turtle_code.py file contains a code example using the turtle library. This simple library allow us to draw pixels using a pen and various color. In this example, we are able to draw whatever we want one the screen using keyboard inputs on the output window. We can change colors by typing the name of the color we want, and we can get the pen up or down to select if we want to draw something when we are moving the pen or not.

import board
import busio
import displayio
from adafruit_turtle import Color, turtle
from fourwire import FourWire
from adafruit_st7789 import ST7789
import time

# Release any resources currently in use for the displays
displayio.release_displays()

# SPI initialization
spi = busio.SPI(clock=board.GP18, MOSI=board.GP19)

tft_cs = board.GP27  # Pin for chip select
tft_dc = board.GP21  # Pin for Data/Command

# SPI Connection with Screen
display_bus = FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=board.GP20)

# ST7789 Screen Initialization
display = ST7789(display_bus, width=320, height=240, rotation=90)

# Create group for display
splash = displayio.Group()
display.root_group = splash

# Creating the turtle
turtle = turtle(display)
turtle.pencolor(Color.WHITE)  # Pen color in white

# Initialization of the position
turtle.penup()
turtle.goto(0, 0)  # Starting position
turtle.pendown()

# Function to change pen color
def change_color(color_name):
    colors = {
        "red": Color.RED,
        "green": Color.GREEN,
        "blue": Color.BLUE,
        "yellow": Color.YELLOW,
        "orange": Color.ORANGE,
        "violet": Color.PURPLE,
        "white": Color.WHITE,
        "black": Color.BLACK
    }
    if color_name in colors:
        turtle.pencolor(colors[color_name])  # Change the color
    else:
        print(f"Color '{color_name}' not recognized, pen still untouched.")

# Fonction to deal with keyboard inputs
def control_turtle():
    while True:
        # Read keyboard inputs
        char = input("Press a key ('q' for pen down, 'e' for pen up, 'w', 'a', 's', 'd' to move, 'red', 'green', 'blue' for color, 'x' to quit) : ")
        
        if char == 'q':  # pen down
            turtle.pendown()
        elif char == 'e':  # pen up
            turtle.penup()
        elif char == 'w':  # move up
            turtle.setheading(90)
            turtle.forward(10)
        elif char == 'a':  # move left
            turtle.setheading(180)
            turtle.forward(10)
        elif char == 's':  # move down
            turtle.setheading(270)
            turtle.forward(10)
        elif char == 'd':  # move right
            turtle.setheading(0)
            turtle.forward(10)
        elif char == 'x':  # Quit
            break
        elif char in ["red", "green", "blue", "yellow", "orange", "violet", "white", "black"]:
            change_color(char)  # Change the pen color
        else:
            print("Unkown command. Try again.")
        
        time.sleep(0.1)

# Start the turtle control
control_turtle()

while True:
    pass

Gameboy Emulator

This part focused on implementing a Gameboy emulator on the pico. This is based on the Gameboy Emulator for the Pi Pico 2 rp2350 tutorial

For the hardware : *Previous hardware *SD card *More jumper cables

For the software :

Prerequisites

Ensure having the following installed:


1. Install Rust

Open a terminal and run:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Follow the prompts to complete installation. After it's done, restart your terminal and check the installation:

rustc --version

2. Install the Rust Standard Library for the Pico Target

Run this command in your terminal:

rustup target add thumbv8m.main-none-eabihf

This installs the required Rust target for compiling to the rp2350 microcontroller.


3. Clone the Emulator Repository

Download the Gameboy emulator source:

git clone https://github.com/Altaflux/gb-rp2350.git  
cd gb-rp2350

4. Configure Environment Variables

Create a .env file using the example provided:

bash
cp .env.example .env

Edit the .env file to match your ROM path and display settings.

You can find on this github branch an example .env file you can use.


5. Put Your Pico in Bootloader Mode

  • Push and hold the BOOTSEL button on the Raspberry Pi Pico 2.
  • While holding, plug the Pico into your computer using the USB cable.
  • Release the button once a new drive called RPI-RP2 appears.

6. Build the Emulator

Build the emulator in release mode:

cargo build --release

This may take a few minutes depending on your system.


7. Convert the ELF Binary to UF2 Format

Use picotool to convert the compiled binary into a UF2 file:

picotool uf2 convert ./target/thumbv8m.main-none-eabihf/release/gb-rp2350 -t elf -o gb-rp2350.uf2

⚠️ Make sure picotool is installed and available in your path.

At this step, we encountered problems converting the binary file, keeping us away from succesfully getting a working uf2 file.

A potential solution here was to use elf2uf2 instead to convert the file, a compiled Python tool. But this solution led us to other problems :

path\to\file\gb-rp2350-main>elf2uf2 .\target\thumbv8m.main-none-eabihf\release\gb-rp2350 -t elf gb-rp2350.uf2
ERROR: HARD-FLOAT not supported

8. Flash the Emulator to the Pico

Drag and drop the gb-rp2350.uf2 file onto the RPI-RP2 drive. The Pico will reboot and start running the Gameboy emulator automatically.


Resources

Next step : Doom & Emulator

The next step is to implement the Doom.wad game into the Pico 2 W, and it also could be to create a few games with our two buttons, despite the fact that some limitations can make it hard to create even simple games.

The next step could also be to finalize the emulator so it could run on the pico and implement therefore various games. This could be done by finding a solution to the convertion problem, obtaining a .uf2 file being the key for the next step.