bc7822d8e9
The 72x40 OLED requires selecting the internal IREF, as opposed to the default external IREF. This is an undocumented feature in the SSD1306 datasheet, but is present in the SSD1315 datasheet. It's possible the 72x40 OLED is actually using the newer SSD1315 controller. Sending the IREF select command to SSD1306 displays has no effect on them, so it's added to the init_display() instead of wrapping in an "if width = 72". Also tested on a 128x64 OLED using the SSD1315 controller (smaller ribbon cable) and the proposed change has no effect on the display, as the module comes with the correct current limiting resistor. Internal and external IREF work the same. Fixes issue #7281.
164 lines
4.8 KiB
Python
164 lines
4.8 KiB
Python
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
|
|
|
|
from micropython import const
|
|
import framebuf
|
|
|
|
|
|
# register definitions
|
|
SET_CONTRAST = const(0x81)
|
|
SET_ENTIRE_ON = const(0xA4)
|
|
SET_NORM_INV = const(0xA6)
|
|
SET_DISP = const(0xAE)
|
|
SET_MEM_ADDR = const(0x20)
|
|
SET_COL_ADDR = const(0x21)
|
|
SET_PAGE_ADDR = const(0x22)
|
|
SET_DISP_START_LINE = const(0x40)
|
|
SET_SEG_REMAP = const(0xA0)
|
|
SET_MUX_RATIO = const(0xA8)
|
|
SET_IREF_SELECT = const(0xAD)
|
|
SET_COM_OUT_DIR = const(0xC0)
|
|
SET_DISP_OFFSET = const(0xD3)
|
|
SET_COM_PIN_CFG = const(0xDA)
|
|
SET_DISP_CLK_DIV = const(0xD5)
|
|
SET_PRECHARGE = const(0xD9)
|
|
SET_VCOM_DESEL = const(0xDB)
|
|
SET_CHARGE_PUMP = const(0x8D)
|
|
|
|
# Subclassing FrameBuffer provides support for graphics primitives
|
|
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
|
|
class SSD1306(framebuf.FrameBuffer):
|
|
def __init__(self, width, height, external_vcc):
|
|
self.width = width
|
|
self.height = height
|
|
self.external_vcc = external_vcc
|
|
self.pages = self.height // 8
|
|
self.buffer = bytearray(self.pages * self.width)
|
|
super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
|
|
self.init_display()
|
|
|
|
def init_display(self):
|
|
for cmd in (
|
|
SET_DISP, # display off
|
|
# address setting
|
|
SET_MEM_ADDR,
|
|
0x00, # horizontal
|
|
# resolution and layout
|
|
SET_DISP_START_LINE, # start at line 0
|
|
SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0
|
|
SET_MUX_RATIO,
|
|
self.height - 1,
|
|
SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0
|
|
SET_DISP_OFFSET,
|
|
0x00,
|
|
SET_COM_PIN_CFG,
|
|
0x02 if self.width > 2 * self.height else 0x12,
|
|
# timing and driving scheme
|
|
SET_DISP_CLK_DIV,
|
|
0x80,
|
|
SET_PRECHARGE,
|
|
0x22 if self.external_vcc else 0xF1,
|
|
SET_VCOM_DESEL,
|
|
0x30, # 0.83*Vcc
|
|
# display
|
|
SET_CONTRAST,
|
|
0xFF, # maximum
|
|
SET_ENTIRE_ON, # output follows RAM contents
|
|
SET_NORM_INV, # not inverted
|
|
SET_IREF_SELECT,
|
|
0x30, # enable internal IREF during display on
|
|
# charge pump
|
|
SET_CHARGE_PUMP,
|
|
0x10 if self.external_vcc else 0x14,
|
|
SET_DISP | 0x01, # display on
|
|
): # on
|
|
self.write_cmd(cmd)
|
|
self.fill(0)
|
|
self.show()
|
|
|
|
def poweroff(self):
|
|
self.write_cmd(SET_DISP)
|
|
|
|
def poweron(self):
|
|
self.write_cmd(SET_DISP | 0x01)
|
|
|
|
def contrast(self, contrast):
|
|
self.write_cmd(SET_CONTRAST)
|
|
self.write_cmd(contrast)
|
|
|
|
def invert(self, invert):
|
|
self.write_cmd(SET_NORM_INV | (invert & 1))
|
|
|
|
def rotate(self, rotate):
|
|
self.write_cmd(SET_COM_OUT_DIR | ((rotate & 1) << 3))
|
|
self.write_cmd(SET_SEG_REMAP | (rotate & 1))
|
|
|
|
def show(self):
|
|
x0 = 0
|
|
x1 = self.width - 1
|
|
if self.width != 128:
|
|
# narrow displays use centred columns
|
|
col_offset = (128 - self.width) // 2
|
|
x0 += col_offset
|
|
x1 += col_offset
|
|
self.write_cmd(SET_COL_ADDR)
|
|
self.write_cmd(x0)
|
|
self.write_cmd(x1)
|
|
self.write_cmd(SET_PAGE_ADDR)
|
|
self.write_cmd(0)
|
|
self.write_cmd(self.pages - 1)
|
|
self.write_data(self.buffer)
|
|
|
|
|
|
class SSD1306_I2C(SSD1306):
|
|
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
|
|
self.i2c = i2c
|
|
self.addr = addr
|
|
self.temp = bytearray(2)
|
|
self.write_list = [b"\x40", None] # Co=0, D/C#=1
|
|
super().__init__(width, height, external_vcc)
|
|
|
|
def write_cmd(self, cmd):
|
|
self.temp[0] = 0x80 # Co=1, D/C#=0
|
|
self.temp[1] = cmd
|
|
self.i2c.writeto(self.addr, self.temp)
|
|
|
|
def write_data(self, buf):
|
|
self.write_list[1] = buf
|
|
self.i2c.writevto(self.addr, self.write_list)
|
|
|
|
|
|
class SSD1306_SPI(SSD1306):
|
|
def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
|
|
self.rate = 10 * 1024 * 1024
|
|
dc.init(dc.OUT, value=0)
|
|
res.init(res.OUT, value=0)
|
|
cs.init(cs.OUT, value=1)
|
|
self.spi = spi
|
|
self.dc = dc
|
|
self.res = res
|
|
self.cs = cs
|
|
import time
|
|
|
|
self.res(1)
|
|
time.sleep_ms(1)
|
|
self.res(0)
|
|
time.sleep_ms(10)
|
|
self.res(1)
|
|
super().__init__(width, height, external_vcc)
|
|
|
|
def write_cmd(self, cmd):
|
|
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
|
self.cs(1)
|
|
self.dc(0)
|
|
self.cs(0)
|
|
self.spi.write(bytearray([cmd]))
|
|
self.cs(1)
|
|
|
|
def write_data(self, buf):
|
|
self.spi.init(baudrate=self.rate, polarity=0, phase=0)
|
|
self.cs(1)
|
|
self.dc(1)
|
|
self.cs(0)
|
|
self.spi.write(buf)
|
|
self.cs(1)
|