The LilyGO TTGO T5-4.7 E-Paper is an integrated development module that combines a 4.7-inch E-paper (e-ink) display with an ESP32-S3 microcontroller.
The board’s E-paper screen operates at a resolution of 540 × 960 pixels with 16 levels of grayscale and requires power only during refresh operations, which minimizes energy consumption for slowly changing data like weather data.
In this tutorial you will learn how to get started with the LilyGO TTGO T5-4.7 E-Paper board. Futhermore, we will build a weather station that retrieves its data over the internet from OpenWeatherMap and displays it on the E-Paper.
Required Parts
You can get the LilyGO TTGO T5-4.7 E-Paper from DFRobot or Amazon using the links below. If you want to run the display on battery power you will also need a battery.
I recommend a 18650 rechargeable battery with the corresponding battery holder. However, you will have to solder the wires of the battery holder to the battery connector of the LilyGO E-Paper Display. This is not difficult but you will need a soldering iron.

LilyGO TTGO T5-4.7 E-Paper

18650 Rechargeable Battery

18650 Battery Holder
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
Hardware of the LilyGO TTGO T5-4.7 E-Paper
The LilyGO TTGO T5-4.7 E-Paper platform is a 4.7-inch E Ink display controller board that integrates an ESP32-S3 module with an attached grayscale E-paper panel.
The design targets applications that benefit from persistent on-screen information and low average power consumption, where the display content can remain visible without continuous refresh. The photo below shows the front and back of the LilyGO TTGO T5-4.7 E-Paper board.

Processor and Memory Subsystem
The board is built around the ESP32-S3-WROOM-1-N16R18 module. This configuration provides 16 MB of onboard flash for firmware and assets, and 8 MB of PSRAM to support memory-intensive operations such as graphics buffers, font rendering, and network data handling.
The ESP32-S3 also enables Wi-Fi connectivity and enough compute for periodic data acquisition, local processing, and display updates typical of weather-station workloads.
E-Paper Display Characteristics
The integrated E-paper panel has a resolution of 540 × 960 pixels in a portrait-oriented layout that supports 16-level grayscale rendering. The display allows for partial refresh, with selective region updates to reduce refresh time and energy usage when only a subset of the screen content changes.
However, the display must not be partially refreshed for a long time. Otherwise residual images will remain irreversibly!
User Input and On-Board Controls
Physical input is supported through three buttons, including one user-customizable button intended for application-level functions. The other buttons are a reset and a boot button.

The board also exposes a dedicated 6-pin interface intended for connecting a touchscreen panel, which can be bought additionally.
Battery Power Interface
For battery power, the board includes a PH 2.0 connector designed for a 3.7 V battery connection. Battery charge and discharge protection is provided by an HX6610S protection IC.

Expansion Header and Host Compatibility
A 40-pin header is provided on the top of the board and is compatible with the Raspberry Pi BUS interface. Here is the pinout of the header:

Note that the I2C (SDA, SCL) and the SPI (MISO, MOSI, CS) interfaces are accessible there. The picture below shows the pinouts of the other connecters of the board:

GPIO
The following table list the GPIO pins and their internal usage by the built-in ESP32. Only GPIO pins 45, 10, 48 and 39 are available for user-applications.
| ESP32S3 GPIO | Connect To | Free |
|---|---|---|
| 13 | 74HCT4094D CFG_DATA | ❌ |
| 12 | 74HCT4094D CFG_CLK | ❌ |
| 0 | 74HCT4094D CFG_STR | ❌ |
| 38 | E-paper CKV | ❌ |
| 40 | E-paper STH | ❌ |
| 41 | E-paper CKH | ❌ |
| 8 | E-paper D0 | ❌ |
| 1 | E-paper D1 | ❌ |
| 2 | E-paper D2 | ❌ |
| 3 | E-paper D3 | ❌ |
| 4 | E-paper D4 | ❌ |
| 5 | E-paper D5 | ❌ |
| 6 | E-paper D6 | ❌ |
| 7 | E-paper D7 | ❌ |
| 21 | Button | ❌ |
| 14 | Battery ADC | ❌ |
| 16 | SD MISO | ❌* |
| 15 | SD MOSI | ❌* |
| 11 | SD SCK | ❌* |
| 42 | SD CS | ❌* |
| 18 | SDA | ❌ |
| 17 | SCL | ❌ |
| 47 | TouchPanel IRQ | ❌ |
| 45 | Not Connected | ✅ |
| 10 | Not Connected | ✅ |
| 48 | Not Connected | ✅ |
| 39 | Not Connected | ✅ |
Technical Specification
The following table summarizes the technical specification of the Weather station with LilyGO TTGO T5-4.7 E-Paper:
| MCU | ESP32-S3-WROOM-1-N16R8 |
| Wireless Connectivity | Wi-fi Bluetooth V5.0 |
| Flash | 16MB |
| PSRAM | 8MB |
| Support PCF8563 RTC | Battery capacity detection |
| Driver IC | EDO47TC1 |
| Size | 4.7 inch |
| Gray levels | 16 |
| Resolution | 540 x 960 pixels |
| Deep-sleep current | ~170µA |
The schematics for the display board can be found in the github repo.
Versions
Note that there are at least three different version of the LilyGO TTGO T5-4.7 E-Paper. There is an early version with five buttons and a holder for a 18650 battery. See the photo below:

I don’t have this version and therefore cannot tell you if the code examples in this tutorial will work for this board.
However, I have the boards for the two newer versions V2.3 from 2021-06-10 and V2.4 from 2024-12-03. I tested the code for both boards and it works. Note that these boards have three buttons and no battery holder. The photo below shows the two boards with their version numbers visible on the back:

Installation of ESP32 Core
The LilyGO TTGO T5-4.7 uses an ESP32 and we need to install a specific version (2.017) of the ESP32 core. Start by opening the Preferences dialog by selecting “Preferences…” from the “File” menu. This will open the Preferences dialog shown below.
Under the Settings tab you will find an edit box at the bottom of the dialog that is labelled “Additional boards manager URLs“:

In this input field copy the following URL:
https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json
This will let the Arduino IDE know, where to find the ESP32 core libraries. Next we will install the ESP32 boards using the Boards Manager.
Open the Boards Manager via “Tools -> Boards -> Board Manager”. You will see the Boards Manager appearing in the left Sidebar. Enter “ESP32” in the search field at the top and you should see two types of ESP32 boards; the “Arduino ESP32 Boards” and the “esp32 by Espressif” boards. We want the “esp32 libraries by Espressif”.
In the drop down box for the esp32 select version 2.0.17 and then press the INSTALL button. After a successful installation, you should see the following picture:

Other (older) versions of the ESP32 core are likely to work as well but I have not tried this. What doesn’t work for the time being are the newer versions 3.x.
Installation of Libraries
Next we need to install specific libraries and library versions. First click on this EPD47 Library library link and download the “EPD47-master.zip” file.
Unpack the ZIP file to extract it contents. You should see the following files in an unpacked folder named “EPD47-master”:

We need to copy the “EPD47-master” folder into the “libraries” folder for the Arduino IDE. Under Windows the “libraries” folder is typically located under:
C:\Users\<username>\OneDrive\Documents\Arduino\libraries
Since this folder already contains installed libraries, I recommend you temporarily rename it, e.g. to “_libraries” and create a new folder named “libraries”. This way you avoid conflicts with your already installed libraries and you don’t loose them either. Later you can easily revert this change. The picture below shows how your “Arduino” folder with the libraries should look like:

Next we copy the “EPD47-master” folder into the new “libraries” folder as shown below:

Finally, we need to install version 6.19.0 of the Arduinojson library by Benoit Blanchon. Open the LIBRARY MANAGER, enter “arduinojson” in the search bar and install the library. After a successful installation you should see the following picture:

If you then look again in the libraries folder you will now find the EPD47-master library and the ArduinoJson library:

ESP32-S3 Board and Settings
The LilyGO TTGO T5-4.7 E-Paper uses an ESP32-S3 and we therefore select the “ESP32S3 Dev Module” board. For that, click on the drop-down menu and then on “Select other board and port…”:

This will open a dialog where you can enter “esp32s3 dev” in the search bar. You will see the “ESP32S3 Dev Module” board under Boards. Click on it and the COM port to activate it and then click OK:

Note that you need to connect the board via the USB cable to your computer, before you can select a COM port.
Tool Settings
Before you can program the board, you will need to adjust the settings. Got to “Tools” and select the settings shown below:

Important are “USB CDC On Boot: Enable” to allow serial communication with the board, “Flash Size : 16MB(128Mb)”, “Partition Scheme: 16M Flash(3M APP/9.9MB FATFS)”, and “PSRAM: OPI PSRAM” to configure the memory. The other default settings should be fine as they are.
Upload
Usually, you can now just upload code via the Arduino IDE and the board will automatically switch to upload mode. However, if the board is in deep-sleep or stuck in a crash loop you can use the following sequence to force it into upload mode:
- Press and hold the BOOT(IO0) button
- While still pressing the BOOT(IO0) button, press RST
- Release the RST
- Release the BOOT(IO0) button

Code Example: Button
In this first code example we just verify that we can program the board, detect a button press and that serial communication works:
#define BTN 21
void setup() {
Serial.begin(112500);
pinMode(BTN, INPUT);
Serial.println("running...");
}
void loop() {
if (!digitalRead(BTN)) {
Serial.println("pressed");
delay(100);
}
}
We start by defining the GPIO pin 21 for the button labelled SENSOP_VN on the board.
#define BTN 21
This is the only button we can use for user input. It is the button next to the SD Card slot:

In the setup function we initiate serial communication, declare the button pin as input, and print “running…” to the serial monitor. The button is internally wired with a pullup resistor, which means we don’t need to enable the pullups when setting the pin mode.
void setup() {
Serial.begin(112500);
pinMode(BTN, INPUT);
Serial.println("running...");
}
In the loop function, we read the current status of the button and if the return value is false, we know that the button is pressed and we print the text “pressed” to the serial monitor.
void loop() {
if (!digitalRead(BTN)) {
Serial.println("pressed");
delay(100);
}
}
You should try this code first, to make sure you can program the board and see the serial output. If you have problems, make sure that the Tool settings are correct.
Code Example: Display
The second code example shows you how control the E-paper display. We will draw a circle and print the text “Makerguides” on the display:

Have a quick look at the complete code first and then we dive into its details:
#include "epd_driver.h"
#include "firasans.h"
uint8_t *framebuffer = NULL;
void create_framebuffer() {
size_t framesize = EPD_WIDTH * EPD_HEIGHT / 2;
framebuffer = (uint8_t *)ps_calloc(sizeof(uint8_t), framesize);
if (!framebuffer) {
Serial.println("alloc memory failed !!!");
return;
}
memset(framebuffer, 0xFF, framesize);
}
void init_display() {
create_framebuffer();
epd_init();
epd_poweron();
epd_clear();
}
void setup() {
Serial.begin(115200);
delay(1000);
init_display();
epd_draw_circle(EPD_WIDTH / 2, EPD_HEIGHT / 2, 150, 0, framebuffer);
const char *text= "Makerguides";
int cursor_x = 355;
int cursor_y = 270;
writeln((GFXfont *)&FiraSans, text, &cursor_x, &cursor_y, framebuffer);
epd_draw_grayscale_image(epd_full_screen(), framebuffer);
epd_poweroff_all();
}
void loop() {
}
Imports
We start by including two header files that provide the display driver API and a font definition. “epd_driver.h” exposes functions such as initialization, power control, drawing primitives, and flushing a pixel buffer to the panel, while “firasans.h” provides the glyph data used by the text rendering routine.
#include "epd_driver.h" #include "firasans.h"
Global State
Next we create a global pointer named framebuffer to store the address of a dynamically allocated pixel buffer in memory. The display driver functions in this sketch draw into this buffer first, and only later is the entire buffer sent to the E-paper panel to be displayed.
uint8_t *framebuffer = NULL;
create_framebuffer
The create_framebuffer() function is responsible for allocating RAM for the grayscale image that will be displayed. The key idea is that the E-paper panel expects a packed representation, and the expression EPD_WIDTH * EPD_HEIGHT / 2 reflects how many bytes are needed when each pixel uses 4 bits (half a byte). With 4-bit grayscale, two pixels can be stored per byte, which is why the total pixel count is divided by two.
After calculating the size, the function uses ps_calloc to allocate and zero-initialize the memory. On ESP32-class targets, this allocator is typically used to place large buffers into PSRAM when available, which is important because a full-frame grayscale buffer can be too large for internal RAM. If allocation fails, the function prints a diagnostic message to the serial console and returns early.
Finally, the buffer is filled with 0xFF. In many E-paper frame buffer formats, 0xFF corresponds to “white” or the lightest grayscale value across the entire buffer, so this step effectively creates a clean white canvas before any drawing happens.
void create_framebuffer() {
size_t framesize = EPD_WIDTH * EPD_HEIGHT / 2;
framebuffer = (uint8_t *)ps_calloc(sizeof(uint8_t), framesize);
if (!framebuffer) {
Serial.println("alloc memory failed !!!");
return;
}
memset(framebuffer, 0xFF, framesize);
}
init_display
The init_display() function centralizes the bring-up sequence for the E-paper hardware and ensures that the frame buffer exists before any drawing calls are made. It first calls create_framebuffer() so that later drawing functions have a valid destination.
The call to epd_init() typically initializes the display controller, configures GPIO/SPI, and prepares internal driver state. epd_poweron() enables the panel power rails and transitions the hardware into an operational state. And epd_clear() clears the panel, which is useful on E-paper because old content can persist if the panel is not explicitly refreshed.
void init_display() {
create_framebuffer();
epd_init();
epd_poweron();
epd_clear();
}
setup
The setup() function runs once at boot and performs all work in this sketch, which is why loop() is empty. We start by initializing the serial port at 115200 baud to allow debug output. Then we wait briefly to ensure the serial connection is stable before continuing. Next, we calls init_display() to allocate the frame buffer and power up and clear the panel.
void setup() {
Serial.begin(115200);
delay(1000);
init_display();
After initialization, we draws a circle into the frame buffer. The circle is centered on the screen by using half the display width and height. The radius is 150, and the color value 0 is passed as the grayscale level. In typical 4-bit grayscale conventions used by many E-paper drivers, lower numeric values often map to darker pixels, so 0 is commonly used for black. The final argument is framebuffer, which makes it explicit that the drawing operation modifies the memory buffer rather than updating the display immediately.
epd_draw_circle(EPD_WIDTH / 2, EPD_HEIGHT / 2, 150, 0, framebuffer);
Next, we prepare a C-string to be rendered and sets up cursor coordinates. The call to writeln() draws the string using the FiraSans font into the frame buffer at the specified starting position.
const char *text= "Makerguides"; int cursor_x = 355; int cursor_y = 270; writeln((GFXfont *)&FiraSans, text, &cursor_x, &cursor_y, framebuffer);
Once the circle and the text are drawn into RAM, the sketch performs the actual screen update. The call to epd_draw_grayscale_image() pushes the buffer to the display. The first argument, epd_full_screen(), indicates that the update region is the entire display rather than a sub-rectangle, which matches the fact that the sketch prepared a full-frame buffer. Finally, epd_poweroff_all() turns off the E-paper power, since the E-paper can hold the image without power once the update is complete.
epd_draw_grayscale_image(epd_full_screen(), framebuffer); epd_poweroff_all(); }
loop
The loop() function is empty because the sketch is designed as a one-shot render. The device initializes, draws into the buffer, updates the display once, powers the panel down, and then does nothing else. This pattern is typical for battery-powered E-paper projects that wake up, refresh content, and return deep-sleep state.
More Examples
For more examples, e.g. how to draw lines, rectangles and grayscale images see the folder under Xinyuan-LilyGO/LilyGo-EPD47/tree/esp32s3/examples.
Code Example: Weather Station
In this last example you will learn how to run a weather station on the LilyGO TTGO T5-4.7 E-Paper. The weather data are received over the internet from the OpenWeatherMap service, which is free but you will have to sign up – more about that later.
The original code, I am using here is by David Bird from 2021 and various versions of this code can be found at Xinyuan-LilyGO/LilyGo-EPD47/tree/esp32s3, Xinyuan-LilyGO/LilyGo-EPD-4-7-OWM-Weather-Display and DzikuVx/LilyGo-EPD-4-7-OWM-Weather-Display, for instance. However, none of those code examples were working well on my LilyGO TTGO T5-4.7 E-Paper. I therefore made some modifications.
Modifications
First, I fixed some issues with the layout of the weather data displayed. If you look at the following display created by the original code, you can see that the text for temperatures is not well aligned:

So, I moved the text elements a bit to improve the layout:

I also integrated the moon image and the icons for sunset and sunrise from the DzikuVx/LilyGo-EPD-4-7-OWM-Weather-Display into the Xinyuan-LilyGO/LilyGo-EPD-4-7-OWM-Weather-Display code.
Battery Charge
Next, I noted that the display is not working and not showing the battery charge icon, when powered from a battery. This is because 1) the original code blocks until serial communication is established, which doesn’t happen without USB connection to a computer:
Serial.begin(115200);
while (!Serial) ;
The while (!Serial) code needs to be removed and to be replaced by a short delay, e.g. delay(1000).
2) The original code reads the battery voltage from pin 36 but for my displays, I found that the battery voltage is measured at pin 14:
float voltage = analogRead(14) / 4096.0 * 6.566 * (vref / 1000.0);
With these changes you can power your LilyGO TTGO T5-4.7 E-Paper from a battery and the battery icon will tell you how much charge is left.
City location
The original code uses the name of a city to retrieve the weather information for that location. However, the OpenWeatherMap service also allows you to retrieve the current weather data for a location given by its latitude and longitude:
https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key}
Potentially, this is more accurate and you don’t have to use the specific city name recognized by OpenWeatherMap. Finding the latitude and longitude for your location is easy. Just go to google maps and perform a right click. A popup dialog will appear with the latitude and longitude numbers at the top. See the following screenshot:

USB connection
Finally, the original code updates the weather data on the display once every hour and goes to deep-sleep mode between updates to preserve battery power. This is great, but makes code changes difficult, since the USB connection is lost in deep-sleep and you cannot flash the board.
You need to reset the board and time it so that the flashing starts, while the USB connection is still active just after the reset. See the Upload section above. Since this is difficult, I added code that ensures that deep-sleep is NOT entered when the user-programmable button SENSOP_VN is pressed:
if(digitalRead(21)) { // SENSOP_VN button NOT pressed
BeginSleep();
}
This means, if you want to flash new code just press and hold SENSOP_VN while flashing. The board will not go into deep-sleep after the reset and the USB connection will remain active. Once flashed, you can just press RST again to restart the board with deep-sleep active.
Open weather map
The code uses the free OpenWeatherMap service to receive weather data from the internet. However, before you can use any of the OpenWeather APIs, you need an API-key and for that you need an account. To create a free account go to the sign-up page and enter your details there.

After that go to the api-key creation page and create an API-key. The API-key is that long string “sdfd87fakeby6apikeysf4z” that you see in the screenshot below. Your key will look different to mine.

Once, you have your API key (and the latitude and longitude of your location), you can try it out in your browser. Enter the following URL, with your API key in your browser:
https://api.openweathermap.org/data/2.5/weather?lat=44.34&lon=10.99&appid={API key}
Note that the Chrome browser has a Pretty Print button you can click and it will then show you the retrieved weather data in nicely formatted JSON:

This way, you can test your API key and your latitude and longitude values. If you need more help, have a look at our tutorial: Simple ESP32 Internet Weather Station.
Download Code and set Parameters
Once you have verified your API key, you can download the code for the weather station as a ZIP file: weather_lilygo_t5_epd.zip . Unpack it and open the file “weather_lilygo_t5_epd.ino” in the “weather_lilygo_t5_epd” folder in your Arduino IDE.
As a last step you need to set the Wi-Fi credentials and weather data parameters. Click the “owm_credentials.h” tab and set your SSID, Password, API key and the location information:

Depending on your location you may also want to change the Language, Hemisphere, Units and Timezone parameters.
There is also example code on how to configure the weather station via a web interface: Xinyuan-LilyGO/LilyGo-EPD-4-7-OWM-Weather-Display/tree/web, if you prefer that over hard coding the settings.
Once, you have adjusted the settings, flash the code to your LilyGO TTGO T5-4.7 board and after some short flickering you should see the weather data appearing on the display:

Connecting 18650 Battery
The LilyGO TTGO T5-4.7 E-Paper board comes with a connector and charging circuit for an external LiPo battery. I connected a 18650 rechargeable battery. You can use a smaller (flat) LiPo battery as well but they tend to be more expensive or have less capacity. The photo below shows you how to connect the 18650 battery:

Running Time
A typical 18650 battery has a capacity of 2300 to 3600mAh and the peak current the ESP32 draws when using Wi-Fi can go up to 400mA. The deep-sleep current of LilyGO TTGO T5-4.7 board is apparently around 170µA, but I did not measure it.
If we use a 18650 with 3300mAh, and if we activate the Wi-Fi for one second once every hour to download the weather data we get an estimated runtime of over a year on a single charge.
Here is the calculation: with an average current of 400mA × 1s / 3600s = 0.111mA for Wi-Fi and a deep-sleep current of 0.17mA, the total average current is 0.111mA + 0.17mA = 0.281mA per second. Assuming a 3300mAh battery, the estimated runtime is then 3300mAh / 0.281mA ≈ 11744h ≈ 1.34 years.
Conclusions
In this tutorial you learned how to build a weather station with the LilyGO TTGO T5-4.7 E-Paper display. For more information about the LilyGO TTGO T5 have a look at the Wiki and the code examples at Xinyuan-LilyGO/LilyGo-EPD47/tree/esp32s3/examples.
The documentation mentions that the screen should not be partially refreshed for a long time, since it will leave irreversible residual images. So be careful when building applications that use Partial Refresh.
For more examples on E-paper displays have a look at our Weather Station on e-Paper Display, Simple ESP32 Internet Weather Station, Temperature Plotter on e-Paper Display, Monthly Calendar on E-Paper Display and the Digital Clock on e-Paper Display tutorials.
If you have any questions feel free to leave them in the comment section.
Happy Tinkering 😉
Stefan is a professional software developer and researcher. He has worked in robotics, bioinformatics, image/audio processing and education at Siemens, IBM and Google. He specializes in AI and machine learning and has a keen interest in DIY projects involving Arduino and 3D printing.


Egor
Thursday 15th of January 2026
Спасибо большое