]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blobdiff - displayio/_display.py
Bug fixes and optimizations. Super close now
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _display.py
index d7d04cf2389dcf5ba1c3badbb42cd754c6851e1b..d8b49c7031cd3e6492dc1474c0f8a5f8ef310255 100644 (file)
@@ -18,7 +18,6 @@ displayio for Blinka
 """
 
 import time
 """
 
 import time
-from array import array
 from typing import Optional
 import digitalio
 import microcontroller
 from typing import Optional
 import digitalio
 import microcontroller
@@ -36,6 +35,7 @@ from ._constants import (
     BACKLIGHT_IN_OUT,
     BACKLIGHT_PWM,
     NO_COMMAND,
     BACKLIGHT_IN_OUT,
     BACKLIGHT_PWM,
     NO_COMMAND,
+    DELAY,
 )
 
 __version__ = "0.0.0+auto.0"
 )
 
 __version__ = "0.0.0+auto.0"
@@ -43,7 +43,7 @@ __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
 
 class Display:
 
 
 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.
     """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 +81,7 @@ class Display:
         backlight_on_high: bool = True,
         SH1107_addressing: bool = False,
     ):
         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`).
 
         """Create a Display object on the given display bus (`displayio.FourWire` or
         `paralleldisplay.ParallelBus`).
 
@@ -111,6 +111,13 @@ class Display:
         The initialization sequence should always leave the display memory access inline with
         the scan of the display to minimize tearing artifacts.
         """
         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
         # Turn off auto-refresh as we init
         self._auto_refresh = False
         ram_width = 0x100
@@ -155,7 +162,42 @@ class Display:
         self._brightness = brightness
         self._auto_refresh = auto_refresh
 
         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
 
         self._current_group = None
         self._last_refresh_call = 0
@@ -191,38 +233,6 @@ class Display:
         allocate_display(display_instance)
         return display_instance
 
         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(
     def _send_pixels(self, pixels):
         if not self._core.data_as_commands:
             self._core.send(
@@ -316,7 +326,7 @@ class Display:
             )
         return areas
 
             )
         return areas
 
-    def background(self):
+    def _background(self):
         """Run background refresh tasks. Do not call directly"""
         if (
             self._auto_refresh
         """Run background refresh tasks. Do not call directly"""
         if (
             self._auto_refresh
@@ -327,8 +337,7 @@ class Display:
 
     def _refresh_area(self, area) -> bool:
         """Loop through dirty areas and redraw that area."""
 
     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.
 
         clipped = Area()
         # Clip the area to the display by overlapping the areas.
@@ -340,6 +349,9 @@ class Display:
         pixels_per_word = 32 // self._core.colorspace.depth
         pixels_per_buffer = clipped.size()
 
         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
         subrectangles = 1
         # for SH1107 and other boundary constrained controllers
         #      write one single row at a time
@@ -365,15 +377,15 @@ class Display:
             buffer_size = pixels_per_buffer // pixels_per_word
             if pixels_per_buffer % pixels_per_word:
                 buffer_size += 1
             buffer_size = pixels_per_buffer // pixels_per_word
             if pixels_per_buffer % pixels_per_word:
                 buffer_size += 1
-        mask_length = (pixels_per_buffer // 8) + 1  # 1 bit per pixel + 1
+        mask_length = (pixels_per_buffer // 32) + 1  # 1 bit per pixel + 1
         remaining_rows = clipped.height()
 
         for subrect_index in range(subrectangles):
             subrectangle = Area(
         remaining_rows = clipped.height()
 
         for subrect_index in range(subrectangles):
             subrectangle = Area(
-                clipped.x1,
-                clipped.y1 + rows_per_buffer * subrect_index,
-                clipped.x2,
-                clipped.y1 + rows_per_buffer * (subrect_index + 1),
+                x1=clipped.x1,
+                y1=clipped.y1 + rows_per_buffer * subrect_index,
+                x2=clipped.x2,
+                y2=clipped.y1 + rows_per_buffer * (subrect_index + 1),
             )
             if remaining_rows < rows_per_buffer:
                 subrectangle.y2 = subrectangle.y1 + remaining_rows
             )
             if remaining_rows < rows_per_buffer:
                 subrectangle.y2 = subrectangle.y1 + remaining_rows
@@ -388,11 +400,16 @@ class Display:
                     8 // self._core.colorspace.depth
                 )
 
                     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)).cast("I")
             self._core.fill_area(subrectangle, mask, buffer)
             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._core.begin_transaction()
-            self._send_pixels(buffer[:subrectangle_size_bytes])
+            self._send_pixels(buffer.tobytes()[:subrectangle_size_bytes])
             self._core.end_transaction()
         return True
 
             self._core.end_transaction()
         return True
 
@@ -408,18 +425,18 @@ class Display:
         if pixels_per_buffer % pixels_per_word:
             buffer_size += 1
 
         if pixels_per_buffer % pixels_per_word:
             buffer_size += 1
 
-        buffer = bytearray([0] * (buffer_size * 4))
+        buffer = memoryview(bytearray([0] * (buffer_size * 4))).cast("I")
         mask_length = (pixels_per_buffer // 32) + 1
         mask_length = (pixels_per_buffer // 32) + 1
-        mask = array("L", [0x00000000] * mask_length)
+        mask = memoryview(bytearray([0] * (mask_length * 4))).cast("I")
         self._core.fill_area(area, mask, buffer)
         return buffer
 
         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()
 
         """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
         """Reset the display"""
         self.auto_refresh = True
         circuitpython_splash.x = 0
@@ -453,23 +470,24 @@ class Display:
             elif self._backlight_type == BACKLIGHT_IN_OUT:
                 self._backlight.value = value > 0.99
             elif self._brightness_command is not None:
             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")
             self._brightness = value
         else:
             raise ValueError("Brightness must be between 0.0 and 1.0")
@@ -491,7 +509,17 @@ class Display:
 
     @rotation.setter
     def rotation(self, value: int):
 
     @rotation.setter
     def rotation(self, value: int):
+        if value % 90 != 0:
+            raise ValueError("Display rotation must be in 90 degree increments")
+        transposed = self._core.rotation in (90, 270)
+        will_transposed = value in (90, 270)
+        if transposed != will_transposed:
+            self._core.width, self._core.height = self._core.height, self._core.width
         self._core.set_rotation(value)
         self._core.set_rotation(value)
+        if self._core.current_group is not None:
+            self._core.current_group._update_transform(  # pylint: disable=protected-access
+                self._core.transform
+            )
 
     @property
     def bus(self) -> _DisplayBus:
 
     @property
     def bus(self) -> _DisplayBus: