From 2f5d4cb3e01c94170bfe07c0e884774e3ee244f3 Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Sat, 30 Sep 2023 16:16:19 +1000 Subject: [PATCH 01/13] 2bpp mode initial commit. Doesn't currently blit white space properly --- papertty/drivers/driver_it8951.py | 42 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/papertty/drivers/driver_it8951.py b/papertty/drivers/driver_it8951.py index c13d8fc..f0ebe56 100644 --- a/papertty/drivers/driver_it8951.py +++ b/papertty/drivers/driver_it8951.py @@ -260,7 +260,7 @@ def draw(self, x, y, image, update_mode_override=None): self.write_command(self.CMD_LOAD_IMAGE_AREA) self.write_data_half_word( (self.LOAD_IMAGE_L_ENDIAN << 8) | - (self.BPP_4 << 4) | + (self.BPP_2 << 4) | self.ROTATE_0) self.write_data_half_word(x) self.write_data_half_word(y) @@ -301,24 +301,28 @@ def pack_image(self, image): # The driver board assumes all data is read in as 16bit ints. To match # the endianness every pair of bytes must be swapped. # The image is always padded to a multiple of 8, so we can safely go in steps of 4. - for i in range(0, len(frame_buffer), 4): - if frame_buffer[i + 2] and frame_buffer[i + 3]: - packed_buffer += [0xFF] - elif frame_buffer[i + 2]: - packed_buffer += [0x0F] - elif frame_buffer[i + 3]: - packed_buffer += [0xF0] - else: - packed_buffer += [0] - - if frame_buffer[i] and frame_buffer[i + 1]: - packed_buffer += [0xFF] - elif frame_buffer[i]: - packed_buffer += [0x0F] - elif frame_buffer[i + 1]: - packed_buffer += [0xF0] - else: - packed_buffer += [0] + for i in range(0, len(frame_buffer), 8): + thisbit = 0 + if frame_buffer[i+7]: + thisbit |= 0xC0 + if frame_buffer[i+6]: + thisbit |= 0x30 + if frame_buffer[i+5]: + thisbit |= 0xC + if frame_buffer[i+4]: + thisbit |= 0x3 + packed_buffer += [thisbit] + thisbit = 0 + if frame_buffer[i+3]: + thisbit |= 0xC0 + if frame_buffer[i+2]: + thisbit |= 0x30 + if frame_buffer[i+1]: + thisbit |= 0xC + if frame_buffer[i+0]: + thisbit |= 0x3 + packed_buffer += [thisbit] + return packed_buffer else: # old packing code for grayscale (VNC) From 766e942b34f8da0149555771bdf0ac90b62d586d Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:14:27 +1100 Subject: [PATCH 02/13] 1bpp mode support --- papertty/drivers/driver_it8951.py | 200 +++++++++++++++++++++--------- 1 file changed, 144 insertions(+), 56 deletions(-) diff --git a/papertty/drivers/driver_it8951.py b/papertty/drivers/driver_it8951.py index f0ebe56..a3ff0c4 100644 --- a/papertty/drivers/driver_it8951.py +++ b/papertty/drivers/driver_it8951.py @@ -39,6 +39,8 @@ class IT8951(DisplayDriver): REG_DISPLAY_BASE = 0x1000 REG_LUTAFSR = REG_DISPLAY_BASE + 0x224 # LUT Status Reg (status of All LUT Engines) + REG_UP1SR = REG_DISPLAY_BASE + 0x138 #Update Parameter1 Setting Reg + REG_BGVR = REG_DISPLAY_BASE + 0x250 #Bitmap (1bpp) image color table REG_MEMORY_CONV_BASE_ADDR = 0x0200 REG_MEMORY_CONV = REG_MEMORY_CONV_BASE_ADDR + 0x0000 @@ -69,6 +71,10 @@ class IT8951(DisplayDriver): # For more documentation on display update modes see the reference document: # http://www.waveshare.net/w/upload/c/c4/E-paper-mode-declaration.pdf + #Colors for 1bpp mode register + Back_Gray_Val = 0xF0 + Front_Gray_Val = 0x00 + def __init__(self): super().__init__() self.name = "IT8951" @@ -246,28 +252,45 @@ def display_area(self, x, y, w, h, display_mode): self.write_data_half_word(h) self.write_data_half_word(display_mode) - def draw(self, x, y, image, update_mode_override=None): + def draw(self, x, y, image, update_mode_override=None, bpp=None): width = image.size[0] height = image.size[1] self.wait_for_display_ready() - self.write_register( - self.REG_MEMORY_CONV_LISAR + 2, (self.img_addr >> 16) & 0xFFFF) - self.write_register(self.REG_MEMORY_CONV_LISAR, self.img_addr & 0xFFFF) + #If bpp isn't set, and it's a fullscreen image, set to 1bpp by default + if bpp is None: + bpp = 1 if (width == self.width and height == self.height) else 4 + + if bpp == 1 and image.mode == "1" and x % 16 == 0 and image.width % 16 == 0: + + #1bpp actually requires the panel to be set in 8bpp mode + bpp_mode = self.BPP_8 + + self.write_register(self.REG_UP1SR+2, self.read_register(self.REG_UP1SR+2) | (1<<2) ) + self.write_register(self.REG_BGVR, (self.Front_Gray_Val<<8) | self.Back_Gray_Val) + else: + bpp = 4 + bpp_mode = self.BPP_4 + + self.write_register( + self.REG_MEMORY_CONV_LISAR + 2, (self.img_addr >> 16) & 0xFFFF) + self.write_register(self.REG_MEMORY_CONV_LISAR, self.img_addr & 0xFFFF) # Define the region being loaded. self.write_command(self.CMD_LOAD_IMAGE_AREA) self.write_data_half_word( (self.LOAD_IMAGE_L_ENDIAN << 8) | - (self.BPP_2 << 4) | + (bpp_mode << 4) | self.ROTATE_0) - self.write_data_half_word(x) + + # x and width are set to value//8 if using 1bpp mode. + self.write_data_half_word((x//8) if bpp == 1 else x) self.write_data_half_word(y) - self.write_data_half_word(width) + self.write_data_half_word((width//8) if bpp == 1 else width) self.write_data_half_word(height) - packed_image = self.pack_image(image) + packed_image = self.pack_image(image, bpp) self.write_data_bytes(packed_image) self.write_command(self.CMD_LOAD_IMAGE_END); @@ -283,64 +306,129 @@ def draw(self, x, y, image, update_mode_override=None): # Blit the image to the display self.display_area(x, y, width, height, update_mode) + if bpp == 1: + self.write_register(self.REG_UP1SR+2, self.read_register(self.REG_UP1SR+2) & ~(1<<2) ) + def clear(self): image = Image.new('1', (self.width, self.height), self.white) self.draw(0, 0, image, self.DISPLAY_UPDATE_MODE_INIT) - def pack_image(self, image): + def pack_image(self, image, bpp): """Packs a PIL image for transfer over SPI to the driver board.""" if image.mode == '1': # B/W pictured can be processed more quickly frame_buffer = list(image.getdata()) - - # For now, only 4 bit packing is supported. Theoretically we could - # achieve a transfer speed up by using 2 bit packing for black and white - # images. However, 2bpp doesn't seem to play well with the DU rendering - # mode. - packed_buffer = [] - # The driver board assumes all data is read in as 16bit ints. To match - # the endianness every pair of bytes must be swapped. - # The image is always padded to a multiple of 8, so we can safely go in steps of 4. - for i in range(0, len(frame_buffer), 8): - thisbit = 0 - if frame_buffer[i+7]: - thisbit |= 0xC0 - if frame_buffer[i+6]: - thisbit |= 0x30 - if frame_buffer[i+5]: - thisbit |= 0xC - if frame_buffer[i+4]: - thisbit |= 0x3 - packed_buffer += [thisbit] - thisbit = 0 - if frame_buffer[i+3]: - thisbit |= 0xC0 - if frame_buffer[i+2]: - thisbit |= 0x30 - if frame_buffer[i+1]: - thisbit |= 0xC - if frame_buffer[i+0]: - thisbit |= 0x3 - packed_buffer += [thisbit] - - return packed_buffer else: # old packing code for grayscale (VNC) + bpp = 4 image_grey = image.convert("L") frame_buffer = list(image_grey.getdata()) - # For now, only 4 bit packing is supported. Theoretically we could - # achieve a transfer speed up by using 2 bit packing for black and white - # images. However, 2bpp doesn't seem to play well with the DU rendering - # mode. - packed_buffer = [] - - # The driver board assumes all data is read in as 16bit ints. To match - # the endianness every pair of bytes must be swapped. - # The image is always padded to a multiple of 8, so we can safely go in steps of 4. - for i in range(0, len(frame_buffer), 4): - # Values are in the range 0..255, so we don't need to "and" after we shift - packed_buffer += [(frame_buffer[i + 2] >> 4) | (frame_buffer[i + 3] & 0xF0)] - packed_buffer += [(frame_buffer[i] >> 4) | (frame_buffer[i + 1] & 0xF0)] - - return packed_buffer + + #Step is the number of bytes we need to read to create a word. + #A word is 2 bytes (16 bits) in size. + #However, the input data we use to create the word will vary + #in length depending on the bpp. + #eg. If bpp is 1, that means we only grab 1 bit from each + #input byte. So we would need 16 bytes to get the needed + #16 bits. + #Whereas if bpp is 4, then we grab 4 bits from each byte. + #So we'd only need to read 4 bytes to get 16 bits. + step = 16 // bpp + + #A halfstep is how many input bytes we need to read from + #frame_buffer in order to pack a single output byte + #into packed_buffer. + halfstep = step // 2 + + #Set the size of packed_buffer to be the length of the + #frame buffer (total input bytes) divided by a halfstep + #(input bytes needed per packed byte). + packed_buffer = [0x00] * (len(frame_buffer) // halfstep) + + #Select the packing function based on which bpp + #mode we're using. + if bpp == 1: + packfn = self.pack_1bpp + elif bpp == 2: + packfn = self.pack_2bpp + else: + packfn = self.pack_4bpp + + #Step through the frame buffer and pack its bytes + #into packed_buffer. + for i in range(0, len(frame_buffer), step): + packfn(packed_buffer, i // halfstep, frame_buffer[i:i+step]) + return packed_buffer + + def pack_1bpp(self, packed_buffer, i, sixteenBytes): + """Pack an image in 1bpp format. + + This only works for black and white images. + This code would look nicer with a loop, but using bitwise operators + like this is significantly faster. So the ugly code stays ;) + + Bytes are read in reverse order because the driver board assumes all + data is read in as 16bit ints. So in order to match the endianness, + every pair of bytes must be swapped. + """ + packed_buffer[i] = \ + (1 if sixteenBytes[8] else 0) | \ + (2 if sixteenBytes[9] else 0) | \ + (4 if sixteenBytes[10] else 0) | \ + (8 if sixteenBytes[11] else 0) | \ + (16 if sixteenBytes[12] else 0) | \ + (32 if sixteenBytes[13] else 0) | \ + (64 if sixteenBytes[14] else 0) | \ + (128 if sixteenBytes[15] else 0) + packed_buffer[i+1] = \ + (1 if sixteenBytes[0] else 0) | \ + (2 if sixteenBytes[1] else 0) | \ + (4 if sixteenBytes[2] else 0) | \ + (8 if sixteenBytes[3] else 0) | \ + (16 if sixteenBytes[4] else 0) | \ + (32 if sixteenBytes[5] else 0) | \ + (64 if sixteenBytes[6] else 0) | \ + (128 if sixteenBytes[7] else 0) + + def pack_2bpp(self, packed_buffer, i, eightBytes): + """Pack an image in 2bpp format. + + The utility of 2bpp format is questionable, as it only works properly + with GC16 mode. DU mode causes artifacts to remain. + It seems unlikely that this is a bug in driver_it8951.py given that + GC16 mode works correctly. + + The waveshare reference code only ever uses GC16 mode with 2bpp, so + perhaps it's a bug within the IT8951 controller? Regardless, 1bpp is + faster, and 4bpp works with DU mode. So chances are you'd be better off + using one of those. + + Bytes are read in reverse order because the driver board assumes all + data is read in as 16bit ints. So in order to match the endianness, + every pair of bytes must be swapped. + """ + packed_buffer[i] = \ + (3 if eightBytes[4] else 0) | \ + (12 if eightBytes[5] else 0) | \ + (48 if eightBytes[6] else 0) | \ + (192 if eightBytes[7] else 0) + packed_buffer[i+1] = \ + (3 if eightBytes[0] else 0) | \ + (12 if eightBytes[1] else 0) | \ + (48 if eightBytes[2] else 0) | \ + (192 if eightBytes[3] else 0) + + def pack_4bpp(self, packed_buffer, i, fourBytes): + """Pack an image in 4bpp format. + + Bytes are read in reverse order because the driver board assumes all + data is read in as 16bit ints. So in order to match the endianness, + every pair of bytes must be swapped. + """ + packed_buffer[i] = \ + (15 if fourBytes[2] else 0) | \ + (240 if fourBytes[3] else 0) + packed_buffer[i+1] = \ + (15 if fourBytes[0] else 0) | \ + (240 if fourBytes[1] else 0) From af196fc3a56335157905df1ae09edfce3d294c20 Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:19:24 +1100 Subject: [PATCH 03/13] A2 mode support for 1bpp --- papertty/drivers/driver_it8951.py | 40 ++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/papertty/drivers/driver_it8951.py b/papertty/drivers/driver_it8951.py index a3ff0c4..8115090 100644 --- a/papertty/drivers/driver_it8951.py +++ b/papertty/drivers/driver_it8951.py @@ -71,6 +71,10 @@ class IT8951(DisplayDriver): # For more documentation on display update modes see the reference document: # http://www.waveshare.net/w/upload/c/c4/E-paper-mode-declaration.pdf + #A2 mode is super fast, but only supports 1bpp, and leaves more + #artifacts than other display modes. + DISPLAY_UPDATE_MODE_A2 = 6 + #Colors for 1bpp mode register Back_Gray_Val = 0xF0 Front_Gray_Val = 0x00 @@ -218,6 +222,37 @@ def init(self, **kwargs): lut_version = self.fixup_string(lut_version) self.img_addr = img_addr_h << 16 | img_addr_l + self.a2_support = False + + #6inch e-Paper HAT(800,600), 6inch HD e-Paper HAT(1448,1072), 6inch HD touch e-Paper HAT(1448,1072) + if len(lut_version) >= 4 and lut_version[:4] == "M641": + + #A2 mode is 4 instead of 6 for this model + self.DISPLAY_UPDATE_MODE_A2 = 4 + + #This model requires four-byte alignment. + #Don't enable a2 support until that has been implemented. + #a2_support = True + + #9.7inch e-Paper HAT(1200,825) + elif len(lut_version) >= 4 and lut_version[:4] == "M841": + self.a2_support = True + + #7.8inch e-Paper HAT(1872,1404) + elif len(lut_version) >= 12 and lut_version[:12] == "M841_TFA2812": + self.a2_support = True + + #10.3inch e-Paper HAT(1872,1404) + elif len(lut_version) >= 12 and lut_version[:12] == "M841_TFA5210": + self.a2_support = True + + #unknown model + else: + #It's PROBABLY safe to turn this to A2 instead of DU, but it would need a suitable test device. + #ie. A model not listed above. + #So for now, let's just leave it disabled + pass + print("width = %d" % self.width) print("height = %d" % self.height) print("img_addr = %08x" % self.img_addr) @@ -299,7 +334,10 @@ def draw(self, x, y, image, update_mode_override=None, bpp=None): elif image.mode == "1": # Use a faster, non-flashy update mode for pure black and white # images. - update_mode = self.DISPLAY_UPDATE_MODE_DU + if bpp == 1 and self.a2_support: + update_mode = self.DISPLAY_UPDATE_MODE_A2 + else: + update_mode = self.DISPLAY_UPDATE_MODE_DU else: # Use a slower, flashy update mode for gray scale images. update_mode = self.DISPLAY_UPDATE_MODE_GC16 From dde9e5bd4e11f75e6f1c9896c92c8470ae120714 Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:30:11 +1100 Subject: [PATCH 04/13] Remember 1bpp mode state to avoid unnecessary register writes --- papertty/drivers/driver_it8951.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/papertty/drivers/driver_it8951.py b/papertty/drivers/driver_it8951.py index 8115090..a7a6e45 100644 --- a/papertty/drivers/driver_it8951.py +++ b/papertty/drivers/driver_it8951.py @@ -222,6 +222,7 @@ def init(self, **kwargs): lut_version = self.fixup_string(lut_version) self.img_addr = img_addr_h << 16 | img_addr_l + self.in_bpp1_mode = False self.a2_support = False #6inch e-Paper HAT(800,600), 6inch HD e-Paper HAT(1448,1072), 6inch HD touch e-Paper HAT(1448,1072) @@ -302,12 +303,20 @@ def draw(self, x, y, image, update_mode_override=None, bpp=None): #1bpp actually requires the panel to be set in 8bpp mode bpp_mode = self.BPP_8 - self.write_register(self.REG_UP1SR+2, self.read_register(self.REG_UP1SR+2) | (1<<2) ) + if not self.in_bpp1_mode: + self.write_register(self.REG_UP1SR+2, self.read_register(self.REG_UP1SR+2) | (1<<2) ) + self.in_bpp1_mode = True + self.write_register(self.REG_BGVR, (self.Front_Gray_Val<<8) | self.Back_Gray_Val) else: bpp = 4 bpp_mode = self.BPP_4 + #If the last write was in 1bpp mode, unset that register + if self.in_bpp1_mode: + self.write_register(self.REG_UP1SR+2, self.read_register(self.REG_UP1SR+2) & ~(1<<2) ) + self.in_bpp1_mode = False + self.write_register( self.REG_MEMORY_CONV_LISAR + 2, (self.img_addr >> 16) & 0xFFFF) self.write_register(self.REG_MEMORY_CONV_LISAR, self.img_addr & 0xFFFF) @@ -344,9 +353,6 @@ def draw(self, x, y, image, update_mode_override=None, bpp=None): # Blit the image to the display self.display_area(x, y, width, height, update_mode) - if bpp == 1: - self.write_register(self.REG_UP1SR+2, self.read_register(self.REG_UP1SR+2) & ~(1<<2) ) - def clear(self): image = Image.new('1', (self.width, self.height), self.white) self.draw(0, 0, image, self.DISPLAY_UPDATE_MODE_INIT) From 191827df78164a6e5913d4635b06cd525e6c379e Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Mon, 2 Oct 2023 16:00:52 +1100 Subject: [PATCH 05/13] Use 1bpp mode where possible --- papertty/drivers/driver_it8951.py | 22 +++++++++++++++++----- papertty/papertty.py | 4 ++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/papertty/drivers/driver_it8951.py b/papertty/drivers/driver_it8951.py index a7a6e45..aa4e386 100644 --- a/papertty/drivers/driver_it8951.py +++ b/papertty/drivers/driver_it8951.py @@ -280,6 +280,14 @@ def init(self, **kwargs): self.wait_for_ready() self.clear() + #Adjust screen size to accommodate 32px bounding in 1bpp mode + #Do this AFTER clearing the screen + self.width -= (self.width % 32) + self.height -= (self.height % 32) + + print("adjusted width = %d" % self.width) + print("adjusted height = %d" % self.height) + def display_area(self, x, y, w, h, display_mode): self.write_command(self.CMD_DISPLAY_AREA) self.write_data_half_word(x) @@ -288,17 +296,21 @@ def display_area(self, x, y, w, h, display_mode): self.write_data_half_word(h) self.write_data_half_word(display_mode) - def draw(self, x, y, image, update_mode_override=None, bpp=None): width = image.size[0] height = image.size[1] self.wait_for_display_ready() - #If bpp isn't set, and it's a fullscreen image, set to 1bpp by default - if bpp is None: - bpp = 1 if (width == self.width and height == self.height) else 4 + #If fullscreen image or dims are divisible by 32, set to 1bpp. + #Otherwise, set to 4bpp. + if width == self.width and height == self.height: + bpp = 1 + elif width % 32 == 0 and height % 32 == 0: + bpp = 1 + else: + bpp = 4 - if bpp == 1 and image.mode == "1" and x % 16 == 0 and image.width % 16 == 0: + if bpp == 1 and image.mode == "1" and x % 16 == 0: #1bpp actually requires the panel to be set in 8bpp mode bpp_mode = self.BPP_8 diff --git a/papertty/papertty.py b/papertty/papertty.py index bb3647a..65995d7 100755 --- a/papertty/papertty.py +++ b/papertty/papertty.py @@ -70,6 +70,7 @@ class PaperTTY: cols = None is_truetype = None fontfile = None + bpp = 4 def __init__(self, driver, font=defaultfont, fontsize=defaultsize, partial=None, encoding='utf-8', spacing=0, cursor=None, vcom=None): """Create a PaperTTY with the chosen driver and settings""" @@ -704,6 +705,9 @@ def terminal(settings, vcsa, font, fontsize, noclear, nocursor, cursor, sleep, t ptty = settings.get_init_tty() + isIt8951 = isinstance(ptty.driver, driver_it8951.IT8951) + ptty.bpp = 1 if isIt8951 else 4 + if apply_scrub: ptty.driver.scrub() oldbuff = '' From 1261c4caf21061dddfd3f95044c99f87fc91a8ac Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Mon, 2 Oct 2023 16:01:59 +1100 Subject: [PATCH 06/13] Band to 32px instead of 8px when 1bpp mode is active --- papertty/papertty.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/papertty/papertty.py b/papertty/papertty.py index 65995d7..94e691a 100755 --- a/papertty/papertty.py +++ b/papertty/papertty.py @@ -108,10 +108,16 @@ def set_tty_size(self, tty, rows, cols): print("Try setting a sane size manually.") @staticmethod - def band(bb): + def band(bb, xdiv = 8, ydiv = 1): """Stretch a bounding box's X coordinates to be divisible by 8, otherwise weird artifacts occur as some bits are skipped.""" - return (int(bb[0] / 8) * 8, bb[1], int((bb[2] + 7) / 8) * 8, bb[3]) if bb else None + print("Before band: "+str(bb)) + return ( \ + int(bb[0] / xdiv) * xdiv, \ + int(bb[1] / ydiv) * ydiv, \ + int((bb[2] + xdiv - 1) / xdiv) * xdiv, \ + int((bb[3] + ydiv - 1) / ydiv) * ydiv \ + ) if bb else None @staticmethod def split(s, n): @@ -424,7 +430,13 @@ def showtext(self, text, fill, cursor=None, portrait=False, flipx=False, flipy=F if oldimage and self.driver.supports_partial and self.partial: # create a bounding box of the altered region and # make the X coordinates divisible by 8 - diff_bbox = self.band(self.img_diff(image, oldimage)) + if self.bpp == 1: + xdiv = 32 + ydiv = 32 + else: + xdiv = 8 + ydiv = 1 + diff_bbox = self.band(self.img_diff(image, oldimage), xdiv=xdiv, ydiv=ydiv) # crop the altered region and draw it on the display if diff_bbox: self.driver.draw(diff_bbox[0], diff_bbox[1], image.crop(diff_bbox)) From 75f41afdb0686f278e66077e586e01d444d1e112 Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Mon, 2 Oct 2023 17:18:23 +1100 Subject: [PATCH 07/13] Fix for previous commit --- papertty/drivers/driver_it8951.py | 1 + 1 file changed, 1 insertion(+) diff --git a/papertty/drivers/driver_it8951.py b/papertty/drivers/driver_it8951.py index aa4e386..cdd748b 100644 --- a/papertty/drivers/driver_it8951.py +++ b/papertty/drivers/driver_it8951.py @@ -296,6 +296,7 @@ def display_area(self, x, y, w, h, display_mode): self.write_data_half_word(h) self.write_data_half_word(display_mode) + def draw(self, x, y, image, update_mode_override=None): width = image.size[0] height = image.size[1] From 6c64823ee3462b87bddfcda001dc59795af71ed6 Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Mon, 2 Oct 2023 18:34:44 +1100 Subject: [PATCH 08/13] Comment out debug --- papertty/papertty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papertty/papertty.py b/papertty/papertty.py index 94e691a..1c9195b 100755 --- a/papertty/papertty.py +++ b/papertty/papertty.py @@ -111,7 +111,7 @@ def set_tty_size(self, tty, rows, cols): def band(bb, xdiv = 8, ydiv = 1): """Stretch a bounding box's X coordinates to be divisible by 8, otherwise weird artifacts occur as some bits are skipped.""" - print("Before band: "+str(bb)) + #print("Before band: "+str(bb)) return ( \ int(bb[0] / xdiv) * xdiv, \ int(bb[1] / ydiv) * ydiv, \ From d5472f64ccfcfaa54ce3d21e433a8d06cf8fb7ef Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Thu, 5 Oct 2023 15:58:55 +1100 Subject: [PATCH 09/13] Reduce height requirement to 16 --- papertty/drivers/driver_it8951.py | 4 ++-- papertty/papertty.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/papertty/drivers/driver_it8951.py b/papertty/drivers/driver_it8951.py index cdd748b..c02aa1a 100644 --- a/papertty/drivers/driver_it8951.py +++ b/papertty/drivers/driver_it8951.py @@ -283,7 +283,7 @@ def init(self, **kwargs): #Adjust screen size to accommodate 32px bounding in 1bpp mode #Do this AFTER clearing the screen self.width -= (self.width % 32) - self.height -= (self.height % 32) + self.height -= (self.height % 16) print("adjusted width = %d" % self.width) print("adjusted height = %d" % self.height) @@ -306,7 +306,7 @@ def draw(self, x, y, image, update_mode_override=None): #Otherwise, set to 4bpp. if width == self.width and height == self.height: bpp = 1 - elif width % 32 == 0 and height % 32 == 0: + elif width % 32 == 0 and height % 16 == 0: bpp = 1 else: bpp = 4 diff --git a/papertty/papertty.py b/papertty/papertty.py index 1c9195b..cd2468e 100755 --- a/papertty/papertty.py +++ b/papertty/papertty.py @@ -432,7 +432,7 @@ def showtext(self, text, fill, cursor=None, portrait=False, flipx=False, flipy=F # make the X coordinates divisible by 8 if self.bpp == 1: xdiv = 32 - ydiv = 32 + ydiv = 16 else: xdiv = 8 ydiv = 1 From ce7fded47c982ac0f41fc979e0c9f173f3a146ad Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:45:29 +1100 Subject: [PATCH 10/13] Added flags for 1bpp and a2 mode --- papertty/drivers/driver_it8951.py | 32 +++++++++++++++++++------------ papertty/drivers/drivers_base.py | 4 ++++ papertty/drivers/drivers_full.py | 4 ++++ papertty/papertty.py | 29 ++++++++++++++++------------ 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/papertty/drivers/driver_it8951.py b/papertty/drivers/driver_it8951.py index c02aa1a..d8ca674 100644 --- a/papertty/drivers/driver_it8951.py +++ b/papertty/drivers/driver_it8951.py @@ -83,6 +83,10 @@ def __init__(self): super().__init__() self.name = "IT8951" self.supports_partial = True + self.supports_1bpp = True + self.enable_1bpp = True + self.align_1bpp_width = 32 + self.align_1bpp_height = 16 def delay_ms(self, delaytime): time.sleep(float(delaytime) / 1000.0) @@ -223,7 +227,9 @@ def init(self, **kwargs): self.img_addr = img_addr_h << 16 | img_addr_l self.in_bpp1_mode = False - self.a2_support = False + self.enable_1bpp = kwargs.get('enable_1bpp', self.enable_1bpp) + self.supports_a2 = False + self.enable_a2 = kwargs.get('enable_a2', True) #6inch e-Paper HAT(800,600), 6inch HD e-Paper HAT(1448,1072), 6inch HD touch e-Paper HAT(1448,1072) if len(lut_version) >= 4 and lut_version[:4] == "M641": @@ -233,19 +239,19 @@ def init(self, **kwargs): #This model requires four-byte alignment. #Don't enable a2 support until that has been implemented. - #a2_support = True + #self.supports_a2 = True #9.7inch e-Paper HAT(1200,825) elif len(lut_version) >= 4 and lut_version[:4] == "M841": - self.a2_support = True + self.supports_a2 = True #7.8inch e-Paper HAT(1872,1404) elif len(lut_version) >= 12 and lut_version[:12] == "M841_TFA2812": - self.a2_support = True + self.supports_a2 = True #10.3inch e-Paper HAT(1872,1404) elif len(lut_version) >= 12 and lut_version[:12] == "M841_TFA5210": - self.a2_support = True + self.supports_a2 = True #unknown model else: @@ -280,13 +286,15 @@ def init(self, **kwargs): self.wait_for_ready() self.clear() - #Adjust screen size to accommodate 32px bounding in 1bpp mode - #Do this AFTER clearing the screen - self.width -= (self.width % 32) - self.height -= (self.height % 16) + if self.enable_1bpp: + #Adjust screen size to accommodate bounding in 1bpp mode + #Do this AFTER clearing the screen + self.width -= (self.width % self.align_1bpp_width) + self.height -= (self.height % self.align_1bpp_height) - print("adjusted width = %d" % self.width) - print("adjusted height = %d" % self.height) + print("1bpp support enabled") + print("adjusted width = %d" % self.width) + print("adjusted height = %d" % self.height) def display_area(self, x, y, w, h, display_mode): self.write_command(self.CMD_DISPLAY_AREA) @@ -356,7 +364,7 @@ def draw(self, x, y, image, update_mode_override=None): elif image.mode == "1": # Use a faster, non-flashy update mode for pure black and white # images. - if bpp == 1 and self.a2_support: + if bpp == 1 and self.supports_a2 and self.enable_a2: update_mode = self.DISPLAY_UPDATE_MODE_A2 else: update_mode = self.DISPLAY_UPDATE_MODE_DU diff --git a/papertty/drivers/drivers_base.py b/papertty/drivers/drivers_base.py index 605c050..bb4051c 100644 --- a/papertty/drivers/drivers_base.py +++ b/papertty/drivers/drivers_base.py @@ -46,6 +46,10 @@ def __init__(self): self.type = None self.supports_partial = None self.partial_refresh = None + self.supports_1bpp = None + self.enable_1bpp = None + self.align_1bpp_width = None + self.align_1bpp_height = None @abstractmethod def init(self, **kwargs): diff --git a/papertty/drivers/drivers_full.py b/papertty/drivers/drivers_full.py index 458e672..189ede1 100644 --- a/papertty/drivers/drivers_full.py +++ b/papertty/drivers/drivers_full.py @@ -48,6 +48,10 @@ def __init__(self, **kwargs): self.supports_partial = False self.colors = 2 self.lut = None + self.supports_1bpp = False + self.enable_1bpp = False + self.align_1bpp_width = False + self.align_1bpp_height = False def wait_until_idle(self): while self.digital_read(self.BUSY_PIN) == 0: # 0: busy, 1: idle diff --git a/papertty/papertty.py b/papertty/papertty.py index cd2468e..cc316f1 100755 --- a/papertty/papertty.py +++ b/papertty/papertty.py @@ -70,9 +70,10 @@ class PaperTTY: cols = None is_truetype = None fontfile = None - bpp = 4 + enable_a2 = True + enable_1bpp = True - def __init__(self, driver, font=defaultfont, fontsize=defaultsize, partial=None, encoding='utf-8', spacing=0, cursor=None, vcom=None): + def __init__(self, driver, font=defaultfont, fontsize=defaultsize, partial=None, encoding='utf-8', spacing=0, cursor=None, vcom=None, enable_a2=True, enable_1bpp=True): """Create a PaperTTY with the chosen driver and settings""" self.driver = get_drivers()[driver]['class']() self.spacing = spacing @@ -84,6 +85,8 @@ def __init__(self, driver, font=defaultfont, fontsize=defaultsize, partial=None, self.encoding = encoding self.cursor = cursor self.vcom = vcom + self.enable_a2 = enable_a2 + self.enable_1bpp = enable_1bpp def ready(self): """Check that the driver is loaded and initialized""" @@ -241,7 +244,7 @@ def recalculate_font(self, font): def init_display(self): """Initialize the display - call the driver's init method""" - self.driver.init(partial=self.partial, vcom=self.vcom) + self.driver.init(partial=self.partial, vcom=self.vcom, enable_a2=self.enable_a2, enable_1bpp=self.enable_1bpp) self.initialized = True def fit(self, portrait=False): @@ -430,9 +433,9 @@ def showtext(self, text, fill, cursor=None, portrait=False, flipx=False, flipy=F if oldimage and self.driver.supports_partial and self.partial: # create a bounding box of the altered region and # make the X coordinates divisible by 8 - if self.bpp == 1: - xdiv = 32 - ydiv = 16 + if self.driver.supports_1bpp and self.driver.enable_1bpp: + xdiv = self.driver.align_1bpp_width + ydiv = self.driver.align_1bpp_height else: xdiv = 8 ydiv = 1 @@ -685,9 +688,11 @@ def fb(settings, fb_num, rotate, invert, sleep, fullevery): @click.option('--attributes', is_flag=True, default=False, help='Use attributes', show_default=True) @click.option('--interactive', is_flag=True, default=False, help='Interactive mode') @click.option('--vcom', default=None, help='VCOM as positive value x 1000. eg. 1460 = -1.46V') +@click.option('--disable_a2', is_flag=True, default=False, help='Disable fast A2 panel refresh for black and white images') +@click.option('--disable_1bpp', is_flag=True, default=False, help='Disable fast 1bpp mode') @click.pass_obj def terminal(settings, vcsa, font, fontsize, noclear, nocursor, cursor, sleep, ttyrows, ttycols, portrait, flipx, flipy, - spacing, apply_scrub, autofit, attributes, interactive, vcom): + spacing, apply_scrub, autofit, attributes, interactive, vcom, disable_a2, disable_1bpp): """Display virtual console on an e-Paper display, exit with Ctrl-C.""" settings.args['font'] = font settings.args['fontsize'] = fontsize @@ -707,6 +712,9 @@ def terminal(settings, vcsa, font, fontsize, noclear, nocursor, cursor, sleep, t print("VCOM should be a positive number. It will be converted automatically. eg. For a value of -1.46V, set VCOM to 1460") sys.exit(1) settings.args['vcom'] = vcom + + settings.args['enable_a2'] = not disable_a2 + settings.args['enable_1bpp'] = not disable_1bpp if cursor == 'default' or cursor == 'legacy': settings.args['cursor'] = 'default' @@ -717,9 +725,6 @@ def terminal(settings, vcsa, font, fontsize, noclear, nocursor, cursor, sleep, t ptty = settings.get_init_tty() - isIt8951 = isinstance(ptty.driver, driver_it8951.IT8951) - ptty.bpp = 1 if isIt8951 else 4 - if apply_scrub: ptty.driver.scrub() oldbuff = '' @@ -836,10 +841,10 @@ def sigusr1_handler(sig, frame): elif ch == 'r': if oldimage: ptty.driver.reset() - ptty.driver.init(partial=False, vcom=self.vcom) + ptty.driver.init(partial=False, vcom=self.vcom, enable_a2=self.enable_a2, enable_1bpp=self.enable_1bpp) ptty.driver.draw(0, 0, oldimage) ptty.driver.reset() - ptty.driver.init(partial=ptty.partial, vcom=self.vcom) + ptty.driver.init(partial=ptty.partial, vcom=self.vcom, enable_a2=self.enable_a2, enable_1bpp=self.enable_1bpp) # if user or SIGUSR1 toggled the scrub flag, scrub display and start with a fresh image if flags['scrub_requested']: From 8083dd85093043dc7100105d42e03ca12e6f5cb4 Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:46:38 +1100 Subject: [PATCH 11/13] Added comments for 1bpp mode code --- papertty/drivers/driver_it8951.py | 49 +++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/papertty/drivers/driver_it8951.py b/papertty/drivers/driver_it8951.py index d8ca674..db14aac 100644 --- a/papertty/drivers/driver_it8951.py +++ b/papertty/drivers/driver_it8951.py @@ -310,34 +310,57 @@ def draw(self, x, y, image, update_mode_override=None): self.wait_for_display_ready() - #If fullscreen image or dims are divisible by 32, set to 1bpp. - #Otherwise, set to 4bpp. - if width == self.width and height == self.height: - bpp = 1 - elif width % 32 == 0 and height % 16 == 0: - bpp = 1 - else: - bpp = 4 - - if bpp == 1 and image.mode == "1" and x % 16 == 0: + #Set to 4bpp by default + bpp = 4 + + #However, if the right conditions are met, switch to 1bpp mode. + #I'm not 100% sure that all of these are the exact conditions, but they appear + #to work consistently during testing. + #Conditions are: + #-1bpp mode is enabled in papertty (self.enable_1bpp) + #-image is black and white (image.mode == "1") + #-x coordinate is on an expected boundary + #-image is fullscreen OR width and height are divisible by the expected boundaries + if self.enable_1bpp and image.mode == "1" and x % self.align_1bpp_width == 0: + if width == self.width and height == self.height: + bpp = 1 + elif width % self.align_1bpp_width == 0 and height % self.align_1bpp_height == 0: + bpp = 1 + + #Once we're sure that the panel can handle this image in 1bpp mode, it's time to + #put the panel in 1bpp mode + if bpp == 1: - #1bpp actually requires the panel to be set in 8bpp mode + #Confusingly, 1bpp actually requires the use of the 8bpp flag bpp_mode = self.BPP_8 + #If the panel isn't already in 1bpp mode, write these specific commands to the + #register to put it into 1bpp mode. + #This is the important bit which actually puts it in 1bpp in spite of the 8bpp flag if not self.in_bpp1_mode: self.write_register(self.REG_UP1SR+2, self.read_register(self.REG_UP1SR+2) | (1<<2) ) self.in_bpp1_mode = True - + + #Also write the black and white color table for 1bpp mode self.write_register(self.REG_BGVR, (self.Front_Gray_Val<<8) | self.Back_Gray_Val) + else: + #If we're not in 1bpp mode, default to 4bpp. + #In theory we could instead go with 8bpp or 2bpp. + #But 8bpp would be a waste for a non-color panel. + #And 2bpp's usefulness seems quite limited as it doesn't add enough levels of gray + #to be worth the extra bit cost. + #So 1bpp and 4bpp seem like the best options for now. + bpp = 4 bpp_mode = self.BPP_4 - #If the last write was in 1bpp mode, unset that register + #If the last write was in 1bpp mode, unset that register to take it out of 1bpp mode. if self.in_bpp1_mode: self.write_register(self.REG_UP1SR+2, self.read_register(self.REG_UP1SR+2) & ~(1<<2) ) self.in_bpp1_mode = False + #Then write the expected registers for 4bpp mode. self.write_register( self.REG_MEMORY_CONV_LISAR + 2, (self.img_addr >> 16) & 0xFFFF) self.write_register(self.REG_MEMORY_CONV_LISAR, self.img_addr & 0xFFFF) From a9860bf041a62f979d08b7369c48c5acaf2633c9 Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:50:51 +1100 Subject: [PATCH 12/13] README update --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2579b7e..7fa8eee 100644 --- a/README.md +++ b/README.md @@ -428,6 +428,8 @@ Option | Description | Default `--scrub` | Apply scrub when starting | disabled `--autofit` | Try to automatically set terminal rows/cols for the font | disabled `--vcom` | Set the VCOM value of the panel. Entered as positive value x 1000. eg. 1460 = -1.46V | *no default* +`--disable_a2` | Disable fast A2 panel refresh for black and white images | disabled +`--disable_1bpp` | Disable fast 1bpp mode | disabled ```sh From 3a27dee6f937b78d4f45e840fe3d12e07bf1cd40 Mon Sep 17 00:00:00 2001 From: mcarr <136939846+mcarr823@users.noreply.github.com> Date: Sat, 7 Oct 2023 09:18:03 +1100 Subject: [PATCH 13/13] Disable 1bpp and a2 for vnc and images for the time being. It's only really relevant for terminal mode right now. There's no flag for vnc to run in black and white, and images are static so they don't benefit from faster refresh. Both should be possible to implement, but require further work to run well --- papertty/papertty.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/papertty/papertty.py b/papertty/papertty.py index cc316f1..da76f92 100755 --- a/papertty/papertty.py +++ b/papertty/papertty.py @@ -635,6 +635,10 @@ def image(settings, image_location, stretch, no_resize, fill_color, mirror, flip image = Image.open(image_data) else: image = Image.open(image_location) + + #Disable 1bpp and a2 by default if not using terminal mode + settings.args['enable_a2'] = False + settings.args['enable_1bpp'] = False ptty = settings.get_init_tty() display_image(ptty.driver, image, stretch=stretch, no_resize=no_resize, fill_color=fill_color, rotate=rotate, mirror=mirror, flip=flip) @@ -651,6 +655,11 @@ def image(settings, image_location, stretch, no_resize, fill_color, mirror, flip @click.pass_obj def vnc(settings, host, display, password, rotate, invert, sleep, fullevery): """Display a VNC desktop""" + + #Disable 1bpp and a2 by default if not using terminal mode + settings.args['enable_a2'] = False + settings.args['enable_1bpp'] = False + ptty = settings.get_init_tty() ptty.showvnc(host, display, password, int(rotate) if rotate else None, invert, sleep, fullevery)