From 593b4d2656a51fd3a05fe5b6fc02b864fd8de15c Mon Sep 17 00:00:00 2001 From: Arksine Date: Wed, 27 Jun 2018 18:15:50 -0400 Subject: [PATCH] display: add UC1701 graphics controller support Signed-off-by: Eric Callahan --- klippy/extras/display/display.py | 40 ++++--- klippy/extras/display/uc1701.py | 180 +++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 17 deletions(-) create mode 100644 klippy/extras/display/uc1701.py diff --git a/klippy/extras/display/display.py b/klippy/extras/display/display.py index 63d3f566..b3c7577e 100644 --- a/klippy/extras/display/display.py +++ b/klippy/extras/display/display.py @@ -6,9 +6,9 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import hd44780, st7920, icons +import hd44780, st7920, uc1701, icons -LCD_chips = { 'st7920': st7920.ST7920, 'hd44780': hd44780.HD44780 } +LCD_chips = { 'st7920': st7920.ST7920, 'hd44780': hd44780.HD44780, 'uc1701' : uc1701.UC1701 } M73_TIMEOUT = 5. class PrinterLCD: @@ -49,18 +49,22 @@ class PrinterLCD: self.load_glyph(self.FAN2_GLYPH, icons.fan2_icon) # Start screen update timer self.reactor.update_timer(self.screen_update_timer, self.reactor.NOW) - # ST7920 Glyphs + # ST7920/UC1701 Glyphs def load_glyph(self, glyph_id, data): - if self.lcd_type != 'st7920': - return - glyph = [0x00] * (len(data) * 2) - for i, bits in enumerate(data): - glyph[i*2] = (bits >> 8) & 0xff - glyph[i*2 + 1] = bits & 0xff - return self.lcd_chip.load_glyph(glyph_id, glyph) + if self.lcd_type == 'uc1701': + self.lcd_chip.load_glyph(glyph_id, data) + elif self.lcd_type == 'st7920': + glyph = [0x00] * (len(data) * 2) + for i, bits in enumerate(data): + glyph[i*2] = (bits >> 8) & 0xff + glyph[i*2 + 1] = bits & 0xff + return self.lcd_chip.load_glyph(glyph_id, glyph) def animate_glyphs(self, eventtime, x, y, glyph_id, do_animate): frame = do_animate and int(eventtime) & 1 - self.lcd_chip.write_text(x, y, (0, (glyph_id + frame)*2)) + if self.lcd_type == 'uc1701': + self.lcd_chip.write_glyph(x, y, glyph_id + frame) + elif self.lcd_type == 'st7920': + self.lcd_chip.write_text(x, y, (0, (glyph_id + frame)*2)) # Graphics drawing def draw_icon(self, x, y, data): for i, bits in enumerate(data): @@ -89,7 +93,7 @@ class PrinterLCD: if self.lcd_type == 'hd44780': self.screen_update_hd44780(eventtime) else: - self.screen_update_st7920(eventtime) + self.screen_update_128x64(eventtime) self.lcd_chip.flush() return eventtime + .500 def screen_update_hd44780(self, eventtime): @@ -146,7 +150,7 @@ class PrinterLCD: self.msg_time = None else: self.draw_status(0, 3, gcode_info, toolhead_info) - def screen_update_st7920(self, eventtime): + def screen_update_128x64(self, eventtime): # Heaters if self.extruder0 is not None: info = self.extruder0.get_heater().get_status(eventtime) @@ -170,7 +174,8 @@ class PrinterLCD: info = self.fan.get_status(eventtime) self.animate_glyphs(eventtime, 10, 0, self.FAN1_GLYPH, info['speed'] != 0.) - self.draw_percent(12, 0, 4, info['speed']) + align = '>'if self.lcd_type == 'uc1701' else '^' + self.draw_percent(12, 0, 4, info['speed'], align) # SD card print progress progress = None toolhead_info = self.toolhead.get_status(eventtime) @@ -195,7 +200,8 @@ class PrinterLCD: gcode_info = self.gcode.get_status(eventtime) if extruder_count == 1: self.draw_icon(10, 1, icons.feedrate_icon) - self.draw_percent(12, 1, 4, gcode_info['speed_factor']) + align = '>'if self.lcd_type == 'uc1701' else '^' + self.draw_percent(12, 1, 4, gcode_info['speed_factor'], align) # Printing time and status printing_time = toolhead_info['printing_time'] remaining_time = None @@ -230,8 +236,8 @@ class PrinterLCD: if self.lcd_type == 'hd44780': s += self.lcd_chip.char_degrees self.lcd_chip.write_text(x, y, s) - def draw_percent(self, x, y, width, value): - self.lcd_chip.write_text(x, y, ("%d%%" % (value * 100.,)).center(width)) + def draw_percent(self, x, y, width, value, align='^'): + self.lcd_chip.write_text(x, y, '{:{}{}.0%}'.format(value, align, width)) def draw_time(self, x, y, seconds): seconds = int(seconds) self.lcd_chip.write_text(x, y, "%02d:%02d" % ( diff --git a/klippy/extras/display/uc1701.py b/klippy/extras/display/uc1701.py new file mode 100644 index 00000000..b579f200 --- /dev/null +++ b/klippy/extras/display/uc1701.py @@ -0,0 +1,180 @@ +# Support for UC1701 (128x64 graphics) LCD displays +# +# Copyright (C) 2018 Kevin O'Connor +# Copyright (C) 2018 Eric Callahan +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging +from font8x14 import VGA_FONT as CHAR_SET + +BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 + +class UC1701: + char_right_arrow = '\x1a' + CURRENT_BUF, OLD_BUF = 0, 1 + EMPTY_CHAR = (0, 32, 255) + 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','a0']] + mcu = None + for pin_params in pins: + if mcu is not None and pin_params['chip'] != mcu: + raise ppins.error("uc1701 all pins must be on same mcu") + mcu = pin_params['chip'] + if pin_params['invert']: + raise ppins.error("uc1701 can not invert pin") + self.pins = [pin_params['pin'] for pin_params in pins] + self.mcu = mcu + self.spi_oid = self.mcu.create_oid() + self.cs_oid = self.mcu.create_oid() + self.a0_oid = self.mcu.create_oid() + self.mcu.add_config_object(self) + self.glyph_buffer = [] + self.spi_xfer_cmd = self.set_pin_cmd = None + self.vram = ([bytearray(128) for i in range(8)], + [bytearray('~'*128) for i in range(8)]) + def build_config(self): + self.mcu.add_config_cmd( + "config_spi_without_cs oid=%d bus=%d mode=%d rate=%d shutdown_msg=" % ( + self.spi_oid, 0, 0, 4000000)) + self.mcu.add_config_cmd( + "config_digital_out oid=%d pin=%s value=%d default_value=%d max_duration=%d" % ( + self.cs_oid, self.pins[0], 1, 1, 0)) + self.mcu.add_config_cmd( + "config_digital_out oid=%d pin=%s value=%d default_value=%d max_duration=%d" % ( + self.a0_oid, self.pins[1], 0, 0, 0)) + cmd_queue = self.mcu.alloc_command_queue() + self.spi_send_cmd = self.mcu.lookup_command( + "spi_send oid=%c data=%*s", cq=cmd_queue) + self.update_pin_cmd = self.mcu.lookup_command( + "update_digital_out oid=%c value=%c", cq=cmd_queue) + def send(self, cmds, is_data=False): + self.update_pin_cmd.send([self.cs_oid, 0], reqclock=BACKGROUND_PRIORITY_CLOCK) + if is_data: + self.update_pin_cmd.send([self.a0_oid, 1], reqclock=BACKGROUND_PRIORITY_CLOCK) + else: + self.update_pin_cmd.send([self.a0_oid, 0], reqclock=BACKGROUND_PRIORITY_CLOCK) + self.spi_send_cmd.send([self.spi_oid, cmds], reqclock=BACKGROUND_PRIORITY_CLOCK) + self.update_pin_cmd.send([self.cs_oid, 1], reqclock=BACKGROUND_PRIORITY_CLOCK) + def init(self): + init_cmds = [0xE2, # System reset + 0x40, # Set display to start at line 0 + 0xA0, # Set SEG direction + 0xC8, # Set COM Direction + 0xA2, # Set Bias = 1/9 + 0x2C, # Boost ON + 0x2E, # Voltage regulator on + 0x2F, # Voltage follower on + 0xF8, # Set booster ratio + 0x00, # Booster ratio value (4x) + 0x23, # Set resistor ratio (3) + 0x81, # Set Electronic Volume + 0x28, # Electronic volume value (40) + 0xAC, # Set static indicator off + 0x00, # NOP + 0xA6, # Disable Inverse + 0xAF] # Set display enable + self.send(init_cmds) + self.send([0xA5]) # display all + self.send([0xA4]) # normal display + self.flush() + logging.info("uc1701 initialized") + def flush(self): + new_data = self.vram[self.CURRENT_BUF] + old_data = self.vram[self.OLD_BUF] + for page in range(8): + if new_data[page] == old_data[page]: + continue + # Find the position of all changed bytes in this framebuffer + diffs = [[i, 1] for i, (nd, od) in enumerate(zip(new_data[page], old_data[page])) + 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 + for col_pos, count in diffs: + # Set Position registers + ra = 0xb0 | (page & 0x0F) + ca_msb = 0x10 | ((col_pos >> 4) & 0x0F) + ca_lsb = col_pos & 0x0F + self.send([ra, ca_msb, ca_lsb]) + # Send Data + self.send(new_data[page][col_pos:col_pos+count], is_data=True) + old_data[page][:] = new_data[page] + def set_pixel(self, pix_x, pix_y, exclusive=True): + page_idx = pix_y // 8 + page_byte = 0x01 << (pix_y % 8) + if exclusive and self.vram[self.CURRENT_BUF][page_idx][pix_x] & page_byte: + #invert pixel if it has alread been set + self.vram[self.CURRENT_BUF][page_idx][pix_x] &= ~page_byte + else: + #set the correct pixel in the vram buffer to 1 + self.vram[self.CURRENT_BUF][page_idx][pix_x] |= page_byte + def clear_pixel(self, pix_x, pix_y): + page_idx = pix_y // 8 + page_byte = ~(0x01 << (pix_y % 8)) + #set the correct pixel in the vram buffer to 0 + self.vram[self.CURRENT_BUF][page_idx][pix_x] &= page_byte + def load_glyph(self, glyph_id, data): + if len(data) > 16: + data = data[:16] + self.glyph_buffer.append((glyph_id, data)) + self.glyph_buffer.sort(key=lambda x: x[0]) + def write_glyph(self, x, y, glyph_id): + pix_x = x*8 + pix_y = y*16 + data = self.glyph_buffer[glyph_id][1] + for bits in data: + if bits: + bit_x = pix_x + for i in range(15, -1, -1): + mask = 0x0001 << i + if bits & mask: + self.set_pixel(bit_x, pix_y) + bit_x += 1 + pix_y += 1 + def write_text(self, x, y, data): + if x + len(data) > 16: + data = data[:16 - min(x, 16)] + pix_x = x*8 + pix_y = y*16 + for c in data: + c_idx = ord(c) & 0xFF + if c_idx in self.EMPTY_CHAR: + # Empty char + pix_x += 8 + continue + char = CHAR_SET[c_idx] + bit_y = pix_y + for bits in char: + if bits: + bit_x = pix_x + for i in range(7, -1, -1): + mask = 0x01 << i + if bits & mask: + self.set_pixel(bit_x, bit_y) + bit_x += 1 + bit_y += 1 + pix_x += 8 + def write_graphics(self, x, y, row, data): + if x + len(data) > 16: + data = data[:16 - min(x, 16)] + pix_x = x*8 + pix_y = y*16 + row + for bits in data: + for i in range(7, -1, -1): + mask = 0x01 << i + if bits & mask: + self.set_pixel(pix_x, pix_y) + pix_x += 1 + def clear(self): + zeros = bytearray(128) + for page in self.vram[self.CURRENT_BUF]: + page[:] = zeros \ No newline at end of file