From 3c721ebfbbe3d8b913291660b51a7a193467df1c Mon Sep 17 00:00:00 2001
From: Marius Andra <marius.andra@gmail.com>
Date: Thu, 30 Jan 2025 00:16:36 +0100
Subject: [PATCH] it8951

---
 backend/app/drivers/devices.py                |   5 +
 backend/app/drivers/waveshare.py              |  16 +-
 backend/app/tasks/deploy_frame.py             |  11 +-
 backend/list_devices.py                       |   1 +
 .../src/drivers/waveshare/it8951/DEV_Config.c | 186 ++++
 .../src/drivers/waveshare/it8951/DEV_Config.h | 104 ++
 .../drivers/waveshare/it8951/DEV_Config.nim   |  68 ++
 .../drivers/waveshare/it8951/EPD_10in3.nim    |  84 ++
 frameos/src/drivers/waveshare/it8951/IT8951.c | 921 ++++++++++++++++++
 frameos/src/drivers/waveshare/it8951/IT8951.h | 209 ++++
 .../src/drivers/waveshare/it8951/IT8951.nim   | 188 ++++
 frameos/src/drivers/waveshare/it8951/Makefile |  25 +
 .../waveshare/it8951/examples/example.c       | 893 +++++++++++++++++
 .../waveshare/it8951/examples/example.h       |  44 +
 .../drivers/waveshare/it8951/examples/main.c  | 192 ++++
 frameos/src/drivers/waveshare/types.nim       |   1 +
 frameos/src/drivers/waveshare/waveshare.nim   |  33 +-
 frontend/src/devices.ts                       |   1 +
 18 files changed, 2978 insertions(+), 4 deletions(-)
 create mode 100644 frameos/src/drivers/waveshare/it8951/DEV_Config.c
 create mode 100644 frameos/src/drivers/waveshare/it8951/DEV_Config.h
 create mode 100644 frameos/src/drivers/waveshare/it8951/DEV_Config.nim
 create mode 100644 frameos/src/drivers/waveshare/it8951/EPD_10in3.nim
 create mode 100644 frameos/src/drivers/waveshare/it8951/IT8951.c
 create mode 100644 frameos/src/drivers/waveshare/it8951/IT8951.h
 create mode 100644 frameos/src/drivers/waveshare/it8951/IT8951.nim
 create mode 100644 frameos/src/drivers/waveshare/it8951/Makefile
 create mode 100644 frameos/src/drivers/waveshare/it8951/examples/example.c
 create mode 100644 frameos/src/drivers/waveshare/it8951/examples/example.h
 create mode 100644 frameos/src/drivers/waveshare/it8951/examples/main.c

diff --git a/backend/app/drivers/devices.py b/backend/app/drivers/devices.py
index 45811398..4f231cda 100644
--- a/backend/app/drivers/devices.py
+++ b/backend/app/drivers/devices.py
@@ -31,6 +31,11 @@ def drivers_for_device(device: str) -> dict[str, Driver]:
         else:
             device_drivers = {"waveshare": waveshare, "spi": DRIVERS["spi"]}
 
+        if waveshare.variant == "EPD_10in3":
+            device_drivers["bootconfig"] = DRIVERS["bootConfig"]
+            device_drivers["bootconfig"].lines = [
+                "dtoverlay=spi0-0cs",
+            ]
         if waveshare.variant == "EPD_13in3e":
             device_drivers["bootconfig"] = DRIVERS["bootConfig"]
             device_drivers["bootconfig"].lines = [
diff --git a/backend/app/drivers/waveshare.py b/backend/app/drivers/waveshare.py
index 768c695e..a9cdb950 100644
--- a/backend/app/drivers/waveshare.py
+++ b/backend/app/drivers/waveshare.py
@@ -66,6 +66,8 @@ class WaveshareVariant:
     "EPD_4in0e": "SpectraSixColor",
     "EPD_7in3e": "SpectraSixColor",
     "EPD_13in3e": "SpectraSixColor",
+
+    "EPD_10in3": "SixteenGray",
 }
 
 def get_variant_keys_for(folder: str) -> list[str]:
@@ -77,13 +79,20 @@ def get_variant_keys_for(folder: str) -> list[str]:
     ]
 
 def get_variant_keys() -> list[str]:
-    return [*get_variant_keys_for("ePaper"), *get_variant_keys_for("epd12in48"), *get_variant_keys_for("epd13in3e")]
+    return [
+        *get_variant_keys_for("ePaper"),
+        *get_variant_keys_for("it8951"),
+        *get_variant_keys_for("epd12in48"),
+        *get_variant_keys_for("epd13in3e"),
+    ]
 
 def get_variant_folder(variant_key: str) -> str:
     if variant_key in get_variant_keys_for("ePaper") :
         return "ePaper"
     elif variant_key == "EPD_13in3e":
         return "epd13in3e"
+    elif variant_key == "EPD_10in3":
+        return "it8951"
     else:
         return "epd12in48"
 
@@ -181,6 +190,9 @@ def convert_waveshare_source(variant_key: Optional[str]) -> WaveshareVariant:
                     variant.display_function = proc_name
                     variant.display_arguments = get_proc_arguments(line, variant_key)
                     # print("-> " + proc_name + "(" + (", ".join(variant.display_arguments)) + ") <-")
+                if (proc_name.lower() == f"{variant.prefix}_16Gray_Display".lower()):
+                    variant.display_function = proc_name
+                    variant.display_arguments = get_proc_arguments(line, variant_key)
 
         if variant.display_arguments == ["Black"]:
             variant.color_option = "Black"
@@ -197,6 +209,8 @@ def convert_waveshare_source(variant_key: Optional[str]) -> WaveshareVariant:
             variant.color_option = "SevenColor"
         elif variant.display_arguments == ["SpectraSixColor"]:
             variant.color_option = "SpectraSixColor"
+        elif variant_key == "EPD_10in3":
+            variant.color_option = "SixteenGray"
         else:
             print(f"Unknown color: {variant_key} - {variant.display_function} -- {variant.display_arguments}" )
 
diff --git a/backend/app/tasks/deploy_frame.py b/backend/app/tasks/deploy_frame.py
index ef61967c..d1e1f9e9 100644
--- a/backend/app/tasks/deploy_frame.py
+++ b/backend/app/tasks/deploy_frame.py
@@ -102,7 +102,7 @@ async def install_if_necessary(pkg: str, raise_on_error=True) -> int:
             )
 
             if low_memory:
-                await log(db, redis, id, "stdout", "- Low memory device, stopping FrameOS for compile")
+                await log(db, redis, id, "stdout", "- Low memory device, stopping FrameOS for compilation")
                 await exec_command(db, redis, frame, ssh, "sudo service frameos stop", raise_on_error=False)
 
             # 2. Remote steps
@@ -486,7 +486,12 @@ async def create_local_build_archive(
     if waveshare := drivers.get('waveshare'):
         if waveshare.variant:
             variant_folder = get_variant_folder(waveshare.variant)
-            util_files = ["Debug.h", "DEV_Config.c", "DEV_Config.h"]
+
+            if variant_folder == "it8951":
+                util_files = ["DEV_Config.c", "DEV_Config.h"]
+            else:
+                util_files = ["Debug.h", "DEV_Config.c", "DEV_Config.h"]
+
             for uf in util_files:
                 shutil.copy(
                     os.path.join(source_dir, "src", "drivers", "waveshare", variant_folder, uf),
@@ -501,6 +506,8 @@ async def create_local_build_archive(
             ]:
                 c_file = re.sub(r'[bc]', 'bc', waveshare.variant)
                 variant_files = [f"{waveshare.variant}.nim", f"{c_file}.c", f"{c_file}.h"]
+            elif waveshare.variant == "EPD_10in3":
+                variant_files = [f"{waveshare.variant}.nim", "IT8951.c", "IT8951.h", "IT8951.nim"]
             else:
                 variant_files = [f"{waveshare.variant}.nim", f"{waveshare.variant}.c", f"{waveshare.variant}.h"]
 
diff --git a/backend/list_devices.py b/backend/list_devices.py
index db35ea82..de2942da 100644
--- a/backend/list_devices.py
+++ b/backend/list_devices.py
@@ -25,6 +25,7 @@
             "BlackWhiteYellow": "Black/White/Yellow",
             "BlackWhiteYellowRed": "Black/White/Yellow/Red",
             "FourGray": "4 Grayscale",
+            "SixteenGray": "16 Grayscale",
             "SevenColor": "7 Color",
             "SpectraSixColor": "Spectra 6 Color",
         }.get(v.color_option, v.color_option)
diff --git a/frameos/src/drivers/waveshare/it8951/DEV_Config.c b/frameos/src/drivers/waveshare/it8951/DEV_Config.c
new file mode 100644
index 00000000..7d6210d6
--- /dev/null
+++ b/frameos/src/drivers/waveshare/it8951/DEV_Config.c
@@ -0,0 +1,186 @@
+/*****************************************************************************
+* | File      	:   DEV_Config.c
+* | Author      :   Waveshare team
+* | Function    :   Hardware underlying interface
+* | Info        :
+*----------------
+* |	This version:   V3.0
+* | Date        :   2019-09-17
+* | 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 theex 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.
+#
+******************************************************************************/
+#include "DEV_Config.h"
+#include <fcntl.h>
+
+int GPIO_Handle;
+int SPI_Handle;
+
+/******************************************************************************
+function:	GPIO Write
+parameter:
+Info:
+******************************************************************************/
+void DEV_Digital_Write(UWORD Pin, UBYTE Value)
+{
+    lgGpioWrite(GPIO_Handle, Pin, Value);
+}
+
+/******************************************************************************
+function:	GPIO Read
+parameter:
+Info:
+******************************************************************************/
+UBYTE DEV_Digital_Read(UWORD Pin)
+{
+	UBYTE Read_Value = 0;
+    Read_Value = lgGpioRead(GPIO_Handle,Pin);
+	return Read_Value;
+}
+
+/******************************************************************************
+function:	SPI Write
+parameter:
+Info:
+******************************************************************************/
+void DEV_SPI_WriteByte(UBYTE Value)
+{
+    lgSpiWrite(SPI_Handle,(char*)&Value, 1);
+}
+
+/******************************************************************************
+function:	SPI Read
+parameter:
+Info:
+******************************************************************************/
+UBYTE DEV_SPI_ReadByte()
+{
+	UBYTE Read_Value = 0x00;
+    lgSpiRead(SPI_Handle, (char*)&Read_Value, 1);
+	return Read_Value;
+}
+
+/******************************************************************************
+function:	Time delay for ms
+parameter:
+Info:
+******************************************************************************/
+void DEV_Delay_ms(UDOUBLE xms)
+{
+    lguSleep(xms/1000.0);
+}
+
+
+/******************************************************************************
+function:	Time delay for us
+parameter:
+Info:
+******************************************************************************/
+void DEV_Delay_us(UDOUBLE xus)
+{
+    lguSleep(xus/1000000.0);
+}
+
+
+/**
+ * GPIO Mode
+**/
+static void DEV_GPIO_Mode(UWORD Pin, UWORD Mode)
+{
+    if(Mode == 0 || Mode == LG_SET_INPUT){
+        lgGpioClaimInput(GPIO_Handle,LFLAGS,Pin);
+        // Debug("IN Pin = %d\r\n",Pin);
+    }else{
+        lgGpioClaimOutput(GPIO_Handle, LFLAGS, Pin, LG_LOW);
+        // Debug("OUT Pin = %d\r\n",Pin);
+    }
+}
+
+
+/**
+ * GPIO Init
+**/
+static void DEV_GPIO_Init(void)
+{
+	DEV_GPIO_Mode(EPD_BUSY_PIN, 0);
+	DEV_GPIO_Mode(EPD_RST_PIN, 1);
+    DEV_GPIO_Mode(EPD_CS_PIN, 1);
+
+    DEV_Digital_Write(EPD_CS_PIN, 1);	
+}
+
+
+
+/******************************************************************************
+function:	Module Initialize, the library and initialize the pins, SPI protocol
+parameter:
+Info:
+******************************************************************************/
+UBYTE DEV_Module_Init(void)
+{
+    Debug("/***********************************/ \r\n");
+
+    char buffer[NUM_MAXBUF];
+    FILE *fp;
+
+    fp = popen("cat /proc/cpuinfo | grep 'Raspberry Pi 5'", "r");
+    if (fp == NULL) {
+        Debug("It is not possible to determine the model of the Raspberry PI\n");
+        return -1;
+    }
+
+    if(fgets(buffer, sizeof(buffer), fp) != NULL)
+    {
+        GPIO_Handle = lgGpiochipOpen(4);
+        if (GPIO_Handle < 0)
+        {
+            Debug( "gpiochip4 Export Failed\n");
+            return -1;
+        }
+    }
+    else
+    {
+        GPIO_Handle = lgGpiochipOpen(0);
+        if (GPIO_Handle < 0)
+        {
+            Debug( "gpiochip0 Export Failed\n");
+            return -1;
+        }
+    }
+    SPI_Handle = lgSpiOpen(0, 0, 12500000, 0);
+    DEV_GPIO_Init();
+    Debug("/***********************************/!! \r\n");
+	return 0;
+}
+
+
+
+/******************************************************************************
+function:	Module exits, closes SPI and BCM2835 library
+parameter:
+Info:
+******************************************************************************/
+void DEV_Module_Exit(void)
+{
+    // DEV_Digital_Write(EPD_CS_PIN, 0);
+	// DEV_Digital_Write(EPD_RST_PIN, 0);
+    // lgSpiClose(SPI_Handle);
+    // lgGpiochipClose(GPIO_Handle);
+}
diff --git a/frameos/src/drivers/waveshare/it8951/DEV_Config.h b/frameos/src/drivers/waveshare/it8951/DEV_Config.h
new file mode 100644
index 00000000..398e2411
--- /dev/null
+++ b/frameos/src/drivers/waveshare/it8951/DEV_Config.h
@@ -0,0 +1,104 @@
+/*****************************************************************************
+* | File      	:   DEV_Config.h
+* | Author      :   Waveshare team
+* | Function    :   Hardware underlying interface
+* | Info        :
+*                Used to shield the underlying layers of each master
+*                and enhance portability
+*----------------
+* |	This version:   V2.0
+* | Date        :   2018-10-30
+* | Info        :
+* 1.add:
+*   UBYTE\UWORD\UDOUBLE
+* 2.Change:
+*   EPD_RST -> EPD_RST_PIN
+*   EPD_DC -> EPD_DC_PIN
+*   EPD_CS -> EPD_CS_PIN
+*   EPD_BUSY -> EPD_BUSY_PIN
+* 3.Remote:
+*   EPD_RST_1\EPD_RST_0
+*   EPD_DC_1\EPD_DC_0
+*   EPD_CS_1\EPD_CS_0
+*   EPD_BUSY_1\EPD_BUSY_0
+* 3.add:
+*   #define DEV_Digital_Write(_pin, _value) bcm2835_GPIOI_write(_pin, _value)
+*   #define DEV_Digital_Read(_pin) bcm2835_GPIOI_lev(_pin)
+*   #define DEV_SPI_WriteByte(__value) bcm2835_spi_transfer(__value)
+#
+# 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.
+#
+******************************************************************************/
+#ifndef _DEV_CONFIG_H_
+#define _DEV_CONFIG_H_
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <lgpio.h>
+
+#define Debug(fmt,...) printf(fmt,##__VA_ARGS__)
+
+#define LFLAGS 0
+#define NUM_MAXBUF  4
+
+
+#define HIGH   0x1
+#define LOW    0x0  
+
+/**
+ * GPIO
+ **/
+ 
+#define EPD_RST_PIN  17
+#define EPD_CS_PIN   8
+#define EPD_BUSY_PIN 24
+
+
+
+/**
+ * data
+**/
+#define UBYTE   uint8_t
+#define UWORD   uint16_t
+#define UDOUBLE uint32_t
+
+
+
+
+
+/*------------------------------------------------------------------------------------------------------*/
+void DEV_Digital_Write(UWORD Pin, UBYTE Value);
+UBYTE DEV_Digital_Read(UWORD Pin);
+
+void DEV_SPI_WriteByte(UBYTE Value);
+UBYTE DEV_SPI_ReadByte();
+
+void DEV_Delay_ms(UDOUBLE xms);
+void DEV_Delay_us(UDOUBLE xus);
+
+UBYTE DEV_Module_Init(void);
+void DEV_Module_Exit(void);
+
+
+#endif
diff --git a/frameos/src/drivers/waveshare/it8951/DEV_Config.nim b/frameos/src/drivers/waveshare/it8951/DEV_Config.nim
new file mode 100644
index 00000000..6eaa6e97
--- /dev/null
+++ b/frameos/src/drivers/waveshare/it8951/DEV_Config.nim
@@ -0,0 +1,68 @@
+{.compile: "DEV_Config.c".}
+{.passl: "-llgpio -lm -lrt".}
+## ***************************************************************************
+##  | File      	:   DEV_Config.h
+##  | Author      :   Waveshare team
+##  | Function    :   Hardware underlying interface
+##  | Info        :
+##                 Used to shield the underlying layers of each master
+##                 and enhance portability
+## ----------------
+##  |	This version:   V2.0
+##  | Date        :   2018-10-30
+##  | Info        :
+##  1.add:
+##    UBYTE\UWORD\UDOUBLE
+##  2.Change:
+##    EPD_RST -> EPD_RST_PIN
+##    EPD_DC -> EPD_DC_PIN
+##    EPD_CS -> EPD_CS_PIN
+##    EPD_BUSY -> EPD_BUSY_PIN
+##  3.Remote:
+##    EPD_RST_1\EPD_RST_0
+##    EPD_DC_1\EPD_DC_0
+##    EPD_CS_1\EPD_CS_0
+##    EPD_BUSY_1\EPD_BUSY_0
+##  3.add:
+##    #define DEV_Digital_Write(_pin, _value) bcm2835_GPIOI_write(_pin, _value)
+##    #define DEV_Digital_Read(_pin) bcm2835_GPIOI_lev(_pin)
+##    #define DEV_SPI_WriteByte(__value) bcm2835_spi_transfer(__value)
+## #
+## # 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.
+## #
+## ****************************************************************************
+
+## !!!Ignored construct:  # _DEV_CONFIG_H_ [NewLine] # _DEV_CONFIG_H_ [NewLine] # < stdint . h > [NewLine] # < stdlib . h > [NewLine] # < stdio . h > [NewLine] # < unistd . h > [NewLine] # < errno . h > [NewLine] # < stdio . h > [NewLine] # < string . h > [NewLine] # < lgpio . h > [NewLine] # Debug ( fmt , ... ) printf ( fmt , ## __VA_ARGS__ ) [NewLine] # LFLAGS 0 [NewLine] # NUM_MAXBUF 4 [NewLine] # HIGH 0x1 [NewLine] # LOW 0x0 [NewLine]
+##  GPIO
+##  # EPD_RST_PIN 17 [NewLine] # EPD_CS_PIN 8 [NewLine] # EPD_BUSY_PIN 24 [NewLine]
+##  data
+##  # UBYTE uint8_t [NewLine] # UWORD uint16_t [NewLine] # UDOUBLE uint32_t [NewLine] ------------------------------------------------------------------------------------------------------ void DEV_Digital_Write ( UWORD Pin , UBYTE Value ) ;
+## Error: did not expect ##!!!
+type
+  UBYTE* = uint8
+  UWORD* = uint16
+  UDOUBLE* = uint32
+
+proc DEV_Digital_Read*(Pin: UWORD): UBYTE {.importc: "DEV_Digital_Read".}
+proc DEV_SPI_WriteByte*(Value: UBYTE) {.importc: "DEV_SPI_WriteByte".}
+proc DEV_SPI_ReadByte*(): UBYTE {.importc: "DEV_SPI_ReadByte".}
+proc DEV_Delay_ms*(xms: UDOUBLE) {.importc: "DEV_Delay_ms".}
+proc DEV_Delay_us*(xus: UDOUBLE) {.importc: "DEV_Delay_us".}
+proc DEV_Module_Init*(): UBYTE {.importc: "DEV_Module_Init".}
+proc DEV_Module_Exit*() {.importc: "DEV_Module_Exit".}
diff --git a/frameos/src/drivers/waveshare/it8951/EPD_10in3.nim b/frameos/src/drivers/waveshare/it8951/EPD_10in3.nim
new file mode 100644
index 00000000..1adfb81d
--- /dev/null
+++ b/frameos/src/drivers/waveshare/it8951/EPD_10in3.nim
@@ -0,0 +1,84 @@
+# Providing a familiar interface for the EPD 10in3 driver
+import
+  DEV_Config,
+  IT8951,
+  std/bitops
+
+const
+  EPD_10IN3_WIDTH* = 1872
+  EPD_10IN3_HEIGHT* = 1404
+
+var initDone = false
+var Dev_Info: IT8951_Dev_Info
+var Init_Target_Memory_Addr: UWORD
+# var A2_Mode = 6
+
+proc EPD_10IN3_Init*(): UBYTE =
+  echo "Initializing?"
+  if DEV_Module_Init() != 0:
+    return -1.UBYTE
+  echo "Initializing!"
+
+  # let temp = -1.48
+  # let vcom = (temp * 1000.float64).UWORD
+  let vcom = 1480.UWORD
+
+  # echo "Temp: ", temp
+  echo "VCOM: ", vcom
+  Dev_Info = EPD_IT8951_Init(vcom)
+
+  echo Dev_Info
+
+  if (Dev_Info.Panel_W != EPD_10IN3_WIDTH) or (Dev_Info.Panel_H != EPD_10IN3_HEIGHT):
+    echo "Panel size mismatch, expected ", EPD_10IN3_WIDTH, "x", EPD_10IN3_HEIGHT, " but got ", Dev_Info.Panel_W, "x",
+        Dev_Info.Panel_H
+    return -1.UBYTE
+
+  Init_Target_Memory_Addr = bitor(Dev_Info.Memory_Addr_L, (Dev_Info.Memory_Addr_H shl 16))
+
+  echo "Memory Addr: ", Init_Target_Memory_Addr
+
+  # TODO: support the other modes
+  # char* LUT_Version = (char*)Dev_Info.LUT_Version;
+  # if( strcmp(LUT_Version, "M641") == 0 ){
+  #     # //6inch e-Paper HAT(800,600), 6inch HD e-Paper HAT(1448,1072), 6inch HD touch e-Paper HAT(1448,1072)
+  #     A2_Mode = 4;
+  #     Four_Byte_Align = true;
+  # }else if( strcmp(LUT_Version, "M841_TFAB512") == 0 ){
+  #     # //Another firmware version for 6inch HD e-Paper HAT(1448,1072), 6inch HD touch e-Paper HAT(1448,1072)
+  #     A2_Mode = 6;
+  #     Four_Byte_Align = true;
+  # }else if( strcmp(LUT_Version, "M841") == 0 ){
+  #     # //9.7inch e-Paper HAT(1200,825)
+  #     A2_Mode = 6;
+  # }else if( strcmp(LUT_Version, "M841_TFA2812") == 0 ){
+  #     # //7.8inch e-Paper HAT(1872,1404)
+  #     A2_Mode = 6;
+  # }else if( strcmp(LUT_Version, "M841_TFA5210") == 0 ){
+  #     # //10.3inch e-Paper HAT(1872,1404)
+  #     A2_Mode = 6;
+  # }else{
+  #     # //default set to 6 as A2 Mode
+  #     A2_Mode = 6;
+  # }
+
+  initDone = true
+  echo "Init done"
+
+proc EPD_10IN3_Clear*() =
+  echo "Clearing?"
+  if initDone:
+    echo "Clearing!"
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, INIT_Mode)
+
+proc EPD_10IN3_16Gray_Display*(Image: ptr UBYTE) =
+  echo "Displaying?"
+  if initDone:
+    echo "Displaying!"
+    EPD_IT8951_4bp_Refresh(Image, 0, 0, EPD_10IN3_WIDTH, EPD_10IN3_HEIGHT, false, Init_Target_Memory_Addr, false)
+
+proc EPD_10IN3_Sleep*() =
+  echo "Sleeping?"
+  if initDone:
+    echo "Sleeping!"
+    EPD_IT8951_Sleep()
diff --git a/frameos/src/drivers/waveshare/it8951/IT8951.c b/frameos/src/drivers/waveshare/it8951/IT8951.c
new file mode 100644
index 00000000..5d9e8832
--- /dev/null
+++ b/frameos/src/drivers/waveshare/it8951/IT8951.c
@@ -0,0 +1,921 @@
+/*****************************************************************************
+* | File      	:   EPD_IT8951.c
+* | Author      :   Waveshare team
+* | Function    :   IT8951 Common driver
+* | Info        :
+*----------------
+* |	This version:   V1.0
+* | Date        :   2019-09-17
+* | 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.
+#
+******************************************************************************/
+#include "IT8951.h"
+#include <time.h>
+
+//basic mode definition
+UBYTE INIT_Mode = 0;
+UBYTE GC16_Mode = 2;
+//A2_Mode's value is not fixed, is decide by firmware's LUT 
+UBYTE A2_Mode = 6;
+
+/******************************************************************************
+function :	Software reset
+parameter:
+******************************************************************************/
+static void EPD_IT8951_Reset(void)
+{
+    DEV_Digital_Write(EPD_RST_PIN, HIGH);
+    DEV_Delay_ms(200);
+    DEV_Digital_Write(EPD_RST_PIN, LOW);
+    DEV_Delay_ms(10);
+    DEV_Digital_Write(EPD_RST_PIN, HIGH);
+    DEV_Delay_ms(200);
+}
+
+
+/******************************************************************************
+function :	Wait until the busy_pin goes HIGH
+parameter:
+******************************************************************************/
+static void EPD_IT8951_ReadBusy(void)
+{
+	Debug("Busy ------\r\n");
+    UBYTE Busy_State = DEV_Digital_Read(EPD_BUSY_PIN);
+    //0: busy, 1: idle
+    while(Busy_State == 0) {
+        Busy_State = DEV_Digital_Read(EPD_BUSY_PIN);
+    }
+	Debug("Busy Release ------\r\n");
+}
+
+
+/******************************************************************************
+function :	write command
+parameter:  command
+******************************************************************************/
+static void EPD_IT8951_WriteCommand(UWORD Command)
+{
+	//Set Preamble for Write Command
+	UWORD Write_Preamble = 0x6000;
+	
+	EPD_IT8951_ReadBusy();
+
+    DEV_Digital_Write(EPD_CS_PIN, LOW);
+	
+	DEV_SPI_WriteByte(Write_Preamble>>8);
+	DEV_SPI_WriteByte(Write_Preamble);
+	
+	EPD_IT8951_ReadBusy();	
+	
+	DEV_SPI_WriteByte(Command>>8);
+	DEV_SPI_WriteByte(Command);
+	
+	DEV_Digital_Write(EPD_CS_PIN, HIGH);
+}
+
+
+/******************************************************************************
+function :	write data
+parameter:  data
+******************************************************************************/
+static void EPD_IT8951_WriteData(UWORD Data)
+{
+    //Set Preamble for Write Command
+	UWORD Write_Preamble = 0x0000;
+
+    EPD_IT8951_ReadBusy();
+
+    DEV_Digital_Write(EPD_CS_PIN, LOW);
+
+	DEV_SPI_WriteByte(Write_Preamble>>8);
+	DEV_SPI_WriteByte(Write_Preamble);
+
+    EPD_IT8951_ReadBusy();
+
+	DEV_SPI_WriteByte(Data>>8);
+	DEV_SPI_WriteByte(Data);
+
+    DEV_Digital_Write(EPD_CS_PIN, HIGH);
+}
+
+
+/******************************************************************************
+function :	write multi data
+parameter:  data
+******************************************************************************/
+static void EPD_IT8951_WriteMuitiData(UWORD* Data_Buf, UDOUBLE Length)
+{
+    //Set Preamble for Write Command
+	UWORD Write_Preamble = 0x0000;
+
+    EPD_IT8951_ReadBusy();
+
+    DEV_Digital_Write(EPD_CS_PIN, LOW);
+
+	DEV_SPI_WriteByte(Write_Preamble>>8);
+	DEV_SPI_WriteByte(Write_Preamble);
+
+    EPD_IT8951_ReadBusy();
+
+    for(UDOUBLE i = 0; i<Length; i++)
+    {
+	    DEV_SPI_WriteByte(Data_Buf[i]>>8);
+	    DEV_SPI_WriteByte(Data_Buf[i]);
+    }
+    DEV_Digital_Write(EPD_CS_PIN, HIGH);
+}
+
+
+
+/******************************************************************************
+function :	read data
+parameter:  data
+******************************************************************************/
+static UWORD EPD_IT8951_ReadData()
+{
+    UWORD ReadData;
+	UWORD Write_Preamble = 0x1000;
+    UWORD Read_Dummy;
+
+    EPD_IT8951_ReadBusy();
+
+    DEV_Digital_Write(EPD_CS_PIN, LOW);
+
+	DEV_SPI_WriteByte(Write_Preamble>>8);
+	DEV_SPI_WriteByte(Write_Preamble);
+
+    EPD_IT8951_ReadBusy();
+
+    //dummy
+    Read_Dummy = DEV_SPI_ReadByte()<<8;
+    Read_Dummy |= DEV_SPI_ReadByte();
+
+    EPD_IT8951_ReadBusy();
+
+    ReadData = DEV_SPI_ReadByte()<<8;
+    ReadData |= DEV_SPI_ReadByte();
+
+    DEV_Digital_Write(EPD_CS_PIN, HIGH);
+
+    return ReadData;
+}
+
+
+
+
+/******************************************************************************
+function :	read multi data
+parameter:  data
+******************************************************************************/
+static void EPD_IT8951_ReadMultiData(UWORD* Data_Buf, UDOUBLE Length)
+{
+	UWORD Write_Preamble = 0x1000;
+    UWORD Read_Dummy;
+
+    EPD_IT8951_ReadBusy();
+
+    DEV_Digital_Write(EPD_CS_PIN, LOW);
+
+	DEV_SPI_WriteByte(Write_Preamble>>8);
+	DEV_SPI_WriteByte(Write_Preamble);
+
+    EPD_IT8951_ReadBusy();
+
+    //dummy
+    Read_Dummy = DEV_SPI_ReadByte()<<8;
+    Read_Dummy |= DEV_SPI_ReadByte();
+
+    EPD_IT8951_ReadBusy();
+
+    for(UDOUBLE i = 0; i<Length; i++)
+    {
+	    Data_Buf[i] = DEV_SPI_ReadByte()<<8;
+	    Data_Buf[i] |= DEV_SPI_ReadByte();
+    }
+
+    DEV_Digital_Write(EPD_CS_PIN, HIGH);
+}
+
+
+
+/******************************************************************************
+function:	write multi arg
+parameter:	data
+description:	some situation like this:
+* 1 commander     0    argument
+* 1 commander     1    argument
+* 1 commander   multi  argument
+******************************************************************************/
+static void EPD_IT8951_WriteMultiArg(UWORD Arg_Cmd, UWORD* Arg_Buf, UWORD Arg_Num)
+{
+     //Send Cmd code
+     EPD_IT8951_WriteCommand(Arg_Cmd);
+     //Send Data
+     for(UWORD i=0; i<Arg_Num; i++)
+     {
+         EPD_IT8951_WriteData(Arg_Buf[i]);
+     }
+}
+
+
+/******************************************************************************
+function :	Cmd4 ReadReg
+parameter:  
+******************************************************************************/
+static UWORD EPD_IT8951_ReadReg(UWORD Reg_Address)
+{
+    UWORD Reg_Value;
+    EPD_IT8951_WriteCommand(IT8951_TCON_REG_RD);
+    EPD_IT8951_WriteData(Reg_Address);
+    Reg_Value =  EPD_IT8951_ReadData();
+    return Reg_Value;
+}
+
+
+
+/******************************************************************************
+function :	Cmd5 WriteReg
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_WriteReg(UWORD Reg_Address,UWORD Reg_Value)
+{
+    EPD_IT8951_WriteCommand(IT8951_TCON_REG_WR);
+    EPD_IT8951_WriteData(Reg_Address);
+    EPD_IT8951_WriteData(Reg_Value);
+}
+
+
+
+/******************************************************************************
+function :	get VCOM
+parameter:  
+******************************************************************************/
+static UWORD EPD_IT8951_GetVCOM(void)
+{
+    UWORD VCOM;
+    EPD_IT8951_WriteCommand(USDEF_I80_CMD_VCOM);
+    EPD_IT8951_WriteData(0x0000);
+    VCOM =  EPD_IT8951_ReadData();
+    return VCOM;
+}
+
+
+
+/******************************************************************************
+function :	set VCOM
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_SetVCOM(UWORD VCOM)
+{
+    EPD_IT8951_WriteCommand(USDEF_I80_CMD_VCOM);
+    EPD_IT8951_WriteData(0x0001);
+    EPD_IT8951_WriteData(VCOM);
+}
+
+
+
+/******************************************************************************
+function :	Cmd10 LD_IMG
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_LoadImgStart( IT8951_Load_Img_Info* Load_Img_Info )
+{
+    UWORD Args;
+    Args = (\
+        Load_Img_Info->Endian_Type<<8 | \
+        Load_Img_Info->Pixel_Format<<4 | \
+        Load_Img_Info->Rotate\
+    );
+    EPD_IT8951_WriteCommand(IT8951_TCON_LD_IMG);
+    EPD_IT8951_WriteData(Args);
+}
+
+
+/******************************************************************************
+function :	Cmd11 LD_IMG_Area
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_LoadImgAreaStart( IT8951_Load_Img_Info* Load_Img_Info, IT8951_Area_Img_Info* Area_Img_Info )
+{
+    UWORD Args[5];
+    Args[0] = (\
+        Load_Img_Info->Endian_Type<<8 | \
+        Load_Img_Info->Pixel_Format<<4 | \
+        Load_Img_Info->Rotate\
+    );
+    Args[1] = Area_Img_Info->Area_X;
+    Args[2] = Area_Img_Info->Area_Y;
+    Args[3] = Area_Img_Info->Area_W;
+    Args[4] = Area_Img_Info->Area_H;
+    EPD_IT8951_WriteMultiArg(IT8951_TCON_LD_IMG_AREA, Args,5);
+}
+
+/******************************************************************************
+function :	Cmd12 LD_IMG_End
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_LoadImgEnd(void)
+{
+    EPD_IT8951_WriteCommand(IT8951_TCON_LD_IMG_END);
+}
+
+
+/******************************************************************************
+function :	EPD_IT8951_Get_System_Info
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_GetSystemInfo(void* Buf)
+{
+    IT8951_Dev_Info* Dev_Info; 
+
+    EPD_IT8951_WriteCommand(USDEF_I80_CMD_GET_DEV_INFO);
+
+    EPD_IT8951_ReadMultiData((UWORD*)Buf, sizeof(IT8951_Dev_Info)/2);
+
+    Dev_Info = (IT8951_Dev_Info*)Buf;
+	Debug("Panel(W,H) = (%d,%d)\r\n",Dev_Info->Panel_W, Dev_Info->Panel_H );
+	Debug("Memory Address = %X\r\n",Dev_Info->Memory_Addr_L | (Dev_Info->Memory_Addr_H << 16));
+	Debug("FW Version = %s\r\n", (UBYTE*)Dev_Info->FW_Version);
+	Debug("LUT Version = %s\r\n", (UBYTE*)Dev_Info->LUT_Version);
+}
+
+
+/******************************************************************************
+function :	EPD_IT8951_Set_Target_Memory_Addr
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_SetTargetMemoryAddr(UDOUBLE Target_Memory_Addr)
+{
+	UWORD WordH = (UWORD)((Target_Memory_Addr >> 16) & 0x0000FFFF);
+	UWORD WordL = (UWORD)( Target_Memory_Addr & 0x0000FFFF);
+
+    EPD_IT8951_WriteReg(LISAR+2, WordH);
+    EPD_IT8951_WriteReg(LISAR  , WordL);
+}
+
+
+/******************************************************************************
+function :	EPD_IT8951_WaitForDisplayReady
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_WaitForDisplayReady(void)
+{
+    //Check IT8951 Register LUTAFSR => NonZero Busy, Zero - Free
+    while( EPD_IT8951_ReadReg(LUTAFSR) )
+    {
+        //wait in idle state
+    }
+}
+
+
+
+
+
+/******************************************************************************
+function :	EPD_IT8951_HostAreaPackedPixelWrite_1bp
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_HostAreaPackedPixelWrite_1bp(IT8951_Load_Img_Info*Load_Img_Info,IT8951_Area_Img_Info*Area_Img_Info, bool Packed_Write)
+{
+    UWORD Source_Buffer_Width, Source_Buffer_Height;
+    UWORD Source_Buffer_Length;
+
+    UWORD* Source_Buffer = (UWORD*)Load_Img_Info->Source_Buffer_Addr;
+    EPD_IT8951_SetTargetMemoryAddr(Load_Img_Info->Target_Memory_Addr);
+    EPD_IT8951_LoadImgAreaStart(Load_Img_Info,Area_Img_Info);
+
+    //from byte to word
+    //use 8bp to display 1bp, so here, divide by 2, because every byte has full bit.
+    Source_Buffer_Width = Area_Img_Info->Area_W/2;
+    Source_Buffer_Height = Area_Img_Info->Area_H;
+    Source_Buffer_Length = Source_Buffer_Width * Source_Buffer_Height;
+    
+    if(Packed_Write == true)
+    {
+        EPD_IT8951_WriteMuitiData(Source_Buffer, Source_Buffer_Length);
+    }
+    else
+    {
+        for(UDOUBLE i=0; i<Source_Buffer_Height; i++)
+        {
+            for(UDOUBLE j=0; j<Source_Buffer_Width; j++)
+            {
+                EPD_IT8951_WriteData(*Source_Buffer);
+                Source_Buffer++;
+            }
+        }
+    }
+
+    EPD_IT8951_LoadImgEnd();
+}
+
+
+
+
+
+/******************************************************************************
+function :	EPD_IT8951_HostAreaPackedPixelWrite_2bp
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_HostAreaPackedPixelWrite_2bp(IT8951_Load_Img_Info*Load_Img_Info, IT8951_Area_Img_Info*Area_Img_Info, bool Packed_Write)
+{
+    UWORD Source_Buffer_Width, Source_Buffer_Height;
+    UWORD Source_Buffer_Length;
+
+    UWORD* Source_Buffer = (UWORD*)Load_Img_Info->Source_Buffer_Addr;
+    EPD_IT8951_SetTargetMemoryAddr(Load_Img_Info->Target_Memory_Addr);
+    EPD_IT8951_LoadImgAreaStart(Load_Img_Info,Area_Img_Info);
+
+    //from byte to word
+    Source_Buffer_Width = (Area_Img_Info->Area_W*2/8)/2;
+    Source_Buffer_Height = Area_Img_Info->Area_H;
+    Source_Buffer_Length = Source_Buffer_Width * Source_Buffer_Height;
+
+    if(Packed_Write == true)
+    {
+        EPD_IT8951_WriteMuitiData(Source_Buffer, Source_Buffer_Length);
+    }
+    else
+    {
+        for(UDOUBLE i=0; i<Source_Buffer_Height; i++)
+        {
+            for(UDOUBLE j=0; j<Source_Buffer_Width; j++)
+            {
+                EPD_IT8951_WriteData(*Source_Buffer);
+                Source_Buffer++;
+            }
+        }
+    }
+
+    EPD_IT8951_LoadImgEnd();
+}
+
+
+
+
+
+/******************************************************************************
+function :	EPD_IT8951_HostAreaPackedPixelWrite_4bp
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_HostAreaPackedPixelWrite_4bp(IT8951_Load_Img_Info*Load_Img_Info, IT8951_Area_Img_Info*Area_Img_Info, bool Packed_Write)
+{
+    UWORD Source_Buffer_Width, Source_Buffer_Height;
+    UWORD Source_Buffer_Length;
+	
+    UWORD* Source_Buffer = (UWORD*)Load_Img_Info->Source_Buffer_Addr;
+    EPD_IT8951_SetTargetMemoryAddr(Load_Img_Info->Target_Memory_Addr);
+    EPD_IT8951_LoadImgAreaStart(Load_Img_Info,Area_Img_Info);
+
+    //from byte to word
+    Source_Buffer_Width = (Area_Img_Info->Area_W*4/8)/2;
+    Source_Buffer_Height = Area_Img_Info->Area_H;
+    Source_Buffer_Length = Source_Buffer_Width * Source_Buffer_Height;
+
+    if(Packed_Write == true)
+    {
+        EPD_IT8951_WriteMuitiData(Source_Buffer, Source_Buffer_Length);
+    }
+    else
+    {
+        for(UDOUBLE i=0; i<Source_Buffer_Height; i++)
+        {
+            for(UDOUBLE j=0; j<Source_Buffer_Width; j++)
+            {
+                EPD_IT8951_WriteData(*Source_Buffer);
+                Source_Buffer++;
+            }
+        }
+    }
+
+    EPD_IT8951_LoadImgEnd();
+}
+
+
+
+
+
+
+
+/******************************************************************************
+function :	EPD_IT8951_HostAreaPackedPixelWrite_8bp
+parameter:  
+Precautions: Can't Packed Write
+******************************************************************************/
+static void EPD_IT8951_HostAreaPackedPixelWrite_8bp(IT8951_Load_Img_Info*Load_Img_Info,IT8951_Area_Img_Info*Area_Img_Info)
+{
+    UWORD Source_Buffer_Width, Source_Buffer_Height;
+
+    UWORD* Source_Buffer = (UWORD*)Load_Img_Info->Source_Buffer_Addr;
+    EPD_IT8951_SetTargetMemoryAddr(Load_Img_Info->Target_Memory_Addr);
+    EPD_IT8951_LoadImgAreaStart(Load_Img_Info,Area_Img_Info);
+
+    //from byte to word
+    Source_Buffer_Width = (Area_Img_Info->Area_W*8/8)/2;
+    Source_Buffer_Height = Area_Img_Info->Area_H;
+
+    for(UDOUBLE i=0; i<Source_Buffer_Height; i++)
+    {
+        for(UDOUBLE j=0; j<Source_Buffer_Width; j++)
+        {
+            EPD_IT8951_WriteData(*Source_Buffer);
+            Source_Buffer++;
+        }
+    }
+    EPD_IT8951_LoadImgEnd();
+}
+
+
+
+
+
+
+/******************************************************************************
+function :	EPD_IT8951_Display_Area
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_Display_Area(UWORD X,UWORD Y,UWORD W,UWORD H,UWORD Mode)
+{
+    UWORD Args[5];
+    Args[0] = X;
+    Args[1] = Y;
+    Args[2] = W;
+    Args[3] = H;
+    Args[4] = Mode;
+    //0x0034
+    EPD_IT8951_WriteMultiArg(USDEF_I80_CMD_DPY_AREA, Args,5);
+}
+
+
+
+/******************************************************************************
+function :	EPD_IT8951_Display_AreaBuf
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_Display_AreaBuf(UWORD X,UWORD Y,UWORD W,UWORD H,UWORD Mode, UDOUBLE Target_Memory_Addr)
+{
+    UWORD Args[7];
+    Args[0] = X;
+    Args[1] = Y;
+    Args[2] = W;
+    Args[3] = H;
+    Args[4] = Mode;
+    Args[5] = (UWORD)Target_Memory_Addr;
+    Args[6] = (UWORD)(Target_Memory_Addr>>16);
+    //0x0037
+    EPD_IT8951_WriteMultiArg(USDEF_I80_CMD_DPY_BUF_AREA, Args,7); 
+}
+
+
+
+/******************************************************************************
+function :	EPD_IT8951_Display_1bp
+parameter:  
+******************************************************************************/
+static void EPD_IT8951_Display_1bp(UWORD X, UWORD Y, UWORD W, UWORD H, UWORD Mode,UDOUBLE Target_Memory_Addr, UBYTE Back_Gray_Val,UBYTE Front_Gray_Val)
+{
+    //Set Display mode to 1 bpp mode - Set 0x18001138 Bit[18](0x1800113A Bit[2])to 1
+    EPD_IT8951_WriteReg(UP1SR+2, EPD_IT8951_ReadReg(UP1SR+2) | (1<<2) );
+
+    EPD_IT8951_WriteReg(BGVR, (Front_Gray_Val<<8) | Back_Gray_Val);
+
+    if(Target_Memory_Addr == 0)
+    {
+        EPD_IT8951_Display_Area(X,Y,W,H,Mode);
+    }
+    else
+    {
+        EPD_IT8951_Display_AreaBuf(X,Y,W,H,Mode,Target_Memory_Addr);
+    }
+    
+    EPD_IT8951_WaitForDisplayReady();
+
+    EPD_IT8951_WriteReg(UP1SR+2, EPD_IT8951_ReadReg(UP1SR+2) & ~(1<<2) );
+}
+
+
+/******************************************************************************
+function :	Enhanced driving capability
+parameter:  Enhanced driving capability for IT8951, in case the blurred display effect
+******************************************************************************/
+void Enhance_Driving_Capability(void)
+{
+    UWORD RegValue = EPD_IT8951_ReadReg(0x0038);
+    Debug("The reg value before writing is %x\r\n", RegValue);
+
+    EPD_IT8951_WriteReg(0x0038, 0x0602);
+
+    RegValue = EPD_IT8951_ReadReg(0x0038);
+    Debug("The reg value after writing is %x\r\n", RegValue);
+}
+
+
+
+
+/******************************************************************************
+function :	Cmd1 SYS_RUN
+parameter:  Run the system
+******************************************************************************/
+void EPD_IT8951_SystemRun(void)
+{
+    EPD_IT8951_WriteCommand(IT8951_TCON_SYS_RUN);
+}
+
+
+/******************************************************************************
+function :	Cmd2 STANDBY
+parameter:  Standby
+******************************************************************************/
+void EPD_IT8951_Standby(void)
+{
+    EPD_IT8951_WriteCommand(IT8951_TCON_STANDBY);
+}
+
+
+/******************************************************************************
+function :	Cmd3 SLEEP
+parameter:  Sleep
+******************************************************************************/
+void EPD_IT8951_Sleep(void)
+{
+    EPD_IT8951_WriteCommand(IT8951_TCON_SLEEP);
+}
+
+
+/******************************************************************************
+function :	EPD_IT8951_Init
+parameter:  
+******************************************************************************/
+IT8951_Dev_Info EPD_IT8951_Init(UWORD VCOM)
+{
+    IT8951_Dev_Info Dev_Info;
+
+    EPD_IT8951_Reset();
+
+    EPD_IT8951_SystemRun();
+
+    EPD_IT8951_GetSystemInfo(&Dev_Info);
+    
+    //Enable Pack write
+    EPD_IT8951_WriteReg(I80CPCR,0x0001);
+
+    //Set VCOM by handle
+    if(VCOM != EPD_IT8951_GetVCOM())
+    {
+        EPD_IT8951_SetVCOM(VCOM);
+        Debug("VCOM = -%.02fV\n",(float)EPD_IT8951_GetVCOM()/1000);
+    }
+    return Dev_Info;
+}
+
+
+/******************************************************************************
+function :	EPD_IT8951_Clear_Refresh
+parameter:  
+******************************************************************************/
+void EPD_IT8951_Clear_Refresh(IT8951_Dev_Info Dev_Info,UDOUBLE Target_Memory_Addr, UWORD Mode)
+{
+
+    UDOUBLE ImageSize = ((Dev_Info.Panel_W * 4 % 8 == 0)? (Dev_Info.Panel_W * 4 / 8 ): (Dev_Info.Panel_W * 4 / 8 + 1)) * Dev_Info.Panel_H;
+    UBYTE* Frame_Buf = malloc (ImageSize);
+    memset(Frame_Buf, 0xFF, ImageSize);
+
+
+    IT8951_Load_Img_Info Load_Img_Info;
+    IT8951_Area_Img_Info Area_Img_Info;
+
+    EPD_IT8951_WaitForDisplayReady();
+
+    Load_Img_Info.Source_Buffer_Addr = Frame_Buf;
+    Load_Img_Info.Endian_Type = IT8951_LDIMG_L_ENDIAN;
+    Load_Img_Info.Pixel_Format = IT8951_4BPP;
+    Load_Img_Info.Rotate =  IT8951_ROTATE_0;
+    Load_Img_Info.Target_Memory_Addr = Target_Memory_Addr;
+
+    Area_Img_Info.Area_X = 0;
+    Area_Img_Info.Area_Y = 0;
+    Area_Img_Info.Area_W = Dev_Info.Panel_W;
+    Area_Img_Info.Area_H = Dev_Info.Panel_H;
+
+    EPD_IT8951_HostAreaPackedPixelWrite_4bp(&Load_Img_Info, &Area_Img_Info, false);
+
+    EPD_IT8951_Display_Area(0, 0, Dev_Info.Panel_W, Dev_Info.Panel_H, Mode);
+
+    free(Frame_Buf);
+    Frame_Buf = NULL;
+}
+
+
+/******************************************************************************
+function :	EPD_IT8951_1bp_Refresh
+parameter:
+******************************************************************************/
+void EPD_IT8951_1bp_Refresh(UBYTE* Frame_Buf, UWORD X, UWORD Y, UWORD W, UWORD H, UBYTE Mode, UDOUBLE Target_Memory_Addr, bool Packed_Write)
+{
+    IT8951_Load_Img_Info Load_Img_Info;
+    IT8951_Area_Img_Info Area_Img_Info;
+
+    EPD_IT8951_WaitForDisplayReady();
+
+    Load_Img_Info.Source_Buffer_Addr = Frame_Buf;
+    Load_Img_Info.Endian_Type = IT8951_LDIMG_L_ENDIAN;
+    //Use 8bpp to set 1bpp
+    Load_Img_Info.Pixel_Format = IT8951_8BPP;
+    Load_Img_Info.Rotate =  IT8951_ROTATE_0;
+    Load_Img_Info.Target_Memory_Addr = Target_Memory_Addr;
+
+    Area_Img_Info.Area_X = X/8;
+    Area_Img_Info.Area_Y = Y;
+    Area_Img_Info.Area_W = W/8;
+    Area_Img_Info.Area_H = H;
+
+
+    //clock_t start, finish;
+    //double duration;
+
+    //start = clock();
+
+    EPD_IT8951_HostAreaPackedPixelWrite_1bp(&Load_Img_Info, &Area_Img_Info, Packed_Write);
+
+    //finish = clock();
+    //duration = (double)(finish - start) / CLOCKS_PER_SEC;
+	//Debug( "Write occupy %f second\n", duration );
+
+    //start = clock();
+
+    EPD_IT8951_Display_1bp(X,Y,W,H,Mode,Target_Memory_Addr,0xF0,0x00);
+
+    //finish = clock();
+    //duration = (double)(finish - start) / CLOCKS_PER_SEC;
+	//Debug( "Show occupy %f second\n", duration );
+}
+
+
+
+/******************************************************************************
+function :	EPD_IT8951_1bp_Multi_Frame_Write
+parameter:  
+******************************************************************************/
+void EPD_IT8951_1bp_Multi_Frame_Write(UBYTE* Frame_Buf, UWORD X, UWORD Y, UWORD W, UWORD H,UDOUBLE Target_Memory_Addr, bool Packed_Write)
+{
+    IT8951_Load_Img_Info Load_Img_Info;
+    IT8951_Area_Img_Info Area_Img_Info;
+
+    EPD_IT8951_WaitForDisplayReady();
+
+    Load_Img_Info.Source_Buffer_Addr = Frame_Buf;
+    Load_Img_Info.Endian_Type = IT8951_LDIMG_L_ENDIAN;
+    //Use 8bpp to set 1bpp
+    Load_Img_Info.Pixel_Format = IT8951_8BPP;
+    Load_Img_Info.Rotate =  IT8951_ROTATE_0;
+    Load_Img_Info.Target_Memory_Addr = Target_Memory_Addr;
+
+    Area_Img_Info.Area_X = X/8;
+    Area_Img_Info.Area_Y = Y;
+    Area_Img_Info.Area_W = W/8;
+    Area_Img_Info.Area_H = H;
+    
+    EPD_IT8951_HostAreaPackedPixelWrite_1bp(&Load_Img_Info, &Area_Img_Info,Packed_Write);
+}
+
+
+
+
+/******************************************************************************
+function :	EPD_IT8951_1bp_Multi_Frame_Refresh
+parameter:  
+******************************************************************************/
+void EPD_IT8951_1bp_Multi_Frame_Refresh(UWORD X, UWORD Y, UWORD W, UWORD H,UDOUBLE Target_Memory_Addr)
+{
+    EPD_IT8951_WaitForDisplayReady();
+
+    EPD_IT8951_Display_1bp(X,Y,W,H, A2_Mode,Target_Memory_Addr,0xF0,0x00);
+}
+
+
+
+
+/******************************************************************************
+function :	EPD_IT8951_2bp_Refresh
+parameter:  
+******************************************************************************/
+void EPD_IT8951_2bp_Refresh(UBYTE* Frame_Buf, UWORD X, UWORD Y, UWORD W, UWORD H, bool Hold, UDOUBLE Target_Memory_Addr, bool Packed_Write)
+{
+    IT8951_Load_Img_Info Load_Img_Info;
+    IT8951_Area_Img_Info Area_Img_Info;
+
+    EPD_IT8951_WaitForDisplayReady();
+
+    Load_Img_Info.Source_Buffer_Addr = Frame_Buf;
+    Load_Img_Info.Endian_Type = IT8951_LDIMG_L_ENDIAN;
+    Load_Img_Info.Pixel_Format = IT8951_2BPP;
+    Load_Img_Info.Rotate =  IT8951_ROTATE_0;
+    Load_Img_Info.Target_Memory_Addr = Target_Memory_Addr;
+
+    Area_Img_Info.Area_X = X;
+    Area_Img_Info.Area_Y = Y;
+    Area_Img_Info.Area_W = W;
+    Area_Img_Info.Area_H = H;
+
+    EPD_IT8951_HostAreaPackedPixelWrite_2bp(&Load_Img_Info, &Area_Img_Info,Packed_Write);
+
+    if(Hold == true)
+    {
+        EPD_IT8951_Display_Area(X,Y,W,H, GC16_Mode);
+    }
+    else
+    {
+        EPD_IT8951_Display_AreaBuf(X,Y,W,H, GC16_Mode,Target_Memory_Addr);
+    }
+}
+
+
+
+
+/******************************************************************************
+function :	EPD_IT8951_4bp_Refresh
+parameter:  
+******************************************************************************/
+void EPD_IT8951_4bp_Refresh(UBYTE* Frame_Buf, UWORD X, UWORD Y, UWORD W, UWORD H, bool Hold, UDOUBLE Target_Memory_Addr, bool Packed_Write)
+{
+    IT8951_Load_Img_Info Load_Img_Info;
+    IT8951_Area_Img_Info Area_Img_Info;
+
+    EPD_IT8951_WaitForDisplayReady();
+
+    Load_Img_Info.Source_Buffer_Addr = Frame_Buf;
+    Load_Img_Info.Endian_Type = IT8951_LDIMG_L_ENDIAN;
+    Load_Img_Info.Pixel_Format = IT8951_4BPP;
+    Load_Img_Info.Rotate =  IT8951_ROTATE_0;
+    Load_Img_Info.Target_Memory_Addr = Target_Memory_Addr;
+
+    Area_Img_Info.Area_X = X;
+    Area_Img_Info.Area_Y = Y;
+    Area_Img_Info.Area_W = W;
+    Area_Img_Info.Area_H = H;
+
+    EPD_IT8951_HostAreaPackedPixelWrite_4bp(&Load_Img_Info, &Area_Img_Info, Packed_Write);
+
+    if(Hold == true)
+    {
+        EPD_IT8951_Display_Area(X,Y,W,H, GC16_Mode);
+    }
+    else
+    {
+        EPD_IT8951_Display_AreaBuf(X,Y,W,H, GC16_Mode,Target_Memory_Addr);
+    }
+}
+
+
+/******************************************************************************
+function :	EPD_IT8951_8bp_Refresh
+parameter:  
+******************************************************************************/
+void EPD_IT8951_8bp_Refresh(UBYTE *Frame_Buf, UWORD X, UWORD Y, UWORD W, UWORD H, bool Hold, UDOUBLE Target_Memory_Addr)
+{
+    IT8951_Load_Img_Info Load_Img_Info;
+    IT8951_Area_Img_Info Area_Img_Info;
+
+    EPD_IT8951_WaitForDisplayReady();
+
+    Load_Img_Info.Source_Buffer_Addr = Frame_Buf;
+    Load_Img_Info.Endian_Type = IT8951_LDIMG_L_ENDIAN;
+    Load_Img_Info.Pixel_Format = IT8951_8BPP;
+    Load_Img_Info.Rotate =  IT8951_ROTATE_0;
+    Load_Img_Info.Target_Memory_Addr = Target_Memory_Addr;
+
+    Area_Img_Info.Area_X = X;
+    Area_Img_Info.Area_Y = Y;
+    Area_Img_Info.Area_W = W;
+    Area_Img_Info.Area_H = H;
+
+    EPD_IT8951_HostAreaPackedPixelWrite_8bp(&Load_Img_Info, &Area_Img_Info);
+
+    if(Hold == true)
+    {
+        EPD_IT8951_Display_Area(X, Y, W, H, GC16_Mode);
+    }
+    else
+    {
+        EPD_IT8951_Display_AreaBuf(X, Y, W, H, GC16_Mode, Target_Memory_Addr);
+    }
+}
diff --git a/frameos/src/drivers/waveshare/it8951/IT8951.h b/frameos/src/drivers/waveshare/it8951/IT8951.h
new file mode 100644
index 00000000..46a5812d
--- /dev/null
+++ b/frameos/src/drivers/waveshare/it8951/IT8951.h
@@ -0,0 +1,209 @@
+/*****************************************************************************
+* | File      	:   EPD_IT8951.h
+* | Author      :   Waveshare team
+* | Function    :   IT8951 Common driver
+* | Info        :
+*----------------
+* |	This version:   V1.0
+* | Date        :   2019-09-17
+* | 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.
+#
+******************************************************************************/
+#ifndef __EPD_IT8951_H_
+#define __EPD_IT8951_H_
+
+#include <stdbool.h>
+
+#include "DEV_Config.h"
+
+
+
+extern UBYTE INIT_Mode;
+
+extern UBYTE GC16_Mode;
+
+extern UBYTE A2_Mode;
+
+
+typedef struct IT8951_Load_Img_Info
+{
+    UWORD    Endian_Type;         
+    UWORD    Pixel_Format;        
+    UWORD    Rotate;              
+    UBYTE*  Source_Buffer_Addr;  
+    UDOUBLE  Target_Memory_Addr;  
+}IT8951_Load_Img_Info;
+
+typedef struct IT8951_Area_Img_Info
+{
+    UWORD Area_X;
+    UWORD Area_Y;
+    UWORD Area_W;
+    UWORD Area_H;
+}IT8951_Area_Img_Info;
+
+typedef struct IT8951_Dev_Info
+{
+    UWORD Panel_W;
+    UWORD Panel_H;
+    UWORD Memory_Addr_L;
+    UWORD Memory_Addr_H;
+    UWORD FW_Version[8];
+    UWORD LUT_Version[8];
+}IT8951_Dev_Info;
+
+/*-----------------------------------------------------------------------
+IT8951 Command defines
+------------------------------------------------------------------------*/
+
+
+#define IT8951_TCON_SYS_RUN      0x0001
+#define IT8951_TCON_STANDBY      0x0002
+#define IT8951_TCON_SLEEP        0x0003
+#define IT8951_TCON_REG_RD       0x0010
+#define IT8951_TCON_REG_WR       0x0011
+
+#define IT8951_TCON_MEM_BST_RD_T 0x0012
+#define IT8951_TCON_MEM_BST_RD_S 0x0013
+#define IT8951_TCON_MEM_BST_WR   0x0014
+#define IT8951_TCON_MEM_BST_END  0x0015
+
+#define IT8951_TCON_LD_IMG       0x0020
+#define IT8951_TCON_LD_IMG_AREA  0x0021
+#define IT8951_TCON_LD_IMG_END   0x0022
+
+
+#define USDEF_I80_CMD_DPY_AREA     0x0034
+#define USDEF_I80_CMD_GET_DEV_INFO 0x0302
+#define USDEF_I80_CMD_DPY_BUF_AREA 0x0037
+#define USDEF_I80_CMD_VCOM		   0x0039
+
+/*-----------------------------------------------------------------------
+ IT8951 Mode defines
+------------------------------------------------------------------------*/
+
+
+#define IT8951_ROTATE_0     0
+#define IT8951_ROTATE_90    1
+#define IT8951_ROTATE_180   2
+#define IT8951_ROTATE_270   3
+
+
+#define IT8951_2BPP   0
+#define IT8951_3BPP   1
+#define IT8951_4BPP   2
+#define IT8951_8BPP   3
+
+
+#define IT8951_LDIMG_L_ENDIAN   0
+#define IT8951_LDIMG_B_ENDIAN   1
+
+/*-----------------------------------------------------------------------
+IT8951 Registers defines
+------------------------------------------------------------------------*/
+
+#define DISPLAY_REG_BASE 0x1000               
+
+
+#define LUT0EWHR  (DISPLAY_REG_BASE + 0x00)   
+#define LUT0XYR   (DISPLAY_REG_BASE + 0x40)   
+#define LUT0BADDR (DISPLAY_REG_BASE + 0x80)   
+#define LUT0MFN   (DISPLAY_REG_BASE + 0xC0)   
+#define LUT01AF   (DISPLAY_REG_BASE + 0x114)  
+
+
+#define UP0SR     (DISPLAY_REG_BASE + 0x134)  
+#define UP1SR     (DISPLAY_REG_BASE + 0x138)  
+#define LUT0ABFRV (DISPLAY_REG_BASE + 0x13C)  
+#define UPBBADDR  (DISPLAY_REG_BASE + 0x17C)  
+#define LUT0IMXY  (DISPLAY_REG_BASE + 0x180)  
+#define LUTAFSR   (DISPLAY_REG_BASE + 0x224)  
+#define BGVR      (DISPLAY_REG_BASE + 0x250)  
+
+
+#define SYS_REG_BASE 0x0000
+
+
+#define I80CPCR (SYS_REG_BASE + 0x04)
+
+
+#define MCSR_BASE_ADDR 0x0200
+#define MCSR  (MCSR_BASE_ADDR + 0x0000)
+#define LISAR (MCSR_BASE_ADDR + 0x0008)
+
+
+/*
+void EPD_IT8951_SystemRun();
+void EPD_IT8951_Standby();
+void EPD_IT8951_Sleep();
+
+UWORD EPD_IT8951_ReadReg(UWORD Reg_Address);
+void EPD_IT8951_WriteReg(UWORD Reg_Address,UWORD Reg_Value);
+UWORD EPD_IT8951_GetVCOM(void);
+void EPD_IT8951_SetVCOM(UWORD VCOM);
+
+void EPD_IT8951_LoadImgStart( IT8951_Load_Img_Info* Load_Img_Info );
+void EPD_IT8951_LoadImgAreaStart( IT8951_Load_Img_Info* Load_Img_Info, IT8951_Area_Img_Info* Area_Img_Info );
+void EPD_IT8951_LoadImgEnd(void);
+
+void EPD_IT8951_GetSystemInfo(void* Buf);
+void EPD_IT8951_SetTargetMemoryAddr(UDOUBLE Target_Memory_Addr);
+void EPD_IT8951_WaitForDisplayReady(void);
+
+
+void EPD_IT8951_HostAreaPackedPixelWrite_8bp(IT8951_Load_Img_Info*Load_Img_Info,IT8951_Area_Img_Info*Area_Img_Info);
+
+void EPD_IT8951_HostAreaPackedPixelWrite_1bp(IT8951_Load_Img_Info*Load_Img_Info,IT8951_Area_Img_Info*Area_Img_Info, bool Packed_Write);
+
+void EPD_IT8951_HostAreaPackedPixelWrite_2bp(IT8951_Load_Img_Info*Load_Img_Info,IT8951_Area_Img_Info*Area_Img_Info, bool Packed_Write);
+
+void EPD_IT8951_Display_Area(UWORD X,UWORD Y,UWORD W,UWORD H,UWORD Mode);
+void EPD_IT8951_Display_AreaBuf(UWORD X,UWORD Y,UWORD W,UWORD H,UWORD Mode, UDOUBLE Target_Memory_Addr);
+
+void EPD_IT8951_Display_1bp(UWORD X, UWORD Y, UWORD W, UWORD H, UWORD Mode,UDOUBLE Target_Memory_Addr, UBYTE Front_Gray_Val, UBYTE Back_Gray_Val);
+*/
+
+void Enhance_Driving_Capability(void);
+
+void EPD_IT8951_SystemRun(void);
+
+void EPD_IT8951_Standby(void);
+
+void EPD_IT8951_Sleep(void);
+
+IT8951_Dev_Info EPD_IT8951_Init(UWORD VCOM);
+
+void EPD_IT8951_Clear_Refresh(IT8951_Dev_Info Dev_Info,UDOUBLE Target_Memory_Addr, UWORD Mode);
+
+void EPD_IT8951_1bp_Refresh(UBYTE* Frame_Buf, UWORD X, UWORD Y, UWORD W, UWORD H, UBYTE Mode, UDOUBLE Target_Memory_Addr, bool Packed_Write);
+void EPD_IT8951_1bp_Multi_Frame_Write(UBYTE* Frame_Buf, UWORD X, UWORD Y, UWORD W, UWORD H,UDOUBLE Target_Memory_Addr, bool Packed_Write);
+void EPD_IT8951_1bp_Multi_Frame_Refresh(UWORD X, UWORD Y, UWORD W, UWORD H,UDOUBLE Target_Memory_Addr);
+
+void EPD_IT8951_2bp_Refresh(UBYTE* Frame_Buf, UWORD X, UWORD Y, UWORD W, UWORD H, bool Hold, UDOUBLE Target_Memory_Addr, bool Packed_Write);
+
+void EPD_IT8951_4bp_Refresh(UBYTE* Frame_Buf, UWORD X, UWORD Y, UWORD W, UWORD H, bool Hold, UDOUBLE Target_Memory_Addr, bool Packed_Write);
+
+void EPD_IT8951_8bp_Refresh(UBYTE *Frame_Buf, UWORD X, UWORD Y, UWORD W, UWORD H, bool Hold, UDOUBLE Target_Memory_Addr);
+
+
+
+#endif
diff --git a/frameos/src/drivers/waveshare/it8951/IT8951.nim b/frameos/src/drivers/waveshare/it8951/IT8951.nim
new file mode 100644
index 00000000..71b49ee6
--- /dev/null
+++ b/frameos/src/drivers/waveshare/it8951/IT8951.nim
@@ -0,0 +1,188 @@
+{.compile: "IT8951.c".}
+## ***************************************************************************
+##  | File      	:   EPD_IT8951.h
+##  | Author      :   Waveshare team
+##  | Function    :   IT8951 Common driver
+##  | Info        :
+## ----------------
+##  |	This version:   V1.0
+##  | Date        :   2019-09-17
+##  | 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
+  DEV_Config
+
+var INIT_Mode*: UBYTE
+
+var GC16_Mode*: UBYTE
+
+var A2_Mode*: UBYTE
+
+type
+  IT8951_Load_Img_Info* {.bycopy.} = object
+    Endian_Type*: UWORD
+    Pixel_Format*: UWORD
+    Rotate*: UWORD
+    Source_Buffer_Addr*: ptr UBYTE
+    Target_Memory_Addr*: UDOUBLE
+
+  IT8951_Area_Img_Info* {.bycopy.} = object
+    Area_X*: UWORD
+    Area_Y*: UWORD
+    Area_W*: UWORD
+    Area_H*: UWORD
+
+  IT8951_Dev_Info* {.bycopy.} = object
+    Panel_W*: UWORD
+    Panel_H*: UWORD
+    Memory_Addr_L*: UWORD
+    Memory_Addr_H*: UWORD
+    FW_Version*: array[8, UWORD]
+    LUT_Version*: array[8, UWORD]
+
+
+## -----------------------------------------------------------------------
+## IT8951 Command defines
+## ------------------------------------------------------------------------
+
+const
+  IT8951_TCON_SYS_RUN* = 0x0001
+  IT8951_TCON_STANDBY* = 0x0002
+  IT8951_TCON_SLEEP* = 0x0003
+  IT8951_TCON_REG_RD* = 0x0010
+  IT8951_TCON_REG_WR* = 0x0011
+  IT8951_TCON_MEM_BST_RD_T* = 0x0012
+  IT8951_TCON_MEM_BST_RD_S* = 0x0013
+  IT8951_TCON_MEM_BST_WR* = 0x0014
+  IT8951_TCON_MEM_BST_END* = 0x0015
+  IT8951_TCON_LD_IMG* = 0x0020
+  IT8951_TCON_LD_IMG_AREA* = 0x0021
+  IT8951_TCON_LD_IMG_END* = 0x0022
+  USDEF_I80_CMD_DPY_AREA* = 0x0034
+  USDEF_I80_CMD_GET_DEV_INFO* = 0x0302
+  USDEF_I80_CMD_DPY_BUF_AREA* = 0x0037
+  USDEF_I80_CMD_VCOM* = 0x0039
+
+## -----------------------------------------------------------------------
+##  IT8951 Mode defines
+## ------------------------------------------------------------------------
+
+const
+  IT8951_ROTATE_0* = 0
+  IT8951_ROTATE_90* = 1
+  IT8951_ROTATE_180* = 2
+  IT8951_ROTATE_270* = 3
+  IT8951_2BPP* = 0
+  IT8951_3BPP* = 1
+  IT8951_4BPP* = 2
+  IT8951_8BPP* = 3
+  IT8951_LDIMG_L_ENDIAN* = 0
+  IT8951_LDIMG_B_ENDIAN* = 1
+
+## -----------------------------------------------------------------------
+## IT8951 Registers defines
+## ------------------------------------------------------------------------
+
+const
+  DISPLAY_REG_BASE* = 0x1000
+  LUT0EWHR* = (DISPLAY_REG_BASE + 0x00)
+  LUT0XYR* = (DISPLAY_REG_BASE + 0x40)
+  LUT0BADDR* = (DISPLAY_REG_BASE + 0x80)
+  LUT0MFN* = (DISPLAY_REG_BASE + 0xC0)
+  LUT01AF* = (DISPLAY_REG_BASE + 0x114)
+  UP0SR* = (DISPLAY_REG_BASE + 0x134)
+  UP1SR* = (DISPLAY_REG_BASE + 0x138)
+  LUT0ABFRV* = (DISPLAY_REG_BASE + 0x13C)
+  UPBBADDR* = (DISPLAY_REG_BASE + 0x17C)
+  LUT0IMXY* = (DISPLAY_REG_BASE + 0x180)
+  LUTAFSR* = (DISPLAY_REG_BASE + 0x224)
+  BGVR* = (DISPLAY_REG_BASE + 0x250)
+  SYS_REG_BASE* = 0x0000
+  I80CPCR* = (SYS_REG_BASE + 0x04)
+  MCSR_BASE_ADDR* = 0x0200
+  MCSR* = (MCSR_BASE_ADDR + 0x0000)
+  LISAR* = (MCSR_BASE_ADDR + 0x0008)
+
+##
+## void EPD_IT8951_SystemRun();
+## void EPD_IT8951_Standby();
+## void EPD_IT8951_Sleep();
+##
+## UWORD EPD_IT8951_ReadReg(UWORD Reg_Address);
+## void EPD_IT8951_WriteReg(UWORD Reg_Address,UWORD Reg_Value);
+## UWORD EPD_IT8951_GetVCOM(void);
+## void EPD_IT8951_SetVCOM(UWORD VCOM);
+##
+## void EPD_IT8951_LoadImgStart( IT8951_Load_Img_Info* Load_Img_Info );
+## void EPD_IT8951_LoadImgAreaStart( IT8951_Load_Img_Info* Load_Img_Info, IT8951_Area_Img_Info* Area_Img_Info );
+## void EPD_IT8951_LoadImgEnd(void);
+##
+## void EPD_IT8951_GetSystemInfo(void* Buf);
+## void EPD_IT8951_SetTargetMemoryAddr(UDOUBLE Target_Memory_Addr);
+## void EPD_IT8951_WaitForDisplayReady(void);
+##
+##
+## void EPD_IT8951_HostAreaPackedPixelWrite_8bp(IT8951_Load_Img_Info*Load_Img_Info,IT8951_Area_Img_Info*Area_Img_Info);
+##
+## void EPD_IT8951_HostAreaPackedPixelWrite_1bp(IT8951_Load_Img_Info*Load_Img_Info,IT8951_Area_Img_Info*Area_Img_Info, bool Packed_Write);
+##
+## void EPD_IT8951_HostAreaPackedPixelWrite_2bp(IT8951_Load_Img_Info*Load_Img_Info,IT8951_Area_Img_Info*Area_Img_Info, bool Packed_Write);
+##
+## void EPD_IT8951_Display_Area(UWORD X,UWORD Y,UWORD W,UWORD H,UWORD Mode);
+## void EPD_IT8951_Display_AreaBuf(UWORD X,UWORD Y,UWORD W,UWORD H,UWORD Mode, UDOUBLE Target_Memory_Addr);
+##
+## void EPD_IT8951_Display_1bp(UWORD X, UWORD Y, UWORD W, UWORD H, UWORD Mode,UDOUBLE Target_Memory_Addr, UBYTE Front_Gray_Val, UBYTE Back_Gray_Val);
+##
+
+proc Enhance_Driving_Capability*() {.importc: "Enhance_Driving_Capability".}
+proc EPD_IT8951_SystemRun*() {.importc: "EPD_IT8951_SystemRun".}
+proc EPD_IT8951_Standby*() {.importc: "EPD_IT8951_Standby".}
+proc EPD_IT8951_Sleep*() {.importc: "EPD_IT8951_Sleep".}
+proc EPD_IT8951_Init*(VCOM: UWORD): IT8951_Dev_Info {.importc: "EPD_IT8951_Init".}
+proc EPD_IT8951_Clear_Refresh*(Dev_Info: IT8951_Dev_Info;
+                              Target_Memory_Addr: UDOUBLE; Mode: UWORD) {.
+    importc: "EPD_IT8951_Clear_Refresh".}
+proc EPD_IT8951_1bp_Refresh*(Frame_Buf: ptr UBYTE; X: UWORD; Y: UWORD; W: UWORD; H: UWORD;
+                            Mode: UBYTE; Target_Memory_Addr: UDOUBLE;
+                            Packed_Write: bool) {.
+    importc: "EPD_IT8951_1bp_Refresh".}
+proc EPD_IT8951_1bp_Multi_Frame_Write*(Frame_Buf: ptr UBYTE; X: UWORD; Y: UWORD;
+                                      W: UWORD; H: UWORD;
+                                      Target_Memory_Addr: UDOUBLE;
+                                      Packed_Write: bool) {.
+    importc: "EPD_IT8951_1bp_Multi_Frame_Write".}
+proc EPD_IT8951_1bp_Multi_Frame_Refresh*(X: UWORD; Y: UWORD; W: UWORD; H: UWORD;
+                                        Target_Memory_Addr: UDOUBLE) {.
+    importc: "EPD_IT8951_1bp_Multi_Frame_Refresh".}
+proc EPD_IT8951_2bp_Refresh*(Frame_Buf: ptr UBYTE; X: UWORD; Y: UWORD; W: UWORD; H: UWORD;
+                            Hold: bool; Target_Memory_Addr: UDOUBLE;
+                            Packed_Write: bool) {.
+    importc: "EPD_IT8951_2bp_Refresh".}
+proc EPD_IT8951_4bp_Refresh*(Frame_Buf: ptr UBYTE; X: UWORD; Y: UWORD; W: UWORD; H: UWORD;
+                            Hold: bool; Target_Memory_Addr: UDOUBLE;
+                            Packed_Write: bool) {.
+    importc: "EPD_IT8951_4bp_Refresh".}
+proc EPD_IT8951_8bp_Refresh*(Frame_Buf: ptr UBYTE; X: UWORD; Y: UWORD; W: UWORD; H: UWORD;
+                            Hold: bool; Target_Memory_Addr: UDOUBLE) {.
+    importc: "EPD_IT8951_8bp_Refresh".}
diff --git a/frameos/src/drivers/waveshare/it8951/Makefile b/frameos/src/drivers/waveshare/it8951/Makefile
new file mode 100644
index 00000000..cb3d38e7
--- /dev/null
+++ b/frameos/src/drivers/waveshare/it8951/Makefile
@@ -0,0 +1,25 @@
+# This was used to convert the non-EPD *.h files
+.PHONY: all normal transform-epd-files remove-slashes
+
+all: transform-epd-files
+
+dev-config:
+	`nimble path c2nim`/c2nim --assumendef:DEBUG --assumedef:USE_DEV_LIB --assumedef:RPI --assumendef:JETSON --assumendef:USE_BCM2835_LIB --assumendef:USE_WIRINGPI_LIB --assumendef:USE_LGPIO_LIB --assumendef:DEV_HARDWARE_SPI_DEBUG --assumendef:GPIOD_DEBUG --importc *.h
+
+transform-epd-files: remove-slashes
+	for file in *.h; do \
+		base=$$(basename "$$file" .h); \
+		`nimble path c2nim`/c2nim --assumendef:DEBUG --importc $$file; \
+		echo '{.compile: "'$$base'.c".}' > "$$file.tmp"; \
+		cat "$$base".nim >> "$$file.tmp"; \
+		mv "$$file.tmp" "$$base".nim; \
+	done
+
+
+remove-slashes:
+	# c2nim stumbles if a line ends with an empty comment "//", and makes noise with a full comment
+	@if [ "$$(uname)" = "Darwin" ]; then \
+		find . -type f -name 'EPD_*.h' -exec sed -i '' 's|//.*$$||' {} +; \
+	else \
+		find . -type f -name 'EPD_*.h' -exec sed -i 's|//$$||' {} +; \
+	fi
diff --git a/frameos/src/drivers/waveshare/it8951/examples/example.c b/frameos/src/drivers/waveshare/it8951/examples/example.c
new file mode 100644
index 00000000..2648f366
--- /dev/null
+++ b/frameos/src/drivers/waveshare/it8951/examples/example.c
@@ -0,0 +1,893 @@
+#include "example.h"
+
+#include <time.h> 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+
+#include "../lib/e-Paper/EPD_IT8951.h"
+#include "../lib/GUI/GUI_Paint.h"
+#include "../lib/GUI/GUI_BMPfile.h"
+#include "../lib/Config/Debug.h"
+
+UBYTE *Refresh_Frame_Buf = NULL;
+
+UBYTE *Panel_Frame_Buf = NULL;
+UBYTE *Panel_Area_Frame_Buf = NULL;
+
+bool Four_Byte_Align = false;
+
+extern int epd_mode;
+extern UWORD VCOM;
+extern UBYTE isColor;
+/******************************************************************************
+function: Change direction of display, Called after Paint_NewImage()
+parameter:
+    mode: display mode
+******************************************************************************/
+static void Epd_Mode(int mode)
+{
+	if(mode == 3) {
+		Paint_SetRotate(ROTATE_0);
+		Paint_SetMirroring(MIRROR_NONE);
+		isColor = 1;
+	}else if(mode == 2) {
+		Paint_SetRotate(ROTATE_0);
+		Paint_SetMirroring(MIRROR_HORIZONTAL);
+	}else if(mode == 1) {
+		Paint_SetRotate(ROTATE_0);
+		Paint_SetMirroring(MIRROR_HORIZONTAL);
+	}else {
+		Paint_SetRotate(ROTATE_0);
+		Paint_SetMirroring(MIRROR_NONE);
+	}
+}
+
+
+/******************************************************************************
+function: Display_ColorPalette_Example
+parameter:
+    Panel_Width: Width of the panel
+    Panel_Height: Height of the panel
+    Init_Target_Memory_Addr: Memory address of IT8951 target memory address
+******************************************************************************/
+UBYTE Display_ColorPalette_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Init_Target_Memory_Addr){
+    UWORD In_4bp_Refresh_Area_Width;
+    if(Four_Byte_Align == true){
+        In_4bp_Refresh_Area_Width = Panel_Width - (Panel_Width % 32);
+    }else{
+        In_4bp_Refresh_Area_Width = Panel_Width;
+    }
+    UWORD In_4bp_Refresh_Area_Height = Panel_Height/16;
+
+    UDOUBLE Imagesize;
+
+    clock_t In_4bp_Refresh_Start, In_4bp_Refresh_Finish;
+    double In_4bp_Refresh_Duration;
+
+    Imagesize  = ((In_4bp_Refresh_Area_Width*4 % 8 == 0)? (In_4bp_Refresh_Area_Width*4 / 8 ): (In_4bp_Refresh_Area_Width*4 / 8 + 1)) * In_4bp_Refresh_Area_Height;
+
+    if((Refresh_Frame_Buf = (UBYTE *)malloc(Imagesize)) == NULL) {
+        Debug("Failed to apply for black memory...\r\n");
+        return -1;
+    }
+
+    Debug("Start to demostrate 4bpp palette example\r\n");
+    In_4bp_Refresh_Start = clock();
+
+    UBYTE SixteenColorPattern[16] = {0xFF,0xEE,0xDD,0xCC,0xBB,0xAA,0x99,0x88,0x77,0x66,0x55,0x44,0x33,0x22,0x11,0x00};
+
+    for(int i=0; i < 16; i++){
+        memset(Refresh_Frame_Buf, SixteenColorPattern[i], Imagesize);
+        EPD_IT8951_4bp_Refresh(Refresh_Frame_Buf, 0, i * In_4bp_Refresh_Area_Height, In_4bp_Refresh_Area_Width, In_4bp_Refresh_Area_Height, false, Init_Target_Memory_Addr, false);
+    }
+
+    In_4bp_Refresh_Finish = clock();
+    In_4bp_Refresh_Duration = (double)(In_4bp_Refresh_Finish - In_4bp_Refresh_Start) / CLOCKS_PER_SEC;
+    Debug( "Write and Show 4bp occupy %f second\n", In_4bp_Refresh_Duration );
+
+    if(Refresh_Frame_Buf != NULL){
+        free(Refresh_Frame_Buf);
+        Refresh_Frame_Buf = NULL;
+    }
+    return 0;
+}
+
+
+/******************************************************************************
+function: Display_CharacterPattern_Example
+parameter:
+    Panel_Width: Width of the panel
+    Panel_Height: Height of the panel
+    Init_Target_Memory_Addr: Memory address of IT8951 target memory address
+    BitsPerPixel: Bits Per Pixel, 2^BitsPerPixel = grayscale
+******************************************************************************/
+UBYTE Display_CharacterPattern_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Init_Target_Memory_Addr, UBYTE BitsPerPixel){
+    UWORD Display_Area_Width;
+    if(Four_Byte_Align == true){
+        Display_Area_Width = Panel_Width - (Panel_Width % 32);
+    }else{
+        Display_Area_Width = Panel_Width;
+    }
+    UWORD Display_Area_Height = Panel_Height;
+
+    UWORD Display_Area_Sub_Width = Display_Area_Width / 5;
+    UWORD Display_Area_Sub_Height = Display_Area_Height / 5;
+
+    UDOUBLE Imagesize;
+
+    Imagesize = ((Display_Area_Width * BitsPerPixel % 8 == 0)? (Display_Area_Width * BitsPerPixel / 8 ): (Display_Area_Width * BitsPerPixel / 8 + 1)) * Display_Area_Height;
+    if((Refresh_Frame_Buf = (UBYTE *)malloc(Imagesize)) == NULL) {
+        Debug("Failed to apply for image memory...\r\n");
+        return -1;
+    }
+
+    Paint_NewImage(Refresh_Frame_Buf, Display_Area_Width, Display_Area_Height, 0, BLACK);
+    Paint_SelectImage(Refresh_Frame_Buf);
+	Epd_Mode(epd_mode);
+    Paint_SetBitsPerPixel(BitsPerPixel);
+    Paint_Clear(WHITE);
+    
+
+    for(int y=20; y<Display_Area_Height - Display_Area_Sub_Height; y += Display_Area_Sub_Height )//To prevent arrays from going out of bounds
+    {
+        for(int  x=20; x<Display_Area_Width - Display_Area_Sub_Width; x += Display_Area_Sub_Width )//To prevent arrays from going out of bounds
+        {
+            //For color definition of all BitsPerPixel, you can refer to GUI_Paint.h
+            Paint_DrawPoint(x+Display_Area_Sub_Width*3/8, y+Display_Area_Sub_Height*3/8, 0x10, DOT_PIXEL_7X7, DOT_STYLE_DFT);
+            Paint_DrawPoint(x+Display_Area_Sub_Width*5/8, y+Display_Area_Sub_Height*3/8, 0x30, DOT_PIXEL_7X7, DOT_STYLE_DFT);
+            Paint_DrawLine(x+Display_Area_Sub_Width*3/8, y+Display_Area_Sub_Height*5/8, x+Display_Area_Sub_Width*5/8, y+Display_Area_Sub_Height*5/8, 0x50, DOT_PIXEL_3X3, LINE_STYLE_SOLID);
+            Paint_DrawRectangle(x, y, x+Display_Area_Sub_Width, y+Display_Area_Sub_Height, 0x00, DOT_PIXEL_3X3, DRAW_FILL_EMPTY);
+            Paint_DrawCircle(x + Display_Area_Sub_Width/2, y + Display_Area_Sub_Height/2, Display_Area_Sub_Height/2, 0x50, DOT_PIXEL_2X2, DRAW_FILL_EMPTY);
+            Paint_DrawNum(x+Display_Area_Sub_Width*3/10, y+Display_Area_Sub_Height*1/4, 1234567890, &Font16, 0x20, 0xE0);
+            Paint_DrawString_EN(x+Display_Area_Sub_Width*3/10, y+Display_Area_Sub_Height*3/4, "hello world", &Font16, 0x30, 0xD0);
+        }
+    }
+
+
+    switch(BitsPerPixel){
+        case BitsPerPixel_8:{
+            EPD_IT8951_8bp_Refresh(Refresh_Frame_Buf, 0, 0, Display_Area_Width,  Display_Area_Height, false, Init_Target_Memory_Addr);
+            break;
+        }
+        case BitsPerPixel_4:{
+            EPD_IT8951_4bp_Refresh(Refresh_Frame_Buf, 0, 0, Display_Area_Width,  Display_Area_Height, false, Init_Target_Memory_Addr,false);
+            break;
+        }
+        case BitsPerPixel_2:{
+            EPD_IT8951_2bp_Refresh(Refresh_Frame_Buf, 0, 0, Display_Area_Width,  Display_Area_Height, false, Init_Target_Memory_Addr,false);
+            break;
+        }
+        case BitsPerPixel_1:{
+            EPD_IT8951_1bp_Refresh(Refresh_Frame_Buf, 0, 0, Display_Area_Width,  Display_Area_Height, A2_Mode, Init_Target_Memory_Addr,false);
+            break;
+        }
+    }
+
+    if(Refresh_Frame_Buf != NULL){
+        free(Refresh_Frame_Buf);
+        Refresh_Frame_Buf = NULL;
+    }
+
+    return 0;
+}
+
+
+
+/******************************************************************************
+function: Display_BMP_Example
+parameter:
+    Panel_Width: Width of the panel
+    Panel_Height: Height of the panel
+    Init_Target_Memory_Addr: Memory address of IT8951 target memory address
+    BitsPerPixel: Bits Per Pixel, 2^BitsPerPixel = grayscale
+******************************************************************************/
+UBYTE Display_BMP_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Init_Target_Memory_Addr, UBYTE BitsPerPixel){
+    UWORD WIDTH;
+    if(Four_Byte_Align == true){
+        WIDTH  = Panel_Width - (Panel_Width % 32);
+    }else{
+        WIDTH = Panel_Width;
+    }
+    UWORD HEIGHT = Panel_Height;
+
+    UDOUBLE Imagesize;
+
+    Imagesize = ((WIDTH * BitsPerPixel % 8 == 0)? (WIDTH * BitsPerPixel / 8 ): (WIDTH * BitsPerPixel / 8 + 1)) * HEIGHT;
+    if((Refresh_Frame_Buf = (UBYTE *)malloc(Imagesize)) == NULL) {
+        Debug("Failed to apply for black memory...\r\n");
+        return -1;
+    }
+
+    Paint_NewImage(Refresh_Frame_Buf, WIDTH, HEIGHT, 0, BLACK);
+    Paint_SelectImage(Refresh_Frame_Buf);
+	Epd_Mode(epd_mode);
+    Paint_SetBitsPerPixel(BitsPerPixel);
+    Paint_Clear(WHITE);
+
+    char Path[30];
+    sprintf(Path,"./pic/%dx%d_0.bmp", WIDTH, HEIGHT);
+
+    GUI_ReadBmp(Path, 0, 0);
+
+    //you can draw your character and pattern on the image, for color definition of all BitsPerPixel, you can refer to GUI_Paint.h, 
+    //Paint_DrawRectangle(50, 50, WIDTH/2, HEIGHT/2, 0x30, DOT_PIXEL_3X3, DRAW_FILL_EMPTY);
+    //Paint_DrawCircle(WIDTH*3/4, HEIGHT/4, 100, 0xF0, DOT_PIXEL_2X2, DRAW_FILL_EMPTY);
+    //Paint_DrawNum(WIDTH/4, HEIGHT/5, 709, &Font20, 0x30, 0xB0);
+
+    switch(BitsPerPixel){
+        case BitsPerPixel_8:{
+            Paint_DrawString_EN(10, 10, "8 bits per pixel 16 grayscale", &Font24, 0xF0, 0x00);
+            EPD_IT8951_8bp_Refresh(Refresh_Frame_Buf, 0, 0, WIDTH,  HEIGHT, false, Init_Target_Memory_Addr);
+            break;
+        }
+        case BitsPerPixel_4:{
+            Paint_DrawString_EN(10, 10, "4 bits per pixel 16 grayscale", &Font24, 0xF0, 0x00);
+            EPD_IT8951_4bp_Refresh(Refresh_Frame_Buf, 0, 0, WIDTH,  HEIGHT, false, Init_Target_Memory_Addr,false);
+            break;
+        }
+        case BitsPerPixel_2:{
+            Paint_DrawString_EN(10, 10, "2 bits per pixel 4 grayscale", &Font24, 0xC0, 0x00);
+            EPD_IT8951_2bp_Refresh(Refresh_Frame_Buf, 0, 0, WIDTH,  HEIGHT, false, Init_Target_Memory_Addr,false);
+            break;
+        }
+        case BitsPerPixel_1:{
+            Paint_DrawString_EN(10, 10, "1 bit per pixel 2 grayscale", &Font24, 0x80, 0x00);
+            EPD_IT8951_1bp_Refresh(Refresh_Frame_Buf, 0, 0, WIDTH,  HEIGHT, A2_Mode, Init_Target_Memory_Addr,false);
+            break;
+        }
+    }
+
+    if(Refresh_Frame_Buf != NULL){
+        free(Refresh_Frame_Buf);
+        Refresh_Frame_Buf = NULL;
+    }
+
+    DEV_Delay_ms(5000);
+
+    return 0;
+}
+
+
+/******************************************************************************
+function: Dynamic_Refresh_Example
+parameter:
+    Dev_Info: Information structure read from IT8951
+    Init_Target_Memory_Addr: Memory address of IT8951 target memory address
+******************************************************************************/
+UBYTE Dynamic_Refresh_Example(IT8951_Dev_Info Dev_Info, UDOUBLE Init_Target_Memory_Addr){
+    UWORD Panel_Width = Dev_Info.Panel_W;
+    UWORD Panel_Height = Dev_Info.Panel_H;
+
+    UWORD Dynamic_Area_Width = 96;
+    UWORD Dynamic_Area_Height = 48;
+
+    UDOUBLE Imagesize;
+
+    UWORD Start_X = 0,Start_Y = 0;
+
+    UWORD Dynamic_Area_Count = 0;
+
+    UWORD Repeat_Area_Times = 0;
+
+    //malloc enough memory for 1bp picture first
+    Imagesize = ((Panel_Width * 1 % 8 == 0)? (Panel_Width * 1 / 8 ): (Panel_Width * 1 / 8 + 1)) * Panel_Height;
+    if((Refresh_Frame_Buf = (UBYTE *)malloc(Imagesize)) == NULL){
+        Debug("Failed to apply for picture memory...\r\n");
+        return -1;
+    }
+
+    clock_t Dynamic_Area_Start, Dynamic_Area_Finish;
+    double Dynamic_Area_Duration;  
+
+    while(1)
+    {
+        Dynamic_Area_Width = 128;
+        Dynamic_Area_Height = 96;
+
+        Start_X = 0;
+        Start_Y = 0;
+
+        Dynamic_Area_Count = 0;
+
+        Dynamic_Area_Start = clock();
+        Debug("Start to dynamic display...\r\n");
+
+        for(Dynamic_Area_Width = 96, Dynamic_Area_Height = 64; (Dynamic_Area_Width < Panel_Width - 32) && (Dynamic_Area_Height < Panel_Height - 24); Dynamic_Area_Width += 32, Dynamic_Area_Height += 24)
+        {
+
+            Imagesize = ((Dynamic_Area_Width % 8 == 0)? (Dynamic_Area_Width / 8 ): (Dynamic_Area_Width / 8 + 1)) * Dynamic_Area_Height;
+            Paint_NewImage(Refresh_Frame_Buf, Dynamic_Area_Width, Dynamic_Area_Height, 0, BLACK);
+            Paint_SelectImage(Refresh_Frame_Buf);
+			Epd_Mode(epd_mode);
+            Paint_SetBitsPerPixel(1);
+
+           for(int y=Start_Y; y< Panel_Height - Dynamic_Area_Height; y += Dynamic_Area_Height)
+            {
+                for(int x=Start_X; x< Panel_Width - Dynamic_Area_Width; x += Dynamic_Area_Width)
+                {
+                    Paint_Clear(WHITE);
+
+                    //For color definition of all BitsPerPixel, you can refer to GUI_Paint.h
+                    Paint_DrawRectangle(0, 0, Dynamic_Area_Width-1, Dynamic_Area_Height, 0x00, DOT_PIXEL_2X2, DRAW_FILL_EMPTY);
+
+                    Paint_DrawCircle(Dynamic_Area_Width*3/4, Dynamic_Area_Height*3/4, 5, 0x00, DOT_PIXEL_1X1, DRAW_FILL_FULL);
+
+                    Paint_DrawNum(Dynamic_Area_Width/4, Dynamic_Area_Height/4, ++Dynamic_Area_Count, &Font20, 0x00, 0xF0);
+
+					if(epd_mode == 2)
+						EPD_IT8951_1bp_Refresh(Refresh_Frame_Buf, 1280-Dynamic_Area_Width-x, y, Dynamic_Area_Width,  Dynamic_Area_Height, A2_Mode, Init_Target_Memory_Addr, true);
+					else if(epd_mode == 1)
+						EPD_IT8951_1bp_Refresh(Refresh_Frame_Buf, Panel_Width-Dynamic_Area_Width-x-16, y, Dynamic_Area_Width,  Dynamic_Area_Height, A2_Mode, Init_Target_Memory_Addr, true);
+                    else
+						EPD_IT8951_1bp_Refresh(Refresh_Frame_Buf, x, y, Dynamic_Area_Width,  Dynamic_Area_Height, A2_Mode, Init_Target_Memory_Addr, true);
+                }
+            }
+            Start_X += 32;
+            Start_Y += 24;
+        }
+
+        Dynamic_Area_Finish = clock();
+        Dynamic_Area_Duration = (double)(Dynamic_Area_Finish - Dynamic_Area_Start) / CLOCKS_PER_SEC;
+        Debug( "Write and Show occupy %f second\n", Dynamic_Area_Duration );
+
+        Repeat_Area_Times ++;
+        if(Repeat_Area_Times > 0){
+            break;
+        }
+    }
+    if(Refresh_Frame_Buf != NULL){
+        free(Refresh_Frame_Buf);
+        Refresh_Frame_Buf = NULL;
+    }
+
+    return 0;
+}
+
+
+/******************************************************************************
+function: Dynamic_GIF_Example
+parameter:
+    Panel_Width: Width of the panel
+    Panel_Height: Height of the panel
+    Init_Target_Memory_Addr: Memory address of IT8951 target memory address
+    BitsPerPixel: Bits Per Pixel, 2^BitsPerPixel = grayscale
+******************************************************************************/
+UBYTE Dynamic_GIF_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Init_Target_Memory_Addr){
+
+    UWORD Animation_Start_X = 0;
+    UWORD Animation_Start_Y = 0;
+    UWORD Animation_Area_Width = 800;
+    UWORD Animation_Area_Height = 600;
+
+    if(Animation_Area_Width > Panel_Width){
+        return -1;
+    }
+    if(Animation_Area_Height > Panel_Height){
+        return -1;
+    }
+
+    UDOUBLE Imagesize;
+
+    UBYTE Pic_Count = 0;
+    UBYTE Pic_Num = 7;
+    char Path[30];
+
+    UDOUBLE Basical_Memory_Addr = Init_Target_Memory_Addr;
+
+    UDOUBLE Target_Memory_Addr = Basical_Memory_Addr;
+    UWORD Repeat_Animation_Times = 0;
+
+    clock_t Animation_Test_Start, Animation_Test_Finish;
+    double Animation_Test_Duration;
+
+    Imagesize = ((Animation_Area_Width * 1 % 8 == 0)? (Animation_Area_Width * 1 / 8 ): (Animation_Area_Width * 1 / 8 + 1)) * Animation_Area_Height;
+
+    if((Refresh_Frame_Buf = (UBYTE *)malloc(Imagesize)) == NULL){
+        Debug("Failed to apply for image memory...\r\n");
+        return -1;
+    }
+
+    Paint_NewImage(Refresh_Frame_Buf, Animation_Area_Width, Animation_Area_Height, 0, BLACK);
+    Paint_SelectImage(Refresh_Frame_Buf);
+	Epd_Mode(epd_mode);
+    Paint_SetBitsPerPixel(1);
+
+    Debug("Start to write a animation\r\n");
+    Animation_Test_Start = clock();
+    for(int i=0; i < Pic_Num; i += 1){
+        Paint_Clear(WHITE);
+        sprintf(Path,"./pic/800x600_gif_%d.bmp",Pic_Count++);
+        GUI_ReadBmp(Path, 0, 0);
+        //For color definition of all BitsPerPixel, you can refer to GUI_Paint.h
+        Paint_DrawNum(10, 10, i+1, &Font16, 0x00, 0xF0);
+		if(epd_mode == 2)
+			EPD_IT8951_1bp_Multi_Frame_Write(Refresh_Frame_Buf, 1280-Animation_Area_Width+Animation_Start_X, Animation_Start_Y, Animation_Area_Width,  Animation_Area_Height, Target_Memory_Addr,false);
+        else if(epd_mode == 1)
+			EPD_IT8951_1bp_Multi_Frame_Write(Refresh_Frame_Buf, Panel_Width-Animation_Area_Width+Animation_Start_X-16, Animation_Start_Y, Animation_Area_Width,  Animation_Area_Height, Target_Memory_Addr,false);
+		else
+			EPD_IT8951_1bp_Multi_Frame_Write(Refresh_Frame_Buf, Animation_Start_X, Animation_Start_Y, Animation_Area_Width,  Animation_Area_Height, Target_Memory_Addr,false);
+        Target_Memory_Addr += Imagesize;
+    }
+
+    Animation_Test_Finish = clock();
+    Animation_Test_Duration = (double)(Animation_Test_Finish - Animation_Test_Start) / CLOCKS_PER_SEC;
+	Debug( "Write all frame occupy %f second\r\n", Animation_Test_Duration);
+
+    Target_Memory_Addr = Basical_Memory_Addr;
+
+    while(1){
+        Debug("Start to show a animation\r\n");
+        Animation_Test_Start = clock();
+
+        for(int i=0; i< Pic_Num; i += 1){
+			if(epd_mode == 2)
+				EPD_IT8951_1bp_Multi_Frame_Refresh(Panel_Width-Animation_Area_Width+Animation_Start_X, Animation_Start_Y, Animation_Area_Width,  Animation_Area_Height, Target_Memory_Addr);
+			else if(epd_mode == 1)
+				EPD_IT8951_1bp_Multi_Frame_Refresh(Panel_Width-Animation_Area_Width+Animation_Start_X-16, Animation_Start_Y, Animation_Area_Width,  Animation_Area_Height, Target_Memory_Addr);
+            else
+				EPD_IT8951_1bp_Multi_Frame_Refresh(Animation_Start_X, Animation_Start_Y, Animation_Area_Width,  Animation_Area_Height, Target_Memory_Addr);
+            Target_Memory_Addr += Imagesize;
+        }
+        Target_Memory_Addr = Basical_Memory_Addr;
+
+        Animation_Test_Finish = clock();
+        Animation_Test_Duration = (double)(Animation_Test_Finish - Animation_Test_Start) / CLOCKS_PER_SEC;
+        Debug( "Show all frame occupy %f second\r\n", Animation_Test_Duration );
+   		Debug( "The frame rate is: %lf fps\r\n", Pic_Num/Animation_Test_Duration);
+
+        Repeat_Animation_Times ++;
+        if(Repeat_Animation_Times >15){
+            break;
+        }
+    }
+
+    if(Refresh_Frame_Buf != NULL){
+        free(Refresh_Frame_Buf);
+        Refresh_Frame_Buf = NULL;
+    }
+
+    return 0;
+}
+
+
+
+/******************************************************************************
+function: Check_FrameRate_Example
+parameter:
+    Panel_Width: Width of the panel
+    Panel_Height: Height of the panel
+    Init_Target_Memory_Addr: Memory address of IT8951 target memory address
+    BitsPerPixel: Bits Per Pixel, 2^BitsPerPixel = grayscale
+******************************************************************************/
+UBYTE Check_FrameRate_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Target_Memory_Addr, UBYTE BitsPerPixel){
+    UWORD Frame_Rate_Test_Width;
+    if(Four_Byte_Align == true){
+        Frame_Rate_Test_Width = Panel_Width - (Panel_Width % 32);
+    }else{
+        Frame_Rate_Test_Width = Panel_Width;
+    }
+    UWORD Frame_Rate_Test_Height = Panel_Height;
+    UDOUBLE Imagesize;
+
+    UBYTE *Refresh_FrameRate_Buf = NULL;
+
+    UBYTE Count = 0;
+
+    clock_t Frame_Rate_Test_Start, Frame_Rate_Test_Finish;
+    double Frame_Rate_Test_Duration;
+
+    Imagesize = ((Frame_Rate_Test_Width * BitsPerPixel % 8 == 0)? (Frame_Rate_Test_Width * BitsPerPixel / 8 ): (Frame_Rate_Test_Width * BitsPerPixel / 8 + 1)) * Frame_Rate_Test_Height;
+
+    if((Refresh_FrameRate_Buf = (UBYTE *)malloc(Imagesize)) == NULL) {
+        Debug("Failed to apply for image memory...\r\n");
+        return -1;
+    }
+
+    Paint_NewImage(Refresh_FrameRate_Buf, Frame_Rate_Test_Width, Frame_Rate_Test_Height, 0, BLACK);
+    Paint_SelectImage(Refresh_FrameRate_Buf);
+	Epd_Mode(epd_mode);
+    Paint_SetBitsPerPixel(BitsPerPixel);
+
+    Debug("Start to test Frame Rate\r\n");
+    Frame_Rate_Test_Start = clock();
+
+    for(int i=0; i<10; i++){
+
+        Paint_Clear(WHITE);
+        //For color definition of all BitsPerPixel, you can refer to GUI_Paint.h
+        Paint_DrawRectangle(20, 20, Frame_Rate_Test_Width-20, Frame_Rate_Test_Height-20, 0x00, DOT_PIXEL_4X4, DRAW_FILL_EMPTY);//To prevent arrays from going out of bounds
+        Paint_DrawNum(Frame_Rate_Test_Width/2, Frame_Rate_Test_Height/2, ++Count, &Font24, 0x00, 0xF0);
+        Paint_DrawString_EN(Frame_Rate_Test_Width/2, Frame_Rate_Test_Height/4, "frame rate test", &Font20, 0xF0, 0x00);
+        Paint_DrawString_EN(Frame_Rate_Test_Width/2, Frame_Rate_Test_Height*3/4, "frame rate test", &Font20, 0xF0, 0x00);
+
+        switch(BitsPerPixel){
+            case 8:{
+				if(epd_mode == 2)
+					EPD_IT8951_8bp_Refresh(Refresh_FrameRate_Buf, 1280-Frame_Rate_Test_Width, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, false, Target_Memory_Addr);
+				else if(epd_mode == 1)
+					EPD_IT8951_8bp_Refresh(Refresh_FrameRate_Buf, 1872-Frame_Rate_Test_Width-16, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, false, Target_Memory_Addr);
+				else					
+					EPD_IT8951_8bp_Refresh(Refresh_FrameRate_Buf, 0, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, false, Target_Memory_Addr);
+                break;
+            }
+            case 4:{
+				if(epd_mode == 2)
+					EPD_IT8951_4bp_Refresh(Refresh_FrameRate_Buf, 1280-Frame_Rate_Test_Width, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, false, Target_Memory_Addr,false);
+				else if(epd_mode == 1)
+					EPD_IT8951_4bp_Refresh(Refresh_FrameRate_Buf, 1872-Frame_Rate_Test_Width-16, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, false, Target_Memory_Addr,false);
+				else
+					EPD_IT8951_4bp_Refresh(Refresh_FrameRate_Buf, 0, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, false, Target_Memory_Addr,false);
+                break;
+            }
+            case 2:{
+				if(epd_mode == 2)
+					EPD_IT8951_2bp_Refresh(Refresh_FrameRate_Buf, 1280-Frame_Rate_Test_Width, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, false, Target_Memory_Addr,false);
+				else if(epd_mode == 1)
+					EPD_IT8951_2bp_Refresh(Refresh_FrameRate_Buf, 1872-Frame_Rate_Test_Width-16, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, false, Target_Memory_Addr,false);
+				else	
+					EPD_IT8951_2bp_Refresh(Refresh_FrameRate_Buf, 0, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, false, Target_Memory_Addr,false);
+                break;
+            }
+            case 1:{
+				if(epd_mode == 2)
+					EPD_IT8951_1bp_Refresh(Refresh_FrameRate_Buf, 1280-Frame_Rate_Test_Width, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, A2_Mode, Target_Memory_Addr,false);
+				else if(epd_mode == 1)
+					EPD_IT8951_1bp_Refresh(Refresh_FrameRate_Buf, 1872-Frame_Rate_Test_Width-16, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, A2_Mode, Target_Memory_Addr,false);
+				else	
+					EPD_IT8951_1bp_Refresh(Refresh_FrameRate_Buf, 0, 0, Frame_Rate_Test_Width,  Frame_Rate_Test_Height, A2_Mode, Target_Memory_Addr,false);
+                break;
+            }
+        }
+    }
+
+    Frame_Rate_Test_Finish = clock();
+    Frame_Rate_Test_Duration = (double)(Frame_Rate_Test_Finish - Frame_Rate_Test_Start) / CLOCKS_PER_SEC;
+	Debug( "Write and Show 10 Frame occupy %f second\r\n", Frame_Rate_Test_Duration);
+    Debug( "The frame rate is: %lf fps\r\n", 10/Frame_Rate_Test_Duration);
+
+    if(Refresh_FrameRate_Buf != NULL){
+        free(Refresh_FrameRate_Buf);
+        Refresh_FrameRate_Buf = NULL;
+    }
+
+    return 0;
+}
+
+/******************************************************************************
+function: TouchPanel_ePaper_Example
+parameter:
+    Panel_Width: Width of the panel
+    Panel_Height: Height of the panel
+    Init_Target_Memory_Addr: Memory address of IT8951 target memory address
+******************************************************************************/
+UBYTE TouchPanel_ePaper_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Init_Target_Memory_Addr){
+    int ret,fd;
+    UWORD Touch_Pannel_Area_Width;
+    if(Four_Byte_Align == true){
+        Touch_Pannel_Area_Width = Panel_Width - (Panel_Width % 32);
+    }else{
+        Touch_Pannel_Area_Width = Panel_Width;
+    }
+    UWORD Touch_Pannel_Area_Height = Panel_Height;
+
+    UDOUBLE Imagesize;
+
+    UWORD Touch_Point0[2];
+    UWORD Touch_X = 0,Touch_Y = 0;
+    UWORD Touch_X_Old = 0,Touch_Y_Old = 0;
+
+    UWORD Min_X = Panel_Width, Max_X = 0;
+    UWORD Min_Y = Panel_Height, Max_Y = 0;
+
+    UWORD X_Start, X_End, Y_Start, Y_End, Width, Height;
+
+    UBYTE First_Point_Flag = 1;
+
+    UWORD Panel_Frame_Buf_WidthByte;
+    UWORD Panel_Area_Frame_Buf_WidthByte;
+
+	if(access("/home/pi/FIFO",F_OK)){
+		ret = mkfifo("/home/pi/FIFO",0777);
+		if(ret == -1){
+			Debug("mkfifo error!\n");
+			exit(0);
+		}
+	}
+    fd = open("/home/pi/FIFO",O_RDWR|O_NONBLOCK);
+    if( fd == -1 )
+    {
+        Debug("Open error\r\n");
+        exit(0);
+    }
+
+    Panel_Frame_Buf_WidthByte =  (Touch_Pannel_Area_Width * 1 % 8 == 0) ? (Touch_Pannel_Area_Width * 1 / 8 ): (Touch_Pannel_Area_Width * 1 / 8 + 1);
+
+    Imagesize = ((Touch_Pannel_Area_Width * 1 % 8 == 0) ? (Touch_Pannel_Area_Width * 1 / 8 ): (Touch_Pannel_Area_Width *1 / 8 + 1)) * Touch_Pannel_Area_Height;
+
+    if((Panel_Frame_Buf = (UBYTE *)malloc(Imagesize)) == NULL) {
+        Debug("Failed to apply for Panel_Frame_Buf memory...\r\n");
+        return -1;
+    }
+
+    //Assume the entire screen is refreshed
+    if((Panel_Area_Frame_Buf = (UBYTE *)malloc(Imagesize)) == NULL) {
+        Debug("Failed to apply for Panel_Area_Frame_Buf memory...\r\n");
+        return -1;
+    }
+
+    Paint_NewImage(Panel_Frame_Buf, Touch_Pannel_Area_Width, Touch_Pannel_Area_Height, 0, BLACK);
+    Paint_SelectImage(Panel_Frame_Buf);
+	Epd_Mode(epd_mode);
+    Paint_SetBitsPerPixel(1);
+    Paint_Clear(WHITE);
+
+    while(1)
+    {
+        Min_X = Panel_Width;
+        Max_X = 0;
+        Min_Y = Panel_Height;
+        Max_Y = 0;
+
+        First_Point_Flag = 1;
+
+        ret = read(fd,Touch_Point0,sizeof(Touch_Point0));
+
+        //Prepare the image
+        while(ret == 4){
+            Touch_X = Touch_Point0[0];
+            Touch_Y = Touch_Point0[1];
+            Paint_NewImage(Panel_Frame_Buf, Touch_Pannel_Area_Width, Touch_Pannel_Area_Height, 0, BLACK);
+            Paint_SelectImage(Panel_Frame_Buf);
+            Paint_SetBitsPerPixel(1);
+
+            if(First_Point_Flag == 0)
+            {
+                //For color definition of all BitsPerPixel, you can refer to GUI_Paint.h
+                Paint_DrawLine(Touch_X_Old, Touch_Y_Old, Touch_X, Touch_Y, 0x00, DOT_PIXEL_3X3, LINE_STYLE_SOLID);
+            }
+            Touch_X_Old = Touch_X;
+            Touch_Y_Old = Touch_Y;
+            First_Point_Flag = 0;
+
+            if(Touch_X < Min_X){
+                Min_X = Touch_X;
+            }
+            if(Touch_X > Max_X){
+                Max_X = Touch_X;
+            }
+            if(Touch_Y < Min_Y){
+                Min_Y = Touch_Y;
+            }
+            if(Touch_Y > Max_Y){
+                Max_Y = Touch_Y;
+            }
+
+            DEV_Delay_ms(15);
+
+            Debug("Touch_X:%d\r\n",Touch_X);
+            Debug("Touch_Y:%d\r\n",Touch_Y);
+            ret = read(fd,Touch_Point0,sizeof(Touch_Point0));
+            Debug("Stop ret:%d\r\n",ret);
+        }
+
+        //If Min < Max, then Indicates that there is a refresh area
+        if( (Min_X<Max_X)||(Min_Y<Max_Y) )
+        {
+            //----------Calculate Data, please be cautious that the width must be 32-bit aligned----------
+            Debug("Min_X - Max_X Output:%d~%d\r\n",Min_X,Max_X);
+            Debug("Min_Y - Max_Y Output:%d~%d\r\n",Min_Y,Max_Y);
+
+            X_Start = Min_X < 32 ? 0 : Min_X - (Min_X % 32);
+            Debug("X_Start:%d\r\n",X_Start);
+            X_End = ( Max_X + (32 - (Max_X % 32)) ) > Touch_Pannel_Area_Width ? ( Max_X - (Max_X % 32) )  : ( Max_X + (32 - (Max_X % 32)) );
+            Debug("X_End:%d\r\n",X_End);
+            Y_Start = Min_Y;
+            Debug("Y_Start:%d\r\n",Y_Start);
+            Y_End = Max_Y;
+            Debug("Y_Start:%d\r\n",Y_End);
+
+            Width = X_End - X_Start;
+            if(Width<=0){
+                Width = 32;
+            }
+            Debug("Width:%d\r\n",Width);
+            Height = Y_End-Y_Start;
+            if(Height<=0){
+                Height = 32;
+            }
+            Debug("Height:%d\r\n",Height);
+
+            //----------Prepare Image----------
+            Paint_NewImage(Panel_Area_Frame_Buf, Width, Height, 0, BLACK);
+            Paint_SelectImage(Panel_Area_Frame_Buf);
+			Epd_Mode(epd_mode);
+            Paint_Clear(WHITE);
+
+            Panel_Area_Frame_Buf_WidthByte = (Width % 8 == 0) ? (Width / 8 ): (Width / 8 + 1);
+
+            for(int y = 0; y< Height; y++){
+                memcpy(Panel_Area_Frame_Buf + (y * Panel_Area_Frame_Buf_WidthByte), Panel_Frame_Buf + ((Y_Start + y) * Panel_Frame_Buf_WidthByte) + X_Start/8 , Panel_Area_Frame_Buf_WidthByte);
+            }
+
+            //----------Display Image----------
+            EPD_IT8951_1bp_Refresh(Panel_Area_Frame_Buf, X_Start, Y_Start, Width,  Height, A2_Mode, Init_Target_Memory_Addr, true);
+        }
+    }
+
+    if( Panel_Area_Frame_Buf != NULL ){
+        free(Panel_Area_Frame_Buf);
+        Panel_Area_Frame_Buf = NULL;
+    }
+
+    if( Panel_Frame_Buf != NULL ){
+        free(Panel_Frame_Buf);
+        Panel_Frame_Buf = NULL;
+    }
+
+    return 0;
+}
+
+
+
+static UBYTE BMP_Test(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Init_Target_Memory_Addr, UBYTE BitsPerPixel, UBYTE Pic_Count)
+{
+    UWORD WIDTH;
+
+    if(Four_Byte_Align == true){
+        WIDTH  = Panel_Width - (Panel_Width % 32);
+    }else{
+        WIDTH = Panel_Width;
+    }
+    UWORD HEIGHT = Panel_Height;
+
+    UDOUBLE Imagesize;
+
+    Imagesize = ((WIDTH * BitsPerPixel % 8 == 0)? (WIDTH * BitsPerPixel / 8 ): (WIDTH * BitsPerPixel / 8 + 1)) * HEIGHT;
+    if((Refresh_Frame_Buf = (UBYTE *)malloc(Imagesize)) == NULL) {
+        Debug("Failed to apply for black memory...\r\n");
+        return -1;
+    }
+
+    Paint_NewImage(Refresh_Frame_Buf, WIDTH, HEIGHT, 0, BLACK);
+    Paint_SelectImage(Refresh_Frame_Buf);
+	Epd_Mode(epd_mode);
+    Paint_SetBitsPerPixel(BitsPerPixel);
+    Paint_Clear(WHITE);
+
+
+	char Path[30];
+	sprintf(Path,"./pic/%dx%d_%d.bmp", WIDTH, HEIGHT, Pic_Count);
+	GUI_ReadBmp(Path, 0, 0);
+
+    switch(BitsPerPixel){
+        case BitsPerPixel_8:{
+            Paint_DrawString_EN(10, 10, "8 bits per pixel 16 grayscale", &Font24, 0xF0, 0x00);
+            EPD_IT8951_8bp_Refresh(Refresh_Frame_Buf, 0, 0, WIDTH,  HEIGHT, false, Init_Target_Memory_Addr);
+            break;
+        }
+        case BitsPerPixel_4:{
+			Paint_DrawString_EN(10, 10, "4 bits per pixel 16 grayscale", &Font24, 0xF0, 0x00);
+            EPD_IT8951_4bp_Refresh(Refresh_Frame_Buf, 0, 0, WIDTH,  HEIGHT, false, Init_Target_Memory_Addr,false);
+            break;
+        }
+        case BitsPerPixel_2:{
+            Paint_DrawString_EN(10, 10, "2 bits per pixel 4 grayscale", &Font24, 0xC0, 0x00);
+            EPD_IT8951_2bp_Refresh(Refresh_Frame_Buf, 0, 0, WIDTH,  HEIGHT, false, Init_Target_Memory_Addr,false);
+            break;
+        }
+        case BitsPerPixel_1:{
+            Paint_DrawString_EN(10, 10, "1 bit per pixel 2 grayscale", &Font24, 0x80, 0x00);
+            EPD_IT8951_1bp_Refresh(Refresh_Frame_Buf, 0, 0, WIDTH,  HEIGHT, A2_Mode, Init_Target_Memory_Addr,false);
+            break;
+        }
+    }
+
+    if(Refresh_Frame_Buf != NULL){
+        free(Refresh_Frame_Buf);
+        Refresh_Frame_Buf = NULL;
+    }
+
+    DEV_Delay_ms(5000);
+
+    return 0;
+}
+
+
+
+
+void Factory_Test_Only(IT8951_Dev_Info Dev_Info, UDOUBLE Init_Target_Memory_Addr)
+{
+    while(1)
+    {
+        for(int i=0; i < 4; i++){
+			EPD_IT8951_SystemRun();
+			
+			EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, GC16_Mode);
+            // BMP_Test(Dev_Info.Panel_W, Dev_Info.Panel_H, Init_Target_Memory_Addr, BitsPerPixel_1, i);
+            // BMP_Test(Dev_Info.Panel_W, Dev_Info.Panel_H, Init_Target_Memory_Addr, BitsPerPixel_2, i);
+            BMP_Test(Dev_Info.Panel_W, Dev_Info.Panel_H, Init_Target_Memory_Addr, BitsPerPixel_4, i);
+            // BMP_Test(Dev_Info.Panel_W, Dev_Info.Panel_H, Init_Target_Memory_Addr, BitsPerPixel_8, i);
+			EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, GC16_Mode);
+			
+			EPD_IT8951_Sleep();
+			DEV_Delay_ms(5000);
+        }
+		EPD_IT8951_SystemRun();
+		
+		EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, A2_Mode);
+        Dynamic_Refresh_Example(Dev_Info,Init_Target_Memory_Addr);
+		EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, A2_Mode);
+		
+		if(isColor) 
+			Color_Test(Dev_Info, Init_Target_Memory_Addr);
+		
+		EPD_IT8951_Sleep();
+		DEV_Delay_ms(5000);
+    }
+}
+
+void Color_Test(IT8951_Dev_Info Dev_Info, UDOUBLE Init_Target_Memory_Addr)
+{
+	PAINT_TIME Time = {2020, 9, 30, 18, 10, 34};
+	
+	while(1) 
+	{
+		UWORD Panel_Width = Dev_Info.Panel_W;
+		UWORD Panel_Height = Dev_Info.Panel_H;
+
+		UDOUBLE Imagesize;
+
+		//malloc enough memory for 1bp picture first
+		Imagesize = ((Panel_Width * 1 % 8 == 0)? (Panel_Width * 1 / 8 ): (Panel_Width * 1 / 8 + 1)) * Panel_Height;
+		if((Refresh_Frame_Buf = (UBYTE *)malloc(Imagesize*4)) == NULL) {
+			Debug("Failed to apply for picture memory...\r\n");
+		}
+
+		Paint_NewImage(Refresh_Frame_Buf, Panel_Width, Panel_Height, 0, BLACK);
+		Paint_SelectImage(Refresh_Frame_Buf);
+		Epd_Mode(epd_mode);
+		Paint_SetBitsPerPixel(4);
+		Paint_Clear(WHITE);
+
+		if(0) {
+			Paint_DrawRectangle(100, 100, 300, 300, 0x0f00, DOT_PIXEL_1X1, DRAW_FILL_FULL);	//Red
+			Paint_DrawRectangle(100, 400, 300, 600, 0x00f0, DOT_PIXEL_1X1, DRAW_FILL_FULL);	//Green
+			Paint_DrawRectangle(100, 700, 300, 900, 0x000f, DOT_PIXEL_1X1, DRAW_FILL_FULL);	//Bule
+			
+			Paint_DrawCircle(500, 200, 100, 0x00ff, DOT_PIXEL_1X1, DRAW_FILL_FULL);
+			Paint_DrawCircle(500, 500, 100, 0x0f0f, DOT_PIXEL_1X1, DRAW_FILL_FULL);
+			Paint_DrawCircle(500, 800, 100, 0x0ff0, DOT_PIXEL_1X1, DRAW_FILL_FULL);
+			
+			Paint_DrawLine(1000, 200, 1100, 200, 0x055a, 10, LINE_STYLE_SOLID);
+			Paint_DrawLine(1000, 300, 1100, 300, 0x05a5, 20, LINE_STYLE_SOLID);
+			Paint_DrawLine(1000, 400, 1100, 400, 0x0a55, 30, LINE_STYLE_SOLID);
+
+			Paint_DrawString_EN(1000, 500, "Hello, World!", &Font24, 0x0aa5, 0x0fff);
+			Paint_DrawString_EN(1000, 600, "Hello, World!", &Font24, 0x0a5a, 0x0fff);
+			Paint_DrawString_EN(1000, 700, "Hello, World!", &Font24, 0x05aa, 0x0fff);
+
+			Paint_DrawString_CN(700, 400, "��� ΢ѩ����", &Font24CN, 0x00fa, 0x0000);
+			Paint_DrawNum(700, 500, 123456789, &Font24, 0x0a0f, 0x0fff);
+			Paint_DrawTime(700, 600, &Time, &Font24, 0x0fa0, 0x0fff);
+		}else {
+			for(UWORD j=0; j<14; j++) {
+				for(UWORD i=0; i<19; i++) {
+					Paint_DrawRectangle(i*72, j*72+1, (i+1)*72-1, (j+1)*72, (i+j*19)*15, DOT_PIXEL_1X1, DRAW_FILL_FULL);
+				}
+			}
+		}
+
+		EPD_IT8951_4bp_Refresh(Refresh_Frame_Buf, 0, 0, Panel_Width,  Panel_Height, false, Init_Target_Memory_Addr, false);
+		
+		if(Refresh_Frame_Buf != NULL) {
+			free(Refresh_Frame_Buf);
+			Refresh_Frame_Buf = NULL;
+		}
+
+		DEV_Delay_ms(5000);
+		break;
+	}
+}
diff --git a/frameos/src/drivers/waveshare/it8951/examples/example.h b/frameos/src/drivers/waveshare/it8951/examples/example.h
new file mode 100644
index 00000000..96b9f179
--- /dev/null
+++ b/frameos/src/drivers/waveshare/it8951/examples/example.h
@@ -0,0 +1,44 @@
+#ifndef __EXAMPLE__
+#define __EXAMPLE__
+
+#include "../lib/e-Paper/EPD_IT8951.h"
+#include "../lib/Config/DEV_Config.h"
+
+
+// 1 bit per pixel, which is 2 grayscale
+#define BitsPerPixel_1 1
+// 2 bit per pixel, which is 4 grayscale 
+#define BitsPerPixel_2 2
+// 4 bit per pixel, which is 16 grayscale
+#define BitsPerPixel_4 4
+// 8 bit per pixel, which is 256 grayscale, but will automatically reduce by hardware to 4bpp, which is 16 grayscale
+#define BitsPerPixel_8 8
+
+
+//For all refresh fram buf except touch panel
+extern UBYTE *Refresh_Frame_Buf;
+
+//Only for touch panel
+extern UBYTE *Panel_Frame_Buf;
+extern UBYTE *Panel_Area_Frame_Buf;
+
+extern bool Four_Byte_Align;
+
+UBYTE Display_ColorPalette_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Init_Target_Memory_Addr);
+
+UBYTE Display_CharacterPattern_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Init_Target_Memory_Addr, UBYTE BitsPerPixel);
+
+UBYTE Display_BMP_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Init_Target_Memory_Addr, UBYTE BitsPerPixel);
+
+UBYTE Dynamic_Refresh_Example(IT8951_Dev_Info Dev_Info, UDOUBLE Init_Target_Memory_Addr);
+
+UBYTE Dynamic_GIF_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Init_Target_Memory_Addr);
+
+UBYTE Check_FrameRate_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Target_Memory_Addr, UBYTE BitsPerPixel);
+
+UBYTE TouchPanel_ePaper_Example(UWORD Panel_Width, UWORD Panel_Height, UDOUBLE Init_Target_Memory_Addr);
+
+void Factory_Test_Only(IT8951_Dev_Info Dev_Info, UDOUBLE Init_Target_Memory_Addr);
+void Color_Test(IT8951_Dev_Info Dev_Info, UDOUBLE Init_Target_Memory_Addr);
+
+#endif
diff --git a/frameos/src/drivers/waveshare/it8951/examples/main.c b/frameos/src/drivers/waveshare/it8951/examples/main.c
new file mode 100644
index 00000000..330c9f44
--- /dev/null
+++ b/frameos/src/drivers/waveshare/it8951/examples/main.c
@@ -0,0 +1,192 @@
+#include "../lib/Config/DEV_Config.h"
+#include "example.h"
+#include "../lib/GUI/GUI_BMPfile.h"
+
+#include <math.h>
+
+#include <stdlib.h>     //exit()
+#include <signal.h>     //signal()
+
+#define Enhance false
+
+#define USE_Factory_Test false
+
+#define USE_Normal_Demo true
+
+#define USE_Touch_Panel false
+
+UWORD VCOM = 2510;
+
+IT8951_Dev_Info Dev_Info = {0, 0};
+UWORD Panel_Width;
+UWORD Panel_Height;
+UDOUBLE Init_Target_Memory_Addr;
+int epd_mode = 0;	//0: no rotate, no mirror
+					//1: no rotate, horizontal mirror, for 10.3inch
+					//2: no totate, horizontal mirror, for 5.17inch
+					//3: no rotate, no mirror, isColor, for 6inch color
+					
+void  Handler(int signo){
+    Debug("\r\nHandler:exit\r\n");
+    if(Refresh_Frame_Buf != NULL){
+        free(Refresh_Frame_Buf);
+        Debug("free Refresh_Frame_Buf\r\n");
+        Refresh_Frame_Buf = NULL;
+    }
+    if(Panel_Frame_Buf != NULL){
+        free(Panel_Frame_Buf);
+        Debug("free Panel_Frame_Buf\r\n");
+        Panel_Frame_Buf = NULL;
+    }
+    if(Panel_Area_Frame_Buf != NULL){
+        free(Panel_Area_Frame_Buf);
+        Debug("free Panel_Area_Frame_Buf\r\n");
+        Panel_Area_Frame_Buf = NULL;
+    }
+    if(bmp_src_buf != NULL){
+        free(bmp_src_buf);
+        Debug("free bmp_src_buf\r\n");
+        bmp_src_buf = NULL;
+    }
+    if(bmp_dst_buf != NULL){
+        free(bmp_dst_buf);
+        Debug("free bmp_dst_buf\r\n");
+        bmp_dst_buf = NULL;
+    }
+	if(Dev_Info.Panel_W != 0){
+		Debug("Going to sleep\r\n");
+		EPD_IT8951_Sleep();
+	}
+    DEV_Module_Exit();
+    exit(0);
+}
+
+
+int main(int argc, char *argv[])
+{
+    //Exception handling:ctrl + c
+    signal(SIGINT, Handler);
+
+    if (argc < 2){
+        Debug("Please input VCOM value on FPC cable!\r\n");
+        Debug("Example: sudo ./epd -2.51\r\n");
+        exit(1);
+    }
+	if (argc != 3){
+		Debug("Please input e-Paper display mode!\r\n");
+		Debug("Example: sudo ./epd -2.51 0 or sudo ./epd -2.51 1\r\n");
+		Debug("Now, 10.3 inch glass panle is mode1, else is mode0\r\n");
+		Debug("If you don't know what to type in just type 0 \r\n");
+		exit(1);
+    }
+
+    //Init the BCM2835 Device
+    if(DEV_Module_Init()!=0){
+        return -1;
+    }
+
+    double temp;
+    sscanf(argv[1],"%lf",&temp);
+    VCOM = (UWORD)(fabs(temp)*1000);
+    Debug("VCOM value:%d\r\n", VCOM);
+	sscanf(argv[2],"%d",&epd_mode);
+    Debug("Display mode:%d\r\n", epd_mode);
+    Dev_Info = EPD_IT8951_Init(VCOM);
+
+#if(Enhance)
+    Debug("Attention! Enhanced driving ability, only used when the screen is blurred\r\n");
+    Enhance_Driving_Capability();
+#endif
+
+    //get some important info from Dev_Info structure
+    Panel_Width = Dev_Info.Panel_W;
+    Panel_Height = Dev_Info.Panel_H;
+    Init_Target_Memory_Addr = Dev_Info.Memory_Addr_L | (Dev_Info.Memory_Addr_H << 16);
+    char* LUT_Version = (char*)Dev_Info.LUT_Version;
+    if( strcmp(LUT_Version, "M641") == 0 ){
+        //6inch e-Paper HAT(800,600), 6inch HD e-Paper HAT(1448,1072), 6inch HD touch e-Paper HAT(1448,1072)
+        A2_Mode = 4;
+        Four_Byte_Align = true;
+    }else if( strcmp(LUT_Version, "M841_TFAB512") == 0 ){
+        //Another firmware version for 6inch HD e-Paper HAT(1448,1072), 6inch HD touch e-Paper HAT(1448,1072)
+        A2_Mode = 6;
+        Four_Byte_Align = true;
+    }else if( strcmp(LUT_Version, "M841") == 0 ){
+        //9.7inch e-Paper HAT(1200,825)
+        A2_Mode = 6;
+    }else if( strcmp(LUT_Version, "M841_TFA2812") == 0 ){
+        //7.8inch e-Paper HAT(1872,1404)
+        A2_Mode = 6;
+    }else if( strcmp(LUT_Version, "M841_TFA5210") == 0 ){
+        //10.3inch e-Paper HAT(1872,1404)
+        A2_Mode = 6;
+    }else{
+        //default set to 6 as A2 Mode
+        A2_Mode = 6;
+    }
+    Debug("A2 Mode:%d\r\n", A2_Mode);
+
+	EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, INIT_Mode);
+
+#if(USE_Factory_Test)
+	if(epd_mode == 3) 	// Color Test
+		Color_Test(Dev_Info, Init_Target_Memory_Addr);
+    else				// Normal Test
+		Factory_Test_Only(Dev_Info, Init_Target_Memory_Addr);
+#endif
+
+
+#if(USE_Normal_Demo)
+    //Show 16 grayscale
+    Display_ColorPalette_Example(Panel_Width, Panel_Height, Init_Target_Memory_Addr);
+	EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, GC16_Mode);
+
+    //Show some character and pattern
+    Display_CharacterPattern_Example(Panel_Width, Panel_Height, Init_Target_Memory_Addr, BitsPerPixel_4);
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, GC16_Mode);
+
+    //Show a bmp file
+    //1bp use A2 mode by default, before used it, refresh the screen with WHITE
+    Display_BMP_Example(Panel_Width, Panel_Height, Init_Target_Memory_Addr, BitsPerPixel_1);
+    Display_BMP_Example(Panel_Width, Panel_Height, Init_Target_Memory_Addr, BitsPerPixel_2);
+    Display_BMP_Example(Panel_Width, Panel_Height, Init_Target_Memory_Addr, BitsPerPixel_4);
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, GC16_Mode);
+    
+    //Show A2 mode refresh effect
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, A2_Mode);
+    Dynamic_Refresh_Example(Dev_Info,Init_Target_Memory_Addr);
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, A2_Mode);
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, GC16_Mode);
+	
+    //Show how to display a gif, not works well on 6inch e-Paper HAT, 9.7inch e-Paper HAT, others work well
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, A2_Mode);
+    Dynamic_GIF_Example(Panel_Width, Panel_Height, Init_Target_Memory_Addr);
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, A2_Mode);
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, GC16_Mode);
+
+    //Show how to test frame rate, test it individually,which is related to refresh area size and refresh mode
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, A2_Mode);
+    Check_FrameRate_Example(800, 600, Init_Target_Memory_Addr, BitsPerPixel_1);
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, A2_Mode);
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, GC16_Mode);
+#endif
+
+
+#if(USE_Touch_Panel)
+    //show a simple demo for hand-painted tablet, only support for <6inch HD touch e-Paper> at present
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, INIT_Mode);
+    TouchPanel_ePaper_Example(Panel_Width, Panel_Height, Init_Target_Memory_Addr);
+#endif
+
+    //We recommended refresh the panel to white color before storing in the warehouse.
+    EPD_IT8951_Clear_Refresh(Dev_Info, Init_Target_Memory_Addr, INIT_Mode);
+
+    //EPD_IT8951_Standby();
+    EPD_IT8951_Sleep();
+
+    //In case RPI is transmitting image in no hold mode, which requires at most 10s
+    DEV_Delay_ms(5000);
+
+    DEV_Module_Exit();
+    return 0;
+}
diff --git a/frameos/src/drivers/waveshare/types.nim b/frameos/src/drivers/waveshare/types.nim
index 71d07809..67d871be 100644
--- a/frameos/src/drivers/waveshare/types.nim
+++ b/frameos/src/drivers/waveshare/types.nim
@@ -4,5 +4,6 @@ type ColorOption* = enum
   BlackWhiteYellow = "BlackWhiteYellow"
   BlackWhiteYellowRed = "BlackWhiteYellowRed"
   FourGray = "FourGray"
+  SixteenGray = "SixteenGray"
   SevenColor = "SevenColor"
   SpectraSixColor = "SpectraSixColor"
diff --git a/frameos/src/drivers/waveshare/waveshare.nim b/frameos/src/drivers/waveshare/waveshare.nim
index 94000ff3..c0b117d5 100644
--- a/frameos/src/drivers/waveshare/waveshare.nim
+++ b/frameos/src/drivers/waveshare/waveshare.nim
@@ -60,7 +60,7 @@ proc init*(frameOS: FrameOS): Driver =
         "stack": e.getStackTrace()})
 
 proc notifyImageAvailable*(self: Driver) =
-  self.logger.log(%*{"event": "render:dither", "info": "Dithered image available"})
+  self.logger.log(%*{"event": "render:dither", "info": "Dithered image available, starting render"})
 
 proc renderBlack*(self: Driver, image: Image) =
   var gray = newSeq[float](image.width * image.height)
@@ -97,6 +97,23 @@ proc renderFourGray*(self: Driver, image: Image) =
       blackImage[index div 4] = blackImage[index div 4] or ((bw and 0b11) shl (6 - (index mod 4) * 2))
   waveshareDriver.renderImage(blackImage)
 
+proc renderSixteenGray*(self: Driver, image: Image) =
+  var gray = newSeq[float](image.width * image.height)
+  image.toGrayscaleFloat(gray, 15)
+  gray.floydSteinberg(image.width, image.height)
+  setLastFloatImage(gray)
+  self.notifyImageAvailable()
+
+  let rowWidth = ceil(image.width.float / 2).int
+  var blackImage = newSeq[uint8](rowWidth * image.height)
+  for y in 0..<image.height:
+    for x in 0..<image.width:
+      let inputIndex = y * image.width + x
+      let index = y * rowWidth * 2 + x
+      let bw: uint8 = gray[inputIndex].uint8 # 0, 1, 2 or 3
+      blackImage[index div 2] = blackImage[index div 2] or ((bw and 0b1111) shl ((index mod 2) * (-4)))
+  waveshareDriver.renderImage(blackImage)
+
 proc renderBlackWhiteRed*(self: Driver, image: Image, isRed = true) =
   let pixels = ditherPaletteIndexed(image, @[(0, 0, 0), (255, if isRed: 0 else: 255, 0), (255, 255, 255)])
   let inputRowWidth = int(ceil(image.width.float / 4))
@@ -166,6 +183,8 @@ proc render*(self: Driver, image: Image) =
     self.renderSpectraSixColor(image)
   of ColorOption.FourGray:
     self.renderFourGray(image)
+  of ColorOption.SixteenGray:
+    self.renderSixteenGray(image)
   of ColorOption.BlackWhiteYellowRed:
     self.renderBlackWhiteYellowRed(image)
 
@@ -199,6 +218,18 @@ proc toPng*(rotate: int = 0): string =
         outputImage.data[index].g = pixel
         outputImage.data[index].b = pixel
         outputImage.data[index].a = 255
+  of ColorOption.SixteenGray:
+    let pixels = getLastFloatImage()
+    if pixels.len == 0:
+      raise newException(Exception, "No render yet")
+    for y in 0 ..< height:
+      for x in 0 ..< width:
+        let index = y * width + x
+        let pixel = (pixels[index] * 17).uint8
+        outputImage.data[index].r = pixel
+        outputImage.data[index].g = pixel
+        outputImage.data[index].b = pixel
+        outputImage.data[index].a = 255
   of ColorOption.BlackWhiteRed, ColorOption.BlackWhiteYellow:
     let pixels = getLastPixels()
     if pixels.len == 0:
diff --git a/frontend/src/devices.ts b/frontend/src/devices.ts
index 8bd9eb20..f0dc3bf8 100644
--- a/frontend/src/devices.ts
+++ b/frontend/src/devices.ts
@@ -90,6 +90,7 @@ export const devices: Option[] = [
   { value: 'waveshare.EPD_7in5_HD', label: 'Waveshare 7.5" (HD) 880x528 Black/White' },
   { value: 'waveshare.EPD_7in5b_HD', label: 'Waveshare 7.5" (B HD) 880x528 Black/White/Red' },
   { value: 'waveshare.EPD_10in2b', label: 'Waveshare 10.2" (B) 960x640 Black/White/Red' },
+  { value: 'waveshare.EPD_10in3', label: 'Waveshare 10.3" 1872x1404 16 Grayscale' },
   { value: 'waveshare.EPD_12in48', label: 'Waveshare 12.48" 1304x984 Black/White' },
   { value: 'waveshare.EPD_12in48b', label: 'Waveshare 12.48" (B) 1304x984 Black/White/Red' },
   { value: 'waveshare.EPD_12in48b_V2', label: 'Waveshare 12.48" (B V2) 1304x984 Black/White/Red' },