/ adafruit_ssd1305.py
adafruit_ssd1305.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2019 Bryan Siepert for Adafruit Industries
  4  #
  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:
 11  #
 12  # The above copyright notice and this permission notice shall be included in
 13  # all copies or substantial portions of the Software.
 14  #
 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
 21  # THE SOFTWARE.
 22  """
 23  `adafruit_ssd1305`
 24  ================================================================================
 25  
 26  Framebuf (non-displayio) driver for SSD1305 displays
 27  
 28  * Author(s): Melissa LeBlanc-Williamns, Bryan Siepert, Tony DiCola, Michael McWethy
 29  
 30  Display init commands taken from
 31      https://www.buydisplay.com/download/democode/ER-OLED022-1_I2C_DemoCode.txt
 32  
 33  Implementation Notes
 34  --------------------
 35  **Software and Dependencies:**
 36  
 37  * Adafruit CircuitPython firmware for the supported boards:
 38    https://github.com/adafruit/circuitpython/releases
 39  
 40  * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 41  * Adafruit's framebuf library: https://github.com/adafruit/Adafruit_CircuitPython_framebuf
 42  
 43  """
 44  
 45  # imports
 46  
 47  __version__ = "0.0.0-auto.0"
 48  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_SSD1305.git"
 49  
 50  
 51  import time
 52  
 53  from micropython import const
 54  from adafruit_bus_device import i2c_device, spi_device
 55  
 56  try:
 57      import framebuf
 58  except ImportError:
 59      import adafruit_framebuf as framebuf
 60  
 61  __version__ = "0.0.0-auto.0"
 62  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_SSD1305.git"
 63  
 64  # pylint: disable-msg=bad-whitespace
 65  # register definitions
 66  SET_CONTRAST = const(0x81)
 67  SET_ENTIRE_ON = const(0xA4)
 68  SET_NORM_INV = const(0xA6)
 69  SET_DISP = const(0xAE)
 70  SET_MEM_ADDR = const(0x20)
 71  SET_COL_ADDR = const(0x21)
 72  SET_PAGE_ADDR = const(0x22)
 73  SET_DISP_START_LINE = const(0x40)
 74  SET_LUT = const(0x91)
 75  SET_SEG_REMAP = const(0xA0)
 76  SET_MUX_RATIO = const(0xA8)
 77  SET_MASTER_CONFIG = const(0xAD)
 78  SET_COM_OUT_DIR = const(0xC0)
 79  SET_COMSCAN_DEC = const(0xC8)
 80  SET_DISP_OFFSET = const(0xD3)
 81  SET_COM_PIN_CFG = const(0xDA)
 82  SET_DISP_CLK_DIV = const(0xD5)
 83  SET_AREA_COLOR = const(0xD8)
 84  SET_PRECHARGE = const(0xD9)
 85  SET_VCOM_DESEL = const(0xDB)
 86  SET_CHARGE_PUMP = const(0x8D)
 87  # pylint: enable-msg=bad-whitespace
 88  
 89  
 90  class _SSD1305(framebuf.FrameBuffer):
 91      """Base class for SSD1305 display driver"""
 92  
 93      # pylint: disable-msg=too-many-arguments
 94      def __init__(self, buffer, width, height, *, external_vcc, reset):
 95          super().__init__(buffer, width, height)
 96          self.width = width
 97          self.height = height
 98          self.external_vcc = external_vcc
 99          # reset may be None if not needed
100          self.reset_pin = reset
101          if self.reset_pin:
102              self.reset_pin.switch_to_output(value=0)
103          self.pages = self.height // 8
104          self._column_offset = 0
105          if self.height == 32:
106              self._column_offset = 4  # hardcoded for now...
107          # Note the subclass must initialize self.framebuf to a framebuffer.
108          # This is necessary because the underlying data buffer is different
109          # between I2C and SPI implementations (I2C needs an extra byte).
110          self.poweron()
111          self.init_display()
112  
113      def init_display(self):
114          """Base class to initialize display"""
115          for cmd in (
116              SET_DISP | 0x00,  # off
117              # timing and driving scheme
118              SET_DISP_CLK_DIV,
119              0x80,  # SET_DISP_CLK_DIV
120              SET_SEG_REMAP | 0x01,  # column addr 127 mapped to SEG0 SET_SEG_REMAP
121              SET_MUX_RATIO,
122              self.height - 1,  # SET_MUX_RATIO
123              SET_DISP_OFFSET,
124              0x00,  # SET_DISP_OFFSET
125              SET_MASTER_CONFIG,
126              0x8E,  # Set Master Configuration
127              SET_AREA_COLOR,
128              0x05,  # Set Area Color Mode On/Off & Low Power Display Mode
129              SET_MEM_ADDR,
130              0x00,  # horizontal SET_MEM_ADDR ADD
131              SET_DISP_START_LINE | 0x00,
132              0x2E,  # SET_DISP_START_LINE ADD
133              SET_COMSCAN_DEC,  # Set COM Output Scan Direction 64 to 1
134              SET_COM_PIN_CFG,
135              0x12,  # SET_COM_PIN_CFG
136              SET_LUT,
137              0x3F,
138              0x3F,
139              0x3F,
140              0x3F,  # Current drive pulse width of BANK0, Color A, B, C
141              SET_CONTRAST,
142              0xFF,  # maximum SET_CONTRAST to maximum
143              SET_PRECHARGE,
144              0xD2,  # SET_PRECHARGE orig: 0xd9, 0x22 if self.external_vcc else 0xf1,
145              SET_VCOM_DESEL,
146              0x34,  # SET_VCOM_DESEL 0xdb, 0x30, $ 0.83* Vcc
147              SET_NORM_INV,  # not inverted SET_NORM_INV
148              SET_ENTIRE_ON,  # output follows RAM contents  SET_ENTIRE_ON
149              SET_CHARGE_PUMP,
150              0x10 if self.external_vcc else 0x14,  # SET_CHARGE_PUMP
151              SET_DISP | 0x01,
152          ):  # //--turn on oled panel
153              self.write_cmd(cmd)
154          self.fill(0)
155          self.show()
156  
157      def poweroff(self):
158          """Turn off the display (nothing visible)"""
159          self.write_cmd(SET_DISP | 0x00)
160  
161      def contrast(self, contrast):
162          """Adjust the contrast"""
163          self.write_cmd(SET_CONTRAST)
164          self.write_cmd(contrast)
165  
166      def invert(self, invert):
167          """Invert all pixels on the display"""
168          self.write_cmd(SET_NORM_INV | (invert & 1))
169  
170      def write_framebuf(self):
171          """Derived class must implement this"""
172          raise NotImplementedError
173  
174      def write_cmd(self, cmd):
175          """Derived class must implement this"""
176          raise NotImplementedError
177  
178      def poweron(self):
179          "Reset device and turn on the display."
180          if self.reset_pin:
181              self.reset_pin.value = 1
182              time.sleep(0.001)
183              self.reset_pin.value = 0
184              time.sleep(0.010)
185              self.reset_pin.value = 1
186              time.sleep(0.010)
187          self.write_cmd(SET_DISP | 0x01)
188  
189      def show(self):
190          """Update the display"""
191          xpos0 = 0
192          xpos1 = self.width - 1
193          if self.width == 64:
194              # displays with width of 64 pixels are shifted by 32
195              xpos0 += 32
196              xpos1 += 32
197          self.write_cmd(SET_COL_ADDR)
198          self.write_cmd(xpos0 + self._column_offset)
199          self.write_cmd(xpos1 + self._column_offset)
200          self.write_cmd(SET_PAGE_ADDR)
201          self.write_cmd(0)
202          self.write_cmd(self.pages - 1)
203          self.write_framebuf()
204  
205  
206  class SSD1305_I2C(_SSD1305):
207      """
208      I2C class for SSD1305
209  
210      :param width: the width of the physical screen in pixels,
211      :param height: the height of the physical screen in pixels,
212      :param i2c: the I2C peripheral to use,
213      :param addr: the 8-bit bus address of the device,
214      :param external_vcc: whether external high-voltage source is connected.
215      :param reset: if needed, DigitalInOut designating reset pin
216      """
217  
218      def __init__(
219          self, width, height, i2c, *, addr=0x3C, external_vcc=False, reset=None
220      ):
221          self.i2c_device = i2c_device.I2CDevice(i2c, addr)
222          self.addr = addr
223          self.temp = bytearray(2)
224          # Add an extra byte to the data buffer to hold an I2C data/command byte
225          # to use hardware-compatible I2C transactions.  A memoryview of the
226          # buffer is used to mask this byte from the framebuffer operations
227          # (without a major memory hit as memoryview doesn't copy to a separate
228          # buffer).
229          self.buffer = bytearray(((height // 8) * width) + 1)
230          self.buffer[0] = 0x40  # Set first byte of data buffer to Co=0, D/C=1
231          super().__init__(
232              memoryview(self.buffer)[1:],
233              width,
234              height,
235              external_vcc=external_vcc,
236              reset=reset,
237          )
238  
239      def write_cmd(self, cmd):
240          """Send a command to the SPI device"""
241          self.temp[0] = 0x80  # Co=1, D/C#=0
242          self.temp[1] = cmd
243          with self.i2c_device:
244              self.i2c_device.write(self.temp)
245  
246      def write_framebuf(self):
247          """Blast out the frame buffer using a single I2C transaction to support
248          hardware I2C interfaces."""
249          with self.i2c_device:
250              self.i2c_device.write(self.buffer)
251  
252  
253  # pylint: disable-msg=too-many-arguments
254  class SSD1305_SPI(_SSD1305):
255      """
256      SPI class for SSD1305
257  
258      :param width: the width of the physical screen in pixels,
259      :param height: the height of the physical screen in pixels,
260      :param spi: the SPI peripheral to use,
261      :param dc: the data/command pin to use (often labeled "D/C"),
262      :param reset: the reset pin to use,
263      :param cs: the chip-select pin to use (sometimes labeled "SS").
264      """
265  
266      # pylint: disable=no-member
267      # Disable should be reconsidered when refactor can be tested.
268      def __init__(
269          self,
270          width,
271          height,
272          spi,
273          dc,
274          reset,
275          cs,
276          *,
277          external_vcc=False,
278          baudrate=8000000,
279          polarity=0,
280          phase=0
281      ):
282          self.rate = 10 * 1024 * 1024
283          dc.switch_to_output(value=0)
284          self.spi_device = spi_device.SPIDevice(
285              spi, cs, baudrate=baudrate, polarity=polarity, phase=phase
286          )
287          self.dc_pin = dc
288          self.buffer = bytearray((height // 8) * width)
289          super().__init__(
290              memoryview(self.buffer),
291              width,
292              height,
293              external_vcc=external_vcc,
294              reset=reset,
295          )
296  
297      def write_cmd(self, cmd):
298          """Send a command to the SPI device"""
299          self.dc_pin.value = 0
300          with self.spi_device as spi:
301              spi.write(bytearray([cmd]))
302  
303      def write_framebuf(self):
304          """write to the frame buffer via SPI"""
305          self.dc_pin.value = 1
306          with self.spi_device as spi:
307              spi.write(self.buffer)