Controlling the display (Digital Systems)

Copyright © 2024 J. M. Spivey
Jump to navigation Jump to search

One of the attractions of the micro:bit for exploring low-level programming is that it has the 25-LED display, but that controlling this display is not quite trivial, giving a series of non-trivial but nevertheless manageable challenges. Our early attempts to program the display will depend on controlling the hardware directly. However, once we start to use the display as a tool for investigating other aspects of the hardware (for example, when we use it to implement a 2-D spirit level based on the accelerometer), it's good if client code can be written in a way that is independent of the hardware. The micro:bian operating system provides a server process that provides just this abstraction.

Static images

The IMAGE macro can be used to create a static image.

static const image heart =
    IMAGE(0,1,0,1,0,
          1,1,1,1,1,
          1,1,1,1,1,
          0,1,1,1,0,
          0,0,1,0,0);

That call of the IMAGE macro expands and simplifies (on V1) to something equivalent to the text

{ 0x28f0, 0x5e00, 0x8060 }

The expansion and simplification process relies both on the macro expansion provided by the C preprocessor, and the fact that the C compiler itself can take complex expressions involving shifts and bitwise-or operations and reduce them to simple constants, suitable for defining a read-only array like heart, whose type is given by the typedef,

#define NIMG 3

typedef unsigned image[NIMG];

This expansion of the macro into an array initialiser means that the macro is suitable for use only in declaring a named image constant, as shown above. It can't be used, for example, on the right hand side of an assignment statement. The same effect can be obtained with an explicit call of memcpy(): first define a constant array for the image, then write something like

image myimage;

memcpy(myimage, heart, sizeof(image));

As well as the image type and the IMAGE macro, the header file hardware.h defines three other statement-like macros that can be ised in simple programs that want to flash a single LED for timing or debugging. The macro

led_setup()

sets up the GPIO port to dirve the LEDs; after calling it, you can use

led_dot()

to light up the central LED, and

led_off()

to turn all the LEDs off.

Computing images dynamically

Programs that do more than show a canned animation need to compute images dynamically, and this is made complicated by the fact that the LEDs are wired in a convoluted way on the board. Programs running under micro:bian can use the device-independent interface described in the next section. Other programs could adopt similar techniques and borrow code from the file display.c of micro:bian. It's sensible to provide an interface that converts from ordinary coordinates to the representation needed by the display, allowing pixels to be set or cleared by coordinate.

We can make a table giving, for each coordinate pair, the row and column in the display matrix.

/* img_map -- map of logical row and column bits for each physical pixel */
static unsigned img_map[5][5] = {
    { PIX(3,3), PIX(2,7), PIX(3,1), PIX(2,6), PIX(3,2) },
    { PIX(1,8), PIX(1,7), PIX(1,6), PIX(1,5), PIX(1,4) },
    { PIX(2,2), PIX(1,9), PIX(2,3), PIX(3,9), PIX(2,1) },
    { PIX(3,4), PIX(3,5), PIX(3,6), PIX(3,7), PIX(3,8) },
    { PIX(1,1), PIX(2,4), PIX(1,2), PIX(2,5), PIX(1,3) }
};

For compactness, each element of this table is stored as a single word, assembled by shifting and or-ing, and

#define PAIR(x, y) (((x) << 5) | (y))
#define XPART(p) ((p) >> 5)
#define YPART(p) ((p) & 0x1f)

#ifdef UBIT_V1
#define PIX(i, j)  PAIR(i-1, j+3)

The micro:bian display server