/ adafruit_ssd1306.py
adafruit_ssd1306.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2017 Michael McWethy 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_ssd1306` 24 ==================================================== 25 26 MicroPython SSD1306 OLED driver, I2C and SPI interfaces 27 28 * Author(s): Tony DiCola, Michael McWethy 29 """ 30 31 import time 32 33 from micropython import const 34 from adafruit_bus_device import i2c_device, spi_device 35 36 try: 37 import framebuf 38 except ImportError: 39 import adafruit_framebuf as framebuf 40 41 __version__ = "0.0.0-auto.0" 42 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_SSD1306.git" 43 44 # pylint: disable-msg=bad-whitespace 45 # register definitions 46 SET_CONTRAST = const(0x81) 47 SET_ENTIRE_ON = const(0xA4) 48 SET_NORM_INV = const(0xA6) 49 SET_DISP = const(0xAE) 50 SET_MEM_ADDR = const(0x20) 51 SET_COL_ADDR = const(0x21) 52 SET_PAGE_ADDR = const(0x22) 53 SET_DISP_START_LINE = const(0x40) 54 SET_SEG_REMAP = const(0xA0) 55 SET_MUX_RATIO = const(0xA8) 56 SET_COM_OUT_DIR = const(0xC0) 57 SET_DISP_OFFSET = const(0xD3) 58 SET_COM_PIN_CFG = const(0xDA) 59 SET_DISP_CLK_DIV = const(0xD5) 60 SET_PRECHARGE = const(0xD9) 61 SET_VCOM_DESEL = const(0xDB) 62 SET_CHARGE_PUMP = const(0x8D) 63 # pylint: enable-msg=bad-whitespace 64 65 66 class _SSD1306(framebuf.FrameBuffer): 67 """Base class for SSD1306 display driver""" 68 69 # pylint: disable-msg=too-many-arguments 70 def __init__(self, buffer, width, height, *, external_vcc, reset): 71 super().__init__(buffer, width, height) 72 self.width = width 73 self.height = height 74 self.external_vcc = external_vcc 75 # reset may be None if not needed 76 self.reset_pin = reset 77 if self.reset_pin: 78 self.reset_pin.switch_to_output(value=0) 79 self.pages = self.height // 8 80 # Note the subclass must initialize self.framebuf to a framebuffer. 81 # This is necessary because the underlying data buffer is different 82 # between I2C and SPI implementations (I2C needs an extra byte). 83 self._power = False 84 self.poweron() 85 self.init_display() 86 87 @property 88 def power(self): 89 """True if the display is currently powered on, otherwise False""" 90 return self._power 91 92 def init_display(self): 93 """Base class to initialize display""" 94 for cmd in ( 95 SET_DISP | 0x00, # off 96 # address setting 97 SET_MEM_ADDR, 98 0x00, # horizontal 99 # resolution and layout 100 SET_DISP_START_LINE | 0x00, 101 SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 102 SET_MUX_RATIO, 103 self.height - 1, 104 SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 105 SET_DISP_OFFSET, 106 0x00, 107 SET_COM_PIN_CFG, 108 0x02 if self.height == 32 or self.height == 16 else 0x12, 109 # timing and driving scheme 110 SET_DISP_CLK_DIV, 111 0x80, 112 SET_PRECHARGE, 113 0x22 if self.external_vcc else 0xF1, 114 SET_VCOM_DESEL, 115 0x30, # 0.83*Vcc 116 # display 117 SET_CONTRAST, 118 0xFF, # maximum 119 SET_ENTIRE_ON, # output follows RAM contents 120 SET_NORM_INV, # not inverted 121 # charge pump 122 SET_CHARGE_PUMP, 123 0x10 if self.external_vcc else 0x14, 124 SET_DISP | 0x01, 125 ): # on 126 self.write_cmd(cmd) 127 if self.width == 72: 128 self.write_cmd(0xAD) 129 self.write_cmd(0x30) 130 self.fill(0) 131 self.show() 132 133 def poweroff(self): 134 """Turn off the display (nothing visible)""" 135 self.write_cmd(SET_DISP | 0x00) 136 self._power = False 137 138 def contrast(self, contrast): 139 """Adjust the contrast""" 140 self.write_cmd(SET_CONTRAST) 141 self.write_cmd(contrast) 142 143 def invert(self, invert): 144 """Invert all pixels on the display""" 145 self.write_cmd(SET_NORM_INV | (invert & 1)) 146 147 def write_framebuf(self): 148 """Derived class must implement this""" 149 raise NotImplementedError 150 151 def write_cmd(self, cmd): 152 """Derived class must implement this""" 153 raise NotImplementedError 154 155 def poweron(self): 156 "Reset device and turn on the display." 157 if self.reset_pin: 158 self.reset_pin.value = 1 159 time.sleep(0.001) 160 self.reset_pin.value = 0 161 time.sleep(0.010) 162 self.reset_pin.value = 1 163 time.sleep(0.010) 164 self.write_cmd(SET_DISP | 0x01) 165 self._power = True 166 167 def show(self): 168 """Update the display""" 169 xpos0 = 0 170 xpos1 = self.width - 1 171 if self.width == 64: 172 # displays with width of 64 pixels are shifted by 32 173 xpos0 += 32 174 xpos1 += 32 175 if self.width == 72: 176 # displays with width of 72 pixels are shifted by 28 177 xpos0 += 28 178 xpos1 += 28 179 self.write_cmd(SET_COL_ADDR) 180 self.write_cmd(xpos0) 181 self.write_cmd(xpos1) 182 self.write_cmd(SET_PAGE_ADDR) 183 self.write_cmd(0) 184 self.write_cmd(self.pages - 1) 185 self.write_framebuf() 186 187 188 class SSD1306_I2C(_SSD1306): 189 """ 190 I2C class for SSD1306 191 192 :param width: the width of the physical screen in pixels, 193 :param height: the height of the physical screen in pixels, 194 :param i2c: the I2C peripheral to use, 195 :param addr: the 8-bit bus address of the device, 196 :param external_vcc: whether external high-voltage source is connected. 197 :param reset: if needed, DigitalInOut designating reset pin 198 """ 199 200 def __init__( 201 self, width, height, i2c, *, addr=0x3C, external_vcc=False, reset=None 202 ): 203 self.i2c_device = i2c_device.I2CDevice(i2c, addr) 204 self.addr = addr 205 self.temp = bytearray(2) 206 # Add an extra byte to the data buffer to hold an I2C data/command byte 207 # to use hardware-compatible I2C transactions. A memoryview of the 208 # buffer is used to mask this byte from the framebuffer operations 209 # (without a major memory hit as memoryview doesn't copy to a separate 210 # buffer). 211 self.buffer = bytearray(((height // 8) * width) + 1) 212 self.buffer[0] = 0x40 # Set first byte of data buffer to Co=0, D/C=1 213 super().__init__( 214 memoryview(self.buffer)[1:], 215 width, 216 height, 217 external_vcc=external_vcc, 218 reset=reset, 219 ) 220 221 def write_cmd(self, cmd): 222 """Send a command to the SPI device""" 223 self.temp[0] = 0x80 # Co=1, D/C#=0 224 self.temp[1] = cmd 225 with self.i2c_device: 226 self.i2c_device.write(self.temp) 227 228 def write_framebuf(self): 229 """Blast out the frame buffer using a single I2C transaction to support 230 hardware I2C interfaces.""" 231 with self.i2c_device: 232 self.i2c_device.write(self.buffer) 233 234 235 # pylint: disable-msg=too-many-arguments 236 class SSD1306_SPI(_SSD1306): 237 """ 238 SPI class for SSD1306 239 240 :param width: the width of the physical screen in pixels, 241 :param height: the height of the physical screen in pixels, 242 :param spi: the SPI peripheral to use, 243 :param dc: the data/command pin to use (often labeled "D/C"), 244 :param reset: the reset pin to use, 245 :param cs: the chip-select pin to use (sometimes labeled "SS"). 246 """ 247 248 # pylint: disable=no-member 249 # Disable should be reconsidered when refactor can be tested. 250 def __init__( 251 self, 252 width, 253 height, 254 spi, 255 dc, 256 reset, 257 cs, 258 *, 259 external_vcc=False, 260 baudrate=8000000, 261 polarity=0, 262 phase=0 263 ): 264 self.rate = 10 * 1024 * 1024 265 dc.switch_to_output(value=0) 266 self.spi_device = spi_device.SPIDevice( 267 spi, cs, baudrate=baudrate, polarity=polarity, phase=phase 268 ) 269 self.dc_pin = dc 270 self.buffer = bytearray((height // 8) * width) 271 super().__init__( 272 memoryview(self.buffer), 273 width, 274 height, 275 external_vcc=external_vcc, 276 reset=reset, 277 ) 278 279 def write_cmd(self, cmd): 280 """Send a command to the SPI device""" 281 self.dc_pin.value = 0 282 with self.spi_device as spi: 283 spi.write(bytearray([cmd])) 284 285 def write_framebuf(self): 286 """write to the frame buffer via SPI""" 287 self.dc_pin.value = 1 288 with self.spi_device as spi: 289 spi.write(self.buffer)