First semi-working Micropython version.

This commit is contained in:
Mike Bell 2024-10-13 16:07:08 +01:00
parent 2c4a17cc49
commit 6b2e2ef5cc
19 changed files with 2069 additions and 17 deletions

129
.github/workflows/micropython.yml vendored Normal file
View file

@ -0,0 +1,129 @@
name: MicroPython
on:
push:
pull_request:
release:
types: [created]
env:
MICROPYTHON_VERSION: feature/psram
MICROPYTHON_FLAVOUR: MichaelBell
PIMORONI_PICO_VERSION: feature/sdk-2.0.0
jobs:
build:
name: ${{ matrix.name }} (${{ matrix.board }} ${{ matrix.variant }})
runs-on: ubuntu-20.04
continue-on-error: true
strategy:
matrix:
include:
- name: pico_plus2_rp2350_psram
board: PIMORONI_PICO_PLUS2
variant: PSRAM
env:
# MicroPython version will be contained in github.event.release.tag_name for releases
RELEASE_FILE: ${{ matrix.name }}-${{ github.event.release.tag_name || github.sha }}-micropython
PIMORONI_PICO_DIR: "${{ github.workspace }}/pimoroni-pico"
USER_C_MODULES: "${{ github.workspace }}/src-${{ github.sha }}/modules/dvhstx.cmake"
TAG_OR_SHA: ${{ github.event.release.tag_name || github.sha }}
MICROPY_BOARD: ${{ matrix.board }}
MICROPY_BOARD_VARIANT: ${{ matrix.variant }}
MICROPY_BOARD_DIR: "${{ github.workspace }}/src-${{ github.sha }}/micropython/board/${{ matrix.BOARD }}"
BOARD_NAME: ${{ matrix.name }}
BUILD_TOOLS: src-${{ github.sha }}/ci/micropython.sh
steps:
- name: "CCache: Restore saved cache"
uses: actions/cache@v4
with:
path: /home/runner/.ccache
key: ccache-micropython-${{ matrix.name }}-${{ github.ref }}-${{ github.sha }}
restore-keys: |
ccache-micropython-${{ matrix.name }}-${{ github.ref }}
ccache-micropython-${{ matrix.name }}-
- name: "Src: Checkout"
uses: actions/checkout@v4
with:
submodules: true
path: src-${{ github.sha }}
- name: "Pimoroni Pico: Checkout"
uses: actions/checkout@v4
with:
repository: pimoroni/pimoroni-pico
ref: ${{env.PIMORONI_PICO_VERSION}}
submodules: true
path: pimoroni-pico
- name: Install Arm GNU Toolchain (arm-none-eabi-gcc)
uses: carlosperate/arm-none-eabi-gcc-action@v1
with:
release: '9-2020-q2'
- name: "CCache: Install"
run: |
source $BUILD_TOOLS
apt_install_build_deps
- name: "MicroPython: Checkout"
run: |
source $BUILD_TOOLS
micropython_clone
- name: "Py_Decl: Checkout"
uses: actions/checkout@v4
with:
repository: gadgetoid/py_decl
ref: v0.0.2
path: py_decl
- name: "dir2uf2: Checkout"
uses: actions/checkout@v4
with:
repository: gadgetoid/dir2uf2
ref: v0.0.7
path: dir2uf2
- name: "MicroPython: Build MPY Cross"
run: |
source $BUILD_TOOLS
micropython_build_mpy_cross
- name: "MicroPython: Configure"
shell: bash
run: |
source $BUILD_TOOLS
micropython_version
cmake_configure
- name: "MicroPython: Build"
shell: bash
run: |
source $BUILD_TOOLS
cmake_build
- name: "Py_Decl: Verify .uf2"
shell: bash
run: |
python3 py_decl/py_decl.py --to-json --verify build-${{ matrix.name }}/${{ env.RELEASE_FILE }}.uf2
- name: "Artifacts: Upload .uf2"
uses: actions/upload-artifact@v4
with:
name: ${{ env.RELEASE_FILE }}.uf2
path: build-${{ matrix.name }}/${{ env.RELEASE_FILE }}.uf2
- name: "Release: Upload .uf2"
if: github.event_name == 'release'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
asset_path: build-${{ matrix.name }}/${{ env.RELEASE_FILE }}.uf2
upload_url: ${{ github.event.release.upload_url }}
asset_name: ${{ env.RELEASE_FILE }}.uf2
asset_content_type: application/octet-stream

65
ci/micropython.sh Normal file
View file

@ -0,0 +1,65 @@
export TERM=${TERM:="xterm-256color"}
function log_success {
echo -e "$(tput setaf 2)$1$(tput sgr0)"
}
function log_inform {
echo -e "$(tput setaf 6)$1$(tput sgr0)"
}
function log_warning {
echo -e "$(tput setaf 1)$1$(tput sgr0)"
}
function micropython_clone {
log_inform "Using MicroPython $MICROPYTHON_VERSION"
git clone https://github.com/$MICROPYTHON_FLAVOUR/micropython
cd micropython
git checkout $MICROPYTHON_VERSION
git submodule update --init lib/pico-sdk
git submodule update --init lib/cyw43-driver
git submodule update --init lib/lwip
git submodule update --init lib/mbedtls
git submodule update --init lib/micropython-lib
git submodule update --init lib/tinyusb
git submodule update --init lib/btstack
cd ../
}
function micropython_build_mpy_cross {
cd micropython/mpy-cross
ccache --zero-stats || true
CROSS_COMPILE="ccache " make
ccache --show-stats || true
cd ../../
}
function apt_install_build_deps {
sudo apt update && sudo apt install ccache
}
function micropython_version {
echo "MICROPY_GIT_TAG=$MICROPYTHON_VERSION, $BOARD_NAME $TAG_OR_SHA" >> $GITHUB_ENV
echo "MICROPY_GIT_HASH=$MICROPYTHON_VERSION-$TAG_OR_SHA" >> $GITHUB_ENV
}
function cmake_configure {
cmake -S micropython/ports/rp2 -B build-$BOARD_NAME \
-DPICO_BUILD_DOCS=0 \
-DPICO_NO_COPRO_DIS=1 \
-DUSER_C_MODULES=$USER_C_MODULES \
-DMICROPY_BOARD_DIR=$MICROPY_BOARD_DIR \
-DMICROPY_BOARD=$MICROPY_BOARD \
-DMICROPY_BOARD_VARIANT=$MICROPY_BOARD_VARIANT \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
}
function cmake_build {
ccache --zero-stats || true
cmake --build build-$BOARD_NAME -j 2
ccache --show-stats || true
cd build-$BOARD_NAME
cp firmware.uf2 $RELEASE_FILE.uf2
}

View file

@ -25,11 +25,25 @@ extern "C" {
using namespace pimoroni;
#ifdef MICROPY_BUILD_TYPE
#define FRAME_BUFFER_SIZE (640*360)
__attribute__((section(".uninitialized_data"))) static uint8_t frame_buffer_a[FRAME_BUFFER_SIZE];
__attribute__((section(".uninitialized_data"))) static uint8_t frame_buffer_b[FRAME_BUFFER_SIZE];
#endif
#include "font.h"
// If changing the font, note this code will not handle glyphs wider than 13 pixels
#define FONT (&intel_one_mono)
#ifdef MICROPY_BUILD_TYPE
extern "C" {
void dvhstx_debug(const char *fmt, ...);
}
#else
#define dvhstx_debug printf
#endif
static inline __attribute__((always_inline)) uint32_t render_char_line(int c, int y) {
if (c < 0x20 || c > 0x7e) return 0;
const lv_font_fmt_txt_glyph_dsc_t* g = &FONT->dsc->glyph_dsc[c - 0x20 + 1];
@ -373,6 +387,7 @@ void __scratch_x("display") DVHSTX::text_dma_handler() {
// ----------------------------------------------------------------------------
// Experimental clock config
#ifndef MICROPY_BUILD_TYPE
static void __no_inline_not_in_flash_func(set_qmi_timing)() {
// Make sure flash is deselected - QMI doesn't appear to have a busy flag(!)
while ((ioqspi_hw->io[1].status & IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS) != IO_QSPI_GPIO_QSPI_SS_STATUS_OUTTOPAD_BITS)
@ -384,8 +399,11 @@ static void __no_inline_not_in_flash_func(set_qmi_timing)() {
volatile uint32_t* ptr = (volatile uint32_t*)0x14000000;
(void) *ptr;
}
#endif
extern "C" void __no_inline_not_in_flash_func(display_setup_clock_preinit)() {
uint32_t intr_stash = save_and_disable_interrupts();
void DVHSTX::display_setup_clock() {
// Before messing with clock speeds ensure QSPI clock is nice and slow
hw_write_masked(&qmi_hw->m[0].timing, 6, QMI_M0_TIMING_CLKDIV_BITS);
@ -396,12 +414,6 @@ void DVHSTX::display_setup_clock() {
volatile uint32_t* ptr = (volatile uint32_t*)0x14000000;
(void) *ptr;
const uint32_t dvi_clock_khz = timing_mode->bit_clk_khz >> 1;
uint vco_freq, post_div1, post_div2;
if (!check_sys_clock_khz(dvi_clock_khz, &vco_freq, &post_div1, &post_div2))
panic("System clock of %u kHz cannot be exactly achieved", dvi_clock_khz);
const uint32_t freq = vco_freq / (post_div1 * post_div2);
// Before we touch PLLs, switch sys and ref cleanly away from their aux sources.
hw_clear_bits(&clocks_hw->clk[clk_sys].ctrl, CLOCKS_CLK_SYS_CTRL_SRC_BITS);
while (clocks_hw->clk[clk_sys].selected != 0x1)
@ -416,8 +428,7 @@ void DVHSTX::display_setup_clock() {
clock_stop(clk_peri);
clock_stop(clk_hstx);
// Set the sys PLL to the requested freq, set USB PLL to 528MHz
pll_init(pll_sys, PLL_COMMON_REFDIV, vco_freq, post_div1, post_div2);
// Set USB PLL to 528MHz
pll_init(pll_usb, PLL_COMMON_REFDIV, 1584 * MHZ, 3, 1);
const uint32_t usb_pll_freq = 528 * MHZ;
@ -448,14 +459,43 @@ void DVHSTX::display_setup_clock() {
usb_pll_freq,
USB_CLK_KHZ * KHZ);
// Now we are running fast set fast QSPI clock and read delay
// On MicroPython this is setup by main.
#ifndef MICROPY_BUILD_TYPE
set_qmi_timing();
#endif
restore_interrupts(intr_stash);
}
#ifndef MICROPY_BUILD_TYPE
// Trigger clock setup early - on MicroPython this is done by a hook in main.
namespace {
class DV_preinit {
public:
DV_preinit() {
display_setup_clock_preinit();
}
};
DV_preinit dv_preinit __attribute__ ((init_priority (101))) ;
}
#endif
void DVHSTX::display_setup_clock() {
const uint32_t dvi_clock_khz = timing_mode->bit_clk_khz >> 1;
uint vco_freq, post_div1, post_div2;
if (!check_sys_clock_khz(dvi_clock_khz, &vco_freq, &post_div1, &post_div2))
panic("System clock of %u kHz cannot be exactly achieved", dvi_clock_khz);
const uint32_t freq = vco_freq / (post_div1 * post_div2);
// Set the sys PLL to the requested freq
pll_init(pll_sys, PLL_COMMON_REFDIV, vco_freq, post_div1, post_div2);
// CLK HSTX = Requested freq
clock_configure(clk_hstx,
0,
CLOCKS_CLK_HSTX_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
freq, freq);
// Now we are running fast set fast QSPI clock and read delay
set_qmi_timing();
}
void DVHSTX::write_pixel(const Point &p, uint16_t colour)
@ -613,9 +653,13 @@ bool DVHSTX::init(uint16_t width, uint16_t height, Mode mode_)
display = this;
display_palette = get_palette();
dvhstx_debug("Setup clock\n");
display_setup_clock();
#ifndef MICROPY_BUILD_TYPE
stdio_init_all();
#endif
dvhstx_debug("Clock setup done\n");
v_inactive_total = timing_mode->v_front_porch + timing_mode->v_sync_width + timing_mode->v_back_porch;
v_total_active_lines = v_inactive_total + timing_mode->v_active_lines;
@ -666,14 +710,24 @@ bool DVHSTX::init(uint16_t width, uint16_t height, Mode mode_)
return false;
}
#ifdef MICROPY_BUILD_TYPE
if (frame_width * frame_height * frame_bytes_per_pixel > sizeof(frame_buffer_a)) {
panic("Frame buffer too large");
}
frame_buffer_display = frame_buffer_a;
frame_buffer_back = frame_buffer_b;
#else
frame_buffer_display = (uint8_t*)malloc(frame_width * frame_height * frame_bytes_per_pixel);
frame_buffer_back = (uint8_t*)malloc(frame_width * frame_height * frame_bytes_per_pixel);
#endif
memset(frame_buffer_display, 0, frame_width * frame_height * frame_bytes_per_pixel);
memset(frame_buffer_back, 0, frame_width * frame_height * frame_bytes_per_pixel);
memset(palette, 0, PALETTE_SIZE * sizeof(palette[0]));
frame_buffer_display = frame_buffer_display;
dvhstx_debug("Frame buffers inited\n");
const bool is_text_mode = (mode == MODE_TEXT_MONO || mode == MODE_TEXT_RGB111);
const int frame_pixel_words = (frame_width * h_repeat * line_bytes_per_pixel + 3) >> 2;
@ -815,6 +869,8 @@ bool DVHSTX::init(uint16_t width, uint16_t height, Mode mode_)
gpio_set_drive_strength(i, GPIO_DRIVE_STRENGTH_4MA);
}
dvhstx_debug("GPIO configured\n");
// Always use the bottom channels
dma_claim_mask((1 << NUM_CHANS) - 1);
@ -860,18 +916,24 @@ bool DVHSTX::init(uint16_t width, uint16_t height, Mode mode_)
);
}
dma_hw->ints0 = (1 << NUM_CHANS) - 1;
dma_hw->inte0 = (1 << NUM_CHANS) - 1;
if (is_text_mode) irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler_text);
else irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler);
irq_set_enabled(DMA_IRQ_0, true);
dvhstx_debug("DMA channels claimed\n");
dma_hw->ints2 = (1 << NUM_CHANS) - 1;
dma_hw->inte2 = (1 << NUM_CHANS) - 1;
if (is_text_mode) irq_set_exclusive_handler(DMA_IRQ_2, dma_irq_handler_text);
else irq_set_exclusive_handler(DMA_IRQ_2, dma_irq_handler);
irq_set_enabled(DMA_IRQ_2, true);
dma_channel_start(0);
dvhstx_debug("DVHSTX started\n");
for (int i = 0; i < frame_height; ++i) {
memset(&frame_buffer_display[i * frame_width * frame_bytes_per_pixel], i, frame_width * frame_bytes_per_pixel);
}
dvhstx_debug("Frame buffer filled\n");
return true;
}

View file

@ -0,0 +1,19 @@
{
"deploy": [
"../deploy.md"
],
"docs": "",
"features": [
"Dual-core",
"External Flash",
"USB"
],
"images": [
"rp2-picos.jpg"
],
"mcu": "rp2350",
"product": "Pico2",
"thumbnail": "",
"url": "https://shop.pimoroni.com/products/raspberry-pi-pico-plus2/",
"vendor": "Raspberry Pi"
}

View file

@ -0,0 +1,3 @@
include("$(PORT_DIR)/boards/manifest.py")
include("../manifest_pico2.py")

View file

@ -0,0 +1,9 @@
# cmake file for Raspberry Pi Pico
set(PICO_BOARD "pimoroni_pico_plus2_rp2350")
set(PICO_PLATFORM "rp2350")
set(PICO_NUM_GPIOS 48)
# Board specific version of the frozen manifest
set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py)
set(MICROPY_C_HEAP_SIZE 32768)

View file

@ -0,0 +1,11 @@
// Board and hardware specific configuration
#ifndef MICROPY_HW_BOARD_NAME
// Might be defined by mpconfigvariant.cmake
#define MICROPY_HW_BOARD_NAME "Pimoroni Pico Plus 2"
#endif
#define MICROPY_HW_FLASH_STORAGE_BYTES (PICO_FLASH_SIZE_BYTES - (2 * 1024 * 1024))
#define MICROPY_HW_PSRAM_CS_PIN PIMORONI_PICO_PLUS2_PSRAM_CS_PIN
extern void display_setup_clock_preinit();
#define MICROPY_BOARD_STARTUP() display_setup_clock_preinit()

View file

@ -0,0 +1,6 @@
# Override the MicroPython board name
list(APPEND MICROPY_DEF_BOARD
"MICROPY_HW_ENABLE_PSRAM=1"
"MICROPY_GC_SPLIT_HEAP=0"
"MICROPY_HW_BOARD_NAME=\"Pimoroni Pico Plus 2 (PSRAM)\""
)

View file

@ -0,0 +1,47 @@
GP0,GPIO0
GP1,GPIO1
GP2,GPIO2
GP3,GPIO3
GP4,GPIO4
GP5,GPIO5
GP6,GPIO6
GP7,GPIO7
GP8,GPIO8
GP9,GPIO9
GP10,GPIO10
GP11,GPIO11
GP12,GPIO12
GP13,GPIO13
GP14,GPIO14
GP15,GPIO15
GP16,GPIO16
GP17,GPIO17
GP18,GPIO18
GP19,GPIO19
GP20,GPIO20
GP21,GPIO21
GP22,GPIO22
GP25,GPIO25
GP26,GPIO26
GP27,GPIO27
GP28,GPIO28
GP29,GPIO29
GP30,GPIO30
GP31,GPIO31
GP32,GPIO32
GP33,GPIO33
GP34,GPIO34
GP35,GPIO35
GP36,GPIO36
GP37,GPIO37
GP38,GPIO38
GP39,GPIO39
GP40,GPIO40
GP41,GPIO41
GP42,GPIO42
GP43,GPIO43
GP44,GPIO44
GP45,GPIO45
GP46,GPIO46
GP47,GPIO47
LED,GPIO25
1 GP0 GPIO0
2 GP1 GPIO1
3 GP2 GPIO2
4 GP3 GPIO3
5 GP4 GPIO4
6 GP5 GPIO5
7 GP6 GPIO6
8 GP7 GPIO7
9 GP8 GPIO8
10 GP9 GPIO9
11 GP10 GPIO10
12 GP11 GPIO11
13 GP12 GPIO12
14 GP13 GPIO13
15 GP14 GPIO14
16 GP15 GPIO15
17 GP16 GPIO16
18 GP17 GPIO17
19 GP18 GPIO18
20 GP19 GPIO19
21 GP20 GPIO20
22 GP21 GPIO21
23 GP22 GPIO22
24 GP25 GPIO25
25 GP26 GPIO26
26 GP27 GPIO27
27 GP28 GPIO28
28 GP29 GPIO29
29 GP30 GPIO30
30 GP31 GPIO31
31 GP32 GPIO32
32 GP33 GPIO33
33 GP34 GPIO34
34 GP35 GPIO35
35 GP36 GPIO36
36 GP37 GPIO37
37 GP38 GPIO38
38 GP39 GPIO39
39 GP40 GPIO40
40 GP41 GPIO41
41 GP42 GPIO42
42 GP43 GPIO43
43 GP44 GPIO44
44 GP45 GPIO45
45 GP46 GPIO46
46 GP47 GPIO47
47 LED GPIO25

View file

@ -0,0 +1,6 @@
MODULES_PY = "../../../pimoroni-pico/micropython/modules_py"
freeze(MODULES_PY, "gfx_pack.py")
freeze(MODULES_PY, "pimoroni.py")
freeze(MODULES_PY, "boot.py")

View file

@ -0,0 +1,120 @@
import time
import gc
from dvhstx import DVHSTX, PEN_P8, PEN_RGB565
from picovector import PicoVector, Polygon, RegularPolygon, Rectangle, ANTIALIAS_X4, ANTIALIAS_X16
display = DVHSTX(PEN_P8, 640, 360)
vector = PicoVector(display)
vector.set_antialiasing(ANTIALIAS_X4)
BG = display.create_pen(20, 20, 50)
RED = display.create_pen(200, 0, 0)
BLACK = display.create_pen(0, 0, 0)
GREY = display.create_pen(200, 200, 200)
WHITE = display.create_pen(255, 255, 255)
"""
# Redefine colours for a Blue clock
RED = display.create_pen(200, 0, 0)
BLACK = display.create_pen(135, 159, 169)
GREY = display.create_pen(10, 40, 50)
WHITE = display.create_pen(14, 60, 76)
"""
WIDTH, HEIGHT = display.get_bounds()
hub = RegularPolygon(int(WIDTH / 2), int(HEIGHT / 2), 24, 5)
face = RegularPolygon(int(WIDTH / 2), int(HEIGHT / 2), 48, int(HEIGHT / 2))
print(time.localtime())
last_second = None
while True:
t_start = time.ticks_ms()
year, month, day, hour, minute, second, _, _ = time.localtime()
if last_second == second:
continue
last_second = second
display.set_pen(0)
display.clear()
display.set_pen(BLACK)
display.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2))
display.set_pen(WHITE)
display.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2) - 4)
display.set_pen(GREY)
for a in range(60):
tick_mark = Rectangle(int(WIDTH / 2) - 3, 10, 6, int(HEIGHT / 48))
vector.rotate(tick_mark, 360 / 60.0 * a, int(WIDTH / 2), int(HEIGHT / 2))
vector.translate(tick_mark, 0, 2)
vector.draw(tick_mark)
for a in range(12):
hour_mark = Rectangle(int(WIDTH / 2) - 5, 10, 10, int(HEIGHT / 10))
vector.rotate(hour_mark, 360 / 12.0 * a, int(WIDTH / 2), int(HEIGHT / 2))
vector.translate(hour_mark, 0, 2)
vector.draw(hour_mark)
angle_second = second * 6
second_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8)
second_hand = Polygon((-2, -second_hand_length), (-2, int(HEIGHT / 8)), (2, int(HEIGHT / 8)), (2, -second_hand_length))
vector.rotate(second_hand, angle_second, 0, 0)
vector.translate(second_hand, int(WIDTH / 2), int(HEIGHT / 2) + 5)
angle_minute = minute * 6
angle_minute += second / 10.0
minute_hand_length = int(HEIGHT / 2) - int(HEIGHT / 24)
minute_hand = Polygon((-5, -minute_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -minute_hand_length))
vector.rotate(minute_hand, angle_minute, 0, 0)
vector.translate(minute_hand, int(WIDTH / 2), int(HEIGHT / 2) + 5)
angle_hour = (hour % 12) * 30
angle_hour += minute / 2
hour_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8)
hour_hand = Polygon((-5, -hour_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -hour_hand_length))
vector.rotate(hour_hand, angle_hour, 0, 0)
vector.translate(hour_hand, int(WIDTH / 2), int(HEIGHT / 2) + 5)
display.set_pen(GREY)
vector.draw(minute_hand)
vector.draw(hour_hand)
vector.draw(second_hand)
display.set_pen(BLACK)
for a in range(60):
tick_mark = Rectangle(int(WIDTH / 2) - 3, 10, 6, int(HEIGHT / 48))
vector.rotate(tick_mark, 360 / 60.0 * a, int(WIDTH / 2), int(HEIGHT / 2))
vector.draw(tick_mark)
for a in range(12):
hour_mark = Rectangle(int(WIDTH / 2) - 5, 10, 10, int(HEIGHT / 10))
vector.rotate(hour_mark, 360 / 12.0 * a, int(WIDTH / 2), int(HEIGHT / 2))
vector.draw(hour_mark)
vector.translate(minute_hand, 0, -5)
vector.translate(hour_hand, 0, -5)
vector.draw(minute_hand)
vector.draw(hour_hand)
display.set_pen(RED)
vector.translate(second_hand, 0, -5)
vector.draw(second_hand)
vector.draw(hub)
display.update()
gc.collect()
t_end = time.ticks_ms()
print(f"Took {t_end - t_start}ms")

44
modules/dvhstx.cmake Normal file
View file

@ -0,0 +1,44 @@
if(NOT DEFINED PIMORONI_PICO_PATH)
set(PIMORONI_PICO_PATH ../pimoroni-pico)
endif()
include(${CMAKE_CURRENT_LIST_DIR}/../pimoroni_pico_import.cmake)
include_directories(${PIMORONI_PICO_PATH}/micropython)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../")
list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython")
list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython/modules")
# Allows us to find /pga/modules/c/<module>/micropython
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
# Essential
include(pimoroni_i2c/micropython)
include(pimoroni_bus/micropython)
# Pico Graphics Essential
include(hershey_fonts/micropython)
include(bitmap_fonts/micropython)
include(picovector/micropython)
include(modules/picographics/micropython)
# Pico Graphics Extra
include(jpegdec/micropython)
include(pngdec/micropython)
include(qrcode/micropython/micropython)
# Sensors & Breakouts
include(micropython-common-breakouts)
# Utility
include(adcfft/micropython)
# Note: cppmem is *required* for C++ code to function on MicroPython
# it redirects `malloc` and `free` calls to MicroPython's heap
include(cppmem/micropython)
# version.py, pimoroni.py and boot.py
include(modules_py/modules_py)

View file

@ -0,0 +1,582 @@
# Pico Graphics <!-- omit in toc -->
Pico Graphics is our unified graphics and display library for driving displays from your Pico in MicroPython.
Pico Graphics replaces the individual drivers for displays- if you're been using breakout_colorlcd, ST7789 then you'll need to update your code!
- [Setting up Pico Graphics](#setting-up-pico-graphics)
- [Supported Displays](#supported-displays)
- [Interstate75 and Interstate75W Display modes](#interstate75-and-interstate75w-display-modes)
- [Supported Graphics Modes (Pen Type)](#supported-graphics-modes-pen-type)
- [Supported Rotations](#supported-rotations)
- [Custom Pins](#custom-pins)
- [SPI / Parallel](#spi--parallel)
- [I2C](#i2c)
- [Function Reference](#function-reference)
- [General](#general)
- [Creating and Setting Pens](#creating-and-setting-pens)
- [RGB888, RGB565, RGB332, P8 and P4 modes](#rgb888-rgb565-rgb332-p8-and-p4-modes)
- [Monochrome Modes](#monochrome-modes)
- [Inky Frame](#inky-frame)
- [Controlling the Backlight](#controlling-the-backlight)
- [Clipping](#clipping)
- [Clear](#clear)
- [Update](#update)
- [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)
- [Circle](#circle)
- [Rectangle](#rectangle)
- [Triangle](#triangle)
- [Polygon](#polygon)
- [Pixels](#pixels)
- [Palette Management](#palette-management)
- [Utility Functions](#utility-functions)
- [Sprites](#sprites)
- [Loading Sprites](#loading-sprites)
- [Drawing Sprites](#drawing-sprites)
- [JPEG Files](#jpeg-files)
## Setting up Pico Graphics
You must construct an instance of PicoGraphics with your desired display:
```python
from picographics import PicoGraphics, DISPLAY_LCD_160X80
display = PicoGraphics(display=DISPLAY_LCD_160X80)
```
Bear in mind that MicroPython has only 192K of RAM available- a 320x240 pixel display in RGB565 mode uses 150K!
### Supported Displays
* Pico Display - 240x135 SPI LCD - `DISPLAY_PICO_DISPLAY`
* Pico Display 2 - 320x240 SPI LCD - `DISPLAY_PICO_DISPLAY_2`
* Tufty 2040 - 320x240 Parallel LCD - `DISPLAY_TUFTY_2040`
* Pico Explorer - 240x240 SPI LCD - `DISPLAY_PICO_EXPLORER`
* Enviro Plus - 240x240 SPI LCD - `DISPLAY_ENVIRO_PLUS`
* 240x240 Round SPI LCD Breakout - `DISPLAY_ROUND_LCD_240X240`
* 240x240 Square SPI LCD Breakout - `DISPLAY_LCD_240X240`
* 160x80 SPI LCD Breakout - `DISPLAY_LCD_160X80`
* 128x128 I2C OLED - `DISPLAY_I2C_OLED_128X128`
* Pico Inky Pack / Badger 2040 / Badger 2040 W - 296x128 mono E ink - `DISPLAY_INKY_PACK`
* Inky Frame 5.7" - 600x448 7-colour E ink - `DISPLAY_INKY_FRAME`
* Inky Frame 4.0" - 640x400 7-colour E ink - `DISPLAY_INKY_FRAME_4`
* Inky Frame 7.3" - 800x480 7-colour E ink - `DISPLAY_INKY_FRAME_7`
* Pico GFX Pack - 128x64 mono LCD Matrix - `DISPLAY_GFX_PACK`
* Galactic Unicorn - 53x11 LED Matrix - `DISPLAY_GALACTIC_UNICORN`
* Interstate75 and 75W - HUB75 Matrix driver - `DISPLAY_INTERSTATE75_SIZEOFMATRIX` please read below!
* Cosmic Unicorn - 32x32 LED Matrix - `DISPLAY_COSMIC_UNICORN`
#### Interstate75 and Interstate75W Display modes
Both the Interstate75 and Interstate75W support lots of different sizes of HUB75 matrix displays.
The available display settings are listed here:
* 32 x 32 Matrix - `DISPLAY_INTERSTATE75_32X32`
* 64 x 32 Matrix - `DISPLAY_INTERSTATE75_64X32`
* 96 x 32 Matrix - `DISPLAY_INTERSTATE75_96X32`
* 128 x 32 Matrix - `DISPLAY_INTERSTATE75_128X32`
* 64 x 64 Matrix - `DISPLAY_INTERSTATE75_64X64`
* 128 x 64 Matrix - `DISPLAY_INTERSTATE75_128X64`
* 192 x 64 Matrix - `DISPLAY_INTERSTATE75_192X64`
* 256 x 64 Matrix - `DISPLAY_INTERSTATE75_256X64`
### Supported Graphics Modes (Pen Type)
* 1-bit - `PEN_1BIT` - mono, used for Pico Inky Pack and i2c OLED
* 3-bit - `PEN_3BIT` - 8-colour, used for Inky Frame
* 4-bit - `PEN_P4` - 16-colour palette of your choice
* 8-bit - `PEN_P8` - 256-colour palette of your choice
* 8-bit RGB332 - `PEN_RGB332` - 256 fixed colours (3 bits red, 3 bits green, 2 bits blue)
* 16-bit RGB565 - `PEN_RGB565` - 64K colours at the cost of RAM. (5 bits red, 6 bits green, 5 bits blue)
* 24-bit RGB888 - `PEN_RGB888` - 16M colours at the cost of lots of RAM. (8 bits red, 8 bits green, 8 bits blue)
These offer a tradeoff between RAM usage and available colours. In most cases you would probably use `RGB332` since it offers the easiest tradeoff. It's also the default for colour LCDs.
Eg:
```python
display = PicoGraphics(display=PICO_DISPLAY)
```
Is equivalent to:
```python
display = PicoGraphics(display=PICO_DISPLAY, pen_type=PEN_RGB332)
```
### Supported Rotations
All SPI LCDs support 0, 90, 180 and 270 degree rotations.
Eg:
```python
display = PicoGraphics(display=PICO_DISPLAY, rotate=90)
```
### Custom Pins
#### SPI / Parallel
The `pimoroni_bus` library includes `SPIBus` for SPI LCDs and `ParallelBus` for Parallel LCDs (like Tufty 2040).
In most cases you'll never need to use these, but they come in useful if you're wiring breakouts to your Pico or using multiple LCDs.
A custom SPI bus:
```python
from pimoroni_bus import SPIBus
from picographics import PicoGraphics, DISPLAY_PICO_EXPLORER, PEN_RGB332
spibus = SPIBus(cs=17, dc=16, sck=18, mosi=19)
display = PicoGraphics(display=DISPLAY_PICO_EXPLORER, bus=spibus, pen_type=PEN_RGB332)
```
#### I2C
The `pimoroni_i2c` library includes `PimoroniI2C` which can be used to change the pins used by the mono OLED:
```python
from pimoroni_i2c import PimoroniI2C
from picographics import PicoGraphics, DISPLAY_I2C_OLED_128X128
i2cbus = PimoroniI2C(4, 5)
display = PicoGraphics(display=DISPLAY_I2C_OLED_128X128, bus=i2cbus)
```
## Function Reference
### General
#### Creating and Setting Pens
##### RGB888, RGB565, RGB332, P8 and P4 modes
Create a pen colour for drawing into a screen:
```python
my_pen = display.create_pen(r, g, b)
```
In RGB565 and RGB332 modes this packs the given RGB into an integer representing a colour in these formats and returns the result.
In P4 and P8 modes this will consume one palette entry, or return an error if your palette is full. Palette colours are stored as RGB and converted when they are displayed on screen.
You can also now specify an HSV pen, which allows a pen to be created from HSV (Hue, Saturation, Value) values between 0.0 and 1.0, avoiding the need to calculate the RGB result in Python.
```python
display.create_pen_hsv(h, s, v)
```
To tell PicoGraphics which pen to use:
```python
display.set_pen(my_pen)
```
This will be either an RGB332, RGB565 or RGB888 colour, or a palette index.
##### Monochrome Modes
For 1BIT mode - such as for Inky Pack and the Mono OLED - pens are handled a little differently.
There's no need to create one, since mapping an RGB colour to black/white is meaningless.
Instead you can pick from 16 shades of grey which are automatically dithered into the PicoGraphics buffer, where:
* `0` is Black,
* `1 - 14` are shades of grey,
* `15` is white.
And just call `set_pen` with your desired shade:
```python
display.set_pen(0) # Black
display.set_pen(15) # White
```
Because shades 1 through 14 are created with ordered dither you should avoid using them for text, small details or lines.
Dithering works by mixing black and white pixels in various patterns and quantities to fake grey shades.
If you were to try and draw a single "grey" pixel it will end up either black or white depending on where it's drawn and which shade of grey you pick.
##### Inky Frame
Inky Frame is a special case- the display itself supports only 7 (8 if you include its cleaning "clear" colour, which we call Taupe) colours.
These are:
* `BLACK` = 0
* `WHITE` = 1
* `GREEN` = 2
* `BLUE` = 3
* `RED` = 4
* `YELLOW` = 5
* `ORANGE` = 6
* `TAUPE` = 7
#### Controlling the Backlight
You can set the display backlight brightness between `0.0` and `1.0`:
```python
display.set_backlight(0.5)
```
#### Clipping
Set the clipping bounds for drawing:
```python
display.set_clip(x, y, w, h)
```
Remove the clipping bounds:
```python
display.remove_clip()
```
#### Clear
Clear the display to the current pen colour:
```python
display.clear()
```
This is equivalent to:
```python
w, h = display.get_bounds()
display.rectangle(0, 0, w, h)
```
You can clear portions of the screen with rectangles to save time redrawing things like JPEGs or complex graphics.
#### Update
Send the contents of your Pico Graphics buffer to your screen:
```python
display.update()
```
If you are using a Galactic Unicorn, then the process for updating the display is different. Instead of the above, do:
```python
galactic_unicorn.update(display)
```
#### Get Bounds
You can use `get_bounds()` to get the width and height of the display - useful for writing code that's portable across different displays.
```python
WIDTH, HEIGHT = display.get_bounds()
```
### Text
#### Changing The Font
Change the font:
```python
display.set_font(font)
```
Bitmap fonts.
These are aligned from their top-left corner.
* `bitmap6`
* `bitmap8`
* `bitmap14_outline`
Vector (Hershey) fonts.
These are aligned horizontally (x) to their left edge, but vertically (y) to their midline excluding descenders [i.e., aligned at top edge of lower case letter m]. At `scale=1`, the top edge of upper case letters is 10 pixels above the specified `y`, text baseline is 10 pixels below the specified `y`, and descenders go down to 20 pixels below the specified `y`.
* `sans`
* `gothic`
* `cursive`
* `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
Write some text:
```python
display.text(text, x, y, wordwrap, scale, angle, spacing)
```
* `text` - the text string to draw
* `x` - the destination X coordinate
* `y` - the destination Y coordinate
* `wordwrap` - number of pixels width before trying to break text into multiple lines
* `scale` - size
* `angle` - rotation angle (Vector only!)
* `spacing` - letter spacing
Text scale can be a whole number (integer) for Bitmap fonts, or a decimal (float) for Vector (Hershey) fonts.
For example:
```python
display.set_font("bitmap8")
display.text("Hello World", 0, 0, scale=2)
```
Draws "Hello World" in a 16px tall, 2x scaled version of the `bitmap8` font.
Sometimes you might want to measure a text string for centering or alignment on screen, you can do this with:
```python
width = display.measure_text(text, scale, spacing)
```
The height of each Bitmap font is explicit in its name.
Write a single character:
```python
display.character(char, x, y, scale)
```
Specify `char` using a [decimal ASCII code](https://www.ascii-code.com/). Note not all characters are supported.
For example:
```python
display.set_font("bitmap8")
display.character(38, 0, 0, scale=2)
```
Draws an ampersand in a 16px tall, 2x scaled version of the 'bitmap8' font.
### Basic Shapes
#### Line
To draw a straight line at any angle between two specified points:
```python
display.line(x1, y1, x2, y2)
```
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
To draw a circle:
```python
display.circle(x, y, r)
```
* `x` - the destination X coordinate
* `y` - the destination Y coordinate
* `r` - the radius
The X/Y coordinates describe the center of your circle.
#### Rectangle
```python
display.rectangle(x, y, w, h)
```
* `x` - the destination X coordinate
* `y` - the destination Y coordinate
* `w` - the width
* `h` - the height
#### Triangle
```python
display.triangle(x1, y1, x2, y2, x3, y3)
```
The three pairs of X/Y coordinates describe each point of the triangle.
#### Polygon
To draw other shapes, you can provide a list of points to `polygon`:
```python
display.polygon([
(0, 10),
(20, 10),
(20, 0),
(30, 20),
(20, 30),
(20, 20),
(0, 20),
])
```
### Pixels
Setting individual pixels is slow, but you can do it with:
```python
display.pixel(x, y)
```
You can draw a horizontal span of pixels a little faster with:
```python
display.pixel_span(x, y, length)
```
(use `display.line()` instead if you want to draw a straight line at any angle)
### Palette Management
Intended for P4 and P8 modes.
You have a 16-color and 256-color palette respectively.
Set n elements in the palette from a list of RGB tuples:
```python
display.set_palette([
(r, g, b),
(r, g, b),
(r, g, b)
])
```
Update an entry in the P4 or P8 palette with the given colour.
```python
display.update_pen(index, r, g, b)
```
This is stored internally as RGB and converted to whatever format your screen requires when displayed.
Reset a pen back to its default value (black, marked unused):
```python
display.reset_pen(index)
```
#### Utility Functions
Sometimes it can be useful to convert between colour formats:
* `RGB332_to_RGB`
* `RGB_to_RGB332`
* `RGB565_to_RGB`
* `RGB_to_RGB565`
### Sprites
Pico Display has very limited support for sprites in RGB332 mode.
Sprites must be 8x8 pixels arranged in a 128x128 pixel spritesheet. 1-bit transparency is handled by electing a single colour to skip over.
We've prepared some RGB332-compatible sprite assets for you, but you can use `spritesheet-to-rgb332.py <filename>` to convert your own.
#### Loading Sprites
You'll need to include the [pen_type](#supported-graphics-modes-pen-type) in the import statement, and define the pen_type before using loading the spritesheet:
``` python
from picographics import PicoGraphics, PEN_RGB565, PEN_RGB332
display = PicoGraphics(display=PICO_DISPLAY, pen_type=PEN_RGB332)
```
Use Thonny to upload your `spritesheet.rgb332` file onto your Pico. Then load it into Pico Graphics:
```python
display.load_spritesheet("s4m_ur4i-dingbads.rgb332")
```
and then update the display, to show the sprite:
```python
display.update()
```
#### Drawing Sprites
And finally display a sprite:
```python
display.sprite(0, 0, 0, 0)
```
These arguments for `sprite` are as follows:
1. Sprite X position (from 0-15) - this selects the horizontal location of an 8x8 sprite from your 128x128 pixel spritesheet.
2. Sprite Y position (from 0-15)
3. Destination X - where to draw on your screen horizontally
4. Destination Y = where to draw on your screen vertically
5. Scale (optional) - an integer scale value, 1 = 8x8, 2 = 16x16 etc.
6. Transparent (optional) - specify a colour to treat as transparent
### JPEG Files
We've included BitBank's JPEGDEC - https://github.com/bitbank2/JPEGDEC - so you can display JPEG files on your LCDs.
Eg:
```python
import picographics
import jpegdec
display = picographics.PicoGraphics(display=picographics.DISPLAY_PICO_EXPLORER)
# Create a new JPEG decoder for our PicoGraphics
j = jpegdec.JPEG(display)
# Open the JPEG file
j.open_file("filename.jpeg")
# Decode the JPEG
j.decode(0, 0, jpegdec.JPEG_SCALE_FULL, dither=True)
# Display the result
display.update()
```
JPEG files must be small enough to load into RAM for decoding, and must *not* be progressive.
JPEG files will be automatically dithered in RGB332 mode.
In P4 and P8 modes JPEGs are dithered to your custom colour palette. Their appearance of an image will vary based on the colours you choose.
The arguments for `decode` are as follows:
1. Decode X - where to place the decoded JPEG on screen
2. Decode Y
3. Flags - one of `JPEG_SCALE_FULL`, `JPEG_SCALE_HALF`, `JPEG_SCALE_QUARTER` or `JPEG_SCALE_EIGHTH`
4. If you want to turn off dither altogether, try `dither=False`. This is useful if you want to [pre-dither your images](https://ditherit.com/) or for artsy posterization effects.

View file

@ -0,0 +1,48 @@
set(MOD_NAME picographics)
string(TOUPPER ${MOD_NAME} MOD_NAME_UPPER)
add_library(usermod_${MOD_NAME} INTERFACE)
get_filename_component(PICOVISION_PATH ${CMAKE_CURRENT_LIST_DIR}/../.. ABSOLUTE)
target_sources(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.cpp
${PICOVISION_PATH}/drivers/dvhstx/dvhstx.cpp
${PICOVISION_PATH}/drivers/dvhstx/dvi.cpp
${PICOVISION_PATH}/drivers/dvhstx/intel_one_mono_2bpp.c
${PIMORONI_PICO_PATH}/libraries/pico_graphics/pico_graphics.cpp
${PICOVISION_PATH}/libraries/pico_graphics/pico_graphics_pen_dvhstx_rgb565.cpp
${PICOVISION_PATH}/libraries/pico_graphics/pico_graphics_pen_dvhstx_p8.cpp
${PIMORONI_PICO_PATH}/libraries/pico_graphics/types.cpp
)
# MicroPython compiles with -Os by default, these functions are critical path enough that -O2 is worth it (note -O3 is slower in this case)
set_source_files_properties(${PICOVISION_PATH}/drivers/dvhstx/dvhstx.cpp PROPERTIES COMPILE_OPTIONS "-O2")
target_include_directories(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}
${PICOVISION_PATH}
${PICOVISION_PATH}/drivers/dvhstx
${PICOVISION_PATH}/libraries/pico_graphics # for pico_graphics_dv.hpp
${PIMORONI_PICO_PATH}/libraries/pico_graphics # for pico_graphics.hpp
# ${PIMORONI_PICO_PATH}/libraries/pngdec
)
target_compile_definitions(usermod_${MOD_NAME} INTERFACE
-DMODULE_${MOD_NAME_UPPER}_ENABLED=1
)
if (SUPPORT_WIDE_MODES)
target_compile_definitions(usermod_${MOD_NAME} INTERFACE
-DSUPPORT_WIDE_MODES=1
)
endif()
target_link_libraries(usermod INTERFACE usermod_${MOD_NAME} hardware_vreg)
set_source_files_properties(
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c
PROPERTIES COMPILE_FLAGS
"-Wno-discarded-qualifiers"
)

View file

@ -0,0 +1,147 @@
#include "picographics.h"
// Class Methods
MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_update_obj, ModPicoGraphics_update);
// Palette management
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_update_pen_obj, 5, 5, ModPicoGraphics_update_pen);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_reset_pen_obj, ModPicoGraphics_reset_pen);
MP_DEFINE_CONST_FUN_OBJ_KW(ModPicoGraphics_set_palette_obj, 2, ModPicoGraphics_set_palette);
// Pen
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_pen_obj, ModPicoGraphics_set_pen);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_create_pen_obj, 4, 4, ModPicoGraphics_create_pen);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_create_pen_hsv_obj, 4, 4, ModPicoGraphics_create_pen_hsv);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_thickness_obj, ModPicoGraphics_set_thickness);
// Alpha Blending
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_bg_obj, ModPicoGraphics_set_bg);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_blend_mode_obj, ModPicoGraphics_set_blend_mode);
// Depth
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_depth_obj, ModPicoGraphics_set_depth);
// Primitives
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_set_clip_obj, 5, 5, ModPicoGraphics_set_clip);
MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_remove_clip_obj, ModPicoGraphics_remove_clip);
MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_clear_obj, ModPicoGraphics_clear);
MP_DEFINE_CONST_FUN_OBJ_3(ModPicoGraphics_pixel_obj, ModPicoGraphics_pixel);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_pixel_span_obj, 4, 4, ModPicoGraphics_pixel_span);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_rectangle_obj, 5, 5, ModPicoGraphics_rectangle);
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_circle_obj, 4, 4, ModPicoGraphics_circle);
MP_DEFINE_CONST_FUN_OBJ_KW(ModPicoGraphics_character_obj, 1, ModPicoGraphics_character);
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, 6, ModPicoGraphics_line);
// Sprites
MP_DEFINE_CONST_FUN_OBJ_KW(ModPicoGraphics_load_sprite_obj, 2, ModPicoGraphics_load_sprite);
MP_DEFINE_CONST_FUN_OBJ_KW(ModPicoGraphics_display_sprite_obj, 5, ModPicoGraphics_display_sprite);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_clear_sprite_obj, ModPicoGraphics_clear_sprite);
// Utility
MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_get_bounds_obj, ModPicoGraphics_get_bounds);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_font_obj, ModPicoGraphics_set_font);
MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics__del__obj, ModPicoGraphics__del__);
// Loop
MP_DEFINE_CONST_FUN_OBJ_3(ModPicoGraphics_loop_obj, ModPicoGraphics_loop);
static const mp_rom_map_elem_t ModPicoGraphics_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_pixel), MP_ROM_PTR(&ModPicoGraphics_pixel_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_pen), MP_ROM_PTR(&ModPicoGraphics_set_pen_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_thickness), MP_ROM_PTR(&ModPicoGraphics_set_thickness_obj) },
{ MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&ModPicoGraphics_clear_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_bg), MP_ROM_PTR(&ModPicoGraphics_set_bg_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_blend_mode), MP_ROM_PTR(&ModPicoGraphics_set_blend_mode_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_depth), MP_ROM_PTR(&ModPicoGraphics_set_depth_obj) },
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&ModPicoGraphics_update_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_clip), MP_ROM_PTR(&ModPicoGraphics_set_clip_obj) },
{ MP_ROM_QSTR(MP_QSTR_remove_clip), MP_ROM_PTR(&ModPicoGraphics_remove_clip_obj) },
{ MP_ROM_QSTR(MP_QSTR_pixel_span), MP_ROM_PTR(&ModPicoGraphics_pixel_span_obj) },
{ MP_ROM_QSTR(MP_QSTR_rectangle), MP_ROM_PTR(&ModPicoGraphics_rectangle_obj) },
{ MP_ROM_QSTR(MP_QSTR_circle), MP_ROM_PTR(&ModPicoGraphics_circle_obj) },
{ MP_ROM_QSTR(MP_QSTR_character), MP_ROM_PTR(&ModPicoGraphics_character_obj) },
{ MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&ModPicoGraphics_text_obj) },
{ MP_ROM_QSTR(MP_QSTR_measure_text), MP_ROM_PTR(&ModPicoGraphics_measure_text_obj) },
{ MP_ROM_QSTR(MP_QSTR_polygon), MP_ROM_PTR(&ModPicoGraphics_polygon_obj) },
{ MP_ROM_QSTR(MP_QSTR_triangle), MP_ROM_PTR(&ModPicoGraphics_triangle_obj) },
{ MP_ROM_QSTR(MP_QSTR_line), MP_ROM_PTR(&ModPicoGraphics_line_obj) },
{ MP_ROM_QSTR(MP_QSTR_create_pen), MP_ROM_PTR(&ModPicoGraphics_create_pen_obj) },
{ MP_ROM_QSTR(MP_QSTR_create_pen_hsv), MP_ROM_PTR(&ModPicoGraphics_create_pen_hsv_obj) },
{ MP_ROM_QSTR(MP_QSTR_update_pen), MP_ROM_PTR(&ModPicoGraphics_update_pen_obj) },
{ MP_ROM_QSTR(MP_QSTR_reset_pen), MP_ROM_PTR(&ModPicoGraphics_reset_pen_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_palette), MP_ROM_PTR(&ModPicoGraphics_set_palette_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_bounds), MP_ROM_PTR(&ModPicoGraphics_get_bounds_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_font), MP_ROM_PTR(&ModPicoGraphics_set_font_obj) },
// { MP_ROM_QSTR(MP_QSTR_loop), MP_ROM_PTR(&ModPicoGraphics_loop_obj) },
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&ModPicoGraphics__del__obj) },
};
static MP_DEFINE_CONST_DICT(ModPicoGraphics_locals_dict, ModPicoGraphics_locals_dict_table);
/***** Class Definition *****/
#ifdef MP_DEFINE_CONST_OBJ_TYPE
MP_DEFINE_CONST_OBJ_TYPE(
ModPicoGraphics_type,
MP_QSTR_dvhstx,
MP_TYPE_FLAG_NONE,
make_new, ModPicoGraphics_make_new,
locals_dict, (mp_obj_dict_t*)&ModPicoGraphics_locals_dict
);
#else
const mp_obj_type_t ModPicoGraphics_type = {
{ &mp_type_type },
.name = MP_QSTR_dvhstx,
.make_new = ModPicoGraphics_make_new,
.locals_dict = (mp_obj_dict_t*)&ModPicoGraphics_locals_dict,
};
#endif
/***** Module Globals *****/
static const mp_map_elem_t picographics_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_dvhstx) },
{ MP_ROM_QSTR(MP_QSTR_DVHSTX), (mp_obj_t)&ModPicoGraphics_type },
{ MP_ROM_QSTR(MP_QSTR_PEN_RGB888), MP_ROM_INT(PEN_RGB888) },
{ MP_ROM_QSTR(MP_QSTR_PEN_RGB565), MP_ROM_INT(PEN_RGB565) },
{ MP_ROM_QSTR(MP_QSTR_PEN_P8), MP_ROM_INT(PEN_P8) },
{ MP_ROM_QSTR(MP_QSTR_BLEND_TARGET), MP_ROM_INT(0) },
{ MP_ROM_QSTR(MP_QSTR_BLEND_FIXED), MP_ROM_INT(1) },
{ MP_ROM_QSTR(MP_QSTR_SPRITE_OVERWRITE), MP_ROM_INT(0) },
{ MP_ROM_QSTR(MP_QSTR_SPRITE_UNDER), MP_ROM_INT(1) },
{ MP_ROM_QSTR(MP_QSTR_SPRITE_OVER), MP_ROM_INT(2) },
{ MP_ROM_QSTR(MP_QSTR_SPRITE_BLEND_UNDER), MP_ROM_INT(3) },
{ MP_ROM_QSTR(MP_QSTR_SPRITE_BLEND_OVER), MP_ROM_INT(4) },
#if SUPPORT_WIDE_MODES
{ MP_ROM_QSTR(MP_QSTR_WIDESCREEN), MP_ROM_TRUE },
#else
{ MP_ROM_QSTR(MP_QSTR_WIDESCREEN), MP_ROM_FALSE },
#endif
};
static MP_DEFINE_CONST_DICT(mp_module_picographics_globals, picographics_globals_table);
/***** Module Definition *****/
const mp_obj_module_t picographics_user_cmodule = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&mp_module_picographics_globals,
};
#if MICROPY_VERSION <= 70144
MP_REGISTER_MODULE(MP_QSTR_dvhstx, picographics_user_cmodule, MODULE_PICOGRAPHICS_ENABLED);
#else
MP_REGISTER_MODULE(MP_QSTR_dvhstx, picographics_user_cmodule);
#endif

View file

@ -0,0 +1,614 @@
#include "drivers/dvhstx/dvhstx.hpp"
#include "libraries/pico_graphics/pico_graphics_dvhstx.hpp"
#include "common/pimoroni_common.hpp"
#include "micropython/modules/util.hpp"
using namespace pimoroni;
extern "C" {
#include "picographics.h"
#include "pimoroni_i2c.h"
#include "py/stream.h"
#include "py/reader.h"
#include "py/objarray.h"
#include "extmod/vfs.h"
const std::string_view mp_obj_to_string_r(const mp_obj_t &obj) {
if(mp_obj_is_str_or_bytes(obj)) {
GET_STR_DATA_LEN(obj, str, str_len);
return std::string_view((const char*)str, str_len);
}
mp_raise_TypeError("can't convert object to str implicitly");
}
void __printf_debug_flush() {
for(auto i = 0u; i < 10; i++) {
sleep_ms(2);
mp_event_handle_nowait();
}
}
int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args);
void dvhstx_debug(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = mp_vprintf(&mp_plat_print, fmt, ap);
va_end(ap);
__printf_debug_flush();
(void)ret;
}
static DVHSTX dv_display;
typedef struct _ModPicoGraphics_obj_t {
mp_obj_base_t base;
PicoGraphicsDVHSTX *graphics;
DVHSTX *display;
} ModPicoGraphics_obj_t;
size_t get_required_buffer_size(PicoGraphicsPenType pen_type, uint width, uint height) {
switch(pen_type) {
//case PEN_RGB888:
//return PicoGraphics_PenDVHSTX_RGB888::buffer_size(width, height);
case PEN_RGB565:
return PicoGraphics_PenDVHSTX_RGB565::buffer_size(width, height);
case PEN_P8:
return PicoGraphics_PenDVHSTX_P8::buffer_size(width, height);
default:
return 0;
}
}
mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
ModPicoGraphics_obj_t *self = nullptr;
enum { ARG_pen_type, ARG_width, ARG_height };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_pen_type, MP_ARG_INT, { .u_int = PEN_P8 } },
{ MP_QSTR_width, MP_ARG_INT, { .u_int = 320 } },
{ MP_QSTR_height, MP_ARG_INT, { .u_int = 240 } }
};
// Parse args.
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
self = mp_obj_malloc_with_finaliser(ModPicoGraphics_obj_t, &ModPicoGraphics_type);
self->base.type = &ModPicoGraphics_type;
bool status = false;
int width = args[ARG_width].u_int;
int height = args[ARG_height].u_int;
int pen_type = args[ARG_pen_type].u_int;
dvhstx_debug("DVHSTX create display\n");
// Create an instance of the graphics library and DV display driver
switch((PicoGraphicsPenType)pen_type) {
case PEN_RGB888:
//self->graphics = m_new_class(PicoGraphics_PenDVHSTX_RGB888, width, height, dv_display);
//status = dv_display.init(width, height, DVHSTX::MODE_RGB888);
break;
case PEN_RGB565:
self->graphics = m_new_class(PicoGraphics_PenDVHSTX_RGB565, width, height, dv_display);
status = dv_display.init(width, height, DVHSTX::MODE_RGB565);
break;
case PEN_P8:
self->graphics = m_new_class(PicoGraphics_PenDVHSTX_P8, width, height, dv_display);
status = dv_display.init(width, height, DVHSTX::MODE_PALETTE);
break;
default:
break;
}
if (!status) {
mp_raise_msg(&mp_type_RuntimeError, "PicoVision: Unsupported Mode!");
}
dvhstx_debug("DVHSTX created\n");
self->display = &dv_display;
// Clear each buffer
for(auto x = 0u; x < 2u; x++){
self->graphics->set_pen(0);
self->graphics->clear();
dv_display.flip_now();
}
return MP_OBJ_FROM_PTR(self);
}
mp_obj_t ModPicoGraphics__del__(mp_obj_t self_in) {
//dv_display.reset();
return mp_const_none;
}
mp_obj_t ModPicoGraphics_set_font(mp_obj_t self_in, mp_obj_t font) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->graphics->set_font(mp_obj_to_string_r(font));
return mp_const_none;
}
mp_obj_t ModPicoGraphics_get_bounds(mp_obj_t self_in) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
mp_obj_t tuple[2] = {
mp_obj_new_int(self->graphics->bounds.w),
mp_obj_new_int(self->graphics->bounds.h)
};
return mp_obj_new_tuple(2, tuple);
}
mp_obj_t ModPicoGraphics_update(mp_obj_t self_in) {
(void)self_in;
dv_display.flip_blocking();
return mp_const_none;
}
mp_obj_t ModPicoGraphics_module_RGB332_to_RGB(mp_obj_t rgb332) {
RGB c((RGB332)mp_obj_get_int(rgb332));
mp_obj_t t[] = {
mp_obj_new_int(c.r),
mp_obj_new_int(c.g),
mp_obj_new_int(c.b),
};
return mp_obj_new_tuple(3, t);
}
mp_obj_t ModPicoGraphics_module_RGB565_to_RGB(mp_obj_t rgb565) {
RGB c((RGB565)mp_obj_get_int(rgb565));
mp_obj_t t[] = {
mp_obj_new_int(c.r),
mp_obj_new_int(c.g),
mp_obj_new_int(c.b),
};
return mp_obj_new_tuple(3, t);
}
mp_obj_t ModPicoGraphics_module_RGB_to_RGB332(mp_obj_t r, mp_obj_t g, mp_obj_t b) {
return mp_obj_new_int(RGB(
mp_obj_get_int(r),
mp_obj_get_int(g),
mp_obj_get_int(b)
).to_rgb332());
}
mp_obj_t ModPicoGraphics_module_RGB_to_RGB565(mp_obj_t r, mp_obj_t g, mp_obj_t b) {
return mp_obj_new_int(RGB(
mp_obj_get_int(r),
mp_obj_get_int(g),
mp_obj_get_int(b)
).to_rgb565());
}
mp_obj_t ModPicoGraphics_set_pen(mp_obj_t self_in, mp_obj_t pen) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->graphics->set_pen(mp_obj_get_int(pen));
return mp_const_none;
}
mp_obj_t ModPicoGraphics_set_bg(mp_obj_t self_in, mp_obj_t pen) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->graphics->set_bg(mp_obj_get_int(pen));
return mp_const_none;
}
mp_obj_t ModPicoGraphics_set_blend_mode(mp_obj_t self_in, mp_obj_t pen) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->graphics->set_blend_mode((BlendMode)mp_obj_get_int(pen));
return mp_const_none;
}
mp_obj_t ModPicoGraphics_set_depth(mp_obj_t self_in, mp_obj_t depth) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->graphics->set_depth(mp_obj_get_int(depth));
return mp_const_none;
}
mp_obj_t ModPicoGraphics_reset_pen(mp_obj_t self_in, mp_obj_t pen) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->graphics->reset_pen(mp_obj_get_int(pen));
return mp_const_none;
}
mp_obj_t ModPicoGraphics_update_pen(size_t n_args, const mp_obj_t *args) {
enum { ARG_self, ARG_i, ARG_r, ARG_g, ARG_b };
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t);
self->graphics->update_pen(
mp_obj_get_int(args[ARG_i]) & 0xff,
mp_obj_get_int(args[ARG_r]) & 0xff,
mp_obj_get_int(args[ARG_g]) & 0xff,
mp_obj_get_int(args[ARG_b]) & 0xff
);
return mp_const_none;
}
mp_obj_t ModPicoGraphics_create_pen(size_t n_args, const mp_obj_t *args) {
enum { ARG_self, ARG_r, ARG_g, ARG_b };
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t);
int result = self->graphics->create_pen(
mp_obj_get_int(args[ARG_r]) & 0xff,
mp_obj_get_int(args[ARG_g]) & 0xff,
mp_obj_get_int(args[ARG_b]) & 0xff
);
if (result == -1) mp_raise_ValueError("create_pen failed. No matching colour or space in palette!");
return mp_obj_new_int(result);
}
mp_obj_t ModPicoGraphics_create_pen_hsv(size_t n_args, const mp_obj_t *args) {
enum { ARG_self, ARG_h, ARG_s, ARG_v };
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t);
int result = self->graphics->create_pen_hsv(
mp_obj_get_float(args[ARG_h]),
mp_obj_get_float(args[ARG_s]),
mp_obj_get_float(args[ARG_v])
);
if (result == -1) mp_raise_ValueError("create_pen failed. No matching colour or space in palette!");
return mp_obj_new_int(result);
}
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);
self->graphics->set_thickness(mp_obj_get_int(pen));
return mp_const_none;
}
mp_obj_t ModPicoGraphics_set_palette(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
size_t num_tuples = n_args - 1;
const mp_obj_t *tuples = pos_args + 1;
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(pos_args[0], ModPicoGraphics_obj_t);
// Check if there is only one argument, which might be a list
if(n_args == 2) {
if(mp_obj_is_type(pos_args[1], &mp_type_list)) {
mp_obj_list_t *points = MP_OBJ_TO_PTR2(pos_args[1], mp_obj_list_t);
if(points->len <= 0) mp_raise_ValueError("set_palette(): cannot provide an empty list");
num_tuples = points->len;
tuples = points->items;
}
else {
mp_raise_TypeError("set_palette(): can't convert object to list");
}
}
for(size_t i = 0; i < num_tuples; i++) {
mp_obj_t obj = tuples[i];
if(!mp_obj_is_type(obj, &mp_type_tuple)) mp_raise_ValueError("set_palette(): can't convert object to tuple");
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR2(obj, mp_obj_tuple_t);
if(tuple->len != 3) mp_raise_ValueError("set_palette(): tuple must contain R, G, B values");
self->graphics->update_pen(
i,
mp_obj_get_int(tuple->items[0]),
mp_obj_get_int(tuple->items[1]),
mp_obj_get_int(tuple->items[2])
);
}
return mp_const_none;
}
mp_obj_t ModPicoGraphics_set_clip(size_t n_args, const mp_obj_t *args) {
enum { ARG_self, ARG_x, ARG_y, ARG_w, ARG_h };
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t);
self->graphics->set_clip({
mp_obj_get_int(args[ARG_x]),
mp_obj_get_int(args[ARG_y]),
mp_obj_get_int(args[ARG_w]),
mp_obj_get_int(args[ARG_h])
});
return mp_const_none;
}
mp_obj_t ModPicoGraphics_remove_clip(mp_obj_t self_in) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->graphics->remove_clip();
return mp_const_none;
}
mp_obj_t ModPicoGraphics_clear(mp_obj_t self_in) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->graphics->clear();
return mp_const_none;
}
mp_obj_t ModPicoGraphics_pixel(mp_obj_t self_in, mp_obj_t x, mp_obj_t y) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->graphics->pixel({
mp_obj_get_int(x),
mp_obj_get_int(y)
});
return mp_const_none;
}
mp_obj_t ModPicoGraphics_pixel_span(size_t n_args, const mp_obj_t *args) {
enum { ARG_self, ARG_x, ARG_y, ARG_l };
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t);
self->graphics->pixel_span({
mp_obj_get_int(args[ARG_x]),
mp_obj_get_int(args[ARG_y])
}, mp_obj_get_int(args[ARG_l]));
return mp_const_none;
}
mp_obj_t ModPicoGraphics_rectangle(size_t n_args, const mp_obj_t *args) {
enum { ARG_self, ARG_x, ARG_y, ARG_w, ARG_h };
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t);
self->graphics->rectangle({
mp_obj_get_int(args[ARG_x]),
mp_obj_get_int(args[ARG_y]),
mp_obj_get_int(args[ARG_w]),
mp_obj_get_int(args[ARG_h])
});
return mp_const_none;
}
mp_obj_t ModPicoGraphics_circle(size_t n_args, const mp_obj_t *args) {
enum { ARG_self, ARG_x, ARG_y, ARG_r };
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t);
self->graphics->circle({
mp_obj_get_int(args[ARG_x]),
mp_obj_get_int(args[ARG_y])
}, mp_obj_get_int(args[ARG_r]));
return mp_const_none;
}
mp_obj_t ModPicoGraphics_character(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_char, ARG_x, ARG_y, ARG_scale };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_char, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_scale, MP_ARG_INT, {.u_int = 2} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, ModPicoGraphics_obj_t);
int c = mp_obj_get_int(args[ARG_char].u_obj);
int x = args[ARG_x].u_int;
int y = args[ARG_y].u_int;
int scale = args[ARG_scale].u_int;
self->graphics->character((char)c, Point(x, y), scale);
return mp_const_none;
}
mp_obj_t ModPicoGraphics_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_text, ARG_x, ARG_y, ARG_wrap, ARG_scale, ARG_angle, ARG_spacing, ARG_fixed_width };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_text, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_x1, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_y1, MP_ARG_REQUIRED | MP_ARG_INT },
{ MP_QSTR_wordwrap, MP_ARG_INT, {.u_int = __INT32_MAX__} }, // Sort-of a fudge, but wide-enough to avoid wrapping on any display size
{ MP_QSTR_scale, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_angle, MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_spacing, MP_ARG_INT, {.u_int = 1} },
{ MP_QSTR_fixed_width, MP_ARG_OBJ, {.u_obj = mp_const_false} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, ModPicoGraphics_obj_t);
mp_obj_t text_obj = args[ARG_text].u_obj;
if(!mp_obj_is_str_or_bytes(text_obj)) mp_raise_TypeError("text: string required");
GET_STR_DATA_LEN(text_obj, str, str_len);
const std::string_view t((const char*)str, str_len);
int x = args[ARG_x].u_int;
int y = args[ARG_y].u_int;
int wrap = args[ARG_wrap].u_int;
float scale = args[ARG_scale].u_obj == mp_const_none ? 2.0f : mp_obj_get_float(args[ARG_scale].u_obj);
int angle = args[ARG_angle].u_int;
int letter_spacing = args[ARG_spacing].u_int;
bool fixed_width = args[ARG_fixed_width].u_obj == mp_const_true;
self->graphics->text(t, Point(x, y), wrap, scale, angle, letter_spacing, fixed_width);
return mp_const_none;
}
mp_obj_t ModPicoGraphics_measure_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_text, ARG_scale, ARG_spacing, ARG_fixed_width };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_text, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_scale, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_spacing, MP_ARG_INT, {.u_int = 1} },
{ MP_QSTR_fixed_width, MP_ARG_OBJ, {.u_obj = mp_const_false} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, ModPicoGraphics_obj_t);
mp_obj_t text_obj = args[ARG_text].u_obj;
if(!mp_obj_is_str_or_bytes(text_obj)) mp_raise_TypeError("text: string required");
GET_STR_DATA_LEN(text_obj, str, str_len);
const std::string_view t((const char*)str, str_len);
float scale = args[ARG_scale].u_obj == mp_const_none ? 2.0f : mp_obj_get_float(args[ARG_scale].u_obj);
int letter_spacing = args[ARG_spacing].u_int;
bool fixed_width = args[ARG_fixed_width].u_obj == mp_const_true;
int width = self->graphics->measure_text(t, scale, letter_spacing, fixed_width);
return mp_obj_new_int(width);
}
mp_obj_t ModPicoGraphics_polygon(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
size_t num_tuples = n_args - 1;
const mp_obj_t *tuples = pos_args + 1;
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(pos_args[0], ModPicoGraphics_obj_t);
// Check if there is only one argument, which might be a list
if(n_args == 2) {
if(mp_obj_is_type(pos_args[1], &mp_type_list)) {
mp_obj_list_t *points = MP_OBJ_TO_PTR2(pos_args[1], mp_obj_list_t);
if(points->len <= 0) mp_raise_ValueError("poly(): cannot provide an empty list");
num_tuples = points->len;
tuples = points->items;
}
else {
mp_raise_TypeError("poly(): can't convert object to list");
}
}
if(num_tuples > 0) {
std::vector<Point> points;
for(size_t i = 0; i < num_tuples; i++) {
mp_obj_t obj = tuples[i];
if(!mp_obj_is_type(obj, &mp_type_tuple)) mp_raise_ValueError("poly(): can't convert object to tuple");
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR2(obj, mp_obj_tuple_t);
if(tuple->len != 2) mp_raise_ValueError("poly(): tuple must only contain two numbers");
points.push_back({
mp_obj_get_int(tuple->items[0]),
mp_obj_get_int(tuple->items[1])
});
}
self->graphics->polygon(points);
}
return mp_const_none;
}
mp_obj_t ModPicoGraphics_triangle(size_t n_args, const mp_obj_t *args) {
enum { ARG_self, ARG_x1, ARG_y1, ARG_x2, ARG_y2, ARG_x3, ARG_y3 };
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t);
self->graphics->triangle(
{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_x3]),
mp_obj_get_int(args[ARG_y3])}
);
return mp_const_none;
}
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, ARG_thickness };
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self], ModPicoGraphics_obj_t);
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;
}
mp_obj_t ModPicoGraphics_loop(mp_obj_t self_in, mp_obj_t update, mp_obj_t render) {
(void)self_in;
/*
TODO: Uh how do we typecheck a function?
if(!mp_obj_is_type(update, &mp_type_function)) {
mp_raise_TypeError("update(ticks_ms) must be a function.");
}
if(!mp_obj_is_type(render, &mp_type_function)) {
mp_raise_TypeError("render(ticks_ms) must be a function.");
}*/
//ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
absolute_time_t t_start = get_absolute_time();
mp_obj_t result;
while(true) {
uint32_t tick = absolute_time_diff_us(t_start, get_absolute_time()) / 1000;
result = mp_call_function_1(update, mp_obj_new_int(tick));
if (result == mp_const_false) break;
dv_display.wait_for_flip();
result = mp_call_function_1(render, mp_obj_new_int(tick));
if (result == mp_const_false) break;
dv_display.flip_async();
#ifdef mp_event_handle_nowait
mp_event_handle_nowait();
#endif
}
return mp_const_none;
}
}

View file

@ -0,0 +1,103 @@
#include "py/runtime.h"
#include "py/objstr.h"
enum PicoGraphicsPenType {
PEN_1BIT = 0,
PEN_3BIT,
PEN_P2,
PEN_P4,
PEN_P8,
PEN_RGB332,
PEN_RGB565,
PEN_RGB888,
PEN_INKY7
};
enum PicoGraphicsBusType {
BUS_I2C,
BUS_SPI,
BUS_PARALLEL,
BUS_PIO
};
// Type
extern const mp_obj_type_t ModPicoGraphics_type;
// Module functions
extern mp_obj_t ModPicoGraphics_get_required_buffer_size(mp_obj_t display_in, mp_obj_t pen_type_in);
// DV Display specific functions
extern mp_obj_t ModPicoGraphics_set_scroll_group_offset(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t ModPicoGraphics_set_scroll_group_for_lines(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_tilemap(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t ModPicoGraphics_load_animation(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
// Class methods
extern mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args);
extern mp_obj_t ModPicoGraphics_update(mp_obj_t self_in);
// Palette management
extern mp_obj_t ModPicoGraphics_update_pen(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_reset_pen(mp_obj_t self_in, mp_obj_t pen);
extern mp_obj_t ModPicoGraphics_set_palette(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t ModPicoGraphics_hsv_to_rgb(size_t n_args, const mp_obj_t *args);
// Pen
extern mp_obj_t ModPicoGraphics_set_pen(mp_obj_t self_in, mp_obj_t pen);
extern mp_obj_t ModPicoGraphics_create_pen(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_create_pen_hsv(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_set_thickness(mp_obj_t self_in, mp_obj_t thickness);
// Alpha Blending
extern mp_obj_t ModPicoGraphics_set_bg(mp_obj_t self_in, mp_obj_t pen);
extern mp_obj_t ModPicoGraphics_set_blend_mode(mp_obj_t self_in, mp_obj_t pen);
// Depth
extern mp_obj_t ModPicoGraphics_set_depth(mp_obj_t self_in, mp_obj_t depth);
// Primitives
extern mp_obj_t ModPicoGraphics_set_clip(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_remove_clip(mp_obj_t self_in);
extern mp_obj_t ModPicoGraphics_clear(mp_obj_t self_in);
extern mp_obj_t ModPicoGraphics_pixel(mp_obj_t self_in, mp_obj_t x, mp_obj_t y);
extern mp_obj_t ModPicoGraphics_pixel_span(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_rectangle(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_circle(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_character(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t ModPicoGraphics_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t ModPicoGraphics_measure_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t ModPicoGraphics_polygon(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t ModPicoGraphics_triangle(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_line(size_t n_args, const mp_obj_t *args);
// Sprites
extern mp_obj_t ModPicoGraphics_load_sprite(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t ModPicoGraphics_display_sprite(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t ModPicoGraphics_clear_sprite(mp_obj_t self_in, mp_obj_t slot);
// Utility
extern mp_obj_t ModPicoGraphics_set_font(mp_obj_t self_in, mp_obj_t font);
extern mp_obj_t ModPicoGraphics_get_bounds(mp_obj_t self_in);
extern mp_obj_t ModPicoGraphics_get_i2c(mp_obj_t self_in);
extern mp_obj_t ModPicoGraphics__del__(mp_obj_t self_in);
// IO IO
extern mp_obj_t ModPicoGraphics_is_button_x_pressed(mp_obj_t self_in);
extern mp_obj_t ModPicoGraphics_is_button_a_pressed(mp_obj_t self_in);
extern mp_obj_t ModPicoGraphics_get_gpu_io_value(mp_obj_t self_in, mp_obj_t pin);
extern mp_obj_t ModPicoGraphics_set_gpu_io_value(mp_obj_t self_in, mp_obj_t pin, mp_obj_t value);
extern mp_obj_t ModPicoGraphics_set_gpu_io_output_enable(mp_obj_t self_in, mp_obj_t pin, mp_obj_t enable);
extern mp_obj_t ModPicoGraphics_set_gpu_io_pull_up(mp_obj_t self_in, mp_obj_t pin, mp_obj_t enable);
extern mp_obj_t ModPicoGraphics_set_gpu_io_pull_down(mp_obj_t self_in, mp_obj_t pin, mp_obj_t enable);
extern mp_obj_t ModPicoGraphics_set_gpu_io_adc_enable(mp_obj_t self_in, mp_obj_t pin, mp_obj_t enable);
extern mp_obj_t ModPicoGraphics_get_gpu_io_adc_voltage(mp_obj_t self_in, mp_obj_t pin);
extern mp_obj_t ModPicoGraphics_get_gpu_temp(mp_obj_t self_in);
// Loop
extern mp_obj_t ModPicoGraphics_loop(mp_obj_t self_in, mp_obj_t update, mp_obj_t render);

View file

@ -0,0 +1,37 @@
#!/bin/env python3
from PIL import Image
import numpy
import sys
import pathlib
# Run with `./filename.py source-image.jpg`
IMAGE_PATH = pathlib.Path(sys.argv[1])
OUTPUT_PATH = IMAGE_PATH.with_suffix(".rgb332")
def image_to_data(image):
"""Generator function to convert a PIL image to 16-bit 565 RGB bytes."""
# NumPy is much faster at doing this. NumPy code provided by:
# Keith (https://www.blogger.com/profile/02555547344016007163)
pb = numpy.array(image.convert('RGBA')).astype('uint16')
r = (pb[:, :, 0] & 0b11100000) >> 0
g = (pb[:, :, 1] & 0b11100000) >> 3
b = (pb[:, :, 2] & 0b11000000) >> 6
a = pb[:, :, 3] # Discard
# AAAA RRRR GGGG BBBB
color = r | g | b
return color.astype("uint8").flatten().tobytes()
img = Image.open(IMAGE_PATH)
w, h = img.size
data = image_to_data(img)
print(f"Converted: {w}x{h} {len(data)} bytes")
with open(OUTPUT_PATH, "wb") as f:
f.write(data)
print(f"Written to: {OUTPUT_PATH}")