aboutsummaryrefslogtreecommitdiff
path: root/ports/nrf/boards/microbit/modules/microbitdisplay.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ports/nrf/boards/microbit/modules/microbitdisplay.cpp')
-rw-r--r--ports/nrf/boards/microbit/modules/microbitdisplay.cpp579
1 files changed, 579 insertions, 0 deletions
diff --git a/ports/nrf/boards/microbit/modules/microbitdisplay.cpp b/ports/nrf/boards/microbit/modules/microbitdisplay.cpp
new file mode 100644
index 000000000..92bf58d82
--- /dev/null
+++ b/ports/nrf/boards/microbit/modules/microbitdisplay.cpp
@@ -0,0 +1,579 @@
+/*
+ * This file is part of the Micro Python project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Damien P. George
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+#include "microbitobj.h"
+#include "nrf_gpio.h"
+
+extern "C" {
+#include "py/runtime.h"
+#include "py/gc.h"
+#include "modmicrobit.h"
+#include "microbitimage.h"
+#include "microbitdisplay.h"
+#include "microbitpin.h"
+#include "lib/iters.h"
+#include "lib/ticker.h"
+
+#define min(a,b) (((a)<(b))?(a):(b))
+
+void microbit_display_show(microbit_display_obj_t *display, microbit_image_obj_t *image) {
+ mp_int_t w = min(image->width(), 5);
+ mp_int_t h = min(image->height(), 5);
+ mp_int_t x = 0;
+ mp_int_t brightnesses = 0;
+ for (; x < w; ++x) {
+ mp_int_t y = 0;
+ for (; y < h; ++y) {
+ uint8_t pix = image->getPixelValue(x, y);
+ display->image_buffer[x][y] = pix;
+ brightnesses |= (1 << pix);
+ }
+ for (; y < 5; ++y) {
+ display->image_buffer[x][y] = 0;
+ }
+ }
+ for (; x < 5; ++x) {
+ for (mp_int_t y = 0; y < 5; ++y) {
+ display->image_buffer[x][y] = 0;
+ }
+ }
+ display->brightnesses = brightnesses;
+}
+
+#define DEFAULT_PRINT_SPEED 400
+
+
+mp_obj_t microbit_display_show_func(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+
+ // Cancel any animations.
+ MP_STATE_PORT(async_data)[0] = NULL;
+ MP_STATE_PORT(async_data)[1] = NULL;
+
+ static const mp_arg_t show_allowed_args[] = {
+ { MP_QSTR_image, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_delay, MP_ARG_INT, {.u_int = DEFAULT_PRINT_SPEED} },
+ { MP_QSTR_clear, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
+ { MP_QSTR_wait, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} },
+ { MP_QSTR_loop, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
+ };
+
+ // Parse the args.
+ microbit_display_obj_t *self = (microbit_display_obj_t*)pos_args[0];
+ mp_arg_val_t args[MP_ARRAY_SIZE(show_allowed_args)];
+ mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(show_allowed_args), show_allowed_args, args);
+
+ mp_obj_t image = args[0].u_obj;
+ mp_int_t delay = args[1].u_int;
+ bool clear = args[2].u_bool;
+ bool wait = args[3].u_bool;
+ bool loop = args[4].u_bool;
+
+ if (MP_OBJ_IS_STR(image)) {
+ // arg is a string object
+ mp_uint_t len;
+ const char *str = mp_obj_str_get_data(image, &len);
+ if (len == 0) {
+ // There are no chars; do nothing.
+ return mp_const_none;
+ } else if (len == 1) {
+ if (!clear && !loop) {
+ // A single char; convert to an image and print that.
+ image = microbit_image_for_char(str[0]);
+ goto single_image_immediate;
+ }
+ }
+ image = microbit_string_facade(image);
+ } else if (mp_obj_get_type(image) == &microbit_image_type) {
+ if (!clear && !loop) {
+ goto single_image_immediate;
+ }
+ image = mp_obj_new_tuple(1, &image);
+ }
+ // iterable:
+ if (args[4].u_bool) { /*loop*/
+ image = microbit_repeat_iterator(image);
+ }
+ microbit_display_animate(self, image, delay, clear, wait);
+ return mp_const_none;
+
+single_image_immediate:
+ microbit_display_show(self, (microbit_image_obj_t *)image);
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_KW(microbit_display_show_obj, 1, microbit_display_show_func);
+
+static uint8_t async_mode;
+static mp_obj_t async_iterator = NULL;
+// Record if an error occurs in async animation. Unfortunately there is no way to report this.
+static volatile bool wakeup_event = false;
+static mp_uint_t async_delay = 1000;
+static mp_uint_t async_tick = 0;
+static bool async_clear = false;
+
+
+bool microbit_display_active_animation(void) {
+ return async_mode == ASYNC_MODE_ANIMATION;
+}
+
+STATIC void async_stop(void) {
+ async_iterator = NULL;
+ async_mode = ASYNC_MODE_STOPPED;
+ async_tick = 0;
+ async_delay = 1000;
+ async_clear = false;
+ MP_STATE_PORT(async_data)[0] = NULL;
+ MP_STATE_PORT(async_data)[1] = NULL;
+ wakeup_event = true;
+}
+
+STATIC void wait_for_event() {
+ while (!wakeup_event) {
+ // allow CTRL-C to stop the animation
+ if (MP_STATE_VM(mp_pending_exception) != MP_OBJ_NULL) {
+ async_stop();
+ return;
+ }
+ __WFI();
+ }
+ wakeup_event = false;
+}
+
+struct DisplayPoint {
+ uint8_t x;
+ uint8_t y;
+};
+
+#define NO_CONN 0
+
+#define ROW_COUNT 3
+#define COLUMN_COUNT 9
+
+static const DisplayPoint display_map[COLUMN_COUNT][ROW_COUNT] = {
+ {{0,0}, {4,2}, {2,4}},
+ {{2,0}, {0,2}, {4,4}},
+ {{4,0}, {2,2}, {0,4}},
+ {{4,3}, {1,0}, {0,1}},
+ {{3,3}, {3,0}, {1,1}},
+ {{2,3}, {3,4}, {2,1}},
+ {{1,3}, {1,4}, {3,1}},
+ {{0,3}, {NO_CONN,NO_CONN}, {4,1}},
+ {{1,2}, {NO_CONN,NO_CONN}, {3,2}}
+};
+
+#define MIN_COLUMN_PIN 4
+#define COLUMN_PINS_MASK 0x1ff0
+#define MIN_ROW_PIN 13
+#define MAX_ROW_PIN 15
+#define ROW_PINS_MASK 0xe000
+
+inline void microbit_display_obj_t::setPinsForRow(uint8_t brightness) {
+ if (brightness == 0) {
+ nrf_gpio_pins_clear(COLUMN_PINS_MASK & ~this->pins_for_brightness[brightness]);
+ } else {
+ nrf_gpio_pins_set(this->pins_for_brightness[brightness]);
+ }
+}
+
+/* This is the primary PWM driver/display driver. It will operate on one row
+ * (9 pins) per invocation. It will turn on LEDs with maximum brightness,
+ * then let the "callback" callback turn off the LEDs as appropriate for the
+ * required brightness level.
+ *
+ * For each row
+ * Turn off all the LEDs in the previous row
+ * Set the column bits high (off)
+ * Set the row strobe low (off)
+ * Turn on all the LEDs in the current row that have maximum brightness
+ * Set the row strobe high (on)
+ * Set some/all column bits low (on)
+ * Register the PWM callback
+ * For each callback start with brightness 0
+ * If brightness 0
+ * Turn off the LEDs specified at this level
+ * Else
+ * Turn on the LEDs specified at this level
+ * If brightness max
+ * Disable the PWM callback
+ * Else
+ * Re-queue the PWM callback after the appropriate delay
+ */
+void microbit_display_obj_t::advanceRow() {
+ /* Clear all of the column bits */
+ nrf_gpio_pins_set(COLUMN_PINS_MASK);
+ /* Clear the strobe bit for this row */
+ nrf_gpio_pin_clear(strobe_row+MIN_ROW_PIN);
+
+ /* Move to the next row. Before this, "this row" refers to the row
+ * manipulated by the previous invocation of this function. After this,
+ * "this row" refers to the row manipulated by the current invocation of
+ * this function. */
+ strobe_row++;
+
+ // Reset the row counts and bit mask when we have hit the max.
+ if (strobe_row == ROW_COUNT) {
+ strobe_row = 0;
+ }
+
+ // Set pin for this row.
+ // Prepare row for rendering.
+ for (int i = 0; i <= MAX_BRIGHTNESS; i++) {
+ pins_for_brightness[i] = 0;
+ }
+ for (int i = 0; i < COLUMN_COUNT; i++) {
+ int x = display_map[i][strobe_row].x;
+ int y = display_map[i][strobe_row].y;
+ uint8_t brightness = microbit_display_obj.image_buffer[x][y];
+ pins_for_brightness[brightness] |= (1<<(i+MIN_COLUMN_PIN));
+ }
+ /* Enable the strobe bit for this row */
+ nrf_gpio_pin_set(strobe_row+MIN_ROW_PIN);
+ /* Enable the column bits for all pins that need to be on. */
+ nrf_gpio_pins_clear(pins_for_brightness[MAX_BRIGHTNESS]);
+}
+
+static const uint16_t render_timings[] =
+// The scale is (approximately) exponential,
+// each step is approx x1.9 greater than the previous.
+{ 0, // Bright, Ticks Duration, Relative power
+ 2, // 1, 2, 32µs, inf
+ 2, // 2, 4, 64µs, 200%
+ 4, // 3, 8, 128µs, 200%
+ 7, // 4, 15, 240µs, 187%
+ 13, // 5, 28, 448µs, 187%
+ 25, // 6, 53, 848µs, 189%
+ 49, // 7, 102, 1632µs, 192%
+ 97, // 8, 199, 3184µs, 195%
+// Always on 9, 375, 6000µs, 188%
+};
+
+#define DISPLAY_TICKER_SLOT 1
+
+/* This is the PWM callback. It is registered by the animation callback and
+ * will unregister itself when all of the brightness steps are complete. */
+static int32_t callback(void) {
+ microbit_display_obj_t *display = &microbit_display_obj;
+ mp_uint_t brightness = display->previous_brightness;
+ display->setPinsForRow(brightness);
+ brightness += 1;
+ if (brightness == MAX_BRIGHTNESS) {
+ clear_ticker_callback(DISPLAY_TICKER_SLOT);
+ return -1;
+ }
+ display->previous_brightness = brightness;
+ // Return interval (in 16µs ticks) until next callback
+ return render_timings[brightness];
+}
+
+static void draw_object(mp_obj_t obj) {
+ microbit_display_obj_t *display = (microbit_display_obj_t*)MP_STATE_PORT(async_data)[0];
+ if (obj == MP_OBJ_STOP_ITERATION) {
+ if (async_clear) {
+ microbit_display_show(&microbit_display_obj, BLANK_IMAGE);
+ async_clear = false;
+ } else {
+ async_stop();
+ }
+ } else if (mp_obj_get_type(obj) == &microbit_image_type) {
+ microbit_display_show(display, (microbit_image_obj_t *)obj);
+ } else if (MP_OBJ_IS_STR(obj)) {
+ mp_uint_t len;
+ const char *str = mp_obj_str_get_data(obj, &len);
+ if (len == 1) {
+ microbit_display_show(display, microbit_image_for_char(str[0]));
+ } else {
+ async_stop();
+ }
+ } else {
+ MP_STATE_VM(mp_pending_exception) = mp_obj_new_exception_msg(&mp_type_TypeError, "not an image.");
+ async_stop();
+ }
+}
+
+static void microbit_display_update(void) {
+ async_tick += MILLISECONDS_PER_MACRO_TICK;
+ if (async_tick < async_delay) {
+ return;
+ }
+ async_tick = 0;
+ switch (async_mode) {
+ case ASYNC_MODE_ANIMATION:
+ {
+ if (MP_STATE_PORT(async_data)[0] == NULL || MP_STATE_PORT(async_data)[1] == NULL) {
+ async_stop();
+ break;
+ }
+ /* WARNING: We are executing in an interrupt handler.
+ * If an exception is raised here then we must hand it to the VM. */
+ mp_obj_t obj;
+ nlr_buf_t nlr;
+ gc_lock();
+ if (nlr_push(&nlr) == 0) {
+ obj = mp_iternext_allow_raise(async_iterator);
+ nlr_pop();
+ gc_unlock();
+ } else {
+ gc_unlock();
+ if (!mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)nlr.ret_val)->type),
+ MP_OBJ_FROM_PTR(&mp_type_StopIteration))) {
+ // An exception other than StopIteration, so set it for the VM to raise later
+ // If memory error, write an appropriate message.
+ if (mp_obj_get_type(nlr.ret_val) == &mp_type_MemoryError) {
+ mp_printf(&mp_plat_print, "Allocation in interrupt handler");
+ }
+ MP_STATE_VM(mp_pending_exception) = MP_OBJ_FROM_PTR(nlr.ret_val);
+ }
+ obj = MP_OBJ_STOP_ITERATION;
+ }
+ draw_object(obj);
+ break;
+ }
+ case ASYNC_MODE_CLEAR:
+ microbit_display_show(&microbit_display_obj, BLANK_IMAGE);
+ async_stop();
+ break;
+ }
+}
+
+#define GREYSCALE_MASK ((1<<MAX_BRIGHTNESS)-2)
+
+/* This is the top-level animation/display callback. It is not a registered
+ * callback. */
+void microbit_display_tick(void) {
+ /* Do nothing if the display is not active. */
+ if (!microbit_display_obj.active) {
+ return;
+ }
+
+ microbit_display_obj.advanceRow();
+
+ microbit_display_update();
+ microbit_display_obj.previous_brightness = 0;
+ if (microbit_display_obj.brightnesses & GREYSCALE_MASK) {
+ set_ticker_callback(DISPLAY_TICKER_SLOT, callback, 1800);
+ }
+}
+
+
+void microbit_display_animate(microbit_display_obj_t *self, mp_obj_t iterable, mp_int_t delay, bool clear, bool wait) {
+ // Reset the repeat state.
+ MP_STATE_PORT(async_data)[0] = NULL;
+ MP_STATE_PORT(async_data)[1] = NULL;
+ async_iterator = mp_getiter(iterable);
+ async_delay = delay;
+ async_clear = clear;
+ MP_STATE_PORT(async_data)[0] = self; // so it doesn't get GC'd
+ MP_STATE_PORT(async_data)[1] = async_iterator;
+ wakeup_event = false;
+ mp_obj_t obj = mp_iternext_allow_raise(async_iterator);
+ draw_object(obj);
+ async_tick = 0;
+ async_mode = ASYNC_MODE_ANIMATION;
+ if (wait) {
+ wait_for_event();
+ }
+}
+
+
+// Delay in ms in between moving display one column to the left.
+#define DEFAULT_SCROLL_SPEED 150
+
+void microbit_display_scroll(microbit_display_obj_t *self, const char* str, bool wait) {
+ mp_obj_t iterable = scrolling_string_image_iterable(str, strlen(str), NULL, false, false);
+ microbit_display_animate(self, iterable, DEFAULT_SCROLL_SPEED, false, wait);
+}
+
+
+mp_obj_t microbit_display_scroll_func(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+ static const mp_arg_t scroll_allowed_args[] = {
+ { MP_QSTR_text, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
+ { MP_QSTR_delay, MP_ARG_INT, {.u_int = DEFAULT_SCROLL_SPEED} },
+ { MP_QSTR_wait, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} },
+ { MP_QSTR_monospace, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
+ { MP_QSTR_loop, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
+ };
+ // Parse the args.
+ microbit_display_obj_t *self = (microbit_display_obj_t*)pos_args[0];
+ mp_arg_val_t args[MP_ARRAY_SIZE(scroll_allowed_args)];
+ mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(scroll_allowed_args), scroll_allowed_args, args);
+ mp_uint_t len;
+ const char* str = mp_obj_str_get_data(args[0].u_obj, &len);
+ mp_obj_t iterable = scrolling_string_image_iterable(str, len, args[0].u_obj, args[3].u_bool /*monospace?*/, args[4].u_bool /*loop*/);
+ microbit_display_animate(self, iterable, args[1].u_int /*delay*/, false/*clear*/, args[2].u_bool/*wait?*/);
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_KW(microbit_display_scroll_obj, 1, microbit_display_scroll_func);
+
+mp_obj_t microbit_display_on_func(mp_obj_t obj) {
+ microbit_display_obj_t *self = (microbit_display_obj_t*)obj;
+ /* Try to reclaim the pins we need */
+ microbit_obj_pin_fail_if_cant_acquire(&microbit_p3_obj);
+ microbit_obj_pin_fail_if_cant_acquire(&microbit_p4_obj);
+ microbit_obj_pin_fail_if_cant_acquire(&microbit_p6_obj);
+ microbit_obj_pin_fail_if_cant_acquire(&microbit_p7_obj);
+ microbit_obj_pin_fail_if_cant_acquire(&microbit_p9_obj);
+ microbit_obj_pin_fail_if_cant_acquire(&microbit_p10_obj);
+ microbit_obj_pin_acquire(&microbit_p3_obj, microbit_pin_mode_display);
+ microbit_obj_pin_acquire(&microbit_p4_obj, microbit_pin_mode_display);
+ microbit_obj_pin_acquire(&microbit_p6_obj, microbit_pin_mode_display);
+ microbit_obj_pin_acquire(&microbit_p7_obj, microbit_pin_mode_display);
+ microbit_obj_pin_acquire(&microbit_p9_obj, microbit_pin_mode_display);
+ microbit_obj_pin_acquire(&microbit_p10_obj, microbit_pin_mode_display);
+ /* Make sure all pins are in the correct state */
+ microbit_display_init();
+ /* Re-enable the display loop. This will resume any animations in
+ * progress and display any static image. */
+ self->active = true;
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_1(microbit_display_on_obj, microbit_display_on_func);
+
+mp_obj_t microbit_display_off_func(mp_obj_t obj) {
+ microbit_display_obj_t *self = (microbit_display_obj_t*)obj;
+ /* Disable the display loop. This will pause any animations in progress.
+ * It will not prevent a user from attempting to modify the state, but
+ * modifications will not appear to have any effect until the display loop
+ * is re-enabled. */
+ self->active = false;
+ /* Disable the row strobes, allowing the columns to be used freely for
+ * GPIO. */
+ nrf_gpio_pins_clear(ROW_PINS_MASK);
+ /* Free pins for other uses */
+ microbit_obj_pin_free(&microbit_p3_obj);
+ microbit_obj_pin_free(&microbit_p4_obj);
+ microbit_obj_pin_free(&microbit_p6_obj);
+ microbit_obj_pin_free(&microbit_p7_obj);
+ microbit_obj_pin_free(&microbit_p9_obj);
+ microbit_obj_pin_free(&microbit_p10_obj);
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_1(microbit_display_off_obj, microbit_display_off_func);
+
+mp_obj_t microbit_display_is_on_func(mp_obj_t obj) {
+ microbit_display_obj_t *self = (microbit_display_obj_t*)obj;
+ if (self->active) {
+ return mp_const_true;
+ }
+ else {
+ return mp_const_false;
+ }
+}
+MP_DEFINE_CONST_FUN_OBJ_1(microbit_display_is_on_obj, microbit_display_is_on_func);
+
+void microbit_display_clear(void) {
+ // Reset repeat state, cancel animation and clear screen.
+ wakeup_event = false;
+ async_mode = ASYNC_MODE_CLEAR;
+ async_tick = async_delay - MILLISECONDS_PER_MACRO_TICK;
+ wait_for_event();
+}
+
+mp_obj_t microbit_display_clear_func(void) {
+ microbit_display_clear();
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_1(microbit_display_clear_obj, microbit_display_clear_func);
+
+void microbit_display_set_pixel(microbit_display_obj_t *display, mp_int_t x, mp_int_t y, mp_int_t bright) {
+ if (x < 0 || y < 0 || x > 4 || y > 4) {
+ nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "index out of bounds."));
+ }
+ if (bright < 0 || bright > MAX_BRIGHTNESS) {
+ nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "brightness out of bounds."));
+ }
+ display->image_buffer[x][y] = bright;
+ display->brightnesses |= (1 << bright);
+}
+
+STATIC mp_obj_t microbit_display_set_pixel_func(mp_uint_t n_args, const mp_obj_t *args) {
+ (void)n_args;
+ microbit_display_obj_t *self = (microbit_display_obj_t*)args[0];
+ microbit_display_set_pixel(self, mp_obj_get_int(args[1]), mp_obj_get_int(args[2]), mp_obj_get_int(args[3]));
+ return mp_const_none;
+}
+MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(microbit_display_set_pixel_obj, 4, 4, microbit_display_set_pixel_func);
+
+mp_int_t microbit_display_get_pixel(microbit_display_obj_t *display, mp_int_t x, mp_int_t y) {
+ if (x < 0 || y < 0 || x > 4 || y > 4) {
+ nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "index out of bounds."));
+ }
+ return display->image_buffer[x][y];
+}
+
+STATIC mp_obj_t microbit_display_get_pixel_func(mp_obj_t self_in, mp_obj_t x_in, mp_obj_t y_in) {
+ microbit_display_obj_t *self = (microbit_display_obj_t*)self_in;
+ return MP_OBJ_NEW_SMALL_INT(microbit_display_get_pixel(self, mp_obj_get_int(x_in), mp_obj_get_int(y_in)));
+}
+MP_DEFINE_CONST_FUN_OBJ_3(microbit_display_get_pixel_obj, microbit_display_get_pixel_func);
+
+STATIC const mp_map_elem_t microbit_display_locals_dict_table[] = {
+
+ { MP_OBJ_NEW_QSTR(MP_QSTR_get_pixel), (mp_obj_t)&microbit_display_get_pixel_obj },
+ { MP_OBJ_NEW_QSTR(MP_QSTR_set_pixel), (mp_obj_t)&microbit_display_set_pixel_obj },
+ { MP_OBJ_NEW_QSTR(MP_QSTR_show), (mp_obj_t)&microbit_display_show_obj },
+ { MP_OBJ_NEW_QSTR(MP_QSTR_scroll), (mp_obj_t)&microbit_display_scroll_obj },
+ { MP_OBJ_NEW_QSTR(MP_QSTR_clear), (mp_obj_t)&microbit_display_clear_obj },
+ { MP_OBJ_NEW_QSTR(MP_QSTR_on), (mp_obj_t)&microbit_display_on_obj },
+ { MP_OBJ_NEW_QSTR(MP_QSTR_off), (mp_obj_t)&microbit_display_off_obj },
+ { MP_OBJ_NEW_QSTR(MP_QSTR_is_on), (mp_obj_t)&microbit_display_is_on_obj },
+};
+
+STATIC MP_DEFINE_CONST_DICT(microbit_display_locals_dict, microbit_display_locals_dict_table);
+
+STATIC const mp_obj_type_t microbit_display_type = {
+ { &mp_type_type },
+ .name = MP_QSTR_MicroBitDisplay,
+ .print = NULL,
+ .make_new = NULL,
+ .call = NULL,
+ .unary_op = NULL,
+ .binary_op = NULL,
+ .attr = NULL,
+ .subscr = NULL,
+ .getiter = NULL,
+ .iternext = NULL,
+ .buffer_p = {NULL},
+ .stream_p = NULL,
+ .bases_tuple = NULL,
+ .locals_dict = (mp_obj_dict_t*)&microbit_display_locals_dict,
+};
+
+microbit_display_obj_t microbit_display_obj = {
+ {&microbit_display_type},
+ { 0 },
+ .previous_brightness = 0,
+ .active = 1,
+ .strobe_row = 0,
+ .brightnesses = 0,
+ .pins_for_brightness = { 0 },
+};
+
+void microbit_display_init(void) {
+ // Set pins as output.
+ nrf_gpio_range_cfg_output(MIN_COLUMN_PIN, MIN_COLUMN_PIN + COLUMN_COUNT + ROW_COUNT);
+}
+
+}