diff --git a/library/ST7735/__init__.py b/library/ST7735/__init__.py index e547645..943fa5e 100644 --- a/library/ST7735/__init__.py +++ b/library/ST7735/__init__.py @@ -98,28 +98,46 @@ 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): - """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') - color = ((pb[:, :, 0] & 0xF8) << 8) | ((pb[:, :, 1] & 0xFC) << 3) | (pb[:, :, 2] >> 3) - return np.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist() +# 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): + if not isinstance(image, np.ndarray): + if image.mode != 'RGB': + image = image.convert('RGB') + pb = np.array(image, 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): """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 +151,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 +172,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 +305,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,30 +394,45 @@ 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): - """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, self._rotation)) + + 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)