/ 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)