412 lines
8.9 KiB
C
412 lines
8.9 KiB
C
/*
|
|
* Copyright (c) 2017 Intel Corporation
|
|
* Copyright (c) 2021, Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/*
|
|
* This tool uses display controller driver API and requires
|
|
* a suitable LED matrix controller driver.
|
|
*/
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/init.h>
|
|
#include <string.h>
|
|
#include <zephyr/sys/printk.h>
|
|
|
|
#include <zephyr/display/mb_display.h>
|
|
#include <zephyr/drivers/display.h>
|
|
|
|
#include "mb_font.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(mb_disp, CONFIG_DISPLAY_LOG_LEVEL);
|
|
|
|
#define MODE_MASK BIT_MASK(16)
|
|
|
|
#define SCROLL_OFF 0
|
|
#define SCROLL_START 1
|
|
#define SCROLL_DEFAULT_DURATION_MS 80
|
|
|
|
#define MB_DISP_XRES 5
|
|
#define MB_DISP_YRES 5
|
|
|
|
struct mb_display {
|
|
const struct device *lm_dev; /* LED matrix display device */
|
|
|
|
struct k_work_delayable dwork; /* Delayable work item */
|
|
|
|
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 */
|
|
msb:1; /* MSB represents the first pixel */
|
|
|
|
int32_t duration; /* Duration for each shown image */
|
|
|
|
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];
|
|
};
|
|
|
|
static inline const struct mb_image *get_font(char ch)
|
|
{
|
|
if (ch < MB_FONT_START || ch > MB_FONT_END) {
|
|
return &mb_font[' ' - MB_FONT_START];
|
|
}
|
|
|
|
return &mb_font[ch - MB_FONT_START];
|
|
}
|
|
|
|
static ALWAYS_INLINE uint8_t flip_pixels(uint8_t b)
|
|
{
|
|
b = (b & 0xf0) >> 4 | (b & 0x0f) << 4;
|
|
b = (b & 0xcc) >> 2 | (b & 0x33) << 2;
|
|
b = (b & 0xaa) >> 1 | (b & 0x55) << 1;
|
|
|
|
return b;
|
|
}
|
|
|
|
static int update_content(struct mb_display *disp, const struct mb_image *img)
|
|
{
|
|
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]);
|
|
}
|
|
|
|
ret = display_write(disp->lm_dev, 0, 0, &buf_desc, &tmp_img);
|
|
} else {
|
|
ret = display_write(disp->lm_dev, 0, 0, &buf_desc, img);
|
|
}
|
|
|
|
if (ret < 0) {
|
|
LOG_ERR("Write to display controller failed");
|
|
return ret;
|
|
}
|
|
|
|
LOG_DBG("Image duration %d", disp->duration);
|
|
if (disp->duration != SYS_FOREVER_MS) {
|
|
k_work_reschedule(&disp->dwork, K_MSEC(disp->duration));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int start_image(struct mb_display *disp, const struct mb_image *img)
|
|
{
|
|
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->cur_img = 0U;
|
|
disp->img = NULL;
|
|
disp->img_count = 0U;
|
|
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)
|
|
{
|
|
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 MB_DISP_XRES + disp->img_sep;
|
|
}
|
|
|
|
static int update_scroll(struct mb_display *disp)
|
|
{
|
|
if (disp->scroll < scroll_steps(disp)) {
|
|
struct mb_image img;
|
|
|
|
for (int i = 0; i < MB_DISP_XRES; i++) {
|
|
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] << (scroll_steps(disp) -
|
|
disp->scroll)));
|
|
}
|
|
|
|
disp->scroll++;
|
|
return update_content(disp, &img);
|
|
} else {
|
|
if (disp->first) {
|
|
disp->first = 0U;
|
|
} else {
|
|
disp->cur_img++;
|
|
}
|
|
|
|
if (last_frame(disp)) {
|
|
if (!disp->loop) {
|
|
return reset_display(disp);
|
|
}
|
|
|
|
disp->cur_img = 0U;
|
|
disp->first = 1U;
|
|
}
|
|
|
|
disp->scroll = SCROLL_START;
|
|
return update_content(disp, current_img(disp));
|
|
}
|
|
}
|
|
|
|
static int update_image(struct mb_display *disp)
|
|
{
|
|
disp->cur_img++;
|
|
|
|
if (last_frame(disp)) {
|
|
if (!disp->loop) {
|
|
return reset_display(disp);
|
|
}
|
|
|
|
disp->cur_img = 0U;
|
|
}
|
|
|
|
return update_content(disp, current_img(disp));
|
|
}
|
|
|
|
static void update_display_work(struct k_work *work)
|
|
{
|
|
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
|
struct mb_display *disp = CONTAINER_OF(dwork, struct mb_display, dwork);
|
|
int ret;
|
|
|
|
if (disp->scroll) {
|
|
ret = update_scroll(disp);
|
|
} else {
|
|
ret = update_image(disp);
|
|
}
|
|
|
|
__ASSERT(ret == 0, "Failed to update display");
|
|
}
|
|
|
|
static int start_scroll(struct mb_display *disp, int32_t duration)
|
|
{
|
|
/* Divide total duration by number of scrolling steps */
|
|
if (duration) {
|
|
disp->duration = duration / scroll_steps(disp);
|
|
} else {
|
|
disp->duration = SCROLL_DEFAULT_DURATION_MS;
|
|
}
|
|
|
|
disp->scroll = SCROLL_START;
|
|
disp->first = 1U;
|
|
disp->cur_img = 0U;
|
|
return start_image(disp, get_font(' '));
|
|
}
|
|
|
|
static int start_single(struct mb_display *disp, int32_t duration)
|
|
{
|
|
disp->duration = duration;
|
|
|
|
if (disp->text) {
|
|
return start_image(disp, get_font(disp->str[0]));
|
|
} else {
|
|
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,
|
|
const struct mb_image *img, uint8_t img_count)
|
|
{
|
|
int ret;
|
|
|
|
mb_display_stop(disp);
|
|
|
|
__ASSERT(img && img_count > 0, "Invalid parameters");
|
|
|
|
disp->text = 0U;
|
|
disp->img_count = img_count;
|
|
disp->img = img;
|
|
disp->img_sep = 0U;
|
|
disp->cur_img = 0U;
|
|
disp->loop = !!(mode & MB_DISPLAY_FLAG_LOOP);
|
|
|
|
switch (mode & MODE_MASK) {
|
|
case MB_DISPLAY_MODE_DEFAULT:
|
|
case MB_DISPLAY_MODE_SINGLE:
|
|
ret = start_single(disp, duration);
|
|
__ASSERT(ret == 0, "Failed to start single mode");
|
|
break;
|
|
case MB_DISPLAY_MODE_SCROLL:
|
|
ret = start_scroll(disp, duration);
|
|
__ASSERT(ret == 0, "Failed to start scroll mode");
|
|
break;
|
|
default:
|
|
__ASSERT(0, "Invalid display mode");
|
|
}
|
|
}
|
|
|
|
void mb_display_print(struct mb_display *disp, uint32_t mode,
|
|
int32_t duration, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int ret;
|
|
|
|
mb_display_stop(disp);
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintk(disp->str_buf, sizeof(disp->str_buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (disp->str_buf[0] == '\0') {
|
|
return;
|
|
}
|
|
|
|
disp->str = disp->str_buf;
|
|
disp->text = 1U;
|
|
disp->img_sep = 1U;
|
|
disp->cur_img = 0U;
|
|
disp->loop = !!(mode & MB_DISPLAY_FLAG_LOOP);
|
|
|
|
switch (mode & MODE_MASK) {
|
|
case MB_DISPLAY_MODE_DEFAULT:
|
|
case MB_DISPLAY_MODE_SCROLL:
|
|
ret = start_scroll(disp, duration);
|
|
__ASSERT(ret == 0, "Failed to start scroll mode");
|
|
break;
|
|
case MB_DISPLAY_MODE_SINGLE:
|
|
ret = start_single(disp, duration);
|
|
__ASSERT(ret == 0, "Failed to start single mode");
|
|
break;
|
|
default:
|
|
__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)
|
|
{
|
|
return &display;
|
|
}
|
|
|
|
static int mb_display_init_on_boot(void)
|
|
{
|
|
|
|
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;
|
|
}
|
|
|
|
return mb_display_init(&display);
|
|
}
|
|
|
|
SYS_INIT(mb_display_init_on_boot, APPLICATION, CONFIG_DISPLAY_INIT_PRIORITY);
|