Connecting LOLIN/WEMOS D32Pro V2.0 and 2.13 Inch e-paper

All ESP32 boards running MicroPython.
Target audience: MicroPython users with an ESP32 board.
User avatar
Werner_G
Posts: 17
Joined: Fri Sep 13, 2019 8:15 am
Location: Dortmund / Germany

Connecting LOLIN/WEMOS D32Pro V2.0 and 2.13 Inch e-paper

Post by Werner_G » Tue Jan 07, 2020 1:12 pm

Santa Claus brought me these nice gadgets, but I need help to get it worked. I am using the on-board TFT-Connector. But so far I had no luck in displaying anything.

My definition is:

Code: Select all

from machine import Pin, SPI
import epaper2in13

sck = Pin(18)
miso = Pin(19)
mosi = Pin(23)
dc = Pin(27)
cs = Pin(14)
rst = Pin(33)
busy = Pin(32)
spi = SPI(2, baudrate=100000, polarity=0, phase=0, sck=sck, mosi=mosi, miso=miso)

w = 104
h = 212
x = 0
y = 0

e = epaper2in13.EPD(spi, cs, dc, rst, busy)
e.init()
It would be fine if somebody could post a working code here. Thanks a lot!

User avatar
Werner_G
Posts: 17
Joined: Fri Sep 13, 2019 8:15 am
Location: Dortmund / Germany

Re: Connecting LOLIN/WEMOS D32Pro V2.0 and 2.13 Inch e-paper

Post by Werner_G » Fri Jan 10, 2020 7:50 am

To precise my problem a little bit more:
LOLIN's e-paper-display has pins only for RST, DC, MOSI, SCK and CS. Can I get rid of MISO and BUSY?
The D32Pro is using Espressif's WROVER-B-Modul.
I cannot find any example using these new products.

User avatar
Werner_G
Posts: 17
Joined: Fri Sep 13, 2019 8:15 am
Location: Dortmund / Germany

Re: Connecting LOLIN/WEMOS D32Pro V2.0 and 2.13 Inch e-paper

Post by Werner_G » Sun Jan 12, 2020 2:45 pm

Meanwhile I understood the H/W-SPI-code on ESP32 and was able to see first activities on the screen. So, settings and pin definitions seems to be OK now:

Code: Select all

import epaper2in13
from machine import Pin, SPI

cs = Pin(14)
busy = Pin(32)
rst = Pin(33)
dc = Pin(27)

vspi = SPI(2, baudrate=100000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(23), miso=Pin(19))

e = epaper2in13.EPD(vspi, cs, dc, rst, busy)
e.init()

w = 104
h = 212
x = 0
y = 0

e.display_frame()
When turning on, the display flashes 4 times black/white. When writing

Code: Select all

e.display_frame('Hallo Welt')
instead, I get this error message:

Code: Select all

TypeError: function takes 1 positional arguments but 2 were given
Has somebody a simple routine to test or write text?

User avatar
Werner_G
Posts: 17
Joined: Fri Sep 13, 2019 8:15 am
Location: Dortmund / Germany

Re: Connecting LOLIN/WEMOS D32Pro V2.0 and 2.13 Inch e-paper

Post by Werner_G » Tue Feb 04, 2020 11:59 am

I'm still struggeling in getting any activity on the epaper-display (the Arduino-examples from LOLIN are working fine). When I now reduce the baudraute to 10MHz instead of 80MHz, I still see not any sign, but the REPL reports:

Code: Select all

[0;32mI (2039350) gpio: GPIO[23]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (2039350) gpio: GPIO[19]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
[0;32mI (2039360) gpio: GPIO[18]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 [0m
I don't know what does it mean. Is there anybody having experience with the LOLIN-display?

User avatar
T-Wilko
Posts: 30
Joined: Thu Sep 19, 2019 8:08 am

Re: Connecting LOLIN/WEMOS D32Pro V2.0 and 2.13 Inch e-paper

Post by T-Wilko » Thu Apr 09, 2020 3:07 pm

Hi Werner,

I'm curious as to what the conclusion was to your efforts, as I now find myself in your shoes with the exact same board and epaper module!

Cheers,
Thomas

User avatar
Werner_G
Posts: 17
Joined: Fri Sep 13, 2019 8:15 am
Location: Dortmund / Germany

Re: Connecting LOLIN/WEMOS D32Pro V2.0 and 2.13 Inch e-paper

Post by Werner_G » Fri Apr 10, 2020 2:51 pm

Hi Thomas,

finally I gave up. Board and display works perfect within Arduino, so wiring is OK. When using MicroPython I used mcauser's driver-lib. Especially the 2.13''-display comes with two different versions. At the end of the day I was able to use the display, but unfortunately the display was always mirrored. Perhaps it is a mistake in the framebuffer-lib, I don't know. Please have a look:
https://github.com/mcauser/micropython- ... r/issues/5
viewtopic.php?f=15&t=7683
Would be great if you could get it worked!

User avatar
T-Wilko
Posts: 30
Joined: Thu Sep 19, 2019 8:08 am

Re: Connecting LOLIN/WEMOS D32Pro V2.0 and 2.13 Inch e-paper

Post by T-Wilko » Tue Apr 28, 2020 6:47 am

Hi Werner,

After many afternoons toiling at the datasheet, I've got it working.
The following code should give you a non mirrored, well aligned image.
The image may appear to be upside down, but I believe that's just the way that Lolin has mounted the EPD on to the PCB.

Code: Select all


from micropython import const
from machine import SPI, Pin
from time import sleep_ms
import ustruct

# Display resolution
EPD_WIDTH  = const(128)
EPD_HEIGHT = const(250)
# datasheet says 250x122 (increased to 128 to be multiples of 8)

# Display commands
DRIVER_OUTPUT_CONTROL                = const(0x01)
# Gate Driving Voltage Control       0x03
# Source Driving voltage Control     0x04
BOOSTER_SOFT_START_CONTROL           = const(0x0C) # not in datasheet
#GATE_SCAN_START_POSITION             = const(0x0F) # not in datasheet
DEEP_SLEEP_MODE                      = const(0x10)
DATA_ENTRY_MODE_SETTING              = const(0x11)
#SW_RESET                             = const(0x12)
#TEMPERATURE_SENSOR_CONTROL           = const(0x1A)
MASTER_ACTIVATION                    = const(0x20)
#DISPLAY_UPDATE_CONTROL_1             = const(0x21)
DISPLAY_UPDATE_CONTROL_2             = const(0x22)
# Panel Break Detection              0x23
WRITE_RAM                            = const(0x24)
WRITE_VCOM_REGISTER                  = const(0x2C)
# Status Bit Read                    0x2F
WRITE_LUT_REGISTER                   = const(0x32)
SET_DUMMY_LINE_PERIOD                = const(0x3A)
SET_GATE_TIME                        = const(0x3B)
#BORDER_WAVEFORM_CONTROL              = const(0x3C)
SET_RAM_X_ADDRESS_START_END_POSITION = const(0x44)
SET_RAM_Y_ADDRESS_START_END_POSITION = const(0x45)
SET_RAM_X_ADDRESS_COUNTER            = const(0x4E)
SET_RAM_Y_ADDRESS_COUNTER            = const(0x4F)
TERMINATE_FRAME_READ_WRITE           = const(0xFF) # not in datasheet, aka NOOP


class EPD:
    def __init__(self):
        self.spi = SPI(2, baudrate=20000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(23), miso=Pin(19))
        #self.spi = SPI(1, 10000000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))
        self.spi.init()

        dc = Pin(27)
        cs = Pin(14)
        rst = Pin(33)

        self.cs = cs
        self.dc = dc
        self.rst = rst
        #self.busy = busy
        self.cs.init(self.cs.OUT, value=1)
        self.dc.init(self.dc.OUT, value=0)
        self.rst.init(self.rst.OUT, value=0)
        self.width = EPD_WIDTH
        self.height = EPD_HEIGHT

        self.size = self.width * self.height // 8
        self.buf = bytearray(self.size)

    LUT_FULL_UPDATE    = bytearray(b'\x80\x60\x40\x00\x00\x00\x00\x10\x60\x20\x00\x00\x00\x00\x80\x60\x40\x00\x00\x00\x00\x10\x60\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x03\x00\x00\x02\x09\x09\x00\x00\x02\x03\x03\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15\x41\xA8\x32\x30\x0A')
    LUT_PARTIAL_UPDATE = bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x15\x41\xA8\x32\x30\x0A')

    def clearBuffer(self):
        self._command(b'\x24')
        for i in range(0, len(self.buf)):
            self.buf[i] = 255
            self._data(bytearray([self.buf[i]]))

    def displayBuffer(self, buf):
        self._command(b'\x24')
        for i in range(0, len(buf)):
            self._data(bytearray([buf[i]]))
        self._command(b'\x22')
        self._command(b'\xC7')
        self._command(b'\x20')
        self._command(bytearray([TERMINATE_FRAME_READ_WRITE]))
        self.wait_until_idle()

    def _command(self, command, data=None):
        self.cs(1) # according to LOLIN_EPD
        self.dc(0)
        self.cs(0)
        self.spi.write(command)
        self.cs(1)
        if data is not None:
            self._data(data)

    def _data(self, data):
        self.cs(1) # according to LOLIN_EPD
        self.dc(1)
        self.cs(0)
        self.spi.write(data)
        self.cs(1)

    def init(self):
        self.reset()

        self.wait_until_idle();
        self._command(b'\x12'); # soft reset
        self.wait_until_idle();

        self._command(b'\x74', b'\x54'); #set analog block control
        self._command(b'\x7E', b'\x3B'); #set digital block control
        self._command(b'\x0f', b'\x00'); #set gate scan start position
        self._command(b'\x01', b'\xF9\x00\x00'); #Driver output control  ### CHANGED x00 to x01 ###
        self._command(b'\x11', b'\x03'); #data entry mode    ### CHANGED x01 to x00 ###
        #set Ram-X address start/end position
        self._command(b'\x44', b'\x00\x0F'); #0x0C-->(15+1)*8=128
        #set Ram-Y address start/end position
        self._command(b'\x45', b'\x00\x00\xF9\x00'); # 0xF9-->(249+1)=250   ### CHANGED xF9 to x00 ###

        self._command(b'\x3C', b'\x03'); # BorderWavefrom
        self._command(b'\x2C', b'\x55'); # VCOM Voltage

        self._command(b'\x03', bytes([self.LUT_FULL_UPDATE[70]])); # ??

        self._command(b'\x04')
        self._data(bytes([self.LUT_FULL_UPDATE[71]])); # ??
        self._data(bytes([self.LUT_FULL_UPDATE[72]])); # ??
        self._data(bytes([self.LUT_FULL_UPDATE[73]])); # ??


        self._command(b'\x3A', bytes([self.LUT_FULL_UPDATE[74]])); # Dummy Line
        self._command(b'\x3B', bytes([self.LUT_FULL_UPDATE[75]])); # Gate time

        self.set_lut(self.LUT_FULL_UPDATE)

        self._command(b'\x4E', b'\x00'); # set RAM x address count to 0;
        self._command(b'\x4F', b'\x00\x00'); # set RAM y address count to 0X127;
        self.wait_until_idle()

    def wait_until_idle(self):
        sleep_ms(1000)

    def reset(self):
        self.rst(1)
        sleep_ms(1)

        self.rst(0)
        sleep_ms(10)

        self.rst(1)

    def set_lut(self, lut):
        self._command(bytearray([WRITE_LUT_REGISTER]), lut)

    # put an image in the frame memory
    def set_frame_memory(self, image, x, y, w, h):
        # x point must be the multiple of 8 or the last 3 bits will be ignored
        x = x & 0xF8
        w = w & 0xF8

        if (x + w >= self.width):
            x_end = self.width - 1
        else:
            x_end = x + w - 1

        if (y + h >= self.height):
            y_end = self.height - 1
        else:
            y_end = y + h - 1

        self.set_memory_area(x, y, x_end, y_end)
        self.set_memory_pointer(x, y)
        self._command(bytearray([WRITE_RAM]), image)

    # replace the frame memory with the specified color
    def clear_frame_memory(self, color):
        self.set_memory_area(0, 0, self.width - 1, self.height - 1)
        self.set_memory_pointer(0, 0)
        self._command(bytearray([WRITE_RAM]))
        # send the color data
        for i in range(0, (self.width * self.height)//8):
            self._data(bytearray([color]))

    # draw the current frame memory and switch to the next memory area
    def display_frame(self):
        self._command(bytearray([DISPLAY_UPDATE_CONTROL_2]), b'\xC7')
        self._command(bytearray([MASTER_ACTIVATION]))
        self._command(bytearray([TERMINATE_FRAME_READ_WRITE]))
        self.wait_until_idle()

    # specify the memory area for data R/W
    def set_memory_area(self, x_start, y_start, x_end, y_end):
        self._command(bytearray([SET_RAM_X_ADDRESS_START_END_POSITION]))
        # x point must be the multiple of 8 or the last 3 bits will be ignored
        self._data(bytearray([(x_start >> 3) & 0xFF]))
        self._data(bytearray([(x_end >> 3) & 0xFF]))
        self._command(bytearray([SET_RAM_Y_ADDRESS_START_END_POSITION]), ustruct.pack("<HH", y_start, y_end))

    # specify the start point for data R/W
    def set_memory_pointer(self, x, y):
        self._command(bytearray([SET_RAM_X_ADDRESS_COUNTER]))
        # x point must be the multiple of 8 or the last 3 bits will be ignored
        self._data(bytearray([(x >> 3) & 0xFF]))
        self._command(bytearray([SET_RAM_Y_ADDRESS_COUNTER]), ustruct.pack("<H", y))
        self.wait_until_idle()

    # to wake call reset() or init()
    def sleep(self):
        self._command(bytearray([DEEP_SLEEP_MODE]))
        self.wait_until_idle()

The changes I had to make were all contained in the init method of the EPD class, which were:
- Added command for gate scan start position
- Changed data entry mode
- Changed both RAM x & y, start and end positions
- Changed both RAM address counter

I should probably put this in a nice micropython github repository... Next thing on to-do list lol

Cheers,
Thomas

fxmike08
Posts: 5
Joined: Wed Feb 05, 2020 1:01 pm

Re: Connecting LOLIN/WEMOS D32Pro V2.0 and 2.13 Inch e-paper

Post by fxmike08 » Tue Apr 28, 2020 5:19 pm

Hi Thomas,

Do you have an working example for framebuf on how to display in landscape mode, using your code version?

Thanks,
Mike

User avatar
T-Wilko
Posts: 30
Joined: Thu Sep 19, 2019 8:08 am

Re: Connecting LOLIN/WEMOS D32Pro V2.0 and 2.13 Inch e-paper

Post by T-Wilko » Thu Apr 30, 2020 8:07 am

fxmike08 wrote:
Tue Apr 28, 2020 5:19 pm
Do you have an working example for framebuf on how to display in landscape mode, using your code version?
Hi Mike,

Not currently, but I'll make a second driver file for landscape usage (or perhaps add a user inputted parameter to indicate orientation) when I get the time soon.
I'll post a link to it here when I'm done writing it.

Cheers,
Thomas

User avatar
Werner_G
Posts: 17
Joined: Fri Sep 13, 2019 8:15 am
Location: Dortmund / Germany

Re: Connecting LOLIN/WEMOS D32Pro V2.0 and 2.13 Inch e-paper

Post by Werner_G » Wed Jul 01, 2020 6:48 am

Hi Thomas,

after a long time I just came back to this forum and saw your post. Using your lib I am now able to run my epaper-display the first time. Great and thank you for the work you have done!
Please mind my request for a landscape-driver - this is my preferred mode.

Post Reply