diff --git a/README.md b/README.md index 2a3808e..79d8e8c 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,43 @@ -# Pimoroni PGA Boilerplate +# Explorer -This repository is intended to provide a baseline MicroPython build for PGA2040 -and PGA2350, in addition to being a minimal example of how you might set up your -own custom MicroPython flavour to support your PGA-based project. +This repository is home to the Explorer MicroPython firmware and examples. -Use this repository as a boilerplate to set up your own project, and GitHub actions -should automatically handle building MicroPython for you. +Explorer is a MicroPython-powered electronic adventure playground for physical computing, built around the RP2350B. -## Contents +Grab yours at https://shop.pimoroni.com/products/explorer -* pga2040 - MicroPython and Pico SDK board definitions for PGA2040 -* pga2350 - MicroPython and Pico SDK board definitions for PGA2350, with PSRAM variant -* modules/c/example - An example MicroPython C++ module, demonstrating C class bindings -* modules/py_frozen - Python files intended to be frozen into the firmware -* modules/py_littlefs - Python files intended to be visible/editable in the LittleFS user filesystem -* modules/default.py - The MicroPython manifest file, for specifying frozen libs -* modules/default.txt - The dir2uf2 LittleFS manifest file, for specifying included files -* modules/default.cmake - The MicroPython USER_C_MODULES file, for specifying included C/C++ modules \ No newline at end of file +- [Introduction](#introduction) +- [Download MicroPython for Explorer](#download-micropython-for-explorer) +- [Flashing The Firmware](#flashing-the-firmware) +- [Examples](#examples) +- [Documentation](#documentation) + +# Introduction + +Explorer comes pre-flashed with MicroPython, our own custom drivers/libraries and a range of examples to get you started. MicroPython support for the RP2350B is currently a work-in-progress, so you should be prepared to update as we make fixes and improvements! + +# Download MicroPython for Explorer + +To upgrade, grab the latest release from https://github.com/pimoroni/explorer/releases/latest + +There are two choices of firmware: + +* explorer-vX.X.X-micropython-with-filesystem.uf2 :warning: (recommended) - A full update package including examples and the explorer library +* explorer-vX.X.X-micropython.uf2 - a firmware-only update, that will leave your filesystem alone! + +:warning: If you flash the `with-filesystem` version, the contents of your Explorer board will be erased- so make sure to back up your own code first. Alternatively you can flash the firmware-only build and manually copy the files in [examples/lib](examples/lib) + +# Flashing The Firmware + +1. Connect Explorer to your computer with a USB Type-C cable +2. Put your Explorer into bootloader mode by holding down "BOOT", the second button from the left when holding Explorer with the screen facing away from you. Keep holding "BOOT" and press "RESET", the button next to "BOOT". +3. Drag and drop your chosen `.uf2` file onto the `RP2350` drive that appears. +4. Your board should reset and, if you used the `with-filesystem build, should display a menu of examples. + +# Examples + +We've tried to cover all the bases with some simple examples to get you started. See [examples/README.md](examples/README.md) for more information. + +# Documentation + +To help you get started we've created a function reference. See [docs/reference.md](docs/reference.md) \ No newline at end of file diff --git a/docs/reference.md b/docs/reference.md index 996763d..4c43b4b 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -2,39 +2,79 @@ This is the library reference for the [Pimoroni Explorer](https://shop.pimoroni.com/products/explorer), an electronics adventure playground, powered by the Raspberry Pi RP2350. +It's split into three main sections for your convenience: -## Table of Content -- [Getting Started](#getting-started) -- [Reading the Switches](#reading-the-switches) -- [`explorer` Reference](#explorer-reference) - - [Index Constants](#index-constants) - - [Count Constants](#count-constants) - - [Colour Constants](#colour-constants) - - [Pin Constants](#pin-constants) - - [Audio Constants](#audio-constants) - - [Variables](#variables) - - [Functions](#functions) +- [Explorer Library](#explorer-library) + - [Getting Started](#getting-started) + - [Reading the Switches](#reading-the-switches) + - [`explorer` Reference](#explorer-reference) + - [Index Constants](#index-constants) + - [Count Constants](#count-constants) + - [Colour Constants](#colour-constants) + - [Pin Constants](#pin-constants) + - [Audio Constants](#audio-constants) + - [Variables](#variables) + - [Functions](#functions) +- [PicoGraphics](#picographics) + - [Basic Drawing Functions](#basic-drawing-functions) + - [Changing The Display Mode](#changing-the-display-mode) + - [JPEG Decoding](#jpeg-decoding) + - [PNG Decoding](#png-decoding) +- [PicoVector](#picovector) + - [Setup \& Anti-aliasing](#setup--anti-aliasing) + - [Shapes \& Primitives](#shapes--primitives) + - [Fonts \& Text](#fonts--text) + - [Transforms](#transforms) +# Explorer Library + +The [explorer library](examples/lib/explorer.py) is a wrapper around some of Explorer's particulars. It aims to get you set up with a PicoGraphics surface for drawing, and help you find the right pins for the many inputs and outputs. ## Getting Started -To start coding your Pimoroni Explorer, you will need to add the following line to the start of your code file. +To start coding your Pimoroni Explorer, you will need to add the following line to the start of your code file: ```python import explorer ``` -... +Alternatively you can import only what you plan to use in your code like so: +```python +from explorer import display, i2c, button_a +``` ## Reading the Switches -... +Import the initialized Pin objects from the explorer library. + +```python +from explorer import button_a, button_b, button_c, button_x, button_y, button_z, button_user +``` +You can read the value of the a button using the Pin function `.value()` The buttons are Active Low. So you'll read `0` when the button is being pressed and `1` when it isn't! + +The example below prints out the string `Pressed!` when it detects that Button A has been pressed + +```python +from explorer import button_a +import time + +while True: + + if button_a.value() == 0: + print("Pressed!") + else: + print("Not Pressed") + + time.sleep(1) + +``` ## `explorer` Reference ### Index Constants + ```python SERVO_1 = 0 SERVO_2 = 1 @@ -43,6 +83,9 @@ SERVO_4 = 3 ``` ### Count Constants + +These handy constants tell you how many of something Explorer has. + ```python NUM_GPIOS = 6 NUM_ADCS = 6 @@ -51,18 +94,54 @@ NUM_SWITCHES = 6 ``` ### Colour Constants + +These are RGB565 colours for Explorer's default PicoGraphics instance. + ```python -WHITE = 65535 -BLACK = 0 -CYAN = 65287 -MAGENTA = 8184 -YELLOW = 57599 -GREEN = 57351 -RED = 248 -BLUE = 7936 +WHITE = 65535 # 255, 255, 255 +BLACK = 0 # 0, 0, 0 +CYAN = 65287 # 0, 255, 255 +MAGENTA = 8184 # 255, 0, 255 +YELLOW = 57599 # 255, 255, 0 +GREEN = 57351 # 0, 255, 0 +RED = 248 # 255, 0, 0 +BLUE = 7936 # 0, 0, 255 +``` + +If you're wondering how these baffling numbers are arrived at, we start with three 8-bit values for Red, Green and Blue: + +``` +red = RRRRRRRR +green = GGGGGGGG +blue = BBBBBBBB +``` + +Then we chop them down to 5, 6 and 5 bits respectively: + +``` +red = RRRRR +green = GGGGGG +blue = BBBBBB +``` + +And stick them together: + ``` +rgb565 = RRRRRGGGGGGBBBBB +``` + +And byteswap them (swap the lower 8 bits with the upper 8 bits, for reasons): + +``` +rgb565 = GGGBBBBBRRRRRGGG +``` + +And that's why 255 red, or `0b0000000011111000` equals 248. ### Pin Constants + +Constants for all the pins you might need to access on Explorer: + ```python SWITCH_A_PIN = 16 SWITCH_B_PIN = 15 @@ -99,12 +178,23 @@ AMP_EN_PIN = 13 ``` ### Audio Constants + ```python AMP_CORRECTION = 4 DEFAULT_VOLUME = 0.2 ``` ### Variables + +Defines a few values that you'll probably use most: + +* `i2c` - A `machine.I2C` compatible I2C instance for I2C devices connected to the Qw/St socket +* `audio_pwm` - A `machine.PWM` instance to drive the piezo for beeps and boops +* `servos` - A list containing four `Servo` instances for driving the four servo connectors +* `display` - A PicoGraphics instance, configured in RGB565 pen mode with two drawing layers +* `button_` - A collection of six `machine.Pin` instances for reading the onboard button +* `button_user` - A `machine.Pin` instance for reading the user / boot button + ```python i2c: PimoroniI2C audio_pwm: PWM @@ -123,10 +213,124 @@ servos: list[Servo] ``` ### Functions + ```python play_tone(frequency: float) -> None play_silence() -> None stop_playing() -> None set_volume(volume: float=None) -> None mute_audio(value: bool=True) -> None -``` \ No newline at end of file +``` + +# PicoGraphics + +Explorer, like all of our display products, uses our in-house framebuffer graphics library - PicoGraphics. + +PicoGraphics is a wrapper around a big ol' chunk of RAM, which corresponds to the pixels on an attached display. By default, Explorer is configured in RGB565 mode which corresponds to two bytes per pixel, or 5 bits of red, 6 bits of green and 5 bits of blue respectively. It's also known as 65k colour, and does a pretty good job of making pretty pictures at half (rather than 2/3rds) the RAM cost (for awkward technical reasons) of RGB888. + +## Basic Drawing Functions + +Since there are many, many things you can do with PicoGraphics that would be silly to repeat here, I'll send you over to our main MicroPython repository for [a full PicoGraphics function reference.][https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/modules/picographics/README.md#function-reference] + +## Changing The Display Mode + +You can replace `explorer.display` by just creating a PicoGraphics instance as normal, for example switching into RGB332 mode (1 byte per pixel) to save RAM can be done by just replacing `explorer.display`: + +```python +from picographics import PicoGraphics, DISPLAY_EXPLORER, PEN_RGB332 +explorer.display = PicoGraphics(display=DISPLAY_EXPLORER, pen_type=PEN_RGB332, layers=2) +``` + +We use two layers by default, since that extra layer is handy for static backgrounds and other fun drawing tricks. + +## JPEG Decoding + +Sometimes it's easy just to grab a JPEG file from somewhere, squash it down and display it on your screen. You can rely on JPEGs lossy compression to help fit more images on your flash storage. + +You can find documentation for the JPEG decoder in our [PicoGraphics reference](https://github.com/pimoroni/pimoroni-pico/tree/main/micropython/modules/picographics#jpeg-files). + +## PNG Decoding + +More often than not you want your image to look exactly as you intended it, without ugly compression artifacts and distortion. This is particularly useful for icons and interface elements- for which you should use the PNG format and aim for a palette mode image with as few colours as you can represent your artwork in. + +You can find documentation for PNG decoder in our [PicoGraphics reference]( +https://github.com/pimoroni/pimoroni-pico/tree/main/micropython/modules/picographics#png-files). + +# PicoVector + +Explorer includes our new and improved PicoVector library, bringing vector graphics and text along with it. + +## Setup & Anti-aliasing + +The first step is to create a PicoVector instance: + +```python +from picovector import PicoVector + +vector = PicoVector(explorer.display) +``` + +You should then pick an anti-aliasing method. Anti-aliasing is the technique that turns harsh, pixellated edges into a smooth transition between elements. You probably look at it all day without realising, and on Explorer there's a very real tradeoff between speed and anti-aliasing quality. You can pick one of: + +* `ANTIALIAS_NONE` - Turn off anti-aliasing altogether +* `ANTIALIAS_FAST` - A nice balance between none, and best +* `ANTIALIAS_BEST` - High quality x16 anti-aliasing + +And set it with: + +```python +from picovector import PicoVector, ANTIALIAS_BEST + +vector = PicoVector(explorer.display) +vector.set_antialiasing(ANTIALIAS_BEST) +``` + +## Shapes & Primitives + +PicoVector is built around polygons, which is really a collection of arbitrary paths that describe a shape. You create a shape by adding predefined primitives to you "Polygon", these are: + +* `rectangle(x, y, w, h [, (corners), stroke])` - A rectangle, with a optional tuple of four corner radii or a stroke width to convert it into an outline +* `regular(x, y, radius, sides [, stroke])` - A regular polygon. Starts as a triangle and converges to a circle. Providing a stroke with makes it an outline. +* `path((x, y), (x, y), (x, y), ...)` - A closed path consisting of at least three points (two would be an invisible line!) +* `circle(x, y, radius [, stroke])` - A circle, with optional stroke to make it an outline +* `arc(x, y, radius, from, to [, stroke])` - A circular arc between the angles from and to + +## Fonts & Text + +* `set_font(filename)` - Load an `.af` font from flash. +* `set_font_size(size)` - What units are we using here? TODO: Maybe we need to normalise this somehow! +* `set_font_word_spacing(spacing)` - The worst named function ever! Sets the space between words. +* `set_font_letter_spacing(spacing)` - Sets the space between letters. +* `set_font_line_height(spacing)` - Sets the text line-height. +* `set_font_align(spacing)` - Sets the text alignment, for reasons currently only *horizontal* alignment works. Remind me to fix this! + +## Transforms + +Every time you draw something with PicoVector, it's affected by the `transform` you've set. This is how you rotate, scale and translate your shapes to position them on the screen. + +* `transform = Transform()` - Create a new transform +* `transform.rotate(angle degrees, (origin_x, origin_y))` +* `transform.scale(scale_x, scale_y)` +* `transform.translate(translate_x, translate_y)` +* `transform.reset()` + +To create a new transform you should create it, apply it to your vector instance and add some transformations: + +```python +from picovector import PicoVector, Transform + +vector = PicoVector(explorer.display) +transform = Transform() +vector.set_transform(transform) + +transform.rotate(90, (0, 0)) +transform.scale(10) +``` + +For a clean slate, you can reset your transform back to its original state: + +```python +transform.reset() +``` + +Transformations can be a little confusing \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..e064920 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,85 @@ +# Examples + +# balls_demo.py + +Our timeless silly bouncing balls demo, shows off drawing in PicoGraphics. + +# button_test.py + +Want to test your buttons? Look no further! + +# clock.py + +We'd be remiss without an example to flex our new PicoVector and PicoGraphics improvements. If you're not using your Explorer for anything else, then here's a handy Analog or Digital desk clock for you. Connect to your computer and use Thonny to automagically set the time. + +# cubes.py + +Seen those spinny wire mesh cube demos and wondered "how do they do that?" well you can continue to wonder, because this code makes me cry. + +# display_jpg.py + +As the name suggests, this example will display a JPEG. Remember, it's "f" as in "Photographic" so technically you should pronounce it "jfeg."" + +# display_png.py + +Bored of jfegs? Display a PNG instead... no, not the sound, but an image. PNG's are great because they can be *paletted* and give you super crisp details for low colour applications. Think icons, icons and, uh, icons! + +# double_tap.py / double_tap_async.py + +A delightful example for the LSM6DS3 accelerometer on our [multi sensor stick](https://shop.pimoroni.com/products/multi-sensor-stick). Knock on your desk, go on! + +The async version uses `asyncio.create_task` to poll for a tap event. Keeps your main loop all nice and clean. + +# explorer_sensor_stick_demo.py + +Another example for our [multi sensor stick](https://shop.pimoroni.com/products/multi-sensor-stick). Uh, sorry. I guess this is a bit of an upsell. But it's three pretty cool sensors in one... and not a bag of virtual gems! + +Displays light, accelerometer and temperature data! + +# external_buttons.py + +Gotta put something on that little breadboard, right! How about more buttons. You can never have enough buttons. The ones they keep taking out of cars must go somewhere!? + +# image_gallery.py + +At a loss for what to do with your Explorer. Clock just not your cup of tea? How about a digital photo frame! Those are still cool, right? + +# marker.py + +If you haven't Google'd "permanent marker font" yet, you really should. It's about the only worthwhile thing Google.com still does. + +# maze.py + +It's a-maze-ing! Use the C, Z, A and B buttons to navigate. + +# multiple_servos.py + +You can't do much with one servo, so how about multiple? You could be well on your way to being the next terrifyingly lifelike robot engineer! + +# potentiometer.py + +While it sounds like something for detecting ghosts or 1920s Soviet silent movies, a potentiometer is actually just a resistor, in a circle, with a knob to change its resistance. This one weird trick gives you a rotary control for your projects... use it to wiggle a servo! + +# rainbow.py + +Cue [appropriate musical accompaniment](https://remix.kwed.org/files/RKOfiles/Lagerfeldt%20-%20Flip%20the%20Flop%20(Rock%20My%20Commodore).mp3) before launching. + +# shake.py + +Another example for our [multi sensor stick](https://shop.pimoroni.com/products/multi-sensor-stick). A truly classic falling sand demo. [Shake it off. Shake it off.](https://www.youtube.com/watch?v=jRMHp7_kPec) + +# simple_menu.py + +How to menu? How to menu! + +# single_servo.py + +Nobody wants one servo... though I guess this does demonstrate all the cool features Chris packed into our servo library. Give it a go! + +# step_counter.py + +Another example for our [multi sensor stick](https://shop.pimoroni.com/products/multi-sensor-stick). Turns out the LSM6DS3 has turnkey step counting built right in... I guess we know how all those sports trackers work now! + +# tone_song.py + +By the grace of alphabetical ordering this wonderful musical example has come last. I think parents will thank us for this. \ No newline at end of file diff --git a/examples/marker.af b/examples/marker.af new file mode 100644 index 0000000..b3a5364 Binary files /dev/null and b/examples/marker.af differ diff --git a/examples/marker.py b/examples/marker.py new file mode 100644 index 0000000..e9a6311 --- /dev/null +++ b/examples/marker.py @@ -0,0 +1,50 @@ +import time + +import explorer +from picovector import ANTIALIAS_NONE, HALIGN_CENTER, PicoVector + +vector = PicoVector(explorer.display) + +FONT_SIZE = 70 + +vector.set_antialiasing(ANTIALIAS_NONE) +vector.set_font("marker.af", FONT_SIZE) +vector.set_font_size(FONT_SIZE) +vector.set_font_line_height(70) +vector.set_font_align(HALIGN_CENTER) + +BG = explorer.display.create_pen(0, 150, 190) +FG = explorer.display.create_pen(0, 100, 126) +BLACK = explorer.BLACK + + +text = """Permanent +marker""" + +x, y, w, h = vector.measure_text(text) + +x = int((320 - w) / 2) +y = int((240 - h) / 2) + (FONT_SIZE // 2) + +explorer.display.set_layer(1) +explorer.display.set_pen(BG) +explorer.display.clear() + +explorer.display.set_pen(FG) +vector.text(text, x, y + 4) +explorer.display.set_pen(BLACK) +vector.text(text, x, y) + + +explorer.display.set_layer(0) + +GRADIENT_WIDTH = 320 * 4 + +rgb = [explorer.display.create_pen_hsv(x / GRADIENT_WIDTH, 1.0, 1.0) for x in range(GRADIENT_WIDTH)] + +while True: + o = time.ticks_ms() // 10 + for x in range(320): + explorer.display.set_pen(rgb[(x + o) % GRADIENT_WIDTH]) + explorer.display.line(x, 0, x, 240) + explorer.display.update()