From d7a189d2ec903c65bba5ba6d2eac6a267fa3c3d1 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Wed, 5 Apr 2017 18:36:36 +0300 Subject: [PATCH] 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 --- drivers/display/Kconfig | 12 +- drivers/display/mb_display.c | 249 +++++++++++++++------ include/display/mb_display.h | 79 ++++--- samples/boards/microbit/display/README.rst | 4 +- samples/boards/microbit/display/src/main.c | 71 +++++- 5 files changed, 291 insertions(+), 124 deletions(-) diff --git a/drivers/display/Kconfig b/drivers/display/Kconfig index 380d3bc950c..54bcf4fd104 100644 --- a/drivers/display/Kconfig +++ b/drivers/display/Kconfig @@ -29,21 +29,11 @@ config MICROBIT_DISPLAY_PIN_GRANULARITY config MICROBIT_DISPLAY_STR_MAX int "Maximum length of strings that can be shown on the display" - range 3 1024 + range 3 255 default 40 help This value specifies the maximum length of strings that can be displayed using the mb_display_string() and mb_display_print() 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 diff --git a/drivers/display/mb_display.c b/drivers/display/mb_display.c index 3c71cee40cd..cf5e8c64e75 100644 --- a/drivers/display/mb_display.c +++ b/drivers/display/mb_display.c @@ -23,21 +23,30 @@ #include "mb_font.h" +#define MODE_MASK BIT_MASK(16) + #define DISPLAY_ROWS 3 #define DISPLAY_COLS 9 #define SCROLL_OFF 0 #define SCROLL_START 1 -/* Time between scroll shifts */ -#define SCROLL_DURATION K_MSEC(CONFIG_MICROBIT_DISPLAY_SCROLL_STEP) +#define SCROLL_DEFAULT_DURATION K_MSEC(80) 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 */ uint8_t cur; /* Currently rendered row */ @@ -45,7 +54,10 @@ struct mb_display { int64_t expiry; /* When to stop showing current 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 */ 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) { - if (disp->scroll < 6) { + if (disp->scroll < scroll_steps(disp)) { struct mb_image img; int i; for (i = 0; i < 5; i++) { - const struct mb_image *i1 = get_font(disp->str[0]); - const struct mb_image *i2; - - if (disp->str[0]) { - i2 = get_font(disp->str[1]); - } else { - i2 = get_font(' '); - } + const struct mb_image *i1 = current_img(disp); + const struct mb_image *i2 = next_img(disp); img.row[i] = ((i1->row[i] >> disp->scroll) | - (i2->row[i] << (6 - disp->scroll))); + (i2->row[i] << (scroll_steps(disp) - + disp->scroll))); } disp->scroll++; start_image(disp, &img); } else { - if (!disp->str[1]) { - disp->scroll = SCROLL_OFF; - disp->str = NULL; + if (disp->first) { + disp->first = 0; + } 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; } - disp->str++; - disp->scroll = SCROLL_START; - start_image(disp, get_font(disp->str[0])); + disp->cur_img = 0; } + + start_image(disp, current_img(disp)); } 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) { if (disp->scroll) { update_scroll(disp); - } else if (disp->str && disp->str[0]) { - start_image(disp, get_font(disp->str[0])); - disp->str++; } 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), }; -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_OFF; + disp->scroll = SCROLL_START; + 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, - int32_t duration) +static void start_single(struct mb_display *disp, 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); - disp->duration = duration; - start_image(disp, img); + __ASSERT(img && img_count > 0, "Invalid parameters"); + + 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) @@ -219,16 +343,13 @@ void mb_display_stop(struct mb_display *disp) reset_display(disp); } -void mb_display_char(struct mb_display *disp, char chr, int32_t duration) -{ - mb_display_image(disp, get_font(chr), duration); -} - -void mb_display_string(struct mb_display *disp, int32_t duration, - const char *fmt, ...) +void mb_display_print(struct mb_display *disp, uint32_t mode, + int32_t duration, const char *fmt, ...) { va_list ap; + reset_display(disp); + va_start(ap, fmt); vsnprintk(disp->str_buf, sizeof(disp->str_buf), fmt, ap); va_end(ap); @@ -237,39 +358,23 @@ void mb_display_string(struct mb_display *disp, int32_t duration, 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]; - disp->duration = duration; - disp->scroll = SCROLL_OFF; - - start_image(disp, get_font(disp->str_buf[0])); -} - -void mb_display_print(struct mb_display *disp, const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vsnprintk(disp->str_buf, sizeof(disp->str_buf), fmt, ap); - va_end(ap); - - if (disp->str_buf[0] == '\0') { - return; + switch (mode & MODE_MASK) { + case MB_DISPLAY_MODE_DEFAULT: + case MB_DISPLAY_MODE_SCROLL: + start_scroll(disp, duration); + break; + case MB_DISPLAY_MODE_SINGLE: + start_single(disp, duration); + break; + default: + __ASSERT(0, "Invalid display mode"); } - - 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) diff --git a/include/display/mb_display.h b/include/display/mb_display.h index 5a0cdd25aa1..186bfda8254 100644 --- a/include/display/mb_display.h +++ b/include/display/mb_display.h @@ -19,6 +19,7 @@ #include #include +#include #include #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 * @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); /** - * @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. - * @param img Bitmap of pixels. - * @param duration Duration how long to show the image (in milliseconds). + * This function takes an array of one or more images and renders them + * sequentially on the micro:bit display. The call is asynchronous, i.e. + * 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, - int32_t duration); - -/** - * @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, ...); +void mb_display_image(struct mb_display *disp, uint32_t mode, int32_t duration, + const struct mb_image *img, uint8_t img_count); /** * @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 - * scrolling fashion to the display. For character-by-character output - * instead of scrolling, see the mb_display_string() API. + * scrolling fashion to the display. + * + * 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 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 ... 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, ...); /** diff --git a/samples/boards/microbit/display/README.rst b/samples/boards/microbit/display/README.rst index 654137bdc49..a40dc4b984c 100644 --- a/samples/boards/microbit/display/README.rst +++ b/samples/boards/microbit/display/README.rst @@ -23,5 +23,5 @@ Sample Output ============= 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 -"Hello Zephyr!" by scrolling, and then stops. +through all pixels one-by-one, displays a smiley face, some animations, +and finally the text "Hello Zephyr!" by scrolling. diff --git a/samples/boards/microbit/display/src/main.c b/samples/boards/microbit/display/src/main.c index d66fe21e839..9121cbc22ae 100644 --- a/samples/boards/microbit/display/src/main.c +++ b/samples/boards/microbit/display/src/main.c @@ -18,14 +18,62 @@ static struct mb_image smiley = MB_IMAGE({ 0, 1, 0, 1, 0 }, { 1, 0, 0, 0, 1 }, { 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) { struct mb_display *disp = mb_display_get(); int x, y; - /* Display countdown from '9' to '0' */ - mb_display_string(disp, K_SECONDS(1), "9876543210"); + /* Note: the k_sleep() calls after mb_display_print() and + * 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)); /* Iterate through all pixels one-by-one */ @@ -33,15 +81,28 @@ void main(void) for (x = 0; x < 5; x++) { struct mb_image pixel = {}; 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)); } } /* 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)); + /* 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!") */ - mb_display_print(disp, "Hello Zephyr!"); + mb_display_print(disp, MB_DISPLAY_MODE_DEFAULT | MB_DISPLAY_FLAG_LOOP, + K_MSEC(500), "Hello Zephyr!"); }