diff options
Diffstat (limited to 'klippy/extras/display/uc1701.py')
-rw-r--r-- | klippy/extras/display/uc1701.py | 180 |
1 files changed, 107 insertions, 73 deletions
diff --git a/klippy/extras/display/uc1701.py b/klippy/extras/display/uc1701.py index 8e877cf2..7d4e4683 100644 --- a/klippy/extras/display/uc1701.py +++ b/klippy/extras/display/uc1701.py @@ -8,9 +8,10 @@ import logging from .. import bus from . import font8x14 -BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 +BACKGROUND_PRIORITY_CLOCK = 0x7FFFFFFF00000000 + +TextGlyphs = {"right_arrow": b"\x1a", "degrees": b"\xf8"} -TextGlyphs = { 'right_arrow': b'\x1a', 'degrees': b'\xf8' } class DisplayBase: def __init__(self, io, columns=128, x_offset=0): @@ -19,37 +20,40 @@ class DisplayBase: self.columns = columns self.x_offset = x_offset self.vram = [bytearray(self.columns) for i in range(8)] - self.all_framebuffers = [(self.vram[i], bytearray(b'~'*self.columns), i) - for i in range(8)] + self.all_framebuffers = [ + (self.vram[i], bytearray(b"~" * self.columns), i) for i in range(8) + ] # Cache fonts and icons in display byte order - self.font = [self._swizzle_bits(bytearray(c)) - for c in font8x14.VGA_FONT] + self.font = [self._swizzle_bits(bytearray(c)) for c in font8x14.VGA_FONT] self.icons = {} + def flush(self): # Find all differences in the framebuffers and send them to the chip for new_data, old_data, page in self.all_framebuffers: if new_data == old_data: continue # Find the position of all changed bytes in this framebuffer - diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) - if n != o] + diffs = [ + [i, 1] for i, (n, o) in enumerate(zip(new_data, old_data)) if n != o + ] # Batch together changes that are close to each other - for i in range(len(diffs)-2, -1, -1): + for i in range(len(diffs) - 2, -1, -1): pos, count = diffs[i] - nextpos, nextcount = diffs[i+1] + nextpos, nextcount = diffs[i + 1] if pos + 5 >= nextpos and nextcount < 16: diffs[i][1] = nextcount + (nextpos - pos) - del diffs[i+1] + del diffs[i + 1] # Transmit changes for col_pos, count in diffs: # Set Position registers - ra = 0xb0 | (page & 0x0F) + 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[col_pos:col_pos+count], is_data=True) + self.send(new_data[col_pos : col_pos + count], is_data=True) old_data[:] = new_data + def _swizzle_bits(self, data): # Convert from "rows of pixels" format to "columns of pixels" top = bot = 0 @@ -58,28 +62,31 @@ class DisplayBase: top |= spaced >> (7 - row) spaced = (data[row + 8] * 0x8040201008040201) & 0x8080808080808080 bot |= spaced >> (7 - row) - bits_top = [(top >> s) & 0xff for s in range(0, 64, 8)] - bits_bot = [(bot >> s) & 0xff for s in range(0, 64, 8)] + bits_top = [(top >> s) & 0xFF for s in range(0, 64, 8)] + bits_bot = [(bot >> s) & 0xFF for s in range(0, 64, 8)] return (bytearray(bits_top), bytearray(bits_bot)) + def set_glyphs(self, glyphs): for glyph_name, glyph_data in glyphs.items(): - icon = glyph_data.get('icon16x16') + icon = glyph_data.get("icon16x16") if icon is not None: top1, bot1 = self._swizzle_bits(icon[0]) top2, bot2 = self._swizzle_bits(icon[1]) self.icons[glyph_name] = (top1 + top2, bot1 + bot2) + def write_text(self, x, y, data): if x + len(data) > 16: - data = data[:16 - min(x, 16)] + data = data[: 16 - min(x, 16)] pix_x = x * 8 pix_x += self.x_offset page_top = self.vram[y * 2] page_bot = self.vram[y * 2 + 1] for c in bytearray(data): bits_top, bits_bot = self.font[c] - page_top[pix_x:pix_x+8] = bits_top - page_bot[pix_x:pix_x+8] = bits_bot + page_top[pix_x : pix_x + 8] = bits_top + page_bot[pix_x : pix_x + 8] = bits_bot pix_x += 8 + def write_graphics(self, x, y, data): if x >= 16 or y >= 4 or len(data) != 16: return @@ -91,6 +98,7 @@ class DisplayBase: for i in range(8): page_top[pix_x + i] ^= bits_top[i] page_bot[pix_x + i] ^= bits_bot[i] + def write_glyph(self, x, y, glyph_name): icon = self.icons.get(glyph_name) if icon is not None and x < 15: @@ -98,8 +106,8 @@ class DisplayBase: pix_x = x * 8 pix_x += self.x_offset page_idx = y * 2 - self.vram[page_idx][pix_x:pix_x+16] = icon[0] - self.vram[page_idx + 1][pix_x:pix_x+16] = icon[1] + self.vram[page_idx][pix_x : pix_x + 16] = icon[0] + self.vram[page_idx + 1][pix_x : pix_x + 16] = icon[1] return 2 char = TextGlyphs.get(glyph_name) if char is not None: @@ -107,30 +115,37 @@ class DisplayBase: self.write_text(x, y, char) return 1 return 0 + def clear(self): zeros = bytearray(self.columns) for page in self.vram: page[:] = zeros + def get_dimensions(self): return (16, 4) + # IO wrapper for "4 wire" spi bus (spi bus with an extra data/control line) class SPI4wire: def __init__(self, config, data_pin_name): self.spi = bus.MCU_SPI_from_config(config, 0, default_speed=10000000) dc_pin = config.get(data_pin_name) - self.mcu_dc = bus.MCU_bus_digital_out(self.spi.get_mcu(), dc_pin, - self.spi.get_command_queue()) + self.mcu_dc = bus.MCU_bus_digital_out( + self.spi.get_mcu(), dc_pin, self.spi.get_command_queue() + ) + def send(self, cmds, is_data=False): - self.mcu_dc.update_digital_out(is_data, - reqclock=BACKGROUND_PRIORITY_CLOCK) + self.mcu_dc.update_digital_out(is_data, reqclock=BACKGROUND_PRIORITY_CLOCK) self.spi.spi_send(cmds, reqclock=BACKGROUND_PRIORITY_CLOCK) + # IO wrapper for i2c bus class I2C: def __init__(self, config, default_addr): - self.i2c = bus.MCU_I2C_from_config(config, default_addr=default_addr, - default_speed=400000) + self.i2c = bus.MCU_I2C_from_config( + config, default_addr=default_addr, default_speed=400000 + ) + def send(self, cmds, is_data=False): if is_data: hdr = 0x40 @@ -140,14 +155,17 @@ class I2C: cmds.insert(0, hdr) self.i2c.i2c_write(cmds, reqclock=BACKGROUND_PRIORITY_CLOCK) + # Helper code for toggling a reset pin on startup class ResetHelper: def __init__(self, pin_desc, io_bus): self.mcu_reset = None if pin_desc is None: return - self.mcu_reset = bus.MCU_bus_digital_out(io_bus.get_mcu(), pin_desc, - io_bus.get_command_queue()) + self.mcu_reset = bus.MCU_bus_digital_out( + io_bus.get_mcu(), pin_desc, io_bus.get_command_queue() + ) + def init(self): if self.mcu_reset is None: return @@ -155,45 +173,50 @@ class ResetHelper: curtime = mcu.get_printer().get_reactor().monotonic() print_time = mcu.estimated_print_time(curtime) # Toggle reset - minclock = mcu.print_time_to_clock(print_time + .100) + minclock = mcu.print_time_to_clock(print_time + 0.100) self.mcu_reset.update_digital_out(0, minclock=minclock) - minclock = mcu.print_time_to_clock(print_time + .200) + minclock = mcu.print_time_to_clock(print_time + 0.200) self.mcu_reset.update_digital_out(1, minclock=minclock) # Force a delay to any subsequent commands on the command queue - minclock = mcu.print_time_to_clock(print_time + .300) + minclock = mcu.print_time_to_clock(print_time + 0.300) self.mcu_reset.update_digital_out(1, minclock=minclock) + # The UC1701 is a "4-wire" SPI display device class UC1701(DisplayBase): def __init__(self, config): io = SPI4wire(config, "a0_pin") DisplayBase.__init__(self, io) - self.contrast = config.getint('contrast', 40, minval=0, maxval=63) + self.contrast = config.getint("contrast", 40, minval=0, maxval=63) self.reset = ResetHelper(config.get("rst_pin", None), io.spi) + def init(self): self.reset.init() - 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 - self.contrast, # Electronic Volume value - 0xAC, # Set static indicator off - 0x00, # NOP - 0xA6, # Disable Inverse - 0xAF] # Set display enable + 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 + self.contrast, # Electronic Volume value + 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.send([0xA5]) # display all + self.send([0xA4]) # normal display self.flush() + # The SSD1306 supports both i2c and "4-wire" spi class SSD1306(DisplayBase): def __init__(self, config, columns=128, x_offset=0): @@ -206,35 +229,46 @@ class SSD1306(DisplayBase): io_bus = io.spi self.reset = ResetHelper(config.get("reset_pin", None), io_bus) DisplayBase.__init__(self, io, columns, x_offset) - self.contrast = config.getint('contrast', 239, minval=0, maxval=255) - self.vcomh = config.getint('vcomh', 0, minval=0, maxval=63) - self.invert = config.getboolean('invert', False) + self.contrast = config.getint("contrast", 239, minval=0, maxval=255) + self.vcomh = config.getint("vcomh", 0, minval=0, maxval=63) + self.invert = config.getboolean("invert", False) + def init(self): self.reset.init() init_cmds = [ - 0xAE, # Display off - 0xD5, 0x80, # Set oscillator frequency - 0xA8, 0x3f, # Set multiplex ratio - 0xD3, 0x00, # Set display offset - 0x40, # Set display start line - 0x8D, 0x14, # Charge pump setting - 0x20, 0x02, # Set Memory addressing mode - 0xA1, # Set Segment re-map - 0xC8, # Set COM output scan direction - 0xDA, 0x12, # Set COM pins hardware configuration - 0x81, self.contrast, # Set contrast control - 0xD9, 0xA1, # Set pre-charge period - 0xDB, self.vcomh, # Set VCOMH deselect level - 0x2E, # Deactivate scroll - 0xA4, # Output ram to display - 0xA7 if self.invert else 0xA6, # Set normal/invert - 0xAF, # Display on + 0xAE, # Display off + 0xD5, + 0x80, # Set oscillator frequency + 0xA8, + 0x3F, # Set multiplex ratio + 0xD3, + 0x00, # Set display offset + 0x40, # Set display start line + 0x8D, + 0x14, # Charge pump setting + 0x20, + 0x02, # Set Memory addressing mode + 0xA1, # Set Segment re-map + 0xC8, # Set COM output scan direction + 0xDA, + 0x12, # Set COM pins hardware configuration + 0x81, + self.contrast, # Set contrast control + 0xD9, + 0xA1, # Set pre-charge period + 0xDB, + self.vcomh, # Set VCOMH deselect level + 0x2E, # Deactivate scroll + 0xA4, # Output ram to display + 0xA7 if self.invert else 0xA6, # Set normal/invert + 0xAF, # Display on ] self.send(init_cmds) self.flush() + # the SH1106 is SSD1306 compatible with up to 132 columns class SH1106(SSD1306): def __init__(self, config): - x_offset = config.getint('x_offset', 0, minval=0, maxval=3) + x_offset = config.getint("x_offset", 0, minval=0, maxval=3) SSD1306.__init__(self, config, 132, x_offset=x_offset) |