Waveshare e-paper display how to rotate?

All ESP32 boards running MicroPython.
Target audience: MicroPython users with an ESP32 board.
JumpZero
Posts: 54
Joined: Mon Oct 30, 2017 5:54 am
Location: Arcachon - France

Waveshare e-paper display how to rotate?

Post by JumpZero » Mon Apr 22, 2019 12:15 pm

Hello,

after 3 days struggling with that I come here to ask for help, if possible.
I have a Waveshare ESP32 driver board + 2.13inch e-paper display (B model: 3 colors)
I have successfully flashed Micropython : MicroPython v1.10-54-g43a894fb4 on 2019-02-07; ESP32 module with ESP32
I use mcauser Micropython display driver and have successfully modified the SPI pins declaration in the exemple code to match that board.
It works. So far so good! I can print text, rectangles, lines, manage the colors :-)
Here is the code

Code: Select all

"""
	Example for 2.13 inch black & white & red Waveshare 2.13B E-ink screen
	Run on ESP32 Waveshare driver board (software SPI)
	Adapted by me -- April 2019
"""

import epaper2in13b
from machine import Pin, SPI

# software SPI on ESP32 Waveshare driver board
sck = Pin(13)
mosi = Pin(14)
cs = Pin(15)
busy = Pin(25)
rst = Pin(26)
dc = Pin(27)
# miso is not used but must be declared. Let's take any unused gpio: 12
miso = Pin(12)
spi = SPI(baudrate=100000, polarity=0, phase=0, sck=sck, mosi=mosi, miso=miso)

e = epaper2in13b.EPD(spi, cs, dc, rst, busy)
e.init()

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

# --------------------

# use a frame buffer
# 212 * 104 / 8 = 2756 - that's some pixels
import framebuf
buf = bytearray(w * h // 8)
fb = framebuf.FrameBuffer(buf, w, h, framebuf.MONO_HLSB)
black = 0
white = 1
fb.fill(white)

# --------------------

# write hello world with black bg and white text

# from toto import hello_world_dark

from image_dark import hello_world_dark
from image_light import hello_world_light
print('Image dark')
bufImage = hello_world_dark
fbImage = framebuf.FrameBuffer(bufImage, 128, 296, framebuf.MONO_HLSB)
fb.blit(fbImage, 20, 2)
bufImage = hello_world_light
fbImage = framebuf.FrameBuffer(bufImage, 128, 296, framebuf.MONO_HLSB)
fb.blit(fbImage, 168, 2)
# e.display_frame(buf)
e.display_frame(buf, buf)

# --------------------

# write hello world with white bg and black text
print('Image light')
#e.display_frame(hello_world_light)

# --------------------


print('Frame buffer things')
fb.fill(white)
fb.text('Hello World',30,0,black)
fb.pixel(30, 10, black)
fb.hline(30, 30, 10, black)
fb.vline(30, 50, 10, black)
fb.line(30, 70, 40, 80, black)
fb.rect(30, 90, 10, 10, black)
fb.fill_rect(30, 110, 10, 10, black)
for row in range(0,36):
	fb.text(str(row),0,row*8,black)
fb.text('Line 36',0,288,black)
#e.display_frame(buf)
e.display_frame(buf, buf)

# --------------------

# wrap text inside a box
black = 0
white = 1
# clear
fb.fill(white)
# display as much as this as fits in the box
str = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vel neque in elit tristique vulputate at et dui. Maecenas nec felis lectus. Pellentesque sit amet facilisis dui. Maecenas ac arcu euismod, tempor massa quis, ultricies est.'

# this could be useful as a new method in FrameBuffer
def text_wrap(str,x,y,color,w,h,border=None):
	# optional box border
	if border is not None:
		fb.rect(x, y, w, h, border)
	cols = w // 8
	# for each row
	j = 0
	for i in range(0, len(str), cols):
		# draw as many chars fit on the line
		fb.text(str[i:i+cols], x, y + j, color)
		j += 8
		# dont overflow text outside the box
		if j >= h:
			break

# clear
fb.fill(white)

# draw text box 1
# box position and dimensions
print('Box 1')
bx = 8
by = 8
bw = 112 #  = 14 cols
bh = 112 #  = 14 rows (196 chars in total)
text_wrap(str,bx,by,black,bw,bh,black)
#e.display_frame(buf)
e.display_frame(buf, buf)

# draw text box 2
print('Box 2 & 3')
bx = 0
by = 128
bw = w # 128 = 16 cols
bh = 6 * 8 # 48 = 6 rows (96 chars in total)
text_wrap(str,bx,by,black,bw,bh,black)

# draw text box 3
bx = 0
by = 184
bw = w//2 # 64 = 8 cols
bh = 8 * 8 # 64 = 8 rows (64 chars in total)
text_wrap(str,bx,by,black,bw,bh,None)
#e.display_frame(buf)
e.display_frame(buf, buf)

# --------------------

But I have no success in rotating the display. it’s 104x212 (WxH) and I’d like to display text horizontally, I want 212x104 (WxH)
I have tried to define the frame buffer 212x104 and use the text method of frame buffer but it’s displaying garbage.
There is a rotate function in mcauser’s driver but calling it: e.set_rotate(1)
has no effect as well, display is the same as without calling it.
A combination of declaring the framebuffer 212x104 and calling the rotate function doesn’t work either.
My understanding is that this function works on the frame buffer only (not modifying the way e.display_frame(buf, buf) will display) same for other functions circle, line, rectangle, etc..

The e-paper display (as I understand) display bytes from top corner left (104x212 WxH) with bit 0 being the leftmost. So rotating the framebuffer itself should work, but unfortunately there is no such method in the framebuffer class.

Does anybody have an idea? another library? a function to rotate the framebuffer? Or anything else?

Thanks

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Waveshare e-paper display how to rotate?

Post by pythoncoder » Tue Apr 23, 2019 8:10 am

From looking at @mcauser's code, the set_rotate method will only work if you use EPD methods for drawing objects. The display_frame method assumes that the passed buffers already have the correct orientation.

You are using framebuf methods which is effectively bypassing the EPD's rotation mechanism.
Peter Hinch
Index to my micropython libraries.

JumpZero
Posts: 54
Joined: Mon Oct 30, 2017 5:54 am
Location: Arcachon - France

Re: Waveshare e-paper display how to rotate?

Post by JumpZero » Tue Apr 23, 2019 6:21 pm

Hi Peter,
thank you for taking the time to answer.
You are right the set_rotate method belongs to EPD class and won't rotate a buffer filled with framebuffer methods.

Unfortunately there is no framebuffer rotate method, but there is a framebuffer method to write text, and there is no EPD method to write text but there is a rotate method! :o
And what I want to achieve is to write text in landscape mode (not portrait).
So I'm trying to define a 212x104 landscape framebuffer with MONO_VLSB format and then to map bytes and bits to the e-paper format.
Not sure it will do it but I try!
--
Jmp0

User avatar
devnull
Posts: 473
Joined: Sat Jan 07, 2017 1:52 am
Location: Singapore / Cornwall
Contact:

Re: Waveshare e-paper display how to rotate?

Post by devnull » Wed Apr 24, 2019 7:56 am

I am also looking for a solution to this, only on the 1.54" display which is 200px x 200px.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Waveshare e-paper display how to rotate?

Post by pythoncoder » Wed Apr 24, 2019 8:30 am

One possible solution is this. Note this is straight off the top of my head and it may be nonsense.

You could create two framebuf instances, one in portrait mode and the other in landscape mode. Use the framebuf method to draw to the portrait mode buffer. When you want to render it, use the blit method to transfer the result to the landscape buffer then use display_frame to output the landscape result.

Note that this repo offers a means of rendering arbitrary fonts to a framebuf.

Please don't hesitate to tell me if I'm talking out of my @rse ;)
Peter Hinch
Index to my micropython libraries.

JumpZero
Posts: 54
Joined: Mon Oct 30, 2017 5:54 am
Location: Arcachon - France

Re: Waveshare e-paper display how to rotate?

Post by JumpZero » Wed Apr 24, 2019 2:30 pm

Hello

I did it! It works :-)
The method is a bit like you said @pythoncoder.
I declare and use a framebuffer in landscape mode: format MONO_VLSB
I write text in it, I can also use other framebuffer methods (line, rectangle, etc..)
And when ready move it to another buffer to send to the e-paper.
And this is the difficult step (I'm on it since yesterday)
The bytes on the e-paper are organized (with display in portrait) first pixel row: byte 0 top left up to byte 12 top right, 2nd pixel row starting with byte 13, etc.. last byte is 2755 at bottom right, row 211.
Knowing the framebuffer bytes organisation MONO_VLSB format as described here
the transformation algorithm is this one (may be a better one exists, but this one works)

Code: Select all

# Move frame buffer bytes to e-paper buffer to match e-paper bytes oranisation.
# That is landscape mode to portrait mode. (for red and black buffers) 
x=0; y=0; n=1; R=0
for i in range(1, 14):
    for j in range(1, 213):
        R = (n-x)+((n-y)*12)
        buf_epaper_black[R-1] = buf_black[n-1]
        buf_epaper_red[R-1] = buf_red[n-1]
        n +=1
    x = n+i-1
    y = n-1
I was expecting to have to move the bits in the byte, I even did it at first but it's not needed.
I can even use EPD methods (circle for instance which isn’t available with framebuffer class) but first I must move the buffers.

Code: Select all

"""
	Example for 2.13 inch black & white & red Waveshare 2.13B E-ink screen
	Run on ESP32 Waveshare driver board (software SPI)
	Adapted by Guy -- April 2019
    We used the 2.13" E-paper in landscape mode
"""

import epaper2in13b
from machine import Pin, SPI

# software SPI on ESP32 Waveshare driver board
sck = Pin(13); mosi = Pin(14); cs = Pin(15); busy = Pin(25); rst = Pin(26); dc = Pin(27)
# miso is not used but must be declared. Let's take any unused gpio: 12
miso = Pin(12)
spi = SPI(baudrate=100000, polarity=0, phase=0, sck=sck, mosi=mosi, miso=miso)

e = epaper2in13b.EPD(spi, cs, dc, rst, busy)
e.init()

h = 104;  w = 212 # e-paper heigth and width. It will be used in landscape mode

buf_black        = bytearray(w * h // 8) # used by frame buffer (landscape)
buf_red          = bytearray(w * h // 8) # used by frame buffer (landscape)
buf_epaper_black = bytearray(w * h // 8) # used to display on e-paper after bytes have been 
buf_epaper_red   = bytearray(w * h // 8) # moved from frame buffer to match e-paper (portrait) 

import framebuf
fb_black = framebuf.FrameBuffer(buf_black, w, h, framebuf.MONO_VLSB)
fb_red   = framebuf.FrameBuffer(buf_red,   w, h, framebuf.MONO_VLSB)
black_red = 0 # will be black on buf_black, red on buf_red
white     = 1

#clear red & black screens, write in black on top left and in red bootom right 
fb_black.fill(white)
fb_red.fill(white)
fb_black.text('Hello world!', 0, 0,black_red)
fb_red.text('Hello world!', 110, 90,black_red)

# Let's draw rectangles, one black one red
fb_black.fill_rect(5, 40, 75, 10, black_red)
fb_red.fill_rect(5, 60, 75, 10, black_red)

# Move frame buffer bytes to e-paper buffer to match e-paper bytes oranisation.
# That is landscape mode to portrait mode. (for red and black buffers) 
x=0; y=0; n=1; R=0
for i in range(1, 14):
    for j in range(1, 213):
        R = (n-x)+((n-y)*12)
        buf_epaper_black[R-1] = buf_black[n-1]
        buf_epaper_red[R-1] = buf_red[n-1]
        n +=1
    x = n+i-1
    y = n-1
    
# We can use EPD class to draw circle(not available in framebuf class) 
# but only after we have moved bytes from framebuffers to e-paper buffers
e.draw_filled_circle(buf_epaper_black, 50, 110, 30, 1)
e.draw_filled_circle(buf_epaper_black, 50, 110, 20, 0)
e.draw_filled_circle(buf_epaper_red, 50, 150, 30, 1)
e.draw_filled_circle(buf_epaper_red, 50, 150, 20, 0)

print('Sending to display')
e.display_frame(buf_epaper_black, buf_epaper_red)
print('Done!.......')
#e.sleep()  # recommended by manufacturer to avoid damage to display
#print('E-paper sleeping!...') 
# also recommended by manufacturer: min. 180s between 2 refresh screen
And here is a photo of the result:
Image
@devnull I don't see the point to rotate a 200x200 display, since it's square can't you just rotate the hardware itself?
@pythoncoder I have used the framebuffer blit method before, but I think it will only work with framebuffers of same format or give unexpected result. Anyway it’s worth a try, I’ll be back.
--
Jmp0

User avatar
devnull
Posts: 473
Joined: Sat Jan 07, 2017 1:52 am
Location: Singapore / Cornwall
Contact:

Re: Waveshare e-paper display how to rotate?

Post by devnull » Wed Apr 24, 2019 11:13 pm

Hi;

The issue regarding display rotation for me is related to hardware layout, the inability to rotate it restricts where I can place the display on the board relative to other components.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Waveshare e-paper display how to rotate?

Post by pythoncoder » Thu Apr 25, 2019 9:52 am

Congratulations on getting it working :D

On reflection I think you're right about the blit method being unfeasible: I can't see a way to transpose the axes of source and destination.
Peter Hinch
Index to my micropython libraries.

JumpZero
Posts: 54
Joined: Mon Oct 30, 2017 5:54 am
Location: Arcachon - France

Re: Waveshare e-paper display how to rotate?

Post by JumpZero » Thu Apr 25, 2019 12:04 pm

Hello,

@pythoncoder:
after several tests I cannot have the blit method doing a rotation. Even if the method accepts framebuffer of different formats, the problem seems to be, as you mentionned, the transposition of the coordinates. I tried many different combinations but none give the expected result.
Here is the code (relevant part) I tried:

Code: Select all

h = 104;  w = 212 # e-paper heigth and width. It will be used in landscape mode

buf_black        = bytearray(w * h // 8) # used by frame buffer (landscape)
buf_red          = bytearray(w * h // 8) # used by frame buffer (landscape)
buf_epaper_black = bytearray(w * h // 8) # used to display on e-paper after bytes have been
buf_epaper_red   = bytearray(w * h // 8) # moved from frame buffer to match e-paper (portrait)

import framebuf
fb_black = framebuf.FrameBuffer(buf_black, 212, 104, framebuf.MONO_VLSB) #landscape
fb_red   = framebuf.FrameBuffer(buf_red,   212, 104, framebuf.MONO_VLSB) #landscape
fb_final_black = framebuf.FrameBuffer(buf_epaper_black, 104, 212, framebuf.MONO_HLSB) #portrait
fb_final_red = framebuf.FrameBuffer(buf_epaper_red, 104, 212, framebuf.MONO_HLSB)     #portrait
black_red = 0 # will be black on buf_black, red on buf_red
white     = 1

#clear red & black screens, then write 
fb_black.fill(white)
fb_red.fill(white)
fb_final_black.fill(white)
fb_final_red.fill(white)
fb_black.text('Hello world!', 5, 10,black_red)
fb_final_black.text('Micropython', 5, 50,black_red)

# Now blit: draw landscape on top of portrait
fb_final_black.blit(fb_black, 0, 0)
fb_final_red.blit(fb_red, 0, 0)

print('Sending to display')
e.display_frame(buf_epaper_black, buf_epaper_red)
No problem I'll stick with my mapping method.

JumpZero
Posts: 54
Joined: Mon Oct 30, 2017 5:54 am
Location: Arcachon - France

Re: Waveshare e-paper display how to rotate?

Post by JumpZero » Thu Apr 25, 2019 12:48 pm

Hi!
@devnull:
You can try this code, it's adapted from my 104x212 to your 200x200, maybe I've done some typo. I cannot test it since I don't have a 200x200 e-paper display. If yours is black only remove all lines with reference to red. It may give you a path to follow even if doesn't work out of the box ;-)
The algorithm is the one I use, I've just changed some figures.
You have to add all your init and spi stuff..

Code: Select all

h = 200;  w = 200 # e-paper heigth and width.

buf_black        = bytearray(w * h // 8) # used by frame buffer (landscape)
buf_red          = bytearray(w * h // 8) # used by frame buffer (landscape)
buf_epaper_black = bytearray(w * h // 8) # used to display on e-paper after bytes have been
buf_epaper_red   = bytearray(w * h // 8) # moved from frame buffer to match e-paper (portrait)

import framebuf
fb_black = framebuf.FrameBuffer(buf_black, w, h, framebuf.MONO_VLSB)
fb_red   = framebuf.FrameBuffer(buf_red,   w, h, framebuf.MONO_VLSB)
black_red = 0 # will be black on buf_black, red on buf_red
white     = 1

#clear red & black screens, write in black on top left and in red bootom right
fb_black.fill(white)
fb_red.fill(white)
fb_black.text('Hello world!', 0, 0,black_red)
fb_red.text('Hello world!', 110, 90,black_red)

# Move frame buffer bytes to e-paper buffer to match e-paper bytes oranisation.
# That is landscape mode to portrait mode. (for red and black buffers)
for i in range(0, 25):
    for j in range(0, 200):
        m = (n-x)+(n-y)*24
        buf_epaper_black[m] = buf_black[n]
        buf_epaper_red[m] = buf_red[n]
        n +=1
    x = n+i+1
    y = n-1

print('Sending to display')
e.display_frame(buf_epaper_black, buf_epaper_red)
print('Done!.......')
I also post here the full code that is working on my 104x212 it may help.
Note that we can have text simultaneoulsy in portrait and paysage mode.

Code: Select all

"""
	Example for 2.13 inch black & white & red Waveshare 2.13B E-ink screen
	Run on ESP32 Waveshare driver board (software SPI)
	Adapted by Guy -- April 2019
    We used the 2.13" E-paper in landscape mode
"""

import epaper2in13b
from machine import Pin, SPI

# software SPI on ESP32 Waveshare driver board
sck = Pin(13); mosi = Pin(14); cs = Pin(15); busy = Pin(25); rst = Pin(26); dc = Pin(27)
# miso is not used but must be declared. Let's take any unused gpio: 12
miso = Pin(12)
spi = SPI(baudrate=100000, polarity=0, phase=0, sck=sck, mosi=mosi, miso=miso)

e = epaper2in13b.EPD(spi, cs, dc, rst, busy)
e.init()

h = 104;  w = 212 # e-paper heigth and width. It will be used in landscape mode

buf_black        = bytearray(w * h // 8) # used by frame buffer (landscape)
buf_red          = bytearray(w * h // 8) # used by frame buffer (landscape)
buf_epaper_black = bytearray(w * h // 8) # used to display on e-paper after bytes have been
buf_epaper_red   = bytearray(w * h // 8) # moved from frame buffer to match e-paper (portrait)

import framebuf
fb_black = framebuf.FrameBuffer(buf_black, w, h, framebuf.MONO_VLSB)
fb_red   = framebuf.FrameBuffer(buf_red,   w, h, framebuf.MONO_VLSB)
black_red = 0 # will be black on buf_black, red on buf_red
white     = 1

#clear red & black screens, write in black on top left and in red bootom right
fb_black.fill(white)
fb_red.fill(white)
fb_black.text('Hello world!', 0, 0,black_red)
fb_red.text('Hello world!', 110, 90,black_red)

# Let's draw rectangles, one black one red
fb_black.fill_rect(10, 40, 75, 10, black_red)
fb_red.fill_rect(10, 60, 75, 10, black_red)

# Move frame buffer bytes to e-paper buffer to match e-paper bytes oranisation.
# That is landscape mode to portrait mode. (for red and black buffers)
x=0; y=-1; n=0; m=0
for i in range(0, 13):
    for j in range(0, 212):
        m = (n-x)+(n-y)*12
        buf_epaper_black[m] = buf_black[n]
        buf_epaper_red[m] = buf_red[n]
        n +=1
    x = n+i+1
    y = n-1

# Now let's write text in portrait mode
fb_portrait_black   = framebuf.FrameBuffer(buf_epaper_black, 104, 212, framebuf.MONO_HLSB)
fb_portrait_red     = framebuf.FrameBuffer(buf_epaper_red, 104, 212, framebuf.MONO_HLSB)
fb_portrait_black.text('Micropython', 0, 0,black_red)
fb_portrait_red.text('Micropython', 15, 200,black_red)
    
# We can use EPD class to draw circle(not available in framebuf class)
# but only after we have moved bytes from framebuffers to e-paper buffers
e.draw_filled_circle(buf_epaper_black, 50, 110, 30, 1)
e.draw_filled_circle(buf_epaper_black, 50, 110, 20, 0)
e.draw_filled_circle(buf_epaper_red, 50, 150, 30, 1)
e.draw_filled_circle(buf_epaper_red, 50, 150, 20, 0)

print('Sending to display')
e.display_frame(buf_epaper_black, buf_epaper_red)
print('Done!.......')
e.sleep()  # recommended by manufacturer to avoid damage to display
print('E-paper sleeping!...')
# also recommended by manufacturer: min. 180s between 2 refresh screen

#print('Test if sleeping: Nothing should be dispayed')
#fb_black.fill(white)
#fb_red.fill(white)
#e.display_frame(buf_black, None)
print('END')
Image

Post Reply