aboutsummaryrefslogtreecommitdiffstats
path: root/klippy/extras/display/st7920.py
diff options
context:
space:
mode:
authorKevin O'Connor <kevin@koconnor.net>2018-06-27 13:10:18 -0400
committerKevin O'Connor <kevin@koconnor.net>2018-06-27 13:18:03 -0400
commite907253dba4ead48386a03b0573c048813310f1a (patch)
tree92200feb6739654192acba8b8ce40bf656dbab13 /klippy/extras/display/st7920.py
parentf85b43a789609236140ec89c5e5ee804496a6661 (diff)
downloadkutter-e907253dba4ead48386a03b0573c048813310f1a.tar.gz
kutter-e907253dba4ead48386a03b0573c048813310f1a.tar.xz
kutter-e907253dba4ead48386a03b0573c048813310f1a.zip
display: Move st7920 code to its own module
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
Diffstat (limited to 'klippy/extras/display/st7920.py')
-rw-r--r--klippy/extras/display/st7920.py127
1 files changed, 127 insertions, 0 deletions
diff --git a/klippy/extras/display/st7920.py b/klippy/extras/display/st7920.py
new file mode 100644
index 00000000..eebc3428
--- /dev/null
+++ b/klippy/extras/display/st7920.py
@@ -0,0 +1,127 @@
+# Support for ST7920 (128x64 graphics) LCD displays
+#
+# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
+#
+# This file may be distributed under the terms of the GNU GPLv3 license.
+import logging
+
+BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
+
+# Spec says 72us, but faster is possible in practice
+ST7920_CMD_DELAY = .000020
+ST7920_SYNC_DELAY = .000045
+
+class ST7920:
+ char_right_arrow = '\x1a'
+ def __init__(self, config):
+ printer = config.get_printer()
+ # pin config
+ ppins = printer.lookup_object('pins')
+ pins = [ppins.lookup_pin('digital_out', config.get(name + '_pin'))
+ for name in ['cs', 'sclk', 'sid']]
+ mcu = None
+ for pin_params in pins:
+ if mcu is not None and pin_params['chip'] != mcu:
+ raise ppins.error("st7920 all pins must be on same mcu")
+ mcu = pin_params['chip']
+ if pin_params['invert']:
+ raise ppins.error("st7920 can not invert pin")
+ self.pins = [pin_params['pin'] for pin_params in pins]
+ self.mcu = mcu
+ self.oid = self.mcu.create_oid()
+ self.mcu.add_config_object(self)
+ self.send_data_cmd = self.send_cmds_cmd = None
+ self.is_extended = False
+ # framebuffers
+ self.text_framebuffer = (bytearray(' '*64), bytearray('~'*64), 0x80)
+ self.glyph_framebuffer = (bytearray(128), bytearray('~'*128), 0x40)
+ self.graphics_framebuffers = [(bytearray(32), bytearray('~'*32), i)
+ for i in range(32)]
+ self.framebuffers = ([self.text_framebuffer, self.glyph_framebuffer]
+ + self.graphics_framebuffers)
+ def build_config(self):
+ self.mcu.add_config_cmd(
+ "config_st7920 oid=%u cs_pin=%s sclk_pin=%s sid_pin=%s"
+ " sync_delay_ticks=%d cmd_delay_ticks=%d" % (
+ self.oid, self.pins[0], self.pins[1], self.pins[2],
+ self.mcu.seconds_to_clock(ST7920_SYNC_DELAY),
+ self.mcu.seconds_to_clock(ST7920_CMD_DELAY)))
+ cmd_queue = self.mcu.alloc_command_queue()
+ self.send_cmds_cmd = self.mcu.lookup_command(
+ "st7920_send_cmds oid=%c cmds=%*s", cq=cmd_queue)
+ self.send_data_cmd = self.mcu.lookup_command(
+ "st7920_send_data oid=%c data=%*s", cq=cmd_queue)
+ def send(self, cmds, is_data=False, is_extended=False):
+ cmd_type = self.send_cmds_cmd
+ if is_data:
+ cmd_type = self.send_data_cmd
+ elif self.is_extended != is_extended:
+ add_cmd = 0x22
+ if is_extended:
+ add_cmd = 0x26
+ cmds = [add_cmd] + cmds
+ self.is_extended = is_extended
+ cmd_type.send([self.oid, cmds], reqclock=BACKGROUND_PRIORITY_CLOCK)
+ #logging.debug("st7920 %d %s", is_data, repr(cmds))
+ def flush(self):
+ # Find all differences in the framebuffers and send them to the chip
+ for new_data, old_data, fb_id in self.framebuffers:
+ if new_data == old_data:
+ continue
+ # Find the position of all changed bytes in this framebuffer
+ diffs = [[i, 1] for i, (nd, od) in enumerate(zip(new_data, old_data))
+ if nd != od]
+ # Batch together changes that are close to each other
+ for i in range(len(diffs)-2, -1, -1):
+ pos, count = diffs[i]
+ nextpos, nextcount = diffs[i+1]
+ if pos + 5 >= nextpos and nextcount < 16:
+ diffs[i][1] = nextcount + (nextpos - pos)
+ del diffs[i+1]
+ # Transmit changes
+ for pos, count in diffs:
+ count += pos & 0x01
+ count += count & 0x01
+ pos = pos & ~0x01
+ chip_pos = pos >> 1
+ if fb_id < 0x40:
+ # Graphics framebuffer update
+ self.send([0x80 + fb_id, 0x80 + chip_pos], is_extended=True)
+ else:
+ self.send([fb_id + chip_pos])
+ self.send(new_data[pos:pos+count], is_data=True)
+ old_data[:] = new_data
+ def init(self):
+ cmds = [0x24, # Enter extended mode
+ 0x40, # Clear vertical scroll address
+ 0x02, # Enable CGRAM access
+ 0x26, # Enable graphics
+ 0x22, # Leave extended mode
+ 0x02, # Home the display
+ 0x06, # Set positive update direction
+ 0x0c] # Enable display and hide cursor
+ self.send(cmds)
+ self.flush()
+ def load_glyph(self, glyph_id, data):
+ if len(data) > 32:
+ data = data[:32]
+ pos = min(glyph_id * 32, 96)
+ self.glyph_framebuffer[0][pos:pos+len(data)] = data
+ def write_text(self, x, y, data):
+ if x + len(data) > 16:
+ data = data[:16 - min(x, 16)]
+ pos = [0, 32, 16, 48][y] + x
+ self.text_framebuffer[0][pos:pos+len(data)] = data
+ def write_graphics(self, x, y, row, data):
+ if x + len(data) > 16:
+ data = data[:16 - min(x, 16)]
+ gfx_fb = y * 16 + row
+ if gfx_fb >= 32:
+ gfx_fb -= 32
+ x += 16
+ self.graphics_framebuffers[gfx_fb][0][x:x+len(data)] = data
+ def clear(self):
+ self.text_framebuffer[0][:] = ' '*64
+ zeros = bytearray(32)
+ for new_data, old_data, fb_id in self.graphics_framebuffers:
+ new_data[:] = zeros