Skip to main content

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 controllerPointsTypical application
CST816 familyOne point plus gestures1.3–2-inch watch-style displays
CST9220Five pointsDisplays up to approximately 2.2 inches
FT3168 / FT6236Two points1.8–3.5-inch displays
GT911Five points3.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

Example board

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

  1. Install Arduino IDE and ESP32 board support using Arduino IDE Setup.
  2. Install SensorLib and GFX Library for Arduino from Library Manager. The former reads touch coordinates, while the latter drives the display drawing. This example uses TouchDrv.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.
  3. Select ESP32S3 Dev Module.
  4. USB CDC On Boot defaults to Disabled, which directs Serial to UART0 on GPIO43/44. Those pins are not connected to the USB-C port. To view coordinates, set it to Enabled and uncomment Serial.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:

SignalGPIO
SDAGPIO15
SCLGPIO14
TP_RSTGPIO40
TP_INTGPIO11

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() and setMirrorXY() as described in Coordinate Alignment.
  • Keep update rectangles even-aligned: CO5300 reliably accepts even starting coordinates and dimensions. The example uses fillRect rather than fillCircle. See AMOLED update-window alignment.
  • Allocate enough coordinate entries: The lengths of x and y must be no smaller than the maximum number of points reported by the driver, which is two for SensorLib's CST92xx driver.
info

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

SymptomLikely causeCorrective action
Squares appear but Serial Monitor is emptyUSB CDC disabled, so Serial uses unconnected UART0Enable USB CDC On Boot and upload again
Initialization prints failedWrong I2C address or pinsCheck SDA, SCL, RST, INT, and the product page
No coordinates or squaresINT not connected, wrong edge, or old SensorLibVerify TP_INT, use FALLING, and update SensorLib
Touch becomes slow with USB CDC enabled and no monitor openUSB transmit buffer blocks when no host reads itUncomment Serial.setTxTimeoutMs(0);; comment it again if USB CDC is disabled because HardwareSerial does not provide this method
Square is mirrored or offsetTransform does not match orientationAdjust setSwapXY() and setMirrorXY()
Array overrun with multiple touchesCoordinate arrays too shortSize 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.