Page 1 of 1

Pycom GPy and ssd1306.py. writevto()

Posted: Thu Oct 29, 2020 12:28 am
by tiensz
I trying to port working code from Pyboard (or ESP8266, both works) to a GPy.
The GPy runs (Pycom MicroPython 1.20.2.r1 [v1.11-a5aa0b8] on 2020-09-09; GPy with ESP32

The code depends on the font_to_py.py library, which specifies that the 'official' ssd1306.py driver be used.
This driver contains the following:
def write_data(self, buf):
self.write_list[1] = buf
self.i2c.writevto(self.addr, buf)

Initializing ssd1306.py returns:
: AttributeError: 'I2C' object has no attributes 'writevto'

I checked:
>>> dir(I2C)
['__class__', '__name__', 'MASTER', 'deinit', 'init', 'readfrom', 'readfrom_into', 'readfrom_mem', 'readfrom_mem_into', 'scan', 'writeto', 'writeto_mem']
>>>

What hackery should I perform to get this to work?

Re: Pycom GPy and ssd1306.py. writevto()

Posted: Thu Oct 29, 2020 4:30 am
by jimmo
tiensz wrote:
Thu Oct 29, 2020 12:28 am
: AttributeError: 'I2C' object has no attributes 'writevto'
writevto was an optimisation added a little while ago to MicroPython that helps with these sorts of drivers that need to send commands over I2C in the form of <prefix><chunk of data> and it prevents having to create a new buffer every time with the concatenated data.

You can see the commit that was applied to ssd1306.py to take advantage of this feature here: https://github.com/micropython/micropyt ... 87332d6d59 -- just do the reverse change and you should hopefully work on Pycom.

Re: Pycom GPy and ssd1306.py. writevto()

Posted: Thu Oct 29, 2020 6:36 am
by Roberthh
There is a ssd1306 driver in the pycom source tree. https://github.com/pycom/pycom-micropyt ... rs/display

Re: Pycom GPy and ssd1306.py. writevto()

Posted: Thu Oct 29, 2020 8:12 am
by tiensz
Roberthh wrote:
Thu Oct 29, 2020 6:36 am
There is a ssd1306 driver in the pycom source tree. https://github.com/pycom/pycom-micropyt ... rs/display
I tried it and get: AttributeError: 'I2C' object has no attribute 'start'
A missing method? What was it supposed to do?

Re: Pycom GPy and ssd1306.py. writevto()

Posted: Thu Oct 29, 2020 8:21 am
by Roberthh
That's the old soft I2C method. I have a modified version which I used with Pycom Micropython: https://github.com/robert-hh/pycom-micr ... ssd1306.py
AFAI recall I made a PR for that, but it did not make it's way into the main repository.

Re: Pycom GPy and ssd1306.py. writevto()

Posted: Thu Oct 29, 2020 8:59 am
by tiensz
jimmo wrote:
Thu Oct 29, 2020 4:30 am
tiensz wrote:
Thu Oct 29, 2020 12:28 am
: AttributeError: 'I2C' object has no attributes 'writevto'
writevto was an optimisation added a little while ago to MicroPython that helps with these sorts of drivers that need to send commands over I2C in the form of <prefix><chunk of data> and it prevents having to create a new buffer every time with the concatenated data.

You can see the commit that was applied to ssd1306.py to take advantage of this feature here: https://github.com/micropython/micropyt ... 87332d6d59 -- just do the reverse change and you should hopefully work on Pycom.
I'm out of my depth inside git. But what I tried was to open the link supplied. I came upon the document with marked up changes, but I don't know how to roll it back properly. What I did instead is to use the parent code. Is that correct?
In any case, I ran into the same problem as with the Pycom driver mentioned by Roberthh:
Extract from ssd1306.py
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()

Running the program I get:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "/flash/lib/ssd1306_setup.py", line 43, in <module>
File "/flash/lib/ssd1306_setup.py", line 41, in setup
File "/flash/lib/ssd1306.py", line 99, in __init__
File "/flash/lib/ssd1306.py", line 36, in __init__
File "/flash/lib/ssd1306.py", line 63, in init_display
File "/flash/lib/ssd1306.py", line 91, in show
File "/flash/lib/ssd1306.py", line 109, in write_data
AttributeError: 'I2C' object has no attribute 'start'

So, unless I figure out what I2C.start() is supposed to do, I'm stumped.
Any suggestions?

Re: Pycom GPy and ssd1306.py. writevto()

Posted: Thu Oct 29, 2020 9:25 am
by Roberthh
start() and stop() are methods of the softI2C class, which is not implemented by Pycom. For ease of use, instead of the link I copy my code below. Note that is is more a question about the Pycom brand of Micropython and therefore better to be discussed in their forum.

Code: Select all

# MicroPython SSD1306 OLED driver, I2C and SPI interfaces

from micropython import const
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)

# Subclassing FrameBuffer provides support for graphics primitives
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
class SSD1306(framebuf.FrameBuffer):
    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)
        super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB)
        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.width > 2 * self.height 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 poweron(self):
        self.write_cmd(SET_DISP | 0x01)

    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)


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.i2c.writeto(self.addr, b'\x40' + buf)  

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
        import time
        self.res(1)
        time.sleep_ms(1)
        self.res(0)
        time.sleep_ms(10)
        self.res(1)
        super().__init__(width, height, external_vcc)

    def write_cmd(self, cmd):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs(1)
        self.dc(0)
        self.cs(0)
        self.spi.write(bytearray([cmd]))
        self.cs(1)

    def write_data(self, buf):
        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
        self.cs(1)
        self.dc(1)
        self.cs(0)
        self.spi.write(buf)
        self.cs(1)

Re: Pycom GPy and ssd1306.py. writevto()

Posted: Thu Oct 29, 2020 9:42 am
by tiensz
Roberthh wrote:
Thu Oct 29, 2020 8:21 am
That's the old soft I2C method. I have a modified version which I used with Pycom Micropython: https://github.com/robert-hh/pycom-micr ... ssd1306.py
AFAI recall I made a PR for that, but it did not make it's way into the main repository.
Thank you. I've got it working with this library. It's unbelievable how many variants of ssd1306.py is floating around.

Re: Pycom GPy and ssd1306.py. writevto()

Posted: Thu Oct 29, 2020 10:54 am
by tiensz
Roberthh wrote:
Thu Oct 29, 2020 9:25 am
start() and stop() are methods of the softI2C class, which is not implemented by Pycom. For ease of use, instead of the link I copy my code below. Note that is is more a question about the Pycom brand of Micropython and therefore better to be discussed in their forum.

This forum is clued up on ssd1306.py. And active, I posted last night, and this morning my problem was solved in 30 minutes.