|
| 1 | +# MicroPython SSD1306 OLED driver, I2C and SPI interfaces |
| 2 | +import time |
| 3 | +import framebuf |
| 4 | + |
| 5 | +from adafruit_bus_device import i2c_device, spi_device |
| 6 | + |
| 7 | + |
| 8 | +# register definitions |
| 9 | +SET_CONTRAST = const(0x81) |
| 10 | +SET_ENTIRE_ON = const(0xa4) |
| 11 | +SET_NORM_INV = const(0xa6) |
| 12 | +SET_DISP = const(0xae) |
| 13 | +SET_MEM_ADDR = const(0x20) |
| 14 | +SET_COL_ADDR = const(0x21) |
| 15 | +SET_PAGE_ADDR = const(0x22) |
| 16 | +SET_DISP_START_LINE = const(0x40) |
| 17 | +SET_SEG_REMAP = const(0xa0) |
| 18 | +SET_MUX_RATIO = const(0xa8) |
| 19 | +SET_COM_OUT_DIR = const(0xc0) |
| 20 | +SET_DISP_OFFSET = const(0xd3) |
| 21 | +SET_COM_PIN_CFG = const(0xda) |
| 22 | +SET_DISP_CLK_DIV = const(0xd5) |
| 23 | +SET_PRECHARGE = const(0xd9) |
| 24 | +SET_VCOM_DESEL = const(0xdb) |
| 25 | +SET_CHARGE_PUMP = const(0x8d) |
| 26 | + |
| 27 | + |
| 28 | +class SSD1306: |
| 29 | + def __init__(self, width, height, external_vcc): |
| 30 | + self.width = width |
| 31 | + self.height = height |
| 32 | + self.external_vcc = external_vcc |
| 33 | + self.pages = self.height // 8 |
| 34 | + # Note the subclass must initialize self.framebuf to a framebuffer. |
| 35 | + # This is necessary because the underlying data buffer is different |
| 36 | + # between I2C and SPI implementations (I2C needs an extra byte). |
| 37 | + self.poweron() |
| 38 | + self.init_display() |
| 39 | + |
| 40 | + def init_display(self): |
| 41 | + for cmd in ( |
| 42 | + SET_DISP | 0x00, # off |
| 43 | + # address setting |
| 44 | + SET_MEM_ADDR, 0x00, # horizontal |
| 45 | + # resolution and layout |
| 46 | + SET_DISP_START_LINE | 0x00, |
| 47 | + SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 |
| 48 | + SET_MUX_RATIO, self.height - 1, |
| 49 | + SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 |
| 50 | + SET_DISP_OFFSET, 0x00, |
| 51 | + SET_COM_PIN_CFG, 0x02 if self.height == 32 else 0x12, |
| 52 | + # timing and driving scheme |
| 53 | + SET_DISP_CLK_DIV, 0x80, |
| 54 | + SET_PRECHARGE, 0x22 if self.external_vcc else 0xf1, |
| 55 | + SET_VCOM_DESEL, 0x30, # 0.83*Vcc |
| 56 | + # display |
| 57 | + SET_CONTRAST, 0xff, # maximum |
| 58 | + SET_ENTIRE_ON, # output follows RAM contents |
| 59 | + SET_NORM_INV, # not inverted |
| 60 | + # charge pump |
| 61 | + SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14, |
| 62 | + SET_DISP | 0x01): # on |
| 63 | + self.write_cmd(cmd) |
| 64 | + self.fill(0) |
| 65 | + self.show() |
| 66 | + |
| 67 | + def poweroff(self): |
| 68 | + self.write_cmd(SET_DISP | 0x00) |
| 69 | + |
| 70 | + def contrast(self, contrast): |
| 71 | + self.write_cmd(SET_CONTRAST) |
| 72 | + self.write_cmd(contrast) |
| 73 | + |
| 74 | + def invert(self, invert): |
| 75 | + self.write_cmd(SET_NORM_INV | (invert & 1)) |
| 76 | + |
| 77 | + def show(self): |
| 78 | + x0 = 0 |
| 79 | + x1 = self.width - 1 |
| 80 | + if self.width == 64: |
| 81 | + # displays with width of 64 pixels are shifted by 32 |
| 82 | + x0 += 32 |
| 83 | + x1 += 32 |
| 84 | + self.write_cmd(SET_COL_ADDR) |
| 85 | + self.write_cmd(x0) |
| 86 | + self.write_cmd(x1) |
| 87 | + self.write_cmd(SET_PAGE_ADDR) |
| 88 | + self.write_cmd(0) |
| 89 | + self.write_cmd(self.pages - 1) |
| 90 | + self.write_framebuf() |
| 91 | + |
| 92 | + def fill(self, col): |
| 93 | + self.framebuf.fill(col) |
| 94 | + |
| 95 | + def pixel(self, x, y, col): |
| 96 | + self.framebuf.pixel(x, y, col) |
| 97 | + |
| 98 | + def scroll(self, dx, dy): |
| 99 | + self.framebuf.scroll(dx, dy) |
| 100 | + |
| 101 | + def text(self, string, x, y, col=1): |
| 102 | + self.framebuf.text(string, x, y, col) |
| 103 | + |
| 104 | + |
| 105 | +class SSD1306_I2C(SSD1306): |
| 106 | + def __init__(self, width, height, i2c, addr=0x3c, external_vcc=False): |
| 107 | + self.i2c_device = i2c_device.I2CDevice(i2c, addr) |
| 108 | + self.addr = addr |
| 109 | + self.temp = bytearray(2) |
| 110 | + # Add an extra byte to the data buffer to hold an I2C data/command byte |
| 111 | + # to use hardware-compatible I2C transactions. A memoryview of the |
| 112 | + # buffer is used to mask this byte from the framebuffer operations |
| 113 | + # (without a major memory hit as memoryview doesn't copy to a separate |
| 114 | + # buffer). |
| 115 | + self.buffer = bytearray(((height // 8) * width) + 1) |
| 116 | + self.buffer[0] = 0x40 # Set first byte of data buffer to Co=0, D/C=1 |
| 117 | + self.framebuf = framebuf.FrameBuffer1(memoryview(self.buffer)[1:], width, height) |
| 118 | + super().__init__(width, height, external_vcc) |
| 119 | + |
| 120 | + def write_cmd(self, cmd): |
| 121 | + self.temp[0] = 0x80 # Co=1, D/C#=0 |
| 122 | + self.temp[1] = cmd |
| 123 | + with self.i2c_device: |
| 124 | + self.i2c_device.writeto(self.temp) |
| 125 | + |
| 126 | + def write_framebuf(self): |
| 127 | + # Blast out the frame buffer using a single I2C transaction to support |
| 128 | + # hardware I2C interfaces. |
| 129 | + with self.i2c_device: |
| 130 | + self.i2c_device.writeto(self.buffer) |
| 131 | + |
| 132 | + def poweron(self): |
| 133 | + pass |
| 134 | + |
| 135 | + |
| 136 | +class SSD1306_SPI(SSD1306): |
| 137 | + def __init__(self, width, height, spi, dc, res, cs, external_vcc=False, |
| 138 | + baudrate=8000000, polarity=0, phase=0): |
| 139 | + self.rate = 10 * 1024 * 1024 |
| 140 | + dc.switch_to_output(value=0) |
| 141 | + res.switch_to_output(value=0) |
| 142 | + self.spi_device = spi_device.SPIDevice(spi, cs, baudrate=baudrate, |
| 143 | + polarity=polarity, phase=phase) |
| 144 | + self.dc = dc |
| 145 | + self.res = res |
| 146 | + self.buffer = bytearray((height // 8) * width) |
| 147 | + self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height) |
| 148 | + super().__init__(width, height, external_vcc) |
| 149 | + |
| 150 | + def write_cmd(self, cmd): |
| 151 | + self.dc.value = 0 |
| 152 | + with self.spi_device: |
| 153 | + self.spi_device.write(bytearray([cmd])) |
| 154 | + |
| 155 | + def write_framebuf(self): |
| 156 | + self.dc.value = 1 |
| 157 | + with self.spi_device: |
| 158 | + self.spi.write(self.buffer) |
| 159 | + |
| 160 | + def poweron(self): |
| 161 | + self.res.value = 1 |
| 162 | + time.sleep(0.001) |
| 163 | + self.res.value = 0 |
| 164 | + time.sleep(0.010) |
| 165 | + self.res.value = 1 |
0 commit comments