drivers: mb_display: rework bbc:microbit display support

Rework bbc:microbit display support to use nRF LED matrix
display controller driver and allow to use it with
bbc:microbit v2 board.

This patch turns the driver into a higher level driver
using the display controller API. Code that directly
accesses hardware (GPIO) is removed.
This driver is reworked to be more generic. It still
has a lot of potential for improvement, but it requires
changes in all applications that use this tool.

Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
This commit is contained in:
Johann Fischer 2021-12-15 23:30:59 +01:00 committed by Carles Cufí
commit 44585b7fc5
2 changed files with 160 additions and 202 deletions

View file

@ -5,9 +5,8 @@
config MICROBIT_DISPLAY config MICROBIT_DISPLAY
bool "BBC micro:bit 5x5 LED Display support" bool "BBC micro:bit 5x5 LED Display support"
depends on BOARD_BBC_MICROBIT depends on BOARD_BBC_MICROBIT || BOARD_BBC_MICROBIT_V2
depends on PRINTK depends on PRINTK
depends on GPIO
help help
Enable this to be able to display images and text on the 5x5 Enable this to be able to display images and text on the 5x5
LED matrix display on the BBC micro:bit. LED matrix display on the BBC micro:bit.

View file

@ -1,106 +1,54 @@
/* /*
* Copyright (c) 2017 Intel Corporation * Copyright (c) 2017 Intel Corporation
* Copyright (c) 2021, Nordic Semiconductor ASA
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
/* /*
* References: * This tool uses display controller driver API and requires
* * a suitable LED matrix controller driver.
* https://www.microbit.co.uk/device/screen
* https://lancaster-university.github.io/microbit-docs/ubit/display/
*/ */
#include <zephyr.h> #include <zephyr.h>
#include <init.h> #include <init.h>
#include <drivers/gpio.h>
#include <device.h>
#include <string.h> #include <string.h>
#include <sys/printk.h> #include <sys/printk.h>
#include <display/mb_display.h> #include <display/mb_display.h>
#include <drivers/display.h>
#include "mb_font.h" #include "mb_font.h"
#include <logging/log.h>
LOG_MODULE_REGISTER(mb_disp, CONFIG_DISPLAY_LOG_LEVEL);
#define MODE_MASK BIT_MASK(16) #define MODE_MASK BIT_MASK(16)
/* Onboard LED Row 1 */
#define LED_ROW1_GPIO_PIN 13
#define LED_ROW1_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
/* Onboard LED Row 2 */
#define LED_ROW2_GPIO_PIN 14
#define LED_ROW2_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
/* Onboard LED Row 3 */
#define LED_ROW3_GPIO_PIN 15
#define LED_ROW3_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
/* Onboard LED Column 1 */
#define LED_COL1_GPIO_PIN 4
#define LED_COL1_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
/* Onboard LED Column 2 */
#define LED_COL2_GPIO_PIN 5
#define LED_COL2_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
/* Onboard LED Column 3 */
#define LED_COL3_GPIO_PIN 6
#define LED_COL3_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
/* Onboard LED Column 4 */
#define LED_COL4_GPIO_PIN 7
#define LED_COL4_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
/* Onboard LED Column 5 */
#define LED_COL5_GPIO_PIN 8
#define LED_COL5_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
/* Onboard LED Column 6 */
#define LED_COL6_GPIO_PIN 9
#define LED_COL6_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
/* Onboard LED Column 7 */
#define LED_COL7_GPIO_PIN 10
#define LED_COL7_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
/* Onboard LED Column 8 */
#define LED_COL8_GPIO_PIN 11
#define LED_COL8_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
/* Onboard LED Column 9 */
#define LED_COL9_GPIO_PIN 12
#define LED_COL9_GPIO_PORT DT_LABEL(DT_NODELABEL(gpio0))
#define DISPLAY_ROWS 3
#define DISPLAY_COLS 9
#define SCROLL_OFF 0 #define SCROLL_OFF 0
#define SCROLL_START 1 #define SCROLL_START 1
#define SCROLL_DEFAULT_DURATION_MS 80 #define SCROLL_DEFAULT_DURATION_MS 80
#define MB_DISP_XRES 5
#define MB_DISP_YRES 5
struct mb_display { struct mb_display {
const struct device *dev; /* GPIO device */ const struct device *lm_dev; /* LED matrix display device */
struct k_timer timer; /* Rendering timer */ struct k_work_delayable dwork; /* Delayable work item */
uint8_t img_count; /* Image count */ uint8_t img_count; /* Image count */
uint8_t cur_img; /* Current image or character to show */ uint8_t cur_img; /* Current image or character to show */
uint8_t scroll:3, /* Scroll shift */ uint8_t scroll:3, /* Scroll shift */
first:1, /* First frame of a scroll sequence */ first:1, /* First frame of a scroll sequence */
loop:1, /* Loop to beginning */ loop:1, /* Loop to beginning */
text:1, /* We're showing a string (not image) */ text:1, /* We're showing a string (not image) */
img_sep:1; /* One column image separation */ img_sep:1, /* One column image separation */
msb:1; /* MSB represents the first pixel */
/* The following variables track the currently shown image */ int32_t duration; /* Duration for each shown image */
uint8_t cur; /* Currently rendered row */
uint32_t row[3]; /* Content (columns) for each row */
int64_t expiry; /* When to stop showing current image */
int32_t duration; /* Duration for each shown image */
union { union {
const struct mb_image *img; /* Array of images to show */ const struct mb_image *img; /* Array of images to show */
@ -111,24 +59,6 @@ struct mb_display {
char str_buf[CONFIG_MICROBIT_DISPLAY_STR_MAX]; char str_buf[CONFIG_MICROBIT_DISPLAY_STR_MAX];
}; };
struct x_y {
uint8_t x:4,
y:4;
};
/* Where the X,Y coordinates of each row/col are found.
* The top left corner has the coordinates 0,0.
*/
static const struct x_y map[DISPLAY_ROWS][DISPLAY_COLS] = {
{{0, 0}, {2, 0}, {4, 0}, {4, 3}, {3, 3}, {2, 3}, {1, 3}, {0, 3}, {1, 2} },
{{4, 2}, {0, 2}, {2, 2}, {1, 0}, {3, 0}, {3, 4}, {1, 4}, {0, 0}, {0, 0} },
{{2, 4}, {4, 4}, {0, 4}, {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, {3, 2} },
};
/* Mask of all the column bits */
static const uint32_t col_mask = (((~0UL) << LED_COL1_GPIO_PIN) &
((~0UL) >> (31 - LED_COL9_GPIO_PIN)));
static inline const struct mb_image *get_font(char ch) static inline const struct mb_image *get_font(char ch)
{ {
if (ch < MB_FONT_START || ch > MB_FONT_END) { if (ch < MB_FONT_START || ch > MB_FONT_END) {
@ -138,64 +68,78 @@ static inline const struct mb_image *get_font(char ch)
return &mb_font[ch - MB_FONT_START]; return &mb_font[ch - MB_FONT_START];
} }
#define GET_PIXEL(img, x, y) ((img)->row[y] & BIT(x)) static ALWAYS_INLINE uint8_t flip_pixels(uint8_t b)
/* Precalculate all three rows of an image and start the rendering. */
static void start_image(struct mb_display *disp, const struct mb_image *img)
{ {
int row, col; b = (b & 0xf0) >> 4 | (b & 0x0f) << 4;
b = (b & 0xcc) >> 2 | (b & 0x33) << 2;
b = (b & 0xaa) >> 1 | (b & 0x55) << 1;
for (row = 0; row < DISPLAY_ROWS; row++) { return b;
disp->row[row] = 0U; }
for (col = 0; col < DISPLAY_COLS; col++) { static int update_content(struct mb_display *disp, const struct mb_image *img)
if (GET_PIXEL(img, map[row][col].x, map[row][col].y)) { {
disp->row[row] |= BIT(LED_COL1_GPIO_PIN + col); const struct display_buffer_descriptor buf_desc = {
} .buf_size = sizeof(struct mb_image),
.width = MB_DISP_XRES,
.height = MB_DISP_YRES,
.pitch = 8,
};
struct mb_image tmp_img;
int ret;
if (disp->msb) {
for (int i = 0; i < sizeof(struct mb_image); i++) {
tmp_img.row[i] = flip_pixels(img->row[i]);
} }
disp->row[row] = ~disp->row[row] & col_mask; ret = display_write(disp->lm_dev, 0, 0, &buf_desc, &tmp_img);
disp->row[row] |= BIT(LED_ROW1_GPIO_PIN + row);
}
disp->cur = 0U;
if (disp->duration == SYS_FOREVER_MS) {
disp->expiry = SYS_FOREVER_MS;
} else { } else {
disp->expiry = k_uptime_get() + disp->duration; ret = display_write(disp->lm_dev, 0, 0, &buf_desc, img);
} }
k_timer_start(&disp->timer, K_NO_WAIT, K_MSEC(4)); if (ret < 0) {
} LOG_ERR("Write to display controller failed");
return ret;
#define ROW_PIN(n) (LED_ROW1_GPIO_PIN + (n))
static inline void update_pins(struct mb_display *disp, uint32_t val)
{
uint32_t pin, prev = (disp->cur + 2) % 3;
/* Disable the previous row */
gpio_pin_set_raw(disp->dev, ROW_PIN(prev), 0);
/* Set the column pins to their correct values */
for (pin = LED_COL1_GPIO_PIN; pin <= LED_COL9_GPIO_PIN; pin++) {
gpio_pin_set_raw(disp->dev, pin, !!(val & BIT(pin)));
} }
/* Enable the new row */ LOG_DBG("Image duration %d", disp->duration);
gpio_pin_set_raw(disp->dev, ROW_PIN(disp->cur), 1); if (disp->duration != SYS_FOREVER_MS) {
k_work_reschedule(&disp->dwork, K_MSEC(disp->duration));
}
return ret;
} }
static void reset_display(struct mb_display *disp) static int start_image(struct mb_display *disp, const struct mb_image *img)
{ {
k_timer_stop(&disp->timer); int ret;
ret = display_blanking_off(disp->lm_dev);
if (ret < 0) {
LOG_ERR("Set blanking off failed");
return ret;
}
return update_content(disp, img);
}
static int reset_display(struct mb_display *disp)
{
int ret;
disp->str = NULL; disp->str = NULL;
disp->cur_img = 0U; disp->cur_img = 0U;
disp->img = NULL; disp->img = NULL;
disp->img_count = 0U; disp->img_count = 0U;
disp->scroll = SCROLL_OFF; disp->scroll = SCROLL_OFF;
ret = display_blanking_on(disp->lm_dev);
if (ret < 0) {
LOG_ERR("Set blanking on failed");
}
return ret;
} }
static const struct mb_image *current_img(struct mb_display *disp) static const struct mb_image *current_img(struct mb_display *disp)
@ -243,16 +187,15 @@ static inline bool last_frame(struct mb_display *disp)
static inline uint8_t scroll_steps(struct mb_display *disp) static inline uint8_t scroll_steps(struct mb_display *disp)
{ {
return 5 + disp->img_sep; return MB_DISP_XRES + disp->img_sep;
} }
static void update_scroll(struct mb_display *disp) static int update_scroll(struct mb_display *disp)
{ {
if (disp->scroll < scroll_steps(disp)) { if (disp->scroll < scroll_steps(disp)) {
struct mb_image img; struct mb_image img;
int i;
for (i = 0; i < 5; i++) { for (int i = 0; i < MB_DISP_XRES; i++) {
const struct mb_image *i1 = current_img(disp); const struct mb_image *i1 = current_img(disp);
const struct mb_image *i2 = next_img(disp); const struct mb_image *i2 = next_img(disp);
@ -262,7 +205,7 @@ static void update_scroll(struct mb_display *disp)
} }
disp->scroll++; disp->scroll++;
start_image(disp, &img); return update_content(disp, &img);
} else { } else {
if (disp->first) { if (disp->first) {
disp->first = 0U; disp->first = 0U;
@ -272,8 +215,7 @@ static void update_scroll(struct mb_display *disp)
if (last_frame(disp)) { if (last_frame(disp)) {
if (!disp->loop) { if (!disp->loop) {
reset_display(disp); return reset_display(disp);
return;
} }
disp->cur_img = 0U; disp->cur_img = 0U;
@ -281,55 +223,41 @@ static void update_scroll(struct mb_display *disp)
} }
disp->scroll = SCROLL_START; disp->scroll = SCROLL_START;
start_image(disp, current_img(disp)); return update_content(disp, current_img(disp));
} }
} }
static void update_image(struct mb_display *disp) static int update_image(struct mb_display *disp)
{ {
disp->cur_img++; disp->cur_img++;
if (last_frame(disp)) { if (last_frame(disp)) {
if (!disp->loop) { if (!disp->loop) {
reset_display(disp); return reset_display(disp);
return;
} }
disp->cur_img = 0U; disp->cur_img = 0U;
} }
start_image(disp, current_img(disp)); return update_content(disp, current_img(disp));
} }
static void show_row(struct k_timer *timer) static void update_display_work(struct k_work *work)
{ {
struct mb_display *disp = CONTAINER_OF(timer, struct mb_display, timer); struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct mb_display *disp = CONTAINER_OF(dwork, struct mb_display, dwork);
int ret;
update_pins(disp, disp->row[disp->cur]); if (disp->scroll) {
disp->cur = (disp->cur + 1) % DISPLAY_ROWS; ret = update_scroll(disp);
} else {
if (disp->cur == 0U && disp->expiry != SYS_FOREVER_MS && ret = update_image(disp);
k_uptime_get() > disp->expiry) {
if (disp->scroll) {
update_scroll(disp);
} else {
update_image(disp);
}
} }
__ASSERT(ret == 0, "Failed to update display");
} }
static void clear_display(struct k_timer *timer) static int start_scroll(struct mb_display *disp, int32_t duration)
{
struct mb_display *disp = CONTAINER_OF(timer, struct mb_display, timer);
update_pins(disp, col_mask);
}
static struct mb_display display = {
.timer = Z_TIMER_INITIALIZER(display.timer, show_row, clear_display),
};
static void start_scroll(struct mb_display *disp, int32_t duration)
{ {
/* Divide total duration by number of scrolling steps */ /* Divide total duration by number of scrolling steps */
if (duration) { if (duration) {
@ -341,24 +269,37 @@ static void start_scroll(struct mb_display *disp, int32_t duration)
disp->scroll = SCROLL_START; disp->scroll = SCROLL_START;
disp->first = 1U; disp->first = 1U;
disp->cur_img = 0U; disp->cur_img = 0U;
start_image(disp, get_font(' ')); return start_image(disp, get_font(' '));
} }
static void start_single(struct mb_display *disp, int32_t duration) static int start_single(struct mb_display *disp, int32_t duration)
{ {
disp->duration = duration; disp->duration = duration;
if (disp->text) { if (disp->text) {
start_image(disp, get_font(disp->str[0])); return start_image(disp, get_font(disp->str[0]));
} else { } else {
start_image(disp, disp->img); return start_image(disp, disp->img);
} }
} }
void mb_display_stop(struct mb_display *disp)
{
struct k_work_sync sync;
int ret;
k_work_cancel_delayable_sync(&disp->dwork, &sync);
LOG_DBG("delayable work stopped %p", disp);
ret = reset_display(disp);
__ASSERT(ret == 0, "Failed to reset display");
}
void mb_display_image(struct mb_display *disp, uint32_t mode, int32_t duration, void mb_display_image(struct mb_display *disp, uint32_t mode, int32_t duration,
const struct mb_image *img, uint8_t img_count) const struct mb_image *img, uint8_t img_count)
{ {
reset_display(disp); int ret;
mb_display_stop(disp);
__ASSERT(img && img_count > 0, "Invalid parameters"); __ASSERT(img && img_count > 0, "Invalid parameters");
@ -372,27 +313,25 @@ void mb_display_image(struct mb_display *disp, uint32_t mode, int32_t duration,
switch (mode & MODE_MASK) { switch (mode & MODE_MASK) {
case MB_DISPLAY_MODE_DEFAULT: case MB_DISPLAY_MODE_DEFAULT:
case MB_DISPLAY_MODE_SINGLE: case MB_DISPLAY_MODE_SINGLE:
start_single(disp, duration); ret = start_single(disp, duration);
__ASSERT(ret == 0, "Failed to start single mode");
break; break;
case MB_DISPLAY_MODE_SCROLL: case MB_DISPLAY_MODE_SCROLL:
start_scroll(disp, duration); ret = start_scroll(disp, duration);
__ASSERT(ret == 0, "Failed to start scroll mode");
break; break;
default: default:
__ASSERT(0, "Invalid display mode"); __ASSERT(0, "Invalid display mode");
} }
} }
void mb_display_stop(struct mb_display *disp)
{
reset_display(disp);
}
void mb_display_print(struct mb_display *disp, uint32_t mode, void mb_display_print(struct mb_display *disp, uint32_t mode,
int32_t duration, const char *fmt, ...) int32_t duration, const char *fmt, ...)
{ {
va_list ap; va_list ap;
int ret;
reset_display(disp); mb_display_stop(disp);
va_start(ap, fmt); va_start(ap, fmt);
vsnprintk(disp->str_buf, sizeof(disp->str_buf), fmt, ap); vsnprintk(disp->str_buf, sizeof(disp->str_buf), fmt, ap);
@ -411,43 +350,63 @@ void mb_display_print(struct mb_display *disp, uint32_t mode,
switch (mode & MODE_MASK) { switch (mode & MODE_MASK) {
case MB_DISPLAY_MODE_DEFAULT: case MB_DISPLAY_MODE_DEFAULT:
case MB_DISPLAY_MODE_SCROLL: case MB_DISPLAY_MODE_SCROLL:
start_scroll(disp, duration); ret = start_scroll(disp, duration);
__ASSERT(ret == 0, "Failed to start scroll mode");
break; break;
case MB_DISPLAY_MODE_SINGLE: case MB_DISPLAY_MODE_SINGLE:
start_single(disp, duration); ret = start_single(disp, duration);
__ASSERT(ret == 0, "Failed to start single mode");
break; break;
default: default:
__ASSERT(0, "Invalid display mode"); __ASSERT(0, "Invalid display mode");
} }
} }
static int mb_display_init(struct mb_display *disp)
{
struct display_capabilities caps;
int ret;
display_get_capabilities(disp->lm_dev, &caps);
if (caps.x_resolution != MB_DISP_XRES ||
caps.y_resolution != MB_DISP_YRES) {
LOG_ERR("Not supported display resolution");
return -ENOTSUP;
}
if (caps.screen_info & SCREEN_INFO_MONO_MSB_FIRST) {
disp->msb = 1U;
}
ret = display_set_brightness(disp->lm_dev, 0xFF);
if (ret < 0) {
LOG_ERR("Failed to set brightness");
return ret;
}
k_work_init_delayable(&disp->dwork, update_display_work);
return 0;
}
static struct mb_display display;
struct mb_display *mb_display_get(void) struct mb_display *mb_display_get(void)
{ {
return &display; return &display;
} }
static int mb_display_init(const struct device *dev) static int mb_display_init_on_boot(const struct device *dev)
{ {
ARG_UNUSED(dev); ARG_UNUSED(dev);
display.dev = device_get_binding(DT_LABEL(DT_NODELABEL(gpio0))); display.lm_dev = DEVICE_DT_GET_ONE(nordic_nrf_led_matrix);
if (!device_is_ready(display.lm_dev)) {
LOG_ERR("Display controller device not ready");
return -ENODEV;
}
__ASSERT(dev, "No GPIO device found"); return mb_display_init(&display);
gpio_pin_configure(display.dev, LED_ROW1_GPIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(display.dev, LED_ROW2_GPIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(display.dev, LED_ROW3_GPIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(display.dev, LED_COL1_GPIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(display.dev, LED_COL2_GPIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(display.dev, LED_COL3_GPIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(display.dev, LED_COL4_GPIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(display.dev, LED_COL5_GPIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(display.dev, LED_COL6_GPIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(display.dev, LED_COL7_GPIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(display.dev, LED_COL8_GPIO_PIN, GPIO_OUTPUT);
gpio_pin_configure(display.dev, LED_COL9_GPIO_PIN, GPIO_OUTPUT);
return 0;
} }
SYS_INIT(mb_display_init, POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY); SYS_INIT(mb_display_init_on_boot, APPLICATION, CONFIG_DISPLAY_INIT_PRIORITY);