printing to OLED display

OpenMV related project discussion.
byte
Posts: 10
Joined: Sun Apr 14, 2019 11:20 pm

printing to OLED display

Postby byte » Mon Apr 15, 2019 12:15 am

I want to print text to an OLED display connected to the OpenMV M7.

I'm using Adafruit's 128 x 32 SPI SSD1306 OLED: https://www.adafruit.com/product/661.

There's an Adafruit MicroPython SSD1306 OLED driver I'm importing into OpenMV IDE. This is the SPI section of the adafruit_ssd1306.py driver file:

Code: Select all

class SSD1306_SPI(_SSD1306):
    """
    SPI class for SSD1306
    :param width: the width of the physical screen in pixels,
    :param height: the height of the physical screen in pixels,
    :param spi: the SPI peripheral to use,
    :param dc: the data/command pin to use (often labeled "D/C"),
    :param reset: the reset pin to use,
    :param cs: the chip-select pin to use (sometimes labeled "SS").
    """
    # pylint: disable=no-member
    # Disable should be reconsidered when refactor can be tested.
    def __init__(self, width, height, spi, dc, reset, cs, *,
                 external_vcc=False, baudrate=8000000, polarity=0, phase=0):
        self.rate = 10 * 1024 * 1024
        dc.switch_to_output(value=0)
        self.spi_device = spi_device.SPIDevice(spi, cs, baudrate=baudrate,
                                               polarity=polarity, phase=phase)
        self.dc_pin = dc
        self.buffer = bytearray((height // 8) * width)
        super().__init__(memoryview(self.buffer), width, height,
                         external_vcc=external_vcc, reset=reset)

    def write_cmd(self, cmd):
        """Send a command to the SPI device"""
        self.dc_pin.value = 0
        with self.spi_device as spi:
            spi.write(bytearray([cmd]))

    def write_framebuf(self):
        """write to the frame buffer via SPI"""
        self.dc_pin.value = 1
        with self.spi_device as spi:
            spi.write(self.buffer)

This is the section of my main script where I import adafruit_ssd1306:

Code: Select all

from pyb import Pin
dc = Pin('P8', Pin.OUT_PP)
reset = Pin('P7', Pin.OUT_PP)
cs = Pin('P6', Pin.OUT_PP)

import adafruit_ssd1306
oled = adafruit_ssd1306.SSD1306_SPI(128, 32, spi, dc, reset, cs)
oled.fill(0)
oled.text("Hello World", 0, 0)
oled.show()
When I run the script, I get the error "AttributeError: 'Pin' object has no attribute 'switch_to_output'".
In the driver code above, there's the line " dc.switch_to_output(value=0)". It looks like the "switch_to_output" attribute is in a digitalio module that is a core module of Adafruit CircuitPython and not in OpenMV IDE.
I changed the

Code: Select all

 dc.switch_to_output(value=0)
line to

Code: Select all

dc = Pin('P8', Pin.OUT_PP)
and then got the same "AttributeError: 'Pin' object has no attribute 'switch_to_output'", but this time it highlighted the line

Code: Select all

 self.write_cmd(SET_COL_ADDR)
in adafruit_ssd1306.py.

I'm not sure how to get around the digitalio dependencies- is there an easy way around this? I'm new to Python/Micropython.
User avatar
kwagyeman
Posts: 3188
Joined: Sun May 24, 2015 2:10 pm

Re: printing to OLED display

Postby kwagyeman » Mon Apr 15, 2019 12:37 am

Hi, Adafruit wrote their own version of the pin control library. It's not compatible with standard MicroPython. That's why Adafruit calls it Circuit Python.

That said, the basic logic is the same. You just need to read the OpenMV Cam API and then do the right behavior for the low level pin control.

E.g. see how to turn the pins into an output using the pyb MicroPython module. There's an example that shops with the IDE.
Nyamekye,
byte
Posts: 10
Joined: Sun Apr 14, 2019 11:20 pm

Re: printing to OLED display

Postby byte » Mon Apr 15, 2019 2:20 am

Thanks for clarifying. I understand how to set pins to be outputs/inputs in OpenMV. Now I'm just confused about why the line "self.write_cmd(SET_COL_ADDR)" would be highlighted with the same errror "'Pin' object has no attribute 'switch_to_output'". What does that line have to do with setting the pin direction?
User avatar
kwagyeman
Posts: 3188
Joined: Sun May 24, 2015 2:10 pm

Re: printing to OLED display

Postby kwagyeman » Mon Apr 15, 2019 1:22 pm

Hi, I don't know the adafruit code. Unsure why this is happening. They completely redid the MicroPython hardware interface layer.
Nyamekye,
byte
Posts: 10
Joined: Sun Apr 14, 2019 11:20 pm

Re: printing to OLED display

Postby byte » Wed Apr 17, 2019 3:23 pm

I found this Micropython driver for the OLED display (from Adafruit before CircuitPython). Now when I run it I get the error "ImportError: no module named 'framebuf'" This looks like a core module and says "frame buffer manipulation" when I hover over "framebuf" in OpenMV. Do you know why this would happen?

Code: Select all

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
        # Note the subclass must initialize self.framebuf to a framebuffer.
        # This is necessary because the underlying data buffer is different
        # between I2C and SPI implementations (I2C needs an extra byte).
        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_framebuf()

    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_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
        self.buffer = bytearray((height // 8) * width)
        self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height)
        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_framebuf(self):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs.high()
        self.dc.high()
        self.cs.low()
        self.spi.write(self.buffer)
        self.cs.high()

    def poweron(self):
        self.res.high()
        time.sleep_ms(1)
        self.res.low()
        time.sleep_ms(10)
        self.res.high()
User avatar
kwagyeman
Posts: 3188
Joined: Sun May 24, 2015 2:10 pm

Re: printing to OLED display

Postby kwagyeman » Wed Apr 17, 2019 3:31 pm

We don't enable to that module. We can add it however. Please create a GitHub ticket for it.
Nyamekye,
byte
Posts: 10
Joined: Sun Apr 14, 2019 11:20 pm

Re: printing to OLED display

Postby byte » Wed Apr 17, 2019 4:07 pm

Ok thank you- what exactly do I write under "how can we help?" for the Github ticket? I'm a little confused about the issue...
User avatar
iabdalkader
Posts: 912
Joined: Sun May 24, 2015 3:53 pm

Re: printing to OLED display

Postby iabdalkader » Wed Apr 17, 2019 4:12 pm

Actually we may not be able to enable this module if it reserves memory.
User avatar
iabdalkader
Posts: 912
Joined: Sun May 24, 2015 3:53 pm

Re: printing to OLED display

Postby iabdalkader » Wed Apr 17, 2019 4:25 pm

I checked, it allocates memory in runtime. I will enable this module.
byte
Posts: 10
Joined: Sun Apr 14, 2019 11:20 pm

Re: printing to OLED display

Postby byte » Wed Apr 17, 2019 4:29 pm

Thank you! I'm not familiar with the process of enabling a module. How do I get the update?
User avatar
iabdalkader
Posts: 912
Joined: Sun May 24, 2015 3:53 pm

Re: printing to OLED display

Postby iabdalkader » Wed Apr 17, 2019 4:30 pm

You have to wait for the next release, or build the firmware from source.
byte
Posts: 10
Joined: Sun Apr 14, 2019 11:20 pm

Re: printing to OLED display

Postby byte » Wed Apr 17, 2019 4:44 pm

How long is it expected to take?
User avatar
iabdalkader
Posts: 912
Joined: Sun May 24, 2015 3:53 pm

Re: printing to OLED display

Postby iabdalkader » Wed Apr 17, 2019 4:59 pm

This could take some time, maybe in a couple of weeks.
byte
Posts: 10
Joined: Sun Apr 14, 2019 11:20 pm

Re: printing to OLED display

Postby byte » Wed Apr 17, 2019 5:13 pm

Ok, could you please notify me here? Thank you.
User avatar
iabdalkader
Posts: 912
Joined: Sun May 24, 2015 3:53 pm

Re: printing to OLED display

Postby iabdalkader » Wed Apr 17, 2019 8:03 pm

Actually I can send you a firmware image for testing. Which camera do you have ?
byte
Posts: 10
Joined: Sun Apr 14, 2019 11:20 pm

Re: printing to OLED display

Postby byte » Wed Apr 17, 2019 10:31 pm

I have the M7...thanks!
User avatar
iabdalkader
Posts: 912
Joined: Sun May 24, 2015 3:53 pm

Re: printing to OLED display

Postby iabdalkader » Thu Apr 18, 2019 12:13 pm

Attached.
Attachments
firmware.zip
(923.64 KiB) Downloaded 1218 times
byte
Posts: 10
Joined: Sun Apr 14, 2019 11:20 pm

Re: printing to OLED display

Postby byte » Thu Apr 18, 2019 4:31 pm

It works, thanks so much!! :D I had to change a few things in the ssd1306.py file that I posted for the SPI OLED display, just for future reference:

Code: Select all

import time
import framebuf
from pyb import SPI

# 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
        # Note the subclass must initialize self.framebuf to a framebuffer.
        # This is necessary because the underlying data buffer is different
        # between I2C and SPI implementations (I2C needs an extra byte).
        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_framebuf()

    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_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
        self.buffer = bytearray((height // 8) * width)
        self.framebuf = framebuf.FrameBuffer1(self.buffer, width, height)
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.spi.init(SPI.MASTER, 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_framebuf(self):
        self.spi.init(SPI.MASTER,baudrate=self.rate, polarity=0, phase=0)
        self.cs.high()
        self.dc.high()
        self.cs.low()
        self.spi.write(self.buffer)
        self.cs.high()

    def poweron(self):
        self.res.high()
        time.sleep(1)
        self.res.low()
        time.sleep(10)
        self.res.high()
User avatar
kwagyeman
Posts: 3188
Joined: Sun May 24, 2015 2:10 pm

Re: printing to OLED display

Postby kwagyeman » Thu Apr 18, 2019 5:56 pm

Post a pic?
Nyamekye,
User avatar
iabdalkader
Posts: 912
Joined: Sun May 24, 2015 3:53 pm

Re: printing to OLED display

Postby iabdalkader » Thu Apr 18, 2019 7:43 pm

Should add this to the IDE libraries...
byte
Posts: 10
Joined: Sun Apr 14, 2019 11:20 pm

Re: printing to OLED display

Postby byte » Thu Apr 18, 2019 8:34 pm

Here's a pic of the OpenMV M7 connected to the SPI OLED display. Thanks again!
Attachments
OpenMV_OLED.JPG
User avatar
kwagyeman
Posts: 3188
Joined: Sun May 24, 2015 2:10 pm

Re: printing to OLED display

Postby kwagyeman » Thu Apr 18, 2019 10:25 pm

Ibrahim, can you add this to the examples?
Nyamekye,
User avatar
iabdalkader
Posts: 912
Joined: Sun May 24, 2015 3:53 pm

Re: printing to OLED display

Postby iabdalkader » Fri Apr 19, 2019 6:29 pm

done.
Nezra
Posts: 5
Joined: Fri Apr 19, 2019 9:08 am

Re: printing to OLED display

Postby Nezra » Mon Apr 29, 2019 9:50 am

Can we add the I2C code for this module back into the driver as well?
User avatar
kwagyeman
Posts: 3188
Joined: Sun May 24, 2015 2:10 pm

Re: printing to OLED display

Postby kwagyeman » Mon Apr 29, 2019 12:52 pm

Do you want the example shipped with the IDE?
Nyamekye,
Nezra
Posts: 5
Joined: Fri Apr 19, 2019 9:08 am

Re: printing to OLED display

Postby Nezra » Tue Apr 30, 2019 6:23 am

kwagyeman wrote:
Mon Apr 29, 2019 12:52 pm
Do you want the example shipped with the IDE?
that would be nice, but i am referencing the ssd1306 driver you added to the libraries.

https://github.com/openmv/openmv/blob/m ... ssd1306.py

The code only supports SPI communication. since it is not pulled from the main micropython branch, or was but was heavily modified to remove the I2C portion and modify some of the initialization for the SPI from the main branch, my request to to either change it to the master, or add the I2C support.

https://github.com/micropython/micropyt ... ssd1306.py

you'll find this code missing from the library added to the openmv github.

Code: Select all

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()
Thanks!

Return to “Project Discussion”

Who is online

Users browsing this forum: Bing [Bot] and 9 guests