Welcome to the Pycom forum Forum
    • \[\[global:header.categories\]\]
    • \[\[global:header.recent\]\]
    • \[\[global:header.tags\]\]
    • \[\[global:header.popular\]\]
    • Login

    Explore Pybytes | Official Documentation | Report a Firmware Bug/Issue | GitHub

    I2C LCD: code inside

    Scheduled Pinned Locked Moved LoPy
    41 Posts 8 Posters 27.8k Views 8 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • B Offline
      brotherdust @DVE
      last edited by

      @DVE So, I tried your code on my 128x64. It just shows up as a bunch of random noise on the display. =( I dug through the MP codebase and found this: https://github.com/pycom/pycom-micropython/blob/master/drivers/display/ssd1306.py

      Lo and behold! It's for the same display driver as what we have! Wait.. no framebuf module.

      Twiddled with the make configurations and managed to make a build with framebuf included. Trying it now. It's late so I might not report back until tomorrow.

      R D 2 Replies Last reply Reply Quote 0
      • R Offline
        rskoniec @brotherdust
        last edited by

        @brotherdust Waiting for the news. :o)

        B 1 Reply Last reply Reply Quote 0
        • D Offline
          DVE @brotherdust
          last edited by

          @brotherdust, thanks for the link. I can test this driver also.

          1 Reply Last reply Reply Quote 0
          • B Offline
            brotherdust @rskoniec
            last edited by

            @rskoniec said in I2C LCD: code inside:

            @brotherdust Waiting for the news. :o)

            Sorry! I was hacking on NyQuil last night. I got the framebuf code to compile and loaded it up on to one of my LoPy's. Tried the driver code, but all I got was the same result: a screen full of static. Same result as when I used @DVE 's code. Maybe my module is defective? Not sure. Anyway, tried a few things, couldn't get it to do anything different, then rage-quit because I wasn't clear-headed enough to figure out what the LoPy was telling me on the console. heh. Either of you have any luck?

            B 1 Reply Last reply Reply Quote 0
            • B Offline
              brotherdust @brotherdust
              last edited by brotherdust

              Here's what I'm putting in:

              # MicroPython SSD1306 OLED driver, I2C and SPI interfaces
              
              from micropython import const
              import time
              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_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)
              
              
              class SSD1306:
                  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)
                      self.framebuf = framebuf.FrameBuffer1(self.buffer, self.width, self.height)
                      self.poweron()
                      self.init_display()
              
                  def init_display(self):
                      for cmd in (
                          SET_DISP | 0x00, # off
                          # address setting
                          SET_MEM_ADDR, 0x00, # horizontal
                          # resolution and layout
                          SET_DISP_START_LINE | 0x00,
                          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.height == 32 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
                          # charge pump
                          SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
                          SET_DISP | 0x01): # on
                          self.write_cmd(cmd)
                      self.fill(0)
                      self.show()
              
                  def poweroff(self):
                      self.write_cmd(SET_DISP | 0x00)
              
                  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):
                      x0 = 0
                      x1 = self.width - 1
                      if self.width == 64:
                          # displays with width of 64 pixels are shifted by 32
                          x0 += 32
                          x1 += 32
                      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)
              
                  def fill(self, col):
                      self.framebuf.fill(col)
              
                  def pixel(self, x, y, col):
                      self.framebuf.pixel(x, y, col)
              
                  def scroll(self, dx, dy):
                      self.framebuf.scroll(dx, dy)
              
                  def text(self, string, x, y, col=1):
                      self.framebuf.text(string, x, y, col)
              
              
              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)
                      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.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 poweron(self):
                      pass
              
              from machine import I2C
              i2c = I2C(0, I2C.MASTER, baudrate=1000000)
              display = SSD1306_I2C(width=128, height=64, i2c=i2c, addr=0x3d)
              

              The 128x64 display has hardware address of 0x3d. Anyway, here's what comes out:

              Traceback (most recent call last):
                File "<stdin>", line 1, in <module>
                File "<stdin>", line 109, in __init__
                File "<stdin>", line 37, in __init__
                File "<stdin>", line 64, in init_display
                File "<stdin>", line 89, in show
                File "<stdin>", line 119, in write_data
              AttributeError: 'I2C' object has no attribute 'start'
              

              The display turns on, but it's full of what looks like random noise. I looked through the source code for the I2C module and the start method is there:
              https://github.com/pycom/pycom-micropython/blob/master/esp32/mods/machine_i2c.c#L348

              STATIC mp_obj_t machine_i2c_start(mp_obj_t self_in) {
                  machine_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in);
                  mp_hal_i2c_start(self);
                  return mp_const_none;
              }
              MP_DEFINE_CONST_FUN_OBJ_1(machine_i2c_start_obj, machine_i2c_start);
              
              >>> help(machine.I2C)
              <class 'I2C'>object  is of type type
                init -- <function>
                scan -- <function>
                readfrom -- <function>
                readfrom_into -- <function>
                writeto -- <function>
                readfrom_mem -- <function>
                readfrom_mem_into -- <function>
                writeto_mem -- <function>
                MASTER -- 0
              

              I don't get why it's not present in the list of available methods for this class. =/

              1 Reply Last reply Reply Quote 0
              • B Offline
                brotherdust
                last edited by

                @DVE Update on using the built-in framebuffer and SSD1306 driver:
                The driver was using I2C "primitives" that were disabled in the esp32 version of machine_i2c. The recent commits I've made on my relevant github repo:
                https://github.com/brotherdust/pycom-micropython/commits/i2c-framebuf

                I've made progress; but still learning how to use this stuff!

                1 Reply Last reply Reply Quote 0
                • B Offline
                  brotherdust
                  last edited by

                  It works! Yay! Code later. Sleep now.

                  D R 2 Replies Last reply Reply Quote 0
                  • D Offline
                    DVE @brotherdust
                    last edited by DVE

                    @brotherdust Its great :)

                    I ordered 128x64 display too on Ebay, will be able to test next week.

                    @brotherdust said in I2C LCD: code inside:

                    The driver was using I2C "primitives" that were disabled in the esp32 version of machine_i2c

                    How it works? Do you need to reload a firmware with enabled primitives?

                    1 Reply Last reply Reply Quote 0
                    • R Offline
                      rskoniec @brotherdust
                      last edited by

                      @brotherdust Any chance for a code and answers for @DVE questions?

                      B 1 Reply Last reply Reply Quote 0
                      • B Offline
                        brotherdust @rskoniec
                        last edited by

                        @rskoniec said in I2C LCD: code inside:

                        @brotherdust Any chance for a code and answers for @DVE questions?

                        @rskoniec, my apologies. I thought that I had answered this and didn't give it any more thought.

                        The commit I referenced enables the I2C primitives. You'll need to recompile the firmware and upload it. Then the code will work.

                        After reviewing the code a bit more, I think it can be refactored not to use the primitives (I2C.start, stop, etc). The primitives are already a part of other, more high-level routines. But the easiest way to get the driver going was just to enable those functions. =) I'm too lazy to re-factor and go through all the pull requests and justifications to get it included upstream.

                        R R 2 Replies Last reply Reply Quote 0
                        • R Offline
                          rskoniec @brotherdust
                          last edited by

                          @brotherdust Thank you very much for quick reply. :o)

                          1 Reply Last reply Reply Quote 0
                          • D Offline
                            DVE
                            last edited by

                            Got the 128x64 display this week. Stupid chinese seller sent SPI version instead of I2C.
                            But I was able to add SPI support as well :)
                            alt text

                            Will update code soon.

                            R 1 Reply Last reply Reply Quote 2
                            • R Offline
                              rskoniec @DVE
                              last edited by rskoniec

                              This post is deleted!
                              1 Reply Last reply Reply Quote 0
                              • J Offline
                                Jiemde Pybytes Beta
                                last edited by

                                Very interested by your code version, thanks in advance.

                                1 Reply Last reply Reply Quote 0
                                • R Offline
                                  rskoniec
                                  last edited by rskoniec

                                  @DVE Me too. :o) Btw, which version of SPI do you use (3 or 4-wire)?

                                  1 Reply Last reply Reply Quote 0
                                  • D Offline
                                    DVE
                                    last edited by DVE

                                    Hi all.
                                    Now its a very slow software implementation. Its just a code and connection diagram, converted to Python from here:

                                    http://www.instructables.com/id/How-to-use-OLED-display-arduino-module/step2/Copy-the-code-in-Arduino-IDE-and-change-the-chars-/

                                    I'll share code soon, want to improve it a bit.

                                    1 Reply Last reply Reply Quote 0
                                    • R Offline
                                      rdixey @brotherdust
                                      last edited by

                                      @brotherdust Hey brother... I'm trying to use an Adafruit 128X64 OLED with both I2C & SPI, on the SPI bus of my LoPy. Working with the SSD1306 OLED driver, I quickly encountered the same problem as you, no framebuf module available on the LoPY.
                                      I see in this thread that you were able to compile the framebuf module for the LoPy. Are you able to share that with me as a .py ? Or else give me some direction on how you accomplished that?
                                      Thanks in advance.

                                      B 1 Reply Last reply Reply Quote 0
                                      • B Offline
                                        brotherdust @rdixey
                                        last edited by

                                        @rdixey said in I2C LCD: code inside:

                                        @brotherdust Hey brother... I'm trying to use an Adafruit 128X64 OLED with both I2C & SPI, on the SPI bus of my LoPy. Working with the SSD1306 OLED driver, I quickly encountered the same problem as you, no framebuf module available on the LoPY.
                                        I see in this thread that you were able to compile the framebuf module for the LoPy. Are you able to share that with me as a .py ? Or else give me some direction on how you accomplished that?
                                        Thanks in advance.

                                        @rdixey :
                                        The SSD1306 driver that comes with MicroPython references methods in the I2C class that aren't available by default in PyCom's ESP32 port. I applied the changes indicated in the commits from my git branch repo (links below) and recompiled the firmware. After that, I was able to use the driver just fine. I then combined the driver code with @DVE's font code and some other functions and it worked pretty well. I'd post the final script here, but I don't have access to it right now. Give my suggestions a shot first and see what you can come up with. =)

                                        Enable framebuf
                                        Enable I2C primitives

                                        R 1 Reply Last reply Reply Quote 0
                                        • R Offline
                                          rdixey @brotherdust
                                          last edited by

                                          @brotherdust I appreciate the straightforward answer. I'm thinking that by using SPI, I will avoid the I2C primitives issues that you encountered. And hopefully not run into other SPI related issues.
                                          Since I've not ever recompiled the firmware before, this will be an adventure that should keep me occupied and out of trouble for a while.

                                          B 1 Reply Last reply Reply Quote 0
                                          • B Offline
                                            brotherdust @rdixey
                                            last edited by

                                            @rdixey :
                                            Here's the code I've been using. It's meant as more of a stress test on the I2C bus and the LoPy itself (watchdog issues over time). It does the following:

                                            Set up I2C bus at 400,000 bps
                                            Initialize BME280 sensor on default I2C address
                                            Initialize SSD1306 display on secondary I2C address 0x3D

                                            While True:

                                            • Read sensor data
                                            • Blank the display buffer
                                            • Write some text to the buffer
                                            • Write the buffer to the display
                                            • Sleep for 1ms
                                            # MicroPython SSD1306 OLED driver, I2C and SPI interfaces
                                            
                                            from micropython import const
                                            import time
                                            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_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)
                                            
                                            
                                            class SSD1306:
                                                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)
                                                    self.framebuf = framebuf.FrameBuffer1(self.buffer, self.width, self.height)
                                                    self.poweron()
                                                    self.init_display()
                                            
                                                def init_display(self):
                                                    for cmd in (
                                                        SET_DISP | 0x00, # off
                                                        # address setting
                                                        SET_MEM_ADDR, 0x00, # horizontal
                                                        # resolution and layout
                                                        SET_DISP_START_LINE | 0x00,
                                                        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.height == 32 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
                                                        # charge pump
                                                        SET_CHARGE_PUMP, 0x10 if self.external_vcc else 0x14,
                                                        SET_DISP | 0x01): # on
                                                        self.write_cmd(cmd)
                                                    self.fill(0)
                                                    self.show()
                                            
                                                def poweroff(self):
                                                    self.write_cmd(SET_DISP | 0x00)
                                            
                                                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):
                                                    x0 = 0
                                                    x1 = self.width - 1
                                                    if self.width == 64:
                                                        # displays with width of 64 pixels are shifted by 32
                                                        x0 += 32
                                                        x1 += 32
                                                    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)
                                            
                                                def fill(self, col):
                                                    self.framebuf.fill(col)
                                            
                                                def pixel(self, x, y, col):
                                                    self.framebuf.pixel(x, y, col)
                                            
                                                def scroll(self, dx, dy):
                                                    self.framebuf.scroll(dx, dy)
                                            
                                                def text(self, string, x, y, col=1):
                                                    self.framebuf.text(string, x, y, col)
                                            
                                            
                                            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)
                                                    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.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 poweron(self):
                                                    pass
                                            
                                            
                                            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
                                                    super().__init__(width, height, external_vcc)
                                            
                                                def write_cmd(self, cmd):
                                                    self.spi.init(baudrate=self.rate, polarity=0, phase=0)
                                                    self.cs.high()
                                                    self.dc.low()
                                                    self.cs.low()
                                                    self.spi.write(bytearray([cmd]))
                                                    self.cs.high()
                                            
                                                def write_data(self, buf):
                                                    self.spi.init(baudrate=self.rate, polarity=0, phase=0)
                                                    self.cs.high()
                                                    self.dc.high()
                                                    self.cs.low()
                                                    self.spi.write(buf)
                                                    self.cs.high()
                                            
                                                def poweron(self):
                                                    self.res.high()
                                                    time.sleep_ms(1)
                                                    self.res.low()
                                                    time.sleep_ms(10)
                                                    self.res.high()
                                            
                                            # Author: Paul Cunnane 2016
                                            #
                                            # This module borrows heavily from the Adafruit BME280 Python library
                                            # and the Adafruit GPIO/I2C library. Original copyright notices are reproduced
                                            # below.
                                            #
                                            # Those libraries were written for the Raspberry Pi. This modification is
                                            # intended for the MicroPython and WiPy boards.
                                            #
                                            #
                                            # Copyright (c) 2014 Adafruit Industries
                                            # Author: Tony DiCola
                                            #
                                            # Based on the BMP280 driver with BME280 changes provided by
                                            # David J Taylor, Edinburgh (www.satsignal.eu)
                                            #
                                            # Based on Adafruit_I2C.py created by Kevin Townsend.
                                            #
                                            # 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.
                                            
                                            import time
                                            
                                            
                                            # BME280 default address.
                                            BME280_I2CADDR = 0x77
                                            
                                            # Operating Modes
                                            BME280_OSAMPLE_1 = 1
                                            BME280_OSAMPLE_2 = 2
                                            BME280_OSAMPLE_4 = 3
                                            BME280_OSAMPLE_8 = 4
                                            BME280_OSAMPLE_16 = 5
                                            
                                            # BME280 Registers
                                            
                                            BME280_REGISTER_DIG_T1 = 0x88  # Trimming parameter registers
                                            BME280_REGISTER_DIG_T2 = 0x8A
                                            BME280_REGISTER_DIG_T3 = 0x8C
                                            
                                            BME280_REGISTER_DIG_P1 = 0x8E
                                            BME280_REGISTER_DIG_P2 = 0x90
                                            BME280_REGISTER_DIG_P3 = 0x92
                                            BME280_REGISTER_DIG_P4 = 0x94
                                            BME280_REGISTER_DIG_P5 = 0x96
                                            BME280_REGISTER_DIG_P6 = 0x98
                                            BME280_REGISTER_DIG_P7 = 0x9A
                                            BME280_REGISTER_DIG_P8 = 0x9C
                                            BME280_REGISTER_DIG_P9 = 0x9E
                                            
                                            BME280_REGISTER_DIG_H1 = 0xA1
                                            BME280_REGISTER_DIG_H2 = 0xE1
                                            BME280_REGISTER_DIG_H3 = 0xE3
                                            BME280_REGISTER_DIG_H4 = 0xE4
                                            BME280_REGISTER_DIG_H5 = 0xE5
                                            BME280_REGISTER_DIG_H6 = 0xE6
                                            BME280_REGISTER_DIG_H7 = 0xE7
                                            
                                            BME280_REGISTER_CHIPID = 0xD0
                                            BME280_REGISTER_VERSION = 0xD1
                                            BME280_REGISTER_SOFTRESET = 0xE0
                                            
                                            BME280_REGISTER_CONTROL_HUM = 0xF2
                                            BME280_REGISTER_CONTROL = 0xF4
                                            BME280_REGISTER_CONFIG = 0xF5
                                            BME280_REGISTER_PRESSURE_DATA = 0xF7
                                            BME280_REGISTER_TEMP_DATA = 0xFA
                                            BME280_REGISTER_HUMIDITY_DATA = 0xFD
                                            
                                            
                                            class Device:
                                                """Class for communicating with an I2C device.
                                            
                                                Allows reading and writing 8-bit, 16-bit, and byte array values to
                                                registers on the device."""
                                            
                                                def __init__(self, address, i2c):
                                                    """Create an instance of the I2C device at the specified address using
                                                    the specified I2C interface object."""
                                                    self._address = address
                                                    self._i2c = i2c
                                            
                                                def writeRaw8(self, value):
                                                    """Write an 8-bit value on the bus (without register)."""
                                                    value = value & 0xFF
                                                    self._i2c.writeto(self._address, value.to_bytes(1))
                                            
                                                def write8(self, register, value):
                                                    """Write an 8-bit value to the specified register."""
                                                    value = value & 0xFF
                                                    self._i2c.writeto_mem(self._address, register, value.to_bytes(1))
                                            
                                                def write16(self, register, value):
                                                    """Write a 16-bit value to the specified register."""
                                                    value = value & 0xFFFF
                                                    self.i2c.writeto_mem(self._address, register, value)
                                            
                                                def readRaw8(self):
                                                    """Read an 8-bit value on the bus (without register)."""
                                                    return int.from_bytes(self._i2c.readfrom(self._address, 1)) & 0xFF
                                            
                                                def readU8(self, register):
                                                    """Read an unsigned byte from the specified register."""
                                                    return int.from_bytes(
                                                        self._i2c.readfrom_mem(self._address, register, 1)) & 0xFF
                                            
                                                def readS8(self, register):
                                                    """Read a signed byte from the specified register."""
                                                    result = self.readU8(register)
                                                    if result > 127:
                                                        result -= 256
                                                    return result
                                            
                                                def readU16(self, register, little_endian=True):
                                                    """Read an unsigned 16-bit value from the specified register, with the
                                                    specified endianness (default little endian, or least significant byte
                                                    first)."""
                                                    result = int.from_bytes(
                                                        self._i2c.readfrom_mem(self._address, register, 2)) & 0xFFFF
                                                    if not little_endian:
                                                        result = ((result << 8) & 0xFF00) + (result >> 8)
                                                    return result
                                            
                                                def readS16(self, register, little_endian=True):
                                                    """Read a signed 16-bit value from the specified register, with the
                                                    specified endianness (default little endian, or least significant byte
                                                    first)."""
                                                    result = self.readU16(register, little_endian)
                                                    if result > 32767:
                                                        result -= 65536
                                                    return result
                                            
                                                def readU16LE(self, register):
                                                    """Read an unsigned 16-bit value from the specified register, in little
                                                    endian byte order."""
                                                    return self.readU16(register, little_endian=True)
                                            
                                                def readU16BE(self, register):
                                                    """Read an unsigned 16-bit value from the specified register, in big
                                                    endian byte order."""
                                                    return self.readU16(register, little_endian=False)
                                            
                                                def readS16LE(self, register):
                                                    """Read a signed 16-bit value from the specified register, in little
                                                    endian byte order."""
                                                    return self.readS16(register, little_endian=True)
                                            
                                                def readS16BE(self, register):
                                                    """Read a signed 16-bit value from the specified register, in big
                                                    endian byte order."""
                                                    return self.readS16(register, little_endian=False)
                                            
                                            
                                            class BME280:
                                                def __init__(self, mode=BME280_OSAMPLE_1, address=BME280_I2CADDR, i2c=None,
                                                             **kwargs):
                                                    # Check that mode is valid.
                                                    if mode not in [BME280_OSAMPLE_1, BME280_OSAMPLE_2, BME280_OSAMPLE_4,
                                                                    BME280_OSAMPLE_8, BME280_OSAMPLE_16]:
                                                        raise ValueError(
                                                            'Unexpected mode value {0}. Set mode to one of '
                                                            'BME280_ULTRALOWPOWER, BME280_STANDARD, BME280_HIGHRES, or '
                                                            'BME280_ULTRAHIGHRES'.format(mode))
                                                    self._mode = mode
                                                    # Create I2C device.
                                                    if i2c is None:
                                                        raise ValueError('An I2C object is required.')
                                                    self._device = Device(address, i2c)
                                                    # Load calibration values.
                                                    self._load_calibration()
                                                    self._device.write8(BME280_REGISTER_CONTROL, 0x3F)
                                                    self.t_fine = 0
                                            
                                                def _load_calibration(self):
                                            
                                                    self.dig_T1 = self._device.readU16LE(BME280_REGISTER_DIG_T1)
                                                    self.dig_T2 = self._device.readS16LE(BME280_REGISTER_DIG_T2)
                                                    self.dig_T3 = self._device.readS16LE(BME280_REGISTER_DIG_T3)
                                            
                                                    self.dig_P1 = self._device.readU16LE(BME280_REGISTER_DIG_P1)
                                                    self.dig_P2 = self._device.readS16LE(BME280_REGISTER_DIG_P2)
                                                    self.dig_P3 = self._device.readS16LE(BME280_REGISTER_DIG_P3)
                                                    self.dig_P4 = self._device.readS16LE(BME280_REGISTER_DIG_P4)
                                                    self.dig_P5 = self._device.readS16LE(BME280_REGISTER_DIG_P5)
                                                    self.dig_P6 = self._device.readS16LE(BME280_REGISTER_DIG_P6)
                                                    self.dig_P7 = self._device.readS16LE(BME280_REGISTER_DIG_P7)
                                                    self.dig_P8 = self._device.readS16LE(BME280_REGISTER_DIG_P8)
                                                    self.dig_P9 = self._device.readS16LE(BME280_REGISTER_DIG_P9)
                                            
                                                    self.dig_H1 = self._device.readU8(BME280_REGISTER_DIG_H1)
                                                    self.dig_H2 = self._device.readS16LE(BME280_REGISTER_DIG_H2)
                                                    self.dig_H3 = self._device.readU8(BME280_REGISTER_DIG_H3)
                                                    self.dig_H6 = self._device.readS8(BME280_REGISTER_DIG_H7)
                                            
                                                    h4 = self._device.readS8(BME280_REGISTER_DIG_H4)
                                                    h4 = (h4 << 24) >> 20
                                                    self.dig_H4 = h4 | (self._device.readU8(BME280_REGISTER_DIG_H5) & 0x0F)
                                            
                                                    h5 = self._device.readS8(BME280_REGISTER_DIG_H6)
                                                    h5 = (h5 << 24) >> 20
                                                    self.dig_H5 = h5 | (
                                                        self._device.readU8(BME280_REGISTER_DIG_H5) >> 4 & 0x0F)
                                            
                                                def read_raw_temp(self):
                                                    """Reads the raw (uncompensated) temperature from the sensor."""
                                                    meas = self._mode
                                                    self._device.write8(BME280_REGISTER_CONTROL_HUM, meas)
                                                    meas = self._mode << 5 | self._mode << 2 | 1
                                                    self._device.write8(BME280_REGISTER_CONTROL, meas)
                                                    sleep_time = 1250 + 2300 * (1 << self._mode)
                                                    sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
                                                    sleep_time = sleep_time + 2300 * (1 << self._mode) + 575
                                                    time.sleep_us(sleep_time)  # Wait the required time
                                                    msb = self._device.readU8(BME280_REGISTER_TEMP_DATA)
                                                    lsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 1)
                                                    xlsb = self._device.readU8(BME280_REGISTER_TEMP_DATA + 2)
                                                    raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
                                                    return raw
                                            
                                                def read_raw_pressure(self):
                                                    """Reads the raw (uncompensated) pressure level from the sensor."""
                                                    """Assumes that the temperature has already been read """
                                                    """i.e. that enough delay has been provided"""
                                                    msb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA)
                                                    lsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 1)
                                                    xlsb = self._device.readU8(BME280_REGISTER_PRESSURE_DATA + 2)
                                                    raw = ((msb << 16) | (lsb << 8) | xlsb) >> 4
                                                    return raw
                                            
                                                def read_raw_humidity(self):
                                                    """Assumes that the temperature has already been read """
                                                    """i.e. that enough delay has been provided"""
                                                    msb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA)
                                                    lsb = self._device.readU8(BME280_REGISTER_HUMIDITY_DATA + 1)
                                                    raw = (msb << 8) | lsb
                                                    return raw
                                            
                                                def read_temperature(self):
                                                    """Get the compensated temperature in 0.01 of a degree celsius."""
                                                    adc = self.read_raw_temp()
                                                    var1 = ((adc >> 3) - (self.dig_T1 << 1)) * (self.dig_T2 >> 11)
                                                    var2 = ((
                                                        (((adc >> 4) - self.dig_T1) * ((adc >> 4) - self.dig_T1)) >> 12) *
                                                        self.dig_T3) >> 14
                                                    self.t_fine = var1 + var2
                                                    return (self.t_fine * 5 + 128) >> 8
                                            
                                                def read_pressure(self):
                                                    """Gets the compensated pressure in Pascals."""
                                                    adc = self.read_raw_pressure()
                                                    var1 = self.t_fine - 128000
                                                    var2 = var1 * var1 * self.dig_P6
                                                    var2 = var2 + ((var1 * self.dig_P5) << 17)
                                                    var2 = var2 + (self.dig_P4 << 35)
                                                    var1 = (((var1 * var1 * self.dig_P3) >> 8) +
                                                            ((var1 * self.dig_P2) >> 12))
                                                    var1 = (((1 << 47) + var1) * self.dig_P1) >> 33
                                                    if var1 == 0:
                                                        return 0
                                                    p = 1048576 - adc
                                                    p = (((p << 31) - var2) * 3125) // var1
                                                    var1 = (self.dig_P9 * (p >> 13) * (p >> 13)) >> 25
                                                    var2 = (self.dig_P8 * p) >> 19
                                                    return ((p + var1 + var2) >> 8) + (self.dig_P7 << 4)
                                            
                                                def read_humidity(self):
                                                    adc = self.read_raw_humidity()
                                                    # print 'Raw humidity = {0:d}'.format (adc)
                                                    h = self.t_fine - 76800
                                                    h = (((((adc << 14) - (self.dig_H4 << 20) - (self.dig_H5 * h)) +
                                                         16384) >> 15) * (((((((h * self.dig_H6) >> 10) * (((h *
                                                                          self.dig_H3) >> 11) + 32768)) >> 10) + 2097152) *
                                                                          self.dig_H2 + 8192) >> 14))
                                                    h = h - (((((h >> 15) * (h >> 15)) >> 7) * self.dig_H1) >> 4)
                                                    h = 0 if h < 0 else h
                                                    h = 419430400 if h > 419430400 else h
                                                    return h >> 12
                                            
                                                @property
                                                def temperature(self):
                                                    "Return the temperature in degrees."
                                                    t = self.read_temperature()
                                                    ti = t // 100
                                                    td = t - ti * 100
                                                    return "{}.{:02d}C".format(ti, td)
                                            
                                                @property
                                                def pressure(self):
                                                    "Return the temperature in hPa."
                                                    p = self.read_pressure() // 256
                                                    pi = p // 100
                                                    pd = p - pi * 100
                                                    return "{}.{:02d}hPa".format(pi, pd)
                                            
                                                @property
                                                def humidity(self):
                                                    "Return the humidity in percent."
                                                    h = self.read_humidity()
                                                    hi = h // 1024
                                                    hd = h * 100 // 1024 - hi * 100
                                                    return "{}.{:02d}%".format(hi, hd)
                                            
                                            from machine import I2C
                                            i2c = I2C(0, I2C.MASTER, baudrate=400000)
                                            
                                            sensor = BME280(address=119, i2c=i2c)
                                            display = SSD1306_I2C(width=128, height=64, i2c=i2c, addr=0x3d)
                                            
                                            
                                            while True:
                                            	temperature = "T: {}".format(sensor.temperature)
                                            	humidity = "H: {}".format(sensor.humidity)
                                            	pressure = "P: {}".format(sensor.pressure)
                                            	display.fill(0)
                                            	display.text(temperature, 0, 0)
                                            	display.text(humidity, 0, 28)
                                            	display.text(pressure, 0, 56)
                                            	display.show()
                                            	time.sleep_ms(1)
                                            

                                            I left it running for a couple of days and it's proven pretty stable. One thing to note is that the SSD1306 (at least mine) requires additional initialization before it will start working. It took me a while to puzzle it out! What happens on startup is the display will show the contents of the memory buffer. If it's unpowered for more than a few seconds, it just looks like static. Setting the RESET pin to LOW clears this and makes the display ready.

                                            I'm curious to know why you selected SPI over I2C. I haven't had a chance to play with SPI yet because all my stuff supports I2C and it's sufficient for my purposes. What are your thoughts @rdixey ?

                                            R 1 Reply Last reply Reply Quote 0

                                            Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                                            Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                                            With your input, this post could be even better 💗

                                            Register Login
                                            • 1
                                            • 2
                                            • 3
                                            • 1 / 3
                                            • First post
                                              Last post

                                            Pycom on Twitter

                                            Pycom