diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a834aad1..ac939c96 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -21,6 +21,7 @@ add_subdirectory(breakout_as7262) add_subdirectory(breakout_bh1745) add_subdirectory(pico_display) +add_subdirectory(pico_display_2) add_subdirectory(pico_unicorn) add_subdirectory(pico_unicorn_plasma) add_subdirectory(pico_scroll) diff --git a/examples/pico_display_2/CMakeLists.txt b/examples/pico_display_2/CMakeLists.txt new file mode 100644 index 00000000..00228e0f --- /dev/null +++ b/examples/pico_display_2/CMakeLists.txt @@ -0,0 +1,12 @@ +set(OUTPUT_NAME display_2) + +add_executable( + ${OUTPUT_NAME} + demo.cpp +) + +# Pull in pico libraries that we need +target_link_libraries( ${OUTPUT_NAME} pico_stdlib hardware_spi hardware_pwm hardware_dma pico_display_2) + +# create map/bin/hex file etc. +pico_add_extra_outputs( ${OUTPUT_NAME}) \ No newline at end of file diff --git a/examples/pico_display_2/demo.cpp b/examples/pico_display_2/demo.cpp new file mode 100644 index 00000000..95f812bb --- /dev/null +++ b/examples/pico_display_2/demo.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include + +#include "pico_display_2.hpp" + +using namespace pimoroni; + +uint16_t buffer[PicoDisplay2::WIDTH * PicoDisplay2::HEIGHT]; +PicoDisplay2 pico_display(buffer); + +// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel +// Outputs are rgb in the range 0-255 for each channel +void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) { + float i = floor(h * 6.0f); + float f = h * 6.0f - i; + v *= 255.0f; + uint8_t p = v * (1.0f - s); + uint8_t q = v * (1.0f - f * s); + uint8_t t = v * (1.0f - (1.0f - f) * s); + + switch (int(i) % 6) { + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + } +} + +int main() { + pico_display.init(); + pico_display.set_backlight(255); + + struct pt { + float x; + float y; + uint8_t r; + float dx; + float dy; + uint16_t pen; + }; + + std::vector shapes; + for(int i = 0; i < 100; i++) { + pt shape; + shape.x = rand() % pico_display.bounds.w; + shape.y = rand() % pico_display.bounds.h; + shape.r = (rand() % 10) + 3; + shape.dx = float(rand() % 255) / 64.0f; + shape.dy = float(rand() % 255) / 64.0f; + shape.pen = pico_display.create_pen(rand() % 255, rand() % 255, rand() % 255); + shapes.push_back(shape); + } + + Point text_location(0, 0); + + while(true) { + if(pico_display.is_pressed(pico_display.A)) text_location.x -= 1; + if(pico_display.is_pressed(pico_display.B)) text_location.x += 1; + + if(pico_display.is_pressed(pico_display.X)) text_location.y -= 1; + if(pico_display.is_pressed(pico_display.Y)) text_location.y += 1; + + pico_display.set_pen(120, 40, 60); + pico_display.clear(); + + for(auto &shape : shapes) { + shape.x += shape.dx; + shape.y += shape.dy; + if((shape.x - shape.r) < 0) { + shape.dx *= -1; + shape.x = shape.r; + } + if((shape.x + shape.r) >= pico_display.bounds.w) { + shape.dx *= -1; + shape.x = pico_display.bounds.w - shape.r; + } + if((shape.y - shape.r) < 0) { + shape.dy *= -1; + shape.y = shape.r; + } + if((shape.y + shape.r) >= pico_display.bounds.h) { + shape.dy *= -1; + shape.y = pico_display.bounds.h - shape.r; + } + + pico_display.set_pen(shape.pen); + pico_display.circle(Point(shape.x, shape.y), shape.r); + + } + + // Since HSV takes a float from 0.0 to 1.0 indicating hue, + // then we can divide millis by the number of milliseconds + // we want a full colour cycle to take. 5000 = 5 sec. + uint8_t r = 0, g = 0, b = 0; + from_hsv((float)millis() / 5000.0f, 1.0f, 0.5f + sinf(millis() / 100.0f / 3.14159f) * 0.5f, r, g, b); + pico_display.set_led(r, g, b); + + + pico_display.set_pen(255, 255, 255); + pico_display.text("Hello World", text_location, 320); + + // update screen + pico_display.update(); + } + + return 0; +} diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 9f1c2250..cd30b160 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -18,6 +18,7 @@ add_subdirectory(breakout_msa301) add_subdirectory(breakout_bh1745) add_subdirectory(pico_graphics) add_subdirectory(pico_display) +add_subdirectory(pico_display_2) add_subdirectory(pico_unicorn) add_subdirectory(pico_scroll) add_subdirectory(pico_explorer) diff --git a/libraries/pico_display_2/CMakeLists.txt b/libraries/pico_display_2/CMakeLists.txt new file mode 100644 index 00000000..a9333008 --- /dev/null +++ b/libraries/pico_display_2/CMakeLists.txt @@ -0,0 +1,11 @@ +set(LIB_NAME pico_display_2) +add_library(${LIB_NAME} INTERFACE) + +target_sources(${LIB_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${LIB_NAME}.cpp +) + +target_include_directories(${LIB_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +# Pull in pico libraries that we need +target_link_libraries(${LIB_NAME} INTERFACE pico_stdlib hardware_spi hardware_pwm hardware_dma st7789 pico_graphics) \ No newline at end of file diff --git a/libraries/pico_display_2/README.md b/libraries/pico_display_2/README.md new file mode 100644 index 00000000..ee454773 --- /dev/null +++ b/libraries/pico_display_2/README.md @@ -0,0 +1,123 @@ +# Pico Display 2.0" Pack + +Our Pico Display Pack offers a vibrant 1.14" (240x135) IPS LCD screen for your Raspberry Pi Pico it also includes four switches and and an RGB LED! + +We've included helper functions to handle every aspect of drawing to the screen and interfacing with the buttons and LED. See the [function reference](#function-reference) for details. + +- [Example Program](#example-program) +- [Function Reference](#function-reference) + - [PicoGraphics](#picographics) + - [init](#init) + - [set_backlight](#set_backlight) + - [set_led](#set_led) + - [is_pressed](#is_pressed) + - [update](#update) + +## Example Program + +The following example sets up Pico Display, displays some basic demo text and graphics and will illuminate the RGB LED green if the A button is pressed. + +```c++ +#include "pico_display_2.hpp" + +using namespace pimoroni; + +uint16_t buffer[PicoDisplay2::WIDTH * PicoDisplay2::HEIGHT]; +PicoDisplay2 pico_display(buffer); + +int main() { + pico_display.init(); + + // set the backlight to a value between 0 and 255 + // the backlight is driven via PWM and is gamma corrected by our + // library to give a gorgeous linear brightness range. + pico_display.set_backlight(100); + + while(true) { + // detect if the A button is pressed (could be A, B, X, or Y) + if(pico_display.is_pressed(pico_display.A)) { + // make the led glow green + // parameters are red, green, blue all between 0 and 255 + // these are also gamma corrected + pico_display.set_led(0, 255, 0); + } + + // set the colour of the pen + // parameters are red, green, blue all between 0 and 255 + pico_display.set_pen(30, 40, 50); + + // fill the screen with the current pen colour + pico_display.clear(); + + // draw a box to put some text in + pico_display.set_pen(10, 20, 30); + Rect text_rect(10, 10, 150, 150); + pico_display.rectangle(text_rect); + + // write some text inside the box with 10 pixels of margin + // automatically word wrapping + text_rect.deflate(10); + pico_display.set_pen(110, 120, 130); + pico_display.text("This is a message", Point(text_rect.x, text_rect.y), text_rect.w); + + // now we've done our drawing let's update the screen + pico_display.update(); + } +} +``` + +## Function Reference + +### PicoGraphics + +Pico Display uses our Pico Graphics library to draw graphics and text. For more information [read the Pico Graphics function reference.](../pico_graphics/README.md#function-reference). + +### init + +Sets up Pico Display. `init` must be called before any other functions since it configures the required PWM and GPIO: + +```c++ +pico_display.init(); +``` + +### set_backlight + +Set the display backlight from 0-255. + +```c++ +pico_display.set_backlight(brightness); +``` + +Uses hardware PWM to dim the display backlight, dimming values are gamma-corrected to provide smooth brightness transitions across the full range of intensity. This may result in some low values mapping as "off." + +### set_led + +Sets the RGB LED on Pico Display with an RGB triplet: + +```c++ +pico_display.set_led(r, g, b); +``` + +Uses hardware PWM to drive the LED. Values are automatically gamma-corrected to provide smooth brightness transitions and low values may map as "off." + +### is_pressed + +Reads the GPIO pin connected to one of Pico Display's buttons, returning a `bool` - `true` if it's pressed and `false` if it is released. + +```c++ +pico_display.is_pressed(button); +``` + +The button vaule should be a `uint8_t` denoting a pin, and constants `A`, `B`, `X` and `Y` are supplied to make it easier. e: + +```c++ +bool is_a_button_pressed = pico_display.is_pressed(PicoDisplay2::A) +``` + +### update + +To display your changes on Pico Display's screen you need to call `update`: + +```c++ +pico_display.update(); +``` diff --git a/libraries/pico_display_2/pico_display_2.cmake b/libraries/pico_display_2/pico_display_2.cmake new file mode 100644 index 00000000..1b75294d --- /dev/null +++ b/libraries/pico_display_2/pico_display_2.cmake @@ -0,0 +1,14 @@ +include(${CMAKE_CURRENT_LIST_DIR}/../../drivers/st7789/st7789.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/../pico_graphics/pico_graphics.cmake) + +set(LIB_NAME pico_display_2) +add_library(${LIB_NAME} INTERFACE) + +target_sources(${LIB_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${LIB_NAME}.cpp +) + +target_include_directories(${LIB_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +# Pull in pico libraries that we need +target_link_libraries(${LIB_NAME} INTERFACE pico_stdlib hardware_spi hardware_pwm hardware_dma st7789 pico_graphics) \ No newline at end of file diff --git a/libraries/pico_display_2/pico_display_2.cpp b/libraries/pico_display_2/pico_display_2.cpp new file mode 100644 index 00000000..38227b33 --- /dev/null +++ b/libraries/pico_display_2/pico_display_2.cpp @@ -0,0 +1,90 @@ +#include +#include + +#include "hardware/gpio.h" // Workaround SDK bug - https://github.com/raspberrypi/pico-sdk/issues/3 +#include "hardware/pwm.h" + +#include "pico_display_2.hpp" + +const uint8_t LED_R = 6; +const uint8_t LED_G = 7; +const uint8_t LED_B = 8; + +namespace pimoroni { + + PicoDisplay2::PicoDisplay2(uint16_t *buf) + : PicoGraphics(WIDTH, HEIGHT, buf), screen(WIDTH, HEIGHT, buf, BG_SPI_FRONT) { + __fb = buf; + } + + PicoDisplay2::PicoDisplay2(uint16_t *buf, int width, int height) + : PicoGraphics(width, height, buf), screen(width, height, buf, BG_SPI_FRONT) { + __fb = buf; + } + + void PicoDisplay2::init() { + // setup the rgb led for pwm control + pwm_config cfg = pwm_get_default_config(); + pwm_config_set_output_polarity(&cfg, true, true); + + // red + pwm_set_wrap(pwm_gpio_to_slice_num(LED_R), 65535); + pwm_init(pwm_gpio_to_slice_num(LED_R), &cfg, true); + gpio_set_function(LED_R, GPIO_FUNC_PWM); + + // green + pwm_set_wrap(pwm_gpio_to_slice_num(LED_G), 65535); + pwm_init(pwm_gpio_to_slice_num(LED_G), &cfg, true); + gpio_set_function(LED_G, GPIO_FUNC_PWM); + + // blue + pwm_set_wrap(pwm_gpio_to_slice_num(LED_B), 65535); + pwm_init(pwm_gpio_to_slice_num(LED_B), &cfg, true); + gpio_set_function(LED_B, GPIO_FUNC_PWM); + + // setup button inputs + gpio_set_function(A, GPIO_FUNC_SIO); gpio_set_dir(A, GPIO_IN); gpio_pull_up(A); + gpio_set_function(B, GPIO_FUNC_SIO); gpio_set_dir(B, GPIO_IN); gpio_pull_up(B); + gpio_set_function(X, GPIO_FUNC_SIO); gpio_set_dir(X, GPIO_IN); gpio_pull_up(X); + gpio_set_function(Y, GPIO_FUNC_SIO); gpio_set_dir(Y, GPIO_IN); gpio_pull_up(Y); + + // initialise the screen + screen.init(true, false, 74 * 1000 * 1000); + } + + void PicoDisplay2::update() { + screen.update(); + } + + void PicoDisplay2::set_backlight(uint8_t brightness) { + screen.set_backlight(brightness); + } + + void PicoDisplay2::set_led(uint8_t r, uint8_t g, uint8_t b) { + // gamma correct the provided 0-255 brightness value onto a + // 0-65535 range for the pwm counter + static const float gamma = 2.8; + + uint16_t value; + + // red + value = (uint16_t)(pow((float)(r) / 255.0f, gamma) * 65535.0f + 0.5f); + pwm_set_gpio_level(LED_R, value); + + // green + value = (uint16_t)(pow((float)(g) / 255.0f, gamma) * 65535.0f + 0.5f); + pwm_set_gpio_level(LED_G, value); + + // blue + value = (uint16_t)(pow((float)(b) / 255.0f, gamma) * 65535.0f + 0.5f); + pwm_set_gpio_level(LED_B, value); + } + + bool PicoDisplay2::is_pressed(uint8_t button) { + return !gpio_get(button); + } + + void PicoDisplay2::flip() { + screen.flip(); + } +} diff --git a/libraries/pico_display_2/pico_display_2.hpp b/libraries/pico_display_2/pico_display_2.hpp new file mode 100644 index 00000000..55c0afdf --- /dev/null +++ b/libraries/pico_display_2/pico_display_2.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "drivers/st7789/st7789.hpp" +#include "libraries/pico_graphics/pico_graphics.hpp" + +namespace pimoroni { + + class PicoDisplay2 : public PicoGraphics { + public: + static const int WIDTH = 320; + static const int HEIGHT = 240; + static const int PORTRAIT_WIDTH = 240; + static const int PORTRAIT_HEIGHT = 320; + static const uint8_t A = 12; + static const uint8_t B = 13; + static const uint8_t X = 14; + static const uint8_t Y = 15; + + uint16_t *__fb; + private: + ST7789 screen; + + public: + PicoDisplay2(uint16_t *buf); + PicoDisplay2(uint16_t *buf, int width, int height); + + void init(); + void update(); + void set_backlight(uint8_t brightness); + void set_led(uint8_t r, uint8_t g, uint8_t b); + bool is_pressed(uint8_t button); + void flip(); + }; + +} diff --git a/micropython/examples/pico_display/buttons.py b/micropython/examples/pico_display/buttons.py index cec17963..c496bedf 100644 --- a/micropython/examples/pico_display/buttons.py +++ b/micropython/examples/pico_display/buttons.py @@ -1,6 +1,7 @@ # This example shows you a simple, non-interrupt way of reading Pico Display's buttons with a loop that checks to see if buttons are pressed. -import picodisplay as display +import picodisplay as display # Comment this line out to use PicoDisplay2 +# import picodisplay2 as display # Uncomment this line to use PicoDisplay2 import utime # Initialise display with a bytearray display buffer diff --git a/micropython/examples/pico_display/demo.py b/micropython/examples/pico_display/demo.py index 5132f556..ce383d70 100644 --- a/micropython/examples/pico_display/demo.py +++ b/micropython/examples/pico_display/demo.py @@ -1,6 +1,7 @@ import time import random -import picodisplay as display +import picodisplay as display # Comment this line out to use PicoDisplay2 +# import picodisplay2 as display # Uncomment this line to use PicoDisplay2 width = display.get_width() height = display.get_height() diff --git a/micropython/examples/pico_display/rainbow.py b/micropython/examples/pico_display/rainbow.py index 8fae5e2e..8ed33c3f 100644 --- a/micropython/examples/pico_display/rainbow.py +++ b/micropython/examples/pico_display/rainbow.py @@ -1,7 +1,8 @@ # This example borrows a CircuitPython hsv_to_rgb function to cycle through some rainbows on Pico Display's screen and RGB LED . If you're into rainbows, HSV (Hue, Saturation, Value) is very useful! import utime -import picodisplay as display +import picodisplay as display # Comment this line out to use PicoDisplay2 +# import picodisplay2 as display # Uncomment this line to use PicoDisplay2 # Set up and initialise Pico Display buf = bytearray(display.get_width() * display.get_height() * 2) diff --git a/micropython/examples/pico_display/thermometer.py b/micropython/examples/pico_display/thermometer.py index bceba3e8..f64a0930 100644 --- a/micropython/examples/pico_display/thermometer.py +++ b/micropython/examples/pico_display/thermometer.py @@ -6,7 +6,9 @@ import utime import gc # Pico Display boilerplate -import picodisplay as display +import picodisplay as display # Comment this line out to use PicoDisplay2 +# import picodisplay2 as display # Uncomment this line to use PicoDisplay2 + width = display.get_width() height = display.get_height() gc.collect() diff --git a/micropython/modules/micropython.cmake b/micropython/modules/micropython.cmake index 30044027..215b3def 100644 --- a/micropython/modules/micropython.cmake +++ b/micropython/modules/micropython.cmake @@ -30,6 +30,7 @@ include(pico_scroll/micropython) include(pico_rgb_keypad/micropython) include(pico_unicorn/micropython) include(pico_display/micropython) +include(pico_display_2/micropython) include(pico_explorer/micropython) include(pico_wireless/micropython) include(plasma/micropython) diff --git a/micropython/modules/pico_display_2/README.md b/micropython/modules/pico_display_2/README.md new file mode 100644 index 00000000..ccf580c4 --- /dev/null +++ b/micropython/modules/pico_display_2/README.md @@ -0,0 +1,206 @@ +# Pico Display Pack - MicroPython + +Pico Display 2.0" Pack is a vibrant 2.0", 320 x 240 pixel IPS LCD screen for your Raspberry Pi Pico, with four useful buttons and a RGB LED. [Click here](https://shop.pimoroni.com/products/pico-display-pack) to find out more! + +We've included helper functions to handle every aspect of drawing to the screen and interfacing with the buttons and LED. See the [function reference](#function-reference) for details. + +Check out [UnfinishedStuff's excellent Display Pack guide](https://github.com/UnfinishedStuff/Pimoroni_Pico_Display_Pack_documentation) for more detail on the functions and code examples, and [tonygo2's Display Pack Workout](https://www.instructables.com/Pimoroni-Pico-Display-Workout/) for a comprehensive demo! + +- [Example Program](#example-program) +- [Function Reference](#function-reference) + - [init](#init) + - [set_backlight](#set_backlight) + - [set_led](#set_led) + - [is_pressed](#is_pressed) + - [update](#update) + - [set_pen](#set_pen) + - [create_pen](#create_pen) + - [clear](#clear) + - [pixel](#pixel) + - [pixel_span](#pixel_span) + - [rectangle](#rectangle) + - [circle](#circle) + - [character](#character) + - [text](#text) + - [set_clip](#set_clip) + - [remove_clip](#remove_clip) + +## Example Program + +The following example sets up Pico Display, displays some basic demo text and illuminates the RGB LED green when the A button is pressed. + +```python +import utime +import picodisplay2 as picodisplay + +# Initialise Picodisplay with a bytearray display buffer +buf = bytearray(picodisplay.get_width() * picodisplay.get_height() * 2) +picodisplay.init(buf) +picodisplay.set_backlight(1.0) + +picodisplay.set_pen(255, 0, 0) # Set a red pen +picodisplay.clear() # Clear the display buffer +picodisplay.set_pen(255, 255, 255) # Set a white pen +picodisplay.text("pico display", 10, 10, 240, 6) # Add some text +picodisplay.update() # Update the display with our changes + +picodisplay.set_led(255, 0, 0) # Set the RGB LED to red +utime.sleep(1) # Wait for a second +picodisplay.set_led(0, 255, 0) # Set the RGB LED to green +utime.sleep(1) # Wait for a second +picodisplay.set_led(0, 0, 255) # Set the RGB LED to blue + +while picodisplay.is_pressed(picodisplay.BUTTON_A) == False: + pass + +picodisplay.set_led(0, 255, 0) # Set the RGB LED to green +``` + +## Function Reference + +### init + +Sets up Pico Display. `init` must be called before any other functions since it configures the required PWM and GPIO. `init()` needs a bytearray type display buffer that MicroPython's garbage collection isn't going to eat, make sure you create one and pass it in like so: + +```python +buf = bytearray(picodisplay.get_width() * picodisplay.get_height() * 2) +picodisplay.init(buf) +``` + +### set_backlight + +Sets the display backlight from 0.0 to 1.0. + +```python +picodisplay.set_backlight(brightness) +``` + +Uses hardware PWM to dim the display backlight, dimming values are gamma-corrected to provide smooth brightness transitions across the full range of intensity. This may result in some low values mapping as "off." + +### set_led + +Sets the RGB LED on Pico Display with an RGB triplet. + +```python +picodisplay.set_led(r, g, b) +``` + +Uses hardware PWM to drive the LED. Values are automatically gamma-corrected to provide smooth brightness transitions and low values may map as "off." + +### is_pressed + +Reads the GPIO pin connected to one of Pico Display's buttons, returning `True` if it's pressed and `False` if it is released. + +```python +picodisplay.is_pressed(button) +``` + +The button value should be a number denoting a pin, and constants `picodisplay.BUTTON_A`, `picodisplay.BUTTON_B`, `picodisplay.BUTTON_X` and `picodisplay.BUTTON_Y` are supplied to make it easier. e: + +```python +is_a_button_pressed = picodisplay.is_pressed(picodisplay.BUTTON_A) +``` + +### update + +To display your changes on Pico Display's screen you need to call `update`. + +```python +picodisplay.update() +``` + +### set_pen + +Sets the colour to be used by subsequent calls to drawing functions. The values for `r`, `g` and `b` should be from 0-255 inclusive. + +```python +picodisplay.set_pen(r, g, b) +``` + +### create_pen + +Creates a pen which can be stored as a variable for faster re-use of the same colour through calls to `set_pen`. The values for `r`, `g` and `b` should be from 0-255 inclusive. + +```python +pen_colour = picodisplay.create_pen(r, g, b) +picodisplay.set_pen(penColour) +``` + +### clear + +Fills the display buffer with the currently set pen colour. + +```python +picodisplay.clear() +``` + +### pixel + +Sets a single pixel in the display buffer to the current pen colour. The `x` and `y` parameters determine the X and Y coordinates of the drawn pixel in the buffer. + +```python +picodisplay.pixel(x, y) +``` + +### pixel_span + +Draws a horizontal line of pixels to the buffer. The `x` and `y` parameters specify the coordinates of the first pixel of the line. The `l` parameter describes the length of the line in pixels. This function will only extend the line towards the end of the screen, i.e. the `x` coordinate should specify the left hand extreme of the line. + +```python +picodisplay.pixel_span(x, y, l) +``` + +### rectangle + +Draws a rectangle filled with the current pen colour to the buffer. The `x` and `y` parameters specify the upper left corner of the rectangle, `w` specifies the width in pixels, and `h` the height. + +```python +picodisplay.rectangle(x, y, w, h) +``` + +![Rectangle function explanation image](/micropython/modules/pico_display/images/rectangle.png) + +### circle + +Draws a circle filled with the current pen colour to the buffer. The `x` and `y` parameters specify the centre of the circle, `r` specifies the radius in pixels. + +```python +picodisplay.circle(x, y, r) +``` + +![Circle function explanation image](/micropython/modules/pico_display/images/circle.png) + +### character + +Draws a single character to the display buffer in the current pen colour. The `c` parameter should be the ASCII numerical representation of the character to be printed, `x` and `y` describe the top-left corner of the character's drawing field. The `character` function can also be given an optional 4th parameter, `scale`, describing the scale of the character to be drawn. Default value is 2. + +```python +char_a = ord('a') +picodisplay.character(char_a, x, y) +picodisplay.character(char_a, x, y, scale) +``` + +### text + +Draws a string of text to the display buffer in the current pen colour. The `string` parameter is the string of text to be drawn, and `x` and `y` specify the upper left corner of the drawing field. The `wrap` parameter describes the width, in pixels, after which the next word in the string will be drawn on a new line underneath the current text. This will wrap the string over multiple lines if required. This function also has an optional parameter, `scale`, which describes the size of the characters to be drawn. The default `scale` is 2. + +```python +picodisplay.text(string, x, y, wrap) +picodisplay.text(string, x, y, wrap, scale) +``` + +![Text scale explanation image](/micropython/modules/pico_display/images/text_scale.png) + +### set_clip + +This function defines a rectangular area outside which no drawing actions will take effect. If a drawing action crosses the boundary of the clip then only the pixels inside the clip will be drawn. Note that `clip` does not remove pixels which have already been drawn, it only prevents new pixels being drawn outside the described area. A more visual description of the function of clips can be found below. Only one clip can be active at a time, and defining a new clip replaces any previous clips. The `x` and `y` parameters describe the upper-left corner of the clip area, `w` and `h` describe the width and height in pixels. + +```python +picodisplay.set_clip(x, y, w, h) +``` + +![Clip function explanation image](/micropython/modules/pico_display/images/clip.png) + +### remove_clip + +This function removes any currently implemented clip. diff --git a/micropython/modules/pico_display_2/images/circle.png b/micropython/modules/pico_display_2/images/circle.png new file mode 100644 index 00000000..4efadd9c Binary files /dev/null and b/micropython/modules/pico_display_2/images/circle.png differ diff --git a/micropython/modules/pico_display_2/images/clip.png b/micropython/modules/pico_display_2/images/clip.png new file mode 100644 index 00000000..a2d28094 Binary files /dev/null and b/micropython/modules/pico_display_2/images/clip.png differ diff --git a/micropython/modules/pico_display_2/images/rectangle.png b/micropython/modules/pico_display_2/images/rectangle.png new file mode 100644 index 00000000..4e0c3467 Binary files /dev/null and b/micropython/modules/pico_display_2/images/rectangle.png differ diff --git a/micropython/modules/pico_display_2/images/text_scale.png b/micropython/modules/pico_display_2/images/text_scale.png new file mode 100644 index 00000000..1f029ed2 Binary files /dev/null and b/micropython/modules/pico_display_2/images/text_scale.png differ diff --git a/micropython/modules/pico_display_2/micropython.cmake b/micropython/modules/pico_display_2/micropython.cmake new file mode 100644 index 00000000..bb710909 --- /dev/null +++ b/micropython/modules/pico_display_2/micropython.cmake @@ -0,0 +1,28 @@ +set(MOD_NAME pico_display_2) +string(TOUPPER ${MOD_NAME} MOD_NAME_UPPER) +add_library(usermod_${MOD_NAME} INTERFACE) + +target_sources(usermod_${MOD_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c + ${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/${MOD_NAME}/${MOD_NAME}.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../../drivers/st7789/st7789.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/pico_graphics.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../../libraries/pico_graphics/types.cpp +) + +target_include_directories(usermod_${MOD_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +target_compile_definitions(usermod_${MOD_NAME} INTERFACE + MODULE_${MOD_NAME_UPPER}_ENABLED=1 +) + +target_link_libraries(usermod INTERFACE usermod_${MOD_NAME}) + +set_source_files_properties( + ${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c + PROPERTIES COMPILE_FLAGS + "-Wno-discarded-qualifiers -Wno-implicit-int" +) diff --git a/micropython/modules/pico_display_2/pico_display_2.c b/micropython/modules/pico_display_2/pico_display_2.c new file mode 100755 index 00000000..64fcfbcb --- /dev/null +++ b/micropython/modules/pico_display_2/pico_display_2.c @@ -0,0 +1,80 @@ +#include "pico_display_2.h" + +/***** Constants *****/ +enum buttons +{ + BUTTON_A = 0, + BUTTON_B, + BUTTON_X, + BUTTON_Y, +}; + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// picodisplay2 Module +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/***** Module Functions *****/ +STATIC MP_DEFINE_CONST_FUN_OBJ_1(picodisplay2_init_obj, picodisplay2_init); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(picodisplay2_get_width_obj, picodisplay2_get_width); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(picodisplay2_get_height_obj, picodisplay2_get_height); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(picodisplay2_update_obj, picodisplay2_update); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(picodisplay2_set_backlight_obj, picodisplay2_set_backlight); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(picodisplay2_set_led_obj, picodisplay2_set_led); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(picodisplay2_is_pressed_obj, picodisplay2_is_pressed); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(picodisplay2_flip_obj, picodisplay2_flip); + +//From PicoGraphics parent class +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(picodisplay2_set_pen_obj, 1, 3, picodisplay2_set_pen); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(picodisplay2_create_pen_obj, picodisplay2_create_pen); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(picodisplay2_set_clip_obj, 4, 4, picodisplay2_set_clip); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(picodisplay2_remove_clip_obj, picodisplay2_remove_clip); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(picodisplay2_clear_obj, picodisplay2_clear); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(picodisplay2_pixel_obj, picodisplay2_pixel); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(picodisplay2_pixel_span_obj, picodisplay2_pixel_span); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(picodisplay2_rectangle_obj, 4, 4, picodisplay2_rectangle); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(picodisplay2_circle_obj, picodisplay2_circle); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(picodisplay2_character_obj, 3, 4, picodisplay2_character); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(picodisplay2_text_obj, 4, 5, picodisplay2_text); + + +/***** Globals Table *****/ +STATIC const mp_map_elem_t picodisplay2_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_picodisplay2) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&picodisplay2_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_width), MP_ROM_PTR(&picodisplay2_get_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_height), MP_ROM_PTR(&picodisplay2_get_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&picodisplay2_update_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_backlight), MP_ROM_PTR(&picodisplay2_set_backlight_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_led), MP_ROM_PTR(&picodisplay2_set_led_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_pressed), MP_ROM_PTR(&picodisplay2_is_pressed_obj) }, + { MP_ROM_QSTR(MP_QSTR_flip), MP_ROM_PTR(&picodisplay2_flip_obj) }, + + { MP_ROM_QSTR(MP_QSTR_set_pen), MP_ROM_PTR(&picodisplay2_set_pen_obj) }, + { MP_ROM_QSTR(MP_QSTR_create_pen), MP_ROM_PTR(&picodisplay2_create_pen_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_clip), MP_ROM_PTR(&picodisplay2_set_clip_obj) }, + { MP_ROM_QSTR(MP_QSTR_remove_clip), MP_ROM_PTR(&picodisplay2_remove_clip_obj) }, + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&picodisplay2_clear_obj) }, + { MP_ROM_QSTR(MP_QSTR_pixel), MP_ROM_PTR(&picodisplay2_pixel_obj) }, + { MP_ROM_QSTR(MP_QSTR_pixel_span), MP_ROM_PTR(&picodisplay2_pixel_span_obj) }, + { MP_ROM_QSTR(MP_QSTR_rectangle), MP_ROM_PTR(&picodisplay2_rectangle_obj) }, + { MP_ROM_QSTR(MP_QSTR_circle), MP_ROM_PTR(&picodisplay2_circle_obj) }, + { MP_ROM_QSTR(MP_QSTR_character), MP_ROM_PTR(&picodisplay2_character_obj) }, + { MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&picodisplay2_text_obj) }, + { MP_ROM_QSTR(MP_QSTR_BUTTON_A), MP_ROM_INT(BUTTON_A) }, + { MP_ROM_QSTR(MP_QSTR_BUTTON_B), MP_ROM_INT(BUTTON_B) }, + { MP_ROM_QSTR(MP_QSTR_BUTTON_X), MP_ROM_INT(BUTTON_X) }, + { MP_ROM_QSTR(MP_QSTR_BUTTON_Y), MP_ROM_INT(BUTTON_Y) }, +}; +STATIC MP_DEFINE_CONST_DICT(mp_module_picodisplay2_globals, picodisplay2_globals_table); + +/***** Module Definition *****/ +const mp_obj_module_t picodisplay2_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_picodisplay2_globals, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +MP_REGISTER_MODULE(MP_QSTR_picodisplay2, picodisplay2_user_cmodule, MODULE_PICO_DISPLAY_2_ENABLED); +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/micropython/modules/pico_display_2/pico_display_2.cpp b/micropython/modules/pico_display_2/pico_display_2.cpp new file mode 100644 index 00000000..6268c8a4 --- /dev/null +++ b/micropython/modules/pico_display_2/pico_display_2.cpp @@ -0,0 +1,341 @@ +#include "hardware/spi.h" +#include "hardware/sync.h" +#include "pico/binary_info.h" + +#include "libraries/pico_display_2/pico_display_2.hpp" + +using namespace pimoroni; + +PicoDisplay2 *display2 = nullptr; + + +extern "C" { +#include "pico_display_2.h" + +#define NOT_INITIALISED_MSG "Cannot call this function, as picodisplay2 is not initialised. Call picodisplay2.init() first." + +mp_obj_t picodisplay2_buf_obj; + +mp_obj_t picodisplay2_init(mp_obj_t buf_obj) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_obj, &bufinfo, MP_BUFFER_RW); + picodisplay2_buf_obj = buf_obj; + if(display2 == nullptr) + display2 = new PicoDisplay2((uint16_t *)bufinfo.buf); + display2->init(); + return mp_const_none; +} + +mp_obj_t picodisplay2_get_width() { + return mp_obj_new_int(PicoDisplay2::WIDTH); +} + +mp_obj_t picodisplay2_get_height() { + return mp_obj_new_int(PicoDisplay2::HEIGHT); +} + +mp_obj_t picodisplay2_update() { + if(display2 != nullptr) + display2->update(); + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_flip() { + if(display2 != nullptr) + display2->flip(); + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_set_backlight(mp_obj_t brightness_obj) { + if(display2 != nullptr) { + float brightness = mp_obj_get_float(brightness_obj); + + if(brightness < 0 || brightness > 1.0f) + mp_raise_ValueError("brightness out of range. Expected 0.0 to 1.0"); + else + display2->set_backlight((uint8_t)(brightness * 255.0f)); + } + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_set_led(mp_obj_t r_obj, mp_obj_t g_obj, mp_obj_t b_obj) { + if(display2 != nullptr) { + int r = mp_obj_get_int(r_obj); + int g = mp_obj_get_int(g_obj); + int b = mp_obj_get_int(b_obj); + + if(r < 0 || r > 255) + mp_raise_ValueError("r out of range. Expected 0 to 255"); + else if(g < 0 || g > 255) + mp_raise_ValueError("g out of range. Expected 0 to 255"); + else if(b < 0 || b > 255) + mp_raise_ValueError("b out of range. Expected 0 to 255"); + else + display2->set_led(r, g, b); + } + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_is_pressed(mp_obj_t button_obj) { + bool buttonPressed = false; + + if(display2 != nullptr) { + int buttonID = mp_obj_get_int(button_obj); + switch(buttonID) { + case 0: + buttonPressed = display2->is_pressed(PicoDisplay2::A); + break; + + case 1: + buttonPressed = display2->is_pressed(PicoDisplay2::B); + break; + + case 2: + buttonPressed = display2->is_pressed(PicoDisplay2::X); + break; + + case 3: + buttonPressed = display2->is_pressed(PicoDisplay2::Y); + break; + + default: + mp_raise_ValueError("button not valid. Expected 0 to 3"); + break; + } + } + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return buttonPressed ? mp_const_true : mp_const_false; +} + +mp_obj_t picodisplay2_set_pen(mp_uint_t n_args, const mp_obj_t *args) { + if(display2 != nullptr) { + switch(n_args) { + case 1: { + int p = mp_obj_get_int(args[0]); + + if(p < 0 || p > 0xffff) + mp_raise_ValueError("p is not a valid pen."); + else + display2->set_pen(p); + } break; + + case 3: { + int r = mp_obj_get_int(args[0]); + int g = mp_obj_get_int(args[1]); + int b = mp_obj_get_int(args[2]); + + if(r < 0 || r > 255) + mp_raise_ValueError("r out of range. Expected 0 to 255"); + else if(g < 0 || g > 255) + mp_raise_ValueError("g out of range. Expected 0 to 255"); + else if(b < 0 || b > 255) + mp_raise_ValueError("b out of range. Expected 0 to 255"); + else + display2->set_pen(r, g, b); + } break; + + default: { + char *buffer; + buffer = (char*)malloc(100); + sprintf(buffer, "function takes 1 or 3 (r,g,b) positional arguments but %d were given", n_args); + mp_raise_TypeError(buffer); + } break; + } + } + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_create_pen(mp_obj_t r_obj, mp_obj_t g_obj, mp_obj_t b_obj) { + int pen = 0; + + if(display2 != nullptr) { + int r = mp_obj_get_int(r_obj); + int g = mp_obj_get_int(g_obj); + int b = mp_obj_get_int(b_obj); + + if(r < 0 || r > 255) + mp_raise_ValueError("r out of range. Expected 0 to 255"); + else if(g < 0 || g > 255) + mp_raise_ValueError("g out of range. Expected 0 to 255"); + else if(b < 0 || b > 255) + mp_raise_ValueError("b out of range. Expected 0 to 255"); + else + pen = display2->create_pen(r, g, b); + } + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_obj_new_int(pen); +} + +mp_obj_t picodisplay2_set_clip(mp_uint_t n_args, const mp_obj_t *args) { + (void)n_args; //Unused input parameter, we know it's 4 + + if(display2 != nullptr) { + int x = mp_obj_get_int(args[0]); + int y = mp_obj_get_int(args[1]); + int w = mp_obj_get_int(args[2]); + int h = mp_obj_get_int(args[3]); + + Rect r(x, y, w, h); + display2->set_clip(r); + } + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_remove_clip() { + if(display2 != nullptr) + display2->remove_clip(); + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_clear() { + if(display2 != nullptr) + display2->clear(); + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_pixel(mp_obj_t x_obj, mp_obj_t y_obj) { + if(display2 != nullptr) { + int x = mp_obj_get_int(x_obj); + int y = mp_obj_get_int(y_obj); + + Point p(x, y); + display2->pixel(p); + } + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_pixel_span(mp_obj_t x_obj, mp_obj_t y_obj, mp_obj_t l_obj) { + if(display2 != nullptr) { + int x = mp_obj_get_int(x_obj); + int y = mp_obj_get_int(y_obj); + int l = mp_obj_get_int(l_obj); + + Point p(x, y); + display2->pixel_span(p, l); + } + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_rectangle(mp_uint_t n_args, const mp_obj_t *args) { + (void)n_args; //Unused input parameter, we know it's 4 + + if(display2 != nullptr) { + int x = mp_obj_get_int(args[0]); + int y = mp_obj_get_int(args[1]); + int w = mp_obj_get_int(args[2]); + int h = mp_obj_get_int(args[3]); + + Rect r(x, y, w, h); + display2->rectangle(r); + } + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_circle(mp_obj_t x_obj, mp_obj_t y_obj, mp_obj_t r_obj) { + if(display2 != nullptr) { + int x = mp_obj_get_int(x_obj); + int y = mp_obj_get_int(y_obj); + int r = mp_obj_get_int(r_obj); + + Point p(x, y); + display2->circle(p, r); + } + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} + +mp_obj_t picodisplay2_character(mp_uint_t n_args, const mp_obj_t *args) { + if(display2 != nullptr) { + int c = mp_obj_get_int(args[0]); + int x = mp_obj_get_int(args[1]); + int y = mp_obj_get_int(args[2]); + + Point p(x, y); + if(n_args == 4) { + int scale = mp_obj_get_int(args[3]); + display2->character((char)c, p, scale); + } + else + display2->character((char)c, p); + } + + return mp_const_none; +} + +mp_obj_t picodisplay2_text(mp_uint_t n_args, const mp_obj_t *args) { + if(display2 != nullptr) { + if(mp_obj_is_str_or_bytes(args[0])) { + GET_STR_DATA_LEN(args[0], str, str_len); + + std::string t((const char*)str); + + int x = mp_obj_get_int(args[1]); + int y = mp_obj_get_int(args[2]); + int wrap = mp_obj_get_int(args[3]); + + Point p(x, y); + if(n_args == 5) { + int scale = mp_obj_get_int(args[4]); + display2->text(t, p, wrap, scale); + } + else + display2->text(t, p, wrap); + } + else if(mp_obj_is_float(args[0])) { + mp_raise_TypeError("can't convert 'float' object to str implicitly"); + } + else if(mp_obj_is_int(args[0])) { + mp_raise_TypeError("can't convert 'int' object to str implicitly"); + } + else if(mp_obj_is_bool(args[0])) { + mp_raise_TypeError("can't convert 'bool' object to str implicitly"); + } + else { + mp_raise_TypeError("can't convert object to str implicitly"); + } + } + else + mp_raise_msg(&mp_type_RuntimeError, NOT_INITIALISED_MSG); + + return mp_const_none; +} +} diff --git a/micropython/modules/pico_display_2/pico_display_2.h b/micropython/modules/pico_display_2/pico_display_2.h new file mode 100644 index 00000000..bccd69dd --- /dev/null +++ b/micropython/modules/pico_display_2/pico_display_2.h @@ -0,0 +1,26 @@ +// Include MicroPython API. +#include "py/runtime.h" +#include "py/objstr.h" + +// Declare the functions we'll make available in Python +extern mp_obj_t picodisplay2_init(mp_obj_t buf_obj); +extern mp_obj_t picodisplay2_get_width(); +extern mp_obj_t picodisplay2_get_height(); +extern mp_obj_t picodisplay2_set_backlight(mp_obj_t brightness_obj); +extern mp_obj_t picodisplay2_update(); +extern mp_obj_t picodisplay2_set_led(mp_obj_t r_obj, mp_obj_t g_obj, mp_obj_t b_obj); +extern mp_obj_t picodisplay2_is_pressed(mp_obj_t button_obj); +extern mp_obj_t picodisplay2_flip(); + +// From PicoGraphics parent class +extern mp_obj_t picodisplay2_set_pen(mp_uint_t n_args, const mp_obj_t *args); +extern mp_obj_t picodisplay2_create_pen(mp_obj_t r_obj, mp_obj_t g_obj, mp_obj_t b_obj); +extern mp_obj_t picodisplay2_set_clip(mp_uint_t n_args, const mp_obj_t *args); +extern mp_obj_t picodisplay2_remove_clip(); +extern mp_obj_t picodisplay2_clear(); +extern mp_obj_t picodisplay2_pixel(mp_obj_t x_obj, mp_obj_t y_obj); +extern mp_obj_t picodisplay2_pixel_span(mp_obj_t x_obj, mp_obj_t y_obj, mp_obj_t l_obj); +extern mp_obj_t picodisplay2_rectangle(mp_uint_t n_args, const mp_obj_t *args); +extern mp_obj_t picodisplay2_circle(mp_obj_t x_obj, mp_obj_t y_obj, mp_obj_t r_obj); +extern mp_obj_t picodisplay2_character(mp_uint_t n_args, const mp_obj_t *args); +extern mp_obj_t picodisplay2_text(mp_uint_t n_args, const mp_obj_t *args);