Merge pull request #561 from pimoroni/driver/st7567

Driver/st7567
This commit is contained in:
Hel Gibbons 2022-11-08 11:24:36 +00:00 committed by GitHub
commit 55821d2896
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1086 additions and 5 deletions

View File

@ -38,3 +38,4 @@ add_subdirectory(vl53l5cx)
add_subdirectory(pcf85063a)
add_subdirectory(pms5003)
add_subdirectory(sh1107)
add_subdirectory(st7567)

View File

@ -0,0 +1 @@
include(st7567.cmake)

42
drivers/st7567/README.md Normal file
View File

@ -0,0 +1,42 @@
# ST7567 Display Driver for Pimoroni LCDs <!-- omit in toc -->
The ST7567 driver supports Serial (SPI) ST7567 displays and is intended for use with:
* Pico GFX Pack
## Setup
Construct an instance of the ST7567 driver with SPI pins.
SPI:
```c++
ST7567 st7567(WIDTH, HEIGHT, {
PIMORONI_SPI_DEFAULT_INSTANCE, // SPI instance
SPI_BG_FRONT_CS, // Chip-select
SPI_DEFAULT_SCK, // SPI Clock
SPI_DEFAULT_MOSI, // SPI Out
PIN_UNUSED, // SPI In
SPI_DEFAULT_DC, // SPI Data/Command
PIN_UNUSED // Backlight
});
```
## Reference
### Update
ST7567's `update` accepts an instance of `PicoGraphics` in 1 bit colour mode:
```c++
st7567.update(&graphics);
```
### Set Backlight
If a backlight pin has been configured, you can set the backlight from 0 to 255:
```c++
st7567.set_backlight(128)
```

View File

@ -0,0 +1,10 @@
set(DRIVER_NAME st7567)
add_library(${DRIVER_NAME} INTERFACE)
target_sources(${DRIVER_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/${DRIVER_NAME}.cpp)
target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib pimoroni_bus hardware_spi hardware_pwm pico_graphics)

183
drivers/st7567/st7567.cpp Normal file
View File

@ -0,0 +1,183 @@
#include "st7567.hpp"
#include <math.h>
#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte) \
(byte & 0x80 ? '1' : '0'), \
(byte & 0x40 ? '1' : '0'), \
(byte & 0x20 ? '1' : '0'), \
(byte & 0x10 ? '1' : '0'), \
(byte & 0x08 ? '1' : '0'), \
(byte & 0x04 ? '1' : '0'), \
(byte & 0x02 ? '1' : '0'), \
(byte & 0x01 ? '1' : '0')
namespace pimoroni {
enum reg : uint8_t {
DISPOFF = 0xAE,//
DISPON = 0xAF,//
SETSTARTLINE = 0x40,//
STARTLINE_MASK = 0x3f,//
REG_RATIO = 0x20,//
SETPAGESTART = 0xb0,//
PAGESTART_MASK = 0x07,//
SETCOLL = 0x00, // # 0x00-0x0f: Set lower column address */
COLL_MASK = 0x0f,//
SETCOLH = 0x10, //# 0x10-0x1f: Set higher column address */
COLH_MASK = 0x0F,//
SEG_DIR_NORMAL = 0xa0, //# 0xa0: Column address 0 is mapped to SEG0 */
SEG_DIR_REV = 0xa1, //# 0xa1: Column address 128 is mapped to S
DISPNORMAL = 0xa6, //# 0xa6: Normal display */
DISPINVERSE = 0xa7, //# 0xa7: Inverse disp
DISPRAM = 0xa4, //# 0xa4: Resume to RAM content display */
DISPENTIRE = 0xa5, //# 0xa5: Entire display
BIAS_1_9 = 0xa2, //# 0xa2: Select BIAS setting 1/9 */
BIAS_1_7 = 0xa3, // # 0xa3: Select BIAS setting
ENTER_RMWMODE = 0xe0, //# 0xe0: Enter the Read Modify Write mode */
EXIT_RMWMODE = 0xee, //# 0xee: Leave the Read Modify Write mode */
EXIT_SOFTRST = 0xe2, // # 0xe2: Software RE
SETCOMNORMAL = 0xc0, //# 0xc0: Set COM output direction, normal mode */
SETCOMREVERSE = 0xc8, // # 0xc8: Set COM output direction, reverse m
POWERCTRL_VF = 0x29, //# 0x29: Control built-in power circuit */
POWERCTRL_VR = 0x2a, //# 0x2a: Control built-in power circuit */
POWERCTRL_VB = 0x2c, //# 0x2c: Control built-in power circuit */
POWERCTRL = 0x2f, // # 0x2c: Control built-in power circ
REG_RES_RR0 = 0x21, //# 0x21: Regulation Resistior ratio */
REG_RES_RR1 = 0x22, //# 0x22: Regulation Resistior ratio */
REG_RES_RR2 = 0x24, // # 0x24: Regulation Resistior ra
SETCONTRAST = 0x81, // # 0x81: Set contrast cont
SETBOOSTER = 0xf8, //# Set booster level */
SETBOOSTER4X = 0x00, //# Set booster level */
SETBOOSTER5X = 0x01 , // # Set booster le
NOP = 0xe3, //# 0xe3: NOP Command for no operation */
STARTBYTES = 0,
};
void ST7567::reset() {
if(reset_pin == PIN_UNUSED)
return;
gpio_put(reset_pin, 0); sleep_ms(10);
sleep_ms(100);
gpio_put(reset_pin, 1); sleep_ms(10);
sleep_ms(100);
}
void ST7567::init(bool auto_init_sequence) {
spi_init(spi, spi_baud);
gpio_set_function(reset_pin, GPIO_FUNC_SIO);
gpio_set_dir(reset_pin, GPIO_OUT);
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
}
//reset display
reset();
// if auto_init_sequence then send initialisation sequence
// for our standard displays based on the width and height
if(auto_init_sequence) {
command(reg::BIAS_1_7);
command(reg::SEG_DIR_NORMAL);
command(reg::SETCOMREVERSE);
command(reg::DISPNORMAL);
command(reg::SETSTARTLINE | 0x00); //Startline from 0x40-0x7F
command(reg::POWERCTRL);
command(reg::REG_RATIO | 4);
command(reg::DISPON);
command(reg::SETCONTRAST);
command(30); // defalut contrast level
}
if(bl != PIN_UNUSED) {
set_backlight(255); // Turn backlight on now surprises have passed
}
}
void ST7567::command(uint8_t command, size_t len, const char *data) {
gpio_put(cs, 0);
gpio_put(dc, 0); // command mode
spi_write_blocking(spi, &command, 1);
gpio_put(cs, 1);
sleep_us(100);
if(data) {
gpio_put(cs, 0);
gpio_put(dc, 1); // data mode
spi_write_blocking(spi, (const uint8_t*)data, len);
gpio_put(cs, 1);
}
}
// Native 16-bit framebuffer update
void ST7567::update(PicoGraphics *graphics) {
uint8_t *fb = (uint8_t *)graphics->frame_buffer;
uint8_t page_buffer[PAGESIZE];
uint8_t page_byte_selector;
uint8_t page_bit_selector;
for(uint8_t page=0; page < 8 ; page++) { //select page
for(uint16_t pixel_index=0 ; pixel_index < (PAGESIZE * 8) ; pixel_index++) { //cycle through a page worth of bits from the fb
page_byte_selector = ((pixel_index % 128));
page_bit_selector = (pixel_index / 128);
if(*fb & (0b10000000 >> (pixel_index % 8))) { // check selected pixel is present
page_buffer[page_byte_selector] |= (1 << page_bit_selector);
}
else {
page_buffer[page_byte_selector] &= ~( 1 << page_bit_selector);
}
if((pixel_index % 8) >= 7) { //increment fb pointer at end of byte
fb++;
}
}
if(graphics->pen_type == PicoGraphics::PEN_1BIT) {
command(reg::ENTER_RMWMODE);
command(reg::SETPAGESTART | page);
command(reg::SETCOLL);
command(reg::SETCOLH);
gpio_put(dc, 1); // data mode
gpio_put(cs, 0);
spi_write_blocking(spi, &page_buffer[0], PAGESIZE );
gpio_put(cs, 1);
gpio_put(dc, 0); // Back to command mode
}
else { //other pen types incompatable
return;
}
}
gpio_put(cs, 1);
}
void ST7567::set_backlight(uint8_t brightness) {
// gamma correct the provided 0-255 brightness value onto a
// 0-65535 range for the pwm counter
float gamma = 2.8;
uint16_t value = (uint16_t)(pow((float)(brightness) / 255.0f, gamma) * 65535.0f + 0.5f);
pwm_set_gpio_level(bl, value);
}
}

64
drivers/st7567/st7567.hpp Normal file
View File

@ -0,0 +1,64 @@
#pragma once
#include "hardware/spi.h"
#include "hardware/pwm.h"
#include "common/pimoroni_bus.hpp"
#include "libraries/pico_graphics/pico_graphics.hpp"
namespace pimoroni {
class ST7567 : public DisplayDriver {
//--------------------------------------------------
// Constants
//--------------------------------------------------
private:
static const uint8_t ROWS = 64;
static const uint8_t COLS = 128;
static const uint8_t PAGESIZE = 128;
//--------------------------------------------------
// Variables
//--------------------------------------------------
private:
spi_inst_t *spi = spi0;
uint32_t dma_channel;
// interface pins with our standard defaults where appropriate
uint cs;
uint dc;
uint sck;
uint mosi;
uint bl;
uint reset_pin=21;
uint32_t spi_baud = 10000000; //10Mhz
uint8_t offset_cols = 0;
uint8_t offset_rows = 0;
//--------------------------------------------------
// Constructors/Destructor
//--------------------------------------------------
public:
ST7567(uint16_t width, uint16_t height, SPIPins pins) :
DisplayDriver(width, height, ROTATE_0),
spi(pins.spi), cs(pins.cs), dc(pins.dc), sck(pins.sck), mosi(pins.mosi), bl(pins.bl) {
init();
}
//--------------------------------------------------
// Methods
//--------------------------------------------------
public:
void update(PicoGraphics *graphics) override;
void set_backlight(uint8_t brightness) override;
void reset();
private:
void init(bool auto_init_sequence = true);
void command(uint8_t command, size_t len = 0, const char *data = NULL);
};
}

View File

@ -57,3 +57,4 @@ add_subdirectory(motor2040)
add_subdirectory(inventor2040w)
add_subdirectory(encoder)
add_subdirectory(galactic_unicorn)
add_subdirectory(gfx_pack)

View File

@ -0,0 +1,12 @@
set(OUTPUT_NAME gfx_pack_demo)
add_executable(
${OUTPUT_NAME}
gfx_demo.cpp
)
# Pull in pico libraries that we need
target_link_libraries(${OUTPUT_NAME} pico_stdlib gfx_pack)
# create map/bin/hex file etc.
pico_add_extra_outputs(${OUTPUT_NAME})

View File

@ -0,0 +1,124 @@
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "libraries/gfx_pack/gfx_pack.hpp"
using namespace pimoroni;
ST7567 st7567(128, 64, GfxPack::gfx_pack_pins);
PicoGraphics_Pen1Bit graphics(st7567.width, st7567.height, nullptr);
RGBLED backlight_rgb(GfxPack::BL_R, GfxPack::BL_G, GfxPack::BL_B, Polarity::ACTIVE_HIGH);
Button button_a(GfxPack::A);
Button button_b(GfxPack::B);
Button button_c(GfxPack::C);
Button button_d(GfxPack::D);
Button button_e(GfxPack::E);
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
int main() {
sleep_ms(100);
st7567.set_backlight(64); // 0 to 255
struct pt {
float x;
float y;
uint8_t r;
float dx;
float dy;
uint16_t pen;
};
std::vector<pt> shapes;
for(int i = 0; i < 10; i++) {
pt shape;
shape.x = rand() % graphics.bounds.w;
shape.y = rand() % graphics.bounds.h;
shape.r = (rand() % 10) + 3;
shape.dx = float(rand() % 255) / 64.0f;
shape.dy = float(rand() % 255) / 64.0f;
shape.pen = rand() % 14;
shapes.push_back(shape);
}
Point text_location(0, 0);
float hue = 0.0f;
while(true) {
if(button_a.raw()) {
text_location.x -= 1;
}
if(button_b.raw()) {
text_location.x += 1;
}
if(button_c.raw()) {
text_location.y -= 1;
}
if(button_d.raw()) {
text_location.y += 1;
}
if(button_e.raw()) {
text_location.x = 0;
text_location.y = 0;
}
graphics.set_pen(0);
graphics.clear();
for(auto &shape : shapes) {
shape.x += shape.dx;
shape.y += shape.dy;
if((shape.x - shape.r) < 0) {
shape.dx *= -1;
shape.x = shape.r;
}
if((shape.x + shape.r) >= graphics.bounds.w) {
shape.dx *= -1;
shape.x = graphics.bounds.w - shape.r;
}
if((shape.y - shape.r) < 0) {
shape.dy *= -1;
shape.y = shape.r;
}
if((shape.y + shape.r) >= graphics.bounds.h) {
shape.dy *= -1;
shape.y = graphics.bounds.h - shape.r;
}
graphics.set_pen(shape.pen);
graphics.circle(Point(shape.x, shape.y), shape.r);
}
graphics.set_pen(15);
graphics.text("Hello World", text_location, 320);
// update screen
backlight_rgb.set_hsv(hue, 1.0f, 1.0f);
hue += 0.002;
st7567.update(&graphics);
sleep_ms(1000/15);
}
return 0;
}

View File

@ -37,3 +37,4 @@ add_subdirectory(adcfft)
add_subdirectory(jpegdec)
add_subdirectory(inky_frame)
add_subdirectory(galactic_unicorn)
add_subdirectory(gfx_pack)

View File

@ -0,0 +1 @@
include(gfx_pack.cmake)

View File

@ -0,0 +1,88 @@
# Pico GFX Pack (C++) <!-- omit in toc -->
This library offers convenient functions for interacting with [Pico GFX Pack](https://shop.pimoroni.com/products/gfxpack) - The Pico GFX Pack adds a 128x64 LCD Matrix display to your headered Raspberry Pi Pico or PicoW, with RGBW backlight and 5 input buttons for all your display and control needs.
- [Example Program](#example-program)
- [Function Reference](#function-reference)
- [PicoGraphics](#picographics)
- [ST7789](#st7789)
## Example Program
The following example sets up Pico Display, displays some basic demo text and graphics and will illuminate the backlight green if the A button is pressed.
```c++
#include "gfx_pack.hpp"
#include "drivers/st7567/st7576.hpp"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "rgbled.hpp"
#include "button.hpp"
// Display driver
ST7567 st7567(128, 64, GfxPack::gfx_pack_pins);
// Graphics library - in 1 Bit mode you get 16 shades with dithering.
PicoGraphics_Pen1Bit graphics(st7567.width, st7567.height, nullptr);
// RGB backlight elements
RGBLED backlight_rgb(GfxPack::BL_R, GfxPack::BL_G, GfxPack::BL_B, Polarity::ACTIVE_HIGH);
// And each button
Button button_a(GfxPack::A);
Button button_b(GfxPack::B);
Button button_c(GfxPack::C);
Button button_d(GfxPack::D);
Button button_e(GfxPack::E);
// RGB LED
RGBLED led(GfxPack::LED_R, GfxPack::LED_G, GfxPack::LED_B);
int main() {
// set the backlight to a value between 0 and 255
// This controls the white elements of the RGBW backlight
st7567.set_backlight(100);
while(true) {
// detect if the A button is pressed (could be A, B, C, D or E)
if(button_a.raw(display.A)) {
// make the LCD glow green
// parameters are red, green, blue all between 0 and 255
// these are also gamma corrected
backlight_rgb.set_rgb(0, 255, 0);
}
// set the colour of the pen
// parameters are red, green, blue all between 0 and 255
graphics.set_pen(30, 40, 50);
// fill the screen with the current pen colour
graphics.clear();
// draw a box to put some text in
graphics.set_pen(10, 20, 30);
Rect text_rect(10, 10, 150, 150);
graphics.rectangle(text_rect);
// write some text inside the box with 10 pixels of margin
// automatically word wrapping
text_rect.deflate(10);
graphics.set_pen(110, 120, 130);
graphics.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
st7567.update(&graphics);
}
}
```
## Function Reference
### PicoGraphics
Pico GFX Pack 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).
### ST7567
Pico Display uses the ST7567 display driver to handle the LCD. For more information [read the ST7567 README.](../../drivers/st7789/README.md).

View File

@ -0,0 +1,7 @@
set(LIB_NAME gfx_pack)
add_library(${LIB_NAME} INTERFACE)
target_include_directories(${LIB_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(${LIB_NAME} INTERFACE pico_stdlib st7567 button rgbled)

View File

@ -0,0 +1,23 @@
#pragma once
#include "drivers/st7567/st7567.hpp"
#include "drivers/button/button.hpp"
#include "drivers/rgbled/rgbled.hpp"
namespace pimoroni {
namespace GfxPack{
static const SPIPins gfx_pack_pins = {PIMORONI_SPI_DEFAULT_INSTANCE, 17, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, PIN_UNUSED, 20, 9};
static const int WIDTH = 128;
static const int HEIGHT = 64;
static const uint8_t A = 12;
static const uint8_t B = 13;
static const uint8_t C = 14;
static const uint8_t D = 15;
static const uint8_t E = 22;
static const uint8_t BL_R = 6;
static const uint8_t BL_G = 7;
static const uint8_t BL_B = 8;
static const uint8_t BL_W = 9;
};
}

View File

@ -0,0 +1,67 @@
import time
import random
from gfx_pack import GfxPack
"""
Bouncing Ball demo
10 balls of different shades will be bouncing around your display.
Funky...
"""
gp = GfxPack()
gp.set_backlight(0, 0, 0, 255)
display = gp.display
WIDTH, HEIGHT = display.get_bounds()
class Ball:
def __init__(self, x, y, r, dx, dy, pen):
self.x = x
self.y = y
self.r = r
self.dx = dx
self.dy = dy
self.pen = pen
# initialise shapes
balls = []
for i in range(0, 10):
r = random.randint(0, 10) + 3
balls.append(
Ball(
random.randint(r, r + (WIDTH - 2 * r)),
random.randint(r, r + (HEIGHT - 2 * r)),
r,
(14 - r) / 2,
(14 - r) / 2,
random.randint(0, 15),
)
)
while True:
display.set_pen(0)
display.clear()
for ball in balls:
ball.x += ball.dx
ball.y += ball.dy
xmax = WIDTH - ball.r
xmin = ball.r
ymax = HEIGHT - ball.r
ymin = ball.r
if ball.x < xmin or ball.x > xmax:
ball.dx *= -1
if ball.y < ymin or ball.y > ymax:
ball.dy *= -1
display.set_pen(ball.pen)
display.circle(int(ball.x), int(ball.y), int(ball.r))
display.update()
time.sleep(0.05)

View File

@ -0,0 +1,71 @@
# This example borrows a CircuitPython hsv_to_rgb function to cycle through some rainbows on GFX Pack's screen.
# If you're into rainbows, HSV (Hue, Saturation, Value) is very useful!
import time
from gfx_pack import GfxPack
gp = GfxPack()
display = gp.display
WIDTH, HEIGHT = display.get_bounds()
display.set_backlight(0) # turn off the white component of the backlight
DANCE_TIME = 1.0
# From CPython Lib/colorsys.py
def hsv_to_rgb(h, s, v):
if s == 0.0:
return v, v, v
i = int(h * 6.0)
f = (h * 6.0) - i
p = v * (1.0 - s)
q = v * (1.0 - s * f)
t = v * (1.0 - s * (1.0 - f))
i = i % 6
if i == 0:
return v, t, p
if i == 1:
return q, v, p
if i == 2:
return p, v, t
if i == 3:
return p, q, v
if i == 4:
return t, p, v
if i == 5:
return v, p, q
# some variables to keep track of rainbows and dancing
h = 0
dancing = True
last_time = time.ticks_ms()
display.set_font("bitmap8")
while True:
time_ms = time.ticks_ms()
h += 1
r, g, b = [int(255 * c) for c in hsv_to_rgb(h / 360.0, 1.0, 1.0)] # rainbow magic
gp.set_backlight(r, g, b) # Set backlight to a converted HSV value
display.set_pen(0) # Set pen to white
display.clear()
display.set_pen(15) # Set pen to black
# draw text and mans
if dancing:
display.text("pico!", 9, 5, WIDTH, 2)
display.text("\\o\\ \\o\\ \\o\\ ", 7, 25, WIDTH, 2)
display.text("\\o\\ \\o\\ \\o\\ ", 7, 45, WIDTH, 2)
else:
display.text("disco!", 69, 5, WIDTH, 2)
display.text("/o/ /o/ /o/ ", 21, 25, WIDTH, 2)
display.text("/o/ /o/ /o/ ", 21, 45, WIDTH, 2)
# our main loop is faster than our dancing loop
# this 'if' checks when it's time to dance
if time_ms - last_time > DANCE_TIME * 1000:
dancing = not dancing
last_time = time_ms
display.update()
time.sleep(1.0 / 60)

View File

@ -0,0 +1,153 @@
import time
import random
from gfx_pack import GfxPack, SWITCH_A, SWITCH_B, SWITCH_C, SWITCH_D, SWITCH_E
"""
Basic Snake demo for GFX Pack
Feel free to add your own improvements :)
A = up
B = down
C = reset
D = left
E = right
"""
# Constants for next move state
MOVE_UP = 0
MOVE_DOWN = 1
MOVE_LEFT = 2
MOVE_RIGHT = 3
# In-game variables
next_move = MOVE_RIGHT
score = 0
head_position = (30, 30)
segments = [head_position]
ate_apple = False
apple_position = None
gp = GfxPack()
gp.set_backlight(0, 0, 0, 255)
display = gp.display
WIDTH, HEIGHT = display.get_bounds()
# Draw the apple in a random possition within the play field
def set_new_apple():
global apple_position
apple_position = random.randint(0, WIDTH), random.randint(30, HEIGHT)
# Reset game to start position and score
def game_over():
global score, segments, head_position, ate_apple
score = 0
head_position = (30, 30)
segments = [head_position]
ate_apple = False
set_new_apple()
pass
# Poll all the butttons to see if anything has been pressed and change next direction accoringly
def check_button():
global next_move, ate_apple
if gp.switch_pressed(SWITCH_A):
if next_move != MOVE_DOWN:
next_move = MOVE_UP
elif gp.switch_pressed(SWITCH_B):
if next_move != MOVE_UP:
next_move = MOVE_DOWN
elif gp.switch_pressed(SWITCH_D):
if next_move != MOVE_RIGHT:
next_move = MOVE_LEFT
elif gp.switch_pressed(SWITCH_E):
if next_move != MOVE_LEFT:
next_move = MOVE_RIGHT
elif gp.switch_pressed(SWITCH_C):
game_over()
# If the snake head and apple are on the same pixel the apple has been eaten
def check_eaten():
global ate_apple, head_position, apple_position, score
if head_position == apple_position:
ate_apple = True
score += 1
set_new_apple()
def check_collision():
# Check if the head or any of the tail segments are on the same pixel
for index in range(len(segments) - 1):
if head_position == segments[index]:
game_over()
return
# Check the snake head has not gone beyond the play area
if head_position[0] >= WIDTH:
game_over()
if head_position[0] <= 0:
game_over()
if head_position[1] >= HEIGHT:
game_over()
if head_position[1] <= 20:
game_over()
def move():
global head_position, segments, ate_apple
head_x, head_y = head_position
if next_move == MOVE_UP:
head_y -= 1
elif next_move == MOVE_DOWN:
head_y += 1
elif next_move == MOVE_LEFT:
head_x -= 1
elif next_move == MOVE_RIGHT:
head_x += 1
head_position = (head_x, head_y)
# Add head to body segments
segments.append(head_position)
# I there is no apple remove end of the tail otherwise tail grows by 1
if ate_apple:
ate_apple = False
else:
segments.pop(0)
def draw():
display.set_pen(0)
display.clear()
display.set_pen(15)
# Draw play field including score
display.text("score: {0}".format(score), 0, 0)
display.line(0, 20, 127, 20)
display.line(0, 63, 127, 63)
display.line(0, 63, 0, 20)
display.line(128, 63, 127, 20)
# Draw apple
display.pixel(apple_position[0], apple_position[1])
# Drawing snake
for segment in segments:
display.pixel(segment[0], segment[1])
display.update()
# Make sure game is reset to begin with
game_over()
while True:
# Game logic
check_button()
check_eaten()
move()
check_collision()
draw()
time.sleep(0.2)

View File

@ -0,0 +1,72 @@
import time
import machine
from gfx_pack import GfxPack
from breakout_bme68x import BreakoutBME68X, STATUS_HEATER_STABLE
"""
GFX temp DEMO
This demo uses a BME680 or BME688 attached to the QWST connector to
measure temperature, pressure, and humidity and display it on the GFX display,
or the internal temperature sensor can be used in place of the
BME68x breakout. Just change use_bme68x_breakout to False
"""
# Settings
lower_temp_bound = 15
upper_temp_bound = 30
use_bme68x_breakout = True
sensor_temp = machine.ADC(4)
conversion_factor = 3.3 / (65535) # used for calculating a temperature from the raw sensor reading
gp = GfxPack()
gp.set_backlight(0, 0, 0) # turn the RGB backlight off
display = gp.display
display.set_backlight(0.4) # set the white to a low value
if use_bme68x_breakout:
bmp = BreakoutBME68X(gp.i2c)
display.set_pen(0)
display.clear()
display.set_font("bitmap14_outline")
while True:
# Clear display
display.set_pen(0)
display.clear()
display.set_pen(15)
display.text("GFXPack Temp demo", 0, 0, scale=0.1)
if use_bme68x_breakout:
temperature, pressure, humidity, gas, status, _, _ = bmp.read()
display.text("Temp: {:0.2f}c".format(temperature), 0, 20, scale=0.2)
display.text("Press: {:0.2f}Pa".format(pressure), 0, 35, scale=0.2)
display.text("Humid: {:0.2f}%".format(humidity), 0, 50, scale=0.2)
heater = "Stable" if status & STATUS_HEATER_STABLE else "Unstable"
print("{:0.2f}c, {:0.2f}Pa, {:0.2f}%, {:0.2f} Ohms, Heater: {}".format(
temperature, pressure, humidity, gas, heater))
else:
reading = sensor_temp.read_u16() * conversion_factor
temperature = 27 - (reading - 0.706) / 0.001721
display.text("Temperature", 25, 15, scale=0.2)
display.text("{:0.2f}c".format(temperature), 25, 30, scale=2)
if temperature < lower_temp_bound:
r = 0
b = 255
elif temperature > upper_temp_bound:
r = 255
b = 0
else:
r = (temperature - lower_temp_bound) / (upper_temp_bound - lower_temp_bound) * 255
b = 255 - ((temperature - lower_temp_bound) / (upper_temp_bound - lower_temp_bound) * 255)
gp.set_backlight(r, 0, b)
display.update()
time.sleep(0.2)

View File

@ -63,6 +63,7 @@ Bear in mind that MicroPython has only 192K of RAM available- a 320x240 pixel di
* 128x128 I2C OLED - `DISPLAY_I2C_OLED_128X128`
* Pico Inky Pack - 296x128 mono e-ink - `DISPLAY_INKY_PACK`
* Inky Frame - 600x447 7-colour e-ink - `DISPLAY_INKY_FRAME`
* Pico GFX Pack - 128x64 mono LCD Matrix - `DISPLAY_GFX_PACK`
* Galactic Unicorn - 53x11 LED Matrix - `DISPLAY_GALACTIC_UNICORN`
### Supported Graphics Modes (Pen Type)

View File

@ -10,6 +10,7 @@ target_sources(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/sh1107/sh1107.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/uc8151/uc8151.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/uc8159/uc8159.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/st7567/st7567.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/pico_graphics.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/pico_graphics_pen_1bit.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/pico_graphics_pen_1bitY.cpp

View File

@ -126,6 +126,7 @@ STATIC const mp_map_elem_t picographics_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INKY_FRAME), MP_ROM_INT(DISPLAY_INKY_FRAME) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_INKY_FRAME_4), MP_ROM_INT(DISPLAY_INKY_FRAME_4) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_GALACTIC_UNICORN), MP_ROM_INT(DISPLAY_GALACTIC_UNICORN) },
{ MP_ROM_QSTR(MP_QSTR_DISPLAY_GFX_PACK), MP_ROM_INT(DISPLAY_GFX_PACK) },
{ MP_ROM_QSTR(MP_QSTR_PEN_1BIT), MP_ROM_INT(PEN_1BIT) },
{ MP_ROM_QSTR(MP_QSTR_PEN_P4), MP_ROM_INT(PEN_P4) },

View File

@ -3,6 +3,7 @@
#include "drivers/sh1107/sh1107.hpp"
#include "drivers/uc8151/uc8151.hpp"
#include "drivers/uc8159/uc8159.hpp"
#include "drivers/st7567/st7567.hpp"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "common/pimoroni_common.hpp"
#include "common/pimoroni_bus.hpp"
@ -120,6 +121,13 @@ bool get_display_settings(PicoGraphicsDisplay display, int &width, int &height,
if(rotate == -1) rotate = (int)Rotation::ROTATE_0;
if(pen_type == -1) pen_type = PEN_RGB888;
break;
case DISPLAY_GFX_PACK:
width = 128;
height = 64;
bus_type = BUS_SPI;
if(rotate == -1) rotate = (int)Rotation::ROTATE_0;
if(pen_type == -1) pen_type = PEN_1BIT;
break;
default:
return false;
}
@ -208,6 +216,8 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size
spi_bus = {PIMORONI_SPI_DEFAULT_INSTANCE, SPI_BG_FRONT_CS, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, PIN_UNUSED, 28, PIN_UNUSED};
} else if (display == DISPLAY_INKY_PACK) {
spi_bus = {PIMORONI_SPI_DEFAULT_INSTANCE, SPI_BG_FRONT_CS, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, PIN_UNUSED, 20, PIN_UNUSED};
} else if (display == DISPLAY_GFX_PACK) {
spi_bus = {PIMORONI_SPI_DEFAULT_INSTANCE, 17, SPI_DEFAULT_SCK, SPI_DEFAULT_MOSI, PIN_UNUSED, 20, 9};
}
}
}
@ -235,8 +245,11 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size
} else if (display == DISPLAY_GALACTIC_UNICORN) {
self->display = m_new_class(DisplayDriver, width, height, (Rotation)rotate);
}
else {
} else if (display == DISPLAY_GFX_PACK) {
self->display = m_new_class(ST7567, width, height, spi_bus);
} else {
self->display = m_new_class(ST7789, width, height, (Rotation)rotate, round, spi_bus);
}

View File

@ -14,7 +14,8 @@ enum PicoGraphicsDisplay {
DISPLAY_INKY_PACK,
DISPLAY_INKY_FRAME,
DISPLAY_INKY_FRAME_4,
DISPLAY_GALACTIC_UNICORN
DISPLAY_GALACTIC_UNICORN,
DISPLAY_GFX_PACK
};
enum PicoGraphicsPenType {

View File

@ -0,0 +1,91 @@
# Pico GFX Pack (MicroPython) <!-- omit in toc -->
This library offers convenient functions for interacting with [Pico GFX Pack](https://shop.pimoroni.com/products/gfxpack) - The Pico GFX Pack adds a 128x64 LCD Matrix display to your headered Raspberry Pi Pico or PicoW, with RGBW backlight and 5 input buttons for all your display and control needs.
## Table of Content
- [Table of Content](#table-of-content)
- [GFX Pack Class](#gfx-pack-class)
- [Switches](#switches)
- [RGBW Backlight](#rgbw-backlight)
- [Display](#display)
- [Backlight](#backlight)
## GFX Pack Class
The `GfxPack` class deals with RGBW backlight and buttons on the GFX Pack. To create one, import the `gfx_pack` module, then define a new `board` variable:
```python
import gfx_pack
board = gfx_pack.GfxPack()
```
From here, all features can be accessed by calling functions on `board`. In addition, when using Qwiic / Stemma QT devices, the I2C channel to use can be accessed with `board.i2c`.
### Switches
GFX Pack has five switches just below the display. To read one of the switches, call `.switch_pressed(switch)`, where `switch` is a value from `0` to `.NUM_SWITCHES - 1`. This returns `True` when the specified switch is pressed, and `False` otherwise.
To read a specific input, the `gfx_pack` module contains these handy constants:
* `SWITCH_A` = `0`
* `SWITCH_B` = `1`
* `SWITCH_C` = `2`
* `SWITCH_D` = `3`
* `SWITCH_E` = `4`
```python
if board.switch_pressed(SWITCH_A):
# Do something interesting here!
if board.switch_pressed(SWITCH_B):
# Do something else even more interesting here!
```
### RGBW Backlight
The GFX has an RGB backlight as well as the regular Matrix display backlight to change the colour of the backlight. This is accessed via the following method.
`.set_backlight(r, g, b, w=None)`
Where r, g, b and w are values between 0 and 255
example:
```python
board.set_backlight(255, 0, 0) # Makes the Backlight Red
board.set_backlight(0, 255, 0) # Makes the Backlight Blue
board.set_backlight(0, 0, 255) # Makes the Backlight Green
board.set_backlight(0, 0, 0, 255) # Makes the Backlight White
```
## Display
The display is all handled by our custom picographics drivers the can be accessed via `.display`.
example:
```python
display = board.display
display.text("Hello World!", 0, 0)
display.line(0, 0, 128, 64)
display.update() # Update display with the above items
```
All the picographics functions can be found [Here](../modules/picographics/README.md)
### Backlight
Included in the picographics display drivers is a function for controling the displays white backlight only which is accessed via `.set_backlight()`.
This function takes a floating point value between `0.0` and `1.0`
```python
display = board.display
display.set_backlight(0.0) # Backlight is off
display.set_backlight(0.5) # Backlight is 50%
display.set_backlight(1.0) # Backlight is 100%
```

View File

@ -0,0 +1,45 @@
from pimoroni import RGBLED, Button
from picographics import PicoGraphics, DISPLAY_GFX_PACK
from pimoroni_i2c import PimoroniI2C
# Index Constants
SWITCH_A = 0
SWITCH_B = 1
SWITCH_C = 2
SWITCH_D = 3
SWITCH_E = 4
class GfxPack:
I2C_SDA_PIN = 4
I2C_SCL_PIN = 5
SWITCH_PINS = (12, 13, 14, 15, 22)
LED_R_PIN = 6
LED_G_PIN = 7
LED_B_PIN = 8
# Count Constants
NUM_SWITCHES = 5
def __init__(self):
self.display = PicoGraphics(display=DISPLAY_GFX_PACK)
# Set up the user switches
self.__switches = []
for i in range(self.NUM_SWITCHES):
self.__switches.append(Button(self.SWITCH_PINS[i]))
self.__rgb = RGBLED(GfxPack.LED_R_PIN, GfxPack.LED_G_PIN, GfxPack.LED_B_PIN, invert=False)
# Set up the i2c for Qw/st and Breakout Garden
self.i2c = PimoroniI2C(self.I2C_SDA_PIN, self.I2C_SCL_PIN, 100000)
def switch_pressed(self, switch):
if switch < 0 or switch >= self.NUM_SWITCHES:
raise ValueError("switch out of range. Expected SWITCH_A (0), SWITCH_B (1), SWITCH_C (2), SWITCH_D (3), or SWITCH_E (4)")
return self.__switches[switch].is_pressed
def set_backlight(self, r, g, b, w=None):
self.__rgb.set_rgb(r, g, b)
if w is not None:
self.display.set_backlight(w / 255)

View File

@ -18,7 +18,7 @@ target_link_libraries(usermod INTERFACE usermod_modules_py)
# .py files to copy from modules_py to ports/rp2/modules
#copy_module(usermod_modules_py ${CMAKE_CURRENT_LIST_DIR}/picosystem.py picosystem)
copy_module(usermod_modules_py ${CMAKE_CURRENT_LIST_DIR}/pimoroni.py pimoroni)
copy_module(usermod_modules_py ${CMAKE_CURRENT_LIST_DIR}/gfx_pack.py gfx_pack)
if(PICO_BOARD STREQUAL "pico_w")
copy_module(usermod_modules_py ${CMAKE_CURRENT_LIST_DIR}/automation.py automation)
copy_module(usermod_modules_py ${CMAKE_CURRENT_LIST_DIR}/inventor.py inventor)

View File

@ -0,0 +1,7 @@
{
"folders": [
{
"path": "."
}
]
}