Skip to content

Commit

Permalink
Use a common config file for examples. (#80)
Browse files Browse the repository at this point in the history
This shifts the `init_display` functions in the examples to a
`tempe_config` file that users can replace with something appropriate
for their hardware. It includes example config files for several types
of display.

This also changes the API of the `Display` protocol to require that it
have a `size` attribute than can be used to constrain rendering.

Fixes #68.  Fixes #81.
  • Loading branch information
corranwebster authored Dec 12, 2024
1 parent e62cba5 commit 84b77c0
Show file tree
Hide file tree
Showing 29 changed files with 795 additions and 381 deletions.
80 changes: 80 additions & 0 deletions docs/source/user_guide/examples.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
==============
Tempe Examples
==============

The Tempe repository contains some example code that demonstrates the concepts
involved in working with Tempe as a library. Most of the code was written to
work on Raspberry Pi Picos with Pimoroni 320x240 ST7789-based SPI displays.
Users have had success with other ST7789-based displays, and in principle any
display which allows blitting a 16-bit framebuffer into a windowed region of
memory should be able to work.

Support Modules
===============

Extra Fonts and Data Modules
----------------------------

Some examples use additional modules to provide fonts and data that are not installed
by the usual `mip` install. You will either need to install the directories
`example_fonts` and `data` on your device manually, or use the `ci.deploy_to_device`
command as discussed in Development Installation.

The `tempe_config` Module
-------------------------

To allow the examples to work with different displays, they expect the user to
have added a :py:mod:`tempe_config` module somewhere on the Python path (eg. at
the top-level directory of the flash storage device), containing an async
function :py:func:`init_display` that might look something like the following::

async init_display():
display = MyDisplay()
await display.init()
return display

There are some examples which show how to write such a file for:

- `Pimoroni ST7789-based SPI displays <https://github.com/unital/tempe/tree/main/examples/configs/tempe_config_pimoroni_spi.py>`_
- `Waveshare Pico ResTouch SPI displays <https://github.com/unital/tempe/tree/main/examples/configs/tempe_config_pico_res_touch.py>`_

If your device is not currently supported, you may need to write a Display subclass
in addition to the `init_display`. The following is an example of how to wrap a
3rd party driver for use with the examples:

- `GC9A01 screens using Robert Hughes' gc9a01_mpy firmware <https://github.com/unital/tempe/tree/main/examples/configs/tempe_config_gc9a01_mpy.py>`_

Ultimo
------

One example uses the Ultimo library. You can mip install this as described in the
Ultimo documentation.

Running the Examples
====================

Once installed the examples can be run in a number of ways.

Running using your IDE
----------------------

Most Micropython IDEs allow you to run scripts directly from the IDE.
This should work for all examples, although this has only been tested
with Thonny.

Running using mpremote
----------------------

Once the support modules are installed, you can run example files stored on your
computer's filesystem via `mpremote`. For example::

mpremote run examples/hello_world.py

Running from the Python REPL
----------------------------

If the example files have been installed on the Python path, you should be
able to run them by importing their `main` function and calling it.

>>> from hello_world import main
>>> main()
1 change: 1 addition & 0 deletions docs/source/user_guide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ Tempe User Guide

introduction.rst
installation.rst
examples.rst
tutorial.rst
displays.rst
10 changes: 0 additions & 10 deletions docs/source/user_guide/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,6 @@ Eg. for a Raspberry Pi Pico, you would do:

python -m ci.deploy_to_device -march armv6m

Running the Examples
--------------------

The example code works with a Raspberry Pi Pico and its internal hardware,
plus a ST7789-based display that communicates via SPI; in particular development
has been done against various Pimoroni screens (Pico Packs, Breakout Garden,
and Pico Explorer should work).

Most examples can be run from inside an IDE like Thonny.

Writing Code Using Tempe
-------------------------

Expand Down
34 changes: 34 additions & 0 deletions examples/configs/tempe_config_gc9a01_mpy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# SPDX-FileCopyrightText: 2024-present Unital Software <info@unital.dev>
#
# SPDX-License-Identifier: MIT

"""Example tempe_config file for GC9A01-based displays.

This example assumes you have the GC9A01 firmware from
https://github.com/russhughes/gc9a01_mpy installed on your
device and that you have a tft_config file as described in
the documentation: https://russhughes.github.io/gc9a01_mpy/examples.html
"""

from tft_config import config
from tempe.display import Display


# Change to match the characteristics of your display
ROTATION = 0


class GC9A01MpyDisplay(Display):
"""Display that wraps a gc9a01_mpy display."""

def __init__(self, rotation=0, buffer_size=0, options=0):
self.display = config(rotation, buffer_size, options)
self.size = (self.display.width(), self.display.height())

def blit(self, buffer, x, y, w, h):
self.display.blit_buffer(buffer, x, y, w, h)


async def init_display():
display = GC9A01MpyDisplay(ROTATION)
return display
20 changes: 20 additions & 0 deletions examples/configs/tempe_config_pico_res_touch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# SPDX-FileCopyrightText: 2024-present Unital Software <info@unital.dev>
#
# SPDX-License-Identifier: MIT

"""Example tempe_config file for Waveshare PicoResTouch display.
"""

from tempe_displays.st7789.waveshare import PicoResTouchDisplay


# Change to match the characteristics of your display
SIZE = (320, 240) # or (240, 320)
ROTATION = 0 # or 90, 180, 270


async def init_display():
display = PicoResTouchDisplay(size=SIZE)
await display.init(ROTATION)
display.backlight_pin(1)
return display
21 changes: 21 additions & 0 deletions examples/configs/tempe_config_pimoroni_spi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# SPDX-FileCopyrightText: 2024-present Unital Software <info@unital.dev>
#
# SPDX-License-Identifier: MIT

"""Example tempe_config file for Pimoroni SPI Displays.
"""

from tempe_displays.st7789.pimoroni import PimoroniDisplay


# Change to match the characteristics of your display
SIZE = (320, 240)
CENTERED = False # True for for round displays and the original Pico Display Pack
ROTATION = 0 # or 90, 180, 270


async def init_display():
display = PimoroniDisplay(size=SIZE, centered=CENTERED)
await display.init(ROTATION)
display.backlight_pin(1)
return display
46 changes: 39 additions & 7 deletions examples/hello_world.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@

"""Example showing basic display of text."""

import time
import asyncio
import gc

from tempe.surface import Surface
from tempe.text import Text
from tempe.shapes import Rectangles
from tempe.display import FileDisplay
from tempe.font import TempeFont
from tempe.fonts import ubuntu16bold


# a buffer one half the size of the screen
# maximize available memory before allocating buffer
gc.collect()

# A buffer one half the size of a 320x240 screen
# NOTE: If you get MemoryErrors, make this smaller
working_buffer = bytearray(2 * 320 * 121)


# create the surface
surface = Surface()

# fill the background with white pixels
Expand All @@ -29,10 +37,34 @@
)
surface.add_shape("DRAWING", hello_tempe)

# set up the display object
display = FileDisplay("hello_world.rgb565", (320, 240))

# refresh the display
with display:
display.clear()
def main(display=None):
"""Render the surface and return the display object."""
if display is None:
try:
from tempe_config import init_display

display = asyncio.run(init_display())
except ImportError:
print(
"Could not find tempe_config.init_display.\n\n"
"To run examples, you must create a top-level tempe_config module containing\n"
"an async init_display function that returns a display.\n\n"
"See https://unital.github.io/tempe more information.\n\n"
"Defaulting to file-based display.\n"
)
from tempe.display import FileDisplay

display = FileDisplay("hello_world.rgb565", (320, 240))
with display:
display.clear()
surface.refresh(display, working_buffer)

start = time.ticks_us()
surface.refresh(display, working_buffer)
print(time.ticks_diff(time.ticks_us(), start))
return display


if __name__ == '__main__':
display = main()
71 changes: 35 additions & 36 deletions examples/line_plot_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"""Example showing how to create a line plot from Tempe Shapes."""

from array import array
import asyncio
import gc
import time

from tempe import colors
from tempe.data_view import Repeat
Expand All @@ -14,12 +16,17 @@
from tempe.text import CENTER, TOP, RIGHT


surface = Surface()
# maximize available memory before allocating buffer
gc.collect()

# a buffer one half the size of the screen
# A buffer one half the size of a 320x240 screen
# NOTE: If you get MemoryErrors, make this smaller
working_buffer = bytearray(2 * 320 * 121)


# create the surface
surface = Surface()

# fill the background with off-white pixels
surface.rectangles("BACKGROUND", (0, 0, 320, 240), colors.grey_f)

Expand Down Expand Up @@ -47,10 +54,11 @@ def scale_values(self, data):
return screen


# maximize available memory before allocating data buffers
gc.collect()
from data.environmental import timestamps, temperature

# Plot screen bounds
# Plot bounds in screen space
x = 24
w = 288
x1 = x + w
Expand Down Expand Up @@ -160,43 +168,34 @@ def scale_values(self, data):
)


async def init_display():
from tempe_displays.st7789.pimoroni import PimoroniDisplay as Display
# or for Waveshare Pico-ResTouch-LCD-28:
# from tempe_displays.st7789.waveshare import PicoResTouchDisplay as Display

display = Display(size=(240, 320))
display.backlight_pin(1)
await display.init()
return display


def main(surface, working_buffer):
import asyncio

# set up the display object
display = asyncio.run(init_display())

# refresh the display
display.clear()
import time
def main(display=None):
"""Render the surface and return the display object."""
if display is None:
try:
from tempe_config import init_display

display = asyncio.run(init_display())
except ImportError:
print(
"Could not find tempe_config.init_display.\n\n"
"To run examples, you must create a top-level tempe_config module containing\n"
"an async init_display function that returns a display.\n\n"
"See https://unital.github.io/tempe more information.\n\n"
"Defaulting to file-based display.\n"
)
from tempe.display import FileDisplay

display = FileDisplay("line_plot.rgb565", (320, 240))
with display:
display.clear()
surface.refresh(display, working_buffer)

start = time.ticks_us()
surface.refresh(display, working_buffer)
print(time.ticks_diff(time.ticks_us(), start))
return display


if __name__ == "__main__":

# if we have an actual screen, use it
main(surface, working_buffer)

elif __name__ != "__test__":
from tempe.display import FileDisplay
if __name__ == '__main__':
display = main()

# set up the display object
display = FileDisplay("line_plot.rgb565", (320, 240))
# refresh the display
with display:
display.clear()
surface.refresh(display, working_buffer)
Loading

0 comments on commit 84b77c0

Please sign in to comment.