Controlling the display: Difference between revisions
No edit summary |
|||
Line 1: | Line 1: | ||
One of the attractions of the {{microbit}} 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. | One of the attractions of the {{microbit}} 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 {{microbian}} operating system provides a server process that provides just this abstraction. | ||
==Static images== | ==Static images== | ||
The @IMAGE@ macro can be used to create a static image. | The @IMAGE@ macro can be used to create a static image. | ||
<pre> | <pre> | ||
static const image heart = | static const image heart = | ||
Line 21: | Line 21: | ||
typedef unsigned image[NIMG]; | typedef unsigned image[NIMG]; | ||
</pre> | </pre> | ||
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 | 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 | ||
<pre> | <pre> | ||
Line 41: | Line 38: | ||
==Computing images dynamically== | ==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 | 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. Programmes running under {{microbian}} can use the device-independent interface described in the next section. Other programmes could adopt similar techniques and borrow code from the file @display.c@ of {{microbian}}. 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. | We can make a table giving, for each coordinate pair, the row and column in the display matrix. | ||
<pre> | <pre> |
Latest revision as of 15:03, 17 April 2023
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. Programmes running under micro:bian can use the device-independent interface described in the next section. Other programmes 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)