ST7789: Refactor and Bugfixes

* Make ST7789 accept only full config with SPI pins
* Make Generic ST7789 wrapper library handle "slot"
* Update Round LCD and 240x240 Square LCD
* Remove Python Round LCD and 240x240 LCD modules (we need the space!)
* Remove C++ Round LCD and 240x240 LCD libraries (they're a little redundant!)
* Fix garbage collected framebuffer bug in Python ST7789
* Deprecate "flip" in favour of "configure_display"
* Tidy up ST7789, remove dead/unused/commented code
This commit is contained in:
Phil Howard 2022-05-12 18:40:37 +01:00
parent 65fb478b6e
commit 8e1e9df002
24 changed files with 375 additions and 325 deletions

View File

@ -3,9 +3,6 @@
#include <cstdlib>
#include <math.h>
#include "hardware/dma.h"
#include "hardware/pwm.h"
namespace pimoroni {
uint8_t madctl;
uint16_t caset[2] = {0, 0};
@ -20,11 +17,6 @@ namespace pimoroni {
HORIZ_ORDER = 0b00000100
};
#define ROT_240_240_0 0
#define ROT_240_240_90 MADCTL::SWAP_XY | MADCTL::HORIZ_ORDER | MADCTL::COL_ORDER
#define ROT_240_240_180 MADCTL::SCAN_ORDER | MADCTL::HORIZ_ORDER | MADCTL::COL_ORDER | MADCTL::ROW_ORDER
#define ROT_240_240_270 MADCTL::SWAP_XY | MADCTL::HORIZ_ORDER | MADCTL::ROW_ORDER
enum reg {
SWRESET = 0x01,
TEOFF = 0x34,
@ -54,122 +46,69 @@ namespace pimoroni {
PWMFRSEL = 0xCC
};
void ST7789::init(bool auto_init_sequence, bool round, uint32_t spi_baud) {
this->round = round;
void ST7789::init() {
command(reg::SWRESET);
// configure spi interface and pins
spi_init(spi, spi_baud);
sleep_ms(150);
gpio_set_function(dc, GPIO_FUNC_SIO);
gpio_set_dir(dc, GPIO_OUT);
// Common init
command(reg::TEON); // enable frame sync signal if used
command(reg::COLMOD, 1, "\x05"); // 16 bits per pixel
gpio_set_function(cs, GPIO_FUNC_SIO);
gpio_set_dir(cs, GPIO_OUT);
command(reg::PORCTRL, 5, "\x0c\x0c\x00\x33\x33");
command(reg::LCMCTRL, 1, "\x2c");
command(reg::VDVVRHEN, 1, "\x01");
command(reg::VRHS, 1, "\x12");
command(reg::VDVS, 1, "\x20");
command(reg::PWCTRL1, 2, "\xa4\xa1");
command(reg::FRCTRL2, 1, "\x0f");
gpio_set_function(sck, GPIO_FUNC_SPI);
gpio_set_function(mosi, GPIO_FUNC_SPI);
if(miso != PIN_UNUSED) {
gpio_set_function(miso, GPIO_FUNC_SPI);
if(width == 240 && height == 240) {
command(reg::GCTRL, 1, "\x14");
command(reg::VCOMS, 1, "\x37");
command(reg::GMCTRP1, 14, "\xD0\x04\x0D\x11\x13\x2B\x3F\x54\x4C\x18\x0D\x0B\x1F\x23");
command(reg::GMCTRN1, 14, "\xD0\x04\x0C\x11\x13\x2C\x3F\x44\x51\x2F\x1F\x1F\x20\x23");
}
// if supported by the display then the vsync pin is
// toggled high during vertical blanking period
if(vsync != PIN_UNUSED) {
gpio_set_function(vsync, GPIO_FUNC_SIO);
gpio_set_dir(vsync, GPIO_IN);
gpio_set_pulls(vsync, false, true);
if((width == 320 && height == 240)
|| (width == 240 && height == 320)) {
command(reg::GCTRL, 1, "\x35");
command(reg::VCOMS, 1, "\x1f");
command(0xd6, 1, "\xa1"); // ???
command(reg::GMCTRP1, 14, "\xD0\x08\x11\x08\x0C\x15\x39\x33\x50\x36\x13\x14\x29\x2D");
command(reg::GMCTRN1, 14, "\xD0\x08\x10\x08\x06\x06\x39\x44\x51\x0B\x16\x14\x2F\x31");
}
// if a backlight pin is provided then set it up for
// pwm control
command(reg::INVON); // set inversion mode
command(reg::SLPOUT); // leave sleep mode
command(reg::DISPON); // turn display on
sleep_ms(100);
configure_display(false);
if(bl != PIN_UNUSED) {
pwm_config cfg = pwm_get_default_config();
pwm_set_wrap(pwm_gpio_to_slice_num(bl), 65535);
pwm_init(pwm_gpio_to_slice_num(bl), &cfg, true);
gpio_set_function(bl, GPIO_FUNC_PWM);
set_backlight(0); // Turn backlight off initially to avoid nasty surprises
update(); // Send the new buffer to the display to clear any previous content
sleep_ms(50); // Wait for the update to apply
set_backlight(255); // Turn backlight on now surprises have passed
}
// if auto_init_sequence then send initialisation sequence
// for our standard displays based on the width and height
if(auto_init_sequence) {
command(reg::SWRESET);
sleep_ms(150);
// Common init
command(reg::TEON); // enable frame sync signal if used
command(reg::COLMOD, 1, "\x05"); // 16 bits per pixel
command(reg::PORCTRL, 5, "\x0c\x0c\x00\x33\x33");
command(reg::LCMCTRL, 1, "\x2c");
command(reg::VDVVRHEN, 1, "\x01");
command(reg::VRHS, 1, "\x12");
command(reg::VDVS, 1, "\x20");
command(reg::PWCTRL1, 2, "\xa4\xa1");
command(reg::FRCTRL2, 1, "\x0f");
if(width == 240 && height == 240) {
command(reg::GCTRL, 1, "\x14");
command(reg::VCOMS, 1, "\x37");
command(reg::GMCTRP1, 14, "\xD0\x04\x0D\x11\x13\x2B\x3F\x54\x4C\x18\x0D\x0B\x1F\x23");
command(reg::GMCTRN1, 14, "\xD0\x04\x0C\x11\x13\x2C\x3F\x44\x51\x2F\x1F\x1F\x20\x23");
}
if((width == 320 && height == 240)
|| (width == 240 && height == 320)) {
command(reg::GCTRL, 1, "\x35");
command(reg::VCOMS, 1, "\x1f");
command(0xd6, 1, "\xa1"); // ???
command(reg::GMCTRP1, 14, "\xD0\x08\x11\x08\x0C\x15\x39\x33\x50\x36\x13\x14\x29\x2D");
command(reg::GMCTRN1, 14, "\xD0\x08\x10\x08\x06\x06\x39\x44\x51\x0B\x16\x14\x2F\x31");
}
command(reg::INVON); // set inversion mode
command(reg::SLPOUT); // leave sleep mode
command(reg::DISPON); // turn display on
sleep_ms(100);
configure_display(false);
if(bl != PIN_UNUSED) {
update(); // Send the new buffer to the display to clear any previous content
sleep_ms(50); // Wait for the update to apply
set_backlight(255); // Turn backlight on now surprises have passed
}
}
// the dma transfer works but without vsync it's not that useful as you could
// be updating the framebuffer during transfer...
//
// this could be avoided by creating another buffer to draw into and flip
// buffers (but costs another ~100kb of ram)
//
// it's probably not worth it for this particular usecase but will consider it
// some more...
// setup spi for 16-bit transfers
// spi_set_format(spi, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
// initialise dma channel for transmitting pixel data to screen
// dma_channel = dma_claim_unused_channel(true);
// dma_channel_config config = dma_channel_get_default_config(dma_channel);
// channel_config_set_transfer_data_size(&config, DMA_SIZE_16);
// channel_config_set_dreq(&config, spi_get_index(spi) ? DREQ_SPI1_TX : DREQ_SPI0_TX);
// dma_channel_configure(
// dma_channel, &config, &spi_get_hw(spi)->dr, frame_buffer, width * height, false);
}
void ST7789::configure_display(bool rotate180) {
// setup correct addressing window
// 240x240 Square and Round LCD Breakouts
// TODO: How can we support 90 degree rotations here?
if(width == 240 && height == 240) {
caset[0] = 0;
caset[1] = 239;
raset[0] = round ? 40 : 0;
raset[1] = round ? 279 : 239;
madctl = MADCTL::HORIZ_ORDER;
if(round) {
raset[0] = 40;
raset[1] = 279;
} else {
raset[0] = rotate180 ? 80 : 0;
raset[1] = rotate180 ? 329 : 239;
}
madctl = rotate180 ? (MADCTL::COL_ORDER | MADCTL::ROW_ORDER) : 0;
madctl |= MADCTL::HORIZ_ORDER;
}
// Pico Display
@ -216,9 +155,9 @@ namespace pimoroni {
raset[0] = __builtin_bswap16(raset[0]);
raset[1] = __builtin_bswap16(raset[1]);
command(reg::CASET, 4, (char *)caset);
command(reg::RASET, 4, (char *)raset);
command(reg::MADCTL, 1, (char *)&madctl);
command(reg::CASET, 4, (char *)caset);
command(reg::RASET, 4, (char *)raset);
command(reg::MADCTL, 1, (char *)&madctl);
}
spi_inst_t* ST7789::get_spi() const {
@ -246,8 +185,6 @@ namespace pimoroni {
}
void ST7789::command(uint8_t command, size_t len, const char *data) {
//dma_channel_wait_for_finish_blocking(dma_channel);
gpio_put(cs, 0);
gpio_put(dc, 0); // command mode
@ -261,25 +198,8 @@ namespace pimoroni {
gpio_put(cs, 1);
}
void ST7789::update(bool dont_block) {
void ST7789::update() {
command(reg::RAMWR, width * height * sizeof(uint16_t), (const char*)frame_buffer);
/*if(dma_channel_is_busy(dma_channel) && dont_block) {
return;
}
dma_channel_wait_for_finish_blocking(dma_channel);
uint8_t r = reg::RAMWR;
gpio_put(cs, 0);
gpio_put(dc, 0); // command mode
spi_write_blocking(spi, &r, 1);
gpio_put(dc, 1); // data mode
dma_channel_set_read_addr(dma_channel, frame_buffer, true);*/
}
void ST7789::set_backlight(uint8_t brightness) {
@ -290,12 +210,7 @@ namespace pimoroni {
pwm_set_gpio_level(bl, value);
}
void ST7789::vsync_callback(gpio_irq_callback_t callback) {
gpio_set_irq_enabled_with_callback(vsync, GPIO_IRQ_EDGE_RISE, true, callback);
}
void ST7789::flip(){
madctl ^= MADCTL::ROW_ORDER | MADCTL::COL_ORDER;
command(reg::MADCTL, 1, (char *)&madctl);
configure_display(true);
}
}

View File

@ -2,14 +2,15 @@
#include "hardware/spi.h"
#include "hardware/gpio.h"
#include "hardware/pwm.h"
#include "../../common/pimoroni_common.hpp"
namespace pimoroni {
class ST7789 {
spi_inst_t *spi = PIMORONI_SPI_DEFAULT_INSTANCE;
//--------------------------------------------------
// Variables
//--------------------------------------------------
@ -18,16 +19,13 @@ namespace pimoroni {
uint16_t width;
uint16_t height;
bool round;
uint16_t row_stride;
uint32_t dma_channel;
// interface pins with our standard defaults where appropriate
uint cs = SPI_BG_FRONT_CS;
uint dc = SPI_DEFAULT_MISO;
uint sck = SPI_DEFAULT_SCK;
uint mosi = SPI_DEFAULT_MOSI;
uint miso = PIN_UNUSED; // used as data/command
uint bl = SPI_BG_FRONT_PWM;
uint cs;
uint dc;
uint sck;
uint mosi;
uint bl;
uint vsync = PIN_UNUSED; // only available on some products
// The ST7789 requires 16 ns between SPI rising edges.
@ -39,43 +37,47 @@ namespace pimoroni {
// frame buffer where pixel data is stored
uint16_t *frame_buffer;
ST7789(uint16_t width, uint16_t height, uint16_t *frame_buffer, BG_SPI_SLOT slot) :
width(width), height(height), frame_buffer(frame_buffer) {
switch(slot) {
case PICO_EXPLORER_ONBOARD:
cs = SPI_BG_FRONT_CS;
bl = PIN_UNUSED;
break;
case BG_SPI_FRONT:
cs = SPI_BG_FRONT_CS;
bl = SPI_BG_FRONT_PWM;
break;
case BG_SPI_BACK:
cs = SPI_BG_BACK_CS;
bl = SPI_BG_BACK_PWM;
break;
}
if(!this->frame_buffer) {
this->frame_buffer = new uint16_t(width * height);
}
}
ST7789(uint16_t width, uint16_t height, uint16_t *frame_buffer) :
width(width), height(height), frame_buffer(frame_buffer) {}
ST7789(uint16_t width, uint16_t height, uint16_t *frame_buffer,
ST7789(uint16_t width, uint16_t height, bool round, uint16_t *frame_buffer,
spi_inst_t *spi,
uint cs, uint dc, uint sck, uint mosi, uint miso = PIN_UNUSED, uint bl = PIN_UNUSED) :
uint cs, uint dc, uint sck, uint mosi, uint bl = PIN_UNUSED) :
spi(spi),
width(width), height(height),
cs(cs), dc(dc), sck(sck), mosi(mosi), miso(miso), bl(bl), frame_buffer(frame_buffer) {}
width(width), height(height), round(round),
cs(cs), dc(dc), sck(sck), mosi(mosi), bl(bl), frame_buffer(frame_buffer) {
if(!this->frame_buffer) {
this->frame_buffer = new uint16_t[width * height];
}
// configure spi interface and pins
spi_init(spi, SPI_BAUD);
gpio_set_function(dc, GPIO_FUNC_SIO);
gpio_set_dir(dc, GPIO_OUT);
gpio_set_function(cs, GPIO_FUNC_SIO);
gpio_set_dir(cs, GPIO_OUT);
gpio_set_function(sck, GPIO_FUNC_SPI);
gpio_set_function(mosi, GPIO_FUNC_SPI);
// if a backlight pin is provided then set it up for
// pwm control
if(bl != PIN_UNUSED) {
pwm_config cfg = pwm_get_default_config();
pwm_set_wrap(pwm_gpio_to_slice_num(bl), 65535);
pwm_init(pwm_gpio_to_slice_num(bl), &cfg, true);
gpio_set_function(bl, GPIO_FUNC_PWM);
set_backlight(0); // Turn backlight off initially to avoid nasty surprises
}
}
//--------------------------------------------------
// Methods
//--------------------------------------------------
public:
void init(bool auto_init_sequence = true, bool round = false, uint32_t spi_baud = SPI_BAUD);
void init();
void configure_display(bool rotate180);
spi_inst_t* get_spi() const;
@ -86,10 +88,33 @@ namespace pimoroni {
uint get_bl() const;
void command(uint8_t command, size_t len = 0, const char *data = NULL);
void vsync_callback(gpio_irq_callback_t callback);
void update(bool dont_block = false);
void update();
void set_backlight(uint8_t brightness);
void flip();
static uint get_slot_cs(BG_SPI_SLOT slot) {
switch(slot) {
case PICO_EXPLORER_ONBOARD:
return SPI_BG_FRONT_CS;
case BG_SPI_FRONT:
return SPI_BG_FRONT_CS;
case BG_SPI_BACK:
return SPI_BG_BACK_CS;
}
return PIN_UNUSED;
};
static uint get_slot_bl(BG_SPI_SLOT slot) {
switch(slot) {
case PICO_EXPLORER_ONBOARD:
return PIN_UNUSED;
case BG_SPI_FRONT:
return SPI_BG_FRONT_PWM;
case BG_SPI_BACK:
return SPI_BG_BACK_PWM;
}
return PIN_UNUSED;
};
};
}

View File

@ -6,7 +6,7 @@ add_executable(
)
# Pull in pico libraries that we need
target_link_libraries(${OUTPUT_NAME} pico_stdlib breakout_colourlcd240x240)
target_link_libraries(${OUTPUT_NAME} pico_stdlib generic_st7789)
# create map/bin/hex file etc.
pico_add_extra_outputs(${OUTPUT_NAME})

View File

@ -1,15 +1,17 @@
#include <math.h>
#include <vector>
#include "breakout_colourlcd240x240.hpp"
#include "generic_st7789.hpp"
using namespace pimoroni;
uint16_t buffer[BreakoutColourLCD240x240::WIDTH * BreakoutColourLCD240x240::HEIGHT];
BreakoutColourLCD240x240 lcd(buffer);
const int WIDTH = 240;
const int HEIGHT = 240;
ST7789Generic lcd(WIDTH, HEIGHT, false, nullptr, BG_SPI_FRONT);
int main() {
lcd.init();
//lcd.configure_display(false);
lcd.set_backlight(255);
struct pt {
@ -24,9 +26,11 @@ int main() {
std::vector<pt> shapes;
for(int i = 0; i < 100; i++) {
pt shape;
shape.x = rand() % lcd.bounds.w;
shape.y = rand() % lcd.bounds.h;
shape.r = (rand() % 10) + 3;
shape.x = rand() % (lcd.bounds.w - (shape.r * 2));
shape.y = rand() % (lcd.bounds.h - (shape.r * 2));
shape.x += shape.r;
shape.y += shape.r;
shape.dx = float(rand() % 255) / 64.0f;
shape.dy = float(rand() % 255) / 64.0f;
shape.pen = lcd.create_pen(rand() % 255, rand() % 255, rand() % 255);
@ -40,15 +44,18 @@ int main() {
for(auto &shape : shapes) {
shape.x += shape.dx;
shape.y += shape.dy;
if(shape.x < 0) shape.dx *= -1;
if(shape.x >= lcd.bounds.w) shape.dx *= -1;
if(shape.y < 0) shape.dy *= -1;
if(shape.y >= lcd.bounds.h) shape.dy *= -1;
if(shape.x < shape.r) shape.dx *= -1;
if(shape.x >= lcd.bounds.w - shape.r) shape.dx *= -1;
if(shape.y < shape.r) shape.dy *= -1;
if(shape.y >= lcd.bounds.h - shape.r) shape.dy *= -1;
lcd.set_pen(shape.pen);
lcd.circle(Point(shape.x, shape.y), shape.r);
}
lcd.set_pen(255, 255, 255);
lcd.text("Hello World", Point(0, 0), 240);
// update screen
lcd.update();
}

View File

@ -6,7 +6,7 @@ add_executable(
)
# Pull in pico libraries that we need
target_link_libraries(${OUTPUT_NAME} pico_stdlib breakout_roundlcd)
target_link_libraries(${OUTPUT_NAME} pico_stdlib generic_st7789)
# create map/bin/hex file etc.
pico_add_extra_outputs(${OUTPUT_NAME})

View File

@ -3,7 +3,7 @@
#include <vector>
#include <cstdlib>
#include "breakout_roundlcd.hpp"
#include "generic_st7789.hpp"
#include "time.h"
// Place a 1.3 Round SPI LCD in the *front* slot of breakout garden.
@ -11,10 +11,12 @@
using namespace pimoroni;
uint16_t buffer[BreakoutRoundLCD::WIDTH * BreakoutRoundLCD::HEIGHT];
BreakoutRoundLCD display(buffer, BG_SPI_FRONT);
const int WIDTH = 240;
const int HEIGHT = 240;
constexpr float RADIUS = BreakoutRoundLCD::WIDTH / 2;
ST7789Generic display(WIDTH, HEIGHT, true, nullptr, BG_SPI_FRONT);
constexpr float RADIUS = WIDTH / 2;
Pen from_hsv(float h, float s, float v) {
uint8_t r = 0, g = 0, b = 0;
@ -39,7 +41,6 @@ Pen from_hsv(float h, float s, float v) {
}
int main() {
display.init();
display.set_backlight(255);
uint32_t steps = 70;

View File

@ -3,8 +3,6 @@ add_subdirectory(breakout_encoder)
add_subdirectory(breakout_ioexpander)
add_subdirectory(breakout_ltr559)
add_subdirectory(breakout_colourlcd160x80)
add_subdirectory(breakout_colourlcd240x240)
add_subdirectory(breakout_roundlcd)
add_subdirectory(breakout_rgbmatrix5x5)
add_subdirectory(breakout_matrix11x7)
add_subdirectory(breakout_mics6814)

View File

@ -3,24 +3,24 @@
namespace pimoroni {
BreakoutColourLCD240x240::BreakoutColourLCD240x240(uint16_t *buf)
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, buf) {
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, false, buf,
PIMORONI_SPI_DEFAULT_INSTANCE, SPI_BG_FRONT_CS, SPI_DEFAULT_MISO, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, SPI_BG_FRONT_PWM) {
__fb = buf;
}
BreakoutColourLCD240x240::BreakoutColourLCD240x240(uint16_t *buf, spi_inst_t *spi,
uint cs, uint dc, uint sck, uint mosi, uint miso, uint bl)
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, buf, spi, cs, dc, sck, mosi, miso, bl) {
uint cs, uint dc, uint sck, uint mosi, uint bl)
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, false, buf, spi, cs, dc, sck, mosi, bl) {
__fb = buf;
}
BreakoutColourLCD240x240::BreakoutColourLCD240x240(uint16_t *buf, BG_SPI_SLOT slot)
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, buf, slot) {
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, false, buf,
PIMORONI_SPI_DEFAULT_INSTANCE, screen.get_slot_cs(slot), SPI_DEFAULT_MISO, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, screen.get_slot_bl(slot)) {
__fb = buf;
}
void BreakoutColourLCD240x240::init() {
// initialise the screen
screen.init();
}
spi_inst_t* BreakoutColourLCD240x240::get_spi() const {

View File

@ -29,7 +29,7 @@ namespace pimoroni {
public:
BreakoutColourLCD240x240(uint16_t *buf);
BreakoutColourLCD240x240(uint16_t *buf, spi_inst_t *spi,
uint cs, uint dc, uint sck, uint mosi, uint miso = PIN_UNUSED, uint bl = PIN_UNUSED);
uint cs, uint dc, uint sck, uint mosi, uint bl = PIN_UNUSED);
BreakoutColourLCD240x240(uint16_t *buf, BG_SPI_SLOT slot);

View File

@ -3,23 +3,24 @@
namespace pimoroni {
BreakoutRoundLCD::BreakoutRoundLCD(uint16_t *buf)
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, buf) {
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, true, buf,
PIMORONI_SPI_DEFAULT_INSTANCE, SPI_BG_FRONT_CS, SPI_DEFAULT_MISO, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, SPI_BG_FRONT_PWM) {
__fb = buf;
}
BreakoutRoundLCD::BreakoutRoundLCD(uint16_t *buf, spi_inst_t *spi,
uint cs, uint dc, uint sck, uint mosi, uint miso, uint bl)
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, buf, spi, cs, dc, sck, mosi, miso, bl) {
uint cs, uint dc, uint sck, uint mosi, uint bl)
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, true, buf, spi, cs, dc, sck, mosi, bl) {
__fb = buf;
}
BreakoutRoundLCD::BreakoutRoundLCD(uint16_t *buf, BG_SPI_SLOT slot) : PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, buf, slot) {
BreakoutRoundLCD::BreakoutRoundLCD(uint16_t *buf, BG_SPI_SLOT slot)
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, true, buf,
PIMORONI_SPI_DEFAULT_INSTANCE, screen.get_slot_cs(slot), SPI_DEFAULT_MISO, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, screen.get_slot_bl(slot)) {
__fb = buf;
}
void BreakoutRoundLCD::init() {
// initialise the screen
screen.init(true, true);
}
spi_inst_t* BreakoutRoundLCD::get_spi() const {

View File

@ -31,7 +31,7 @@ namespace pimoroni {
public:
BreakoutRoundLCD(uint16_t *buf);
BreakoutRoundLCD(uint16_t *buf, spi_inst_t *spi,
uint cs, uint dc, uint sck, uint mosi, uint miso = PIN_UNUSED, uint bl = PIN_UNUSED);
uint cs, uint dc, uint sck, uint mosi, uint bl = PIN_UNUSED);
BreakoutRoundLCD(uint16_t *buf, BG_SPI_SLOT slot);

View File

@ -0,0 +1,126 @@
# ST7789 - Pico Display Pack & Pico Display Pack 2.0" <!-- omit in toc -->
Our Pico Display Packs offers a vibrant 1.14" (240x135) or 2.0" (320x240) IPS LCD screen for your Raspberry Pi Pico it also includes four switches and and an RGB LED!
We've included helper functions to handle every aspect of drawing to the screen and interfacing with the buttons and LED. See the [function reference](#function-reference) for details.
- [Example Program](#example-program)
- [Function Reference](#function-reference)
- [PicoGraphics](#picographics)
- [init](#init)
- [set_backlight](#set_backlight)
- [set_led](#set_led)
- [is_pressed](#is_pressed)
- [update](#update)
## Example Program
The following example sets up Pico Display, displays some basic demo text and graphics and will illuminate the RGB LED green if the A button is pressed.
```c++
#include "pico_display.hpp"
#include "generic_st7789.hpp"
#include "rgbled.hpp"
#include "button.hpp"
using namespace pimoroni;
uint16_t buffer[PicoDisplay::WIDTH * PicoDisplay::HEIGHT];
PicoDisplay pico_display(buffer);
int main() {
pico_display.init();
// set the backlight to a value between 0 and 255
// the backlight is driven via PWM and is gamma corrected by our
// library to give a gorgeous linear brightness range.
pico_display.set_backlight(100);
while(true) {
// detect if the A button is pressed (could be A, B, X, or Y)
if(pico_display.is_pressed(pico_display.A)) {
// make the led glow green
// parameters are red, green, blue all between 0 and 255
// these are also gamma corrected
pico_display.set_led(0, 255, 0);
}
// set the colour of the pen
// parameters are red, green, blue all between 0 and 255
pico_display.set_pen(30, 40, 50);
// fill the screen with the current pen colour
pico_display.clear();
// draw a box to put some text in
pico_display.set_pen(10, 20, 30);
Rect text_rect(10, 10, 150, 150);
pico_display.rectangle(text_rect);
// write some text inside the box with 10 pixels of margin
// automatically word wrapping
text_rect.deflate(10);
pico_display.set_pen(110, 120, 130);
pico_display.text("This is a message", Point(text_rect.x, text_rect.y), text_rect.w);
// now we've done our drawing let's update the screen
pico_display.update();
}
}
```
## Function Reference
### PicoGraphics
Pico Display uses our Pico Graphics library to draw graphics and text. For more information [read the Pico Graphics function reference.](../pico_graphics/README.md#function-reference).
### init
Sets up Pico Display. `init` must be called before any other functions since it configures the required PWM and GPIO:
```c++
pico_display.init();
```
### set_backlight
Set the display backlight from 0-255.
```c++
pico_display.set_backlight(brightness);
```
Uses hardware PWM to dim the display backlight, dimming values are gamma-corrected to provide smooth brightness transitions across the full range of intensity. This may result in some low values mapping as "off."
### set_led
Sets the RGB LED on Pico Display with an RGB triplet:
```c++
pico_display.set_led(r, g, b);
```
Uses hardware PWM to drive the LED. Values are automatically gamma-corrected to provide smooth brightness transitions and low values may map as "off."
### is_pressed
Reads the GPIO pin connected to one of Pico Display's buttons, returning a `bool` - `true` if it's pressed and `false` if it is released.
```c++
pico_display.is_pressed(button);
```
The button vaule should be a `uint8_t` denoting a pin, and constants `A`, `B`, `X` and `Y` are supplied to make it easier. e:
```c++
bool is_a_button_pressed = pico_display.is_pressed(PicoDisplay::A)
```
### update
To display your changes on Pico Display's screen you need to call `update`:
```c++
pico_display.update();
```

View File

@ -5,28 +5,6 @@
namespace pimoroni {
ST7789Generic::ST7789Generic(int width, int height, BG_SPI_SLOT slot, uint16_t *frame_buffer)
: PicoGraphics(width, height, frame_buffer), st7789(width, height, frame_buffer, slot) {
this->frame_buffer = st7789.frame_buffer;
this->st7789.init(true, false);
}
ST7789Generic::ST7789Generic(int width, int height, uint16_t *frame_buffer)
: PicoGraphics(width, height, frame_buffer), st7789(width, height, frame_buffer, BG_SPI_FRONT) {
this->frame_buffer = st7789.frame_buffer;
this->st7789.init(true, false);
}
ST7789Generic::ST7789Generic(uint16_t width, uint16_t height, spi_inst_t *spi,
uint cs, uint dc, uint sck, uint mosi, uint miso, uint bl,
uint16_t *frame_buffer) :
PicoGraphics(width, height, frame_buffer),
st7789(width, height, frame_buffer, spi, cs, dc, sck, mosi, miso, bl) {
this->frame_buffer = st7789.frame_buffer;
this->st7789.init(true, false);
}
spi_inst_t* ST7789Generic::get_spi() const {
return st7789.get_spi();
}
@ -56,7 +34,7 @@ namespace pimoroni {
}
void ST7789Generic::flip() {
st7789.flip();
st7789.configure_display(true);
}
void ST7789Generic::set_backlight(uint8_t brightness) {

View File

@ -10,11 +10,28 @@ namespace pimoroni {
ST7789 st7789;
public:
ST7789Generic(int width, int height, BG_SPI_SLOT slot, uint16_t *frame_buffer = nullptr);
ST7789Generic(int width, int height, uint16_t *frame_buffer = nullptr);
ST7789Generic(uint16_t width, uint16_t height, spi_inst_t *spi,
uint cs, uint dc, uint sck, uint mosi, uint miso = PIN_UNUSED, uint bl = PIN_UNUSED,
uint16_t *frame_buffer = nullptr);
ST7789Generic(uint16_t width, uint16_t height, bool round=false, uint16_t *frame_buffer=nullptr) :
PicoGraphics(width, height, frame_buffer),
st7789(width, height, round, frame_buffer, PIMORONI_SPI_DEFAULT_INSTANCE, SPI_BG_FRONT_CS, SPI_DEFAULT_MISO, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, SPI_BG_FRONT_PWM) {
this->frame_buffer = st7789.frame_buffer;
this->st7789.init();
};
ST7789Generic(uint16_t width, uint16_t height, bool round, uint16_t *frame_buffer, BG_SPI_SLOT slot) :
PicoGraphics(width, height, frame_buffer),
st7789(width, height, round, frame_buffer, PIMORONI_SPI_DEFAULT_INSTANCE, st7789.get_slot_cs(slot), SPI_DEFAULT_MISO, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, st7789.get_slot_bl(slot)) {
this->frame_buffer = st7789.frame_buffer;
this->st7789.init();
};
ST7789Generic(uint16_t width, uint16_t height, bool round, uint16_t *frame_buffer,
spi_inst_t *spi,
uint cs, uint dc, uint sck, uint mosi, uint bl = PIN_UNUSED) :
PicoGraphics(width, height, frame_buffer),
st7789(width, height, round, frame_buffer, spi, cs, dc, sck, mosi, bl) {
this->frame_buffer = st7789.frame_buffer;
this->st7789.init();
};
spi_inst_t* get_spi() const;
int get_cs() const;
@ -24,7 +41,7 @@ namespace pimoroni {
int get_bl() const;
void update();
void flip();
[[deprecated("Use configure_display(true) instead.")]] void flip();
void set_backlight(uint8_t brightness);
void configure_display(bool rotate180);
};

View File

@ -19,49 +19,60 @@ The following example sets up Pico Display, displays some basic demo text and gr
```c++
#include "pico_display.hpp"
using namespace pimoroni;
#include "generic_st7789.hpp"
#include "rgbled.hpp"
#include "button.hpp"
uint16_t buffer[PicoDisplay::WIDTH * PicoDisplay::HEIGHT];
PicoDisplay pico_display(buffer);
// Swap WIDTH and HEIGHT to rotate 90 degrees
ST7789Generic display(PicoDisplay::WIDTH, PicoDisplay::HEIGHT, buffer);
// Create an RGB LED
RGBLED led(PicoDisplay::LED_R, PicoDisplay::LED_G, PicoDisplay::LED_B);
// And each button
Button button_a(PicoDisplay::A);
Button button_b(PicoDisplay::B);
Button button_x(PicoDisplay::X);
Button button_y(PicoDisplay::Y);
int main() {
pico_display.init();
// set the backlight to a value between 0 and 255
// the backlight is driven via PWM and is gamma corrected by our
// library to give a gorgeous linear brightness range.
pico_display.set_backlight(100);
display.set_backlight(100);
while(true) {
// detect if the A button is pressed (could be A, B, X, or Y)
if(pico_display.is_pressed(pico_display.A)) {
if(button_a.raw(display.A)) {
// make the led glow green
// parameters are red, green, blue all between 0 and 255
// these are also gamma corrected
pico_display.set_led(0, 255, 0);
led.set_rgb(0, 255, 0);
}
// set the colour of the pen
// parameters are red, green, blue all between 0 and 255
pico_display.set_pen(30, 40, 50);
display.set_pen(30, 40, 50);
// fill the screen with the current pen colour
pico_display.clear();
display.clear();
// draw a box to put some text in
pico_display.set_pen(10, 20, 30);
display.set_pen(10, 20, 30);
Rect text_rect(10, 10, 150, 150);
pico_display.rectangle(text_rect);
display.rectangle(text_rect);
// write some text inside the box with 10 pixels of margin
// automatically word wrapping
text_rect.deflate(10);
pico_display.set_pen(110, 120, 130);
pico_display.text("This is a message", Point(text_rect.x, text_rect.y), text_rect.w);
display.set_pen(110, 120, 130);
display.text("This is a message", Point(text_rect.x, text_rect.y), text_rect.w);
// now we've done our drawing let's update the screen
pico_display.update();
display.update();
}
}
```
@ -72,52 +83,32 @@ int main() {
Pico Display uses our Pico Graphics library to draw graphics and text. For more information [read the Pico Graphics function reference.](../pico_graphics/README.md#function-reference).
### init
### configure_display
Sets up Pico Display. `init` must be called before any other functions since it configures the required PWM and GPIO:
Configures an ST7789 display. Done by default, but you can use this to set 180 degree rotation like so:
```c++
pico_display.init();
display.configure_display(true);
```
### flip
Deprecated: calls `configure_display(true);`
### set_backlight
Set the display backlight from 0-255.
```c++
pico_display.set_backlight(brightness);
display.set_backlight(brightness);
```
Uses hardware PWM to dim the display backlight, dimming values are gamma-corrected to provide smooth brightness transitions across the full range of intensity. This may result in some low values mapping as "off."
### set_led
Sets the RGB LED on Pico Display with an RGB triplet:
```c++
pico_display.set_led(r, g, b);
```
Uses hardware PWM to drive the LED. Values are automatically gamma-corrected to provide smooth brightness transitions and low values may map as "off."
### is_pressed
Reads the GPIO pin connected to one of Pico Display's buttons, returning a `bool` - `true` if it's pressed and `false` if it is released.
```c++
pico_display.is_pressed(button);
```
The button vaule should be a `uint8_t` denoting a pin, and constants `A`, `B`, `X` and `Y` are supplied to make it easier. e:
```c++
bool is_a_button_pressed = pico_display.is_pressed(PicoDisplay::A)
```
### update
To display your changes on Pico Display's screen you need to call `update`:
```c++
pico_display.update();
```
display.update();
```

View File

@ -9,12 +9,14 @@
namespace pimoroni {
PicoDisplay::PicoDisplay(uint16_t *buf)
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, buf, BG_SPI_FRONT) {
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, false, buf,
PIMORONI_SPI_DEFAULT_INSTANCE, SPI_BG_FRONT_CS, SPI_DEFAULT_MISO, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, SPI_BG_FRONT_PWM) {
__fb = buf;
}
PicoDisplay::PicoDisplay(uint16_t *buf, int width, int height)
: PicoGraphics(width, height, buf), screen(width, height, buf, BG_SPI_FRONT) {
: PicoGraphics(width, height, buf), screen(width, height, false, buf,
PIMORONI_SPI_DEFAULT_INSTANCE, SPI_BG_FRONT_CS, SPI_DEFAULT_MISO, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, SPI_BG_FRONT_PWM) {
__fb = buf;
}
@ -43,9 +45,6 @@ namespace pimoroni {
gpio_set_function(B, GPIO_FUNC_SIO); gpio_set_dir(B, GPIO_IN); gpio_pull_up(B);
gpio_set_function(X, GPIO_FUNC_SIO); gpio_set_dir(X, GPIO_IN); gpio_pull_up(X);
gpio_set_function(Y, GPIO_FUNC_SIO); gpio_set_dir(Y, GPIO_IN); gpio_pull_up(Y);
// initialise the screen
screen.init();
}
void PicoDisplay::update() {

View File

@ -9,12 +9,14 @@
namespace pimoroni {
PicoDisplay2::PicoDisplay2(uint16_t *buf)
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, buf, BG_SPI_FRONT) {
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, false, buf,
PIMORONI_SPI_DEFAULT_INSTANCE, SPI_BG_FRONT_CS, SPI_DEFAULT_MISO, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, SPI_BG_FRONT_PWM) {
__fb = buf;
}
PicoDisplay2::PicoDisplay2(uint16_t *buf, int width, int height)
: PicoGraphics(width, height, buf), screen(width, height, buf, BG_SPI_FRONT) {
: PicoGraphics(width, height, buf), screen(width, height, false, buf,
PIMORONI_SPI_DEFAULT_INSTANCE, SPI_BG_FRONT_CS, SPI_DEFAULT_MISO, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, SPI_BG_FRONT_PWM) {
__fb = buf;
}
@ -43,9 +45,6 @@ namespace pimoroni {
gpio_set_function(B, GPIO_FUNC_SIO); gpio_set_dir(B, GPIO_IN); gpio_pull_up(B);
gpio_set_function(X, GPIO_FUNC_SIO); gpio_set_dir(X, GPIO_IN); gpio_pull_up(X);
gpio_set_function(Y, GPIO_FUNC_SIO); gpio_set_dir(Y, GPIO_IN); gpio_pull_up(Y);
// initialise the screen
screen.init(true, false);
}
void PicoDisplay2::update() {

View File

@ -15,8 +15,9 @@ const uint8_t MOTOR2P = 11;
namespace pimoroni {
PicoExplorer::PicoExplorer(uint16_t *buf)
: PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, buf, PICO_EXPLORER_ONBOARD) {
__fb = buf;
: PicoGraphics(WIDTH, HEIGHT, buf),
screen(WIDTH, HEIGHT, false, buf, PIMORONI_SPI_DEFAULT_INSTANCE, screen.get_slot_cs(PICO_EXPLORER_ONBOARD), SPI_DEFAULT_MISO, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, screen.get_slot_bl(PICO_EXPLORER_ONBOARD)) {
__fb = buf;
}
void PicoExplorer::init() {
@ -48,9 +49,6 @@ namespace pimoroni {
pwm_init(pwm_gpio_to_slice_num(MOTOR2P), &motor_pwm_cfg, true);
gpio_set_function(MOTOR2P, GPIO_FUNC_PWM);
// initialise the screen
screen.init();
}
void PicoExplorer::update() {

View File

@ -1,12 +1,10 @@
import time
import random
from breakout_colourlcd240x240 import BreakoutColourLCD240x240
from st7789 import ST7789
width = BreakoutColourLCD240x240.WIDTH
height = BreakoutColourLCD240x240.HEIGHT
WIDTH, HEIGHT = 240, 240
display_buffer = bytearray(width * height * 2) # 2-bytes per pixel (RGB565)
display = BreakoutColourLCD240x240(display_buffer)
display = ST7789(WIDTH, HEIGHT, round=False)
display.set_backlight(1.0)
@ -27,8 +25,8 @@ for i in range(0, 100):
r = random.randint(0, 10) + 3
balls.append(
Ball(
random.randint(r, r + (width - 2 * r)),
random.randint(r, r + (height - 2 * r)),
random.randint(r, r + (WIDTH - 2 * r)),
random.randint(r, r + (HEIGHT - 2 * r)),
r,
(14 - r) / 2,
(14 - r) / 2,
@ -44,9 +42,9 @@ while True:
ball.x += ball.dx
ball.y += ball.dy
xmax = width - ball.r
xmax = WIDTH - ball.r
xmin = ball.r
ymax = height - ball.r
ymax = HEIGHT - ball.r
ymin = ball.r
if ball.x < xmin or ball.x > xmax:

View File

@ -1,16 +1,14 @@
import time
import math
from breakout_roundlcd import BreakoutRoundLCD
from st7789 import ST7789
width = BreakoutRoundLCD.WIDTH
height = BreakoutRoundLCD.HEIGHT
WIDTH, HEIGHT = 240, 240
display_buffer = bytearray(width * height * 2) # 2-bytes per pixel (RGB565)
display = BreakoutRoundLCD(display_buffer)
display = ST7789(WIDTH, HEIGHT, round=True)
display.set_backlight(1.0)
RADIUS = width // 2
RADIUS = WIDTH // 2
def hsv_to_rgb(h, s, v):

View File

@ -120,7 +120,7 @@ mp_obj_t BreakoutColourLCD240x240_make_new(const mp_obj_type_t *type, size_t n_a
spi_inst_t *spi = (spi_id == 0) ? spi0 : spi1;
self->breakout = new BreakoutColourLCD240x240((uint16_t *)bufinfo.buf, spi,
args[ARG_cs].u_int, args[ARG_dc].u_int, sck, mosi, PIN_UNUSED, args[ARG_bl].u_int);
args[ARG_cs].u_int, args[ARG_dc].u_int, sck, mosi, args[ARG_bl].u_int);
}
self->breakout->init();

View File

@ -120,7 +120,7 @@ mp_obj_t BreakoutRoundLCD_make_new(const mp_obj_type_t *type, size_t n_args, siz
spi_inst_t *spi = (spi_id == 0) ? spi0 : spi1;
self->breakout = new BreakoutRoundLCD((uint16_t *)bufinfo.buf, spi,
args[ARG_cs].u_int, args[ARG_dc].u_int, sck, mosi, PIN_UNUSED, args[ARG_bl].u_int);
args[ARG_cs].u_int, args[ARG_dc].u_int, sck, mosi, args[ARG_bl].u_int);
}
self->breakout->init();

View File

@ -12,7 +12,6 @@ include(breakout_ioexpander/micropython)
include(breakout_ltr559/micropython)
include(breakout_colourlcd160x80/micropython)
include(breakout_as7262/micropython)
include(breakout_roundlcd/micropython)
include(breakout_rgbmatrix5x5/micropython)
include(breakout_matrix11x7/micropython)
include(breakout_msa301/micropython)
@ -22,7 +21,6 @@ include(breakout_potentiometer/micropython)
include(breakout_rtc/micropython)
include(breakout_trackball/micropython)
include(breakout_sgp30/micropython)
include(breakout_colourlcd240x240/micropython)
include(breakout_bh1745/micropython)
include(breakout_bme68x/micropython)
include(breakout_bme280/micropython)

View File

@ -17,6 +17,7 @@ extern "C" {
typedef struct _GenericST7789_obj_t {
mp_obj_base_t base;
ST7789Generic *st7789;
uint16_t *buffer;
} GenericST7789_obj_t;
/***** Print *****/
@ -51,10 +52,11 @@ void GenericST7789_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kin
mp_obj_t GenericST7789_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
GenericST7789_obj_t *self = nullptr;
enum { ARG_width, ARG_height, ARG_rotate180, ARG_slot, ARG_buffer, ARG_spi, ARG_cs, ARG_dc, ARG_sck, ARG_mosi, ARG_bl };
enum { ARG_width, ARG_height, ARG_round, ARG_rotate180, ARG_slot, ARG_buffer, ARG_spi, ARG_cs, ARG_dc, ARG_sck, ARG_mosi, ARG_bl };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_width, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_height, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_round, MP_ARG_OBJ, {.u_obj = mp_const_false} },
{ MP_QSTR_rotate180, MP_ARG_OBJ, {.u_obj = mp_const_false} },
{ MP_QSTR_slot, MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_buffer, MP_ARG_OBJ, {.u_obj = mp_const_none} },
@ -74,24 +76,24 @@ mp_obj_t GenericST7789_make_new(const mp_obj_type_t *type, size_t n_args, size_t
self->base.type = &GenericST7789_type;
bool rotate180 = args[ARG_rotate180].u_obj == mp_const_true;
bool round = args[ARG_round].u_obj == mp_const_true;
int width = args[ARG_width].u_int;
int height = args[ARG_height].u_int;
uint16_t *buffer;
if (args[ARG_buffer].u_obj != mp_const_none) {
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_RW);
buffer = (uint16_t *)bufinfo.buf;
self->buffer = (uint16_t *)bufinfo.buf;
if(bufinfo.len < (size_t)(width * height * 2)) {
mp_raise_ValueError("Supplied buffer is too small!");
}
} else {
buffer = m_new(uint16_t, width * height);
self->buffer = m_new(uint16_t, width * height);
}
if(args[ARG_slot].u_int != -1) {
BG_SPI_SLOT slot = (BG_SPI_SLOT)args[ARG_slot].u_int;
self->st7789 = new ST7789Generic(width, height, slot, buffer);
self->st7789 = new ST7789Generic(width, height, round, self->buffer, slot);
if (rotate180) {
self->st7789->configure_display(true);
}
@ -119,9 +121,8 @@ mp_obj_t GenericST7789_make_new(const mp_obj_type_t *type, size_t n_args, size_t
mp_raise_ValueError(MP_ERROR_TEXT("bad MOSI pin"));
}
spi_inst_t *spi = (spi_id == 0) ? spi0 : spi1;
self->st7789 = new ST7789Generic(width, height, spi,
cs, dc, sck, mosi, PIN_UNUSED, bl,
buffer);
self->st7789 = new ST7789Generic(width, height, round, self->buffer,
spi, cs, dc, sck, mosi, bl);
if (rotate180) {
self->st7789->configure_display(true);
}