drivers: display: mb_display: Unify image and string APIs

Both the string and image rendering may want to take advantage of
scrolling and sequential display capabilities. Consolidate the APIs so
that there's a single one for images (mb_display_image) and a single
one for strings (mb_display_print). Both take a duration parameter for
the per-frame duration as well as a mode parameter which specifies
sequential vs scrolling behavior as well as an optional looping flag.

Change-Id: Ia092d771e3f1b94afd494c7544dab988161c539e
Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
This commit is contained in:
Johan Hedberg 2017-04-05 18:36:36 +03:00 committed by Anas Nashif
commit d7a189d2ec
5 changed files with 291 additions and 124 deletions

View file

@ -29,21 +29,11 @@ config MICROBIT_DISPLAY_PIN_GRANULARITY
config MICROBIT_DISPLAY_STR_MAX config MICROBIT_DISPLAY_STR_MAX
int "Maximum length of strings that can be shown on the display" int "Maximum length of strings that can be shown on the display"
range 3 1024 range 3 255
default 40 default 40
help help
This value specifies the maximum length of strings that can This value specifies the maximum length of strings that can
be displayed using the mb_display_string() and mb_display_print() be displayed using the mb_display_string() and mb_display_print()
APIs. APIs.
config MICROBIT_DISPLAY_SCROLL_STEP
int "Duration between two string scrolling steps (in milliseconds)"
range 20 2000
default 80
help
This value specifies the time between two scrolling steps of the
string scrolling functionality. Smaller values mean faster
scrolling whereas bigger values mean slower scrolling. It is
usually best to leave this at its default value (80ms).
endif # MICROBIT_DISPLAY endif # MICROBIT_DISPLAY

View file

@ -23,21 +23,30 @@
#include "mb_font.h" #include "mb_font.h"
#define MODE_MASK BIT_MASK(16)
#define DISPLAY_ROWS 3 #define DISPLAY_ROWS 3
#define DISPLAY_COLS 9 #define DISPLAY_COLS 9
#define SCROLL_OFF 0 #define SCROLL_OFF 0
#define SCROLL_START 1 #define SCROLL_START 1
/* Time between scroll shifts */ #define SCROLL_DEFAULT_DURATION K_MSEC(80)
#define SCROLL_DURATION K_MSEC(CONFIG_MICROBIT_DISPLAY_SCROLL_STEP)
struct mb_display { struct mb_display {
struct device *dev; /* GPIO device */ struct device *dev; /* GPIO device */
struct k_timer timer; /* Rendering timer */ struct k_timer timer; /* Rendering timer */
uint8_t scroll; /* Scroll shift */ uint8_t img_count; /* Image count */
uint8_t cur_img; /* Current image or character to show */
uint8_t scroll:3, /* Scroll shift */
first:1, /* First frame of a scroll sequence */
loop:1, /* Loop to beginning */
text:1, /* We're showing a string (not image) */
img_sep:1; /* One column image separation */
/* The following variables track the currently shown image */ /* The following variables track the currently shown image */
uint8_t cur; /* Currently rendered row */ uint8_t cur; /* Currently rendered row */
@ -45,7 +54,10 @@ struct mb_display {
int64_t expiry; /* When to stop showing current image */ int64_t expiry; /* When to stop showing current image */
int32_t duration; /* Duration for each shown image */ int32_t duration; /* Duration for each shown image */
const char *str; /* String to be shown */ union {
const struct mb_image *img; /* Array of images to show */
const char *str; /* String to be shown */
};
/* Buffer for printed strings */ /* Buffer for printed strings */
char str_buf[CONFIG_MICROBIT_DISPLAY_STR_MAX]; char str_buf[CONFIG_MICROBIT_DISPLAY_STR_MAX];
@ -131,39 +143,118 @@ static inline void update_pins(struct mb_display *disp, uint32_t val)
} }
} }
static void reset_display(struct mb_display *disp)
{
k_timer_stop(&disp->timer);
disp->str = NULL;
disp->cur_img = 0;
disp->img = NULL;
disp->img_count = 0;
disp->scroll = SCROLL_OFF;
}
static const struct mb_image *current_img(struct mb_display *disp)
{
if (disp->scroll && disp->first) {
return get_font(' ');
}
if (disp->text) {
return get_font(disp->str[disp->cur_img]);
} else {
return &disp->img[disp->cur_img];
}
}
static const struct mb_image *next_img(struct mb_display *disp)
{
if (disp->text) {
if (disp->first) {
return get_font(disp->str[0]);
} else if (disp->str[disp->cur_img]) {
return get_font(disp->str[disp->cur_img + 1]);
} else {
return get_font(' ');
}
} else {
if (disp->first) {
return &disp->img[0];
} else if (disp->cur_img < (disp->img_count - 1)) {
return &disp->img[disp->cur_img + 1];
} else {
return get_font(' ');
}
}
}
static inline bool last_frame(struct mb_display *disp)
{
if (disp->text) {
return (disp->str[disp->cur_img] == '\0');
} else {
return (disp->cur_img >= disp->img_count);
}
}
static inline uint8_t scroll_steps(struct mb_display *disp)
{
return 5 + disp->img_sep;
}
static void update_scroll(struct mb_display *disp) static void update_scroll(struct mb_display *disp)
{ {
if (disp->scroll < 6) { if (disp->scroll < scroll_steps(disp)) {
struct mb_image img; struct mb_image img;
int i; int i;
for (i = 0; i < 5; i++) { for (i = 0; i < 5; i++) {
const struct mb_image *i1 = get_font(disp->str[0]); const struct mb_image *i1 = current_img(disp);
const struct mb_image *i2; const struct mb_image *i2 = next_img(disp);
if (disp->str[0]) {
i2 = get_font(disp->str[1]);
} else {
i2 = get_font(' ');
}
img.row[i] = ((i1->row[i] >> disp->scroll) | img.row[i] = ((i1->row[i] >> disp->scroll) |
(i2->row[i] << (6 - disp->scroll))); (i2->row[i] << (scroll_steps(disp) -
disp->scroll)));
} }
disp->scroll++; disp->scroll++;
start_image(disp, &img); start_image(disp, &img);
} else { } else {
if (!disp->str[1]) { if (disp->first) {
disp->scroll = SCROLL_OFF; disp->first = 0;
disp->str = NULL; } else {
disp->cur_img++;
}
if (last_frame(disp)) {
if (!disp->loop) {
reset_display(disp);
return;
}
disp->cur_img = 0;
disp->first = 1;
}
disp->scroll = SCROLL_START;
start_image(disp, current_img(disp));
}
}
static void update_image(struct mb_display *disp)
{
disp->cur_img++;
if (last_frame(disp)) {
if (!disp->loop) {
reset_display(disp);
return; return;
} }
disp->str++; disp->cur_img = 0;
disp->scroll = SCROLL_START;
start_image(disp, get_font(disp->str[0]));
} }
start_image(disp, current_img(disp));
} }
static void show_row(struct k_timer *timer) static void show_row(struct k_timer *timer)
@ -177,11 +268,8 @@ static void show_row(struct k_timer *timer)
k_uptime_get() > disp->expiry) { k_uptime_get() > disp->expiry) {
if (disp->scroll) { if (disp->scroll) {
update_scroll(disp); update_scroll(disp);
} else if (disp->str && disp->str[0]) {
start_image(disp, get_font(disp->str[0]));
disp->str++;
} else { } else {
k_timer_stop(&disp->timer); update_image(disp);
} }
} }
} }
@ -197,21 +285,57 @@ static struct mb_display display = {
.timer = K_TIMER_INITIALIZER(display.timer, show_row, clear_display), .timer = K_TIMER_INITIALIZER(display.timer, show_row, clear_display),
}; };
static void reset_display(struct mb_display *disp) static void start_scroll(struct mb_display *disp, int32_t duration)
{ {
k_timer_stop(&disp->timer); /* Divide total duration by number of scrolling steps */
if (duration) {
disp->duration = duration / scroll_steps(disp);
} else {
disp->duration = SCROLL_DEFAULT_DURATION;
}
disp->str = NULL; disp->scroll = SCROLL_START;
disp->scroll = SCROLL_OFF; disp->first = 1;
disp->cur_img = 0;
start_image(disp, get_font(' '));
} }
void mb_display_image(struct mb_display *disp, const struct mb_image *img, static void start_single(struct mb_display *disp, int32_t duration)
int32_t duration) {
disp->duration = duration;
if (disp->text) {
start_image(disp, get_font(disp->str[0]));
} else {
start_image(disp, disp->img);
}
}
void mb_display_image(struct mb_display *disp, uint32_t mode, int32_t duration,
const struct mb_image *img, uint8_t img_count)
{ {
reset_display(disp); reset_display(disp);
disp->duration = duration; __ASSERT(img && img_count > 0, "Invalid parameters");
start_image(disp, img);
disp->text = 0;
disp->img_count = img_count;
disp->img = img;
disp->img_sep = 0;
disp->cur_img = 0;
disp->loop = !!(mode & MB_DISPLAY_FLAG_LOOP);
switch (mode & MODE_MASK) {
case MB_DISPLAY_MODE_DEFAULT:
case MB_DISPLAY_MODE_SINGLE:
start_single(disp, duration);
break;
case MB_DISPLAY_MODE_SCROLL:
start_scroll(disp, duration);
break;
default:
__ASSERT(0, "Invalid display mode");
}
} }
void mb_display_stop(struct mb_display *disp) void mb_display_stop(struct mb_display *disp)
@ -219,16 +343,13 @@ void mb_display_stop(struct mb_display *disp)
reset_display(disp); reset_display(disp);
} }
void mb_display_char(struct mb_display *disp, char chr, int32_t duration) void mb_display_print(struct mb_display *disp, uint32_t mode,
{ int32_t duration, const char *fmt, ...)
mb_display_image(disp, get_font(chr), duration);
}
void mb_display_string(struct mb_display *disp, int32_t duration,
const char *fmt, ...)
{ {
va_list ap; va_list ap;
reset_display(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);
va_end(ap); va_end(ap);
@ -237,39 +358,23 @@ void mb_display_string(struct mb_display *disp, int32_t duration,
return; return;
} }
reset_display(disp); disp->str = disp->str_buf;
disp->text = 1;
disp->img_sep = 1;
disp->cur_img = 0;
disp->loop = !!(mode & MB_DISPLAY_FLAG_LOOP);
disp->str = &disp->str_buf[1]; switch (mode & MODE_MASK) {
disp->duration = duration; case MB_DISPLAY_MODE_DEFAULT:
disp->scroll = SCROLL_OFF; case MB_DISPLAY_MODE_SCROLL:
start_scroll(disp, duration);
start_image(disp, get_font(disp->str_buf[0])); break;
} case MB_DISPLAY_MODE_SINGLE:
start_single(disp, duration);
void mb_display_print(struct mb_display *disp, const char *fmt, ...) break;
{ default:
va_list ap; __ASSERT(0, "Invalid display mode");
va_start(ap, fmt);
vsnprintk(disp->str_buf, sizeof(disp->str_buf), fmt, ap);
va_end(ap);
if (disp->str_buf[0] == '\0') {
return;
} }
reset_display(disp);
if (disp->str_buf[1] == '\0') {
disp->str = NULL;
} else {
disp->str = disp->str_buf;
}
disp->scroll = SCROLL_START;
disp->duration = SCROLL_DURATION;
start_image(disp, get_font(disp->str[0]));
} }
struct mb_display *mb_display_get(void) struct mb_display *mb_display_get(void)

View file

@ -19,6 +19,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
#include <misc/util.h> #include <misc/util.h>
#ifdef __cplusplus #ifdef __cplusplus
@ -44,6 +45,27 @@ struct mb_image {
}; };
}; };
/**
* @brief Display mode.
*
* First 16 bits are reserved for modes, last 16 for flags.
*/
enum mb_display_mode {
/** Default mode ("single" for images, "scroll" for text). */
MB_DISPLAY_MODE_DEFAULT,
/** Display images sequentially, one at a time. */
MB_DISPLAY_MODE_SINGLE,
/** Display images by scrolling. */
MB_DISPLAY_MODE_SCROLL,
/* Display flags, i.e. modifiers to the chosen mode */
/** Loop back to the beginning when reaching the last image. */
MB_DISPLAY_FLAG_LOOP = BIT(16),
};
/** /**
* @def MB_IMAGE * @def MB_IMAGE
* @brief Generate an image object from a given array rows/columns. * @brief Generate an image object from a given array rows/columns.
@ -90,52 +112,41 @@ struct mb_display;
struct mb_display *mb_display_get(void); struct mb_display *mb_display_get(void);
/** /**
* @brief Display an image on the BBC micro:bit LED display. * @brief Display one or more images on the BBC micro:bit LED display.
* *
* @param disp Display object. * This function takes an array of one or more images and renders them
* @param img Bitmap of pixels. * sequentially on the micro:bit display. The call is asynchronous, i.e.
* @param duration Duration how long to show the image (in milliseconds). * the processing of the display happens in the background. If there is
* another image being displayed it will be canceled and the new one takes
* over.
*
* @param disp Display object.
* @param mode One of the MB_DISPLAY_MODE_* options.
* @param duration Duration how long to show each image (in milliseconds).
* @param img Array of image bitmaps (struct mb_image objects).
* @param img_count Number of images in 'img' array.
*/ */
void mb_display_image(struct mb_display *disp, const struct mb_image *img, void mb_display_image(struct mb_display *disp, uint32_t mode, int32_t duration,
int32_t duration); const struct mb_image *img, uint8_t img_count);
/**
* @brief Display a character on the BBC micro:bit LED display.
*
* @param disp Display object.
* @param chr Character to display.
* @param duration Duration how long to show the character (in milliseconds).
*/
void mb_display_char(struct mb_display *disp, char chr, int32_t duration);
/**
* @brief Display a string of characters on the BBC micro:bit LED display.
*
* This function takes a printf-style format string and outputs it one
* character at a time to the display. For scrolling-based string output
* see the mb_display_print() API.
*
* @param disp Display object.
* @param duration Duration how long to show each character (in milliseconds).
* @param fmt printf-style format string.
* @param ... Optional list of format arguments.
*/
__printf_like(3, 4) void mb_display_string(struct mb_display *disp,
int32_t duration,
const char *fmt, ...);
/** /**
* @brief Print a string of characters on the BBC micro:bit LED display. * @brief Print a string of characters on the BBC micro:bit LED display.
* *
* This function takes a printf-style format string and outputs it in a * This function takes a printf-style format string and outputs it in a
* scrolling fashion to the display. For character-by-character output * scrolling fashion to the display.
* instead of scrolling, see the mb_display_string() API. *
* The call is asynchronous, i.e. the processing of the display happens in
* the background. If there is another image or string being displayed it
* will be canceled and the new one takes over.
* *
* @param disp Display object. * @param disp Display object.
* @param mode One of the MB_DISPLAY_MODE_* options.
* @param duration Duration how long to show each character (in milliseconds).
* @param fmt printf-style format string * @param fmt printf-style format string
* @param ... Optional list of format arguments. * @param ... Optional list of format arguments.
*/ */
__printf_like(2, 3) void mb_display_print(struct mb_display *disp, __printf_like(4, 5) void mb_display_print(struct mb_display *disp,
uint32_t mode, int32_t duration,
const char *fmt, ...); const char *fmt, ...);
/** /**

View file

@ -23,5 +23,5 @@ Sample Output
============= =============
The sample app displays a countdown of the characters 9-0, iterates The sample app displays a countdown of the characters 9-0, iterates
through all pixels one-by-one, displays a smiley face, displays the text through all pixels one-by-one, displays a smiley face, some animations,
"Hello Zephyr!" by scrolling, and then stops. and finally the text "Hello Zephyr!" by scrolling.

View file

@ -18,14 +18,62 @@ static struct mb_image smiley = MB_IMAGE({ 0, 1, 0, 1, 0 },
{ 1, 0, 0, 0, 1 }, { 1, 0, 0, 0, 1 },
{ 0, 1, 1, 1, 0 }); { 0, 1, 1, 1, 0 });
static const struct mb_image scroll[] = {
MB_IMAGE({ 1, 0, 0, 0, 0 },
{ 1, 0, 0, 0, 1 },
{ 1, 0, 0, 1, 0 },
{ 1, 0, 1, 0, 0 },
{ 1, 1, 0, 0, 0 }),
MB_IMAGE({ 1, 0, 0, 0, 0 },
{ 0, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 0 },
{ 0, 0, 0, 1, 0 },
{ 0, 0, 0, 0, 1 }),
MB_IMAGE({ 0, 0, 0, 0, 1 },
{ 0, 0, 1, 0, 1 },
{ 0, 1, 0, 1, 1 },
{ 1, 0, 0, 0, 1 },
{ 0, 0, 0, 0, 1 }),
};
static const struct mb_image animation[] = {
MB_IMAGE({ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 1, 0, 0 },
{ 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0 }),
MB_IMAGE({ 0, 0, 0, 0, 0 },
{ 0, 1, 1, 1, 0 },
{ 0, 1, 1, 1, 0 },
{ 0, 1, 1, 1, 0 },
{ 0, 0, 0, 0, 0 }),
MB_IMAGE({ 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1 },
{ 1, 1, 0, 1, 1 },
{ 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1 }),
MB_IMAGE({ 1, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 1 },
{ 1, 1, 1, 1, 1 }),
};
void main(void) void main(void)
{ {
struct mb_display *disp = mb_display_get(); struct mb_display *disp = mb_display_get();
int x, y; int x, y;
/* Display countdown from '9' to '0' */ /* Note: the k_sleep() calls after mb_display_print() and
mb_display_string(disp, K_SECONDS(1), "9876543210"); * mb_display_image() are not normally needed since the APIs
* are used in an asynchronous manner. The k_sleep() calls
* are used here so the APIs can be sequentially demonstrated
* through this single main function.
*/
/* Display countdown from '9' to '0' */
mb_display_print(disp, MB_DISPLAY_MODE_SINGLE,
K_SECONDS(1), "9876543210");
k_sleep(K_SECONDS(11)); k_sleep(K_SECONDS(11));
/* Iterate through all pixels one-by-one */ /* Iterate through all pixels one-by-one */
@ -33,15 +81,28 @@ void main(void)
for (x = 0; x < 5; x++) { for (x = 0; x < 5; x++) {
struct mb_image pixel = {}; struct mb_image pixel = {};
pixel.row[y] = BIT(x); pixel.row[y] = BIT(x);
mb_display_image(disp, &pixel, K_MSEC(250)); mb_display_image(disp, MB_DISPLAY_MODE_SINGLE,
K_MSEC(250), &pixel, 1);
k_sleep(K_MSEC(300)); k_sleep(K_MSEC(300));
} }
} }
/* Show a smiley-face */ /* Show a smiley-face */
mb_display_image(disp, &smiley, K_SECONDS(2)); mb_display_image(disp, MB_DISPLAY_MODE_SINGLE, K_SECONDS(2),
&smiley, 1);
k_sleep(K_SECONDS(2)); k_sleep(K_SECONDS(2));
/* Show a short scrolling animation */
mb_display_image(disp, MB_DISPLAY_MODE_SCROLL, K_SECONDS(1),
scroll, ARRAY_SIZE(scroll));
k_sleep(K_SECONDS(1) * (ARRAY_SIZE(scroll) + 2));
/* Show a sequential animation */
mb_display_image(disp, MB_DISPLAY_MODE_DEFAULT | MB_DISPLAY_FLAG_LOOP,
K_MSEC(150), animation, ARRAY_SIZE(animation));
k_sleep(K_MSEC(150) * ARRAY_SIZE(animation) * 5);
/* Show some scrolling text ("Hello Zephyr!") */ /* Show some scrolling text ("Hello Zephyr!") */
mb_display_print(disp, "Hello Zephyr!"); mb_display_print(disp, MB_DISPLAY_MODE_DEFAULT | MB_DISPLAY_FLAG_LOOP,
K_MSEC(500), "Hello Zephyr!");
} }