text: #
  # MicroPython SH1106 OLED driver, I2C and SPI interfaces
  #
  # The MIT License (MIT)
  #
  # Copyright (c) 2016 Radomir Dopieralski (@deshipu),
  #               2017 Robert Hammelrath (@robert-hh)
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy
  # of this software and associated documentation files (the "Software"), to deal
  # in the Software without restriction, including without limitation the rights
  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  # copies of the Software, and to permit persons to whom the Software is
  # furnished to do so, subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in
  # all copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  # THE SOFTWARE.
  #
  # Sample code sections
  # ------------ SPI ------------------
  # Pin Map SPI
  #   - 3v - xxxxxx   - Vcc
  #   - G  - xxxxxx   - Gnd
  #   - D7 - GPIO 13  - Din / MOSI fixed
  #   - D5 - GPIO 14  - Clk / Sck fixed
  #   - D8 - GPIO 4   - CS (optional, if the only connected device)
  #   - D2 - GPIO 5   - D/C
  #   - D1 - GPIO 2   - Res
  #
  # for CS, D/C and Res other ports may be chosen.
  #
  # from machine import Pin, SPI
  # import sh1106
  
  # spi = SPI(1, baudrate=1000000)
  # display = sh1106.SH1106_SPI(128, 64, spi, Pin(5), Pin(2), Pin(4))
  # display.sleep(False)
  # display.fill(0)
  # display.text('Testing 1', 0, 0, 1)
  # display.show()
  #
  # --------------- I2C ------------------
  #
  # Pin Map I2C
  #   - 3v - xxxxxx   - Vcc
  #   - G  - xxxxxx   - Gnd
  #   - D2 - GPIO 5   - SCK / SCL
  #   - D1 - GPIO 4   - DIN / SDA
  #   - D0 - GPIO 16  - Res
  #   - G  - xxxxxx     CS
  #   - G  - xxxxxx     D/C
  #
  # Pin's for I2C can be set almost arbitrary
  #
  # from machine import Pin, I2C
  # import sh1106
  #
  # i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
  # display = sh1106.SH1106_I2C(128, 64, i2c, Pin(16), 0x3c)
  # display.sleep(False)
  # display.fill(0)
  # display.text('Testing 1', 0, 0, 1)
  # display.show()
  
  from micropython import const
  import utime as time
  import framebuf
  
  
  # a few register definitions
  _SET_CONTRAST        = const(0x81)
  _SET_NORM_INV        = const(0xa6)
  _SET_DISP            = const(0xae)
  _SET_SCAN_DIR        = const(0xc0)
  _SET_SEG_REMAP       = const(0xa0)
  _LOW_COLUMN_ADDRESS  = const(0x00)
  _HIGH_COLUMN_ADDRESS = const(0x10)
  _SET_PAGE_ADDRESS    = const(0xB0)
  
  
  class SH1106:
      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)
          fb = framebuf.FrameBuffer(self.buffer, self.width, self.height,
                                    framebuf.MVLSB)
          self.framebuf = fb
  # set shortcuts for the methods of framebuf
          self.fill = fb.fill
          self.fill_rect = fb.fill_rect
          self.hline = fb.hline
          self.vline = fb.vline
          self.line = fb.line
          self.rect = fb.rect
          self.pixel = fb.pixel
          self.scroll = fb.scroll
          self.text = fb.text
          self.blit = fb.blit
  
          self.init_display()
  
      def init_display(self):
          self.reset()
          self.fill(0)
          self.poweron()
          self.show()
  
      def poweroff(self):
          self.write_cmd(_SET_DISP | 0x00)
  
      def poweron(self):
          self.write_cmd(_SET_DISP | 0x01)
  
      def rotate(self, flag, update=True):
          if flag:
              self.write_cmd(_SET_SEG_REMAP | 0x01)  # mirror display vertically
              self.write_cmd(_SET_SCAN_DIR | 0x08)  # mirror display hor.
          else:
              self.write_cmd(_SET_SEG_REMAP | 0x00)
              self.write_cmd(_SET_SCAN_DIR | 0x00)
          if update:
              self.show()
  
      def sleep(self, value):
          self.write_cmd(_SET_DISP | (not value))
  
      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 show(self):
          for page in range(self.height // 8):
              self.write_cmd(_SET_PAGE_ADDRESS | page)
              self.write_cmd(_LOW_COLUMN_ADDRESS | 2)
              self.write_cmd(_HIGH_COLUMN_ADDRESS | 0)
              self.write_data(self.buffer[
                  self.width * page:self.width * page + self.width
              ])
  
      def reset(self, res):
          if res is not None:
              res(1)
              time.sleep_ms(1)
              res(0)
              time.sleep_ms(20)
              res(1)
              time.sleep_ms(20)
  
  
  class SH1106_I2C(SH1106):
      def __init__(self, width, height, i2c, res=None, addr=0x3c,
                   external_vcc=False):
          self.i2c = i2c
          self.addr = addr
          self.res = res
          self.temp = bytearray(2)
          if hasattr(self.i2c, "start"):
              self.write_data = self.sw_write_data
          else:
              self.write_data = self.hw_write_data
          if res is not None:
              res.init(res.OUT, value=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 hw_write_data(self, buf):
          self.i2c.writeto(self.addr, b'\x40'+buf)
  
      def sw_write_data(self, buf):
          self.temp[0] = self.addr << 1
          self.temp[1] = 0x40  # Co=0, D/C#=1
          self.i2c.start()
          self.i2c.write(self.temp)
          self.i2c.write(buf)
          self.i2c.stop()
  
      def reset(self):
          super().reset(self.res)
  
  
  class SH1106_SPI(SH1106):
      def __init__(self, width, height, spi, dc, res=None, cs=None,
                   external_vcc=False):
          self.rate = 10 * 1000 * 1000
          dc.init(dc.OUT, value=0)
          if res is not None:
              res.init(res.OUT, value=0)
          if cs is not None:
              cs.init(cs.OUT, value=1)
          self.spi = spi
          self.dc = dc
          self.res = res
          self.cs = cs
          super().__init__(width, height, external_vcc)
  
      def write_cmd(self, cmd):
          self.spi.init(baudrate=self.rate, polarity=0, phase=0)
          if self.cs is not None:
              self.cs(1)
              self.dc(0)
              self.cs(0)
              self.spi.write(bytearray([cmd]))
              self.cs(1)
          else:
              self.dc(0)
              self.spi.write(bytearray([cmd]))
  
      def write_data(self, buf):
          self.spi.init(baudrate=self.rate, polarity=0, phase=0)
          if self.cs is not None:
              self.cs(1)
              self.dc(1)
              self.cs(0)
              self.spi.write(buf)
              self.cs(1)
          else:
              self.dc(1)
              self.spi.write(buf)
  
      def reset(self):
          super().reset(self.res)
  