├── .gitignore ├── README.md ├── Smart_ePaper_Frame.ino ├── credentials.h ├── data └── index.html ├── display.h └── readme images ├── dithering.png ├── img1.jpg ├── img2.jpg └── img3.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | /credentials.h -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32_ePaper_Frame 2 | Smart E-Paper frame controlled over wifi, using ESP32 and 7.5inch waveshare E-Ink display. 3 | 4 | ![Demo Image 1](https://raw.githubusercontent.com/dani3lwinter/ESP32_ePaper_Frame/master/readme%20images/img1.jpg) 5 | 6 | ## Requirements 7 | ### Libraries 8 | - [**AsyncTCP**](https://github.com/me-no-dev/AsyncTCP) library 9 | - [**ESPAsyncWebServer**](https://github.com/me-no-dev/ESPAsyncWebServer) library 10 | 11 | ESP32_ePaper_Frame uses **ESPAsyncWebServer** library, 12 | and **AsyncTCP** is a dependency for the ESPAsyncWebServer. 13 | 14 | Clone/download both libraries and save them to your Arduino libraries folder (usually located on _C:\\Users\\%USERPROFILE%\\Documents\\Arduino\\libraries_) 15 | 16 | ### Hardware 17 | - ESP32 microcontroller 18 | - 7.5inch E-Ink display with driver board for ESP32 19 | ([Waveshare](https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT_(B))). 20 | 21 | ## Installation 22 | 1. Open *Smart_ePaper_Frame.ino* file with Arduino. 23 | 2. Upload the data folder using *ESP32 Filesystem Uploader* ([Tutorial](https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/)). 24 | 3. Upload the code to your ESP32 board. 25 | 26 | ## Usage 27 | 28 | 1. Configure your wifi credentials in the *credentials.h* file: 29 | ```cpp 30 | WifiCredentials wifi_credentials = { 31 | "YOUR_WIFI_SSID", 32 | "YOUR_WIFI_PASSWORD" 33 | }; 34 | ``` 35 | 36 | 2. Configure the SPI pins connected to the display driver board in the *display.h* file: 37 | ```cpp 38 | #define PIN_SPI_SCK 18 39 | #define PIN_SPI_DIN 23 40 | #define PIN_SPI_CS 5 41 | #define PIN_SPI_BUSY 4 42 | #define PIN_SPI_RST 16 43 | #define PIN_SPI_DC 17 44 | ``` 45 | 46 | 3. After the program started, The IP of the board will be shown in the Serial Monitor: 47 | ``` 48 | Connecting to WiFi.. 49 | 192.168.1.32 50 | ``` 51 | 52 | 4. Browse to the given IP in your browser and upload a photo. 53 | 54 | 55 | ### More Images: 56 | ![Demo Image 2](https://raw.githubusercontent.com/dani3lwinter/ESP32_ePaper_Frame/master/readme%20images/img2.jpg) 57 | 58 | ![Dithering](https://raw.githubusercontent.com/dani3lwinter/ESP32_ePaper_Frame/master/readme%20images/dithering.png) 59 | 60 | ![Demo Image 3](https://raw.githubusercontent.com/dani3lwinter/ESP32_ePaper_Frame/master/readme%20images/img3.jpg) 61 | 62 | -------------------------------------------------------------------------------- /Smart_ePaper_Frame.ino: -------------------------------------------------------------------------------- 1 | #include "WiFi.h" 2 | #include "SPIFFS.h" 3 | #include "ESPAsyncWebServer.h" 4 | #include "credentials.h" 5 | #include "display.h" 6 | 7 | AsyncWebServer server(80); 8 | AsyncWebSocket ws("/test"); 9 | 10 | bool displayInisiated = false; 11 | void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, 12 | AwsEventType type, void * arg, uint8_t *data, size_t len){ 13 | 14 | if(type == WS_EVT_CONNECT){ 15 | Serial.println("Websocket client connection received"); 16 | } 17 | else if(type == WS_EVT_DISCONNECT){ 18 | Serial.println("Websocket client disconnected"); 19 | } 20 | else if(type == WS_EVT_DATA){ 21 | AwsFrameInfo * info = (AwsFrameInfo*)arg; 22 | if(info->opcode == WS_TEXT){ // Got text data (the client checks if the display is busy) 23 | client->text(responseToNewImage()); 24 | } 25 | else{ // Got binary data (the client sent the image) 26 | // Send the received data to the display buffer. 27 | loadImage((const char*)data, len); 28 | if((info->index + len) == info->len){ // if this data was the end of the file 29 | //client->text("IMAGE_LOADED"); // tell the client that the server got the image 30 | Serial.printf("Updating Display: %d bytes total\n", info->len); 31 | updateDisplay_withoutIdle(); // Display the image that received 32 | } 33 | } 34 | 35 | 36 | } 37 | } 38 | 39 | char* responseToNewImage(){ 40 | if(isDisplayBusy()){ 41 | return "BUSY"; 42 | } 43 | else{ 44 | displayStartTransmission(); 45 | return "OK"; 46 | } 47 | } 48 | void setup(){ 49 | Serial.begin(115200); 50 | 51 | // Inisialize SPIFFS 52 | if(!SPIFFS.begin()){ 53 | Serial.println("An Error has occurred while mounting SPIFFS"); 54 | return; 55 | } 56 | 57 | // Inisialize display 58 | if(!displayInisiated){ 59 | waitForIdle(); 60 | initDisplay(); 61 | displayInisiated = true; 62 | } 63 | 64 | // Inisialize WiFi 65 | WiFi.begin(wifi_credentials.ssid, wifi_credentials.password); 66 | while (WiFi.status() != WL_CONNECTED) { 67 | delay(1000); 68 | Serial.println("Connecting to WiFi.."); 69 | } 70 | Serial.println(WiFi.localIP()); 71 | 72 | delay(2000); 73 | 74 | // setup handles for websockets connetions 75 | ws.onEvent(onWsEvent); 76 | server.addHandler(&ws); 77 | 78 | // handle GET requests to route /index.html 79 | server.on("/index.html", HTTP_GET, [](AsyncWebServerRequest *request){ 80 | Serial.println("Request recived: GET /index.html"); 81 | request->send(SPIFFS, "/index.html", "text/html"); 82 | }); 83 | 84 | // handle GET requests to route / 85 | server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ 86 | Serial.println("Request recived: GET /"); 87 | request->send(SPIFFS, "/index.html", "text/html"); 88 | }); 89 | 90 | // handle GET requests to route /hello - echo "Hello Word" (for debugging) 91 | server.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request){ 92 | Serial.println("Request recived: GET /hello"); 93 | request->send(200, "text/plain", "Hello World"); 94 | }); 95 | 96 | server.begin(); //start listening to incoming HTTP requests 97 | } 98 | 99 | void loop(){ 100 | delay(1000); 101 | } 102 | -------------------------------------------------------------------------------- /credentials.h: -------------------------------------------------------------------------------- 1 | #ifndef credentials_h 2 | #define credentials_h 3 | 4 | struct WifiCredentials { 5 | const char* ssid; 6 | const char* password; 7 | }; 8 | 9 | WifiCredentials wifi_credentials = { 10 | "YOUR_WIFI_SSID", 11 | "YOUR_WIFI_PASSWORD" 12 | }; 13 | 14 | #endif // credentials_h 15 | -------------------------------------------------------------------------------- /data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 75 | 76 | 77 | 78 | 79 |
80 |

Smart E-Paper Frame

81 | 82 | 85 | 86 | 87 | 88 | 89 | 90 | 95 | 96 | 97 | 98 | 103 | 104 | 105 | 106 | 111 | 112 |
Contrast: 91 |
92 | 93 |
94 |
Brightness: 99 |
100 | 101 |
102 |
Dithering: 107 |
108 | 109 |
110 |
113 |
114 | 115 | 116 | 117 |
118 | 119 |
120 |
121 | 122 |
123 | 124 | 125 |
126 | 127 | 353 | 354 | -------------------------------------------------------------------------------- /display.h: -------------------------------------------------------------------------------- 1 | #ifndef display_h 2 | #define display_h 3 | 4 | #include 5 | 6 | // SPI pins. 7 | #define PIN_SPI_SCK 18 8 | #define PIN_SPI_DIN 23 9 | #define PIN_SPI_CS 5 10 | #define PIN_SPI_BUSY 4 11 | #define PIN_SPI_RST 16 12 | #define PIN_SPI_DC 17 13 | 14 | // Wakes up the display from sleep. 15 | void resetDisplay() { 16 | digitalWrite(PIN_SPI_RST, LOW); 17 | delay(200); 18 | digitalWrite(PIN_SPI_RST, HIGH); 19 | delay(200); 20 | } 21 | 22 | // Sends one byte via SPI. 23 | void sendSpi(byte data) { 24 | digitalWrite(PIN_SPI_CS, LOW); 25 | for (int i = 0; i < 8; i++) { 26 | if ((data & 0x80) == 0) { 27 | digitalWrite(PIN_SPI_DIN, LOW); 28 | } else { 29 | digitalWrite(PIN_SPI_DIN, HIGH); 30 | } 31 | data <<= 1; 32 | digitalWrite(PIN_SPI_SCK, HIGH); 33 | digitalWrite(PIN_SPI_SCK, LOW); 34 | } 35 | digitalWrite(PIN_SPI_CS, HIGH); 36 | } 37 | 38 | // Sends one byte as a command. 39 | void sendCommand(byte command) { 40 | digitalWrite(PIN_SPI_DC, LOW); 41 | sendSpi(command); 42 | } 43 | 44 | // Sends one byte as data. 45 | void sendData(byte data) { 46 | digitalWrite(PIN_SPI_DC, HIGH); 47 | sendSpi(data); 48 | } 49 | 50 | // Sends a 1-argument command. 51 | void sendCommand1(byte command, byte data) { 52 | sendCommand(command); 53 | sendData(data); 54 | } 55 | 56 | // Sends a 2-argument command. 57 | void sendCommand2(byte command, byte data1, byte data2) { 58 | sendCommand(command); 59 | sendData(data1); 60 | sendData(data2); 61 | } 62 | 63 | // Sends a 3-argument command. 64 | void sendCommand3(byte command, byte data1, byte data2, byte data3) { 65 | sendCommand(command); 66 | sendData(data1); 67 | sendData(data2); 68 | sendData(data3); 69 | } 70 | 71 | // Sends a 4-argument command. 72 | void sendCommand4(byte command, byte data1, byte data2, byte data3, 73 | byte data4) { 74 | sendCommand(command); 75 | sendData(data1); 76 | sendData(data2); 77 | sendData(data3); 78 | sendData(data4); 79 | } 80 | 81 | // Waits until the display is ready. 82 | void waitForIdle() { 83 | while (digitalRead(PIN_SPI_BUSY) == LOW /* busy */) { 84 | delay(100); 85 | } 86 | } 87 | 88 | // Returns whether the display is busy 89 | bool isDisplayBusy() { 90 | return (digitalRead(PIN_SPI_BUSY) == LOW); 91 | } 92 | 93 | // Initializes the display. 94 | void initDisplay() { 95 | Serial.println("Initializing display"); 96 | 97 | // Initialize SPI. 98 | pinMode(PIN_SPI_BUSY, INPUT); 99 | pinMode(PIN_SPI_RST, OUTPUT); 100 | pinMode(PIN_SPI_DC, OUTPUT); 101 | pinMode(PIN_SPI_SCK, OUTPUT); 102 | pinMode(PIN_SPI_DIN, OUTPUT); 103 | pinMode(PIN_SPI_CS, OUTPUT); 104 | digitalWrite(PIN_SPI_CS, HIGH); 105 | digitalWrite(PIN_SPI_SCK, LOW); 106 | 107 | // Initialize the display. 108 | resetDisplay(); 109 | sendCommand2(0x01, 0x37, 0x00); // POWER_SETTING 110 | sendCommand2(0x00, 0xCF, 0x08); // PANEL_SETTING 111 | sendCommand3(0x06, 0xC7, 0xCC, 0x28); // BOOSTER_SOFT_START 112 | sendCommand(0x4); // POWER_ON 113 | waitForIdle(); 114 | sendCommand1(0x30, 0x3C); // PLL_CONTROL 115 | sendCommand1(0x41, 0x00); // TEMPERATURE_CALIBRATION 116 | sendCommand1(0x50, 0x77); // VCOM_AND_DATA_INTERVAL_SETTING 117 | sendCommand1(0x60, 0x22); // TCON_SETTING 118 | sendCommand4(0x61, 0x02, 0x80, 0x01, 0x80); // TCON_RESOLUTION 119 | sendCommand1(0x82, 0x1E); // VCM_DC_SETTING 120 | sendCommand1(0xE5, 0x03); // FLASH MODE 121 | sendCommand(0x10); // DATA_START_TRANSMISSION_1 122 | delay(2); 123 | } 124 | 125 | void displayStartTransmission(){ 126 | sendCommand(0x10); // DATA_START_TRANSMISSION_1 127 | delay(2); 128 | } 129 | // Converts one pixel from input encoding (2 bits) to output encoding (4 bits). 130 | byte convertPixel(char input, byte mask, int shift) { 131 | const byte value = (input & mask) >> shift; 132 | switch (value) { 133 | case 0x0: 134 | // Black: 00 -> 0000 135 | return 0x0; 136 | case 0x1: 137 | // White: 01 -> 0011 138 | return 0x3; 139 | case 0x3: 140 | // Red: 11 -> 0100 141 | return 0x4; 142 | default: 143 | Serial.printf("Unknown pixel value: 0x%04X\n", value); 144 | return 0x0; 145 | } 146 | } 147 | 148 | // Loads partial image data onto the display. 149 | void loadImage(const char* image_data, size_t length) { 150 | Serial.printf("Loading image data: %d bytes\n", length); 151 | 152 | // Look at the image data one byte at a time, which is 4 input pixels. 153 | for (int i = 0; i < length; i++) { 154 | // 4 input pixels. 155 | const byte p1 = convertPixel(image_data[i], 0xC0, 6); 156 | const byte p2 = convertPixel(image_data[i], 0x30, 4); 157 | const byte p3 = convertPixel(image_data[i], 0x0C, 2); 158 | const byte p4 = convertPixel(image_data[i], 0x03, 0); 159 | 160 | // 2 output pixels. 161 | sendData((p1 << 4) | p2); 162 | sendData((p3 << 4) | p4); 163 | } 164 | } 165 | 166 | // Shows the loaded image and sends the display to sleep. 167 | void updateDisplay() { 168 | // Refresh. 169 | Serial.println("Refreshing image"); 170 | sendCommand(0x12); // DISPLAY_REFRESH 171 | delay(100); 172 | Serial.println("sendCommand: DISPLAY_REFRESH"); 173 | waitForIdle(); 174 | 175 | // Sleep. 176 | Serial.println("Suspending display"); 177 | sendCommand(0x02); // POWER_OFF 178 | waitForIdle(); 179 | sendCommand1(0x07, 0xA5); // DEEP_SLEEP 180 | } 181 | 182 | // Shows the loaded image 183 | void updateDisplay_withoutIdle() { 184 | // Refresh. 185 | Serial.println("Refreshing image"); 186 | sendCommand(0x12); // DISPLAY_REFRESH 187 | delay(100); 188 | /*Serial.println("sendCommand: DISPLAY_REFRESH"); 189 | waitForIdle(); 190 | 191 | // Sleep. 192 | Serial.println("Suspending display"); 193 | sendCommand(0x02); // POWER_OFF 194 | waitForIdle(); 195 | sendCommand1(0x07, 0xA5); // DEEP_SLEEP*/ 196 | } 197 | 198 | #endif // display_h 199 | -------------------------------------------------------------------------------- /readme images/dithering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dani3lwinter/ESP32_ePaper_Frame/e25b2d917f2aaf1e6ceb65b01ee7498b92fffcc6/readme images/dithering.png -------------------------------------------------------------------------------- /readme images/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dani3lwinter/ESP32_ePaper_Frame/e25b2d917f2aaf1e6ceb65b01ee7498b92fffcc6/readme images/img1.jpg -------------------------------------------------------------------------------- /readme images/img2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dani3lwinter/ESP32_ePaper_Frame/e25b2d917f2aaf1e6ceb65b01ee7498b92fffcc6/readme images/img2.jpg -------------------------------------------------------------------------------- /readme images/img3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dani3lwinter/ESP32_ePaper_Frame/e25b2d917f2aaf1e6ceb65b01ee7498b92fffcc6/readme images/img3.jpg --------------------------------------------------------------------------------