From ae5cbc1d81f39dfa423d3fd879f7f627fd1091af Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 14 Aug 2018 07:06:12 +0000 Subject: [PATCH] applet.rgb_grabber: implement. --- software/glasgow/access/direct/arguments.py | 2 +- software/glasgow/applet/__init__.py | 1 + software/glasgow/applet/rgb_grabber.py | 161 ++++++++++++++++++++ 3 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 software/glasgow/applet/rgb_grabber.py diff --git a/software/glasgow/access/direct/arguments.py b/software/glasgow/access/direct/arguments.py index d6ac85082..220adbcb6 100644 --- a/software/glasgow/access/direct/arguments.py +++ b/software/glasgow/access/direct/arguments.py @@ -67,7 +67,7 @@ def _pin_set(self, width, arg): return numbers def _add_pin_set_argument(self, parser, name, width, default, required): - help = "bind the applet I/O lines {!r} to pins SET".format(self._applet_name, name) + help = "bind the applet I/O lines {!r} to pins SET".format(name) if default is not None: help += " (default: %(default)s)" diff --git a/software/glasgow/applet/__init__.py b/software/glasgow/applet/__init__.py index 4b88e5a8e..069cf1e83 100644 --- a/software/glasgow/applet/__init__.py +++ b/software/glasgow/applet/__init__.py @@ -129,6 +129,7 @@ def wrapper(self): # ------------------------------------------------------------------------------------------------- +from .rgb_grabber import RGBGrabberApplet from .hd44780 import HD44780Applet from .i2c_master import I2CMasterApplet from .i2c.bmp280 import I2CBMP280Applet diff --git a/software/glasgow/applet/rgb_grabber.py b/software/glasgow/applet/rgb_grabber.py new file mode 100644 index 000000000..61f593a02 --- /dev/null +++ b/software/glasgow/applet/rgb_grabber.py @@ -0,0 +1,161 @@ +import math +from migen import * +from migen.genlib.fsm import * +import logging +from migen.genlib.cdc import * + +from . import GlasgowApplet + + +class RGBGrabberSubtarget(Module): + def __init__(self, rows, columns, vblank, pads, in_fifo): + rx = Signal(5) + gx = Signal(5) + bx = Signal(5) + dck = Signal() + self.specials += [ + MultiReg(pads.r_t.i, rx), + MultiReg(pads.g_t.i, gx), + MultiReg(pads.b_t.i, bx), + MultiReg(pads.dck_t.i, dck) + ] + + dck_r = Signal() + stb = Signal() + self.sync += [ + dck_r.eq(dck), + stb.eq(~dck_r & dck) + ] + + we = Signal.like(in_fifo.we) + din = Signal.like(in_fifo.din) + ovf = Signal() + ovf_c = Signal() + self.comb += [ + in_fifo.we.eq(we & ~ovf), + in_fifo.din.eq(din), + ] + self.sync += \ + ovf.eq(~ovf_c & (ovf | (we & ~in_fifo.writable))) + + pixel = Signal(15) + self.sync += \ + If(stb, pixel.eq(Cat(rx, gx, bx))) + + frame = Signal(5, reset_less=True) + ovf_r = Signal() + row = Signal(max=rows) + col = Signal(max=columns) + self.submodules.fsm = ResetInserter()(FSM(reset_state="CAPTURE-ROW")) + self.fsm.act("CAPTURE-ROW", + If(stb, + If(row == 0, + ovf_r.eq(ovf), + ovf_c.eq(1), + NextValue(frame, frame + 1), + NextState("SKIP-FIRST-PIXEL") + ).Elif(row == rows, + NextValue(row, 0), + NextState("REPORT-FRAME") + ).Else( + NextState("REPORT-FRAME") + ) + ) + ) + self.fsm.act("SKIP-FIRST-PIXEL", + If(stb, + NextState("REPORT-FRAME") + ) + ) + self.fsm.act("REPORT-FRAME", + din.eq(0x80 | (ovf_r << 7) | (frame << 1) | (row >> 7)), + we.eq(1), + NextState("REPORT-ROW") + ) + self.fsm.act("REPORT-ROW", + din.eq(row & 0x7f), + we.eq(1), + NextState("REPORT-1") + ) + for (state, offset, nextstate) in ( + ("REPORT-1", 0, "REPORT-2"), + ("REPORT-2", 5, "REPORT-3"), + ("REPORT-3", 10, "CAPTURE-PIXEL") + ): + self.fsm.act(state, + din.eq((pixel >> offset) & 0x1f), + we.eq(1), + NextState(nextstate) + ) + self.fsm.act("CAPTURE-PIXEL", + If(stb, + If(col == columns - 1, + NextValue(col, 0), + NextValue(row, row + 1), + NextState("CAPTURE-ROW") + ).Else( + NextValue(col, col + 1), + NextState("REPORT-1") + ) + ) + ) + + vblank_cyc = math.ceil(vblank * 0.9 * 30e6) # reset at 90% vblank + timer = Signal(max=vblank_cyc) + self.sync += [ + If(dck, + If(timer != vblank_cyc, + timer.eq(timer + 1) + ) + ).Else( + timer.eq(0) + ) + ] + self.comb += [ + self.fsm.reset.eq(timer == vblank_cyc), + # sync.oe.eq(self.fsm.reset), + ] + + +class RGBGrabberApplet(GlasgowApplet, name="rgb-grabber"): + logger = logging.getLogger(__name__) + help = "grab images from RGB555 LCD bus" + description = """ + Streams screen contents from a color parallel RGB555 LCD, such as Sharp LQ035Q7DH06. + """ + + @classmethod + def add_build_arguments(cls, parser, access): + access.add_build_arguments(parser) + access.add_pin_set_argument(parser, "r", width=5) + access.add_pin_set_argument(parser, "g", width=5) + access.add_pin_set_argument(parser, "b", width=5) + access.add_pin_argument(parser, "dck") + parser.add_argument("--rows", type=int, + help="LCD row count") + parser.add_argument("--columns", type=int, + help="LCD column count") + parser.add_argument("--vblank", type=float, + help="vertical blanking interval, in us") + + def build(self, target, args): + self.mux_interface = iface = target.multiplexer.claim_interface(self, args) + target.submodules += RGBGrabberSubtarget( + rows=args.rows, + columns=args.columns, + vblank=args.vblank, + pads=iface.get_pads(args, pins=("dck",), pin_sets=("r", "g", "b")), + in_fifo=iface.get_in_fifo(depth=512 * 30, streaming=True), + ) + + async def run(self, device, args): + iface = await device.demultiplexer.claim_interface(self, self.mux_interface, args) + + for _ in range(200): + sync = 0 + while not (sync & 0x80): + sync = (await iface.read(1))[0] + frame = (sync & 0x3e) >> 1 + row = ((sync & 0x01) << 7) | (await iface.read(1))[0] + + print("frame {} row {}".format(frame, row))