X-Git-Url: https://git.ayoreis.com/hackapet/Adafruit_Blinka_Displayio.git/blobdiff_plain/4ce6ccfe5fdee154abaa19bcdbdc1560b8cf43a0..d86b9125007033a7efb82008497b6b856333ca9c:/displayio/_display.py diff --git a/displayio/_display.py b/displayio/_display.py index d7d04cf..9572a81 100644 --- a/displayio/_display.py +++ b/displayio/_display.py @@ -36,6 +36,7 @@ from ._constants import ( BACKLIGHT_IN_OUT, BACKLIGHT_PWM, NO_COMMAND, + DELAY, ) __version__ = "0.0.0+auto.0" @@ -43,7 +44,7 @@ __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git" class Display: - # pylint: disable=too-many-instance-attributes + # pylint: disable=too-many-instance-attributes, too-many-statements """This initializes a display and connects it into CircuitPython. Unlike other objects in CircuitPython, Display objects live until ``displayio.release_displays()`` is called. This is done so that CircuitPython can use the display itself. @@ -81,7 +82,7 @@ class Display: backlight_on_high: bool = True, SH1107_addressing: bool = False, ): - # pylint: disable=too-many-locals,invalid-name + # pylint: disable=too-many-locals,invalid-name, too-many-branches """Create a Display object on the given display bus (`displayio.FourWire` or `paralleldisplay.ParallelBus`). @@ -111,6 +112,13 @@ class Display: The initialization sequence should always leave the display memory access inline with the scan of the display to minimize tearing artifacts. """ + + if rotation % 90 != 0: + raise ValueError("Display rotation must be in 90 degree increments") + + if SH1107_addressing and color_depth != 1: + raise ValueError("color_depth must be 1 when SH1107_addressing is True") + # Turn off auto-refresh as we init self._auto_refresh = False ram_width = 0x100 @@ -155,7 +163,42 @@ class Display: self._brightness = brightness self._auto_refresh = auto_refresh - self._initialize(init_sequence) + i = 0 + while i < len(init_sequence): + command = init_sequence[i] + data_size = init_sequence[i + 1] + delay = (data_size & DELAY) != 0 + data_size &= ~DELAY + while self._core.begin_transaction(): + pass + + if self._core.data_as_commands: + full_command = bytearray(data_size + 1) + full_command[0] = command + full_command[1:] = init_sequence[i + 2 : i + 2 + data_size] + self._core.send( + DISPLAY_COMMAND, + CHIP_SELECT_TOGGLE_EVERY_BYTE, + full_command, + ) + else: + self._core.send( + DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, bytes([command]) + ) + self._core.send( + DISPLAY_DATA, + CHIP_SELECT_UNTOUCHED, + init_sequence[i + 2 : i + 2 + data_size], + ) + self._core.end_transaction() + delay_time_ms = 10 + if delay: + data_size += 1 + delay_time_ms = init_sequence[i + 1 + data_size] + if delay_time_ms == 255: + delay_time_ms = 500 + time.sleep(delay_time_ms / 1000) + i += 2 + data_size self._current_group = None self._last_refresh_call = 0 @@ -191,38 +234,6 @@ class Display: allocate_display(display_instance) return display_instance - def _initialize(self, init_sequence): - i = 0 - while i < len(init_sequence): - command = init_sequence[i] - data_size = init_sequence[i + 1] - delay = (data_size & 0x80) > 0 - data_size &= ~0x80 - - if self._core.data_as_commands: - self._core.send( - DISPLAY_COMMAND, - CHIP_SELECT_TOGGLE_EVERY_BYTE, - bytes([command]) + init_sequence[i + 2 : i + 2 + data_size], - ) - else: - self._core.send( - DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, bytes([command]) - ) - self._core.send( - DISPLAY_DATA, - CHIP_SELECT_UNTOUCHED, - init_sequence[i + 2 : i + 2 + data_size], - ) - delay_time_ms = 10 - if delay: - data_size += 1 - delay_time_ms = init_sequence[i + 1 + data_size] - if delay_time_ms == 255: - delay_time_ms = 500 - time.sleep(delay_time_ms / 1000) - i += 2 + data_size - def _send_pixels(self, pixels): if not self._core.data_as_commands: self._core.send( @@ -316,7 +327,7 @@ class Display: ) return areas - def background(self): + def _background(self): """Run background refresh tasks. Do not call directly""" if ( self._auto_refresh @@ -327,8 +338,7 @@ class Display: def _refresh_area(self, area) -> bool: """Loop through dirty areas and redraw that area.""" - # pylint: disable=too-many-locals - buffer_size = 128 + # pylint: disable=too-many-locals, too-many-branches clipped = Area() # Clip the area to the display by overlapping the areas. @@ -340,6 +350,9 @@ class Display: pixels_per_word = 32 // self._core.colorspace.depth pixels_per_buffer = clipped.size() + # We should have lots of memory + buffer_size = clipped.size() // pixels_per_word + subrectangles = 1 # for SH1107 and other boundary constrained controllers # write one single row at a time @@ -388,9 +401,14 @@ class Display: 8 // self._core.colorspace.depth ) - buffer = memoryview(bytearray([0] * (buffer_size * 4))) - mask = memoryview(bytearray([0] * mask_length)) + buffer = memoryview(bytearray([0] * (buffer_size * 4))).cast("I") + mask = memoryview(bytearray([0] * (mask_length * 4))).cast("I") self._core.fill_area(subrectangle, mask, buffer) + + # Can't acquire display bus; skip the rest of the data. + if not self._core.bus_free(): + return False + self._core.begin_transaction() self._send_pixels(buffer[:subrectangle_size_bytes]) self._core.end_transaction() @@ -414,12 +432,12 @@ class Display: self._core.fill_area(area, mask, buffer) return buffer - def release(self) -> None: + def _release(self) -> None: """Release the display and free its resources""" self.auto_refresh = False self._core.release_display_core() - def reset(self) -> None: + def _reset(self) -> None: """Reset the display""" self.auto_refresh = True circuitpython_splash.x = 0 @@ -453,23 +471,24 @@ class Display: elif self._backlight_type == BACKLIGHT_IN_OUT: self._backlight.value = value > 0.99 elif self._brightness_command is not None: - self._core.begin_transaction() - if self._core.data_as_commands: - self._core.send( - DISPLAY_COMMAND, - CHIP_SELECT_TOGGLE_EVERY_BYTE, - bytes([self._brightness_command, 0xFF * value]), - ) - else: - self._core.send( - DISPLAY_COMMAND, - CHIP_SELECT_TOGGLE_EVERY_BYTE, - bytes([self._brightness_command]), - ) - self._core.send( - DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, round(value * 255) - ) - self._core.end_transaction() + okay = self._core.begin_transaction() + if okay: + if self._core.data_as_commands: + self._core.send( + DISPLAY_COMMAND, + CHIP_SELECT_TOGGLE_EVERY_BYTE, + bytes([self._brightness_command, round(0xFF * value)]), + ) + else: + self._core.send( + DISPLAY_COMMAND, + CHIP_SELECT_TOGGLE_EVERY_BYTE, + bytes([self._brightness_command]), + ) + self._core.send( + DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, round(value * 255) + ) + self._core.end_transaction() self._brightness = value else: raise ValueError("Brightness must be between 0.0 and 1.0") @@ -491,6 +510,8 @@ class Display: @rotation.setter def rotation(self, value: int): + if value % 90 != 0: + raise ValueError("Display rotation must be in 90 degree increments") self._core.set_rotation(value) @property