The Matouch 1.28″ ToolSet_Controller is a compact ESP32-S3 development board with many features. It comes with a 1.28-inch round RGB touchscreen with a rotary encoder ring, Wi-Fi, Bluetooth 5.0, and haptic vibration feedback. There is also an RTC, micro-SD card slot, and connectors for I²C, UART, SPI, and GPIO.
In this getting started guide you will learn how to setup the board for programming with the Arduino IDE. Two code examples will show you how to use the display, the touch screen, the rotary encoder and the motor for haptic feedback.
Let’s get started …
Required Parts
You will need a Matouch 1.28″ ToolSet_Controller board by Makerfabs and a USB-C cable to program the board and to try out the code examples.

Matouch 1.28″ ToolSet_Controller

USB C Cable
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.
The Matouch 1.28″ ToolSet_Controller
The Matouch 1.28″ ToolSet_Controller is powered by the ESP32-S3 microcontroller, which features dual-core processing along with Wi-Fi and Bluetooth 5.0 connectivity. At its center is a 1.28-inch round RGB IPS display with a resolution of 240 by 240 pixels. The display supports capacitive touch for precise interaction. Surrounding the display is a rotary encoder ring with push button functionality, which allows intuitive input such as scrolling, volume adjustment, or menu navigation.

Components of Matouch 1.28″ ToolSet_Controller
In addition to the display and rotary control, the board integrates a haptic vibration motor that provides tactile feedback. A real-time clock (RTC) ensures accurate timekeeping even when the device is in deep sleep mode. For local data storage, the micro-SD card slot makes it easy to log information or store media.

The board measures 60 by 60 millimeters. This compact square design makes it easy to integrate into handheld controllers, desktop tools, and user interfaces. Expansion is supported through accessible connectors for I²C, UART, SPI, and GPIO pins, which allow the use of external sensors, actuators, or other modules. Power is managed by an onboard system that supports both USB Type-C and external battery operation but no charging circuit.
Pinout of Matouch 1.28″ ToolSet_Controller
The picture below shows the Pinout of the Matouch 1.28″ ToolSet_Controller. There are 15 GPIO pins and 5V or 3.3V power pins available:

The Matouch 1.28″ ToolSet_Controller is well suited for projects that need a small but capable interface. Example applications include a desktop media controller for adjusting volume or switching tracks, a smart home control panel or a device configuration tool.
Technical Specification
The table below summarizes the technical specifications of the board.
| Specification | Details |
|---|---|
| Microcontroller | ESP32-S3 dual-core, Xtensa LX7 |
| Wireless | Wi-Fi 802.11 b/g/n, Bluetooth 5.0 |
| Display | 1.28″ round RGB IPS, 240 × 240 pixels, capacitive touch |
| Rotary input | Rotary encoder ring with push button |
| Vibration | Haptic motor |
| Storage | Micro-SD card slot |
| Clock | Real-time clock (RTC) |
| Expansion | I²C, UART, SPI, GPIO |
| Power | USB Type-C, external battery |
| Dimensions | 60 × 60 mm |
| Open-source support | Arduino IDE, example projects on GitHub |
Finally, you can find the Schematics for the Matouch 1.28″ ToolSet_Controller under the following link:
Installation of ESP32 Core
If this is your first project with a board of the ESP32 series, you will need to install the ESP32 core first. If ESP32 boards are already installed in your Arduino IDE, you can skip this section.
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”. Click on the INSTALL button and wait until the download and install is complete.

Selecting Board
Finally we need to select a ESP32 board. In case of the Matouch 1.28″ ToolSet_Controller, we pick the generic “ESP32S3 Dev Module”. 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.
Install Adafruit GFX Library
Once, you have the ESP32 code installed the next step is to install the Arduino_GFX library by moononournation, which will use to draw on the Matouch 1.28″ display. Open the LIBRARY MANAGER, type “GFX Library for Arduino” into the search bar, find the “GFX Library for Arduino” by Moon, and click the INSTALL button:

Now everything is in place to actually write and run some code on the Matouch 1.28″ ToolSet_Controller.
Code for Encoder, Motor and Display
In this first sketch you will learn how to use the display, the encoder and the motor for haptic feedback.

We will use the encoder ring to change the brightness of the display between 1% and 100% and we will show the brightness value on the display. A short vibration will signal if the ring is turned too far (<1%, >100%). Finally, pressing the encoder’s push button will toggle the display on or off. The photo below shows the display switched off, at 4% brightness and at 100% brightness:

Have a quick look at the complete code first and then we discuss its details:
#include <Arduino_GFX_Library.h>
#define TFT_BLK 45
#define TFT_RES 21
#define TFT_CS 1
#define TFT_MOSI 2
#define TFT_MISO -1
#define TFT_SCLK 42
#define TFT_DC 46
#define ENCODER_BTN 17
#define ENCODER_CLK 48
#define ENCODER_DT 47
#define MOTOR_PIN 41
int brightness = 50;
bool btn_display = true;
int enc_state = 0;
int old_state = -1;
bool has_changed = true;
Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, TFT_SCLK,
TFT_MOSI, TFT_MISO, HSPI, true);
Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);
void encoder_irq() {
enc_state = digitalRead(ENCODER_CLK);
if (enc_state != old_state) {
brightness = digitalRead(ENCODER_DT) == enc_state ? brightness + 1 : brightness - 1;
}
old_state = enc_state;
has_changed = true;
}
void button_irq() {
btn_display = !btn_display;
has_changed = true;
}
void draw_text() {
gfx->setTextSize(2);
gfx->setCursor(60, 55);
gfx->println(F("Makerguides"));
}
void limit_brightness() {
if (brightness > 100 || brightness < 0) {
digitalWrite(MOTOR_PIN, HIGH);
delay(50);
digitalWrite(MOTOR_PIN, LOW);
}
brightness = constrain(brightness, 0, 100);
}
void update_brightness() {
gfx->fillRect(65, 95, 120, 65, WHITE);
gfx->setTextSize(6);
gfx->setCursor(65, 100);
gfx->printf("%3d", brightness);
int b = map(brightness, 0, 100, 1, 255);
analogWrite(TFT_BLK, btn_display ? b : 0);
}
void init_display() {
gfx->begin();
gfx->fillScreen(WHITE);
gfx->setTextColor(BLACK);
}
void init_pins() {
pinMode(TFT_BLK, OUTPUT);
digitalWrite(TFT_BLK, HIGH);
pinMode(MOTOR_PIN, OUTPUT);
pinMode(ENCODER_BTN, INPUT);
pinMode(ENCODER_CLK, INPUT);
pinMode(ENCODER_DT, INPUT);
attachInterrupt(ENCODER_CLK, encoder_irq, CHANGE);
attachInterrupt(ENCODER_BTN, button_irq, FALLING);
}
void setup(void) {
Serial.begin(115200);
init_pins();
init_display();
draw_text();
}
void loop() {
if (has_changed) {
has_changed = false;
limit_brightness();
update_brightness();
}
}
Imports
The program begins by including the graphics library for the round GC9A01 display. This header brings in display drivers, color constants such as WHITE, BLACK, or RED, and drawing primitives.
#include <Arduino_GFX_Library.h>
Pin and Hardware Constants
Named constants map meaningful labels to the ESP32’s GPIO pins. They define the TFT backlight PWM pin, reset, chip-select, SPI pins, display data/command, the rotary encoder’s pushbutton and two quadrature channels, and the vibration motor’s control pin. Defining them once here makes the rest of the code more readable.
#define TFT_BLK 45 #define TFT_RES 21 #define TFT_CS 1 #define TFT_MOSI 2 #define TFT_MISO -1 #define TFT_SCLK 42 #define TFT_DC 46 #define ENCODER_BTN 17 #define ENCODER_CLK 48 #define ENCODER_DT 47 #define MOTOR_PIN 41
Global State Variables
A few global variables hold the current brightness, display on/off state, the most recent encoder logic level and its previous value, and a “dirty” flag that tells the main loop when something changed due to an interrupt. The initial brightness is 50%, the display starts enabled, and old_state is initialized to an impossible value (-1) so the very first encoder read will be seen as a change.
int brightness = 50; bool btn_display = true; int enc_state = 0; int old_state = -1; bool has_changed = true;
Display Bus and Driver Objects
Two objects set up the display. Arduino_ESP32SPI configures the SPI bus and control pins for the TFT. The constructor selects the DC, CS, SCLK, MOSI, and MISO pins, chooses the HSPI peripheral, and enables DMA for faster transfers. Arduino_GC9A01 is the specific driver for the 1.28″ round panel; it receives the SPI bus object, the reset pin, rotation, and IPS flag. The resulting gfx object exposes drawing methods used throughout the sketch.
Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, TFT_SCLK,
TFT_MOSI, TFT_MISO, HSPI, true);
Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);
Interrupt Service Routine: encoder_irq
This ISR runs whenever the encoder’s CLK line changes. It reads the current CLK level and compares it to the previous one. If a transition occurred, it looks at the DT line to determine direction: when DT equals the new CLK level the brightness increments, otherwise it decrements. It then remembers the new state and marks the UI as changed so the main loop can redraw and apply the new brightness. Using an ISR makes the encoder responsive without busy-waiting in loop().
void encoder_irq() {
enc_state = digitalRead(ENCODER_CLK);
if (enc_state != old_state) {
brightness = digitalRead(ENCODER_DT) == enc_state ? brightness + 1 : brightness - 1;
}
old_state = enc_state;
has_changed = true;
}
Interrupt Service Routine: button_irq
This ISR toggles whether the display should be on. Each falling edge on the encoder’s pushbutton flips btn_display and flags that a change occurred so the main loop will update the screen and backlight.
void button_irq() {
btn_display = !btn_display;
has_changed = true;
}
Drawing Helper: draw_text
This helper places a fixed “Makerguides” label near the top of the round display. It sets a small text size, moves the cursor, and prints the string from flash memory (F() macro).
void draw_text() {
gfx->setTextSize(2);
gfx->setCursor(60, 55);
gfx->println(F("Makerguides"));
}
Logic Helper: limit_brightness
This function enforces the valid brightness range and haptically signals when the user tries to go beyond it. If the current value is outside [0, 100], it briefly drives the vibration motor to indicate the limit, then clamps the value into range using constrain. The motor feedback fires only when an out-of-range condition is detected, so end-stops “buzz” but normal steps do not.
void limit_brightness() {
if (brightness > 100 || brightness < 0) {
digitalWrite(MOTOR_PIN, HIGH);
delay(50);
digitalWrite(MOTOR_PIN, LOW);
}
brightness = constrain(brightness, 0, 100);
}
UI and Backlight Update: update_brightness
This function refreshes the on-screen numeric readout and applies the new backlight PWM level. It first paints a white rectangle over the previous number to erase it cleanly, sets a larger text size, positions the cursor, and prints the brightness as a right-aligned three-digit value. It then maps 0–100% to the 1–255 PWM range and writes that duty cycle to the backlight pin. If the display has been toggled off with the pushbutton, it forces the duty cycle to zero to turn the backlight off without changing the stored brightness value.
void update_brightness() {
gfx->fillRect(65, 95, 120, 65, WHITE);
gfx->setTextSize(6);
gfx->setCursor(65, 100);
gfx->printf("%3d", brightness);
int b = map(brightness, 0, 100, 1, 255);
analogWrite(TFT_BLK, btn_display ? b : 0);
}
Display Initialization: init_display
On startup, the display driver is initialized, the screen is cleared to white, and the default text color is set to black. These steps guarantee a known drawing state before any UI elements are rendered.
void init_display() {
gfx->begin();
gfx->fillScreen(WHITE);
gfx->setTextColor(BLACK);
}
Pin Initialization and Interrupt Wiring: init_pins
All GPIO modes are configured here. The backlight pin is set as an output and driven high initially so the panel is visible during startup. The motor pin is set as an output. The encoder’s pushbutton, CLK, and DT pins are inputs. Note that the encoder has internal 10K pull-up resistors, so that INPUT_PULLUP is not necessary here (though it appears in some example code).

Finally, hardware interrupts are attached: the encoder CLK uses CHANGE to catch both rising and falling edges for maximum resolution, while the button uses FALLING to fire once per press. This setup keeps the main loop simple and responsive.
void init_pins() {
pinMode(TFT_BLK, OUTPUT);
digitalWrite(TFT_BLK, HIGH);
pinMode(MOTOR_PIN, OUTPUT);
pinMode(ENCODER_BTN, INPUT);
pinMode(ENCODER_CLK, INPUT);
pinMode(ENCODER_DT, INPUT);
attachInterrupt(ENCODER_CLK, encoder_irq, CHANGE);
attachInterrupt(ENCODER_BTN, button_irq, FALLING);
}
Setup
The setup() function initializes serial for debugging, configures pins and interrupts, prepares the display, and draws the static label. After setup(), the device is ready to respond to encoder movements and button presses.
void setup(void) {
Serial.begin(115200);
init_pins();
init_display();
draw_text();
}
Main Loop
The loop() is intentionally minimal. It only reacts when an ISR has marked has_changed. When that flag is set, it clears the flag, enforces brightness limits with haptic feedback if necessary, and updates both the on-screen number and the backlight PWM. This pattern avoids unnecessary redrawing and PWM writes, which keeps the UI snappy and power-efficient.
void loop() {
if (has_changed) {
has_changed = false;
limit_brightness();
update_brightness();
}
}
How the Encoder Drives Brightness and Haptics
The encoder provides two square-wave signals, CLK and DT, whose relative phase indicates direction. On every CLK transition, the ISR compares DT to the new CLK level to decide whether to add or subtract one from brightness. When the user turns past the ends, limit_brightness() vibrates the motor for 50 milliseconds and clamps the value to stay within [0, 100]. The display’s numeric value always reflects the clamped number, while the actual visible brightness comes from PWM on TFT_BLK, which is set to the mapped duty cycle or to zero when the pushbutton has toggled the display off.
Code for Touch Screen
In this next code example you will learn how to use the touch screen of the display. We will write the text “Touch” in the center of the display and draw a black circle where the display is touched. The photo below shows the controller display before and after some touch events:

As before, I suggest you have a quick glance at the complete code first before we discuss the details:
#include <Wire.h>
#include <Arduino_GFX_Library.h>
#define TFT_BLK 45
#define TFT_RES 21
#define TFT_CS 1
#define TFT_MOSI 2
#define TFT_MISO -1
#define TFT_SCLK 42
#define TFT_DC 46
#define TOUCH_INT 40
#define TOUCH_SDA 38
#define TOUCH_SCL 39
#define TOUCH_RST 18
#define MOTOR_PIN 41
Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, TFT_SCLK,
TFT_MOSI, TFT_MISO, HSPI, true);
Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);
int read_touch(int *x, int *y) {
byte data_raw[7];
i2c_read(0x15, 0x02, data_raw, 7);
int event = data_raw[1] >> 6;
if (event == 2) {
*x = (int)data_raw[2] + (int)(data_raw[1] & 0x0f) * 256;
*y = (int)data_raw[4] + (int)(data_raw[3] & 0x0f) * 256;
return 1;
}
return 0;
}
int i2c_read(uint16_t addr, uint8_t reg_addr, uint8_t *reg_data, uint32_t length) {
Wire.beginTransmission(addr);
Wire.write(reg_addr);
if (Wire.endTransmission(true))
return -1;
Wire.requestFrom(addr, length, true);
for (int i = 0; i < length; i++) {
*reg_data++ = Wire.read();
}
return 0;
}
void init_pins() {
pinMode(TFT_BLK, OUTPUT);
digitalWrite(TFT_BLK, HIGH);
Wire.begin(TOUCH_SDA, TOUCH_SCL);
pinMode(MOTOR_PIN, OUTPUT);
}
void init_display() {
gfx->begin();
gfx->fillScreen(WHITE);
gfx->setTextColor(BLACK);
gfx->setTextSize(3);
gfx->setCursor(80, 105);
gfx->print(F("Touch"));
}
void setup(void) {
Serial.begin(115200);
init_pins();
init_display();
}
void loop() {
static int x, y;
if (read_touch(&x, &y) == 1) {
gfx->fillCircle(x, y, 5, BLACK);
digitalWrite(MOTOR_PIN, HIGH);
delay(20);
digitalWrite(MOTOR_PIN, LOW);
}
}
Imports
The program includes the I²C library for talking to the touch controller and the same graphics library you saw earlier for drawing on the circular GC9A01 display. The Wire library provides begin, beginTransmission, write, endTransmission, and requestFrom for register-level I²C access, while Arduino_GFX_Library.h brings in the display driver, drawing primitives, and color constants you used before.
#include <Wire.h> #include <Arduino_GFX_Library.h>
Pin and Hardware Constants
The display pins mirror the previous sketch and identify the backlight, reset, SPI chip-select and bus lines, and the D/C pin. Additional constants define the touch controller’s interrupt, SDA, SCL, and reset pins. The motor pin again drives the haptic vibration motor. In this sketch the touch interrupt and reset lines are defined for completeness, but only the I²C lines are actually used.
#define TFT_BLK 45 #define TFT_RES 21 #define TFT_CS 1 #define TFT_MOSI 2 #define TFT_MISO -1 #define TFT_SCLK 42 #define TFT_DC 46 #define TOUCH_INT 40 #define TOUCH_SDA 38 #define TOUCH_SCL 39 #define TOUCH_RST 18 #define MOTOR_PIN 41
Display Bus and Driver Objects
The display setup is the same pattern as before. An Arduino_ESP32SPI object wires up the ESP32’s HSPI peripheral with your chosen pins and DMA enabled, and an Arduino_GC9A01 object drives the 1.28″ round IPS panel. The resulting gfx object exposes the same drawing API you already used.
Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, TFT_SCLK,
TFT_MOSI, TFT_MISO, HSPI, true);
Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);
Touch Reader: read_touch
This function polls the touch controller over I²C, parses a packet, and outputs the touch coordinates through pointer parameters. It first reads seven bytes starting at register 0x02 from device address 0x15. The controller encodes an “event” in the top two bits of the X-high byte; shifting by six yields that event code.
Only when the event equals 2 does the function treat the packet as a valid finger contact and compute coordinates. The X position is formed by taking the low byte and adding the lower four bits of the high byte multiplied by 256, which reconstructs a typical 12-bit coordinate; Y is decoded the same way from its high and low bytes.
When a valid touch is decoded the function stores x and y through the pointers and returns 1. For any other event it returns 0.
int read_touch(int *x, int *y) {
byte data_raw[7];
i2c_read(0x15, 0x02, data_raw, 7);
int event = data_raw[1] >> 6;
if (event == 2) {
*x = (int)data_raw[2] + (int)(data_raw[1] & 0x0f) * 256;
*y = (int)data_raw[4] + (int)(data_raw[3] & 0x0f) * 256;
return 1;
}
return 0;
}
I²C Helper: i2c_read
This helper performs a common “write register, then read data” transaction. It begins a transmission to the 7-bit address, writes the register address, and checks endTransmission. A nonzero result signals an error and returns -1. It then requests the specified number of bytes and pulls them from Wire.read() into the caller’s buffer, finally returning 0 for success. This is the low-level piece that read_touch relies on to fetch the touch packet.
int i2c_read(uint16_t addr, uint8_t reg_addr, uint8_t *reg_data, uint32_t length) {
Wire.beginTransmission(addr);
Wire.write(reg_addr);
if (Wire.endTransmission(true))
return -1;
Wire.requestFrom(addr, length, true);
for (int i = 0; i < length; i++) {
*reg_data++ = Wire.read();
}
return 0;
}
Pin and Bus Initialization: init_pins
The GPIO setup mirrors the previous sketch for the display backlight and motor. The backlight pin is made an output and driven high so the screen is visible immediately. The motor pin is configured as an output ready for haptic pulses. The I²C bus is started on the specified SDA and SCL pins using Wire.begin(TOUCH_SDA, TOUCH_SCL), which selects the alternate ESP32 pins you defined for the touch controller. No touch interrupt is attached here; this sketch chooses simple polling instead.
void init_pins() {
pinMode(TFT_BLK, OUTPUT);
digitalWrite(TFT_BLK, HIGH);
Wire.begin(TOUCH_SDA, TOUCH_SCL);
pinMode(MOTOR_PIN, OUTPUT);
}
Display Initialization: init_display
Display bring-up follows the same steps you saw earlier. The gfx driver is started, the screen is cleared to white, and the text color is set to black. A friendly “Touch” label is rendered in the middle using a larger text size to indicate the expected interaction. The F() macro again keeps the literal in flash memory.
void init_display() {
gfx->begin();
gfx->fillScreen(WHITE);
gfx->setTextColor(BLACK);
gfx->setTextSize(3);
gfx->setCursor(80, 105);
gfx->print(F("Touch"));
}
Setup
Startup is similar in structure to the previous sketch. The serial port is opened for debugging, pins and the I²C bus are initialized, and the display is prepared and labeled. After setup returns, the device is ready to poll for touches and draw feedback.
void setup(void) {
Serial.begin(115200);
init_pins();
init_display();
}
Loop
The main loop continuously polls the touch controller and reacts immediately when a valid contact is reported. The x and y variables are declared static so their storage persists across iterations, allowing read_touch to write through the pointers.
When read_touch returns 1, a filled black circle of radius five pixels is drawn at the reported coordinates to mark the tap. A short 20-millisecond pulse drives the vibration motor high and then low to provide haptic confirmation of the touch.
void loop() {
static int x, y;
if (read_touch(&x, &y) == 1) {
gfx->fillCircle(x, y, 5, BLACK);
digitalWrite(MOTOR_PIN, HIGH);
delay(20);
digitalWrite(MOTOR_PIN, LOW);
}
}
How This Relates to the Previous Sketch
The display creation, initialization, drawing API, and motor control follow the exact same patterns you already used. Instead of encoder interrupts and a has_changed flag, this sketch polls the touch controller over I²C and triggers haptics on every valid finger contact rather than on a brightness end-stop.
The backlight is simply enabled and not PWM-modulated, because there is no brightness state here. The touch interrupt and reset pins are defined but not leveraged; adding an external interrupt on TOUCH_INT could let you sleep between touches, while using TOUCH_RST could perform a hardware reset of the touch IC if needed.
Conclusions
In this tutorial you learned how to get started with the Matouch 1.28″ ToolSet_Controller. The two code examples around the display, the touch screen, the rotary encoder and the haptic feedback should enable you to get your own project of the ground more easily.
We did not discuss Wi-Fi, Bluetooth 5.0, the SD Card or the RTC support in this tutorial. But you can find examples on Makerfabs’s Github repo for the Matouch display. If you want to learn more about rotary encoders, have a look at our How To Interface A Quadrature Rotary Encoder tutorial.
The quadratic base of the Matouch 1.28″ ToolSet_Controller can be a bit big for some appliactions. If you are looking for a similar display with a rotary encoder but a round base have a look at the CrowPanel 1.28inch-HMI ESP32 Rotary Display. And should you need only a round display (without the rotary encoder ring), the Digital Clock on CrowPanel 1.28″ Round Display tutorial might be useful.
Otherwise, feel free to leave any questions 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.

