1 # SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
2 # SPDX-FileCopyrightText: 2021 James Carr
4 # SPDX-License-Identifier: MIT
7 `displayio._displaycore`
8 ================================================================================
10 Super class of the display classes
12 **Software and Dependencies:**
15 https://github.com/adafruit/Adafruit_Blinka/releases
17 * Author(s): James Carr, Melissa LeBlanc-Williams
21 __version__ = "0.0.0-auto.0"
22 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git"
25 from typing import Optional, Union
27 from ._fourwire import FourWire
28 from ._group import Group
29 from ._i2cdisplay import I2CDisplay
30 from ._structs import ColorspaceStruct, TransformStruct, RectangleStruct
31 from ._area import Area
33 from paralleldisplay import ParallelBus
34 from ._constants import (
37 CHIP_SELECT_TOGGLE_EVERY_BYTE,
38 CHIP_SELECT_UNTOUCHED,
48 # set_region_to_update
54 # pylint: disable=too-many-arguments, too-many-instance-attributes
68 pixels_in_byte_share_row: bool,
70 reverse_pixels_in_byte: bool,
71 reverse_bytes_in_word: bool
73 self._colorspace = ColorspaceStruct(
75 grayscale = grayscale,
76 grayscale_bit = 8 - color_depth,
77 pixels_in_byte_share_row = pixels_in_byte_share_row,
78 bytes_per_cell = bytes_per_cell,
79 reverse_pixels_in_byte = reverse_pixels_in_byte,
80 reverse_bytes_in_word = reverse_bytes_in_word,
83 self._current_group = None
84 self._colstart = colstart
85 self._rowstart = rowstart
86 self._last_refresh = 0
87 self._refresh_in_progress = False
90 if isinstance(bus, (FourWire, I2CDisplay, ParallelBus)):
91 self._bus_reset = bus.reset
92 self._begin_transaction = bus._begin_transaction
93 self._send = bus._send
94 self._end_transaction = bus._end_transaction
96 raise ValueError("Unsupported display bus type")
99 self._area = Area(0, 0, width, height)
102 self._height = height
103 self._ram_width = ram_width
104 self._ram_height = ram_height
105 self._rotation = rotation
106 self._transform = TransformStruct()
108 def set_rotation(self, rotation: int) -> None:
110 Sets the rotation of the display as an int in degrees.
112 # pylint: disable=protected-access
113 transposed = self._rotation in (90, 270)
114 will_be_transposed = rotation in (90, 270)
115 if transposed != will_be_transposed:
116 self._width, self._height = self._height, self._width
118 height = self._height
122 self._rotation = rotation
123 self._transform.x = 0
124 self._transform.y = 0
125 self._transform.scale = 1
126 self._transform.mirror_x = False
127 self._transform.mirror_y = False
128 self._transform.transpose_xy = False
130 if rotation in (0, 180):
132 self._transform.mirror_x = True
133 self._transform.mirror_y = True
135 self._transform.transpose_xy = True
137 self._transform.mirror_y = True
139 self._transform.mirror_x = True
143 self._area.next = None
145 self._transform.dx = 1
146 self._transform.dy = 1
147 if self._transform.transpose_xy:
148 self._area.x2 = height
149 self._area.y2 = width
150 if self._transform.mirror_x:
151 self._transform.x = height
152 self._transform.dx = -1
153 if self._transform.mirror_y:
154 self._transform.y = width
155 self._transform.dy = -1
157 self._area.x2 = width
158 self._area.y2 = height
159 if self._transform.mirror_x:
160 self._transform.x = width
161 self._transform.dx = -1
162 if self._transform.mirror_y:
163 self._transform.y = height
164 self._transform.dy = -1
166 if self._current_group is not None:
167 self._current_group._update_transform(self._transform)
169 def show(self, root_group: Group) -> bool:
170 # pylint: disable=protected-access
173 Switches to displaying the given group of layers. When group is `None`, the
174 default CircuitPython terminal will be shown.
176 :param Optional[displayio.Group] root_group: The group to show.
180 # TODO: Implement Supervisor
181 if root_group is None:
182 circuitpython_splash = _Supervisor().circuitpython_splash
183 if not circuitpython_splash._in_group:
184 root_group = circuitpython_splash
185 elif self._current_group == circuitpython_splash:
189 if root_group == self._current_group:
192 if root_group is not None and root_group._in_group:
195 if self._current_group is not None:
196 self._current_group._in_group = False
198 if root_group is not None:
199 root_group._update_transform(self._transform)
200 root_group._in_group = True
202 self._current_group = root_group
203 self._full_refresh = True
207 def set_region_to_update(
211 set_current_column_command: Optional[int],
212 set_current_row_command: Optional[int],
213 data_as_commands: bool,
214 always_toggle_chip_select: bool,
216 SH1107_addressing: bool,
218 # pylint: disable=invalid-name, too-many-arguments, too-many-locals, too-many-branches,
219 # pylint: disable=too-many-statements
221 big_endian = True # default is True # TODO ????
223 x1 = area.x1 + self._colstart
224 x2 = area.x2 + self._colstart
225 y1 = area.y1 + self._rowstart
226 y2 = area.y2 + self._rowstart
228 # Collapse down the dimension where multiple pixels are in a byte.
229 if self._colorspace.depth < 8:
230 pixels_per_byte = 8 // self._colorspace.depth
231 if self._colorspace.pixels_in_byte_share_row:
232 x1 //= pixels_per_byte * self._colorspace.bytes_per_cell
233 x2 //= pixels_per_byte * self._colorspace.bytes_per_cell
235 y1 //= pixels_per_byte * self._colorspace.bytes_per_cell
236 y2 //= pixels_per_byte * self._colorspace.bytes_per_cell
241 chip_select = CHIP_SELECT_UNTOUCHED
242 if always_toggle_chip_select or data_as_commands:
243 chip_select = CHIP_SELECT_TOGGLE_EVERY_BYTE
246 self._begin_transaction()
248 data[0] = column_command
249 if not data_as_commands:
250 self._send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data, 1)
251 data_type = DISPLAY_DATA
254 data_type = DISPLAY_COMMAND
257 if self._ram_width < 0x100:
258 data[data_length] = x1
260 data[data_length] = x2
264 data[data_length] = x1 >> 8
266 data[data_length] = x1 & 0xFF
268 data[data_length] = x2 >> 8
270 data[data_length] = x2 & 0xFF
273 data[data_length] = x1 & 0xFF
275 data[data_length] = x1 >> 8
277 data[data_length] = x2 & 0xFF
279 data[data_length] = x2 >> 8
282 # Quirk for "SH1107_addressing"
283 # Column lower command = 0x00, Column upper command = 0x10
284 if SH1107_addressing:
285 data[0] = ((x1 >> 4) & 0x0F) | 0x10 # 0x10 to 0x17
286 data[1] = x1 & 0x0F # 0x00 to 0x0F
289 self._send(data_type, chip_select, data, data_length)
290 self._end_transaction()
292 if set_current_column_command is not None:
293 command = bytearray(1)
294 command[0] = set_current_column_command
295 self._begin_transaction()
296 self._send(DISPLAY_COMMAND, chip_select, command, 1)
297 self._send(DISPLAY_DATA, chip_select, data, data_length // 2)
298 self._end_transaction()
301 self._begin_transaction()
302 data[0] = row_command
304 if not data_as_commands:
305 self._send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data, 1)
308 if self._ram_height < 0x100:
309 data[data_length] = y1
311 data[data_length] = y2
315 data[data_length] = y1 >> 8
317 data[data_length] = y1 & 0xFF
319 data[data_length] = y2 >> 8
321 data[data_length] = y2 & 0xFF
323 # TODO Which is right? The core uses above
325 data[data_length] = y1 & 0xFF
327 data[data_length] = y1 >> 8
329 data[data_length] = y2 & 0xFF
331 data[data_length] = y2 >> 8
334 # Quirk for "SH1107_addressing"
335 # Page address command = 0xB0
336 if SH1107_addressing:
337 # Set the page to out y value
341 self._send(data_type, chip_select, data, data_length)
342 self._end_transaction()
344 if set_current_row_command is not None:
345 command = bytearray(1)
346 command[0] = set_current_row_command
347 self._begin_transaction()
348 self._send(DISPLAY_COMMAND, chip_select, command, 1)
349 self._send(DISPLAY_DATA, chip_select, data, data_length // 2)
350 self._end_transaction()
352 def start_refresh(self) -> bool:
353 # pylint: disable=protected-access
355 if self._refresh_in_progress:
358 self._refresh_in_progress = True
359 #self._last_refresh = _Supervisor()._ticks_ms64()
362 def finish_refresh(self) -> None:
363 # pylint: disable=protected-access
365 if self._current_group is not None:
366 self._current_group._finish_refresh()
368 self._full_refresh = False
369 self._refresh_in_progress = False
370 #self._last_refresh = _Supervisor()._ticks_ms64()
372 def get_refresh_areas(self):
374 if self._current_group is not None:
375 # Eventually calculate dirty rectangles here
376 subrectangles.append(RectangleStruct(0, 0, self._width, self._height))
379 def release(self) -> None:
380 # pylint: disable=protected-access
382 if self._current_group is not None:
383 self._current_group._in_group = False
386 self, area: Area, mask: _typing.WriteableBuffer, buffer: _typing.WriteableBuffer
388 # pylint: disable=protected-access
390 return self._current_group._fill_area(self._colorspace, area, mask, buffer)
392 def clip_area(self, area: Area, clipped: Area) -> bool:
393 # pylint: disable=protected-access
395 overlaps = self._area._compute_overlap(area, clipped)
399 # Expand the area if we have multiple pixels per byte and we need to byte align the bounds
400 if self._colorspace.depth < 8:
402 8 // self._colorspace.depth * self._colorspace.bytes_per_cell
404 if self._colorspace.pixels_in_byte_share_row:
405 if clipped.x1 % pixels_per_byte != 0:
406 clipped.x1 -= clipped.x1 % pixels_per_byte
407 if clipped.x2 % pixels_per_byte != 0:
408 clipped.x2 += pixels_per_byte - clipped.x2 % pixels_per_byte
410 if clipped.y1 % pixels_per_byte != 0:
411 clipped.y1 -= clipped.y1 % pixels_per_byte
412 if clipped.y2 % pixels_per_byte != 0:
413 clipped.y2 += pixels_per_byte - clipped.y2 % pixels_per_byte
417 def send(self, data_type: int, chip_select: int, data: _typing.ReadableBuffer) -> None:
419 Send the data to the current bus
421 self._send(data_type, chip_select, data)
423 def begin_transaction(self) -> None:
425 Begin Bus Transaction
427 self._begin_transaction()
429 def end_transaction(self) -> None:
433 self._end_transaction()
435 def get_width(self) -> int:
437 Gets the width of the display in pixels.
441 def get_height(self) -> int:
443 Gets the height of the display in pixels.
447 def get_rotation(self) -> int:
449 Gets the rotation of the display as an int in degrees.
451 return self._rotation
453 def get_bus(self) -> Union[FourWire,ParallelBus,I2CDisplay]:
455 The bus being used by the display. [readonly]