Skip to content

Feature: ST7789 control with minimal, lightweight driver, supports strings and pixelart #662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ App|Description
[spi_master_slave](spi/spi_master_slave) | Demonstrate SPI communication as master and slave.
[max7219_8x7seg_spi](spi/max7219_8x7seg_spi) | Attaching a Max7219 driving an 8 digit 7 segment display via SPI
[max7219_32x8_spi](spi/max7219_32x8_spi) | Attaching a Max7219 driving an 32x8 LED display via SPI
[st7789_display_spi](spi/st7789_display_spi) | Driving an ST7789 based display via SPI, minimal driver

### System

Expand Down
1 change: 1 addition & 0 deletions spi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ if (TARGET hardware_spi)
add_subdirectory_exclude_platforms(spi_flash)
add_subdirectory_exclude_platforms(max7219_32x8_spi)
add_subdirectory_exclude_platforms(max7219_8x7seg_spi)
add_subdirectory_exclude_platforms(st7789_display_spi)
else()
message("Skipping SPI examples as hardware_spi is unavailable on this platform")
endif()
13 changes: 13 additions & 0 deletions spi/st7789_display_spi/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
add_executable(st7789_display_spi
st7789_display_demo.c
fonts.c
)

# pull in common dependencies and additional spi hardware support
target_link_libraries(st7789_display_spi
pico_stdlib
hardware_spi
)

# create map/bin/hex file etc.
pico_add_extra_outputs(st7789_display_spi)
9 changes: 9 additions & 0 deletions spi/st7789_display_spi/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
This driver is a super-compact version of the competitor ST7789 drivers, with a
focus on being as light as possible on the Pico. It can display strings, chars,
and individual pixels for pixelart as shown. It saves lots of overhead by not
supporting microSD storage, or including animation support by default. Ideal for
super light or multi-use applications.

Also included is an example of how a sprite can be included as pixelart, I hope
the example is useful and entertaining (simply remove the references and sprite
file for a more normal output).
86 changes: 86 additions & 0 deletions spi/st7789_display_spi/chicken_sprite.h

Large diffs are not rendered by default.

100 changes: 100 additions & 0 deletions spi/st7789_display_spi/fonts.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#include "fonts.h"

// 8x8 font data (96 printable ASCII characters starting from space)
const uint8_t font_8x8[96][8] = {
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // space
{0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // !
{0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // "
{0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // #
{0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // $
{0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // %
{0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // &
{0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // '
{0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // (
{0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // )
{0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // *
{0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // +
{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x06, 0x00}, // ,
{0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // -
{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // .
{0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // /
{0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // 0
{0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // 1
{0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // 2
{0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // 3
{0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // 4
{0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // 5
{0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // 6
{0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // 7
{0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // 8
{0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // 9
{0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // :
{0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x06, 0x00}, // ;
{0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // <
{0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // =
{0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // >
{0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // ?
{0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // @
{0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // A
{0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // B
{0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // C
{0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // D
{0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // E
{0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // F
{0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // G
{0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // H
{0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // I
{0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // J
{0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // K
{0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // L
{0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // M
{0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // N
{0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // O
{0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // P
{0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // Q
{0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // R
{0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // S
{0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // T
{0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U
{0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // V
{0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // W
{0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // X
{0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // Y
{0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // Z
{0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // [
{0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // backslash
{0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // ]
{0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // ^
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // _
{0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // `
{0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // a
{0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // b
{0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // c
{0x38, 0x30, 0x30, 0x3E, 0x33, 0x33, 0x6E, 0x00}, // d
{0x00, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00}, // e
{0x1C, 0x36, 0x06, 0x0F, 0x06, 0x06, 0x0F, 0x00}, // f
{0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // g
{0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // h
{0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // i
{0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // j
{0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // k
{0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // l
{0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // m
{0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // n
{0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // o
{0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // p
{0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // q
{0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // r
{0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // s
{0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // t
{0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // u
{0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // v
{0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // w
{0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // x
{0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // y
{0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // z
{0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // {
{0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // |
{0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // }
{0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ~
};
9 changes: 9 additions & 0 deletions spi/st7789_display_spi/fonts.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef FONTS_H
#define FONTS_H

#include <stdint.h>

// 8x8 font bitmap data
extern const uint8_t font_8x8[96][8];

#endif
203 changes: 203 additions & 0 deletions spi/st7789_display_spi/st7789_display_demo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
#include "st7789_display_demo.h"
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "hardware/gpio.h"
#include "fonts.h"
#include "chicken_sprite.h"
#include <stdlib.h>

static uint8_t rotation = 0;

static void st7789_write_cmd(uint8_t cmd) {
gpio_put(PIN_DC, 0);
gpio_put(PIN_CS, 0);
spi_write_blocking(SPI_PORT, &cmd, 1);
gpio_put(PIN_CS, 1);
}

static void st7789_write_data(uint8_t data) {
gpio_put(PIN_DC, 1);
gpio_put(PIN_CS, 0);
spi_write_blocking(SPI_PORT, &data, 1);
gpio_put(PIN_CS, 1);
}

static void st7789_write_data16(uint16_t data) {
uint8_t buf[2] = { data >> 8, data & 0xFF };
gpio_put(PIN_DC, 1);
gpio_put(PIN_CS, 0);
spi_write_blocking(SPI_PORT, buf, 2);
gpio_put(PIN_CS, 1);
}

static void st7789_set_address_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
st7789_write_cmd(0x2A);
st7789_write_data(x0 >> 8);
st7789_write_data(x0 & 0xFF);
st7789_write_data(x1 >> 8);
st7789_write_data(x1 & 0xFF);

st7789_write_cmd(0x2B);
st7789_write_data(y0 >> 8);
st7789_write_data(y0 & 0xFF);
st7789_write_data(y1 >> 8);
st7789_write_data(y1 & 0xFF);

st7789_write_cmd(0x2C);
}

static void st7789_init(void) {
spi_init(SPI_PORT, 62.5 * 1000 * 1000);
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);

gpio_init(PIN_CS);
gpio_init(PIN_DC);
gpio_init(PIN_RST);
gpio_init(PIN_BL);
gpio_set_dir(PIN_CS, GPIO_OUT);
gpio_set_dir(PIN_DC, GPIO_OUT);
gpio_set_dir(PIN_RST, GPIO_OUT);
gpio_set_dir(PIN_BL, GPIO_OUT);

gpio_put(PIN_BL, 1);

gpio_put(PIN_RST, 0);
sleep_ms(50);
gpio_put(PIN_RST, 1);
sleep_ms(120);

st7789_write_cmd(0x36);
st7789_write_data(0x00);

st7789_write_cmd(0x3A);
st7789_write_data(0x55);

st7789_write_cmd(0x21);

st7789_write_cmd(0x11);
sleep_ms(120);

st7789_write_cmd(0x29);
sleep_ms(20);
}

static void st7789_set_rotation(uint8_t r) {
rotation = r % 4;
st7789_write_cmd(0x36);
switch (rotation) {
case 0: st7789_write_data(0x00); break;
case 1: st7789_write_data(0x60); break;
case 2: st7789_write_data(0xC0); break;
case 3: st7789_write_data(0xA0); break;
}
}

static void st7789_draw_pixel(uint16_t x, uint16_t y, uint16_t color) {
if (x >= DISPLAY_WIDTH || y >= DISPLAY_HEIGHT) return;
st7789_set_address_window(x, y, x, y);
st7789_write_data16(color);
}

static void st7789_fill_screen(uint16_t color) {
st7789_set_address_window(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
gpio_put(PIN_DC, 1);
gpio_put(PIN_CS, 0);
uint8_t buf[2] = { color >> 8, color & 0xFF };
for (uint32_t i = 0; i < DISPLAY_WIDTH * DISPLAY_HEIGHT; i++) {
spi_write_blocking(SPI_PORT, buf, 2);
}
gpio_put(PIN_CS, 1);
}

static void st7789_draw_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) {
int16_t dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int16_t dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int16_t err = dx + dy, e2;

while (true) {
st7789_draw_pixel(x0, y0, color);
if (x0 == x1 && y0 == y1) break;
e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; }
if (e2 <= dx) { err += dx; y0 += sy; }
}
}

static void st7789_draw_char(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bg, uint8_t size) {
if (c < 32 || c > 126) return;
const uint8_t *glyph = font_8x8[c - 32];
for (int8_t i = 0; i < 8; i++) {
uint8_t line = glyph[i];
for (int8_t j = 0; j < 8; j++) {
uint16_t px = x + j * size;
uint16_t py = y + i * size;
uint16_t col = (line & (1 << j)) ? color : bg;
for (uint8_t sx = 0; sx < size; sx++) {
for (uint8_t sy = 0; sy < size; sy++) {
st7789_draw_pixel(px + sx, py + sy, col);
}
}
}
}
}

static void st7789_draw_string(uint16_t x, uint16_t y, const char *str, uint16_t color, uint16_t bg, uint8_t size) {
uint16_t start_x = x;
while (*str) {
if (*str == '\n') {
y += 8 * size;
x = start_x;
} else {
st7789_draw_char(x, y, *str, color, bg, size);
x += 8 * size;
}
str++;
}
}

void draw_chicken(uint16_t top_left_x, uint16_t top_left_y) {
for (uint8_t row = 0; row < CHICKEN_HEIGHT; row++) {
for (uint8_t col = 0; col < CHICKEN_WIDTH; col++) {
uint16_t color = chicken_sprite[row][col];
if (color != TRANSPARENT) {
st7789_draw_pixel(top_left_x + col, top_left_y + row, color);
}
}
}
}

static void draw_initial_screen() {

// Cycle screen
st7789_fill_screen(RED);
sleep_ms(1000);
st7789_fill_screen(GREEN);
sleep_ms(1000);
st7789_fill_screen(BLUE);
sleep_ms(1000);
st7789_fill_screen(BLACK);

// Header
st7789_draw_string(20, 10, "Pico Display Demo", WHITE, BLACK, 2);
st7789_draw_line(10, 35, 310, 35, WHITE);

// Placeholder lines -> adjust as necessary
st7789_draw_string(20, 50, "Perhaps one day,", GREEN, BLACK, 1);
st7789_draw_string(20, 85, "a hen will come out to play", MAGENTA, BLACK, 1);
st7789_draw_string(20, 120, "and lead the world astray.", YELLOW, BLACK, 1);
st7789_draw_string(20, 155, "She is coming now.", WHITE, BLACK, 1);
st7789_draw_string(20, 190, "Beware the demon chicken,", CYAN, BLACK, 1);

// Footer
st7789_draw_string(10, 210, "the demon chicken is coming,", RED, BLACK, 1);
st7789_draw_string(10, 230, "and you cannot escape her.", RED, BLACK, 1);
}

int main() {
st7789_init();
st7789_set_rotation(1); // Landscape
draw_initial_screen();
draw_chicken(240, 80); // optional chicken
return 0;
}
27 changes: 27 additions & 0 deletions spi/st7789_display_spi/st7789_display_demo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Display Configuration - adjust as needed
#define PIN_MOSI 11 // MOSI (SPI data to display)
#define PIN_SCK 10 // CLK (SPI clock)
#define PIN_CS 9 // LCD_CS
#define PIN_DC 8 // LCD_DC
#define PIN_RST 15 // LCD_RST

// Display Dimensions - adjust as needed
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240

// Backlight control
#define PIN_BL 13 // LCD_BL (Backlight, if controllable)

// SPI Configuration
#define SPI_PORT spi1
#define SPI_BAUDRATE 10000000 // 10MHz

// Display Colors (RGB565)
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF