1 # The MIT License (MIT)
3 # Copyright (c) 2020 Melissa LeBlanc-Williams for Adafruit Industries
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 ================================================================================
29 **Software and Dependencies:**
32 https://github.com/adafruit/Adafruit_Blinka/releases
34 * Author(s): Melissa LeBlanc-Williams
43 from displayio import Rectangle
44 from displayio import displays
46 __version__ = "0.0.0-auto.0"
47 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
49 # pylint: disable=unnecessary-pass, unused-argument
51 # pylint: disable=too-many-instance-attributes
53 """This initializes a display and connects it into CircuitPython. Unlike other objects
54 in CircuitPython, Display objects live until ``displayio.release_displays()`` is called.
55 This is done so that CircuitPython can use the display itself.
57 Most people should not use this class directly. Use a specific display driver instead
58 that will contain the initialization sequence at minimum.
61 Display(display_bus, init_sequence, *, width, height, colstart=0, rowstart=0, rotation=0,
62 color_depth=16, grayscale=False, pixels_in_byte_share_row=True, bytes_per_cell=1,
63 reverse_pixels_in_byte=False, set_column_command=0x2a, set_row_command=0x2b,
64 write_ram_command=0x2c, set_vertical_scroll=0, backlight_pin=None, brightness_command=None,
65 brightness=1.0, auto_brightness=False, single_byte_bounds=False, data_as_commands=False,
66 auto_refresh=True, native_frames_per_second=60)
69 # pylint: disable=too-many-locals
82 pixels_in_byte_share_row=True,
84 reverse_pixels_in_byte=False,
85 set_column_command=0x2A,
87 write_ram_command=0x2C,
88 set_vertical_scroll=0,
90 brightness_command=None,
92 auto_brightness=False,
93 single_byte_bounds=False,
94 data_as_commands=False,
96 native_frames_per_second=60
98 """Create a Display object on the given display bus (`displayio.FourWire` or
99 `displayio.ParallelBus`).
101 The ``init_sequence`` is bitpacked to minimize the ram impact. Every command begins
102 with a command byte followed by a byte to determine the parameter count and if a
103 delay is need after. When the top bit of the second byte is 1, the next byte will be
104 the delay time in milliseconds. The remaining 7 bits are the parameter count
105 excluding any delay byte. The third through final bytes are the remaining command
106 parameters. The next byte will begin a new command definition. Here is a portion of
108 .. code-block:: python
111 b"\xe1\x0f\x00\x0E\x14\x03\x11\x07\x31\xC1\x48\x08\x0F\x0C\x31\x36\x0F"
112 b"\x11\x80\x78"# Exit Sleep then delay 0x78 (120ms)
113 b"\x29\x80\x78"# Display on then delay 0x78 (120ms)
115 display = displayio.Display(display_bus, init_sequence, width=320, height=240)
117 The first command is 0xe1 with 15 (0xf) parameters following. The second and third
118 are 0x11 and 0x29 respectively with delays (0x80) of 120ms (0x78) and no parameters.
119 Multiple byte literals (b”“) are merged together on load. The parens are needed to
120 allow byte literals on subsequent lines.
122 The initialization sequence should always leave the display memory access inline with
123 the scan of the display to minimize tearing artifacts.
125 self._bus = display_bus
126 self._set_column_command = set_column_command
127 self._set_row_command = set_row_command
128 self._write_ram_command = write_ram_command
129 self._brightness_command = brightness_command
130 self._data_as_commands = data_as_commands
131 self._single_byte_bounds = single_byte_bounds
133 self._height = height
134 self._colstart = colstart
135 self._rowstart = rowstart
136 self._rotation = rotation
137 self._auto_brightness = auto_brightness
138 self._brightness = brightness
139 self._auto_refresh = auto_refresh
140 self._initialize(init_sequence)
141 self._buffer = Image.new("RGB", (width, height))
142 self._subrectangles = []
143 self._bounds_encoding = ">BB" if single_byte_bounds else ">HH"
144 self._current_group = None
145 displays.append(self)
146 self._refresh_thread = None
147 if self._auto_refresh:
148 self.auto_refresh = True
150 # pylint: enable=too-many-locals
152 def _initialize(self, init_sequence):
154 while i < len(init_sequence):
155 command = init_sequence[i]
156 data_size = init_sequence[i + 1]
157 delay = (data_size & 0x80) > 0
159 self._write(command, init_sequence[i + 2 : i + 2 + data_size])
163 delay_time_ms = init_sequence[i + 1 + data_size]
164 if delay_time_ms == 255:
166 time.sleep(delay_time_ms / 1000)
169 def _write(self, command, data):
170 if self._single_byte_bounds:
171 self._bus.send(True, bytes([command]) + data, toggle_every_byte=True)
173 self._bus.send(True, bytes([command]), toggle_every_byte=True)
174 self._bus.send(False, data)
180 def show(self, group):
181 """Switches to displaying the given group of layers. When group is None, the
182 default CircuitPython terminal will be shown.
184 self._current_group = group
186 def refresh(self, *, target_frames_per_second=60, minimum_frames_per_second=1):
187 """When auto refresh is off, waits for the target frame rate and then refreshes the
188 display, returning True. If the call has taken too long since the last refresh call
189 for the given target frame rate, then the refresh returns False immediately without
190 updating the screen to hopefully help getting caught up.
192 If the time since the last successful refresh is below the minimum frame rate, then
193 an exception will be raised. Set minimum_frames_per_second to 0 to disable.
195 When auto refresh is on, updates the display immediately. (The display will also
196 update without calls to this.)
198 # Go through groups and and add each to buffer
199 if self._current_group is not None:
200 buffer = Image.new("RGBA", (self._width, self._height))
201 # Recursively have everything draw to the image
202 self._current_group._fill_area(buffer) # pylint: disable=protected-access
203 # save image to buffer (or probably refresh buffer so we can compare)
204 self._buffer.paste(buffer)
206 # Eventually calculate dirty rectangles here
207 self._subrectangles.append(Rectangle(0, 0, self._width, self._height))
209 for area in self._subrectangles:
210 self._refresh_display_area(area)
212 def _refresh_loop(self):
213 while self._auto_refresh:
216 def _refresh_display_area(self, rectangle):
217 """Loop through dirty rectangles and redraw that area."""
218 data = numpy.array(self._buffer.crop(rectangle).convert("RGB")).astype("uint16")
220 ((data[:, :, 0] & 0xF8) << 8)
221 | ((data[:, :, 1] & 0xFC) << 3)
222 | (data[:, :, 2] >> 3)
226 numpy.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist()
230 self._set_column_command,
232 rectangle.x1 + self._colstart, rectangle.x2 + self._colstart
236 self._set_row_command,
238 rectangle.y1 + self._rowstart, rectangle.y2 + self._rowstart
241 self._write(self._write_ram_command, pixels)
243 def _encode_pos(self, x, y):
244 """Encode a postion into bytes."""
245 return struct.pack(self._bounds_encoding, x, y)
247 def fill_row(self, y, buffer):
248 """Extract the pixels from a single row"""
252 def auto_refresh(self):
253 """True when the display is refreshed automatically."""
254 return self._auto_refresh
257 def auto_refresh(self, value):
258 self._auto_refresh = value
259 if self._refresh_thread is None:
260 self._refresh_thread = threading.Thread(
261 target=self._refresh_loop, daemon=True
263 if value and not self._refresh_thread.is_alive():
265 self._refresh_thread.start()
266 elif not value and self._refresh_thread.is_alive():
268 self._refresh_thread.join()
271 def brightness(self):
272 """The brightness of the display as a float. 0.0 is off and 1.0 is full `brightness`.
273 When `auto_brightness` is True, the value of `brightness` will change automatically.
274 If `brightness` is set, `auto_brightness` will be disabled and will be set to False.
276 return self._brightness
279 def brightness(self, value):
280 self._brightness = value
283 def auto_brightness(self):
284 """True when the display brightness is adjusted automatically, based on an ambient
285 light sensor or other method. Note that some displays may have this set to True by
286 default, but not actually implement automatic brightness adjustment.
287 `auto_brightness` is set to False if `brightness` is set manually.
289 return self._auto_brightness
291 @auto_brightness.setter
292 def auto_brightness(self, value):
293 self._auto_brightness = value
307 """The rotation of the display as an int in degrees."""
308 return self._rotation
311 def rotation(self, value):
312 if value not in (0, 90, 180, 270):
313 raise ValueError("Rotation must be 0/90/180/270")
314 self._rotation = value
318 """Current Display Bus"""
322 # pylint: enable=too-many-instance-attributes