Introduction#
Tempe is a pure-Micropython graphics system designed to be make using the full capabilities of display devices more accessible, particularly on memory-constrained microcontrollers. The aim is to allow data scientists, user interface designers and data visualization specialists to be able to create beautiful, responsive displays without needing to worry about the mechanics of rendering to bytes on the screen device, handling partial screen updates, and so on.
The Problem Tempe Solves#
There are many high-quality, inexpensive LCD and OLED displays that are available and capable of being driven by modern Micropython-based microcontrollers: typical devices are as large as 320x240 (QVGA) and capable of 16-bit or 18-bit color and use SPI or I2C to communicate meaning that many pins are available for other uses.
Programs which want to use these displays need to render their graphics
into a framebuffer and then transmit the raw bytes to the display’s internal
memory. Micropython provides a reasonably high-level framebuf
module
that allows code to draw to a framebuffer. However a framebuffer for one of
these screens at 16-bit color depth requires over 153K, where the
microcontroller might have a total of 256K available.
A typical data visualization application is going to need additional memory for the data being displayed, bitmap fonts, icons, and for the program itself, and it is very easy to run out of memory.
Additionally, dynamic applications where the information being displayed is changing need to redraw the buffer on every update, which for complex displays can be challenging to do in a responsive way.
One way to handle this situation is to simply reduce the color-depth, for example using an 8-bit or 4-bit color palette with a corresponding reduction in the buffer size, but with a corresponding loss of color fidelity.
However there are also standard ways to handle these issues while still keeping
full 16-bit color, such as tracking which regions of the screen have changed
as the display updates, and rendering to subregions of the screen as needed
using smaller in-memory buffers. However this significantly complicates the
drawing code if using the standard framebuf
module: what is being
drawn needs to be translated into the relative coordinates of the smaller
buffer and, ideally, objects that are outside of the region being updated
shouldn’t attempt to draw themselves at all.
At its core, Tempe solves this problem: it provides a high-level drawing API that lets users concentrate on what they want to draw and not how those drawing commands are translated to bytes in a framebuffer or transmitted to the screen. It handles partial re-draws of the display with automatic clipping of shapes which are outside of the drawing area, and where memory is constrained it can automatically break the updates into multiple smaller updates that fit into the available memory.
Additionally, the design of the API permits more efficient memory use when specifying the geometry and aesthetics of the graphical objects being drawn.
It does this using Micropython only, meaning users do not need to cross-compile
C code or create a custom firmware: it can be installed using standard mechanisms
lime mip
by users who are comfortable working with Micropython.
What Tempe Isn’t#
Tempe is built on top of the standard Micropython framebuffer module, so
it can’t do sub-pixel rendering, alpha compositing/variable opacity or
full 24/32-bit color (or even 18-bit color), and has a number of other
limitations. Some experimentation shows that it may be practical to
extend the framebuffer module using micropython.viper
to
provide some of these capabilities, but that is not currently on the
roadmap.
Tempe doesn’t provide much for devices which are not memory constrained. If your available memory is in the 100s of megabytes, you are running on Linux, and you are driving an HDMI display, then you are likely better-off with regular Python and one of its drawing libraries such as Cairo, Agg (via something like Celiagg), Qt’s QPainter, PyOpenGL or even the basic drawing capabilities of Pillow. Most of these systems are capable of sub-pixel rendering and full alpha compositing.
Tempe is primarily concerned with drawing graphics and text, and is not a complete UI or data visualization system. As experience with the system grows, we expect that additional libraries built on top of Tempe will grow to address more complete use cases. However Tempe does include the basic hooks to interact with an asyncio-based application to automatically update a display when things change, and interfaces particularly nicely with the Ultimo framework.
For user interfaces, Tempe is a rendering layer that could be used as part of the “View” component of a Model-View-Controller-style system. Tempe can draw shapes and text to display a user interface, but it has no concept of things like text fields, buttons and sliders.
For data visualization, Tempe can be thought of as providing the geometry and aesthetics in a Grammar of Graphics-style system. Tempe assumes that data has already had any statistical transformations applied, and has been mapped to screen values.
Why “Tempe”#
Tempe follows the naming style of Ultimo taking its name from a suburb of my home town of Sydney. The name “Tempe” itself comes from the Vale of Tempe at the foot of Mt. Olympus in Greece, known for its dramatic landscape.