Merge pull request #702 from pimoroni/docs/inky73

Inky 7.3 tweaks & docs
refactor/micropython-cmake v1.19.16
Philip Howard 2023-03-08 21:02:06 +00:00 zatwierdzone przez GitHub
commit faf4efac34
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
16 zmienionych plików z 578 dodań i 79 usunięć

Wyświetl plik

@ -54,7 +54,7 @@ namespace pimoroni {
}
datetime_t PCF85063A::get_datetime() {
static uint8_t result[7] = {0};
uint8_t result[7] = {0};
i2c->read_bytes(address, Registers::SECONDS, result, 7);
@ -72,7 +72,7 @@ namespace pimoroni {
}
void PCF85063A::set_datetime(datetime_t *t) {
static uint8_t data[7] = {
uint8_t data[7] = {
bcd_encode((uint)t->sec),
bcd_encode((uint)t->min),
bcd_encode((uint)t->hour),

Wyświetl plik

@ -50,6 +50,10 @@ namespace pimoroni {
}
}
void PicoGraphics::set_thickness(uint t) {
thickness = t;
}
void PicoGraphics::set_clip(const Rect &r) {
clip = bounds.intersection(r);
}
@ -284,6 +288,25 @@ namespace pimoroni {
}
void PicoGraphics::thick_line(Point p1, Point p2, uint thickness) {
int32_t ht = thickness / 2;
int32_t t = (int32_t)thickness;
// fast horizontal line
if(p1.y == p2.y) {
int32_t start = std::min(p1.x, p2.x);
int32_t end = std::max(p1.x, p2.x);
rectangle(Rect(start, p1.y - ht, end - start, t));
return;
}
// fast vertical line
if(p1.x == p2.x) {
int32_t start = std::min(p1.y, p2.y);
int32_t length = std::max(p1.y, p2.y) - start;
rectangle(Rect(p1.x - ht, start, t, length));
return;
}
// general purpose line
// lines are either "shallow" or "steep" based on whether the x delta
// is greater than the y delta
@ -298,8 +321,7 @@ namespace pimoroni {
int32_t x = p1.x;
int32_t y = p1.y << 16;
while(s--) {
int32_t ht = thickness / 2;
rectangle({x - ht, (y >> 16) - ht, ht * 2, ht * 2});
rectangle({x - ht, (y >> 16) - ht, t, t});
y += sy;
x += sx;
}
@ -311,8 +333,7 @@ namespace pimoroni {
int32_t y = p1.y;
int32_t x = p1.x << 16;
while(s--) {
int32_t ht = thickness / 2;
rectangle({(x >> 16) - ht, y - ht, ht * 2, ht * 2});
rectangle({(x >> 16) - ht, y - ht, t, t});
y += sy;
x += sx;
}

Wyświetl plik

@ -251,7 +251,7 @@ namespace pimoroni {
virtual void set_pen(uint8_t r, uint8_t g, uint8_t b) = 0;
virtual void set_pixel(const Point &p) = 0;
virtual void set_pixel_span(const Point &p, uint l) = 0;
virtual void set_thickness(uint t) = 0;
void set_thickness(uint t);
virtual int get_palette_size();
virtual RGB* get_palette();
@ -304,7 +304,6 @@ namespace pimoroni {
PicoGraphics_Pen1Bit(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override;
void set_pixel(const Point &p) override;
void set_pixel_span(const Point &p, uint l) override;
@ -321,7 +320,6 @@ namespace pimoroni {
PicoGraphics_Pen1BitY(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override;
void set_pixel(const Point &p) override;
void set_pixel_span(const Point &p, uint l) override;
@ -364,7 +362,6 @@ namespace pimoroni {
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int create_pen_hsv(float h, float s, float v) override;
@ -397,7 +394,6 @@ namespace pimoroni {
PicoGraphics_PenP4(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) override;
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int create_pen_hsv(float h, float s, float v) override;
@ -431,7 +427,6 @@ namespace pimoroni {
PicoGraphics_PenP8(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) override;
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int create_pen_hsv(float h, float s, float v) override;
@ -457,7 +452,6 @@ namespace pimoroni {
PicoGraphics_PenRGB332(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int create_pen_hsv(float h, float s, float v) override;
void set_pixel(const Point &p) override;
@ -480,7 +474,6 @@ namespace pimoroni {
PicoGraphics_PenRGB565(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int create_pen_hsv(float h, float s, float v) override;
void set_pixel(const Point &p) override;
@ -497,7 +490,6 @@ namespace pimoroni {
PicoGraphics_PenRGB888(uint16_t width, uint16_t height, void *frame_buffer);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int create_pen_hsv(float h, float s, float v) override;
void set_pixel(const Point &p) override;
@ -570,7 +562,6 @@ namespace pimoroni {
PicoGraphics_PenInky7(uint16_t width, uint16_t height, IDirectDisplayDriver<uint8_t> &direct_display_driver);
void set_pen(uint c) override;
void set_pen(uint8_t r, uint8_t g, uint8_t b) override;
void set_thickness(uint t) override {};
int create_pen(uint8_t r, uint8_t g, uint8_t b) override;
int create_pen_hsv(float h, float s, float v) override;
void set_pixel(const Point &p) override;

Wyświetl plik

@ -18,10 +18,6 @@ namespace pimoroni {
color = std::max(r, std::max(g, b)) >> 4;
}
void PicoGraphics_Pen1Bit::set_thickness(uint t) {
thickness = t;
}
void PicoGraphics_Pen1Bit::set_pixel(const Point &p) {
// pointer to byte in framebuffer that contains this pixel
uint8_t *buf = (uint8_t *)frame_buffer;

Wyświetl plik

@ -18,10 +18,6 @@ namespace pimoroni {
color = std::max(r, std::max(g, b));
}
void PicoGraphics_Pen1BitY::set_thickness(uint t) {
thickness = t;
}
void PicoGraphics_Pen1BitY::set_pixel(const Point &p) {
// pointer to byte in framebuffer that contains this pixel
uint8_t *buf = (uint8_t *)frame_buffer;

Wyświetl plik

@ -12,6 +12,7 @@
- [Random Joke](#random-joke)
- [SD Card Test](#sd-card-test)
- [XKCD Daily](#xkcd-daily)
- [Dithering](#dithering)
## PicoGraphics
@ -91,3 +92,8 @@ The webcomic is rendered "offline" by our feed2image service since xkcd.com requ
For bugs/contributions see: https://github.com/pimoroni/feed2image
### Dithering
[inky_frame_dithering.py](inky_frame_dithering.py)
A basic example showing automatic dithering in action, as PicoGraphics tries to use Inky Frame's limited colour palette to match arbitrary colours.

Wyświetl plik

@ -1,12 +1,17 @@
# An offline image gallery that switches between five jpg images
# Copy them into your Pico's flash using Thonny.
# If you want to use your own images they must be 600 x 448 pixels or smaller
# and saved as *non-progressive* jpgs
# Copy them into the root of your Pico's flash using Thonny.
from pimoroni import ShiftRegister
from picographics import PicoGraphics, DISPLAY_INKY_FRAME
# If you want to use your own images they must be the screen dimensions (or smaller)
# and saved as *non-progressive* jpgs.
# Make sure to uncomment the correct size for your display!
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
from machine import Pin
import jpegdec
from pimoroni import ShiftRegister
# you can change your file names here
IMAGE_A = "jwst1.jpg"
@ -16,7 +21,7 @@ IMAGE_D = "jwst4.jpg"
IMAGE_E = "jwst5.jpg"
# set up the display
display = PicoGraphics(display=DISPLAY_INKY_FRAME)
graphics = PicoGraphics(DISPLAY)
# Inky Frame uses a shift register to read the buttons
SR_CLOCK = 8
@ -42,14 +47,7 @@ hold_vsys_en_pin = Pin(HOLD_VSYS_EN_PIN, Pin.OUT)
hold_vsys_en_pin.value(True)
# Create a new JPEG decoder for our PicoGraphics
j = jpegdec.JPEG(display)
# setup
activity_led.on()
# update the image on Inky every time it's powered up
# comment these lines out if running on battery power
# button_a_led.on()
# display_image(IMAGE_A)
j = jpegdec.JPEG(graphics)
def display_image(filename):
@ -61,9 +59,16 @@ def display_image(filename):
j.decode(0, 0, jpegdec.JPEG_SCALE_FULL)
# Display the result
display.update()
graphics.update()
# setup
activity_led.on()
# update the image on Inky every time it's powered up
# comment these lines out if running on battery power
# button_a_led.on()
# display_image(IMAGE_A)
while True:
button_a_led.off()
button_b_led.off()

Wyświetl plik

@ -1,15 +1,21 @@
# An offline image gallery that switches between five jpg images
# on your SD card (copy them across by plugging your SD into a computer).
# If you want to use your own images they must be 600 x 448 pixels or smaller
# If you want to use your own images they must be the screen dimensions (or smaller)
# and saved as *non-progressive* jpgs
# Make sure to uncomment the correct size for your display!
from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
from pimoroni import ShiftRegister
from picographics import PicoGraphics, DISPLAY_INKY_FRAME
from machine import Pin, SPI
import jpegdec
import sdcard
import uos
# you can change your file names here
IMAGE_A = "sd/jwst1.jpg"
IMAGE_B = "sd/jwst2.jpg"
@ -18,7 +24,7 @@ IMAGE_D = "sd/jwst4.jpg"
IMAGE_E = "sd/jwst5.jpg"
# set up the display
display = PicoGraphics(display=DISPLAY_INKY_FRAME)
graphics = PicoGraphics(DISPLAY)
# Inky Frame uses a shift register to read the buttons
SR_CLOCK = 8
@ -49,14 +55,7 @@ hold_vsys_en_pin = Pin(HOLD_VSYS_EN_PIN, Pin.OUT)
hold_vsys_en_pin.value(True)
# Create a new JPEG decoder for our PicoGraphics
j = jpegdec.JPEG(display)
# setup
activity_led.on()
# update the image on Inky every time it's powered up
# comment these lines out if running on battery power
# button_a_led.on()
# display_image(IMAGE_A)
j = jpegdec.JPEG(graphics)
def display_image(filename):
@ -68,9 +67,16 @@ def display_image(filename):
j.decode(0, 0, jpegdec.JPEG_SCALE_FULL)
# Display the result
display.update()
graphics.update()
# setup
activity_led.on()
# update the image on Inky every time it's powered up
# comment these lines out if running on battery power
# button_a_led.on()
# display_image(IMAGE_A)
while True:
button_a_led.off()
button_b_led.off()

Wyświetl plik

@ -0,0 +1,68 @@
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
graphics.set_pen(1)
graphics.clear()
w = int(WIDTH / 8)
# Solid Colours
for p in range(8):
graphics.set_pen(p)
graphics.rectangle(w * p, 0, w, 50)
# "Greydient"
for x in range(WIDTH):
g = int(x / float(WIDTH) * 255)
graphics.set_pen(graphics.create_pen(g, g, g))
for y in range(30):
graphics.pixel(x, 60 + y)
# Rainbow Gradient
for x in range(WIDTH):
h = x / float(WIDTH)
graphics.set_pen(graphics.create_pen_hsv(h, 1.0, 1.0))
for y in range(100):
graphics.pixel(x, 100 + y)
# Block Colours & Text
graphics.set_pen(graphics.create_pen(128, 128, 0))
graphics.rectangle(0, 210, 200, 100)
graphics.set_pen(graphics.create_pen(200, 200, 200))
graphics.text("Hello", 10, 220)
graphics.text("Hello", 10, 240, scale=4.0)
graphics.set_pen(graphics.create_pen(0, 128, 128))
graphics.rectangle(200, 210, 200, 100)
graphics.set_pen(graphics.create_pen(200, 200, 200))
graphics.text("Hello", 210, 220)
graphics.text("Hello", 210, 240, scale=4.0)
graphics.set_pen(graphics.create_pen(128, 0, 128))
graphics.rectangle(400, 210, 200, 100)
graphics.set_pen(graphics.create_pen(200, 200, 200))
graphics.text("Hello", 410, 220)
graphics.text("Hello", 410, 240, scale=4.0)
# Red, Green and Blue gradients
for x in range(WIDTH):
g = int(x / float(WIDTH) * 255)
for y in range(20):
graphics.set_pen(graphics.create_pen(g, 0, 0))
graphics.pixel(x, 320 + y)
graphics.set_pen(graphics.create_pen(0, g, 0))
graphics.pixel(x, 350 + y)
graphics.set_pen(graphics.create_pen(0, 0, g))
graphics.pixel(x, 380 + y)
graphics.update()

Wyświetl plik

@ -0,0 +1,82 @@
import time
import uasyncio
import WIFI_CONFIG
import inky_frame
from network_manager import NetworkManager
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME as DISPLAY # 5.7"
# from picographics import PicoGraphics, DISPLAY_INKY_FRAME_4 as DISPLAY # 4.0"
from picographics import PicoGraphics, DISPLAY_INKY_FRAME_7 as DISPLAY # 7.3"
# Sync the Inky (always on) RTC to the Pico W so that "time.localtime()" works.
inky_frame.pcf_to_pico_rtc()
# Avoid running code unless we've been triggered by an event
# Keeps this example from locking up Thonny when we want to tweak the code
if inky_frame.woken_by_rtc() or inky_frame.woken_by_button():
graphics = PicoGraphics(DISPLAY)
WIDTH, HEIGHT = graphics.get_bounds()
graphics.set_pen(1)
graphics.clear()
# Look, just because this is an RTC demo,
# doesn't mean we can't make it rainbow.
for x in range(WIDTH):
h = x / WIDTH
p = graphics.create_pen_hsv(h, 1.0, 1.0)
graphics.set_pen(p)
graphics.line(x, 0, x, HEIGHT)
graphics.set_pen(0)
graphics.rectangle(0, 0, WIDTH, 14)
graphics.set_pen(1)
graphics.text("Inky Frame", 1, 0)
graphics.set_pen(0)
def status_handler(mode, status, ip):
print(mode, status, ip)
year, month, day, hour, minute, second, dow, _ = time.localtime()
# Connect to the network and get the time if it's not set
if year < 2023:
connected = False
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler, client_timeout=60)
t_start = time.time()
try:
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
connected = True
except RuntimeError:
pass
t_end = time.time()
if connected:
inky_frame.set_time()
graphics.text("Setting time from network...", 0, 40)
graphics.text(f"Connection took: {t_end-t_start}s", 0, 60)
else:
graphics.text("Failed to connect!", 0, 40)
# Display the date and time
year, month, day, hour, minute, second, dow, _ = time.localtime()
date_time = f"{year:04}/{month:02}/{day:02} {hour:02}:{minute:02}:{second:02}"
graphics.set_font("bitmap8")
text_scale = 8 if WIDTH == 800 else 6
text_height = 8 * text_scale
offset_left = (WIDTH - graphics.measure_text(date_time, scale=text_scale)) // 2
offset_top = (HEIGHT - text_height) // 2
graphics.set_pen(graphics.create_pen(50, 50, 50))
graphics.text(date_time, offset_left + 2, offset_top + 2, scale=text_scale)
graphics.set_pen(1)
graphics.text(date_time, offset_left, offset_top, scale=text_scale)
graphics.update()
inky_frame.sleep_for(2)

Wyświetl plik

@ -25,6 +25,7 @@ Pico Graphics replaces the individual drivers for displays- if you're been using
- [Get Bounds](#get-bounds)
- [Text](#text)
- [Changing The Font](#changing-the-font)
- [Changing The Thickness](#changing-the-thickness)
- [Drawing Text](#drawing-text)
- [Basic Shapes](#basic-shapes)
- [Line](#line)
@ -311,6 +312,17 @@ These are aligned horizontally (x) to their left edge, but vertically (y) to the
* `serif_italic`
* `serif`
#### Changing The Thickness
Vector (Hershey) fonts are drawn with individual lines. By default these are 1px thick, making for very thin and typically illegible text.
To change the thickness of lines used for Vector fonts, use the `set_thickness` method:
```python
display.set_thickness(n)
```
Drawing thick text involves setting a lot more pixels and may slow your drawing down considerably. Be careful how and where you use this.
#### Drawing Text
@ -371,7 +383,13 @@ To draw a straight line at any angle between two specified points:
display.line(x1, y1, x2, y2)
```
The X1/Y1 and X2/Y2 coordinates describe the start and end of the line respectively.
The X1/Y1 and X2/Y2 coordinates describe the start and end of the line respectively.
If you need a thicker line, for an outline or UI elements you can supply a fifth parameter - thickness - like so:
```python
display.line(x1, y1, x2, y2, thickness)
```
#### Circle

Wyświetl plik

@ -37,7 +37,7 @@ MP_DEFINE_CONST_FUN_OBJ_KW(ModPicoGraphics_text_obj, 1, ModPicoGraphics_text);
MP_DEFINE_CONST_FUN_OBJ_KW(ModPicoGraphics_measure_text_obj, 1, ModPicoGraphics_measure_text);
MP_DEFINE_CONST_FUN_OBJ_KW(ModPicoGraphics_polygon_obj, 2, ModPicoGraphics_polygon);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_triangle_obj, 7, 7, ModPicoGraphics_triangle);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_line_obj, 5, 5, ModPicoGraphics_line);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_line_obj, 5, 6, ModPicoGraphics_line);
// Sprites
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_spritesheet_obj, ModPicoGraphics_set_spritesheet);

Wyświetl plik

@ -503,6 +503,10 @@ mp_obj_t ModPicoGraphics_set_font(mp_obj_t self_in, mp_obj_t font) {
mp_int_t ModPicoGraphics_get_framebuffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
(void)flags;
if((PicoGraphicsPenType)self->graphics->pen_type == PEN_INKY7) {
// Special case for Inky Frame 7.3" which uses a PSRAM framebuffer not accessible as a raw buffer
mp_raise_ValueError("No local framebuffer.");
}
bufinfo->buf = self->graphics->frame_buffer;
bufinfo->len = get_required_buffer_size((PicoGraphicsPenType)self->graphics->pen_type, self->graphics->bounds.w, self->graphics->bounds.h);
bufinfo->typecode = 'B';
@ -512,6 +516,11 @@ mp_int_t ModPicoGraphics_get_framebuffer(mp_obj_t self_in, mp_buffer_info_t *buf
mp_obj_t ModPicoGraphics_set_framebuffer(mp_obj_t self_in, mp_obj_t framebuffer) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
if((PicoGraphicsPenType)self->graphics->pen_type == PEN_INKY7) {
// Special case for Inky Frame 7.3" which uses a PSRAM framebuffer not accessible as a raw buffer
mp_raise_ValueError("No local framebuffer.");
}
if (framebuffer == mp_const_none) {
m_del(uint8_t, self->buffer, self->graphics->bounds.w * self->graphics->bounds.h);
self->buffer = nullptr;
@ -749,10 +758,6 @@ mp_obj_t ModPicoGraphics_create_pen_hsv(size_t n_args, const mp_obj_t *args) {
mp_obj_t ModPicoGraphics_set_thickness(mp_obj_t self_in, mp_obj_t pen) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
if(self->graphics->pen_type != PicoGraphics::PEN_1BIT) {
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("Thickness not supported!"));
}
self->graphics->set_thickness(mp_obj_get_int(pen));
return mp_const_none;
@ -1034,16 +1039,27 @@ mp_obj_t ModPicoGraphics_triangle(size_t n_args, const mp_obj_t *args) {
}
mp_obj_t ModPicoGraphics_line(size_t n_args, const mp_obj_t *args) {
enum { ARG_self, ARG_x1, ARG_y1, ARG_x2, ARG_y2 };
enum { ARG_self, ARG_x1, ARG_y1, ARG_x2, ARG_y2, ARG_thickness };
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t);
self->graphics->line(
{mp_obj_get_int(args[ARG_x1]),
mp_obj_get_int(args[ARG_y1])},
{mp_obj_get_int(args[ARG_x2]),
mp_obj_get_int(args[ARG_y2])}
);
if(n_args == 5) {
self->graphics->line(
{mp_obj_get_int(args[ARG_x1]),
mp_obj_get_int(args[ARG_y1])},
{mp_obj_get_int(args[ARG_x2]),
mp_obj_get_int(args[ARG_y2])}
);
}
else if(n_args == 6) {
self->graphics->thick_line(
{mp_obj_get_int(args[ARG_x1]),
mp_obj_get_int(args[ARG_y1])},
{mp_obj_get_int(args[ARG_x2]),
mp_obj_get_int(args[ARG_y2])},
mp_obj_get_int(args[ARG_thickness])
);
}
return mp_const_none;
}

Wyświetl plik

@ -0,0 +1,164 @@
# Inky Frame (MicroPython) <!-- omit in toc -->
Most of your interaction with Inky Frame will be via the PicoGraphics library, but we have created an `inky_frame` module to help you read the onboard A, B, C, D and E buttons and control the LEDs.
- [Pico Graphics](#pico-graphics)
- [Colour \& Dithering](#colour--dithering)
- [Images \& JPEGs](#images--jpegs)
- [Buttons \& LEDs](#buttons--leds)
- [Status LEDs](#status-leds)
- [Battery Power \& RTC](#battery-power--rtc)
- [Function Reference](#function-reference)
- [Wakeup States](#wakeup-states)
- [RTC](#rtc)
- [Other](#other)
- [RAM Usage](#ram-usage)
- [Accessing The Framebuffer](#accessing-the-framebuffer)
## Pico Graphics
You can draw on Inky Frame using our PicoGraphics display library.
- [PicoGraphics MicroPython function reference](../../modules/picographics)
### Colour & Dithering
Inky Frame supports eight native colours, those that it's physically capable of displaying. These are:
* `BLACK` = 0
* `WHITE` = 1
* `GREEN` = 2
* `BLUE` = 3
* `RED` = 4
* `YELLOW` = 5
* `ORANGE` = 6
* `TAUPE` = 7 (only used for dither on 4.0" and 5.7" displays †)
:info: † - the "taupe" colour (also known as clear) is the colour used to reset the display to a default, clear state. We misuse this as an extra colour on 4.0" and 5.7", but the 7.3" display clear colour is a sort of muddy, greenish gradient that's not consistent enough. You can stil use it, by all means, but it wont be considered for dithering.
These colours are available as constants in the `inky_frame` module so you don't have to remember which number corresponds to which, eg:
```python
display.set_pen(inky_frame.YELLOW)
```
You can use colours outside of these eight by using `create_pen(r, g, b)` or `create_pen_hsv(h, s, v)`. eg:
```python
my_colour = display.create_pen(255, 128, 0)
display.set_pen(my_colour)
```
PicoGraphics will do its best to match your colour choice by mixing the available colours with a dither pattern. This works best on large areas of colour such as backgrounds, big UI elements or chunky text. See the `inky_frame_dithering.py` example for a demonstration.
:info: Due to the lack of light blue or pink native colours, Inky Frame isn't very good at reproducing pink, purple, violet or light blue, cyan and teal.
### Images & JPEGs
You can use the `jpegdec` (JPEG decoding) module to display JPEG files on Inky, but you should be aware of some caveats:
1. JPEGs are compressed and lossy, a small JPEG displayed on Inky might show random specks of colour where you don't expect them as PicoGraphics tries its best to dither noise to the nearest available colours.
2. JPEGs are, by default, dithered to the 7 (or 8 on 4.0 and 5.7) available colours. This uses "ordered dither," which is fast, but not very pretty.
3. You can turn off dithering with `jpeg.decode(dither=False)` for a posterised effect.
## Buttons & LEDs
Inky Frame has five user buttons labelled A to E, with LEDs. The buttons are connected via a shift-register and available in the `inky_frame` module as `button_a`, `button_b`, `button_c`, `button_d` and `button_e`.
Each button has some convenient methods for checking if it's pressed:
* `raw()` - get the current state of the button with no debounce (returns `True` the first time it's called if the corresponding button)
* `read()` - read the current button state, observing debounce (50ms)
Additionally each button has some methods for controlling its associated LED:
* `led_on()` - turn the LED on
* `led_off()` - turn the LED off
* `led_toggle()` - toggle the LED
* `led_brightness(0.5)` - set the LED brightness (from 0.0 to 1.0)
### Status LEDs
In addition to the button LEDs there are two status LEDs, `busy` and `wifi`, which are available as:
* `led_busy` - connected to pin `LED_BUSY`
* `led_wifi` - connected to pin `LED_WIFI`
These are instances of the `pimoroni.PWMLED` class, and have the following methods:
* `on()` - turn the LED on
* `off()` - turn the LED off
* `toggle()` - toggle the LED
* `brightness(0.5)` - set the LED brightness (from 0.0 to 1.0)
:info: `toggle()` is provided for compatibility with the `Pin()` class. It's a little odd, since it changes the LED brightness to `1.0 - current_brightness`.
## Battery Power & RTC
When running on battery power, Inky Frame's buttons cause it to wake from a power-off state and start running your code. Additionally an onboard PCF85063A real-time clock runs continuously from battery and can wake up your Frame automatically.
The Inky Frame library includes a number of convenience functions to set the clock and sleep your device. If you want accurate time you must check if the clock is set - usually a simple check against the year works - connect to a network, and set via NTP (Network Time Protocol).
Inky Frame technically has *two* real-time clocks, the external RTC we've added - which remains continuously powered by battery - and the internal RTC of the Pico W. For convenience and compatibility with other code examples we recommend using the external RTC only to set the time on the internal one which makes functions like `time.localtime()` work as you'd expect.
```python
import time
import machine
import inky_frame
inky_frame.pcf_to_pico_rtc() # Sync Inky RTC time to Pico's RTC
year, month, day, dow, hour, minute, second, _ = machine.RTC().datetime()
if year < 2023:
# Connect to network
inky_frame.set_time() # Sets both the Inky and Pico RTC
print(time.localtime())
```
### Function Reference
#### Wakeup States
For your convenience these wakeup state functions also check the *current* state of their associated event, this allows you to run code from Thonny with a button held down - for example - to test how your deployed code will behave on battery-
* `inky_frame.woken_by_rtc()` - Returns `True` if the RTC caused a wakeup, or if the RTC ALARM is currently raised.
* `inky_frame.woken_by_button()` - Returns `True` if a button caused a wakeup, or if a button is currently pressed.
* `inky_frame.woken_by_ext_trigger()` Returns `True` if the external trigger caused a wakeup, or if the trigger is currently asserted.
#### RTC
* `inky_frame.set_time()` - Attempt to run `ntptime.settime()` and set the time on both RTCs
* `inky_frame.pcf_to_pico_rtc()` - Sync from Inky's RTC to the Pico W's RTC
* `inky_frame.pico_rtc_to_pcf()` - Sync from Pico W's RTC to Inky's RTC (sometime useful since Thonny sets the Pico W RTC automatically)
* `inky_frame.sleep_for(minutes)` - Set the RTC alarm for a number of minutes and cut the power. This will completely power off the Pico W, but leave the Inky RTC running to wake it back up.
:info: You can access all Inky RTC (PCF85063A) functions via `inky_frame.rtc`
#### Other
* `inky_frame.turn_off()` - Cut the power to the Pico W (on battery only), only an alarm event or a button press can wake it back up.
## RAM Usage
Both Inky 4.0" and 5.7" use only the Pico's onboard RAM. It's quite cozy. The frame buffers are 3-bits-per-pixel, making Inky 4.0"
On 7.3" we had to add a PSRAM chip to act as the display's framebuffer. Right now it acts exclusively as a framebuffer, but that frees up some Pico RAM to work with so you can do more with that 7.3" panel.
The Inky frame buffer sizes are as follows:
* 4.0" - 640x400 - 96,000 bytes.
* 5.7" - 600x448 - 100,800 bytes.
* 7.3" - 800x480 - 144,000 bytes.
Since MicroPython on a Pico W has only 166k that would have left *just* 22k on 7.3", instead you get (almost) all 166k to play with since the PicoGraphics instance itself uses only 8544 bytes!
### Accessing The Framebuffer
PicoGraphics has undocumented support for accessing its raw framebuffer using `memoryview(graphics)`.
This is useful for copying raw binary images (effectively valid Inky frame buffers saved to a file) avoiding JPEG compression and so forth. For some dicussion about why and how you might do this, see: https://github.com/pimoroni/pimoroni-pico/issues/681
:warning: This *does not work* for Inky 7.3, since there is no framebuffer in memory. PicoGraphics will raise a `ValueError: No local framebuffer.` if you try. We aim to fix this with some hardfault handling sorcery.

Wyświetl plik

@ -1,32 +1,129 @@
from pimoroni import ShiftRegister
from machine import Pin
from pimoroni import ShiftRegister, PWMLED
from machine import Pin, I2C, RTC
from wakeup import get_shift_state, reset_shift_state
from micropython import const
import pcf85063a
import ntptime
import time
BLACK = const(0)
WHITE = const(1)
SR_CLOCK = 8
SR_LATCH = 9
SR_OUT = 10
GREEN = const(2)
BLUE = const(3)
RED = const(4)
YELLOW = const(5)
ORANGE = const(6)
TAUPE = const(7)
LED_A = 11
LED_B = 12
LED_C = 13
LED_D = 14
LED_E = 15
SR_CLOCK = const(8)
SR_LATCH = const(9)
SR_OUT = const(10)
LED_BUSY = 6
LED_WIFI = 7
LED_A = const(11)
LED_B = const(12)
LED_C = const(13)
LED_D = const(14)
LED_E = const(15)
LED_BUSY = const(6)
LED_WIFI = const(7)
HOLD_VSYS_EN = const(2)
RTC_ALARM = const(2)
EXTERNAL_TRIGGER = const(1)
EINK_BUSY = const(0)
SHIFT_STATE = get_shift_state()
reset_shift_state()
i2c = I2C(0)
rtc = pcf85063a.PCF85063A(i2c)
i2c.writeto_mem(0x51, 0x00, b'\x00') # ensure rtc is running (this should be default?)
rtc.enable_timer_interrupt(False)
vsys = Pin(HOLD_VSYS_EN)
vsys.on()
def woken_by_rtc():
mask = (1 << RTC_ALARM)
return bool(sr.read() & mask) or bool(SHIFT_STATE & mask)
def woken_by_ext_trigger():
mask = (1 << EXTERNAL_TRIGGER)
return bool(sr.read() & mask) or bool(SHIFT_STATE & mask)
def woken_by_button():
return bool(sr.read() & 0b11111000) or bool(SHIFT_STATE & 0b11111000)
def pico_rtc_to_pcf():
# Set the PCF85063A to the time stored by Pico W's RTC
year, month, day, dow, hour, minute, second, _ = RTC().datetime()
rtc.datetime((year, month, day, hour, minute, second, dow))
def pcf_to_pico_rtc():
# Set Pico W's RTC to the time stored by the PCF85063A
t = rtc.datetime()
# BUG ERRNO 22, EINVAL, when date read from RTC is invalid for the Pico's RTC.
try:
RTC().datetime((t[0], t[1], t[2], t[6], t[3], t[4], t[5], 0))
return True
except OSError:
return False
def sleep_for(minutes):
year, month, day, hour, minute, second, dow = rtc.datetime()
# if the time is very close to the end of the minute, advance to the next minute
# this aims to fix the edge case where the board goes to sleep right as the RTC triggers, thus never waking up
if second >= 55:
minute += 1
minute += minutes
while minute >= 60:
minute -= 60
hour += 1
if hour >= 24:
hour -= 24
rtc.clear_alarm_flag()
rtc.set_alarm(0, minute, hour)
rtc.enable_alarm_interrupt(True)
turn_off()
# Simulate sleep while on USB power
while minutes > 0:
time.sleep(60)
minutes -= 1
def turn_off():
time.sleep(0.1)
vsys.off()
def set_time():
# Set both Pico W's RTC and PCF85063A to NTP time
ntptime.settime()
pico_rtc_to_pcf()
class Button:
def __init__(self, sr, idx, led, debounce=50):
self.sr = sr
self.startup_state = bool(SHIFT_STATE & (1 << idx))
self.led = Pin(led, Pin.OUT) # LEDs are just regular IOs
self.led = PWMLED(led)
self.led.off()
self._idx = idx
self._debounce_time = debounce
@ -39,6 +136,12 @@ class Button:
def led_off(self):
self.led.off()
def led_brightness(self, brightness):
self.led.brightness(brightness)
def led_toggle(self):
self.led.toggle()
def read(self):
if self.startup_state:
self.startup_state = False
@ -70,5 +173,5 @@ button_c = Button(sr, 5, LED_C)
button_d = Button(sr, 4, LED_D)
button_e = Button(sr, 3, LED_E)
led_busy = Pin(LED_BUSY, Pin.OUT)
led_wifi = Pin(LED_WIFI, Pin.OUT)
led_busy = PWMLED(LED_BUSY)
led_wifi = PWMLED(LED_WIFI)

Wyświetl plik

@ -224,3 +224,30 @@ class ShiftRegister:
def is_set(self, mask):
return self.read() & mask == mask
# A basic wrapper for PWM with regular on/off and toggle functions from Pin
# Intended to be used for driving LEDs with brightness control & compatibility with Pin
class PWMLED:
def __init__(self, pin, invert=False):
self._invert = invert
self._led = PWM(Pin(pin, Pin.OUT))
self._led.freq(1000)
self._brightness = 0
self.brightness(0)
def brightness(self, brightness):
brightness = min(1.0, max(0.0, brightness))
self._brightness = brightness
if self._invert:
brightness = 1.0 - brightness
self._led.duty_u16(int(65535 * brightness))
def on(self):
self.brightness(1)
def off(self):
self.brightness(0)
def toggle(self):
self.brightness(1 - self._brightness)