Skip to Content

CrowPanel 2.8″ ESP32 Display : Easy Setup Guide

CrowPanel 2.8″ ESP32 Display : Easy Setup Guide

In this tutorial you will learn how to get started with the CrowPanel 2.8″ ESP32 Display from ELECROW. The CrowPanel displays are great, since the have pretty much everything you need already built in. This includes the TFT touch screen, an ESP32, a TF Card slot, a battery connector with charger, an I2C interface and even a speaker interface.

However, depending on your experience, the first steps in using them can be a bit challenging. Hopefully this setup guide will help you. I specifically focus on the 2.8″ display version but the information also applies to the 2.4″ and the 3.5″ version. The other, larger screens in the CrowPanel series, however, use different display drivers.

So, let’s get started.

Required Parts

The required parts obviously include the display and if you want to connect some external hardware; some cables, resistors, LEDs and a breadboard will come in handy.

CrowPanel 2.8″ ESP32 Display

USB C Cable

Dupont wire set

Dupont Wire Set

Half_breadboard56a

Tablero de pruebas

Resistor & LED kit

Makerguides.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to products on Amazon.com. As an Amazon Associate we earn from qualifying purchases.

Features of the CrowPanel 2.8″ ESP32 Display

The CrowPanel 2.8″ ESP32 Display by ELECROW is a HMI resistive touch screen with a 320*240 resolution TFT display. It uses the ESP32-WROOM-32 module as the control processor, which comes with integrated WiFi and Bluetooth.

Front of CrowPanel 2.8″ ESP32 Display (source)

In addition, the board has a TF card slot, an UART interface, an I2C interface, a speaker interface, a battery connector with charging capability and a small GPIO port with two GPIO pins. See the pinout below:

Pinout of CrowPanel 2.8″ ESP32 Display (source)

The following table summarizes which GPIO pins are assigned to which of the three IO interfaces. For other GPIO pins have a look at the schematics in the appendix of this post.

GPIO_DIO25; IO32
UARTRX(IO16); TX(IO17)
I2CSDA(IO22); SCL(IO21)

You can power the board via the USB port (5V, 2A) or by connecting a standard 3.7V LiPo battery to the BAT connector. If the USB cable and the battery are connected at the same time, the board will charge the battery. The maximum charging current is 500mA.

The board supports multiple development environments such as Arduino IDE, Espressif IDF, Lua RTOS, and Micro Python, and is compatible with the LVGL graphics library. However, in this tutorial I will focus on the Arduino IDE and the TFT_eSPI graphics library

CrowPanel ESP32 Display Series

The CrowPanel 2.8″ ESP32 Display is part of an entire family of displays ranging from 2.4 inch up to 7 inch. For an overview see the table below.

CrowPanel ESP32 Displays
CrowPanel ESP32 Displays (source)

In this tutorial, I will specifically describe the setup of the 2.8″ version. But the code examples and setup procedure are essentially the same for the 2.4″ display and the 3.5″ display, since they are using the same or a similar display driver (ILI9341, ILI9488). See the yellow marked sections in the table above.

However, since I am using the TFT_eSPI library, the code examples will probably not work for the larger displays (4.3″, 5″, 7″), as the drivers for those displays are not supported – as far as I can tell. Correct me if I am wrong, since I actually haven’t tried this.

Before you implement any fancy graphics, however, I suggest you to test the uploading and running of a simple blink program first. And this is the topic of the next section.

Testing GPIO

The display comes with a UI demo that runs when plug-in the board. That is a good first test. After that, however, we want to test if we can upload and run our own code. The typical test example for this is the Blink program.

Since, the display has no built-in LED, we are going to use the two available GPIO pins of the GPIO_D port to control two external LEDs. The wiring diagram below shows you how to connect the two LEDs to the port. You can use the cable that is provided with the display for that.

Wiring for blinking two LEDs
Wiring for blinking two LEDs

The pins within the GPIO_D port are as follows IO25; IO32, 3.3V, GND. We don’t need the 3.3V output, just the two GPIO pins and ground. When building the circuit, don’t forget the 220Ω resistors to limit the current to the LEDs and make sure ground is properly connected.

After that you can upload the following code that blinks the two LEDs alternatingly. It also prints to the Serial monitor, so if there is some issue with the wiring, you can at least check that the program is running. Make sure the baud rate for the Serial monitor is set to 115200.

const int led1 = 25;
const int led2 = 32;

void setup() {
  Serial.begin(115200);
  pinMode(led1, OUTPUT);
  pinMode(led2 , OUTPUT);
}

void loop() {
  Serial.println("on");
  digitalWrite(led1, HIGH);
  digitalWrite(led2 , LOW);
  delay(1000);

  Serial.println("off");
  digitalWrite(led1, LOW);
  digitalWrite(led2, HIGH);
  delay(1000);
}

Since, the display is using an ESP32 you will need the ESP32 core installed. If you have trouble with that have a look at our tutorial A Beginner’s Guide To ESP32 Programming, where we describe how this is done.

Note that there are two versions of the display board. The older V1 version requires you to hold the BOOT button, and then press the RESET button to switch into programming mode. The new V2 version doesn’t require that anymore. I have the V2 version and could upload without any issues or the need to press buttons.

Setting up TFT_eSPI library via User_Setup.h

So far things should have been easy. Here comes the difficult part. If you want show something on the display you will need to use a graphics library. The most common one is probably the TFT_eSPI library by Bodmer. You will find it in the Library Manager and you can install it in the usual way from there:

TFT_eSPI library in Library Manager
TFT_eSPI library in Library Manager

The difficult part is to configure the right parameters for the CrowPanel 2.8″ display to make the TFT_eSPI library work. For that you need to find a file named User_Setup.h, which is part of the TFT_eSPI library. Typically, on Windows it will be located under a path similar to the following

C:\Users\stefa\OneDrive\Documents\Arduino\libraries\TFT_eSPI

The exact path depends on your user name (in my case “stefa“) and your operating system and whether you are using OneDrive or not. You will have to hunt for it.

If you open the User_Setup.h file you will see a massive amount of settings that need to be correct to make the display show anything. Good luck with that!

User_Setup.h
User_Setup.h

It is surprisingly hard to find the correct setting and I have to thank Ralph S Bacon and his TFT 3.5″ Touch Screen & ESP32 built in – Elecrow review that saved me a lot of trial and error. Here are the settings you need:

#define ILI9341_DRIVER
#define TFT_WIDTH  320
#define TFT_HEIGHT 240 

#define TFT_BACKLIGHT_ON HIGH
#define TFT_BL   27 
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS   15
#define TFT_DC    2 
#define TFT_RST  -1
#define TOUCH_CS 33

#define SPI_FREQUENCY        27000000
#define SPI_TOUCH_FREQUENCY   2500000
#define SPI_READ_FREQUENCY   16000000

#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6  // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7  // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
#define LOAD_FONT8  // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts
#define SMOOTH_FONT

According to the specs, the 2.8″ display uses the ILI9341 driver and has a resolution of 320×240 pixels. So that is the first part of the settings. Then comes the assignment of the GPIO pins that control the display via SPI, and the SPI frequency settings. And finally the available fonts.

You have to edit User_Setup.h, comment everything else out (//) and make sure that only these settings are active. I suggest you save the original User_Setup.h to a new file, e.g. “User_Setup.h.bak” and simply replace everything in User_Setup.h by the settings above.

Once that is done, you should be able to run any of the example programs that come with the TFT_eSPI library. My favorite being the Cellular_Automata program:

Example programs of the TFT_eSPI library
Example programs of the TFT_eSPI library

Settings for the 2.4″ and the 3.5″ CrowPanel Displays

The settings for 2.4″ Display are same as for the 2.8″ Display shown above, since the 2.4″ Display has the same resolution (320×240) and display driver (ILI9341). For the larger 3.5″ CrowPanel Display you just have to adjust the driver (ILI9488) and the resolution settings (480×320) to match its specs:

#define ILI9488_DRIVER
#define TFT_WIDTH  480
#define TFT_HEIGHT 320 

The 4.3″, 5″ and 7″ Crowpanel displays use drivers that are not directly supported by the TFT_eSPI library. So, as far as, I can tell you cannot use this library with those displays but I did not actually test this.

Setting up TFT_eSPI library via tft_setup.h

Modifying the settings in User_Setup.h is fine for trying out the examples of the TFT_eSPI library. But this is not a good approach, when you are writing and uploading your own code. The reason is, whenever you wite code for a different display or reinstall/update the TFT_eSPI library you have to change the settings in User_Setup again.

Luckily, the TFT_eSPI library provides a solution for this. Instead of modifying the settings in User_Setup.h, which globally affects all projects, you can add a file named tft_setup.h to your specific Arduino project. This file contains the same settings as described above but will only be valid for the specific project. That means you can have different projects with different displays, without having to change User_Setup.h whenever you use a different display.

Here is an example how that works in detail. First create a new Arduino project and save it as “tft_test“, for instance. That will create a folder tft_test with a file tft_test.ino in it:

Arduino Project Folder tft_test
Arduino Project Folder tft_test

In this folder, now create a file named “tft_setup.h” – exactly with this name. Your project folder should then look like this:

Arduino Project Folder with tft_setup.h

In your Arduino IDE you now should see two tabs; tft_test.ino y tft_setup.h that you can click on to edit the files.

Arduino IDE with tft_test.ino and tft_setup.h tabs
Arduino IDE with tft_test.ino y tft_setup.h tabs

In the file tft_setup.h copy the complete display settings shown above. The following excerpt is just an example. You need to copy the complete settings.

#define ILI9341_DRIVER
#define TFT_WIDTH  320
#define TFT_HEIGHT 240 
..
#define LOAD_FONT8
#define LOAD_GFXFF  
#define SMOOTH_FONT

In the file tft_test.ino copy the following code. This a little test that uses the TFT_eSPI library to display the text “Makerguides” in the center of the screen.

#include "tft_setup.h"
#include"TFT_eSPI.h"

TFT_eSPI tft = TFT_eSPI();

void setup(void) {
  tft.init();
  tft.fillScreen(TFT_BLACK);  
}

void loop() {
  tft.setCursor(50, 150, 2);  
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextSize(2);
  tft.println("Makerguides");
  delay(5000);
}

Note that tft_setup.h is included at the top of the program. The code itself is easy to understand. We create a TFT_eSPI object and initialize it in the setup() function. In the loop function, we set the cursor and font (2), text color and text size, and finally print the string “Makerguides” to the TFT display.

If you compile and upload this program, you should see the following output on your display:

Text "Makerguides" shown on display
Text “Makerguides” shown on display

If you made it that far, congratulations! The worst is over. In the next sections, you will learn how to read touch data to calibrate the touch screen, and how to build a simple user interface.

Calibrating the Touchscreen

The CrowPanel 2.8″ Display comes with a resistive touch screen that you need to calibrate first, before you can used it with the TFT_eSPI library. Some of the code examples for the TFT_eSPI library have the calibration code integrated, but it is cumbersome to have it every project.

Extracting calibration parameters

I therefore derived the following program from the examples that extracts the relevant calibration parameters:

#include "tft_setup.h"
#include "TFT_eSPI.h"

TFT_eSPI tft = TFT_eSPI();

void setup() {
  Serial.begin(115200);
  tft.begin();
  tft.setRotation(0);
}

void loop() {
  uint16_t cal[5];

  tft.fillScreen(TFT_BLACK);
  tft.setCursor(20, 0);
  tft.setTextFont(2);
  tft.setTextSize(1);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.print("Touch corners ... ");
  tft.calibrateTouch(cal, TFT_MAGENTA, TFT_BLACK, 15);
  tft.println("done.");

  Serial.printf("cal: {%d, %d, %d, %d, %d}\n",
                cal[0], cal[1], cal[2], cal[3], cal[4]);
  delay(10000);
}

When you run it it asks you to touch the four corners of the the screen and then prints out the 5 calibration parameters (corner coordinates and screen orientation) to the Serial.monitor. Your display during calibration should look like this:

Calibration of touch screen
Calibration of touch screen

and on the Serial.monitor you should see something like this printed out, when the calibration is finished:

  cal: {286, 3478, 196, 3536, 2}

The calibration repeats itself every 10 seconds, so you can have several tries to get the most accurate parameters. Copy them somewhere, since you will need them for the next step.

Reading touch coordinates

In this section we implement a little program to read the coordinate where the display is touched. Have a look at the following code:

#include "tft_setup.h"
#include "TFT_eSPI.h"

TFT_eSPI tft = TFT_eSPI();
uint16_t cal[5] = { 286, 3478, 196, 3536, 2 };

void setup() {
  tft.begin();
  tft.setRotation(0);
  tft.setTextSize(2);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.fillScreen(TFT_BLACK);
  tft.setTouch(cal);
}

void loop() {
  uint16_t x = 0, y = 0;

  if (tft.getTouch(&x, &y)) {
    tft.setCursor(50, 150);
    tft.printf("x=%d, y=%d    ", x, y);
  }

  delay(100);
}

First, we include the libraries as usual and create the screen object. The next line is important. Here we define and store the calibration parameter we extracted before:

uint16_t cal[5] = { 286, 3478, 196, 3536, 2 };

After that, in the setup function we set the typical display parameters such as screen rotation, text size and text color. At the end comes the most important line, where we actually set the calibration parameters:

  tft.setTouch(cal);

The main loop is simple. Every 100msec we call tft.getTouch and check if we have detected a touch. If that is the case, we display the touch coordinates on the screen. Your screen should look like this:

Reading and displaying touch coordinates
Reading and displaying touch coordinates

And with that we have everything to actually build a User Interface. The next sections provide a simple example of that.

Building a User Interface

In this section we are going to build aa simple User Interface (UI) with two buttons that will allow us to control the two LEDs we connected to the GPIO_D port before. The User Interface will look like this:

UI with two Buttons to control the LEDs
UI with two Buttons to control the LEDs

However, before we can implement this UI we need to install one more library. The TFT_eWidget library is a small extension to the TFT_eSPI library that provides UI elements like buttons, gauges or graphs. It can be installed in the usual way via the Library Manager:

TFT_eWidget library in Library Manager
TFT_eWidget library in Library Manager

Once installed, we have everything we need. Have a look at the complete code for the UI first, before we discuss its details:

#include "tft_setup.h"
#include "TFT_eSPI.h"
#include "TFT_eWidget.h"

TFT_eSPI tft = TFT_eSPI();
uint16_t cal[5] = { 286, 3478, 196, 3536, 2 };

const int led1 = 25;
const int led2 = 32;

ButtonWidget btn1 = ButtonWidget(&tft);
ButtonWidget btn2 = ButtonWidget(&tft);
ButtonWidget* btns[] = { &btn1, &btn2 };

void btn1_pressed(void) {
  if (btn1.justPressed()) {
    bool state = !btn1.getState();
    btn1.drawSmoothButton(state, 2, TFT_WHITE, state ? "ON" : "OFF");
    digitalWrite(led1, state ? HIGH : LOW);
  }
}

void btn2_pressed(void) {
  if (btn2.justPressed()) {
    bool state = !btn2.getState();
    btn2.drawSmoothButton(state, 2, TFT_WHITE, state ? "ON" : "OFF");
    digitalWrite(led2, state ? HIGH : LOW);
  }
}

void initButtons() {
  uint16_t w = 100;
  uint16_t h = 50;
  uint16_t x = (tft.width() - w) / 2;
  uint16_t y = tft.height() / 2 - h - 10;

  btn1.initButtonUL(x, y, w, h, TFT_WHITE, TFT_BLACK, TFT_YELLOW, "LED1", 2);
  btn1.setPressAction(btn1_pressed);
  btn1.drawSmoothButton(false, 2, TFT_BLACK);

  y = tft.height() / 2 + 10;
  btn2.initButtonUL(x, y, w, h, TFT_WHITE, TFT_BLACK, TFT_GREEN, "LED2", 2);
  btn2.setPressAction(btn2_pressed);
  btn2.drawSmoothButton(false, 2, TFT_BLACK);
}

void handleButtons() {
  uint8_t nBtns = sizeof(btns) / sizeof(btns[0]);
  uint16_t x = 0, y = 0;
  bool touched = tft.getTouch(&x, &y);
  for (uint8_t b = 0; b < nBtns; b++) {
    if (touched) {
      if (btns[b]->contains(x, y)) {
        btns[b]->press(true);
        btns[b]->pressAction();
      }
    } else {
      btns[b]->press(false);
      btns[b]->releaseAction();
    }
  }
}

void setup() {
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);

  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  tft.setTouch(cal);
  
  initButtons();2
}

void loop() {
  handleButtons();
  delay(50);
}

The UI will have the following five states or screens: the two button initialized and then either in an on or off state.

Different screens of the UI
Different screens of the UI

Let’s have a closer look at how this is achieved.

Libraries

First, we install the three required libraries. Remember that “tft_setup.h” is the file that contains the settings for the TFT display and must be part of the Arduino project that contains the code above.

#include "tft_setup.h"
#include "TFT_eSPI.h"
#include "TFT_eWidget.h"

However, if you have the correct display settings in User_Setup.h, you don’t need to include the tft_setup.h file.

Constants and Objects

Next we create the display object via TFT_eSPI tft and a constant cal with the calibration parameters. Then we define the GPIO pins for the two LEDs. After that, comes the creation of the two button objects btn1 y btn2.

TFT_eSPI tft = TFT_eSPI();
uint16_t cal[5] = { 286, 3478, 196, 3536, 2 };

const int led1 = 25;
const int led2 = 32;

ButtonWidget btn1 = ButtonWidget(&tft);
ButtonWidget btn2 = ButtonWidget(&tft);
ButtonWidget* btns[] = { &btn1, &btn2 };

We store the two buttons in an array btns, since it will make the event handling simpler. More about that later.

Button Press Handlers

The functions btn1_pressed() y btn2_pressed() handle button presses for each LED button. When a button is pressed, the corresponding LED state is toggled and the button’s appearance is updated on the screen. Below is the only the code for btn1 but the code for btn2 is essentially the same, just a different led is switched.

void btn1_pressed(void) {
  if (btn1.justPressed()) {
    bool state = !btn1.getState();
    btn1.drawSmoothButton(state, 2, TFT_WHITE, state ? "ON" : "OFF");
    digitalWrite(led1, state ? HIGH : LOW);
  }
}

Button Initialization

El initButtons() function sets up the position, appearance, and actions for the two buttons on the TFT screen. Each button is initialized with specific properties and its press action. We make the button for the yellow LED, yellow (TFT_YELLOW) and the one for the green LED, green (TFT_GREEN).

void initButtons() {
  ...
  btn1.initButtonUL(x, y, w, h, TFT_WHITE, TFT_BLACK, TFT_YELLOW, "LED1", 2);
  btn1.setPressAction(btn1_pressed);
  btn1.drawSmoothButton(false, 2, TFT_BLACK);

  ...
  btn2.initButtonUL(x, y, w, h, TFT_WHITE, TFT_BLACK, TFT_GREEN, "LED2", 2);
  btn2.setPressAction(btn2_pressed);
  btn2.drawSmoothButton(false, 2, TFT_BLACK);
}

Button Handling

En el handleButtons() function, we check for touch input on the TFT screen via tft.getTouch(). We iterate over all buttons and call the pressAction() or the releaseAction() based on the touch coordinates.

void handleButtons() {
  ...
  bool touched = tft.getTouch(&x, &y);
  for (uint8_t b = 0; b < nBtns; b++) {
    if (touched) {
      if (btns[b]->contains(x, y)) {
        btns[b]->press(true);
        btns[b]->pressAction();
      }
    } else {
      btns[b]->press(false);
      btns[b]->releaseAction();
    }
  }
}

The buttons we created have no release action attached, since we don’t need it here. But to keep the code generic, we still call releaseAction(). This has the advantage that the code will work unchanged, if you want to use release actions later.

Setup Function

En el setup() function, we configure the LED pins as outputs, initialize the TFT screen, set its rotation, fill the screen with a black color, set touch calibration and create the buttons.

void setup() {
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);

  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  tft.setTouch(cal);
  
  initButtons();
}

Loop Function

El loop() function continuously checks for button interactions by calling handleButtons() and introduces a small delay to avoid rapid button presses.

void loop() {
  handleButtons();
  delay(50);
}

Note that calling delay() blocks all other actions. Alternatively, you could do something like this to avoid the blocking:

void loop() {
  static uint32_t lastTime = millis();
  if (millis() - lastTime >= 50) {
    handleButtons();
    lastTime = millis();
  }
}

And there you have it, a simple UI to control two buttons from a CrowPanel 2.8″ ESP32 Display.

The code example above is derived from the Button_demo.ino example of the TFT_eWidget library. For more information and additional examples have a look there.

Conclusión

This tutorial provided a detailed setup guide for the CrowPanel 2.8″ ESP32 Display. With no or little changes this guide will also apply to the 2.4″ and 3.5″ display.

The CrowPanel ESP32 Display boards come with pretty much everything you need for a project that involves a display. It includes a TFT touch screen, an ESP32, a TF Card slot, a battery connector with charger, a speaker interface and a I2C interface.

However, this also means that the number of free GPIO pins is limited to only two. But since there is a I2C interface, you can use a GPIO expander to add more GPIOs. Have a look at our tutorial Using GPIO Expander MCP23017 With Arduino.

Working with displays is often tricky, since you need the correct display driver and settings. In this setup guide you learned how to configure the TFT_eSPI.h library to drive the CrowPanel 2.8″ Display. We furthermore used the TFT_eWidget extension library to implement a simple UI with two buttons to control two LEDs. Have a look at the Digital Clock with CrowPanel 3.5″ ESP32 Display tutorial, where we extend the example code of this tutorial to build a Digital Clock Display.

If you need more advanced UIs the LVGL library is a better choice, since it has more widgets and a GUI Builder software. However, it is also a lot more complex to set up and to use. For more information, have a look at the tutorials at the ELECROW website. There is Arduino example code for LVGL, but also code examples for ESP-IDF, ESPHome, PlatformIO and MicroPython.

If you want to use LVGL, also have a look at the CrowPanel ESP32 Display Video Tutorials that explain how to use the Squareline Studio GUI builder. Note that there is a free, albeit limited version (Personal plan) of this GUI builder.

Plenty of things to play with! Have fun ; )

Links

Here some links that I found useful when writing this guide.

Schematics

I extracted the following schematics of the board from the link CrowPanel 2.8″-ESP32 Display Schematics. It seems some code examples use the wrong or outdated pins and in this case the schematics will help to clear things up.

Schematic USB Type-C
USB Type-C Schematics
Schematic USB
USB Schematics
Schematic Battery port and charger
Battery port and charger Schematics
Schematic Power
Power Schematics
Schematic ESP32-WROOM-32-N4
ESP32-WROOM-32-N4 Schematics
Schematic LCD Connector
LCD Connector Schematics
Schematic Speaker
Speaker Schematics
Schematic GPIO_D
GPIO_D Port Schematics
Schematic I2C
I2C Port Schematics
Schematic UART
UART Port Schematics
Schematic TF Card
TF Card Schematics
Schematic XPT2046
XPT2046 Schematics