From 0ea67cb97faaa141c5acd5ef43a99550bc803c9a Mon Sep 17 00:00:00 2001 From: root Date: Sat, 19 Oct 2019 04:49:38 +0100 Subject: [PATCH] Adding support for Waveshare LCD Hat (ST7789 chip) --- pwnagotchi/ui/display.py | 4 + pwnagotchi/ui/hw/__init__.py | 5 + pwnagotchi/ui/hw/lcdhat.py | 46 +++++ .../ui/hw/libs/waveshare/lcdhat/ST7789.py | 165 ++++++++++++++++++ .../ui/hw/libs/waveshare/lcdhat/ST7789.pyc | Bin 0 -> 5124 bytes .../ui/hw/libs/waveshare/lcdhat/__init__.py | 0 .../ui/hw/libs/waveshare/lcdhat/config.py | 76 ++++++++ pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py | 28 +++ 8 files changed, 324 insertions(+) create mode 100644 pwnagotchi/ui/hw/lcdhat.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.pyc create mode 100644 pwnagotchi/ui/hw/libs/waveshare/lcdhat/__init__.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py create mode 100644 pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py index 597691f2..479324be 100644 --- a/pwnagotchi/ui/display.py +++ b/pwnagotchi/ui/display.py @@ -123,6 +123,10 @@ class Display(View): def is_oledhat(self): return self._implementation.name == 'oledhat' + def is_lcdhat(self): + return self._implementation.name == 'lcdhat' + + def is_waveshare_any(self): return self.is_waveshare_v1() or self.is_waveshare_v2() diff --git a/pwnagotchi/ui/hw/__init__.py b/pwnagotchi/ui/hw/__init__.py index 94dc671b..ee00a73e 100644 --- a/pwnagotchi/ui/hw/__init__.py +++ b/pwnagotchi/ui/hw/__init__.py @@ -1,6 +1,7 @@ from pwnagotchi.ui.hw.inky import Inky from pwnagotchi.ui.hw.papirus import Papirus from pwnagotchi.ui.hw.oledhat import OledHat +from pwnagotchi.ui.hw.lcdhat import LcdHat from pwnagotchi.ui.hw.waveshare1 import WaveshareV1 from pwnagotchi.ui.hw.waveshare2 import WaveshareV2 @@ -16,6 +17,10 @@ def display_for(config): if config['ui']['display']['type'] == 'oledhat': return OledHat(config) + if config['ui']['display']['type'] == 'lcdhat': + return LcdHat(config) + + elif config['ui']['display']['type'] == 'waveshare_1': return WaveshareV1(config) diff --git a/pwnagotchi/ui/hw/lcdhat.py b/pwnagotchi/ui/hw/lcdhat.py new file mode 100644 index 00000000..75779a33 --- /dev/null +++ b/pwnagotchi/ui/hw/lcdhat.py @@ -0,0 +1,46 @@ +import logging + +import pwnagotchi.ui.fonts as fonts +from pwnagotchi.ui.hw.base import DisplayImpl + + +class LcdHat(DisplayImpl): + def __init__(self, config): + super(LcdHat, self).__init__(config, 'lcdhat') + self._display = None + + def layout(self): + fonts.setup(10, 9, 10, 35) + self._layout['width'] = 240 + self._layout['height'] = 240 + self._layout['face'] = (0, 40) + self._layout['name'] = (5, 20) + self._layout['channel'] = (0, 0) + self._layout['aps'] = (28, 0) + self._layout['uptime'] = (175, 0) + self._layout['line1'] = [0, 14, 240, 14] + self._layout['line2'] = [0, 108, 240, 108] + self._layout['friend_face'] = (0, 92) + self._layout['friend_name'] = (40, 94) + self._layout['shakes'] = (0, 109) + self._layout['mode'] = (215, 109) + self._layout['status'] = { + 'pos': (125, 20), + 'font': fonts.Medium, + 'max': 20 + } + + return self._layout + + def initialize(self): + logging.info("initializing lcdhat display") + from pwnagotchi.ui.hw.libs.waveshare.lcdhat.epd import EPD + self._display = EPD() + self._display.init() + self._display.Clear() + + def render(self, canvas): + self._display.display(canvas) + + def clear(self): + self._display.clear() diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py new file mode 100644 index 00000000..0edd1d12 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py @@ -0,0 +1,165 @@ +import spidev +import RPi.GPIO as GPIO +import time +import numpy as np + + +class ST7789(object): + """class for ST7789 240*240 1.3inch OLED displays.""" + + def __init__(self,spi,rst = 27,dc = 25,bl = 24): + self.width = 240 + self.height = 240 + #Initialize DC RST pin + self._dc = dc + self._rst = rst + self._bl = bl + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + GPIO.setup(self._dc,GPIO.OUT) + GPIO.setup(self._rst,GPIO.OUT) + GPIO.setup(self._bl,GPIO.OUT) + GPIO.output(self._bl, GPIO.HIGH) + #Initialize SPI + self._spi = spi + self._spi.max_speed_hz = 40000000 + + """ Write register address and data """ + def command(self, cmd): + GPIO.output(self._dc, GPIO.LOW) + self._spi.writebytes([cmd]) + + def data(self, val): + GPIO.output(self._dc, GPIO.HIGH) + self._spi.writebytes([val]) + + def Init(self): + """Initialize dispaly""" + self.reset() + + self.command(0x36) + self.data(0x70) #self.data(0x00) + + self.command(0x3A) + self.data(0x05) + + self.command(0xB2) + self.data(0x0C) + self.data(0x0C) + self.data(0x00) + self.data(0x33) + self.data(0x33) + + self.command(0xB7) + self.data(0x35) + + self.command(0xBB) + self.data(0x19) + + self.command(0xC0) + self.data(0x2C) + + self.command(0xC2) + self.data(0x01) + + self.command(0xC3) + self.data(0x12) + + self.command(0xC4) + self.data(0x20) + + self.command(0xC6) + self.data(0x0F) + + self.command(0xD0) + self.data(0xA4) + self.data(0xA1) + + self.command(0xE0) + self.data(0xD0) + self.data(0x04) + self.data(0x0D) + self.data(0x11) + self.data(0x13) + self.data(0x2B) + self.data(0x3F) + self.data(0x54) + self.data(0x4C) + self.data(0x18) + self.data(0x0D) + self.data(0x0B) + self.data(0x1F) + self.data(0x23) + + self.command(0xE1) + self.data(0xD0) + self.data(0x04) + self.data(0x0C) + self.data(0x11) + self.data(0x13) + self.data(0x2C) + self.data(0x3F) + self.data(0x44) + self.data(0x51) + self.data(0x2F) + self.data(0x1F) + self.data(0x1F) + self.data(0x20) + self.data(0x23) + + self.command(0x21) + + self.command(0x11) + + self.command(0x29) + + def reset(self): + """Reset the display""" + GPIO.output(self._rst,GPIO.HIGH) + time.sleep(0.01) + GPIO.output(self._rst,GPIO.LOW) + time.sleep(0.01) + GPIO.output(self._rst,GPIO.HIGH) + time.sleep(0.01) + + def SetWindows(self, Xstart, Ystart, Xend, Yend): + #set the X coordinates + self.command(0x2A) + self.data(0x00) #Set the horizontal starting point to the high octet + self.data(Xstart & 0xff) #Set the horizontal starting point to the low octet + self.data(0x00) #Set the horizontal end to the high octet + self.data((Xend - 1) & 0xff) #Set the horizontal end to the low octet + + #set the Y coordinates + self.command(0x2B) + self.data(0x00) + self.data((Ystart & 0xff)) + self.data(0x00) + self.data((Yend - 1) & 0xff ) + + self.command(0x2C) + + def ShowImage(self,Image,Xstart,Ystart): + """Set buffer to value of Python Imaging Library image.""" + """Write display buffer to physical display""" + imwidth, imheight = Image.size + if imwidth != self.width or imheight != self.height: + raise ValueError('Image must be same dimensions as display \ + ({0}x{1}).' .format(self.width, self.height)) + img = np.asarray(Image) + pix = np.zeros((self.width,self.height,2), dtype = np.uint8) + pix[...,[0]] = np.add(np.bitwise_and(img[...,[0]],0xF8),np.right_shift(img[...,[1]],5)) + pix[...,[1]] = np.add(np.bitwise_and(np.left_shift(img[...,[1]],3),0xE0),np.right_shift(img[...,[2]],3)) + pix = pix.flatten().tolist() + self.SetWindows ( 0, 0, self.width, self.height) + GPIO.output(self._dc,GPIO.HIGH) + for i in range(0,len(pix),4096): + self._spi.writebytes(pix[i:i+4096]) + + def clear(self): + """Clear contents of image buffer""" + _buffer = [0xff]*(self.width * self.height * 2) + self.SetWindows ( 0, 0, self.width, self.height) + GPIO.output(self._dc,GPIO.HIGH) + for i in range(0,len(_buffer),4096): + self._spi.writebytes(_buffer[i:i+4096]) diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.pyc b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1837ca2a4eec4c9252d12c8a890cadbc766433c7 GIT binary patch literal 5124 zcmcIoOK%*<5$>5?K9(Xyk*27prJ*13O14Qkc49jg{GuF508Pn5C9;SJgW=9Bx0&79 z&GgU|De@BO9RGwMfKLhFgD(z{OOOOU`zP4{AV4m;Ip0^kGdrYB9m0gmuC40ou71?B zxAL!vv5hzXc)y|ge-(Vci)KCqi16>Iwoo6J$*JEA??MbOoT%4vq1e-PE(&@w^|u zcI7gf_saaMI%%}LTT35Y^CE3}?Qq-7>vNc-&k&sE^dSH?UfUv?`3nGcysH$>(MLd5 zq{mge_y-GQRgkL6hSbKez!CMxQ4Z-*mDLJjj017$WE@kETsy?}R(`P83LT6c7YVj^ zLL}_k9UkY+Qu=WfO!B%{>O9D@{wJVTt7~rk1Td_u)RvC2mME?j>-AQaabyrRGAb@e zO}}HUEgjcy-@KKLfMepU(~V*o{r=*I*)iL<6{d+!){Th%dDzAwrpt znFqGe>UQFVo?htdJXl)17TkF6PSC*47kb-StD7uX`Plr2jrcc5n>XK)O!YuUgJvb1rn~fJJ8as=-rk_YabBG}*S^bbecydKqeGZff zPwsQ71%Ww31%WxkhY~gv^aYHThYbiwvq7RiY^)dt4xE3mgYkj@2LWKfIbIOpOcn(8 zcA_A_nJNfyrV9d`rwRg`nSuc4WI=#4TM*!!DhO~+mx(h4fnjG00>k#rIalVKFB4Cf zi3?@oVwv#D#9Wzprc69rCiboKT$%HHnfOsb;0P}i#1oIO&khbh!a!M&w(kh_@;aBw z#N{%vZ=?H&f#x83-w|Fc1mHY_y@7a1IMK^U8VsUl8sH{^q7K`7Czg~Mwzu_9(XD#~ zKOc10kE&UGykKii;MnL^F!Dj@2 zAozxWKCT%tHDjTEir__pcL?qfEDFW^Zwamud_=H7Fh_6+U|F)IB(5|@ z>hhTg{PO^HZdF3>(Lye_&O3b$-)09uxnu5xGm$qW8u{e(Vc&K}KKlFLoOk%g#d>ao zM`A-Lv_Y{0)-A?erLJPG*!xyyh2GLQX6Et)2}qzaoqDyf%!kumH#wk;jGHgu$4QD`?T%*MdF_@ zfKgj2#5^3srOHVJ6##19cMs!AIefZ20>TE_<)ARQ^%I1llCG+Q!faHESQPR~m-S^p z>#*Muqb*oJqKDeVJa7Q|xvf1|uzYN?FwLZ(yJu0V<>NSsL@W1z9L-T!`K-j*U7bYT zEwcy8%mOGe>?x??WQ7JUnA_muH8gH*5zYJ=hrxwIA-{6fURCX(0+RwAdH&ZGR&2ZI zkxUOVU1r?{^G2=4&b*F;T|-^JbyNaNkg^^!(X*G=_vOJ*b`wHu1<$PR?2X8*nn2X| zc+EgDLo5WFt~$eVA%6w*c_Rf603}%!;4PKSaCml- zo5yXjcB!aq_vh5!s0^P~8>h(I1&R0EF?DK=Uh$hxox9OEtdpVLyaoUR@YXh)%{cY4 zu7}!iGxoYo@3s{1-px+9jwgq=q}S3g-S#x-c-~O|WW`=*(_nt=nXtpS(uotJyNU5a zlfVDGgMamhSN0w}d}Z&_yk@N^sUXU>d-1$=lz*esI*R1I3dwH>6y8vexBo$!c2jX1JW)HLea0j`d0B-fOw({%*4&BHu6;{w>LhzZUIk$k$!lv( zXIt9D0m^ovQ@-8;)6&gM9t`ccnUnPMW*f`Ii7c9R+uCIQ^W50}cZZFrJX9Jc>-Krq zj+0WMPXDq2fTQGw{We2bn~fqs0d9WTn;Jl0Ezm zgnh78CY=jT1#QxK!THF!?@T-A5wR~i4X#O572)Ai5O8-mA`RTeZb!|)g=c2=avO&$ zLB+Piw2)@``4aajJiGhX=*2a{~{ literal 0 HcmV?d00001 diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/__init__.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py new file mode 100644 index 00000000..a319364f --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py @@ -0,0 +1,76 @@ +# /***************************************************************************** +# * | File : config.py +# * | Author : Guillaume Giraudon +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2019-10-18 +# * | Info : +# ******************************************************************************/ + +import RPi.GPIO as GPIO +import time +from smbus import SMBus +import spidev + +import ctypes +# import spidev + +# Pin definition +RST_PIN = 27 +DC_PIN = 25 +BL_PIN = 24 + +Device_SPI = 1 +Device_I2C = 0 + +Device = Device_SPI +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) + 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) + + + # SPI.max_speed_hz = 2000000 + # SPI.mode = 0b00 + # i2c_writebyte(0xff,0xff) + # 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(BL_PIN, 1) + GPIO.output(DC_PIN, 0) + return 0 + +def module_exit(): + spi.SYSFS_software_spi_end() + GPIO.output(RST_PIN, 0) + GPIO.output(DC_PIN, 0) + + + +### END OF FILE ### diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py new file mode 100644 index 00000000..cd0d81f4 --- /dev/null +++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py @@ -0,0 +1,28 @@ +from . import ST7789 +from . import config + +# Display resolution +EPD_WIDTH = 240 +EPD_HEIGHT = 240 + +disp = ST7789.ST7789(config.spi,config.RST_PIN, config.DC_PIN, config.BL_PIN) + +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): + rgb_im = image.convert('RGB') + disp.ShowImage(rgb_im,0,0)