├── LICENSE ├── package.json ├── readme.md ├── sh1106.py └── sh1106.pyi /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Radomir Dopieralski (@deshipu), 4 | 2017-2021 Robert Hammelrath (@robert-hh) 5 | 2021 Tim Weber (@scy) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": [ 3 | ["sh1106.py", "github:robert-hh/SH1106/sh1106.py"] 4 | ], 5 | "version": "1.0.0", 6 | "deps": [] 7 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Driver for the SH1106 display 2 | 3 | This driver consists mostly of the work of Radomir Dopieralski (@deshipu). 4 | I added a few functions and changed the existing ones so it matches better 5 | my needs for a project. 6 | 7 | A modified version of this driver compatible with [nano-gui widget library](https://github.com/peterhinch/micropython-nano-gui) is hosted in that project. 8 | 9 | ## Features 10 | 11 | Use OLED display with the SH1106 driver with SPI or I2C. It is based on the MicroPython 12 | framebuffer class and consists wrappers for this class as well as special methods 13 | for controlling the display. 14 | 15 | ## Type Stubs for IDE Support 16 | This library now includes `.pyi` type stubs for better IDE integration. To enable autocomplete and type checking in VS Code or other editors: 17 | - Ensure the `.pyi` files are in your project directory or accessible in your Python path. 18 | - If needed, add the directory to your IDE's settings (e.g., `python.analysis.extraPaths` in VS Code). 19 | - If you are using a virtual environment, ensure the stubs are installed in the virtual environment. 20 | 21 | ### Content Rotation 22 | 23 | The `rotate` parameter in the constructor allows you to rotate the display by a 90, 180 or 270 24 | degrees clockwise. 180 degrees are easy, because this can be done using only hardware flags of the 25 | SH1106 display. 90 and 270 degrees however are not. These come at a price: Since we will have to it 26 | in software, a second, internal framebuffer will be created, using an additional 27 | `width * height / 8` bytes of RAM. Also, each call to `show()` will take about 33% longer. 28 | 29 | Set `width` and `height` in the constructor to the _physical_ dimensions of your display, regardless 30 | of how you would like to rotate it. 31 | 32 | You can use the `flip()` method to toggle between 0 and 180 degrees of rotation, or between 90 and 33 | 270 degrees, at runtime, which is equivalent to rotating the contents for 180 degrees compared to 34 | whatever the rotation was before. It is however not possible to switch from "portrait" to 35 | "landscape" or vice versa at runtime, because of the additional buffer required. 36 | 37 | ## Connection 38 | 39 | The SH1106 supports next to thers the I2C or SPI interface. The connection depends on the interface used 40 | and the number of devices in the system. Especially the ESP8266 with their small 41 | number of GPIO ports may require optimization. 42 | 43 | ### I2C 44 | SCL and SDA have to be connected as minimum. The driver also resets the device by the reset PIN. 45 | If your are low on GPIO ports, reset can be applied by a dedicated circuit, like the MCP100-300. 46 | 47 | ### SPI 48 | SCLK, MOSI, D/C are always required. If the display is the only SPI device in the set-up, 49 | CS may be tied to GND. Reset has also to be connected, unless it is driven 50 | by an external circuit. 51 | 52 | 53 | ## Class 54 | 55 | The driver contains the SH1106 class and the derived SH1106_I2C and SH1106_SPI classes. 56 | Besides the constructors, the methods are the same. 57 | 58 | ### I2C 59 | ``` 60 | display = sh1106.SH1106_I2C(width, height, i2c, reset, address, rotate=0, delay=0) 61 | ``` 62 | - width and height define the size of the display 63 | - i2c is an I2C object, which has to be created beforehand and tells the ports for SDA and SCL. 64 | - res is the GPIO Pin object for the reset connection. It will be initialized by the driver. 65 | If it is not needed, `None` has to be supplied. 66 | - adr is the I2C address of the display. Default 0x3c or 60 67 | - rotate defines display content rotation. See above for details and caveats. 68 | - delay specifies an optional delay during poweron. The quantity is ms. 69 | 70 | 71 | ### SPI 72 | ``` 73 | display = sh1106.SH1106_SPI(width, height, spi, dc, res, cs, rotate=0, delay=0) 74 | ``` 75 | - width and height define the size of the display 76 | - spi is an SPI object, which has to be created beforehand and tells the ports for SCLJ and MOSI. 77 | MISO is not used. 78 | - dc is the GPIO Pin object for the Data/Command selection. It will be initialized by the driver. 79 | - res is the GPIO Pin object for the reset connection. It will be initialized by the driver. 80 | If it is not needed, it can be set to `None` or omitted. In this case the default value 81 | of `None` applies. 82 | - cs is the GPIO Pin object for the CS connection. It will be initialized by the driver. 83 | If it is not needed, it can be set to `None` or omitted. In this case the default value 84 | of `None` applies. 85 | - rotate defines display content rotation. See above for details and caveats. 86 | - delay specifies an optional delay during poweron. The quantity is ms. 87 | 88 | 89 | ## Methods 90 | 91 | ### display.init_display() 92 | ``` 93 | display.init_display() 94 | ``` 95 | Initializes the display, fills it with the color 0 and displays the empty screen. It also tries 96 | to apply the reset signal, if it is connected ( = not `None`). 97 | 98 | ### display.power_on() and display.power_off() 99 | 100 | ``` 101 | display.poweron() 102 | display.poweroff() 103 | display.sleep(state) 104 | ``` 105 | Enable and disable the display. `display.sleep(True)` is identical to `display.poweroff()`, 106 | `display.sleep(False)` is equivalent to `display.poweron()`. 107 | Other than the literal meaning could tell, it does not switch the power line(Vcc) 108 | of the display. 109 | 110 | ### display.contrast() 111 | 112 | ``` 113 | display.contrast(level) 114 | ``` 115 | Set the display's contrast to the given level. The range is 0..255. For a single color 116 | display like the SH1106, this is actually the brightness. 117 | 118 | ### display.invert() 119 | ``` 120 | display.invert(flag) 121 | ``` 122 | Invert the content of the display, depending on the value of Flag. This is immediately 123 | effective for the whole display. 124 | - flag = True Invert 125 | - flag = False Normal mode 126 | 127 | ### display.flip() 128 | ``` 129 | display.flip([flag=None[, update=True]]) 130 | ``` 131 | Rotate the content of the display an additional 180 degrees, depending on the value of `flag`. 132 | 133 | - `True`: If you selected 0 or 90 degrees of rotation in the constructor, rotation will be set to 180 or 270, respectively. Else, it has no effect. 134 | - `False`: If you selected 180 or 270 degrees of rotation in the constructor, rotation will be set to 0 or 90, respectively. Else, it has no effect. 135 | - `None`: Toggle flip on or off: 0 degrees will become 180, 90 will become 270, 180 will become 0 and 270 will become 90. 136 | 137 | To become fully effective, you have to run `display.show()`. If the parameter `update` is `True`, 138 | `show()` is called by the function itself. 139 | 140 | ### display.show() 141 | 142 | Display the content of the frame buffer on the display. 143 | ``` 144 | display.show() 145 | ``` 146 | The usual program flow would set-up/update the frame buffer content with a sequence of calls 147 | an the call display.show() for the content to be shown (see examples below). 148 | 149 | ## Framebuffer Methods 150 | 151 | The below listed display methods of the framebuffer class are mirrored in this 152 | class. For a documentation, please look into the MicroPython documentation at http://docs.micropython.org/en/latest/pyboard/library/framebuf.html?highlight=framebuf#module-framebuf: 153 | 154 | - fill 155 | - fill_rect 156 | - line 157 | - vline 158 | - hline 159 | - rect 160 | - pixel 161 | - scroll 162 | - text 163 | - blit 164 | - ellipse 165 | 166 | 167 | The text is displayed with the built-in 168 | 8x8 pixel font, which support the ASCII character set values 32..127. The text overlays 169 | the previous content; 'on' pixels in a character will not overwrite existing 'off' pixels. 170 | If you want to rewrite an area of the screen, you have to clear it beforehand, 171 | e.g. with the `fill_rect()` method. 172 | 173 | Remark: If you want to use other font styles and sizes, have a look at 174 | the work of Peter Hinch (@pythoncoder) at https://github.com/peterhinch/micropython-font-to-py 175 | 176 | ### display.reset() 177 | ``` 178 | display.reset() 179 | ``` 180 | Attempt to reset the display by toggling the reset line. This is obviously only effective 181 | is reset is connected. Otherwise it's a No-Op. 182 | 183 | 184 | # Sample Code 185 | 186 | ## SPI 187 | ``` 188 | # MicroPython SH1106 OLED driver 189 | # 190 | # Pin Map SPI for ESP8266 191 | # - 3v - xxxxxx - Vcc 192 | # - G - xxxxxx - Gnd 193 | # - D7 - GPIO 13 - Din / MOSI fixed 194 | # - D5 - GPIO 14 - Clk / SCLK fixed 195 | # - D8 - GPIO 4 - CS (optional, if the only connected device) 196 | # - D2 - GPIO 5 - D/C 197 | # - D1 - GPIO 2 - Res (required, unless a Hardware reset circuit is connected) 198 | # 199 | # for CS, D/C and Res other ports may be chosen. 200 | # 201 | from machine import Pin, SPI 202 | import sh1106 203 | 204 | spi = SPI(1, baudrate=1000000) 205 | display = sh1106.SH1106_SPI(128, 64, spi, Pin(5), Pin(2), Pin(4)) 206 | display.sleep(False) 207 | display.fill(0) 208 | display.text('Testing 1', 0, 0, 1) 209 | display.show() 210 | ``` 211 | ## I2C 212 | 213 | ``` 214 | # MicroPython SH1106 OLED driver 215 | # 216 | # Pin Map I2C for ESP8266 217 | # - 3v - xxxxxx - Vcc 218 | # - G - xxxxxx - Gnd 219 | # - D2 - GPIO 5 - SCK / SCL 220 | # - D1 - GPIO 4 - DIN / SDA 221 | # - D0 - GPIO 16 - Res (required, unless a Hardware reset circuit is connected) 222 | # - G - xxxxxx CS 223 | # - G - xxxxxx D/C 224 | # 225 | # Pin's for I2C can be set almost arbitrary 226 | # 227 | from machine import Pin, I2C 228 | import sh1106 229 | 230 | i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000) 231 | display = sh1106.SH1106_I2C(128, 64, i2c, Pin(16), 0x3c) 232 | display.sleep(False) 233 | display.fill(0) 234 | display.text('Testing 1', 0, 0, 1) 235 | display.show() 236 | ``` 237 | -------------------------------------------------------------------------------- /sh1106.py: -------------------------------------------------------------------------------- 1 | # 2 | # MicroPython SH1106 OLED driver, I2C and SPI interfaces 3 | # 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2016 Radomir Dopieralski (@deshipu), 7 | # 2017-2021 Robert Hammelrath (@robert-hh) 8 | # 2021 Tim Weber (@scy) 9 | # 10 | # Permission is hereby granted, free of charge, to any person obtaining a copy 11 | # of this software and associated documentation files (the "Software"), to deal 12 | # in the Software without restriction, including without limitation the rights 13 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | # copies of the Software, and to permit persons to whom the Software is 15 | # furnished to do so, subject to the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be included in 18 | # all copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | # THE SOFTWARE. 27 | # 28 | # Sample code sections for ESP8266 pin assignments 29 | # ------------ SPI ------------------ 30 | # Pin Map SPI 31 | # - 3v - xxxxxx - Vcc 32 | # - G - xxxxxx - Gnd 33 | # - D7 - GPIO 13 - Din / MOSI fixed 34 | # - D5 - GPIO 14 - Clk / Sck fixed 35 | # - D8 - GPIO 4 - CS (optional, if the only connected device) 36 | # - D2 - GPIO 5 - D/C 37 | # - D1 - GPIO 2 - Res 38 | # 39 | # for CS, D/C and Res other ports may be chosen. 40 | # 41 | # from machine import Pin, SPI 42 | # import sh1106 43 | 44 | # spi = SPI(1, baudrate=1000000) 45 | # display = sh1106.SH1106_SPI(128, 64, spi, Pin(5), Pin(2), Pin(4)) 46 | # display.sleep(False) 47 | # display.fill(0) 48 | # display.text('Testing 1', 0, 0, 1) 49 | # display.show() 50 | # 51 | # --------------- I2C ------------------ 52 | # 53 | # Pin Map I2C 54 | # - 3v - xxxxxx - Vcc 55 | # - G - xxxxxx - Gnd 56 | # - D2 - GPIO 5 - SCK / SCL 57 | # - D1 - GPIO 4 - DIN / SDA 58 | # - D0 - GPIO 16 - Res 59 | # - G - xxxxxx CS 60 | # - G - xxxxxx D/C 61 | # 62 | # Pin's for I2C can be set almost arbitrary 63 | # 64 | # from machine import Pin, I2C 65 | # import sh1106 66 | # 67 | # i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000) 68 | # display = sh1106.SH1106_I2C(128, 64, i2c, Pin(16), 0x3c) 69 | # display.sleep(False) 70 | # display.fill(0) 71 | # display.text('Testing 1', 0, 0, 1) 72 | # display.show() 73 | 74 | from micropython import const 75 | import utime as time 76 | import framebuf 77 | 78 | 79 | # a few register definitions 80 | _SET_CONTRAST = const(0x81) 81 | _SET_NORM_INV = const(0xa6) 82 | _SET_DISP = const(0xae) 83 | _SET_SCAN_DIR = const(0xc0) 84 | _SET_SEG_REMAP = const(0xa0) 85 | _LOW_COLUMN_ADDRESS = const(0x00) 86 | _HIGH_COLUMN_ADDRESS = const(0x10) 87 | _SET_PAGE_ADDRESS = const(0xB0) 88 | 89 | 90 | class SH1106(framebuf.FrameBuffer): 91 | 92 | def __init__(self, width, height, external_vcc, rotate=0): 93 | self.width = width 94 | self.height = height 95 | self.external_vcc = external_vcc 96 | self.flip_en = rotate == 180 or rotate == 270 97 | self.rotate90 = rotate == 90 or rotate == 270 98 | self.pages = self.height // 8 99 | self.bufsize = self.pages * self.width 100 | self.renderbuf = bytearray(self.bufsize) 101 | self.pages_to_update = 0 102 | self.delay = 0 103 | 104 | if self.rotate90: 105 | self.displaybuf = bytearray(self.bufsize) 106 | # HMSB is required to keep the bit order in the render buffer 107 | # compatible with byte-for-byte remapping to the display buffer, 108 | # which is in VLSB. Else we'd have to copy bit-by-bit! 109 | super().__init__(self.renderbuf, self.height, self.width, 110 | framebuf.MONO_HMSB) 111 | else: 112 | self.displaybuf = self.renderbuf 113 | super().__init__(self.renderbuf, self.width, self.height, 114 | framebuf.MONO_VLSB) 115 | 116 | # flip() was called rotate() once, provide backwards compatibility. 117 | self.rotate = self.flip 118 | self.init_display() 119 | 120 | # abstractmethod 121 | def write_cmd(self, *args, **kwargs): 122 | raise NotImplementedError 123 | 124 | # abstractmethod 125 | def write_data(self, *args, **kwargs): 126 | raise NotImplementedError 127 | 128 | def init_display(self): 129 | self.reset() 130 | self.fill(0) 131 | self.show() 132 | self.poweron() 133 | # rotate90 requires a call to flip() for setting up. 134 | self.flip(self.flip_en) 135 | 136 | def poweroff(self): 137 | self.write_cmd(_SET_DISP | 0x00) 138 | 139 | def poweron(self): 140 | self.write_cmd(_SET_DISP | 0x01) 141 | if self.delay: 142 | time.sleep_ms(self.delay) 143 | 144 | def flip(self, flag=None, update=True): 145 | if flag is None: 146 | flag = not self.flip_en 147 | mir_v = flag ^ self.rotate90 148 | mir_h = flag 149 | self.write_cmd(_SET_SEG_REMAP | (0x01 if mir_v else 0x00)) 150 | self.write_cmd(_SET_SCAN_DIR | (0x08 if mir_h else 0x00)) 151 | self.flip_en = flag 152 | if update: 153 | self.show(True) # full update 154 | 155 | def sleep(self, value): 156 | self.write_cmd(_SET_DISP | (not value)) 157 | 158 | def contrast(self, contrast): 159 | self.write_cmd(_SET_CONTRAST) 160 | self.write_cmd(contrast) 161 | 162 | def invert(self, invert): 163 | self.write_cmd(_SET_NORM_INV | (invert & 1)) 164 | 165 | def show(self, full_update = False): 166 | # self.* lookups in loops take significant time (~4fps). 167 | (w, p, db, rb) = (self.width, self.pages, 168 | self.displaybuf, self.renderbuf) 169 | if self.rotate90: 170 | for i in range(self.bufsize): 171 | db[w * (i % p) + (i // p)] = rb[i] 172 | if full_update: 173 | pages_to_update = (1 << self.pages) - 1 174 | else: 175 | pages_to_update = self.pages_to_update 176 | #print("Updating pages: {:08b}".format(pages_to_update)) 177 | for page in range(self.pages): 178 | if (pages_to_update & (1 << page)): 179 | self.write_cmd(_SET_PAGE_ADDRESS | page) 180 | self.write_cmd(_LOW_COLUMN_ADDRESS | 2) 181 | self.write_cmd(_HIGH_COLUMN_ADDRESS | 0) 182 | self.write_data(db[(w*page):(w*page+w)]) 183 | self.pages_to_update = 0 184 | 185 | def pixel(self, x, y, color=None): 186 | if color is None: 187 | return super().pixel(x, y) 188 | else: 189 | super().pixel(x, y , color) 190 | page = y // 8 191 | self.pages_to_update |= 1 << page 192 | 193 | def text(self, text, x, y, color=1): 194 | super().text(text, x, y, color) 195 | self.register_updates(y, y+7) 196 | 197 | def line(self, x0, y0, x1, y1, color): 198 | super().line(x0, y0, x1, y1, color) 199 | self.register_updates(y0, y1) 200 | 201 | def hline(self, x, y, w, color): 202 | super().hline(x, y, w, color) 203 | self.register_updates(y) 204 | 205 | def vline(self, x, y, h, color): 206 | super().vline(x, y, h, color) 207 | self.register_updates(y, y+h-1) 208 | 209 | def fill(self, color): 210 | super().fill(color) 211 | self.pages_to_update = (1 << self.pages) - 1 212 | 213 | def blit(self, fbuf, x, y, key=-1, palette=None): 214 | super().blit(fbuf, x, y, key, palette) 215 | self.register_updates(y, y+self.height) 216 | 217 | def scroll(self, x, y): 218 | # my understanding is that scroll() does a full screen change 219 | super().scroll(x, y) 220 | self.pages_to_update = (1 << self.pages) - 1 221 | 222 | def fill_rect(self, x, y, w, h, color): 223 | super().fill_rect(x, y, w, h, color) 224 | self.register_updates(y, y+h-1) 225 | 226 | def rect(self, x, y, w, h, color): 227 | super().rect(x, y, w, h, color) 228 | self.register_updates(y, y+h-1) 229 | 230 | def ellipse(self, x, y, xr, yr, color): 231 | super().ellipse(x, y, xr, yr, color) 232 | self.register_updates(y-yr, y+yr-1) 233 | 234 | def register_updates(self, y0, y1=None): 235 | # this function takes the top and optional bottom address of the changes made 236 | # and updates the pages_to_change list with any changed pages 237 | # that are not yet on the list 238 | start_page = max(0, y0 // 8) 239 | end_page = max(0, y1 // 8) if y1 is not None else start_page 240 | # rearrange start_page and end_page if coordinates were given from bottom to top 241 | if start_page > end_page: 242 | start_page, end_page = end_page, start_page 243 | for page in range(start_page, end_page+1): 244 | self.pages_to_update |= 1 << page 245 | 246 | def reset(self, res=None): 247 | if res is not None: 248 | res(1) 249 | time.sleep_ms(1) 250 | res(0) 251 | time.sleep_ms(20) 252 | res(1) 253 | time.sleep_ms(20) 254 | 255 | 256 | class SH1106_I2C(SH1106): 257 | def __init__(self, width, height, i2c, res=None, addr=0x3c, 258 | rotate=0, external_vcc=False, delay=0): 259 | self.i2c = i2c 260 | self.addr = addr 261 | self.res = res 262 | self.temp = bytearray(2) 263 | self.delay = delay 264 | if res is not None: 265 | res.init(res.OUT, value=1) 266 | super().__init__(width, height, external_vcc, rotate) 267 | 268 | def write_cmd(self, cmd): 269 | self.temp[0] = 0x80 # Co=1, D/C#=0 270 | self.temp[1] = cmd 271 | self.i2c.writeto(self.addr, self.temp) 272 | 273 | def write_data(self, buf): 274 | self.i2c.writeto(self.addr, b'\x40'+buf) 275 | 276 | def reset(self,res=None): 277 | super().reset(self.res) 278 | 279 | 280 | class SH1106_SPI(SH1106): 281 | def __init__(self, width, height, spi, dc, res=None, cs=None, 282 | rotate=0, external_vcc=False, delay=0): 283 | dc.init(dc.OUT, value=0) 284 | if res is not None: 285 | res.init(res.OUT, value=0) 286 | if cs is not None: 287 | cs.init(cs.OUT, value=1) 288 | self.spi = spi 289 | self.dc = dc 290 | self.res = res 291 | self.cs = cs 292 | self.delay = delay 293 | super().__init__(width, height, external_vcc, rotate) 294 | 295 | def write_cmd(self, cmd): 296 | if self.cs is not None: 297 | self.cs(1) 298 | self.dc(0) 299 | self.cs(0) 300 | self.spi.write(bytearray([cmd])) 301 | self.cs(1) 302 | else: 303 | self.dc(0) 304 | self.spi.write(bytearray([cmd])) 305 | 306 | def write_data(self, buf): 307 | if self.cs is not None: 308 | self.cs(1) 309 | self.dc(1) 310 | self.cs(0) 311 | self.spi.write(buf) 312 | self.cs(1) 313 | else: 314 | self.dc(1) 315 | self.spi.write(buf) 316 | 317 | def reset(self, res=None): 318 | super().reset(self.res) 319 | -------------------------------------------------------------------------------- /sh1106.pyi: -------------------------------------------------------------------------------- 1 | """ 2 | Type stub for the MicroPython SH1106 OLED driver. 3 | 4 | This library provides a driver for controlling SH1106 OLED displays over 5 | I2C or SPI interfaces. It supports basic drawing operations, text rendering, 6 | and display configuration. 7 | 8 | ### Usage Examples: 9 | 10 | #### SPI Interface: 11 | ```python 12 | from machine import Pin, SPI 13 | import sh1106 14 | 15 | spi = SPI(1, baudrate=1000000) 16 | display = sh1106.SH1106_SPI(128, 64, spi, dc=Pin(5), res=Pin(2), cs=Pin(4)) 17 | display.fill(0) # Clear the display 18 | display.text("Hello, World!", 0, 0, 1) # Display text 19 | display.show() 20 | ``` 21 | 22 | #### I2C Interface: 23 | ```python 24 | from machine import Pin, I2C 25 | import sh1106 26 | 27 | i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000) 28 | display = sh1106.SH1106_I2C(128, 64, i2c, res=Pin(16)) 29 | display.fill(0) # Clear the display 30 | display.text("Hello, World!", 0, 0, 1) # Display text 31 | display.show() 32 | ``` 33 | 34 | ### Features: 35 | - Supports 128x64 SH1106 OLED displays. 36 | - Drawing primitives (lines, rectangles, circles, etc.). 37 | - Text rendering. 38 | - Contrast adjustment and screen inversion. 39 | 40 | For more details, visit the [SH1106 GitHub repository](https://github.com/robert-hh/SH1106). 41 | """ 42 | 43 | # sh1106.pyi - Stub file for SH1106 MicroPython library 44 | from abc import abstractmethod 45 | from typing import Optional, overload 46 | 47 | from _typeshed import Incomplete 48 | from framebuf import FrameBuffer 49 | from machine import I2C, SPI, Pin 50 | 51 | class SH1106(FrameBuffer): 52 | """ 53 | Base class for SH1106 OLED display drivers. 54 | Handles common functionality such as rendering, power management, and drawing operations. 55 | """ 56 | def __init__(self, width: int, height: int, external_vcc: bool, rotate: int = 0) -> None: 57 | """ 58 | Initialize the SH1106 driver. 59 | 60 | :param width: Display width in pixels. 61 | :param height: Display height in pixels. 62 | :param external_vcc: Whether to use external VCC (True) or internal (False). 63 | :param rotate: Rotation angle (0, 90, 180, 270 degrees). 64 | """ 65 | ... 66 | 67 | @abstractmethod 68 | def write_cmd(self, *args, **kwargs) -> Incomplete: ... 69 | 70 | @abstractmethod 71 | def write_data(self, *args, **kwargs) -> Incomplete: ... 72 | 73 | def init_display(self) -> None: 74 | """Initialize and reset the display.""" 75 | ... 76 | 77 | def poweroff(self) -> None: 78 | """Turn off the display.""" 79 | ... 80 | 81 | def poweron(self) -> None: 82 | """Turn on the display.""" 83 | ... 84 | 85 | def flip(self, flag: Optional[bool] = None, update: bool = True) -> None: 86 | """ 87 | Flip the display horizontally or vertically. 88 | 89 | :param flag: If True, enable flipping; if False, disable. 90 | :param update: Whether to update the display immediately. 91 | """ 92 | ... 93 | 94 | def sleep(self, value: bool) -> None: 95 | """ 96 | Put the display into sleep mode or wake it up. 97 | 98 | :param value: True to sleep, False to wake up. 99 | """ 100 | ... 101 | 102 | def contrast(self, contrast: int) -> None: 103 | """ 104 | Set the display contrast level. 105 | 106 | :param contrast: Contrast value (0-255). 107 | """ 108 | ... 109 | 110 | def invert(self, invert: bool) -> None: 111 | """ 112 | Invert the display colors. 113 | 114 | :param invert: True to invert, False to reset to normal. 115 | """ 116 | ... 117 | 118 | def show(self, full_update: bool = False) -> None: 119 | """ 120 | Refresh the display with the current buffer content. 121 | 122 | :param full_update: If True, update all pages; otherwise, update only modified pages. 123 | """ 124 | ... 125 | @overload 126 | def pixel(self, x: int, y: int, /) -> int: 127 | """ 128 | Get or set the color of a specific pixel. 129 | 130 | :param x: X-coordinate. 131 | :param y: Y-coordinate. 132 | :param color: Pixel color (0 or 1). If None, return the current color. 133 | """ 134 | ... 135 | @overload 136 | def pixel(self, x: int, y: int, color: int) -> None: 137 | """ 138 | Get or set the color of a specific pixel. 139 | 140 | :param x: X-coordinate. 141 | :param y: Y-coordinate. 142 | :param color: Pixel color (0 or 1). If None, return the current color. 143 | """ 144 | ... 145 | 146 | def text(self, text: str, x: int, y: int, color: int = 1) -> None: 147 | """ 148 | Draw text on the display. 149 | 150 | :param text: String to draw. 151 | :param x: X-coordinate of the top-left corner. 152 | :param y: Y-coordinate of the top-left corner. 153 | :param color: Text color (1 for white, 0 for black). 154 | """ 155 | ... 156 | 157 | def line(self, x0: int, y0: int, x1: int, y1: int, color: int) -> None: 158 | """Draw a line between two points.""" 159 | ... 160 | 161 | def hline(self, x: int, y: int, w: int, color: int) -> None: 162 | """Draw a horizontal line.""" 163 | ... 164 | 165 | def vline(self, x: int, y: int, h: int, color: int) -> None: 166 | """Draw a vertical line.""" 167 | ... 168 | 169 | def fill(self, color: int) -> None: 170 | """Fill the entire display with a single color.""" 171 | ... 172 | 173 | def blit(self, fbuf: FrameBuffer, x: int, y: int, key: int = -1, palette: Optional[bytes] = None) -> None: 174 | """ 175 | Copy a framebuffer onto the display. 176 | 177 | :param fbuf: Source framebuffer. 178 | :param x: X-coordinate for placement. 179 | :param y: Y-coordinate for placement. 180 | :param key: Transparent color key. 181 | :param palette: Optional color palette for translation. 182 | """ 183 | ... 184 | 185 | def scroll(self, x: int, y: int) -> None: 186 | """Scroll the display content by a certain amount.""" 187 | ... 188 | 189 | def fill_rect(self, x: int, y: int, w: int, h: int, color: int) -> None: 190 | """Draw a filled rectangle.""" 191 | ... 192 | 193 | def rect(self, x: int, y: int, w: int, h: int, color: int) -> None: 194 | """Draw an outlined rectangle.""" 195 | ... 196 | 197 | def ellipse(self, x: int, y: int, xr: int, yr: int, color: int) -> None: 198 | """Draw an outlined ellipse.""" 199 | ... 200 | 201 | def reset(self, res: Optional[Pin]=None) -> None: 202 | """Reset the display using the reset pin.""" 203 | ... 204 | 205 | class SH1106_I2C(SH1106): 206 | """ 207 | SH1106 driver for I2C communication. 208 | """ 209 | def __init__(self, width: int, height: int, i2c: I2C, res: Optional[Pin] = None, 210 | addr: int = 0x3c, rotate: int = 0, external_vcc: bool = False, delay: int = 0) -> None: 211 | """ 212 | Initialize the SH1106 I2C driver. 213 | """ 214 | ... 215 | 216 | def write_cmd(self, cmd: int) -> None: 217 | """Write a command to the display via I2C.""" 218 | ... 219 | 220 | def write_data(self, buf: bytes) -> None: 221 | """Write data to the display via I2C.""" 222 | ... 223 | 224 | def reset(self, res: Optional[Pin]=None) -> None: 225 | """Reset the display via the reset pin (if available).""" 226 | ... 227 | 228 | class SH1106_SPI(SH1106): 229 | """ 230 | SH1106 driver for SPI communication. 231 | """ 232 | def __init__(self, width: int, height: int, spi: SPI, dc: Pin, res: Optional[Pin] = None, 233 | cs: Optional[Pin] = None, rotate: int = 0, external_vcc: bool = False, delay: int = 0) -> None: 234 | """ 235 | Initialize the SH1106 SPI driver. 236 | """ 237 | ... 238 | 239 | def write_cmd(self, cmd: int) -> None: 240 | """Write a command to the display via SPI.""" 241 | ... 242 | 243 | def write_data(self, buf: bytes) -> None: 244 | """Write data to the display via SPI.""" 245 | ... 246 | 247 | def reset(self, res: Optional[Pin]=None) -> None: 248 | """Reset the display via the reset pin (if available).""" 249 | ... 250 | --------------------------------------------------------------------------------