Touch Input
Touch sensing is independent of the display technology. LCD, AMOLED, and even e-paper can have a separate touch layer. Capacitive-touch controllers normally communicate with the microcontroller over I2C, so the same driver pattern applies across display types.
1. Touchscreen Technologies
Touchscreens fall into two categories based on their sensing method:
- Resistive: Two conductive films make contact under pressure, and the controller calculates the coordinates by measuring the voltage division at the contact point. Any object can trigger the panel by applying pressure, including a gloved finger or a pen tip, and the technology is inexpensive. However, it requires a certain amount of pressure, supports only a single touch point, has slightly lower light transmission, and requires calibration because the film coordinates deviate from the display coordinates.
- Capacitive: An electrode array beneath a glass or plastic cover detects the local capacitance change caused by a nearby finger, and the touch controller scans the array to calculate the coordinates. It responds to a light touch, supports multiple points, and provides good light transmission, making it the mainstream solution today. Its limitation is that the touching object must be conductive, so an ordinary glove does not work.
Most Waveshare development boards with “Touch” in their name use capacitive sensing. The touch controller calculates the coordinates, so the microcontroller reads them without calibration.
2. Common Touch Controllers
A capacitive-touch controller is separate from the display controller. It normally communicates with the host over I2C and includes two auxiliary signals: INT, the touch-interrupt output, and RST, the reset input:
| Touch controller | Points | Typical application |
|---|---|---|
| CST816 family | One point plus gestures | 1.3–2-inch watch-style displays |
| CST9220 | Five points | Displays up to approximately 2.2 inches |
| FT3168 / FT6236 | Two points | 1.8–3.5-inch displays |
| GT911 | Five points | 3.5–7-inch displays |
3. Polling and Interrupts
Regardless of the touch controller, the host reads touch input in one of two ways:
- Polling reads the touch registers periodically from the main loop. It is simple but generates I2C traffic even when there is no touch.
- Interrupt-driven reading sets a flag in the interrupt service routine when INT changes and reads the coordinates from the main loop. It responds promptly, saves bus bandwidth, and is the pattern used by many Waveshare examples.
The following example uses an interrupt.
4. Arduino and SensorLib Example
The Waveshare ESP32-S3-Touch-AMOLED-2.16 uses a CST9220 that supports up to five points. SensorLib currently reports up to two points through its CST92xx driver, so this example allocates two coordinates. It uses the Arduino_GFX configuration from AMOLED to draw a square at each touch.
4.1 Preparation
- Install Arduino IDE and ESP32 board support using Arduino IDE Setup.
- Install
SensorLibandGFX Library for Arduinofrom Library Manager. The former reads touch coordinates, while the latter drives the display drawing. This example usesTouchDrv.hpp, introduced in SensorLib v0.4.1, so make sure the installed version is v0.4.1 or later. See Installing Arduino Libraries for other installation methods. - Select
ESP32S3 Dev Module. - USB CDC On Boot defaults to Disabled, which directs
Serialto UART0 on GPIO43/44. Those pins are not connected to the USB-C port. To view coordinates, set it to Enabled and uncommentSerial.setTxTimeoutMs(0);.
4.2 Confirm the Pins
The touch controller and display on the ESP32-S3-Touch-AMOLED-2.16 are connected directly to the host. The touch-related pins are listed below:
| Signal | GPIO |
|---|---|
| SDA | GPIO15 |
| SCL | GPIO14 |
| TP_RST | GPIO40 |
| TP_INT | GPIO11 |
See AMOLED pin assignments for the QSPI display signals.
4.3 Example Code
In the following example, a touch interrupt notifies the main loop to read the coordinates. The program prints them to the serial port and draws a square cursor at each touch point:
#include <Wire.h>
#include <TouchDrv.hpp>
#include <Arduino_GFX_Library.h>
// CST9220 touch pins
#define IIC_SDA 15
#define IIC_SCL 14
#define TP_RST 40
#define TP_INT 11
// CO5300 QSPI display pins
#define LCD_SDIO0 4
#define LCD_SDIO1 5
#define LCD_SDIO2 6
#define LCD_SDIO3 7
#define LCD_SCLK 38
#define LCD_CS 12
#define LCD_RESET 39
#define LCD_WIDTH 480
#define LCD_HEIGHT 480
TouchDrvCST92xx touch;
// SensorLib's CST92xx driver currently reports at most two points.
int16_t x[2], y[2];
volatile bool touchPending = false;
Arduino_DataBus *bus = new Arduino_ESP32QSPI(
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);
Arduino_CO5300 *gfx = new Arduino_CO5300(
bus, LCD_RESET, 0, LCD_WIDTH, LCD_HEIGHT, 0, 0, 0, 0);
// Even size for the CO5300 update-window requirement.
#define CURSOR 20
// Set only a flag in the ISR. Perform the I2C transaction in the main loop.
void IRAM_ATTR onTouchInterrupt() {
touchPending = true;
}
void setup() {
Serial.begin(115200);
// Serial.setTxTimeoutMs(0); // Drop output when no USB serial host is reading.
// 1. Initialize the display: clear it, set full brightness, and use the
// orientation parameters from the AMOLED example.
gfx->begin();
bus->writeC8D8(0x36, 0xA0);
gfx->fillScreen(RGB565_BLACK);
gfx->setBrightness(255);
// 2. Assign the reset and interrupt pins, then initialize the touch controller.
touch.setPins(TP_RST, TP_INT);
if (!touch.begin(Wire, CST92XX_SLAVE_ADDRESS, IIC_SDA, IIC_SCL)) {
Serial.println("Touch initialization failed");
while (true) {
delay(1000);
}
}
// 3. Coordinate transform: the reference range and swap/mirror settings
// must match the display orientation.
touch.setMaxCoordinates(LCD_WIDTH, LCD_HEIGHT);
touch.setSwapXY(true);
touch.setMirrorXY(true, false);
// 4. Configure the interrupt pin for a falling-edge trigger.
pinMode(TP_INT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(TP_INT), onTouchInterrupt, FALLING);
}
void loop() {
// 5. Return immediately when no touch event is pending.
if (!touchPending) {
return;
}
noInterrupts();
touchPending = false;
interrupts();
// 6. Read every touch point, print its coordinates, and draw a square cursor.
uint8_t touched = touch.getPoint(x, y, touch.getSupportTouchPoint());
for (uint8_t i = 0; i < touched; ++i) {
Serial.printf("Touch %u: x=%d, y=%d\n", i, x[i], y[i]);
// Use even coordinates and dimensions for CO5300.
int16_t px = (x[i] - CURSOR / 2) & ~1;
int16_t py = (y[i] - CURSOR / 2) & ~1;
gfx->fillRect(px, py, CURSOR, CURSOR, RGB565_CYAN);
}
}
Touching the panel leaves cyan square trails (this example does not erase previous cursors). With USB CDC enabled as described in Section 4.1, the Serial Monitor also reports coordinates.
- Set only a flag in the interrupt: I2C operations take time and do not belong in an ISR.
- Use the square as an alignment check: If it is mirrored or offset from the finger, adjust
setSwapXY()andsetMirrorXY()as described in Coordinate Alignment. - Keep update rectangles even-aligned: CO5300 reliably accepts even starting coordinates and dimensions. The example uses
fillRectrather thanfillCircle. See AMOLED update-window alignment. - Allocate enough coordinate entries: The lengths of
xandymust be no smaller than the maximum number of points reported by the driver, which is two for SensorLib's CST92xx driver.
For another board, verify that SensorLib supports its controller and update the pins, I2C address, and coordinate transform. Avoid address conflicts with onboard I2C devices. See Resources and Documents for the complete board examples.
4.4 Troubleshooting
| Symptom | Likely cause | Corrective action |
|---|---|---|
| Squares appear but Serial Monitor is empty | USB CDC disabled, so Serial uses unconnected UART0 | Enable USB CDC On Boot and upload again |
Initialization prints failed | Wrong I2C address or pins | Check SDA, SCL, RST, INT, and the product page |
| No coordinates or squares | INT not connected, wrong edge, or old SensorLib | Verify TP_INT, use FALLING, and update SensorLib |
| Touch becomes slow with USB CDC enabled and no monitor open | USB transmit buffer blocks when no host reads it | Uncomment Serial.setTxTimeoutMs(0);; comment it again if USB CDC is disabled because HardwareSerial does not provide this method |
| Square is mirrored or offset | Transform does not match orientation | Adjust setSwapXY() and setMirrorXY() |
| Array overrun with multiple touches | Coordinate arrays too short | Size both arrays for the driver's maximum point count |
5. Coordinate Alignment
The touch controller reports coordinates in the panel's native orientation. When the displayed image is rotated—for example, when portrait content is shown in landscape—the touch coordinates must undergo the same transform. Otherwise, the touch position will not align with the displayed response. SensorLib provides three methods for this transform, corresponding to the calls in the preceding example:
setMaxCoordinates(w, h)sets the reference range used for mirroring and coordinate clipping; normally use the display resolution.setSwapXY(true)swaps the axes for a 90° or 270° rotation.setMirrorXY(mirrorX, mirrorY)mirrors either axis or implements a 180° transform.
Panel origins and axis directions vary. Begin with only setMaxCoordinates(), touch all four corners while printing the values, then enable the required swap and mirrors.
A resistive panel also requires calibration by sampling raw ADC readings at known points and calculating the mapping coefficients. Capacitive panels do not require this step, so resistive-panel calibration is not covered further in this tutorial.
6. Integrating a GUI Framework
A GUI framework treats touch as an input device. For LVGL v8, register a callback that reports coordinates and pressed/released state:
void my_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) {
int32_t x, y;
if (read_touch(&x, &y)) {
data->state = LV_INDEV_STATE_PR;
data->point.x = x;
data->point.y = y;
} else {
data->state = LV_INDEV_STATE_REL;
}
}
After the callback is registered, LVGL calls it periodically and handles widget clicks, dragging, and swipe gestures automatically. See GUI Frameworks for choosing a graphics library and Arduino Tutorial Section 12: LVGL Graphics Development for the complete registration procedure.