diff --git a/adafruit_ra8875/bmp.py b/adafruit_ra8875/bmp.py new file mode 100644 index 0000000..4c95b7f --- /dev/null +++ b/adafruit_ra8875/bmp.py @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-License-Identifier: MIT +# 2024 Optimized with ChatGPT by DJDevon3 +# https://chat.openai.com/share/57ee2bb5-33ba-4538-a4b7-ec3dea8ea5c7 +""" Raw Bitmap Helper Class (not hardware accelerated)""" + +import struct + + +class BMP: + """ + Raw Bitmap Helper Class (not hardware accelerated) + + :param str: filename BMP filename + :param int colors: BMP color data + :param int data: BMP data + :param int data_size: BMP data size + :param int bpp: BMP bit depth data + :param int width: BMP width + :param int height: BMP height + :param int read_header: BMP read header function + + """ + + def __init__(self, filename, debug: bool = False): + self.filename = filename + self.colors = None + self.data = None + self.data_size = 0 + self.bpp = 0 + self.width = 0 + self.height = 0 + self.debug = debug # Store debug mode + self.read_header() + + def read_header(self): + """Read file header data""" + if self.colors: + return + with open(self.filename, "rb") as bmp_file: + header_data = bmp_file.read( + 54 + ) # Read the entire BMP header (assuming it's 54 bytes) + self.data = int.from_bytes(header_data[10:14], "little") + self.width = int.from_bytes(header_data[18:22], "little") + self.height = int.from_bytes(header_data[22:26], "little") + self.bpp = int.from_bytes(header_data[28:30], "little") + self.data_size = int.from_bytes(header_data[34:38], "little") + self.colors = int.from_bytes(header_data[46:50], "little") + if self.debug: # Check debug mode + print(f"Header Hex Dump: {header_data}") + print(f"Header Data: {self.data}") + print(f"Header Width: {self.width}") + print(f"Header Height: {self.height}") + print(f"Header BPP: {self.bpp}") + print(f"Header Size: {self.data_size}") + print(f"Header Colors: {self.colors}") + + def draw_bmp(self, disp, x: int = 0, y: int = 0, chunk_size: int = 1): + """Draw BMP""" + if self.debug: # Check debug mode + print(f"{self.width}x{self.height} image") + print(f"{self.bpp}-bit encoding detected") + + with open(self.filename, "rb") as bmp_file: + bmp_file.seek(self.data) + line_data = bmp_file.read() + + for start_line in range(0, self.height, chunk_size): + end_line = min(start_line + chunk_size, self.height) + current_line_data = b"" + for line in range(start_line, end_line): + line_start = line * self.width * (self.bpp // 8) + line_end = line_start + self.width * (self.bpp // 8) + for i in range(line_start, line_end, self.bpp // 8): + if (line_end - i) < self.bpp // 8: + break + if self.bpp == 16: + color = self.convert_555_to_565( + line_data[i] | line_data[i + 1] << 8 + ) + elif self.bpp in (24, 32): + color = self.color565( + line_data[i + 2], line_data[i + 1], line_data[i] + ) + current_line_data += struct.pack(">H", color) + disp.setxy(x, self.height - end_line + y) + disp.push_pixels(current_line_data) + + @staticmethod + def convert_555_to_565(color_555): + """Convert 16-bit color from 5-5-5 to 5-6-5 format""" + r = (color_555 & 0x1F) << 3 + g = ((color_555 >> 5) & 0x1F) << 2 + b = ((color_555 >> 10) & 0x1F) << 3 + return (r << 11) | (g << 5) | b + + @staticmethod + def color565(r, g, b): + """Convert 24-bit RGB color to 16-bit color (5-6-5 format)""" + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) diff --git a/adafruit_ra8875/ra8875.py b/adafruit_ra8875/ra8875.py index a4f5268..c65956d 100755 --- a/adafruit_ra8875/ra8875.py +++ b/adafruit_ra8875/ra8875.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2019 Melissa LeBlanc-Williams for Adafruit Industries # # SPDX-License-Identifier: MIT - +# pylint: disable=too-many-lines """ `adafruit_ra8875.ra8875` ==================================================== @@ -31,7 +31,7 @@ import time from adafruit_bus_device import spi_device -import adafruit_ra8875.registers as reg +from adafruit_ra8875 import registers as reg try: from typing import Optional, Tuple, Union @@ -57,6 +57,18 @@ def color565(r: int, g: int = 0, b: int = 0) -> int: # pylint: enable-msg=invalid-name +# pylint: disable-msg=invalid-name +def convert_555_to_565(color_555): + """Convert 16-bit color from 5-5-5 to 5-6-5 format""" + r = (color_555 & 0x1F) << 3 + g = ((color_555 >> 5) & 0x1F) << 2 + b = ((color_555 >> 10) & 0x1F) << 3 + return (r << 11) | (g << 5) | b + + +# pylint: enable-msg=invalid-name + + class RA8875_Device: """ Base Class for the Display. Contains all the low level stuff. As well @@ -111,7 +123,11 @@ def init(self, start_on: bool = True) -> None: :param bool start_on: (optional) If the display should start in an On State (default=True) """ + + hw_init = True + release_displays = True hsync_finetune = 0 + if self.width == 480 and self.height == 82: self.vert_offset = 190 @@ -136,47 +152,50 @@ def init(self, start_on: bool = True) -> None: raise ValueError("An invalid display size was specified.") self.pllinit() - self._write_reg(reg.SYSR, reg.SYSR_16BPP | reg.SYSR_MCU8) - self._write_reg(reg.PCSR, pixclk) - time.sleep(0.001) - # Horizontal settings registers - self._write_reg(reg.HDWR, self.width // 8 - 1) - self._write_reg(reg.HNDFTR, reg.HNDFTR_DE_HIGH + hsync_finetune) - self._write_reg(reg.HNDR, (hsync_nondisp - hsync_finetune - 2) // 8) - self._write_reg(reg.HSTR, hsync_start // 8 - 1) - self._write_reg(reg.HPWR, reg.HPWR_LOW + (hsync_pw // 8 - 1)) - - # Vertical settings registers - self._write_reg16(reg.VDHR0, (self.height - 1 + self.vert_offset) & 0xFF) - self._write_reg16(reg.VDHR1, (self.height - 1 + self.vert_offset) >> 8) - self._write_reg16(reg.VNDR0, vsync_nondisp - 1) - self._write_reg16(reg.VNDR1, vsync_nondisp >> 8) - self._write_reg16(reg.VSTR0, vsync_start - 1) - self._write_reg16(reg.VSTR1, vsync_start >> 8) - self._write_reg(reg.VPWR, reg.VPWR_LOW + vsync_pw - 1) - - # Set active window X - self._write_reg16(reg.HSAW0, 0) - self._write_reg16(reg.HSAW1, 0) - self._write_reg16(reg.HEAW0, (self.width - 1) & 0xFF) - self._write_reg16(reg.HEAW1, (self.width - 1) >> 8) - - # Set active window Y - self._write_reg16(reg.VSAW0, self.vert_offset) - self._write_reg16(reg.VSAW1, self.vert_offset) - self._write_reg16(reg.VEAW0, (self.height - 1 + self.vert_offset) & 0xFF) - self._write_reg16(reg.VEAW1, (self.height - 1 + self.vert_offset) >> 8) - - # Clear the entire window - self._write_reg(reg.MCLR, reg.MCLR_START | reg.MCLR_FULL) - time.sleep(0.500) - - # Turn the display on, enable GPIO, and setup the backlight - self.turn_on(start_on) - self._gpiox(True) - self._pwm1_config(True, reg.PWM_CLK_DIV1024) - self.brightness(255) + if hw_init: # Bits Per Pixel: 16 | Bus: 8-Bit + self._write_reg(reg.SYSR, reg.SYSR_16BPP | reg.SYSR_MCU8) + self._write_reg(reg.PCSR, pixclk) + time.sleep(0.001) + + if release_displays: # equivilent to displayio release_displays() + # Horizontal settings registers + self._write_reg(reg.HDWR, self.width // 8 - 1) + self._write_reg(reg.HNDFTR, reg.HNDFTR_DE_HIGH + hsync_finetune) + self._write_reg(reg.HNDR, (hsync_nondisp - hsync_finetune - 2) // 8) + self._write_reg(reg.HSTR, hsync_start // 8 - 1) + self._write_reg(reg.HPWR, reg.HPWR_LOW + hsync_pw // 8 - 1) + + # Vertical settings registers + self._write_reg16(reg.VDHR0, (self.height - 1 + self.vert_offset) & 0xFF) + self._write_reg16(reg.VDHR1, (self.height - 1 + self.vert_offset) >> 8) + self._write_reg16(reg.VNDR0, vsync_nondisp - 1) + self._write_reg16(reg.VNDR1, vsync_nondisp >> 8) + self._write_reg16(reg.VSTR0, vsync_start - 1) + self._write_reg16(reg.VSTR1, vsync_start >> 8) + self._write_reg(reg.VPWR, reg.VPWR_LOW + vsync_pw - 1) + + # Set active window X + self._write_reg16(reg.HSAW0, 0) + self._write_reg16(reg.HSAW1, 0) + self._write_reg16(reg.HEAW0, (self.width - 1) & 0xFF) + self._write_reg16(reg.HEAW1, (self.width - 1) >> 8) + + # Set active window Y + self._write_reg16(reg.VSAW0, self.vert_offset) + self._write_reg16(reg.VSAW1, self.vert_offset) + self._write_reg16(reg.VEAW0, (self.height - 1 + self.vert_offset) & 0xFF) + self._write_reg16(reg.VEAW1, (self.height - 1 + self.vert_offset) >> 8) + + # Clear the entire window + self._write_reg(reg.MCLR, reg.MCLR_START | reg.MCLR_FULL) + time.sleep(0.500) + + # Turn the display on, enable GPIO, and setup the backlight + self.turn_on(start_on) + self._gpiox(True) + self._pwm1_config(True, reg.PWM_CLK_DIV1024) + self.brightness(255) def pllinit(self) -> None: """Init the Controller PLL""" @@ -185,7 +204,9 @@ def pllinit(self) -> None: self._write_reg(reg.PLLC2, reg.PLLC2_DIV4) time.sleep(0.001) - def _write_reg(self, cmd: int, data: int, raw: bool = False) -> None: + def _write_reg( + self, cmd: int, data: int, raw: bool = False, debug: bool = False + ) -> None: """ Select a Register and write a byte or push raw data out @@ -194,10 +215,14 @@ def _write_reg(self, cmd: int, data: int, raw: bool = False) -> None: :type data: byte or bytearray :param bool raw: (optional) Is the data a raw bytearray (default=False) """ + if debug: + print(f"_write_reg: cmd={cmd}, data={data}, raw={raw}") self._write_cmd(cmd) self._write_data(data, raw) - def _write_reg16(self, cmd: int, data: Union[int, bytearray]) -> None: + def _write_reg16( + self, cmd: int, data: Union[int, bytearray], debug: bool = False + ) -> None: """ Select a Register and write 2 bytes or push raw data out @@ -205,22 +230,26 @@ def _write_reg16(self, cmd: int, data: Union[int, bytearray]) -> None: :param data: The byte to write to the register :type data: byte or bytearray """ + if debug: + print(f"_write_reg16: cmd={cmd}, data={data}") self._write_cmd(cmd) self._write_data(data) self._write_cmd(cmd + 1) self._write_data(data >> 8) - def _write_cmd(self, cmd: int) -> None: + def _write_cmd(self, cmd: int, debug: bool = False) -> None: """ Select a Register/Command :param byte cmd: The register to select """ + if debug: + print(f"_write_cmd: cmd={cmd}") with self.spi_device as spi: spi.write(reg.CMDWR) # pylint: disable=no-member spi.write(bytearray([cmd & 0xFF])) # pylint: disable=no-member - def _write_data(self, data: int, raw: bool = False) -> None: + def _write_data(self, data: int, raw: bool = False, debug: bool = False) -> None: """ Write a byte or push raw data out using the previously selected register @@ -228,6 +257,8 @@ def _write_data(self, data: int, raw: bool = False) -> None: :type data: byte or bytearray :param bool raw: (optional) Is the data a raw bytearray (default=False) """ + if debug: + print(f"_write_data: data={data}, raw={raw}") with self.spi_device as spi: spi.write(reg.DATWR) # pylint: disable=no-member if raw and isinstance(data, str): @@ -236,7 +267,7 @@ def _write_data(self, data: int, raw: bool = False) -> None: data if raw else bytearray([data & 0xFF]) ) # pylint: disable=no-member - def _read_reg(self, cmd: int) -> int: + def _read_reg(self, cmd: int, debug: bool = False) -> int: """ Select a Register and read a byte @@ -244,10 +275,12 @@ def _read_reg(self, cmd: int) -> int: :return: The results of the register :rtype: byte """ + if debug: + print(f"_read_reg: cmd={cmd}") self._write_cmd(cmd) return self._read_data() - def _read_data(self) -> int: + def _read_data(self, debug: bool = False) -> int: """ Read the data of the previously selected register @@ -255,11 +288,44 @@ def _read_data(self) -> int: :rtype: byte """ data = bytearray(1) + if debug: + print(f"_read_data: data={data}") with self.spi_device as spi: spi.write(reg.DATRD) # pylint: disable=no-member spi.readinto(data) # pylint: disable=no-member return struct.unpack(">B", data)[0] + def _read_data16(self, debug: bool = False) -> int: + """ + Read 16 bits of data from the previously selected register + + :return: The 16-bit data read from the register + :rtype: int + """ + # Allocate space for 2 bytes + data = bytearray(2) + + if debug: + print("_read_data16: Reading 16 bits of data") + + with self.spi_device as spi: + spi.write(reg.DATRD) # Write the data read command + spi.readinto(data) # Read 2 bytes of data + if debug: + print( + f"_read_data16: (data[0]: {data[0] & 0xFF:08b} " + + f"{(data[0] >> 8) & 0xFF:08b} data[1]: {data[1] & 0xFF:08b} " + + f"{(data[1] >> 8) & 0xFF:08b})" + ) + + # Combine the two bytes into a single 16-bit integer + result = (data[0] << 8) | data[1] + + if debug: + print(f"_read_data16: Read data: {result:#06x} (Binary: {result:016b})") + + return result + def _wait_poll(self, register: int, mask: int) -> bool: """ Keep checking a status bit and wait for an operation to complete. @@ -537,15 +603,20 @@ def set_bgcolor(self, color: int) -> None: self._write_reg(0x61, (color & 0x07E0) >> 5) self._write_reg(0x62, (color & 0x001F)) - def set_color(self, color: int) -> None: + def set_color(self, color: int, debug: bool = False) -> None: """ Set the foreground color for graphics/text :param int color: The of the text or graphics """ - self._write_reg(0x63, (color & 0xF800) >> 11) - self._write_reg(0x64, (color & 0x07E0) >> 5) - self._write_reg(0x65, (color & 0x001F)) + r = (color & 0xF800) >> 11 + g = (color & 0x07E0) >> 5 + b = color & 0x001F + if debug: + print(f"set_color: {color} (R: {r}, G: {g}, B: {b})") + self._write_reg(0x63, r) + self._write_reg(0x64, g) + self._write_reg(0x65, b) # pylint: disable-msg=invalid-name def pixel(self, x: int, y: int, color: int) -> None: @@ -571,29 +642,79 @@ def push_pixels(self, pixel_data: bytearray) -> None: self._write_reg(reg.MRWC, pixel_data, True) # pylint: disable-msg=invalid-name,too-many-arguments - def set_window(self, x: int, y: int, width: int, height: int) -> None: + def set_window( + self, + x1: int, + y1: int, + win1_width: int, + win1_height: int, + color: int, + filled: bool = True, + debug: bool = False, + ) -> None: """ Set an Active Drawing Window, which can be used in conjuntion with push_pixels for faster drawing - :param int x: The X coordinate of the left side of the window - :param int y: The Y coordinate of the top side of the window + :param int x1: The X coordinate of the left side of the window + :param int y1: The Y coordinate of the top side of the window :param int width: The width of the window :param int height: The height of the window + :param Tuple[int, int, int] bg_color: The background color of the window as an RGB tuple """ - if x + width >= self.width: - width = self.width - x - if y + height >= self.height: - height = self.height - y - # X - self._write_reg16(reg.HSAW0, x) - self._write_reg16(reg.HEAW0, x + width) - # Y - self._write_reg16(reg.VSAW0, y) - self._write_reg16(reg.VEAW0, y + height) + if x1 + win1_width >= self.width: + win1_width = self.width - x1 + if y1 + win1_height >= self.height: + win1_height = self.height - y1 + + self._gfx_mode() + + # Set window coordinates + self._write_reg16(reg.HSAW0, x1) + self._write_reg16(reg.HEAW0, x1 + win1_width) + self._write_reg16(reg.VSAW0, y1) + self._write_reg16(reg.VEAW0, y1 + win1_height) + + # Set X and Y + self._write_reg16(0x91, x1) + self._write_reg16(0x93, y1 + self.vert_offset) + + # Set Width and Height + self._write_reg16(0x95, win1_width) + self._write_reg16(0x97, win1_height + self.vert_offset) + + self.set_color(color) + if debug: + print(f"Color: {color}") + + # Draw it + self._write_reg(reg.DCR, 0xB0 if filled else 0x90) + self._wait_poll(reg.DCR, reg.DCR_LNSQTR_STATUS) # pylint: enable-msg=invalid-name,too-many-arguments + def lighten_mode(self): + """Transparency Lighten""" + self._gfx_mode() + self._write_reg(0x52, 0b01010000, debug=False) + + # pylint: disable-msg=invalid-name,too-many-arguments + def draw_cursor(self, x, y, inside_color, outline_color, debug: bool = False): + """Draw 2-color crosshair cursor at x,y position""" + # Draw the outline of the cursor + for i in range(-4, 5): + self.pixel(x + i, y - 4, outline_color) # Top line + self.pixel(x + i, y + 4, outline_color) # Bottom line + self.pixel(x - 4, y + i, outline_color) # Left line + self.pixel(x + 4, y + i, outline_color) # Right line + + # Draw the inside of the cursor + for i in range(-3, 4): + self.pixel(x + i, y, inside_color) # Horizontal line + self.pixel(x, y + i, inside_color) # Vertical line + if debug: + print(f"Drawing Cursor at: {x},{y}") + class RA8875(RA8875Display): """ @@ -602,8 +723,52 @@ class RA8875(RA8875Display): currently 800x480 and 480x272. """ + def read_single_pixel(self, x: int, y: int, debug: bool = False) -> int: + """ + Read the color of a single pixel from layer 0 of the display at x,y coordinates. + + :param int x: X coordinate of the pixel. + :param int y: Y coordinate of the pixel. + :param bool debug: Flag to enable debug printing. + :return: Color of the pixel in RGB format (24-bit). + :rtype: int + """ + # Ensure x and y are within display bounds + if not (0 <= x < self.width and 0 <= y < self.height): + raise ValueError( + f"Coordinates ({x}, {y}) out of bounds for display size {self.width}x{self.height}" + ) + if debug: + print(f"Reading pixel at ({x}, {y})") + + # Set graphics mode + self._gfx_mode() + # Set read cursor position for layer0 (800x480 16-bit mode) + self._write_reg16(reg.RCURH0, x, debug=False) + self._write_reg16(reg.RCURV0, y + self.vert_offset, debug=False) + # Set Memory Read/Write Control + self._read_reg(reg.MRWC) + # Read the color data + dummy = self._read_data16(debug=False) # Dummy read + msb = self._read_data16(debug=False) # MSB read + + # Extract color components from MSB + green = (msb >> 8) & 0xFF # Extracting 8 bits of green component + blue = (msb >> 3) & 0xFF # Extracting 8 bits of blue component + red = (msb << 3) & 0xFF # Extracting 8 bits of red component + if debug: + print(f"Dummy: {dummy} MSB: {msb}") + print(f"Extracted Colors: {red}, {green}, {blue}") + print( + f"Binary values: Red: {red:08b}, Green: {green:08b}, Blue: {blue:08b}" + ) + + return red, green, blue + # pylint: disable-msg=invalid-name,too-many-arguments - def rect(self, x: int, y: int, width: int, height: int, color: int) -> None: + def rect( + self, x: int, y: int, width: int, height: int, color: int, filled: bool = False + ) -> None: """ Draw a rectangle (HW Accelerated) @@ -613,9 +778,11 @@ def rect(self, x: int, y: int, width: int, height: int, color: int) -> None: :param int height: The height of the rectangle :param int color: The color of the rectangle """ - self._rect_helper(x, y, x + width - 1, y + height - 1, color, False) + self._rect_helper(x, y, x + width - 1, y + height - 1, color, filled=filled) - def fill_rect(self, x: int, y: int, width: int, height: int, color: int) -> None: + def fill_rect( + self, x: int, y: int, width: int, height: int, color: int, debug: bool = False + ) -> None: """ Draw a filled rectangle (HW Accelerated) @@ -625,6 +792,8 @@ def fill_rect(self, x: int, y: int, width: int, height: int, color: int) -> None :param int height: The height of the rectangle :param int color: The color of the rectangle """ + if debug: + print(f"fill_rect color: {color}") self._rect_helper(x, y, x + width - 1, y + height - 1, color, True) def fill(self, color: int) -> None: @@ -891,7 +1060,14 @@ def _circle_helper( self._wait_poll(reg.DCR, reg.DCR_CIRC_STATUS) def _rect_helper( - self, x1: int, y1: int, x2: int, y2: int, color: int, filled: bool + self, + x1: int, + y1: int, + x2: int, + y2: int, + color: int, + filled: bool, + debug: bool = False, ) -> None: """General Rectangle Drawing Helper""" self._gfx_mode() @@ -903,7 +1079,8 @@ def _rect_helper( # Set Width and Height self._write_reg16(0x95, x2) self._write_reg16(0x97, y2 + self.vert_offset) - + if debug: + print(f"_rect_helper: color={color}") self.set_color(color) # Draw it @@ -990,5 +1167,3 @@ def _ellipse_helper( # Draw it self._write_reg(reg.ELLIPSE, 0xC0 if filled else 0x80) self._wait_poll(reg.ELLIPSE, reg.ELLIPSE_STATUS) - - # pylint: enable-msg=invalid-name,too-many-arguments diff --git a/adafruit_ra8875/registers.py b/adafruit_ra8875/registers.py index 23ee9b8..2e891b8 100755 --- a/adafruit_ra8875/registers.py +++ b/adafruit_ra8875/registers.py @@ -18,158 +18,175 @@ CMDRD = b"\xC0" # Status Read # Registers and Bits -PWRR = 0x01 -PWRR_DISPON = 0x80 -PWRR_DISPOFF = 0x00 -PWRR_SLEEP = 0x02 -PWRR_NORMAL = 0x00 -PWRR_SOFTRESET = 0x01 -MRWC = 0x02 -GPIOX = 0xC7 - -PLLC1 = 0x88 -PLLC1_PLLDIV1 = 0x00 - -PLLC2 = 0x89 -PLLC2_DIV4 = 0x02 - -SYSR = 0x10 -SYSR_8BPP = 0x00 -SYSR_16BPP = 0x0C -SYSR_MCU8 = 0x00 -SYSR_MCU16 = 0x03 - -PCSR = 0x04 -PCSR_PDATR = 0x00 -PCSR_PDATL = 0x80 -PCSR_CLK = 0x00 -PCSR_2CLK = 0x01 -PCSR_4CLK = 0x02 -PCSR_8CLK = 0x03 - -HDWR = 0x14 - -HNDFTR = 0x15 -HNDFTR_DE_HIGH = 0x00 -HNDFTR_DE_LOW = 0x80 - -HNDR = 0x16 -HSTR = 0x17 -HPWR = 0x18 -HPWR_LOW = 0x00 -HPWR_HIGH = 0x80 - -VDHR0 = 0x19 +PWRR = 0x01 # Power Register +PWRR_DISPON = 0x80 # Display on +PWRR_DISPOFF = 0x00 # Display off +PWRR_SLEEP = 0x02 # Sleep mode +PWRR_NORMAL = 0x00 # Normal mode +PWRR_SOFTRESET = 0x01 # Software reset + +MRWC = 0x02 # Memory Read/Write Control + +GPIOX = 0xC7 # GPIOX + +PLLC1 = 0x88 # PLL Control 1 +PLLC1_PLLDIV1 = 0x00 # PLLDIV1 + +PLLC2 = 0x89 # PLL Control 2 +PLLC2_DIV4 = 0x02 # PLLDIV4 + +SYSR = 0x10 # System Configuration + +SYSR_8BPP = 0x00 # 8 bits per pixel +SYSR_16BPP = 0x0C # 16 bits per pixel +SYSR_MCU8 = 0x00 # MCU 8-bit bus +SYSR_MCU16 = 0x03 # MCU 16-bit bus + +PCSR = 0x04 # PWM and Clock Control + +PCSR_PDATR = 0x00 # PWM: Data from PWM pin +PCSR_PDATL = 0x80 # PWM: Data from PWM register +PCSR_CLK = 0x00 # PWM: System clock +PCSR_2CLK = 0x01 # PWM: 2 System clocks +PCSR_4CLK = 0x02 # PWM: 4 System clocks +PCSR_8CLK = 0x03 # PWM: 8 System clocks + +HDWR = 0x14 # Hardware Width + +HNDFTR = 0x15 # Non-Display Area Start + +HNDFTR_DE_HIGH = 0x00 # DE signal high +HNDFTR_DE_LOW = 0x80 # DE signal low + +HNDR = 0x16 # Non-Display Area End +HSTR = 0x17 # HSYNC Start +HPWR = 0x18 # HSYNC Pulse Width +HPWR_LOW = 0x00 # HSYNC Low +HPWR_HIGH = 0x80 # HSYNC High + +VDHR0 = 0x19 # Vertical Start VDHR1 = 0x1A -VNDR0 = 0x1B +VNDR0 = 0x1B # Vertical End VNDR1 = 0x1C -VSTR0 = 0x1D +VSTR0 = 0x1D # VSYNC Start VSTR1 = 0x1E -VPWR = 0x1F -VPWR_LOW = 0x00 -VPWR_HIGH = 0x80 - -FNCR0 = 0x21 -FNCR1 = 0x22 - -HSAW0 = 0x30 -HSAW1 = 0x31 -VSAW0 = 0x32 -VSAW1 = 0x33 - -HEAW0 = 0x34 -HEAW1 = 0x35 -VEAW0 = 0x36 -VEAW1 = 0x37 - -MCLR = 0x8E -MCLR_START = 0x80 -MCLR_STOP = 0x00 -MCLR_READSTATUS = 0x80 -MCLR_FULL = 0x00 -MCLR_ACTIVE = 0x40 - -DCR = 0x90 -DCR_LNSQTR_START = 0x80 -DCR_LNSQTR_STOP = 0x00 -DCR_LNSQTR_STATUS = 0x80 -DCR_CIRC_START = 0x40 -DCR_CIRC_STATUS = 0x40 -DCR_CIRC_STOP = 0x00 -DCR_FILL = 0x20 -DCR_NOFILL = 0x00 -DCR_DRAWLN = 0x00 -DCR_DRAWTRI = 0x01 -DCR_DRAWSQU = 0x10 - -ELLIPSE = 0xA0 -ELLIPSE_STATUS = 0x80 - -MWCR0 = 0x40 -MWCR0_GFXMODE = 0x00 -MWCR0_TXTMODE = 0x80 - -CURH0 = 0x46 -CURV0 = 0x48 - -P1CR = 0x8A -P1CR_ENABLE = 0x80 -P1CR_DISABLE = 0x00 -P1CR_CLKOUT = 0x10 -P1CR_PWMOUT = 0x00 - -P1DCR = 0x8B - -P2CR = 0x8C -P2CR_ENABLE = 0x80 -P2CR_DISABLE = 0x00 -P2CR_CLKOUT = 0x10 -P2CR_PWMOUT = 0x00 - -P2DCR = 0x8D -PWM_CLK_DIV1024 = 0x0A - -TPCR0 = 0x70 -TPCR0_ENABLE = 0x80 -TPCR0_DISABLE = 0x00 -TPCR0_WAIT_512CLK = 0x00 -TPCR0_WAIT_1024CLK = 0x10 -TPCR0_WAIT_2048CLK = 0x20 -TPCR0_WAIT_4096CLK = 0x30 -TPCR0_WAIT_8192CLK = 0x40 -TPCR0_WAIT_16384CLK = 0x50 -TPCR0_WAIT_32768CLK = 0x60 -TPCR0_WAIT_65536CLK = 0x70 -TPCR0_WAKEENABLE = 0x08 -TPCR0_WAKEDISABLE = 0x00 -TPCR0_ADCCLK_DIV4 = 0x02 -TPCR0_ADCCLK_DIV8 = 0x03 -TPCR0_ADCCLK_DIV16 = 0x04 -TPCR0_ADCCLK_DIV32 = 0x05 -TPCR0_ADCCLK_DIV64 = 0x06 -TPCR0_ADCCLK_DIV128 = 0x07 - -TPCR1 = 0x71 -TPCR1_AUTO = 0x00 -TPCR1_MANUAL = 0x40 -TPCR1_DEBOUNCE = 0x04 -TPCR1_NODEBOUNCE = 0x00 - -TPXH = 0x72 -TPYH = 0x73 -TPXYL = 0x74 - -INTC1 = 0xF0 -INTC1_KEY = 0x10 -INTC1_DMA = 0x08 -INTC1_TP = 0x04 -INTC1_BTE = 0x02 - -INTC2 = 0xF1 -INTC2_KEY = 0x10 -INTC2_DMA = 0x08 -INTC2_TP = 0x04 -INTC2_BTE = 0x02 + +VPWR = 0x1F # VSYNC Pulse Width +VPWR_LOW = 0x00 # VSYNC Low +VPWR_HIGH = 0x80 # VSYNC High + +FNCR0 = 0x21 # Font Control 0 +FNCR1 = 0x22 # Font Control 1 + +HSAW0 = 0x30 # Horizontal Start Point 0 of Active Window +HSAW1 = 0x31 # Horizontal Start Point 1 of Active Window +VSAW0 = 0x32 # Vertical Start Point 0 of Active Window +VSAW1 = 0x33 # Vertical Start Point 1 of Active Window + +HEAW0 = 0x34 # Horizontal End Point 0 of Active Window +HEAW1 = 0x35 # Horizontal End Point 1 of Active Window +VEAW0 = 0x36 # Vertical End Point of Active Window 0 +VEAW1 = 0x37 # Vertical End Point of Active Window 1 + +MCLR = 0x8E # Memory Clear Control + +MCLR_START = 0x80 # Start Clearing Memory +MCLR_STOP = 0x00 # Stop Clearing Memory +MCLR_READSTATUS = 0x80 # Read Status +MCLR_FULL = 0x00 # Full Clear +MCLR_ACTIVE = 0x40 # Clear Active + +DCR = 0x90 # Drawing Control +DCR_LNSQTR_START = 0x80 # Line / Square / Triangle Drawing Start +DCR_LNSQTR_STOP = 0x00 # Line / Square / Triangle Drawing Stop +DCR_LNSQTR_STATUS = 0x80 # Line / Square / Triangle Drawing Status +DCR_CIRC_START = 0x40 # Circle Drawing Start +DCR_CIRC_STATUS = 0x40 # Circle Drawing Status +DCR_CIRC_STOP = 0x00 # Circle Drawing Stop +DCR_FILL = 0x20 # Fill Shape +DCR_NOFILL = 0x00 # Do Not Fill Shape +DCR_DRAWLN = 0x00 # Draw Line +DCR_DRAWTRI = 0x01 # Draw Triangle +DCR_DRAWSQU = 0x10 # Draw Square + +ELLIPSE = 0xA0 # Ellipse Setup +ELLIPSE_STATUS = 0x80 # Ellipse Setup Status + +MWCR0 = 0x40 # Memory Write Control +MWCR0_GFXMODE = 0x00 # Graphics Mode +MWCR0_TXTMODE = 0x80 # Text Mode + +MRCD = 0x45 # Memory Read Cursor Direction + +CURH0 = 0x46 # Memory Write Cursor Horizontal Position Register 0 +CURH1 = 0x47 # Memory Write Cursor Horizontal Position Register 1 +CURV0 = 0x48 # Memory Write Cursor Vertical Position Register 0 +CURV1 = 0x49 # Memory Write Cursor Vertical Position Register 1 + +RCURH0 = 0x4A # Memory Read Cursor Horizontal Position Register 0 +RCURH1 = 0x4B # Memory Read Cursor Horizontal Position Register 1 +RCURV0 = 0x4C # Memory Read Cursor Vertical Position Register 0 +RCURV1 = 0x4D # Memory Read Cursor Vertical Position Register 1 + +P1CR = 0x8A # Pointer 1 Control Register +P1CR_ENABLE = 0x80 # Enable Pointer 1 +P1CR_DISABLE = 0x00 # Disable Pointer 1 +P1CR_CLKOUT = 0x10 # Clock out Pointer 1 +P1CR_PWMOUT = 0x00 # PWM out Pointer 1 + +P1DCR = 0x8B # Pointer 1 Default Color Register + +P2CR = 0x8C # Pointer 2 Control Register +P2CR_ENABLE = 0x80 # Enable Pointer 2 +P2CR_DISABLE = 0x00 # Disable Pointer 2 +P2CR_CLKOUT = 0x10 # Clock out Pointer 2 +P2CR_PWMOUT = 0x00 # PWM out Pointer 2 + +P2DCR = 0x8D # Pointer 2 Default Color Register + +PWM_CLK_DIV1024 = 0x0A # PWM Clock Divider + +TPCR0 = 0x70 # Touch Panel Control Register 0 +TPCR0_ENABLE = 0x80 # Enable Touch Panel +TPCR0_DISABLE = 0x00 # Disable Touch Panel +TPCR0_WAIT_512CLK = 0x00 # Wait 512 clocks +TPCR0_WAIT_1024CLK = 0x10 # Wait 1024 clocks +TPCR0_WAIT_2048CLK = 0x20 # Wait 2048 clocks +TPCR0_WAIT_4096CLK = 0x30 # Wait 4096 clocks +TPCR0_WAIT_8192CLK = 0x40 # Wait 8192 clocks +TPCR0_WAIT_16384CLK = 0x50 # Wait 16384 clocks +TPCR0_WAIT_32768CLK = 0x60 # Wait 32768 clocks +TPCR0_WAIT_65536CLK = 0x70 # Wait 65536 clocks +TPCR0_WAKEENABLE = 0x08 # Wake Enable +TPCR0_WAKEDISABLE = 0x00 # Wake Disable +TPCR0_ADCCLK_DIV4 = 0x02 # ADC Clock Divider 4 +TPCR0_ADCCLK_DIV8 = 0x03 # ADC Clock Divider 8 +TPCR0_ADCCLK_DIV16 = 0x04 # ADC Clock Divider 16 +TPCR0_ADCCLK_DIV32 = 0x05 # ADC Clock Divider 32 +TPCR0_ADCCLK_DIV64 = 0x06 # ADC Clock Divider 64 +TPCR0_ADCCLK_DIV128 = 0x07 # ADC Clock Divider 128 + +TPCR1 = 0x71 # Touch Panel Control Register 1 +TPCR1_AUTO = 0x00 # Automatic Mode +TPCR1_MANUAL = 0x40 # Manual Mode +TPCR1_DEBOUNCE = 0x04 # Debounce +TPCR1_NODEBOUNCE = 0x00 # No Debounce + +TPXH = 0x72 # Touch Panel X High Register +TPYH = 0x73 # Touch Panel Y High Register +TPXYL = 0x74 # Touch Panel XY Low Register + +INTC1 = 0xF0 # Interrupt Control Register 1 +INTC1_KEY = 0x10 # Interrupt: Key +INTC1_DMA = 0x08 # Interrupt: DMA +INTC1_TP = 0x04 # Interrupt: Touch Panel +INTC1_BTE = 0x02 # Interrupt: BTE + +INTC2 = 0xF1 # Interrupt Control Register 2 +INTC2_KEY = 0x10 # Interrupt: Key +INTC2_DMA = 0x08 # Interrupt: DMA +INTC2_TP = 0x04 # Interrupt: Touch Panel +INTC2_BTE = 0x02 # Interrupt: BTE WAITTIME_LUT = { TPCR0_ADCCLK_DIV4: TPCR0_WAIT_512CLK, diff --git a/examples/ra8875_bmptest.py b/examples/ra8875_bmptest.py index 4743591..f35f75e 100755 --- a/examples/ra8875_bmptest.py +++ b/examples/ra8875_bmptest.py @@ -1,24 +1,21 @@ # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT -# Quick bitmap test of RA8875 with Feather M4 -import struct - +# Quick bitmap test of RA8875 with FeatherS3 import busio import digitalio import board from adafruit_ra8875 import ra8875 +from adafruit_ra8875.bmp import BMP from adafruit_ra8875.ra8875 import color565 -WHITE = color565(255, 255, 255) - # Configuration for CS and RST pins: cs_pin = digitalio.DigitalInOut(board.D9) rst_pin = digitalio.DigitalInOut(board.D10) # Config for display baudrate (default max is 6mhz): -BAUDRATE = 8000000 +BAUDRATE = 6000000 # Setup SPI bus using hardware SPI: spi = busio.SPI(clock=board.SCK, MOSI=board.MOSI, MISO=board.MISO) @@ -26,70 +23,11 @@ # Create and setup the RA8875 display: display = ra8875.RA8875(spi, cs=cs_pin, rst=rst_pin, baudrate=BAUDRATE) display.init() -display.fill(WHITE) - - -def convert_555_to_565(rgb): - return (rgb & 0x7FE0) << 1 | 0x20 | rgb & 0x001F - - -class BMP: - def __init__(self, filename): - self.filename = filename - self.colors = None - self.data = 0 - self.data_size = 0 - self.bpp = 0 - self.width = 0 - self.height = 0 - self.read_header() - - def read_header(self): - if self.colors: - return - with open(self.filename, "rb") as f: - f.seek(10) - self.data = int.from_bytes(f.read(4), "little") - f.seek(18) - self.width = int.from_bytes(f.read(4), "little") - self.height = int.from_bytes(f.read(4), "little") - f.seek(28) - self.bpp = int.from_bytes(f.read(2), "little") - f.seek(34) - self.data_size = int.from_bytes(f.read(4), "little") - f.seek(46) - self.colors = int.from_bytes(f.read(4), "little") - - def draw(self, disp, x=0, y=0): - print("{:d}x{:d} image".format(self.width, self.height)) - print("{:d}-bit encoding detected".format(self.bpp)) - line = 0 - line_size = self.width * (self.bpp // 8) - if line_size % 4 != 0: - line_size += 4 - line_size % 4 - current_line_data = b"" - with open(self.filename, "rb") as f: - f.seek(self.data) - disp.set_window(x, y, self.width, self.height) - for line in range(self.height): - current_line_data = b"" - line_data = f.read(line_size) - for i in range(0, line_size, self.bpp // 8): - if (line_size - i) < self.bpp // 8: - break - if self.bpp == 16: - color = convert_555_to_565(line_data[i] | line_data[i + 1] << 8) - if self.bpp in (24, 32): - color = color565( - line_data[i + 2], line_data[i + 1], line_data[i] - ) - current_line_data = current_line_data + struct.pack(">H", color) - disp.setxy(x, self.height - line + y) - disp.push_pixels(current_line_data) - disp.set_window(0, 0, disp.width, disp.height) +WHITE = color565(255, 255, 255) +display.fill(WHITE) bitmap = BMP("/ra8875_blinka.bmp") x_position = (display.width // 2) - (bitmap.width // 2) y_position = (display.height // 2) - (bitmap.height // 2) -bitmap.draw(display, x_position, y_position) +bitmap.draw_bmp(display, x_position, y_position) diff --git a/examples/ra8875_color_chart.bmp b/examples/ra8875_color_chart.bmp new file mode 100644 index 0000000..2b77442 Binary files /dev/null and b/examples/ra8875_color_chart.bmp differ diff --git a/examples/ra8875_color_chart.bmp.license b/examples/ra8875_color_chart.bmp.license new file mode 100644 index 0000000..58f84ca --- /dev/null +++ b/examples/ra8875_color_chart.bmp.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: 2024 DJDevon3 +# SPDX-License-Identifier: MIT diff --git a/examples/ra8875_readsinglepixel_test.py b/examples/ra8875_readsinglepixel_test.py new file mode 100644 index 0000000..1555731 --- /dev/null +++ b/examples/ra8875_readsinglepixel_test.py @@ -0,0 +1,159 @@ +# SPDX-FileCopyrightText: 2024 DJDevon3 +# SPDX-License-Identifier: MIT +"""RA8875 Read Single Pixel example""" + +import time +import busio +import digitalio +import board + +from adafruit_ra8875 import ra8875 +from adafruit_ra8875.bmp import BMP +from adafruit_ra8875.ra8875 import color565 + +# Configuration for CS and RST pins: +cs_pin = digitalio.DigitalInOut(board.D9) +rst_pin = digitalio.DigitalInOut(board.D10) + +# Config for display baudrate (default max is 6mhz): +BAUDRATE = 6000000 + +# Setup SPI bus using hardware SPI: +spi = busio.SPI(clock=board.SCK, MOSI=board.MOSI, MISO=board.MISO) + +# Create and setup the RA8875 display: +display = ra8875.RA8875(spi, cs=cs_pin, rst=rst_pin, baudrate=BAUDRATE) +display.init() + +BLACK = color565(0, 0, 0) +RED = color565(255, 0, 0) +GREEN = color565(0, 255, 0) +BLUE = color565(0, 0, 255) +YELLOW = color565(255, 255, 0) +CYAN = color565(0, 255, 255) +MAGENTA = color565(255, 0, 255) +WHITE = color565(255, 255, 255) + +# Load the bitmap image +bitmap = BMP("/ra8875_color_chart.bmp") + +# Center BMP image on the display +x_position = (display.width // 2) - (bitmap.width // 2) +y_position = (display.height // 2) - (bitmap.height // 2) + +# Fill entire display background with white +display.fill(WHITE) +print("Filled display layer0 with white\n") + +# Draw BMP (bottom to top) +bitmap.draw_bmp(display, x_position, y_position) + +# Coordinates inside of a 53x53 red square in the bmp +X1 = 320 +Y1 = 190 +# Coordinates inside of a 53x53 blue square in the bmp +X2 = 370 +Y2 = 190 +# Coordinates inside of a 53x53 purple square in the bmp +X3 = 425 +Y3 = 190 +# Coordinates inside of a 53x53 yellow square in the bmp +X4 = 485 +Y4 = 190 +# Coordinates inside of a 53x53 green square in the bmp +X5 = 320 +Y5 = 240 +# Coordinates inside of a 53x53 cyan square in the bmp +X6 = 370 +Y6 = 240 +# Coordinates inside of a 53x53 white square in the bmp +X7 = 425 +Y7 = 240 +# Coordinates inside of a 53x53 black square in the bmp +X8 = 485 +Y8 = 240 +# Coordinates inside of a 53x53 yellow square in the bmp +X9 = 320 +Y9 = 290 +# Coordinates inside of a 53x53 red square in the bmp +X10 = 370 +Y10 = 290 +# Coordinates inside of a 53x53 green square in the bmp +X11 = 425 +Y11 = 290 +# Coordinates inside of a 53x53 blue square in the bmp +X12 = 485 +Y12 = 290 + +# List of color sampling coordinates +coordinates = [ + (X1, Y1), + (X2, Y2), + (X3, Y3), + (X4, Y4), + (X5, Y5), + (X6, Y6), + (X7, Y7), + (X8, Y8), + (X9, Y9), + (X10, Y10), + (X11, Y11), + (X12, Y12), +] + +# Giving them names makes it easier to spot errors +color_names = [ + "Red", + "Blue", + "Purple", + "Yellow", + "Green", + "Cyan", + "White", + "Black", + "Yellow", + "Red", + "Green", + "Blue", +] + +# Starting x,y for color rectangles to create +rect_coordinates = [ + (0, 0), + (53, 0), + (106, 0), + (159, 0), + (0, 53), + (53, 53), + (106, 53), + (159, 53), + (0, 106), + (53, 106), + (106, 106), + (159, 106), +] + +# Read every pixel at listed coordinates +# Returns colors as r,g,b +# Creates filled rectangles using r,g,b to confirm color sample +for i, (x, y) in enumerate(coordinates): + color = display.read_single_pixel(x, y) + print(f"color{i+1} at ({x},{y}): {color_names[i]} - {color}") + time.sleep(0.1) + rect_x, rect_y = rect_coordinates[i] + display.fill_rect(rect_x, rect_y, 53, 53, color565(color)) + +# Draws cross-hair to confirm sampled coordinates +# This can only happen after the sample is taken +display.draw_cursor(X1, Y1, RED, BLACK) +display.draw_cursor(X2, Y2, RED, BLACK) +display.draw_cursor(X3, Y3, RED, BLACK) +display.draw_cursor(X4, Y4, RED, BLACK) +display.draw_cursor(X5, Y5, RED, BLACK) +display.draw_cursor(X6, Y6, RED, BLACK) +display.draw_cursor(X7, Y7, RED, BLACK) +display.draw_cursor(X8, Y8, RED, BLACK) +display.draw_cursor(X9, Y9, RED, BLACK) +display.draw_cursor(X10, Y10, RED, BLACK) +display.draw_cursor(X11, Y11, RED, BLACK) +display.draw_cursor(X12, Y12, RED, BLACK) diff --git a/examples/ra8875_screensaver.py b/examples/ra8875_screensaver.py new file mode 100644 index 0000000..f72a04b --- /dev/null +++ b/examples/ra8875_screensaver.py @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: 2024 DJDevon3 +# SPDX-License-Identifier: MIT + +# RA8875 Screen Saver example + +import random +import busio +import digitalio +import board + +from adafruit_ra8875 import ra8875 + +# Configuration for CS and RST pins: +cs_pin = digitalio.DigitalInOut(board.D9) +rst_pin = digitalio.DigitalInOut(board.D10) + +# Config for display baudrate (default max is 6mhz): +BAUDRATE = 6000000 + +# Setup SPI bus using hardware SPI: +spi = busio.SPI(clock=board.SCK, MOSI=board.MOSI, MISO=board.MISO) + +# Create and setup the RA8875 display: +display = ra8875.RA8875(spi, cs=cs_pin, rst=rst_pin, baudrate=BAUDRATE) +display.init() + +square_size = 32 +horizontal_squares = 25 +vertical_squares = 15 + +while True: + for row_index in range(vertical_squares): + for square_index in range(horizontal_squares): + x = square_index * square_size + y = row_index * square_size + + # Generate random RGB values for each square + r = random.randint(0, 255) + g = random.randint(0, 255) + b = random.randint(0, 255) + + # Convert RGB to color565 format + color565_value = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) + + # Draw the square with the random color + display.fill_rect(x, y, square_size, square_size, color565_value) diff --git a/examples/ra8875_simpletest.py b/examples/ra8875_simpletest.py index 71c21d3..b70d550 100644 --- a/examples/ra8875_simpletest.py +++ b/examples/ra8875_simpletest.py @@ -12,8 +12,8 @@ BLACK = color565(0, 0, 0) RED = color565(255, 0, 0) -BLUE = color565(0, 255, 0) -GREEN = color565(0, 0, 255) +GREEN = color565(0, 255, 0) +BLUE = color565(0, 0, 255) YELLOW = color565(255, 255, 0) CYAN = color565(0, 255, 255) MAGENTA = color565(255, 0, 255)