diff mbox series

[7/7] hw/display/led_matrix: Add LED matrix display device

Message ID 20180806100114.21410-8-contrib@steffen-goertz.de
State New
Headers show
Series arm: nRF51 Devices and Microbit Support | expand

Commit Message

Steffen Görtz Aug. 6, 2018, 10:01 a.m. UTC
The LEDs are not individually connected to the output
pins of the microcontroller. Instead, the LEDs share pins
for rows and columns.
The pattern for a row or a column is only displayed for a short moment.
The slowness of the human eye results in a complete and
flicker-free image (persistence of vision). More information
can be found here: https://en.wikipedia.org/wiki/Multiplexed_display .

This device demultiplexes the dot-matrix pattern to a grayscale
image as it would be perceived by the human eye.
The number of rows, columns and the refresh period can be configured.

At the moment it is assumed that the LEDs are connected in forward
direction from rows to columns.

The demultiplexed LEDs are drawn to the canvas of a graphics console.
In the future it is planed to send QMP events with the updated
matrix image.

Signed-off-by: Steffen Görtz <contrib@steffen-goertz.de>
---
 hw/display/Makefile.objs        |   2 +
 hw/display/led_matrix.c         | 262 ++++++++++++++++++++++++++++++++
 include/hw/display/led_matrix.h |  38 +++++
 3 files changed, 302 insertions(+)
 create mode 100644 hw/display/led_matrix.c
 create mode 100644 include/hw/display/led_matrix.h

Comments

Stefan Hajnoczi Aug. 9, 2018, 5:08 p.m. UTC | #1
On Mon, Aug 6, 2018 at 11:01 AM, Steffen Görtz
<contrib@steffen-goertz.de> wrote:
> +static void led_timer_expire(void *opaque)
> +{
> +    LEDMatrixState *s = LED_MATRIX(opaque);
> +/*
> +    uint8_t divider = s->strobe_row ? s->nrows : s->ncols;
> +    int64_t max_on = (s->refresh_period * 1000) / divider;
> +*/

Please remove dead code.

> +
> +    update_on_times(s);
> +
> +    memcpy(s->led_frame_dc, s->led_working_dc,
> +           sizeof(int64_t) * s->nrows * s->ncols);
> +    memset(s->led_working_dc, 0x00, sizeof(int64_t) * s->nrows * s->ncols);
> +
> +    timer_mod(&s->timer,
> +            qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + s->refresh_period);
> +    s->redraw = true;
> +}

Have you considered an approach based on the access pattern rather
than a timer (sampling)?

Sampling-based approaches are more CPU intensive and may result in
artifacts if the guest doesn't update pins at exactly the refresh
rate.

The access pattern approach assumes that the rows/columns are scanned
in a certain way.  It uses the guest's pin accesses as the event for
checking the matrix state.  It's more efficient, doesn't suffer from
sampling artifacts, but requires knowledge of how rows/columns are
scanned.

> +static void draw_pixel(DisplaySurface *ds, int x, int y, uint32_t color)
> +{
> +    int bpp;
> +    uint8_t *d;
> +    bpp = (surface_bits_per_pixel(ds) + 7) >> 3;
> +    d = surface_data(ds) + surface_stride(ds) * y + bpp * x;
> +    switch (bpp) {
> +    case 1:
> +        *((uint8_t *) d) = color;
> +        d++;
> +        break;
> +    case 2:
> +        *((uint16_t *) d) = color;
> +        d += 2;
> +        break;
> +    case 4:
> +        *((uint32_t *) d) = color;
> +        d += 4;
> +        break;
> +    }

There is no need to increment d.

> +    for (x = 0; x < s->nrows ; x++) {
> +        for (y = 0; y < s->ncols; y++) {
> +            idx = x * s->ncols + y;

I'm confused by the naming here.  "rows" is height/vertical/y.
"columns" is width/horizontal/x.  Why is "x" used for vertical and "y"
for horizontal?

> +            red = (s->led_frame_dc[idx] * 0xFF) / (s->refresh_period * 1000);
> +            color_led = colorfunc(red, 0x00, 0x00);
> +
> +            draw_box(surface, idx * 10, 0, 5, 10, color_led);

Are the LEDs layed out horizontally?  The y coordinate is hardcoded to
0.  I thought this would be a 2D LED matrix.

> +static void led_matrix_unrealize(DeviceState *dev, Error **errp)
> +{
> +    LEDMatrixState *s = LED_MATRIX(dev);
> +
> +    g_free(s->led_working_dc);
> +    s->led_working_dc = NULL;

led_frame_dc is leaked.

graphic_console_close() is missing.

> diff --git a/include/hw/display/led_matrix.h b/include/hw/display/led_matrix.h
> new file mode 100644
> index 0000000000..4a43b69f5b
> --- /dev/null
> +++ b/include/hw/display/led_matrix.h
> @@ -0,0 +1,38 @@
> +/*
> + * LED Matrix Demultiplexer
> + *
> + * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
> + *
> + * This code is licensed under the GPL version 2 or later.  See
> + * the COPYING file in the top-level directory.
> + */
> +#ifndef LED_MATRIX_H
> +#define LED_MATRIX_H
> +
> +#include "hw/sysbus.h"
> +#include "qemu/timer.h"
> +#define TYPE_LED_MATRIX "led_matrix"
> +#define LED_MATRIX(obj) OBJECT_CHECK(LEDMatrixState, (obj), TYPE_LED_MATRIX)
> +
> +typedef struct LEDMatrixState {

There is too little documentation here for a generic device that could
be reused by other boards.  Please describe the purpose and
functionality of this device.
diff mbox series

Patch

diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
index fb8408c6d0..c423f1eb1a 100644
--- a/hw/display/Makefile.objs
+++ b/hw/display/Makefile.objs
@@ -23,6 +23,8 @@  common-obj-$(CONFIG_FRAMEBUFFER) += framebuffer.o
 common-obj-$(CONFIG_MILKYMIST) += milkymist-vgafb.o
 common-obj-$(CONFIG_ZAURUS) += tc6393xb.o
 
+common-obj-$(CONFIG_NRF51_SOC) += led_matrix.o
+
 common-obj-$(CONFIG_MILKYMIST_TMU2) += milkymist-tmu2.o
 milkymist-tmu2.o-cflags := $(X11_CFLAGS)
 milkymist-tmu2.o-libs := $(X11_LIBS)
diff --git a/hw/display/led_matrix.c b/hw/display/led_matrix.c
new file mode 100644
index 0000000000..34c3ad9bde
--- /dev/null
+++ b/hw/display/led_matrix.c
@@ -0,0 +1,262 @@ 
+/*
+ * LED Matrix Demultiplexer
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "ui/console.h"
+#include "hw/display/led_matrix.h"
+#include "ui/pixel_ops.h"
+
+static bool led_was_on(LEDMatrixState *s, size_t x, size_t y)
+{
+    /* TODO implying Direction is ROW |-> COL */
+    /* TODO add direction flag and generalize */
+    bool row_level = extract64(s->row, x, 1);
+    bool col_level = extract64(s->col, y, 1);
+
+    return row_level && !col_level;
+}
+
+static void update_on_times(LEDMatrixState *s)
+{
+    size_t x;
+    int64_t now = qemu_clock_get_us(QEMU_CLOCK_VIRTUAL);
+    int64_t diff = now - s->timestamp;
+    s->timestamp = now;
+
+    for (x = 0; x < s->nrows ; x++) {
+        for (size_t y = 0; y < s->ncols; y++) {
+            if (led_was_on(s, x, y)) {
+                s->led_working_dc[x * s->ncols + y] += diff;
+            }
+        }
+    }
+}
+
+static void led_timer_expire(void *opaque)
+{
+    LEDMatrixState *s = LED_MATRIX(opaque);
+/*
+    uint8_t divider = s->strobe_row ? s->nrows : s->ncols;
+    int64_t max_on = (s->refresh_period * 1000) / divider;
+*/
+
+    update_on_times(s);
+
+    memcpy(s->led_frame_dc, s->led_working_dc,
+           sizeof(int64_t) * s->nrows * s->ncols);
+    memset(s->led_working_dc, 0x00, sizeof(int64_t) * s->nrows * s->ncols);
+
+    timer_mod(&s->timer,
+            qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + s->refresh_period);
+    s->redraw = true;
+}
+
+static void set_row(void *opaque, int line, int value)
+{
+    LEDMatrixState *s = LED_MATRIX(opaque);
+
+    update_on_times(s);
+
+    s->row = deposit32(s->row, line, 1, value > 0);
+}
+
+static void set_column(void *opaque, int line, int value)
+{
+    LEDMatrixState *s = LED_MATRIX(opaque);
+
+    update_on_times(s);
+
+    s->col = deposit32(s->col, line, 1, value > 0);
+}
+
+static void draw_pixel(DisplaySurface *ds, int x, int y, uint32_t color)
+{
+    int bpp;
+    uint8_t *d;
+    bpp = (surface_bits_per_pixel(ds) + 7) >> 3;
+    d = surface_data(ds) + surface_stride(ds) * y + bpp * x;
+    switch (bpp) {
+    case 1:
+        *((uint8_t *) d) = color;
+        d++;
+        break;
+    case 2:
+        *((uint16_t *) d) = color;
+        d += 2;
+        break;
+    case 4:
+        *((uint32_t *) d) = color;
+        d += 4;
+        break;
+    }
+}
+
+static void draw_box(DisplaySurface *ds,
+                     int x0, int y0, int w, int h, uint32_t color)
+{
+    int x, y;
+    for (x = 0; x < w; x++) {
+        for (y = 0; y < h; y++) {
+            draw_pixel(ds, x0 + x, y0 + y, color);
+        }
+    }
+}
+
+typedef unsigned int (*color_func)(unsigned int, unsigned int, unsigned int);
+
+static void led_invalidate_display(void *opaque)
+{
+    LEDMatrixState *s = LED_MATRIX(opaque);
+    s->redraw = true;
+}
+
+static void led_update_display(void *opaque)
+{
+    LEDMatrixState *s = LED_MATRIX(opaque);
+    DisplaySurface *surface = qemu_console_surface(s->con);
+    color_func colorfunc;
+    uint32_t color_led;
+    int bpp;
+    uint8_t *d1;
+    uint8_t red;
+    size_t x, y, idx;
+
+    if (!s->redraw) {
+        return;
+    }
+
+    /* clear screen */
+    bpp = (surface_bits_per_pixel(surface) + 7) >> 3;
+    d1 = surface_data(surface);
+    for (y = 0; y < surface_height(surface); y++) {
+        memset(d1, 0x00, surface_width(surface) * bpp);
+        d1 += surface_stride(surface);
+    }
+
+    /* set colors according to bpp */
+    switch (surface_bits_per_pixel(surface)) {
+    case 8:
+        colorfunc = rgb_to_pixel8;
+        break;
+    case 15:
+        colorfunc = rgb_to_pixel15;
+        break;
+    case 16:
+        colorfunc = rgb_to_pixel16;
+        break;
+    case 24:
+        colorfunc = rgb_to_pixel24;
+        break;
+    case 32:
+        colorfunc = rgb_to_pixel32;
+        break;
+    default:
+        return;
+    }
+
+    for (x = 0; x < s->nrows ; x++) {
+        for (y = 0; y < s->ncols; y++) {
+            idx = x * s->ncols + y;
+            red = (s->led_frame_dc[idx] * 0xFF) / (s->refresh_period * 1000);
+            color_led = colorfunc(red, 0x00, 0x00);
+
+            draw_box(surface, idx * 10, 0, 5, 10, color_led);
+        }
+    }
+
+    s->redraw = 0;
+    dpy_gfx_update(s->con, 0, 0, 500, 500);
+}
+
+static const GraphicHwOps graphic_ops = {
+    .invalidate  = led_invalidate_display,
+    .gfx_update  = led_update_display,
+};
+
+static void led_matrix_init(Object *obj)
+{
+    LEDMatrixState *s = LED_MATRIX(obj);
+
+    timer_init_ms(&s->timer, QEMU_CLOCK_VIRTUAL, led_timer_expire, s);
+}
+
+static void led_matrix_realize(DeviceState *dev, Error **errp)
+{
+    LEDMatrixState *s = LED_MATRIX(dev);
+    if (!s->nrows || (s->nrows > 64)) {
+        error_setg(errp, "rows not set or larger than 64");
+        return;
+    }
+
+    if (!s->ncols || (s->ncols > 64)) {
+        error_setg(errp, "cols not set or larger than 64");
+        return;
+    }
+
+    s->led_working_dc = g_malloc0_n(s->ncols * s->nrows, sizeof(int64_t));
+    s->led_frame_dc = g_malloc0_n(s->ncols * s->nrows, sizeof(int64_t));
+
+    qdev_init_gpio_in_named(dev, set_row, "row", s->nrows);
+    qdev_init_gpio_in_named(dev, set_column, "col", s->ncols);
+
+    s->con = graphic_console_init(NULL, 0, &graphic_ops, s);
+    qemu_console_resize(s->con, 500, 500);
+}
+
+static void led_matrix_reset(DeviceState *dev)
+{
+    LEDMatrixState *s = LED_MATRIX(dev);
+
+    timer_mod(&s->timer,
+            qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + s->refresh_period);
+}
+
+static void led_matrix_unrealize(DeviceState *dev, Error **errp)
+{
+    LEDMatrixState *s = LED_MATRIX(dev);
+
+    g_free(s->led_working_dc);
+    s->led_working_dc = NULL;
+}
+
+static Property led_matrix_properties[] = {
+    DEFINE_PROP_UINT32("refresh_period", LEDMatrixState, refresh_period, 500),
+    DEFINE_PROP_UINT8("rows", LEDMatrixState, nrows, 0),
+    DEFINE_PROP_UINT8("cols", LEDMatrixState, ncols, 0),
+    DEFINE_PROP_BOOL("strobe_row", LEDMatrixState, strobe_row, true),
+    /* TODO Save/restore state of led_state matrix */
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void led_matrix_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->props = led_matrix_properties;
+    dc->realize = led_matrix_realize;
+    dc->reset = led_matrix_reset;
+    dc->unrealize = led_matrix_unrealize;
+}
+
+static const TypeInfo led_matrix_info = {
+    .name = TYPE_LED_MATRIX,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(LEDMatrixState),
+    .instance_init = led_matrix_init,
+    .class_init = led_matrix_class_init
+};
+
+static void led_matrix_register_types(void)
+{
+    type_register_static(&led_matrix_info);
+}
+
+type_init(led_matrix_register_types)
diff --git a/include/hw/display/led_matrix.h b/include/hw/display/led_matrix.h
new file mode 100644
index 0000000000..4a43b69f5b
--- /dev/null
+++ b/include/hw/display/led_matrix.h
@@ -0,0 +1,38 @@ 
+/*
+ * LED Matrix Demultiplexer
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ */
+#ifndef LED_MATRIX_H
+#define LED_MATRIX_H
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#define TYPE_LED_MATRIX "led_matrix"
+#define LED_MATRIX(obj) OBJECT_CHECK(LEDMatrixState, (obj), TYPE_LED_MATRIX)
+
+typedef struct LEDMatrixState {
+    SysBusDevice parent_obj;
+
+    QemuConsole *con;
+    bool redraw;
+
+    uint32_t refresh_period; /* refresh period in ms */
+    uint8_t nrows;
+    uint8_t ncols;
+    bool strobe_row;
+
+    QEMUTimer timer;
+    int64_t timestamp;
+
+    uint64_t row;
+    uint64_t col;
+    int64_t *led_working_dc; /* Current LED duty cycle acquisition */
+    int64_t *led_frame_dc; /* Last complete LED duty cycle acquisition */
+} LEDMatrixState;
+
+
+#endif