qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: "Steffen Görtz" <contrib@steffen-goertz.de>
To: qemu-devel@nongnu.org
Cc: "Stefan Hajnoczi" <stefanha@gmail.com>,
	"Joel Stanley" <joel@jms.id.au>,
	"Jim Mussared" <jim@groklearning.com>,
	"Julia Suvorova" <jusual@mail.ru>,
	"Peter Maydell" <peter.maydell@linaro.org>,
	"Thomas Huth" <thuth@redhat.com>,
	"Steffen Görtz" <contrib@steffen-goertz.de>
Subject: [Qemu-devel] [PATCH 7/7] hw/display/led_matrix: Add LED matrix display device
Date: Mon,  6 Aug 2018 12:01:14 +0200	[thread overview]
Message-ID: <20180806100114.21410-8-contrib@steffen-goertz.de> (raw)
In-Reply-To: <20180806100114.21410-1-contrib@steffen-goertz.de>

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

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
-- 
2.18.0

  parent reply	other threads:[~2018-08-06 10:01 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-08-06 10:01 [Qemu-devel] [PATCH 0/7] arm: nRF51 Devices and Microbit Support Steffen Görtz
2018-08-06 10:01 ` [Qemu-devel] [PATCH 1/7] hw/misc/nrf51_rng: Add NRF51 random number generator peripheral Steffen Görtz
2018-08-06 14:00   ` Stefan Hajnoczi
2018-08-16 15:45   ` Peter Maydell
2018-08-06 10:01 ` [Qemu-devel] [PATCH 2/7] hw/nvram/nrf51_nvm: Add nRF51 non-volatile memories Steffen Görtz
2018-08-06 16:09   ` Stefan Hajnoczi
2018-08-08  9:58     ` Steffen Görtz
2018-08-16 16:03   ` Peter Maydell
2018-08-21  8:31     ` Steffen Görtz
2018-08-06 10:01 ` [Qemu-devel] [PATCH 3/7] tests: Add bbc:microbit / nRF51 test suite Steffen Görtz
2018-08-08  9:09   ` Stefan Hajnoczi
2018-08-08  9:46     ` Julia Suvorova
2018-08-09 16:16       ` Stefan Hajnoczi
2018-08-06 10:01 ` [Qemu-devel] [PATCH 4/7] hw/gpio/nrf51_gpio: Add nRF51 GPIO peripheral Steffen Görtz
2018-08-08  9:11   ` Stefan Hajnoczi
2018-08-16 16:08   ` Peter Maydell
2018-08-06 10:01 ` [Qemu-devel] [PATCH 5/7] tests/microbit-test: Add Tests for nRF51 GPIO Steffen Görtz
2018-08-09 16:14   ` Stefan Hajnoczi
2018-08-06 10:01 ` [Qemu-devel] [PATCH 6/7] hw/timer/nrf51_timer: Add nRF51 Timer peripheral Steffen Görtz
2018-08-09 16:45   ` Stefan Hajnoczi
2018-08-06 10:01 ` Steffen Görtz [this message]
2018-08-09 17:08   ` [Qemu-devel] [PATCH 7/7] hw/display/led_matrix: Add LED matrix display device Stefan Hajnoczi
2018-08-06 10:09 ` [Qemu-devel] [PATCH 0/7] arm: nRF51 Devices and Microbit Support Peter Maydell
2018-08-06 10:31   ` Steffen Görtz
2018-08-16 16:10   ` Peter Maydell

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180806100114.21410-8-contrib@steffen-goertz.de \
    --to=contrib@steffen-goertz.de \
    --cc=jim@groklearning.com \
    --cc=joel@jms.id.au \
    --cc=jusual@mail.ru \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@gmail.com \
    --cc=thuth@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).