The waveshare esp32 still usses the st7796s. I bought it based on your screen but had a couple of beers and didn’t notice the inbuilt esp32.Which Teensy filter model are you using?
Can I develop a my custom GUI using a Waveshare ESP32-S3 display (this is my GUI running on an ST7796S display)? Can I drive the display via SPI from a Teensy, and how fast is it (real-time scope and filter curves)?
// Touchscreen interrupt ---------------------------------------
void Touchscreen_Interrupt()
{
touch_flag = true;
}
//--------------------------------------------------------------
// Main loop
//--------------------------------------------------------------
void loop()
{
// render Midi data
MIDI.read(); // MIDI 5 Pin DIN
if (touch_flag == true && touch_Timer > 15)
{
touch_Timer = 0;
read_touchscreen();
}
// render encoder data
read_encoder();
//--------------------------------------------------------------
// read Touchscreen
//--------------------------------------------------------------
void read_touchscreen(void)
{
if (touch_flag)
{
touch_flag = false;
byte ts_status, ts_ID_1, new_ts_event_1, page_ID, parm_ID;
ts_status = ft6336u.read_td_status();
if (ts_status >= 0)
{
ts_ID_1 = ft6336u.read_touch1_id(); // 1 = 1st touch, 2 = 2nd touch
if (ts_ID_1 == 0)
{
new_ts_event_1 = ft6336u.read_touch1_event(); // 0 = No touching, 1 = touch
if (old_ts_event_1 == 0 && new_ts_event_1 == 2)
{
uint16_t ts_ypos_1 = 319 - ft6336u.read_touch1_x(); // Display rotated 180 degrees
uint16_t ts_xpos_1 = ft6336u.read_touch1_y(); // and x/y exchange
// press Top menu buttons 1 - 4
if (ts_xpos_1 >= 0 && ts_ypos_1 <= 50)
{
page_ID = 0;
parm_ID = 1;
//TS_ctrl_handle_0(page_ID, parm_ID, ts_xpos_1, ts_ypos_1);
}
// press Bottom menu buttons on page 1 - 4
else if (ts_xpos_1 >= 0 && ts_ypos_1 >= 270)
{
page_ID = Menu_page;
parm_ID = 1;
switch (page_ID)
{
case 1:
// TS_ctrl_handle_1(page_ID, parm_ID, ts_xpos_1, ts_ypos_1);
break;
case 2:
//TS_ctrl_handle_2(page_ID, parm_ID, ts_xpos_1, ts_ypos_1);
break;
case 3:
//TS_ctrl_handle_3(page_ID, parm_ID, ts_xpos_1, ts_ypos_1);
break;
case 4:
//TS_ctrl_handle_4(page_ID, parm_ID, ts_xpos_1, ts_ypos_1);
break;
default:
break;
}
}
old_ts_event_1 = new_ts_event_1;
}
else if (old_ts_event_1 == 2 && new_ts_event_1 == 1)
{
old_ts_event_1 = 0; // no touch
}
}
}
}
}
}
// ============================================================================
// update() — poll hardware, maintain state, fire gesture detection.
//
// PERFORMANCE RULES FOR THIS FUNCTION:
// - Called every loop() at ~1 kHz or faster.
// - NO Serial.print() here — USB TX flood will crash USB enumeration.
// - NO blocking calls (delay, Wire timeout loops).
// - Keep total execution under ~50 us.
// ============================================================================
void TouchInput::update() {
bool nowTouched = false;
int16_t rawX = 0, rawY = 0;
if (_touchController.touched()) {
TS_Point p = _touchController.getPoint();
rawX = p.x;
rawY = p.y;
nowTouched = true;
}
if (nowTouched) {
_currentPoint = mapCoordinates(rawX, rawY);
if (!_isTouched) {
// ---- Finger just landed ----
_isTouched = true;
_gestureStart = _currentPoint;
_touchStartTime = millis();
_detectedGesture = GESTURE_NONE;
// DEBUG: log ONCE per touch-down only.
// Uncomment only during calibration - leave commented for production.
// Serial.printf("Touch DOWN raw(%d,%d) -> screen(%d,%d)\n",
// rawX, rawY, _currentPoint.x, _currentPoint.y);
}
_lastPoint = _currentPoint;
} else {
if (_isTouched) {
// ---- Finger just lifted ----
_isTouched = false;
_touchEndTime = millis();
detectGesture();
}
}
That’s not really a different library - the ReadMe says it’s based on the terrible one you had trouble with before…I tried a different FT6336u library
There certainly need to be pull-ups! They aren’t optional. And if you want to get to 400kHz they really need to be about 2k2 with short wires.Could it be the wiring? I’m not using any external pull-ups.
/**************************************************************************/
/*!
@brief Reads all touch data registers in one transaction
@returns True if read was successful, false otherwise
*/
/**************************************************************************/
bool Adafruit_FT6x36::readData(void) {
uint8_t data[16];
Adafruit_BusIO_Register touch_array =
Adafruit_BusIO_Register(i2c_dev, 0x00, 16);
if (!touch_array.read(data, 16)) {
return false;
}
// Process touch points
for (uint8_t i = 0; i < 2; i++) {
const uint8_t offset = 6 * i;
_touchX[i] = ((uint16_t)(data[FT6X36_REG_P1_XH + offset] & 0x0F) << 8) |
data[FT6X36_REG_P1_XL + offset];
_touchY[i] = ((uint16_t)(data[FT6X36_REG_P1_YH + offset] & 0x0F) << 8) |
data[FT6X36_REG_P1_YL + offset];
_touchEvent[i] = data[FT6X36_REG_P1_XH + offset] >> 6;
}
return true;
}
// =============================================================
// test.cpp — Touch + DIN MIDI integration test
//
// Hardware:
// Teensy 4.1
// FT6336U touch controller on Wire (I2C0), INT pin → TOUCH_INT_PIN
// DIN MIDI on Serial1 (or whichever HW serial is wired)
//
// Problem solved:
// The original code made 5 separate I2C reads per touch poll
// (~1.2 ms blocking). DIN MIDI at 31250 baud delivers a byte
// every ~320 µs. The Teensy 4.1 UART FIFO is only 4 bytes
// deep, so >1.3 ms of blocking causes byte drops.
//
// The Adafruit_FT6206 library does a single burst read inside
// touched(), reducing I2C blocking to ~0.3 ms. Combined with
// the volatile fix and early-exit on no-touch, MIDI reception
// is no longer starved.
//
// Library:
// Adafruit_FT6206_Library v1.1.1 (supports FT6206/FT6236/FT6336U)
// Install via Library Manager → "Adafruit FT6206"
// Requires: Adafruit_BusIO (installed automatically)
// =============================================================
#include <Arduino.h>
#include <Wire.h>
#include <MIDI.h>
#include <Adafruit_FT6206.h>
// ----- Pin assignments (adjust to match your wiring) ---------
static constexpr uint8_t TOUCH_INT_PIN = 2; // FT6336 INT output
// ----- MIDI on hardware serial (DIN 5-pin) -------------------
MIDI_CREATE_DEFAULT_INSTANCE(); // Serial1 at 31250 baud
// ----- Touch controller --------------------------------------
Adafruit_FT6206 touchscreen;
// ----- Interrupt flag ----------------------------------------
// CRITICAL: must be volatile — written by ISR, read by loop().
// Without volatile the compiler may cache it in a register and
// loop() never sees the ISR's write.
volatile bool touch_flag = false;
// ----- Debounce timer ----------------------------------------
// elapsedMillis auto-increments every millisecond (Teensy built-in)
elapsedMillis touch_Timer;
// ----- State for press / release edge detection --------------
static bool was_touched = false;
// ----- Forward declarations ----------------------------------
void touchscreen_ISR();
void handle_touch();
// Stub — replace with your encoder reading code
extern void read_encoder();
// Stub — replace with your menu page variable
extern uint8_t Menu_page;
// =============================================================
// setup
// =============================================================
void setup()
{
Serial.begin(115200);
// I2C for touch controller
Wire.begin();
Wire.setClock(400000); // 400 kHz — halves I2C transaction time
// Touch controller init
if (!touchscreen.begin(40, &Wire)) {
Serial.println("FT6336 not found — check wiring");
while (1) { delay(10); }
}
Serial.println("FT6336 OK");
// INT pin: active-low, falling edge = new touch data ready
pinMode(TOUCH_INT_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(TOUCH_INT_PIN),
touchscreen_ISR, FALLING);
// DIN MIDI
MIDI.begin(MIDI_CHANNEL_OMNI);
Serial.println("Ready");
}
// =============================================================
// ISR — set flag only, no I2C here
// =============================================================
void touchscreen_ISR()
{
touch_flag = true;
}
// =============================================================
// Main loop
// =============================================================
void loop()
{
// ---------------------------------------------------------
// 1. MIDI first, every single iteration.
// This is the highest-priority task. Never gate it
// behind anything that might block.
// ---------------------------------------------------------
MIDI.read();
// ---------------------------------------------------------
// 2. Touch: poll at most every 15 ms (~67 Hz).
// Human fingers move at ~100 mm/s max; 67 Hz gives
// ~1.5 mm per sample — more than enough resolution.
//
// Total I2C blocking per poll ≈ 0.15 ms at 400 kHz
// (one burst read inside touched()). MIDI is safe.
// ---------------------------------------------------------
if (touch_flag && touch_Timer > 15) {
touch_Timer = 0;
touch_flag = false; // clear before I2C, not after —
// if a new touch arrives during
// the read, the flag gets re-set
// and we catch it next iteration.
handle_touch();
}
// ---------------------------------------------------------
// 3. Encoders — assumed fast, no I2C contention
// ---------------------------------------------------------
read_encoder();
}
// =============================================================
// handle_touch — all I2C happens inside touched() / getPoint()
//
// touched() does a single burst read of registers 0x00–0x0E
// internally. getPoint() returns the cached result.
// Total: ONE I2C transaction, not five.
// =============================================================
void handle_touch()
{
uint8_t n_touches = touchscreen.touched();
// --- No finger down → detect release edge, then exit -----
if (n_touches == 0) {
if (was_touched) {
was_touched = false;
// Optional: handle release event here
}
return; // nothing else to do — skip getPoint() entirely
}
// --- Press edge: only act on the transition 0 → 1 --------
if (!was_touched) {
was_touched = true;
TS_Point p = touchscreen.getPoint(0); // no I2C — cached data
// Apply display rotation (180°, axes swapped)
uint16_t ts_xpos = p.y;
uint16_t ts_ypos = 319 - p.x;
// --- Top menu (y 0..50) ---
if (ts_ypos <= 50) {
uint8_t page_ID = 0;
uint8_t parm_ID = 1;
(void)page_ID; (void)parm_ID; // suppress unused warnings
// TS_ctrl_handle_0(page_ID, parm_ID, ts_xpos, ts_ypos);
}
// --- Bottom menu (y 270..319) ---
else if (ts_ypos >= 270) {
uint8_t parm_ID = 1;
(void)parm_ID;
switch (Menu_page) {
case 1: /* TS_ctrl_handle_1(Menu_page, parm_ID, ts_xpos, ts_ypos); */ break;
case 2: /* TS_ctrl_handle_2(Menu_page, parm_ID, ts_xpos, ts_ypos); */ break;
case 3: /* TS_ctrl_handle_3(Menu_page, parm_ID, ts_xpos, ts_ypos); */ break;
case 4: /* TS_ctrl_handle_4(Menu_page, parm_ID, ts_xpos, ts_ypos); */ break;
default: break;
}
}
}
}
Brilliant news. Glad that I could help in some way.Thanks for your excellent helpI'll test it this evening and report back afterwards..
...one hour laterMIDI reception is now working flawlessly. Thanks a lot.
I checked the wire speed of your code with my oscilloscope and found it was only 100 kHz. I modified the initialization slightly, and now it's 400 kHz. A touch query now takes 550 µs.// =============================================================
// setup
// =============================================================
void setup()
{
Serial.begin(115200);
// I2C for touch controller
Wire.begin();
Wire.setClock(400000); // 400 kHz — halves I2C transaction time
// Touch controller init
if (!touchscreen.begin(40, &Wire)) {
Serial.println("FT6336 not found — check wiring");
while (1) { delay(10); }
}
Serial.println("FT6336 OK");
// INT pin: active-low, falling edge = new touch data ready
pinMode(TOUCH_INT_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(TOUCH_INT_PIN),
touchscreen_ISR, FALLING);
// DIN MIDI
MIDI.begin(MIDI_CHANNEL_OMNI);
Serial.println("Ready");
}
//----------------------------------------------------------
// init Touchscreen on Wire0
//----------------------------------------------------------
if (!touch.begin(40, &Wire))
{ // pass in 'sensitivity' coefficient and I2C bus
Serial.println("Couldn't start FT6206 touchscreen controller");
while (1)
delay(10);
}
Serial.println("Capacitive touchscreen started");
Wire.setClock(400000UL); // I2C speed 400KHz