├── .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 | 
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 | 
57 |
58 | 
59 |
60 | 
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 |
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
--------------------------------------------------------------------------------