CrowPanel ESP32-C3 1.28 inch Round IPS Capacitive Touch Display
Table of Contents
CrowPanel ESP32-C3:
CrowPanel ESP32-C3 1.28 inch Round IPS Capacitive Touch Display- Today, we are unboxing and reviewing an exciting piece of tech for your next DIY project – the CrowPanel ESP32-C3 Display.
Let’s start with the unboxing.
Unboxing Experience:
Inside the box, we have:
The CrowPanel ESP32-C3 display itself, which looks sleek and modern with its round design. This compact, round, 1.28-inches IPS display comes with capacitive touch and is powered by the ESP32-C3.
And a USB cable for powering and programming the device.
Physical Hardware Review:
Let’s take a closer look at the hardware. The CrowPanel ESP32 Display is compact and lightweight, with a 1.28-inch round IPS screen at the front. It has a resolution of 240*240 and 178° viewing angle.
On the back, we can see the onboard ESP32C3 microcontroller, equipped with a 32-bit dual-core chip, and the highest clock frequency reaches 160Mhz. Supports 2.4G WiFi and BLE low-power Bluetooth. There’s also a reset button, a Boot button, Encoder, a Vibration Motor, Buzzer, Type-C 5V UART Serial port, a Battery Socket, CR927 battery holder for the RTC based on the BM8563, and a Custom button.
Pin Out:
Specification
Main Chip | ESP32-C3 |
Processor | 32-bit RISC-V single-core processor, up to 160 MHz |
Memory | 384 KB ROM
400 KB SRAM (16 KB for cache) |
8 KB SRAM in RTC | |
Size | 1.28-inch |
Resolution | 240*240 |
Signal Interface | SPI |
Touch Type | Capacitive Touch |
Panel Type | TFT LCD, IPS Panel |
Color Depth | 262K |
Brightness | 350 cd/m² |
Viewing Angel | 178° |
Button | Rest Button, Boot Button, Custom Button |
Interface | Type-C Interface, Battery Interface |
Operation Power | Module:DC5V
Main Chip:3.3V
|
Active Area | 32.51*32.51mm |
Operating temperature | -10~60°C |
Dimensions | 42x42x9.8mm |
Net Weight | 15g |
Powering Up and First Impressions:
Now, let’s power it up and see how it performs.
Once connected, the 1.28-inch IPS screen lights up with sharp colors and wide viewing angles. The round form factor adds a unique aesthetic, making it ideal for applications like smartwatches, dashboards, or portable devices.
Supports multiple development environments;
- Arduino IDE
- Espressif IDF
- Lua RTOS
- Home Assistant
- PlatformIO
- Micro python, and
- Supports LVGL library.
Touch Feel and Responsiveness:
The capacitive touch screen is incredibly responsive.
Taps, swipes, and gestures feel smooth and accurate.
Applications and Use Cases:
So, what can you use the CrowPanel for? Well, the possibilities are endless!
- You can build a smartwatch for notifications and fitness tracking.
- Create a mini dashboard for your IoT devices.
- Develop a gaming interface or even an interactive art piece.
- Its compact size and advanced features make it ideal for wearable devices, home automation, and more.
You can use the CrowPanel to make a complete smartwatch for yourself, adding any features you like.
Not only that, but you can also connect it to any IoT platform to monitor sensors and control devices. I am making a full series of articles and videos on CrowPanel, and after watching them, you will be able to create any type of smartwatch. So, make sure to subscribe if you don’t want to miss this amazing series of articles and videos. Anyway;
5 Examples:
Next, I am going to share with you 5 examples that you can use to control the Vibration Motor and Buzzer, check if a button is pressed, access the date and time from the RTC (Real-Time Clock/Calendar), and read the x, y values along with gestures.
But before we start with these examples, first; we need to install the required libraries.
Go to the product’s official page and download the ESP32_1.28_Arduino_Demo. If you open this folder, you will see a libraries folder. Open this folder and, for now, copy these 3 folders.
Then, go to the Arduino Libraries folder and paste the copied folders.
Next, open the latest version of the Arduino IDE.
Go to the File menu then to Preferences.
https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json
Use this URL in the Additional Boards Manager URLs field.
Open the Boards Manager and search for ESP32.
Install the latest version of ESP32 by Espressif Systems. I have already installed it. Next, go to the Tools menu, then to Board, and then to ESP32. You will see all the variants of the ESP32 have been installed. The one we will be using is the ESP32C3 Dev Module.
Now, click on the Library Manager and search for this library “LovyanGFX”.
Although we already pasted this library in the Arduino libraries folder, you need to update it to the latest version. I have also updated it to the latest version.
Now, let’s go ahead and start with our first example.
Example 1: Controlling a Vibration Motor
Using this program, you can control the Vibration Motor. All the instructions are well-commented. The purpose of this program is to turn the Vibration Motor ON and OFF. This is just a basic example. In upcoming videos, we will use it at an advanced level with Squareline and LVGL Library.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
#include <Wire.h> #define PI4IO_I2C_ADDR 0x43 // I2C address for the I/O extender // Function to initialize the I/O extender void init_IO_extender() { Wire.beginTransmission(PI4IO_I2C_ADDR); // Start I2C transmission to the extender Wire.write(0x01); // Select the test register Wire.endTransmission(); // End the transmission Wire.requestFrom(PI4IO_I2C_ADDR, 1); // Request 1 byte from the extender uint8_t rxdata = Wire.read(); // Read the received data Serial.print("Device ID: "); Serial.println(rxdata, HEX); // Print the device ID in hexadecimal Wire.beginTransmission(PI4IO_I2C_ADDR); // Start I2C transmission again Wire.write(0x03); // Select the I/O direction register Wire.write((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4)); // Set pins 0-4 as outputs Wire.endTransmission(); // End the transmission Wire.beginTransmission(PI4IO_I2C_ADDR); // Start I2C transmission Wire.write(0x07); // Select the Output Hi-Z register Wire.write(~((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4))); // Set pins 0-4 low Wire.endTransmission(); // End the transmission } // Function to set the state of a specific pin on the I/O extender void set_pin_io(uint8_t pin_number, bool value) { Wire.beginTransmission(PI4IO_I2C_ADDR); // Start I2C transmission Wire.write(0x05); // Select the test register Wire.endTransmission(); // End the transmission Wire.requestFrom(PI4IO_I2C_ADDR, 1); // Request 1 byte from the extender uint8_t rxdata = Wire.read(); // Read the received data Serial.print("Before the change: "); Serial.println(rxdata, HEX); // Print the data before change Wire.beginTransmission(PI4IO_I2C_ADDR); // Start I2C transmission Wire.write(0x05); // Select the Output register if (!value) { Wire.write((~(1 << pin_number)) & rxdata); // Set the pin low } else { Wire.write((1 << pin_number) | rxdata); // Set the pin high } Wire.endTransmission(); // End the transmission // Read the output state to verify the change Wire.beginTransmission(PI4IO_I2C_ADDR); Wire.write(0x05); Wire.endTransmission(); Wire.requestFrom(PI4IO_I2C_ADDR, 1); rxdata = Wire.read(); Serial.print("after the change: "); Serial.println(rxdata, HEX); // Print the data after change } void setup() { Serial.begin(115200); // Start serial communication at 115200 baud rate Wire.begin(4, 5); // Initialize I2C bus with SDA and SCL pins init_IO_extender(); // Initialize the I/O extender delay(100); // Short delay } void loop() { // Main code to run repeatedly set_pin_io(0, true); // Set pin 0 to high delay(1000); // Wait for 1000 milliseconds set_pin_io(0, false); // Set pin 0 to low delay(1000); // Wait for another 1000 milliseconds } |
For the practical demonstration watch the video tutorial given at the end of this article.
Now, let’s move on to example #2, which is about controlling the Buzzer.
Example 2: Controlling a Buzzer
Using this program, you can control the onboard buzzer connected to GPIO 3. You can generate amazing tones. Right now, it’s set to produce a tone at a frequency of 100 Hz.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Define the pin number for the buzzer #define BUZZER 3 void setup() { // This code runs once at the beginning of the program: pinMode(BUZZER, OUTPUT); // Set the buzzer pin as an output digitalWrite(BUZZER, LOW); // Initially set the buzzer to a low state (off) } void loop() { // This code runs repeatedly after the setup(): tone(BUZZER, 100); // Make the buzzer produce a tone at a frequency of 100 Hz delay(1000); // Wait for one second tone(BUZZER, 0); // Stop the buzzer from producing sound (frequency set to 0) delay(1000); // Wait for another second } |
Let’s generate the “Happy Birthday” tone.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#define BUZZER 3 // Define the buzzer pin // Notes frequencies (in Hz) #define NOTE_C4 262 #define NOTE_D4 294 #define NOTE_E4 330 #define NOTE_F4 349 #define NOTE_G4 392 #define NOTE_A4 440 #define NOTE_AS4 466 // Add A#4 (A-sharp) frequency #define NOTE_B4 494 #define NOTE_C5 523 // Happy Birthday melody int melody[] = { NOTE_C4, NOTE_C4, NOTE_D4, NOTE_C4, NOTE_F4, NOTE_E4, // Happy birthday to you NOTE_C4, NOTE_C4, NOTE_D4, NOTE_C4, NOTE_G4, NOTE_F4, // Happy birthday to you NOTE_C4, NOTE_C4, NOTE_C5, NOTE_A4, NOTE_F4, NOTE_E4, NOTE_D4, // Happy birthday dear [Name] NOTE_AS4, NOTE_AS4, NOTE_A4, NOTE_F4, NOTE_G4, NOTE_F4 // Happy birthday to you }; // Note durations: 4 = quarter note, 8 = eighth note, etc. int noteDurations[] = { 4, 8, 4, 4, 4, 2, 4, 8, 4, 4, 4, 2, 4, 8, 4, 4, 4, 8, 4, 4, 8, 4, 4, 4, 2 }; void setup() { pinMode(BUZZER, OUTPUT); // Set the buzzer pin as an output digitalWrite(BUZZER, LOW); // Ensure the buzzer is off initially } void loop() { // Play the Happy Birthday melody for (int i = 0; i < sizeof(melody) / sizeof(melody[0]); i++) { int duration = 1000 / noteDurations[i]; // Calculate the note duration tone(BUZZER, melody[i], duration); // Play the note delay(duration * 1.3); // Wait for the note to finish with a slight pause noTone(BUZZER); // Stop the tone } delay(2000); // Wait for 2 seconds before repeating the melody } |
Now, let’s move on to example #3, which is about reading a button press.
Example 3: Button Press
The purpose of this program is to check whether the button connected to GPIO 1 is pressed or released.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// Define the pin number for the button const int buttonPin = 1; // The pin number where the button is connected // Variable to store the current state of the button int buttonState = 0; // Variable to hold the button's state, initially set to HIGH (0) void setup() { Serial.begin(115200); // Initialize serial communication at a baud rate of 115200 pinMode(buttonPin, INPUT); // Set the button pin as an input } void loop() { // Read the current state of the button (HIGH or LOW) buttonState = digitalRead(buttonPin); // If the button is pressed (connected to ground, state is LOW) if (buttonState == LOW) { Serial.println("Button Press"); // Print a message when the button is pressed } // If the button is not pressed, it is in the release state (HIGH) else { Serial.println("Button release"); // Print a message when the button is released } // Note: This loop will continuously read the button state and print messages. // This may not be the desired behavior in all applications. // Consider adding a delay or using a debouncing method for the button. } |
Practical Demonstration:
Now, let’s move on to example #4, which is about accessing the Time and Date information.
Example 4: Time and Date
Using this program, you can not only monitor the time and date information on the serial monitor, but you can also set a custom time and date if needed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/* I2C_BM8563_TimeTypeDef timeStruct; // Define a time structure timeStruct.hours = 12; // Set the hour (0 - 23) timeStruct.minutes = 16; // Set the minute (0 - 59) timeStruct.seconds = 0; // Set the second (0 - 59) rtc.setTime(&timeStruct); // Set the time in the RTC */ /* I2C_BM8563_DateTypeDef dateStruct; // Define a date structure dateStruct.weekDay = 3; // Set the weekday (0 - 6, where 0 is Sunday) dateStruct.month = 12; // Set the month (1 - 12) dateStruct.date = 9; // Set the day of the month (1 - 31) dateStruct.year = 2024; // Set the year rtc.setDate(&dateStruct); // Set the date in the RTC */ |
Simply uncomment these lines, set the time and date, and upload the program. Once the code is uploaded, make sure to comment out these lines again. You only need to do this once.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#include "I2C_BM8563.h" #define I2C_SDA 4 // Define the SDA pin for I2C communication #define I2C_SCL 5 // Define the SCL pin for I2C communication I2C_BM8563 rtc(I2C_BM8563_DEFAULT_ADDRESS, Wire); // Create an RTC object using the default I2C address and Wire library I2C_BM8563_DateTypeDef dateStruct; // Define a structure to hold the date information I2C_BM8563_TimeTypeDef timeStruct; // Define a structure to hold the time information // Function to initialize the RTC void RTC_init() { rtc.begin(); // Begin communication with the RTC // The following lines are commented out and provided as an example // of how to set a custom time and date if needed: /* I2C_BM8563_TimeTypeDef timeStruct; // Define a time structure timeStruct.hours = 12; // Set the hour (0 - 23) timeStruct.minutes = 16; // Set the minute (0 - 59) timeStruct.seconds = 0; // Set the second (0 - 59) rtc.setTime(&timeStruct); // Set the time in the RTC */ /* I2C_BM8563_DateTypeDef dateStruct; // Define a date structure dateStruct.weekDay = 3; // Set the weekday (0 - 6, where 0 is Sunday) dateStruct.month = 12; // Set the month (1 - 12) dateStruct.date = 9; // Set the day of the month (1 - 31) dateStruct.year = 2024; // Set the year rtc.setDate(&dateStruct); // Set the date in the RTC */ } void setup() { Serial.begin(115200); // Start serial communication at 115200 baud rate Wire.begin(I2C_SDA, I2C_SCL); // Initialize the I2C bus with defined SDA and SCL pins RTC_init(); // Call the RTC initialization function } void loop() { rtc.getDate(&dateStruct); // Get the current date from the RTC rtc.getTime(&timeStruct); // Get the current time from the RTC // Print the current time in a formatted manner Serial.printf("The update time is: %02d:%02d:%02d\n", timeStruct.hours, timeStruct.minutes, timeStruct.seconds ); Serial.printf("Date: %02d:%02d:%02d\n", dateStruct.date, dateStruct.month, dateStruct.year ); delay(1000); // Wait for 1 second before updating the time again } |
Practical Demonstration:
You can see the time and date. I am really impressed with the BM8563 I2C Real-Time Clock and Calendar. Thanks to the onboard battery, the time and date information is not lost even if you turn it off.
Now, let’s move on to example #5, which is about reading touch information.
Example 5: Display Touch x, y, and Gesture
The purpose of this program is to read the x and y values of the area on the display where you touch it with your finger, along with the gesture information.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include "Arduino.h" #include "CST816D.h" #define I2C_SDA 4 #define I2C_SCL 5 #define TP_INT 0 #define TP_RST 1 CST816D touch(I2C_SDA, I2C_SCL, TP_RST, TP_INT); void setup() { Serial.begin(115200); touch.begin(); } bool FingerNum; uint8_t gesture; uint16_t touchX, touchY; void loop() { FingerNum = touch.getTouch(&touchX, &touchY, &gesture); if (FingerNum) { Serial.printf("X:%d,Y:%d,gesture:%x\n", touchX, touchY, gesture); } delay(100); } |
Practical Demonstration:
Read my article on how to use the CrowPanel ESP32C3 with SquareLine and LVGL. So, that’s all for now.
Watch Video Tutorial: