├── .gitignore ├── LICENSE ├── README.md ├── sdist_upip.py ├── setup.py └── ssd1306.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Stefan Lehmann 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # micropython-ssd1306 2 | This is a fork of the driver for SSD1306 displays which is hosted in the 3 | [Micropython package](https://github.com/micropython/micropython). The purpose 4 | of this fork is to make the driver available on PyPi and thus installable via 5 | the upip package manager. 6 | 7 | ## Installation 8 | Use the upip package manager: 9 | 10 | upip.install('micropython-ssd1306') 11 | 12 | If your board or your computer doesn't have an active internet connection you 13 | can also clone this project on your local drive and copy the *ssd1306.py* to 14 | your board. 15 | 16 | ## Example Usage 17 | This shows an example usage on an *ESP32* board with an *SSD1306* display 18 | with an resolution of 128x32 pixels. The display is connected via I2C. On the 19 | *ESP32* the I2C pins are: SDA: 23, SCL: 22. 20 | 21 | First we set up the I2C bus on our *ESP32* and scan for devices. 22 | 23 | >>> import machine 24 | >>> i2c = machine.I2C(sda=machine.Pin(23), scl=machine.Pin(22)) 25 | >>> i2c.scan() 26 | [60] 27 | 28 | This shows us that there is a device on address 60 which is 3C in Hex. That is 29 | where our display is supposed to live. Now we create an object for our OLED 30 | display. 31 | 32 | >>> from ssd1306 import SSD1306_I2C 33 | >>> oled = SSD1306_I2C(128, 32, i2c) 34 | 35 | This is it. Now we can use our OLED display: 36 | 37 | >>> oled.fill(1) 38 | >>> oled.show() 39 | 40 | This fills the whole display with white pixels. To clear the display do: 41 | 42 | >>> oled.fill(0) 43 | >>> oled.show() 44 | 45 | Now we can also write some text: 46 | 47 | >>> oled.text('Hello', 0, 0) 48 | >>> oled.text('World', 0, 10) 49 | >>> oled.show() 50 | 51 | Find more information on how to use the SSD1306 on the great [tutorial about 52 | the OLED featherwing from Adafruit](https://learn.adafruit.com/adafruit-oled-featherwing/circuitpython#usage-6-4). 53 | -------------------------------------------------------------------------------- /sdist_upip.py: -------------------------------------------------------------------------------- 1 | # 2 | # This module overrides distutils (also compatible with setuptools) "sdist" 3 | # command to perform pre- and post-processing as required for MicroPython's 4 | # upip package manager. 5 | # 6 | # Preprocessing steps: 7 | # * Creation of Python resource module (R.py) from each top-level package's 8 | # resources. 9 | # Postprocessing steps: 10 | # * Removing metadata files not used by upip (this includes setup.py) 11 | # * Recompressing gzip archive with 4K dictionary size so it can be 12 | # installed even on low-heap targets. 13 | # 14 | import sys 15 | import os 16 | import zlib 17 | from subprocess import Popen, PIPE 18 | import glob 19 | import tarfile 20 | import re 21 | import io 22 | 23 | from distutils.filelist import FileList 24 | from setuptools.command.sdist import sdist as _sdist 25 | 26 | 27 | def gzip_4k(inf, fname): 28 | comp = zlib.compressobj(level=9, wbits=16 + 12) 29 | with open(fname + ".out", "wb") as outf: 30 | while 1: 31 | data = inf.read(1024) 32 | if not data: 33 | break 34 | outf.write(comp.compress(data)) 35 | outf.write(comp.flush()) 36 | os.rename(fname, fname + ".orig") 37 | os.rename(fname + ".out", fname) 38 | 39 | 40 | FILTERS = [ 41 | # include, exclude, repeat 42 | (r".+\.egg-info/(PKG-INFO|requires\.txt)", r"setup.py$"), 43 | (r".+\.py$", r"[^/]+$"), 44 | (None, r".+\.egg-info/.+"), 45 | ] 46 | 47 | 48 | outbuf = io.BytesIO() 49 | 50 | def filter_tar(name): 51 | fin = tarfile.open(name, "r:gz") 52 | fout = tarfile.open(fileobj=outbuf, mode="w") 53 | for info in fin: 54 | # print(info) 55 | if not "/" in info.name: 56 | continue 57 | fname = info.name.split("/", 1)[1] 58 | include = None 59 | 60 | for inc_re, exc_re in FILTERS: 61 | if include is None and inc_re: 62 | if re.match(inc_re, fname): 63 | include = True 64 | 65 | if include is None and exc_re: 66 | if re.match(exc_re, fname): 67 | include = False 68 | 69 | if include is None: 70 | include = True 71 | 72 | if include: 73 | print("including:", fname) 74 | else: 75 | print("excluding:", fname) 76 | continue 77 | 78 | farch = fin.extractfile(info) 79 | fout.addfile(info, farch) 80 | fout.close() 81 | fin.close() 82 | 83 | 84 | def make_resource_module(manifest_files): 85 | resources = [] 86 | # Any non-python file included in manifest is resource 87 | for fname in manifest_files: 88 | ext = fname.rsplit(".", 1) 89 | if len(ext) > 1: 90 | ext = ext[1] 91 | else: 92 | ext = "" 93 | if ext != "py": 94 | resources.append(fname) 95 | 96 | if resources: 97 | print("creating resource module R.py") 98 | resources.sort() 99 | last_pkg = None 100 | r_file = None 101 | for fname in resources: 102 | try: 103 | pkg, res_name = fname.split("/", 1) 104 | except ValueError: 105 | print("not treating %s as a resource" % fname) 106 | continue 107 | if last_pkg != pkg: 108 | last_pkg = pkg 109 | if r_file: 110 | r_file.write("}\n") 111 | r_file.close() 112 | r_file = open(pkg + "/R.py", "w") 113 | r_file.write("R = {\n") 114 | 115 | with open(fname, "rb") as f: 116 | r_file.write("%r: %r,\n" % (res_name, f.read())) 117 | 118 | if r_file: 119 | r_file.write("}\n") 120 | r_file.close() 121 | 122 | 123 | class sdist(_sdist): 124 | 125 | def run(self): 126 | self.filelist = FileList() 127 | self.get_file_list() 128 | make_resource_module(self.filelist.files) 129 | 130 | r = super().run() 131 | 132 | assert len(self.archive_files) == 1 133 | print("filtering files and recompressing with 4K dictionary") 134 | filter_tar(self.archive_files[0]) 135 | outbuf.seek(0) 136 | gzip_4k(outbuf, self.archive_files[0]) 137 | 138 | return r 139 | 140 | 141 | # For testing only 142 | if __name__ == "__main__": 143 | filter_tar(sys.argv[1]) 144 | outbuf.seek(0) 145 | gzip_4k(outbuf, sys.argv[1]) 146 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | # Remove current dir from sys.path, otherwise setuptools will peek up our 3 | # module instead of system's. 4 | sys.path.pop(0) 5 | from setuptools import setup 6 | sys.path.append("..") 7 | import sdist_upip 8 | 9 | setup(name='micropython-ssd1306', 10 | version='0.3', 11 | description='ssd1306 module for MicroPython', 12 | long_description="", 13 | url='https://github.com/stlehmann/micropython-ssd1306', 14 | author='Stefan Lehmann', 15 | author_email='stlm@posteo.de', 16 | maintainer='Stefan Lehmann', 17 | maintainer_email='stlm@posteo.de', 18 | license='MIT', 19 | cmdclass={'sdist': sdist_upip.sdist}, 20 | py_modules=['ssd1306']) 21 | -------------------------------------------------------------------------------- /ssd1306.py: -------------------------------------------------------------------------------- 1 | # MicroPython SSD1306 OLED driver, I2C and SPI interfaces 2 | 3 | from micropython import const 4 | import framebuf 5 | 6 | 7 | # register definitions 8 | SET_CONTRAST = const(0x81) 9 | SET_ENTIRE_ON = const(0xA4) 10 | SET_NORM_INV = const(0xA6) 11 | SET_DISP = const(0xAE) 12 | SET_MEM_ADDR = const(0x20) 13 | SET_COL_ADDR = const(0x21) 14 | SET_PAGE_ADDR = const(0x22) 15 | SET_DISP_START_LINE = const(0x40) 16 | SET_SEG_REMAP = const(0xA0) 17 | SET_MUX_RATIO = const(0xA8) 18 | SET_COM_OUT_DIR = const(0xC0) 19 | SET_DISP_OFFSET = const(0xD3) 20 | SET_COM_PIN_CFG = const(0xDA) 21 | SET_DISP_CLK_DIV = const(0xD5) 22 | SET_PRECHARGE = const(0xD9) 23 | SET_VCOM_DESEL = const(0xDB) 24 | SET_CHARGE_PUMP = const(0x8D) 25 | 26 | # Subclassing FrameBuffer provides support for graphics primitives 27 | # http://docs.micropython.org/en/latest/pyboard/library/framebuf.html 28 | class SSD1306(framebuf.FrameBuffer): 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 | self.buffer = bytearray(self.pages * self.width) 35 | super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) 36 | self.init_display() 37 | 38 | def init_display(self): 39 | for cmd in ( 40 | SET_DISP | 0x00, # off 41 | # address setting 42 | SET_MEM_ADDR, 43 | 0x00, # horizontal 44 | # resolution and layout 45 | SET_DISP_START_LINE | 0x00, 46 | SET_SEG_REMAP | 0x01, # column addr 127 mapped to SEG0 47 | SET_MUX_RATIO, 48 | self.height - 1, 49 | SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 50 | SET_DISP_OFFSET, 51 | 0x00, 52 | SET_COM_PIN_CFG, 53 | 0x02 if self.width > 2 * self.height else 0x12, 54 | # timing and driving scheme 55 | SET_DISP_CLK_DIV, 56 | 0x80, 57 | SET_PRECHARGE, 58 | 0x22 if self.external_vcc else 0xF1, 59 | SET_VCOM_DESEL, 60 | 0x30, # 0.83*Vcc 61 | # display 62 | SET_CONTRAST, 63 | 0xFF, # maximum 64 | SET_ENTIRE_ON, # output follows RAM contents 65 | SET_NORM_INV, # not inverted 66 | # charge pump 67 | SET_CHARGE_PUMP, 68 | 0x10 if self.external_vcc else 0x14, 69 | SET_DISP | 0x01, 70 | ): # on 71 | self.write_cmd(cmd) 72 | self.fill(0) 73 | self.show() 74 | 75 | def poweroff(self): 76 | self.write_cmd(SET_DISP | 0x00) 77 | 78 | def poweron(self): 79 | self.write_cmd(SET_DISP | 0x01) 80 | 81 | def contrast(self, contrast): 82 | self.write_cmd(SET_CONTRAST) 83 | self.write_cmd(contrast) 84 | 85 | def invert(self, invert): 86 | self.write_cmd(SET_NORM_INV | (invert & 1)) 87 | 88 | def show(self): 89 | x0 = 0 90 | x1 = self.width - 1 91 | if self.width == 64: 92 | # displays with width of 64 pixels are shifted by 32 93 | x0 += 32 94 | x1 += 32 95 | self.write_cmd(SET_COL_ADDR) 96 | self.write_cmd(x0) 97 | self.write_cmd(x1) 98 | self.write_cmd(SET_PAGE_ADDR) 99 | self.write_cmd(0) 100 | self.write_cmd(self.pages - 1) 101 | self.write_data(self.buffer) 102 | 103 | 104 | class SSD1306_I2C(SSD1306): 105 | def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): 106 | self.i2c = i2c 107 | self.addr = addr 108 | self.temp = bytearray(2) 109 | self.write_list = [b"\x40", None] # Co=0, D/C#=1 110 | super().__init__(width, height, external_vcc) 111 | 112 | def write_cmd(self, cmd): 113 | self.temp[0] = 0x80 # Co=1, D/C#=0 114 | self.temp[1] = cmd 115 | self.i2c.writeto(self.addr, self.temp) 116 | 117 | def write_data(self, buf): 118 | self.write_list[1] = buf 119 | self.i2c.writevto(self.addr, self.write_list) 120 | 121 | 122 | class SSD1306_SPI(SSD1306): 123 | def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): 124 | self.rate = 10 * 1024 * 1024 125 | dc.init(dc.OUT, value=0) 126 | res.init(res.OUT, value=0) 127 | cs.init(cs.OUT, value=1) 128 | self.spi = spi 129 | self.dc = dc 130 | self.res = res 131 | self.cs = cs 132 | import time 133 | 134 | self.res(1) 135 | time.sleep_ms(1) 136 | self.res(0) 137 | time.sleep_ms(10) 138 | self.res(1) 139 | super().__init__(width, height, external_vcc) 140 | 141 | def write_cmd(self, cmd): 142 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 143 | self.cs(1) 144 | self.dc(0) 145 | self.cs(0) 146 | self.spi.write(bytearray([cmd])) 147 | self.cs(1) 148 | 149 | def write_data(self, buf): 150 | self.spi.init(baudrate=self.rate, polarity=0, phase=0) 151 | self.cs(1) 152 | self.dc(1) 153 | self.cs(0) 154 | self.spi.write(buf) 155 | self.cs(1) 156 | --------------------------------------------------------------------------------