misc: attempted refactoring of the display support in something less shitty

This commit is contained in:
Simone Margaritelli
2019-10-15 11:50:09 +02:00
parent df33d20cb2
commit 13d68c7c24
30 changed files with 429 additions and 317 deletions

@@ -0,0 +1,24 @@
from inky.inky import Inky, CS0_PIN, DC_PIN, RESET_PIN, BUSY_PIN
class InkyFast(Inky):
def __init__(self, resolution=(400, 300), colour='black', cs_pin=CS0_PIN, dc_pin=DC_PIN, reset_pin=RESET_PIN,
busy_pin=BUSY_PIN, h_flip=False, v_flip=False):
super(InkyFast, self).__init__(resolution, colour, cs_pin, dc_pin, reset_pin, busy_pin, h_flip, v_flip)
self._luts['black'] = [
0b01001000, 0b10100000, 0b00010000, 0b00010000, 0b00010011, 0b00000000, 0b00000000,
0b01001000, 0b10100000, 0b10000000, 0b00000000, 0b00000011, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b01001000, 0b10100101, 0b00000000, 0b10111011, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
# The following timings have been reduced to avoid the fade to black
0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x04, 0x04, 0x04, 0x04,
0x04, 0x08, 0x08, 0x10, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
]

@@ -0,0 +1,27 @@
"""Inky pHAT e-Ink Display Driver."""
from . import inkyfast
class InkyPHATFast(inkyfast.InkyFast):
"""Inky wHAT e-Ink Display Driver."""
WIDTH = 212
HEIGHT = 104
WHITE = 0
BLACK = 1
RED = 2
YELLOW = 2
def __init__(self, colour):
"""Initialise an Inky pHAT Display.
:param colour: one of red, black or yellow, default: black
"""
inkyfast.InkyFast.__init__(
self,
resolution=(self.WIDTH, self.HEIGHT),
colour=colour,
h_flip=False,
v_flip=False)

@@ -0,0 +1,213 @@
#qCopyright 2013-2015 Pervasive Displays, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http:#www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied. See the License for the specific language
# governing permissions and limitations under the License.
from PIL import Image
from PIL import ImageOps
from pwnagotchi.ui.hw.libs.papirus import LM75B
import re
import os
import sys
if sys.version_info < (3,):
def b(x):
return x
else:
def b(x):
return x.encode('ISO-8859-1')
class EPDError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
class EPD(object):
"""EPD E-Ink interface
to use:
from EPD import EPD
epd = EPD([path='/path/to/epd'], [auto=boolean], [rotation = 0|90|180|270])
image = Image.new('1', epd.size, 0)
# draw on image
epd.clear() # clear the panel
epd.display(image) # tranfer image data
epd.update() # refresh the panel image - not needed if auto=true
"""
PANEL_RE = re.compile('^([A-Za-z]+)\s+(\d+\.\d+)\s+(\d+)x(\d+)\s+COG\s+(\d+)\s+FILM\s+(\d+)\s*$', flags=0)
def __init__(self, *args, **kwargs):
self._epd_path = '/dev/epd'
self._width = 200
self._height = 96
self._panel = 'EPD 2.0'
self._cog = 0
self._film = 0
self._auto = False
self._lm75b = LM75B()
self._rotation = 0
self._uselm75b = True
if len(args) > 0:
self._epd_path = args[0]
elif 'epd' in kwargs:
self._epd_path = kwargs['epd']
if ('auto' in kwargs) and kwargs['auto']:
self._auto = True
if ('rotation' in kwargs):
rot = kwargs['rotation']
if rot in (0, 90, 180, 270):
self._rotation = rot
else:
raise EPDError('rotation can only be 0, 90, 180 or 270')
with open(os.path.join(self._epd_path, 'version')) as f:
self._version = f.readline().rstrip('\n')
with open(os.path.join(self._epd_path, 'panel')) as f:
line = f.readline().rstrip('\n')
m = self.PANEL_RE.match(line)
if m is None:
raise EPDError('invalid panel string')
self._panel = m.group(1) + ' ' + m.group(2)
self._width = int(m.group(3))
self._height = int(m.group(4))
self._cog = int(m.group(5))
self._film = int(m.group(6))
if self._width < 1 or self._height < 1:
raise EPDError('invalid panel geometry')
if self._rotation in (90, 270):
self._width, self._height = self._height, self._width
@property
def size(self):
return (self._width, self._height)
@property
def width(self):
return self._width
@property
def height(self):
return self._height
@property
def panel(self):
return self._panel
@property
def version(self):
return self._version
@property
def cog(self):
return self._cog
@property
def film(self):
return self._film
@property
def auto(self):
return self._auto
@auto.setter
def auto(self, flag):
if flag:
self._auto = True
else:
self._auto = False
@property
def rotation(self):
return self._rotation
@rotation.setter
def rotation(self, rot):
if rot not in (0, 90, 180, 270):
raise EPDError('rotation can only be 0, 90, 180 or 270')
if abs(self._rotation - rot) == 90 or abs(self._rotation - rot) == 270:
self._width, self._height = self._height, self._width
self._rotation = rot
@property
def use_lm75b(self):
return self._uselm75b
@use_lm75b.setter
def use_lm75b(self, flag):
if flag:
self._uselm75b = True
else:
self._uselm75b = False
def error_status(self):
with open(os.path.join(self._epd_path, 'error'), 'r') as f:
return(f.readline().rstrip('\n'))
def rotation_angle(self, rotation):
angles = { 90 : Image.ROTATE_90, 180 : Image.ROTATE_180, 270 : Image.ROTATE_270 }
return angles[rotation]
def display(self, image):
# attempt grayscale conversion, and then to single bit
# better to do this before calling this if the image is to
# be dispayed several times
if image.mode != "1":
image = ImageOps.grayscale(image).convert("1", dither=Image.FLOYDSTEINBERG)
if image.mode != "1":
raise EPDError('only single bit images are supported')
if image.size != self.size:
raise EPDError('image size mismatch')
if self._rotation != 0:
image = image.transpose(self.rotation_angle(self._rotation))
with open(os.path.join(self._epd_path, 'LE', 'display_inverse'), 'r+b') as f:
f.write(image.tobytes())
if self.auto:
self.update()
def update(self):
self._command('U')
def partial_update(self):
self._command('P')
def fast_update(self):
self._command('F')
def clear(self):
self._command('C')
def _command(self, c):
if self._uselm75b:
with open(os.path.join(self._epd_path, 'temperature'), 'wb') as f:
f.write(b(repr(self._lm75b.getTempC())))
with open(os.path.join(self._epd_path, 'command'), 'wb') as f:
f.write(b(c))

@@ -0,0 +1,46 @@
# Minimal support for LM75b temperature sensor on the Papirus HAT / Papirus Zero
# This module allows you to read the temperature.
# The OS-output (Over-temperature Shutdown) connected to GPIO xx (pin 11) is not supported
# by this module
#
from __future__ import (print_function, division)
import smbus
LM75B_ADDRESS = 0x48
LM75B_TEMP_REGISTER = 0
LM75B_CONF_REGISTER = 1
LM75B_THYST_REGISTER = 2
LM75B_TOS_REGISTER = 3
LM75B_CONF_NORMAL = 0
class LM75B(object):
def __init__(self, address=LM75B_ADDRESS, busnum=1):
self._address = address
self._bus = smbus.SMBus(busnum)
self._bus.write_byte_data(self._address, LM75B_CONF_REGISTER, LM75B_CONF_NORMAL)
def getTempCFloat(self):
"""Return temperature in degrees Celsius as float"""
raw = self._bus.read_word_data(self._address, LM75B_TEMP_REGISTER) & 0xFFFF
raw = ((raw << 8) & 0xFF00) + (raw >> 8)
return (raw / 32.0) / 8.0
def getTempFFloat(self):
"""Return temperature in degrees Fahrenheit as float"""
return (self.getTempCFloat() * (9.0 / 5.0)) + 32.0
def getTempC(self):
"""Return temperature in degrees Celsius as integer, so it can be
used to write to /dev/epd/temperature"""
raw = self._bus.read_word_data(self._address, LM75B_TEMP_REGISTER) & 0xFFFF
raw = ((raw << 8) & 0xFF00) + (raw >> 8)
return (raw + 128) // 256 # round to nearest integer
if __name__ == "__main__":
sens = LM75B()
print(sens.getTempC(), sens.getTempFFloat())

@@ -0,0 +1,135 @@
from . import config
import RPi.GPIO as GPIO
import time
Device_SPI = config.Device_SPI
Device_I2C = config.Device_I2C
LCD_WIDTH = 128 #LCD width
LCD_HEIGHT = 64 #LCD height
class SH1106(object):
def __init__(self):
self.width = LCD_WIDTH
self.height = LCD_HEIGHT
#Initialize DC RST pin
self._dc = config.DC_PIN
self._rst = config.RST_PIN
self._bl = config.BL_PIN
self.Device = config.Device
""" Write register address and data """
def command(self, cmd):
if(self.Device == Device_SPI):
GPIO.output(self._dc, GPIO.LOW)
config.spi_writebyte([cmd])
else:
config.i2c_writebyte(0x00, cmd)
# def data(self, val):
# GPIO.output(self._dc, GPIO.HIGH)
# config.spi_writebyte([val])
def Init(self):
if (config.module_init() != 0):
return -1
"""Initialize dispaly"""
self.reset()
self.command(0xAE);#--turn off oled panel
self.command(0x02);#---set low column address
self.command(0x10);#---set high column address
self.command(0x40);#--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
self.command(0x81);#--set contrast control register
self.command(0xA0);#--Set SEG/Column Mapping
self.command(0xC0);#Set COM/Row Scan Direction
self.command(0xA6);#--set normal display
self.command(0xA8);#--set multiplex ratio(1 to 64)
self.command(0x3F);#--1/64 duty
self.command(0xD3);#-set display offset Shift Mapping RAM Counter (0x00~0x3F)
self.command(0x00);#-not offset
self.command(0xd5);#--set display clock divide ratio/oscillator frequency
self.command(0x80);#--set divide ratio, Set Clock as 100 Frames/Sec
self.command(0xD9);#--set pre-charge period
self.command(0xF1);#Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
self.command(0xDA);#--set com pins hardware configuration
self.command(0x12);
self.command(0xDB);#--set vcomh
self.command(0x40);#Set VCOM Deselect Level
self.command(0x20);#-Set Page Addressing Mode (0x00/0x01/0x02)
self.command(0x02);#
self.command(0xA4);# Disable Entire Display On (0xa4/0xa5)
self.command(0xA6);# Disable Inverse Display On (0xa6/a7)
time.sleep(0.1)
self.command(0xAF);#--turn on oled panel
def reset(self):
"""Reset the display"""
GPIO.output(self._rst,GPIO.HIGH)
time.sleep(0.1)
GPIO.output(self._rst,GPIO.LOW)
time.sleep(0.1)
GPIO.output(self._rst,GPIO.HIGH)
time.sleep(0.1)
def getbuffer(self, image):
# print "bufsiz = ",(self.width/8) * self.height
buf = [0xFF] * ((self.width//8) * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
# print "imwidth = %d, imheight = %d",imwidth,imheight
if(imwidth == self.width and imheight == self.height):
#print ("Vertical")
for y in range(imheight):
for x in range(imwidth):
# Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0:
buf[x + (y // 8) * self.width] &= ~(1 << (y % 8))
# print x,y,x + (y * self.width)/8,buf[(x + y * self.width) / 8]
elif(imwidth == self.height and imheight == self.width):
#print ("Vertical")
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = self.height - x - 1
if pixels[x, y] == 0:
buf[(newx + (newy // 8 )*self.width) ] &= ~(1 << (y % 8))
return buf
# def ShowImage(self,Image):
# self.SetWindows()
# GPIO.output(self._dc, GPIO.HIGH);
# for i in range(0,self.width * self.height/8):
# config.spi_writebyte([~Image[i]])
def ShowImage(self, pBuf):
for page in range(0,8):
# set page address #
self.command(0xB0 + page);
# set low column address #
self.command(0x02);
# set high column address #
self.command(0x10);
# write data #
time.sleep(0.01)
if(self.Device == Device_SPI):
GPIO.output(self._dc, GPIO.HIGH);
for i in range(0,self.width):#for(int i=0;i<self.width; i++)
if(self.Device == Device_SPI):
config.spi_writebyte([~pBuf[i + self.width * page]]);
else :
config.i2c_writebyte(0x40, ~pBuf[i + self.width * page])
def clear(self):
"""Clear contents of image buffer"""
_buffer = [0xff]*(self.width * self.height//8)
self.ShowImage(_buffer)
#print "%d",_buffer[i:i+4096]

@@ -0,0 +1,111 @@
# /*****************************************************************************
# * | File : config.py
# * | Author : Waveshare team
# * | Function : Hardware underlying interface,for Jetson nano
# * | Info :
# *----------------
# * | This version: V1.0
# * | Date : 2019-06-06
# * | Info :
# ******************************************************************************/
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation 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
# furished 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 OR 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 RPi.GPIO as GPIO
import time
from smbus import SMBus
import spidev
import ctypes
# import spidev
# Pin definition
RST_PIN = 25
DC_PIN = 24
CS_PIN = 8
BL_PIN = 18
BUSY_PIN = 18
Device_SPI = 1
Device_I2C = 0
if(Device_SPI == 1):
Device = Device_SPI
spi = spidev.SpiDev(0, 0)
else :
Device = Device_I2C
address = 0x3C
bus = SMBus(1)
def digital_write(pin, value):
GPIO.output(pin, value)
def digital_read(pin):
return GPIO.input(BUSY_PIN)
def delay_ms(delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(data):
# SPI.writebytes(data)
spi.writebytes([data[0]])
def i2c_writebyte(reg, value):
bus.write_byte_data(address, reg, value)
# time.sleep(0.01)
def module_init():
# print("module_init")
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RST_PIN, GPIO.OUT)
GPIO.setup(DC_PIN, GPIO.OUT)
GPIO.setup(CS_PIN, GPIO.OUT)
GPIO.setup(BL_PIN, GPIO.OUT)
# SPI.max_speed_hz = 2000000
# SPI.mode = 0b00
# i2c_writebyte(0xff,0xff)
if(Device == Device_SPI):
# spi.SYSFS_software_spi_begin()
# spi.SYSFS_software_spi_setDataMode(0);
# spi.SYSFS_software_spi_setClockDivider(1);
spi.max_speed_hz = 2000000
spi.mode = 0b00
GPIO.output(CS_PIN, 0)
GPIO.output(BL_PIN, 1)
GPIO.output(DC_PIN, 0)
return 0
def module_exit():
if(Device == Device_SPI):
spi.SYSFS_software_spi_end()
else :
bus.close()
GPIO.output(RST_PIN, 0)
GPIO.output(DC_PIN, 0)
### END OF FILE ###

@@ -0,0 +1,27 @@
from . import SH1106
from . import config
# Display resolution
EPD_WIDTH = 64
EPD_HEIGHT = 128
disp = SH1106.SH1106()
class EPD(object):
def __init__(self):
self.reset_pin = config.RST_PIN
self.dc_pin = config.DC_PIN
self.busy_pin = config.BUSY_PIN
self.cs_pin = config.CS_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
def init(self):
disp.Init()
def Clear(self):
disp.clear()
def display(self, image):
disp.ShowImage(disp.getbuffer(image))

@@ -0,0 +1,224 @@
# *****************************************************************************
# * | File : epd2in13.py
# * | Author : Waveshare team
# * | Function : Electronic paper driver
# * | Info :
# *----------------
# * | This version: V4.0
# * | Date : 2019-06-20
# # | Info : python demo
# -----------------------------------------------------------------------------
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation 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
# furished 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 OR 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 logging
from . import epdconfig
# Display resolution
EPD_WIDTH = 122
EPD_HEIGHT = 250
class EPD:
def __init__(self):
self.reset_pin = epdconfig.RST_PIN
self.dc_pin = epdconfig.DC_PIN
self.busy_pin = epdconfig.BUSY_PIN
self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
lut_full_update = [
0x22, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x11,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00
]
lut_partial_update = [
0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
# Hardware reset
def reset(self):
epdconfig.digital_write(self.cs_pin, 0)
epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(10)
epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200)
epdconfig.digital_write(self.cs_pin, 1)
def send_command(self, command):
epdconfig.digital_write(self.cs_pin, 0)
epdconfig.digital_write(self.dc_pin, 0)
epdconfig.spi_writebyte([command])
epdconfig.digital_write(self.cs_pin, 1)
def send_data(self, data):
epdconfig.digital_write(self.cs_pin, 0)
epdconfig.digital_write(self.dc_pin, 1)
epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self):
while(epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
epdconfig.delay_ms(100)
def TurnOnDisplay(self):
self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
self.send_data(0xC4)
self.send_command(0x20) # MASTER_ACTIVATION
self.send_command(0xFF) # TERMINATE_FRAME_READ_WRITE
logging.debug("e-Paper busy")
self.ReadBusy()
logging.debug("e-Paper busy release")
def init(self, lut):
if (epdconfig.module_init() != 0):
return -1
# EPD hardware init start
self.reset()
self.send_command(0x01) # DRIVER_OUTPUT_CONTROL
self.send_data((EPD_HEIGHT - 1) & 0xFF)
self.send_data(((EPD_HEIGHT - 1) >> 8) & 0xFF)
self.send_data(0x00) # GD = 0 SM = 0 TB = 0
self.send_command(0x0C) # BOOSTER_SOFT_START_CONTROL
self.send_data(0xD7)
self.send_data(0xD6)
self.send_data(0x9D)
self.send_command(0x2C) # WRITE_VCOM_REGISTER
self.send_data(0xA8) # VCOM 7C
self.send_command(0x3A) # SET_DUMMY_LINE_PERIOD
self.send_data(0x1A) # 4 dummy lines per gate
self.send_command(0x3B) # SET_GATE_TIME
self.send_data(0x08) # 2us per line
self.send_command(0X3C) # BORDER_WAVEFORM_CONTROL
self.send_data(0x03)
self.send_command(0X11) # DATA_ENTRY_MODE_SETTING
self.send_data(0x03) # X increment; Y increment
# WRITE_LUT_REGISTER
self.send_command(0x32)
for count in range(30):
self.send_data(lut[count])
return 0
##
# @brief: specify the memory area for data R/W
##
def SetWindows(self, x_start, y_start, x_end, y_end):
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
self.send_data((x_start >> 3) & 0xFF)
self.send_data((x_end >> 3) & 0xFF)
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
self.send_data(y_start & 0xFF)
self.send_data((y_start >> 8) & 0xFF)
self.send_data(y_end & 0xFF)
self.send_data((y_end >> 8) & 0xFF)
##
# @brief: specify the start point for data R/W
##
def SetCursor(self, x, y):
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
# x point must be the multiple of 8 or the last 3 bits will be ignored
self.send_data((x >> 3) & 0xFF)
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
self.send_data(y & 0xFF)
self.send_data((y >> 8) & 0xFF)
self.ReadBusy()
def getbuffer(self, image):
if self.width%8 == 0:
linewidth = int(self.width/8)
else:
linewidth = int(self.width/8) + 1
buf = [0xFF] * (linewidth * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
if(imwidth == self.width and imheight == self.height):
for y in range(imheight):
for x in range(imwidth):
if pixels[x, y] == 0:
# x = imwidth - x
buf[int(x / 8) + y * linewidth] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width):
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = self.height - x - 1
if pixels[x, y] == 0:
# newy = imwidth - newy - 1
buf[int(newx / 8) + newy*linewidth] &= ~(0x80 >> (y % 8))
return buf
def display(self, image):
if self.width%8 == 0:
linewidth = int(self.width/8)
else:
linewidth = int(self.width/8) + 1
self.SetWindows(0, 0, self.width, self.height);
for j in range(0, self.height):
self.SetCursor(0, j);
self.send_command(0x24);
for i in range(0, linewidth):
self.send_data(image[i + j * linewidth])
self.TurnOnDisplay()
def Clear(self, color):
if self.width%8 == 0:
linewidth = int(self.width/8)
else:
linewidth = int(self.width/8) + 1
self.SetWindows(0, 0, self.width, self.height);
for j in range(0, self.height):
self.SetCursor(0, j);
self.send_command(0x24);
for i in range(0, linewidth):
self.send_data(color)
self.TurnOnDisplay()
def sleep(self):
self.send_command(0x10) #enter deep sleep
self.send_data(0x01)
epdconfig.delay_ms(100)
epdconfig.module_exit()
### END OF FILE ###

@@ -0,0 +1,163 @@
# *****************************************************************************
# * | File : epd2in13bc.py
# * | Author : Waveshare team
# * | Function : Electronic paper driver
# * | Info :
# *----------------
# * | This version: V4.0
# * | Date : 2019-06-20
# # | Info : python demo
# -----------------------------------------------------------------------------
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation 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
# furished 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 OR 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.
#
from . import epdconfig
import RPi.GPIO as GPIO
# import numpy as np
# Display resolution
EPD_WIDTH = 104
EPD_HEIGHT = 212
class EPD:
def __init__(self):
self.reset_pin = epdconfig.RST_PIN
self.dc_pin = epdconfig.DC_PIN
self.busy_pin = epdconfig.BUSY_PIN
self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
# Hardware reset
def reset(self):
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
epdconfig.delay_ms(200)
def send_command(self, command):
epdconfig.digital_write(self.dc_pin, GPIO.LOW)
epdconfig.digital_write(self.cs_pin, GPIO.LOW)
epdconfig.spi_writebyte([command])
epdconfig.digital_write(self.cs_pin, GPIO.HIGH)
def send_data(self, data):
epdconfig.digital_write(self.dc_pin, GPIO.HIGH)
epdconfig.digital_write(self.cs_pin, GPIO.LOW)
epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, GPIO.HIGH)
def ReadBusy(self):
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(100)
def init(self):
if (epdconfig.module_init() != 0):
return -1
# EPD hardware init start
self.reset()
self.send_command(0x06) # BOOSTER_SOFT_START
self.send_data(0x17)
self.send_data(0x17)
self.send_data(0x17)
self.send_command(0x04) # POWER_ON
self.ReadBusy()
self.send_command(0x00) # PANEL_SETTING
self.send_data(0x8F)
self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
self.send_data(0xF0)
self.send_command(0x61) # RESOLUTION_SETTING
self.send_data(self.width & 0xff)
self.send_data(self.height >> 8)
self.send_data(self.height & 0xff)
return 0
def getbuffer(self, image):
buf = [0xFF] * (int(self.width/8) * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
if(imwidth == self.width and imheight == self.height):
for y in range(imheight):
for x in range(imwidth):
# Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width):
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = self.height - x - 1
if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf
def displayBlack(self, imageblack):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i])
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
def display(self, imageblack, imagecolor):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i])
self.send_command(0x92)
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imagecolor[i])
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
def Clear(self):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x92)
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
def sleep(self):
self.send_command(0x02) # POWER_OFF
self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP
self.send_data(0xA5) # check code
# epdconfig.module_exit()
### END OF FILE ###

@@ -0,0 +1,154 @@
# /*****************************************************************************
# * | File : epdconfig.py
# * | Author : Waveshare team
# * | Function : Hardware underlying interface
# * | Info :
# *----------------
# * | This version: V1.0
# * | Date : 2019-06-21
# * | Info :
# ******************************************************************************
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation 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
# furished 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 OR 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 os
import logging
import sys
import time
class RaspberryPi:
# Pin definition
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
def __init__(self):
import spidev
import RPi.GPIO
self.GPIO = RPi.GPIO
# SPI device, bus = 0, device = 0
self.SPI = spidev.SpiDev(0, 0)
def digital_write(self, pin, value):
self.GPIO.output(pin, value)
def digital_read(self, pin):
return self.GPIO.input(self.BUSY_PIN)
def delay_ms(self, delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(self, data):
self.SPI.writebytes(data)
def module_init(self):
self.GPIO.setmode(self.GPIO.BCM)
self.GPIO.setwarnings(False)
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
self.SPI.max_speed_hz = 4000000
self.SPI.mode = 0b00
return 0
def module_exit(self):
logging.debug("spi end")
self.SPI.close()
logging.debug("close 5V, Module enters 0 power consumption ...")
self.GPIO.output(self.RST_PIN, 0)
self.GPIO.output(self.DC_PIN, 0)
self.GPIO.cleanup()
class JetsonNano:
# Pin definition
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
def __init__(self):
import ctypes
find_dirs = [
os.path.dirname(os.path.realpath(__file__)),
'/usr/local/lib',
'/usr/lib',
]
self.SPI = None
for find_dir in find_dirs:
so_filename = os.path.join(find_dir, 'sysfs_software_spi.so')
if os.path.exists(so_filename):
self.SPI = ctypes.cdll.LoadLibrary(so_filename)
break
if self.SPI is None:
raise RuntimeError('Cannot find sysfs_software_spi.so')
import Jetson.GPIO
self.GPIO = Jetson.GPIO
def digital_write(self, pin, value):
self.GPIO.output(pin, value)
def digital_read(self, pin):
return self.GPIO.input(self.BUSY_PIN)
def delay_ms(self, delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(self, data):
self.SPI.SYSFS_software_spi_transfer(data[0])
def module_init(self):
self.GPIO.setmode(self.GPIO.BCM)
self.GPIO.setwarnings(False)
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
self.SPI.SYSFS_software_spi_begin()
return 0
def module_exit(self):
logging.debug("spi end")
self.SPI.SYSFS_software_spi_end()
logging.debug("close 5V, Module enters 0 power consumption ...")
self.GPIO.output(self.RST_PIN, 0)
self.GPIO.output(self.DC_PIN, 0)
self.GPIO.cleanup()
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
implementation = RaspberryPi()
else:
implementation = JetsonNano()
for func in [x for x in dir(implementation) if not x.startswith('_')]:
setattr(sys.modules[__name__], func, getattr(implementation, func))
### END OF FILE ###

@@ -0,0 +1,338 @@
# //*****************************************************************************
# * | File : epd2in13.py
# * | Author : Waveshare team
# * | Function : Electronic paper driver
# * | Info :
# *----------------
# * | This version: V3.0
# * | Date : 2018-11-01
# * | Info : python2 demo
# * 1.Remove:
# digital_write(self, pin, value)
# digital_read(self, pin)
# delay_ms(self, delaytime)
# set_lut(self, lut)
# self.lut = self.lut_full_update
# * 2.Change:
# display_frame -> TurnOnDisplay
# set_memory_area -> SetWindow
# set_memory_pointer -> SetCursor
# * 3.How to use
# epd = epd2in13.EPD()
# epd.init(epd.lut_full_update)
# image = Image.new('1', (epd2in13.EPD_WIDTH, epd2in13.EPD_HEIGHT), 255)
# ...
# drawing ......
# ...
# epd.display(getbuffer(image))
# ******************************************************************************//
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation 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
# furished 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 OR 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
import spidev
import RPi.GPIO as GPIO
from PIL import Image
# Pin definition
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
# SPI device, bus = 0, device = 0
SPI = spidev.SpiDev(0, 0)
def digital_write(pin, value):
GPIO.output(pin, value)
def digital_read(pin):
return GPIO.input(BUSY_PIN)
def delay_ms(delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(data):
SPI.writebytes(data)
def module_init():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RST_PIN, GPIO.OUT)
GPIO.setup(DC_PIN, GPIO.OUT)
GPIO.setup(CS_PIN, GPIO.OUT)
GPIO.setup(BUSY_PIN, GPIO.IN)
SPI.max_speed_hz = 2000000
SPI.mode = 0b00
return 0;
# Display resolution
EPD_WIDTH = 122
EPD_HEIGHT = 250
class EPD:
def __init__(self):
self.reset_pin = RST_PIN
self.dc_pin = DC_PIN
self.busy_pin = BUSY_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
FULL_UPDATE = 0
PART_UPDATE = 1
lut_full_update = [
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, # LUT0: BB: VS 0 ~7
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, # LUT1: BW: VS 0 ~7
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, # LUT2: WB: VS 0 ~7
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, # LUT3: WW: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT4: VCOM: VS 0 ~7
0x03, 0x03, 0x00, 0x00, 0x02, # TP0 A~D RP0
0x09, 0x09, 0x00, 0x00, 0x02, # TP1 A~D RP1
0x03, 0x03, 0x00, 0x00, 0x02, # TP2 A~D RP2
0x00, 0x00, 0x00, 0x00, 0x00, # TP3 A~D RP3
0x00, 0x00, 0x00, 0x00, 0x00, # TP4 A~D RP4
0x00, 0x00, 0x00, 0x00, 0x00, # TP5 A~D RP5
0x00, 0x00, 0x00, 0x00, 0x00, # TP6 A~D RP6
0x15, 0x41, 0xA8, 0x32, 0x30, 0x0A,
]
lut_partial_update = [ # 20 bytes
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT0: BB: VS 0 ~7
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT1: BW: VS 0 ~7
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT2: WB: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT3: WW: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT4: VCOM: VS 0 ~7
0x0A, 0x00, 0x00, 0x00, 0x00, # TP0 A~D RP0
0x00, 0x00, 0x00, 0x00, 0x00, # TP1 A~D RP1
0x00, 0x00, 0x00, 0x00, 0x00, # TP2 A~D RP2
0x00, 0x00, 0x00, 0x00, 0x00, # TP3 A~D RP3
0x00, 0x00, 0x00, 0x00, 0x00, # TP4 A~D RP4
0x00, 0x00, 0x00, 0x00, 0x00, # TP5 A~D RP5
0x00, 0x00, 0x00, 0x00, 0x00, # TP6 A~D RP6
0x15, 0x41, 0xA8, 0x32, 0x30, 0x0A,
]
# Hardware reset
def reset(self):
digital_write(self.reset_pin, GPIO.HIGH)
delay_ms(200)
digital_write(self.reset_pin, GPIO.LOW) # module reset
delay_ms(200)
digital_write(self.reset_pin, GPIO.HIGH)
delay_ms(200)
def send_command(self, command):
digital_write(self.dc_pin, GPIO.LOW)
spi_writebyte([command])
def send_data(self, data):
digital_write(self.dc_pin, GPIO.HIGH)
spi_writebyte([data])
def wait_until_idle(self):
while (digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
delay_ms(100)
def TurnOnDisplay(self):
self.send_command(0x22)
self.send_data(0xC7)
self.send_command(0x20)
self.wait_until_idle()
def init(self, update):
if (module_init() != 0):
return -1
# EPD hardware init start
self.reset()
if (update == self.FULL_UPDATE):
self.wait_until_idle()
self.send_command(0x12) # soft reset
self.wait_until_idle()
self.send_command(0x74) # set analog block control
self.send_data(0x54)
self.send_command(0x7E) # set digital block control
self.send_data(0x3B)
self.send_command(0x01) # Driver output control
self.send_data(0xF9)
self.send_data(0x00)
self.send_data(0x00)
self.send_command(0x11) # data entry mode
self.send_data(0x01)
self.send_command(0x44) # set Ram-X address start//end position
self.send_data(0x00)
self.send_data(0x0F) # 0x0C-->(15+1)*8=128
self.send_command(0x45) # set Ram-Y address start//end position
self.send_data(0xF9) # 0xF9-->(249+1)=250
self.send_data(0x00)
self.send_data(0x00)
self.send_data(0x00)
self.send_command(0x3C) # BorderWavefrom
self.send_data(0x03)
self.send_command(0x2C) # VCOM Voltage
self.send_data(0x55) #
self.send_command(0x03)
self.send_data(self.lut_full_update[70])
self.send_command(0x04) #
self.send_data(self.lut_full_update[71])
self.send_data(self.lut_full_update[72])
self.send_data(self.lut_full_update[73])
self.send_command(0x3A) # Dummy Line
self.send_data(self.lut_full_update[74])
self.send_command(0x3B) # Gate time
self.send_data(self.lut_full_update[75])
self.send_command(0x32)
for count in range(70):
self.send_data(self.lut_full_update[count])
self.send_command(0x4E) # set RAM x address count to 0
self.send_data(0x00)
self.send_command(0x4F) # set RAM y address count to 0X127
self.send_data(0xF9)
self.send_data(0x00)
self.wait_until_idle()
else:
self.send_command(0x2C) # VCOM Voltage
self.send_data(0x26)
self.wait_until_idle()
self.send_command(0x32)
for count in range(70):
self.send_data(self.lut_partial_update[count])
self.send_command(0x37)
self.send_data(0x00)
self.send_data(0x00)
self.send_data(0x00)
self.send_data(0x00)
self.send_data(0x40)
self.send_data(0x00)
self.send_data(0x00)
self.send_command(0x22)
self.send_data(0xC0)
self.send_command(0x20)
self.wait_until_idle()
self.send_command(0x3C) # BorderWavefrom
self.send_data(0x01)
return 0
def getbuffer(self, image):
if self.width % 8 == 0:
linewidth = self.width // 8
else:
linewidth = self.width // 8 + 1
buf = [0xFF] * (linewidth * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
if (imwidth == self.width and imheight == self.height):
# print("Vertical")
for y in range(imheight):
for x in range(imwidth):
if pixels[x, y] == 0:
x = imwidth - x
buf[x // 8 + y * linewidth] &= ~(0x80 >> (x % 8))
elif (imwidth == self.height and imheight == self.width):
# print("Horizontal")
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = self.height - x - 1
if pixels[x, y] == 0:
newy = imwidth - newy - 1
buf[newx // 8 + newy * linewidth] &= ~(0x80 >> (y % 8))
return buf
def display(self, image):
if self.width % 8 == 0:
linewidth = self.width // 8
else:
linewidth = self.width // 8 + 1
self.send_command(0x24)
for j in range(0, self.height):
for i in range(0, linewidth):
self.send_data(image[i + j * linewidth])
self.TurnOnDisplay()
def displayPartial(self, image):
if self.width % 8 == 0:
linewidth = self.width // 8
else:
linewidth = self.width // 8 + 1
self.send_command(0x24)
for j in range(0, self.height):
for i in range(0, linewidth):
self.send_data(image[i + j * linewidth])
self.send_command(0x26)
for j in range(0, self.height):
for i in range(0, linewidth):
self.send_data(~image[i + j * linewidth])
self.TurnOnDisplay()
def Clear(self, color):
if self.width % 8 == 0:
linewidth = self.width // 8
else:
linewidth = self.width // 8 + 1
# print(linewidth)
self.send_command(0x24)
for j in range(0, self.height):
for i in range(0, linewidth):
self.send_data(color)
self.TurnOnDisplay()
def sleep(self):
self.send_command(0x22) # POWER OFF
self.send_data(0xC3)
self.send_command(0x20)
self.send_command(0x10) # enter deep sleep
self.send_data(0x01)
delay_ms(100)
### END OF FILE ###