From 46854f9e5832dae41026e974eae5d512a1e52aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Santisi?= Date: Wed, 1 Dec 2021 15:26:33 +0000 Subject: [PATCH 1/7] Handling rotation and mirroring through MADCTL --- library/ST7735/__init__.py | 59 ++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/library/ST7735/__init__.py b/library/ST7735/__init__.py index e547645..eb46d87 100644 --- a/library/ST7735/__init__.py +++ b/library/ST7735/__init__.py @@ -98,19 +98,23 @@ ST7735_YELLOW = 0xFFE0 # 0b 11111 111111 00000 ST7735_WHITE = 0xFFFF # 0b 11111 111111 11111 - -def color565(r, g, b): - """Convert red, green, blue components to a 16-bit 565 RGB value. Components - should be values 0 to 255. - """ - return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) - - -def image_to_data(image, rotation=0): +# Rotation modes +ST7735_MADCTL_MY = 0x80 +ST7735_MADCTL_MX = 0x40 +ST7735_MADCTL_MV = 0x20 +ST7735_MADCTL_ROTATIONS = { + 0: 0, + 90: ST7735_MADCTL_MX | ST7735_MADCTL_MV, + 180: ST7735_MADCTL_MY | ST7735_MADCTL_MX, + 270: ST7735_MADCTL_MY | ST7735_MADCTL_MV, +} + + +def image_to_data(image): """Generator function to convert a PIL image to 16-bit 565 RGB bytes.""" # NumPy is much faster at doing this. NumPy code provided by: # Keith (https://www.blogger.com/profile/02555547344016007163) - pb = np.rot90(np.array(image.convert('RGB')), rotation // 90).astype('uint16') + pb = np.array(image.convert('RGB')).astype('uint16') color = ((pb[:, :, 0] & 0xF8) << 8) | ((pb[:, :, 1] & 0xFC) << 3) | (pb[:, :, 2] >> 3) return np.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist() @@ -119,7 +123,8 @@ class ST7735(object): """Representation of an ST7735 TFT LCD.""" def __init__(self, port, cs, dc, backlight=None, rst=None, width=ST7735_TFTWIDTH, - height=ST7735_TFTHEIGHT, rotation=90, offset_left=None, offset_top=None, invert=True, spi_speed_hz=4000000): + height=ST7735_TFTHEIGHT, rotation=90, mirror=False, offset_left=None, + offset_top=None, invert=True, spi_speed_hz=4000000): """Create an instance of the display using SPI communication. Must provide the GPIO pin number for the D/C pin and the SPI driver. @@ -133,6 +138,7 @@ def __init__(self, port, cs, dc, backlight=None, rst=None, width=ST7735_TFTWIDTH :param width: Width of display connected to ST7735 :param height: Height of display connected to ST7735 :param rotation: Rotation of display connected to ST7735 + :param mirror: Mirror of display connected to ST7735 :param offset_left: COL offset in ST7735 memory :param offset_top: ROW offset in ST7735 memory :param invert: Invert display @@ -153,6 +159,7 @@ def __init__(self, port, cs, dc, backlight=None, rst=None, width=ST7735_TFTWIDTH self._width = width self._height = height self._rotation = rotation + self._mirror = mirror self._invert = invert # Default left offset to center display @@ -285,7 +292,10 @@ def _init(self): self.command(ST7735_INVOFF) # Don't invert display self.command(ST7735_MADCTL) # Memory access control (directions) - self.data(0xC8) # row addr/col addr, bottom to top refresh + madctl = ST7735_MADCTL_ROTATIONS[self._rotation] | 0x08 # rgb + if self._mirror: + madctl ^= ST7735_MADCTL_MX + self.data(madctl) self.command(ST7735_COLMOD) # set color mode self.data(0x05) # 16-bit color @@ -371,16 +381,23 @@ def set_window(self, x0=0, y0=0, x1=None, y1=None): x0 += self._offset_left x1 += self._offset_left + if self._rotation % 180: + r0, r1 = x0, x1 + c0, c1 = y0, y1 + else: + c0, c1 = x0, x1 + r0, r1 = y0, y1 + self.command(ST7735_CASET) # Column addr set - self.data(x0 >> 8) - self.data(x0) # XSTART - self.data(x1 >> 8) - self.data(x1) # XEND + self.data(c0 >> 8) + self.data(c0) # COLSTART + self.data(c1 >> 8) + self.data(c1) # COLEND self.command(ST7735_RASET) # Row addr set - self.data(y0 >> 8) - self.data(y0) # YSTART - self.data(y1 >> 8) - self.data(y1) # YEND + self.data(r0 >> 8) + self.data(r0) # ROWSTART + self.data(r1 >> 8) + self.data(r1) # ROWEND self.command(ST7735_RAMWR) # write to RAM def display(self, image): @@ -395,6 +412,6 @@ def display(self, image): # Unfortunate that this copy has to occur, but the SPI byte writing # function needs to take an array of bytes and PIL doesn't natively # store images in 16-bit 565 RGB format. - pixelbytes = list(image_to_data(image, self._rotation)) + pixelbytes = list(image_to_data(image)) # Write data to hardware. self.data(pixelbytes) From d68015c2d4ac3508c95fd034c9688bf9e3904175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Santisi?= Date: Wed, 1 Dec 2021 15:47:19 +0000 Subject: [PATCH 2/7] Indentation changes for Stickier CI --- library/ST7735/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/ST7735/__init__.py b/library/ST7735/__init__.py index eb46d87..5057cbc 100644 --- a/library/ST7735/__init__.py +++ b/library/ST7735/__init__.py @@ -123,8 +123,8 @@ class ST7735(object): """Representation of an ST7735 TFT LCD.""" def __init__(self, port, cs, dc, backlight=None, rst=None, width=ST7735_TFTWIDTH, - height=ST7735_TFTHEIGHT, rotation=90, mirror=False, offset_left=None, - offset_top=None, invert=True, spi_speed_hz=4000000): + height=ST7735_TFTHEIGHT, rotation=90, mirror=False, offset_left=None, + offset_top=None, invert=True, spi_speed_hz=4000000): """Create an instance of the display using SPI communication. Must provide the GPIO pin number for the D/C pin and the SPI driver. @@ -292,7 +292,7 @@ def _init(self): self.command(ST7735_INVOFF) # Don't invert display self.command(ST7735_MADCTL) # Memory access control (directions) - madctl = ST7735_MADCTL_ROTATIONS[self._rotation] | 0x08 # rgb + madctl = ST7735_MADCTL_ROTATIONS[self._rotation] | 0x08 # rgb if self._mirror: madctl ^= ST7735_MADCTL_MX self.data(madctl) From f940b1b265fbecc61a594318d8001df98935b2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Santisi?= Date: Wed, 1 Dec 2021 15:48:57 +0000 Subject: [PATCH 3/7] Indentation changes for Stickier CI --- library/ST7735/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/ST7735/__init__.py b/library/ST7735/__init__.py index 5057cbc..cdc3394 100644 --- a/library/ST7735/__init__.py +++ b/library/ST7735/__init__.py @@ -123,8 +123,7 @@ class ST7735(object): """Representation of an ST7735 TFT LCD.""" def __init__(self, port, cs, dc, backlight=None, rst=None, width=ST7735_TFTWIDTH, - height=ST7735_TFTHEIGHT, rotation=90, mirror=False, offset_left=None, - offset_top=None, invert=True, spi_speed_hz=4000000): + height=ST7735_TFTHEIGHT, rotation=90, mirror=False, offset_left=None, offset_top=None, invert=True, spi_speed_hz=4000000): """Create an instance of the display using SPI communication. Must provide the GPIO pin number for the D/C pin and the SPI driver. From 1bf1f179ad55387f9ed08e34ae471940487676bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Santisi?= Date: Wed, 1 Dec 2021 15:50:28 +0000 Subject: [PATCH 4/7] Indentation changes for Stickier CI --- library/ST7735/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/ST7735/__init__.py b/library/ST7735/__init__.py index cdc3394..8d7cf29 100644 --- a/library/ST7735/__init__.py +++ b/library/ST7735/__init__.py @@ -123,7 +123,8 @@ class ST7735(object): """Representation of an ST7735 TFT LCD.""" def __init__(self, port, cs, dc, backlight=None, rst=None, width=ST7735_TFTWIDTH, - height=ST7735_TFTHEIGHT, rotation=90, mirror=False, offset_left=None, offset_top=None, invert=True, spi_speed_hz=4000000): + height=ST7735_TFTHEIGHT, rotation=90, mirror=False, offset_left=None, + offset_top=None, invert=True, spi_speed_hz=4000000): """Create an instance of the display using SPI communication. Must provide the GPIO pin number for the D/C pin and the SPI driver. From 954fd6ee3e40ca547b96e8342b0002e340466033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Santisi?= Date: Mon, 6 Dec 2021 12:45:21 +0000 Subject: [PATCH 5/7] Porting image_to_data from ST7789 --- library/ST7735/__init__.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/library/ST7735/__init__.py b/library/ST7735/__init__.py index 8d7cf29..df0724d 100644 --- a/library/ST7735/__init__.py +++ b/library/ST7735/__init__.py @@ -111,12 +111,23 @@ def image_to_data(image): - """Generator function to convert a PIL image to 16-bit 565 RGB bytes.""" - # NumPy is much faster at doing this. NumPy code provided by: - # Keith (https://www.blogger.com/profile/02555547344016007163) - pb = np.array(image.convert('RGB')).astype('uint16') - color = ((pb[:, :, 0] & 0xF8) << 8) | ((pb[:, :, 1] & 0xFC) << 3) | (pb[:, :, 2] >> 3) - return np.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist() + if not isinstance(image, np.ndarray): + pb = np.array(image.convert('RGB'), dtype='uint16') + elif image.dtype != 'uint16': + pb = image.astype('uint16') + else: + pb = image + + # Mask and shift the 888 RGB into 565 RGB + red = (pb[..., [0]] & 0xF8) << 8 + green = (pb[..., [1]] & 0xFC) << 3 + blue = (pb[..., [2]] & 0xF8) >> 3 + + # Stick 'em together + result = red | green | blue + + # Output the raw bytes + return result.byteswap().tobytes() class ST7735(object): From a48271c9119177209859fc4f41a6f0a0e3cab775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Santisi?= Date: Mon, 6 Dec 2021 12:47:21 +0000 Subject: [PATCH 6/7] Making Stickler happy --- library/ST7735/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/ST7735/__init__.py b/library/ST7735/__init__.py index df0724d..25022f0 100644 --- a/library/ST7735/__init__.py +++ b/library/ST7735/__init__.py @@ -119,9 +119,9 @@ def image_to_data(image): pb = image # Mask and shift the 888 RGB into 565 RGB - red = (pb[..., [0]] & 0xF8) << 8 + red = (pb[..., [0]] & 0xF8) << 8 green = (pb[..., [1]] & 0xFC) << 3 - blue = (pb[..., [2]] & 0xF8) >> 3 + blue = (pb[..., [2]] & 0xF8) >> 3 # Stick 'em together result = red | green | blue From 2db821bc04ab8d18197e3582f0ec4478332e9300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Santisi?= Date: Mon, 6 Dec 2021 21:17:48 +0000 Subject: [PATCH 7/7] raw data in display() --- library/ST7735/__init__.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/library/ST7735/__init__.py b/library/ST7735/__init__.py index 25022f0..943fa5e 100644 --- a/library/ST7735/__init__.py +++ b/library/ST7735/__init__.py @@ -112,7 +112,9 @@ def image_to_data(image): if not isinstance(image, np.ndarray): - pb = np.array(image.convert('RGB'), dtype='uint16') + if image.mode != 'RGB': + image = image.convert('RGB') + pb = np.array(image, dtype='uint16') elif image.dtype != 'uint16': pb = image.astype('uint16') else: @@ -412,17 +414,25 @@ def set_window(self, x0=0, y0=0, x1=None, y1=None): self.command(ST7735_RAMWR) # write to RAM def display(self, image): - """Write the provided image to the hardware. + """Write the provided image to the hardware. image could be a PIL image, + a numpy array or raw data. If image is a PIL image it must be the same + dimensions as the display hardware. If image is a numpy array it must be + shape (width, height, 3). If the image is raw data it could be a bytes + string or a uint8 list both of width*height*2 length of RGB565 colors + encoded in big-endian format. - :param image: Should be RGB format and the same dimensions as the display hardware. + :param image: Should the same dimensions as the display hardware. """ # Set address bounds to entire display. self.set_window() - # Convert image to array of 16bit 565 RGB data bytes. - # Unfortunate that this copy has to occur, but the SPI byte writing - # function needs to take an array of bytes and PIL doesn't natively - # store images in 16-bit 565 RGB format. - pixelbytes = list(image_to_data(image)) + + if not isinstance(image, (list, bytes)): + # Convert image to array of 16bit 565 RGB data bytes. + # Unfortunate that this copy has to occur, but the SPI byte writing + # function needs to take an array of bytes and PIL doesn't natively + # store images in 16-bit 565 RGB format. + image = image_to_data(image) + # Write data to hardware. - self.data(pixelbytes) + self.data(image)