-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathadafruit_sharpmemorydisplay.py
162 lines (131 loc) · 5.15 KB
/
adafruit_sharpmemorydisplay.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# SPDX-FileCopyrightText: 2018 ladyada for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# pylint: disable=line-too-long
"""
`adafruit_sharpmemorydisplay`
====================================================
A display control library for Sharp 'memory' displays
* Author(s): ladyada
Implementation Notes
--------------------
**Hardware:**
* `Adafruit SHARP Memory Display Breakout - 1.3 inch 144x168 Monochrome <https://www.adafruit.com/product/3502>`_
* `Adafruit SHARP Memory Display Breakout - 1.3 inch 96x96 Monochrome <https://www.adafruit.com/product/1393>`_
**Software and Dependencies:**
* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases
"""
# pylint: enable=line-too-long
from __future__ import annotations
try:
# pylint: disable=unused-import
import typing
from busio import SPI
from digitalio import DigitalInOut
from circuitpython_typing.pil import Image
except ImportError:
pass
import adafruit_framebuf
from adafruit_bus_device.spi_device import SPIDevice
from micropython import const
try:
import numpy
except ImportError:
numpy = None
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_SharpMemoryDisplay.git"
_SHARPMEM_BIT_WRITECMD = const(0x80) # in lsb
_SHARPMEM_BIT_VCOM = const(0x40) # in lsb
_SHARPMEM_BIT_CLEAR = const(0x20) # in lsb
def reverse_bit(num: int) -> int:
"""Turn an LSB byte to an MSB byte, and vice versa. Used for SPI as
it is LSB for the SHARP, but 99% of SPI implementations are MSB only!"""
result = 0
for _ in range(8):
result <<= 1
result += num & 1
num >>= 1
return result
class SharpMemoryDisplay(adafruit_framebuf.FrameBuffer):
"""A driver for sharp memory displays, you can use any size but the
full display must be buffered in memory!"""
# pylint: disable=too-many-instance-attributes,abstract-method
def __init__(
self,
spi: SPI,
scs_pin: DigitalInOut,
width: int,
height: int,
*,
baudrate=2000000,
):
scs_pin.switch_to_output(value=True)
self.spi_device = SPIDevice(
spi, scs_pin, cs_active_value=True, baudrate=baudrate
)
# prealloc for when we write the display
self._buf = bytearray(1)
# even tho technically this display is LSB, we have to flip the bits
# when writing out SPI so lets just do flipping once, in the buffer
self.buffer = bytearray((width // 8) * height)
super().__init__(self.buffer, width, height, buf_format=adafruit_framebuf.MHMSB)
# Set the vcom bit to a defined state
self._vcom = True
def show(self) -> None:
"""write out the frame buffer via SPI, we use MSB SPI only so some
bit-swapping is required.
"""
with self.spi_device as spi:
image_buffer = bytearray()
# toggle the VCOM bit
self._buf[0] = _SHARPMEM_BIT_WRITECMD
if self._vcom:
self._buf[0] |= _SHARPMEM_BIT_VCOM
self._vcom = not self._vcom
image_buffer.extend(self._buf)
slice_from = 0
line_len = self.width // 8
for line in range(self.height):
self._buf[0] = reverse_bit(line + 1)
image_buffer.extend(self._buf)
image_buffer.extend(self.buffer[slice_from : slice_from + line_len])
slice_from += line_len
self._buf[0] = 0
image_buffer.extend(self._buf)
image_buffer.extend(self._buf)
spi.write(image_buffer)
def image(self, img: Image) -> None:
"""Set buffer to value of Python Imaging Library image. The image should
be in 1 bit mode and a size equal to the display size."""
# determine our effective width/height, taking rotation into account
width = self.width
height = self.height
if self.rotation in (1, 3):
width, height = height, width
if img.mode != "1":
raise ValueError("Image must be in mode 1.")
imwidth, imheight = img.size
if imwidth != width or imheight != height:
raise ValueError(
"Image must be same dimensions as display ({0}x{1}).".format(
width, height
)
)
if numpy:
self.buffer = bytearray(
numpy.packbits(numpy.asarray(img), axis=1).flatten().tolist()
)
else:
# Grab all the pixels from the image, faster than getpixel.
pixels = img.load()
# Clear buffer
for i in range(len(self.buf)): # pylint: disable=consider-using-enumerate
self.buf[i] = 0
# Iterate through the pixels
for x in range(width): # yes this double loop is slow,
for y in range(height): # but these displays are small!
if img.mode == "RGB":
self.pixel(x, y, pixels[(x, y)])
elif pixels[(x, y)]:
self.pixel(x, y, 1) # only write if pixel is true