XIAO ESP32S3 /C3 I2c Nack errors with SEEED_GFX2 only AND with ESP_NOW

I am currently using Arduino IDE version: 2.3.10; esp Board: 3.3.0 and SEEED_GFX2 library from github: My display is XIAO Round. I have also tried esp Board: 3.3.12 with same error message/

Used example from GFX2 both the UI example and MultiTouch (but Round Display)

The program compiles correctly and the display touch works but the Monitor displays continuously:

i2c.master: I2C transaction unexpected nack detected
i2c.master: s_i2c_synchronous_transaction(945): I2C transaction failed
i2c.master: i2c_master_receive(1268): I2C transaction failed
CoPIlot recommended delay(5), location or code, and suppressing the I2C warning.
None worked.
I have also flashed the firmware on my XIAO-ESP32C3 which also received the same errors.
#include <Seeed_GFX.h>
Seeed_GFX display(Seeed_Product::Seeed_Round_Display_XIAO);
// Seeed_GFX display(Seeed_Product::SenseCAP_Watcher);
// https://github.com/Seeed-Studio/Seeed_GFX2/tree/main/examples/ePaper%20Displays/Expansion%20Board%20Series




TouchPoint points[10];
TouchPoint previousVisible[10];
uint8_t previousVisibleCount = 0;
uint8_t previousTouchCount = 0;
uint32_t lastPoll = 0;

constexpr int16_t kFrameX = 35;
constexpr int16_t kFrameY = 82;
constexpr int16_t kFrameW = 342;
constexpr int16_t kFrameH = 280;
constexpr int16_t kMarkerMinX = 55;
constexpr int16_t kMarkerMaxX = 357;
constexpr int16_t kMarkerMinY = 140;
constexpr int16_t kMarkerMaxY = 342;

bool markerIsVisible(const TouchPoint& point) {
    return point.x >= kMarkerMinX && point.x <= kMarkerMaxX &&
           point.y >= kMarkerMinY && point.y <= kMarkerMaxY;
}

uint8_t collectVisiblePoints(const TouchPoint* source, uint8_t count,
                             TouchPoint* output) {
    uint8_t visibleCount = 0;
    for (uint8_t i = 0; i < count && visibleCount < 10; ++i) {
        if (markerIsVisible(source[i])) output[visibleCount++] = source[i];
    }
    return visibleCount;
}

bool visiblePointsChanged(const TouchPoint* current, uint8_t currentCount) {
    if (currentCount != previousVisibleCount) return true;
    for (uint8_t i = 0; i < currentCount; ++i) {
        const int16_t dx = static_cast<int16_t>(current[i].x) -
                           static_cast<int16_t>(previousVisible[i].x);
        const int16_t dy = static_cast<int16_t>(current[i].y) -
                           static_cast<int16_t>(previousVisible[i].y);
        if (current[i].id != previousVisible[i].id ||
            abs(dx) > 1 || abs(dy) > 1) {
            return true;
        }
    }
    return false;
}

void drawTouchCount(uint8_t count) {
    display.fillRect(146, 90, 120, 20, TFT_BLACK);
    display.setTextDatum(TC_DATUM);
    display.setTextColor(TFT_CYAN, TFT_BLACK);
    char label[24];
    snprintf(label, sizeof(label), "points: %u", count);
    display.drawString(label, display.width() / 2, 92, 2);
}

void eraseMarker(const TouchPoint& point) {
    // Covers the 10-pixel circle and the short "#id" label above it.
    display.fillRect(static_cast<int16_t>(point.x) - 18,
                     static_cast<int16_t>(point.y) - 28,
                     37, 47, TFT_BLACK);
}

void drawMarker(const TouchPoint& point, uint8_t index) {
    const uint16_t color = (index & 1U) ? TFT_YELLOW : TFT_GREEN;
    display.fillCircle(point.x, point.y, 10, color);
    display.setTextDatum(BC_DATUM);
    display.setTextColor(TFT_WHITE, TFT_BLACK);
    char label[8];
    snprintf(label, sizeof(label), "#%u", point.id);
    display.drawString(label, point.x, point.y - 14, 1);
}

void setup() {
    Serial.begin(115200);
    if (!display.begin()) {
        Serial.print("SenseCAP Watcher display init failed: ");
        Serial.println(display.lastResult().message);
        return;
    }

    display.fillScreen(TFT_BLACK);
    display.setTextColor(TFT_WHITE, TFT_BLACK);
    display.setTextDatum(TC_DATUM);
    display.drawString("SPD2010 MultiTouch", display.width() / 2, 28, 2);
    display.drawString("Touch with one or more fingers",
                       display.width() / 2, 52, 2);
    display.drawRoundRect(kFrameX, kFrameY, kFrameW, kFrameH,
                          16, TFT_DARKGREY);
    drawTouchCount(0);
    Serial.printf("Touch capacity: %u points\n",
                  display.touchPointCapacity());
}

void loop() {
    if (millis() - lastPoll < 40) return;
    lastPoll = millis();

    const uint8_t count = display.getTouchPoints(points, 10);
    TouchPoint visible[10];
    const uint8_t visibleCount =
        collectVisiblePoints(points, count, visible);
    const bool markersChanged =
        visiblePointsChanged(visible, visibleCount);
    const bool countChanged = count != previousTouchCount;

    if (!markersChanged && !countChanged) return;

    display.startWrite();
    if (markersChanged) {
        for (uint8_t i = 0; i < previousVisibleCount; ++i) {
            eraseMarker(previousVisible[i]);
        }
        for (uint8_t i = 0; i < visibleCount; ++i) {
            drawMarker(visible[i], i);
            previousVisible[i] = visible[i];
        }
        previousVisibleCount = visibleCount;
    }
    if (countChanged) drawTouchCount(count);
    display.endWrite();

    if (countChanged || markersChanged) {
        Serial.printf("touch points=%u, visible markers=%u\n",
                      count, visibleCount);
    }
    for (uint8_t i = 0; i < count; ++i) {
        Serial.printf("point[%u] id=%u x=%u y=%u strength=%u\n",
                      i, points[i].id, points[i].x, points[i].y,
                      points[i].strength);
    }
    previousTouchCount = count;
}

Solved: 10/16/2026; /* 10/14/2026
10/16/2026 - timing issues, some send fails, hopefully fixed
Arduino IDE version: 2.3.10
esp 32 Board version: 3.3.12
PSRAM: enabled
Preference reference: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
ESP32 Header: <esp_now.h>

The GFX2 UI example came from the author’s example.

NOTE: I found the minimum delay(1801) AFTER send message to SLAVE.
AND a delay(63) after Display: “invalidate(bounds());”
So far these DELAYS are working
delay(1801) 1801 milliseconds or 1.8 seconds
After touching a “button” on the display wait about 2 seconds or a send_fail will occur
if the result on the SLave does not take place a short wait and try button again.

Following code is currently working with ESP_NOW Slave:

#include <Arduino.h>

#include <Wire.h>

#include <stdio.h>

#include <Seeed_GFX.h>

#include <Seeed_UI.h>    //included in GFX2

#include <string.h>

#include <esp_now.h>

#include <WiFi.h>

#include <esp_mac.h>  // For the MAC2STR and MACSTR macros

Seeed_GFX display(Seeed_Product::Seeed_Round_Display_XIAO);

#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S3)

#error "Touch_UI targets XIAO ESP32S3. Select XIAO_ESP32S3 in Arduino IDE."

#endif

//MAC

uint8_t MAC1[] = {0x10, 0x91, 0xa8, 0x02, 0x5a, 0xac};  //XIAO-ESP32C3  "red 21"

typedef struct struct_message {

char a[16];  //could be increased if necessary

//int b;  //These are not needed for sending button numbers via esp_now

//float c;

//bool d;

} struct_message;

struct_message sendData;

struct_message recvData;

esp_now_peer_info_t peerInfo;

//SCallback when data is sent

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {

if(status == ESP_NOW_SEND_SUCCESS){

Serial.println("Send_Success");

}else{

Serial.println("Send_Fail");

}

}

//Callback when data is received

void OnDataRecv(const esp_now_recv_info_t *info, const uint8_t *Data, int len) {

memcpy(&recvData, Data, sizeof(recvData));

Serial.print("Bytes received: ");

Serial.println(len);

Serial.println(recvData.a);

//Serial.println(recvData.b);

//Serial.println(recvData.c);

//Serial.println(recvData.d);

Serial.println("---------");

}

//  *************** GFX2

constexpr uint16_t kBackground = 0x0004;

constexpr uint16_t kSurface = 0x10A3;

constexpr uint16_t kPadColor = 0x0841;

constexpr uint16_t kBorder = 0x2D7F;

constexpr uint16_t kPalette[] = {

TFT_CYAN, TFT_YELLOW, TFT_MAGENTA, TFT_GREEN, TFT_WHITE

};

const char* const kColorNames[] = {

"CYAN", "YELLOW", "MAGENTA", "GREEN", "WHITE"

};

constexpr size_t kPaletteSize = sizeof(kPalette) / sizeof(kPalette[0]);

constexpr size_t kMaxPaintPoints = 256;

enum CommandId : uint16_t {

CommandColor = 1,

CommandUndo,

CommandClear,

CommandB03,   // 

CommandB04,   //  #@! Button

CommandB08,

CommandB09,

CommandB12

};

struct PaintPoint {

int16_t x;

int16_t y;

uint16_t color;

bool startsStroke;

};

class PaintPad : public UiWidget {

public:

PaintPad(UiLabel& status, char\* statusText, size_t statusCapacity)

    : UiWidget(10), \_status(status), \_statusText(statusText),

      \_statusCapacity(statusCapacity) {}



void render(UiCanvas& canvas, const UiTheme&) override {

    Seeed_GFX& gfx = canvas.gfx();

    const UiRect& area = bounds();

    int cx = display.width() / 2;

    int cy = display.height() / 2;

    gfx.drawCircle(cx, cy, 119,  TFT_WHITE );

    //int32_t x, int32_t y, int32_t r, uint32_t color

    gfx.fillRoundRect(area.x, area.y, area.w, area.h, 12, kPadColor);

    gfx.drawRoundRect(area.x, area.y, area.w, area.h, 12, kBorder);



    if (\_count == 0) {

        gfx.setTextDatum(MC_DATUM);

        gfx.setTextColor(0x7BEF);

        gfx.setTextSize(1);

        gfx.drawString("DRAW HERE", area.x + area.w / 2,

                       area.y + area.h / 2);

    } else {

        for (size_t i = 0; i < \_count; ++i) {

            const PaintPoint& point = \_points\[i\];

            if (i && !point.startsStroke) {

                const PaintPoint& previous = \_points\[i - 1\];

                gfx.drawLine(previous.x, previous.y, point.x, point.y,

                             point.color);

            }

            gfx.fillCircle(point.x, point.y, 3, point.color);

        }

    }



    // Current brush indicator, kept above the drawing.

    gfx.fillCircle(area.x + area.w - 13, area.y + 13, 6,

                   kPalette\[\_colorIndex\]);

    gfx.drawCircle(area.x + area.w - 13, area.y + 13, 7, TFT_WHITE);

}



bool onEvent(UiEvent& event) override {

    const UiPoint position = {event.x, event.y};

    if (event.type == UiEventType::PointerDown) {

        if (!bounds().contains(position)) return false;

        \_drawing = true;

        addPoint(event.x, event.y, true);

        return true;



    }

    if (event.type == UiEventType::PointerMove && \_drawing) {

        if (bounds().contains(position)) addPoint(event.x, event.y, false);

        return true;

    }

    if (event.type == UiEventType::PointerUp ||

        event.type == UiEventType::PointerCancel) {

        const bool handled = \_drawing;

        \_drawing = false;

        return handled;

    }

    return false;

}



void cycleColor() {

    \_colorIndex = (\_colorIndex + 1) % kPaletteSize;

    snprintf(\_statusText, \_statusCapacity, "BRUSH: %s",

             kColorNames\[\_colorIndex\]);

    \_status.setText(\_statusText);

    Serial.println("pressed the COLOR Button\[7\]; send to espNOW");

    Serial.println(kColorNames\[\_colorIndex\]);

    invalidate(bounds());

    delay(63);

    // NOTE:  Future: possible to use a color to SEND message to another PEER /SLAVE

    strcpy(sendData.a, "B07");

    //sendData.b = 7;

    //sendData.c = 7.9;

    //sendData.d = true;



    esp_err_t result = esp_now_send(MAC1, (uint8_t \*)&sendData, sizeof(sendData));

    if (result == ESP_OK) {

      Serial.println("Send success");

    }

    else {

      Serial.println("Send Fail");

    }

    delay(1801);               

}



void undo() {

    Serial.println("pressed the UNDO Button \[6\]; send to espNOW");

    delay(63);

    strcpy(sendData.a, "B06");



    esp_err_t result = esp_now_send(MAC1, (uint8_t \*)&sendData, sizeof(sendData));

    if (result == ESP_OK) {

      Serial.println("Send success");

    }

    else {

      Serial.println("Send Fail");

    }

    delay(1801); 

    if (\_count == 0) {

        setStatus("NOTHING TO UNDO");

        return;

    }

    size_t strokeStart = \_count - 1;

    while (strokeStart > 0 && !\_points\[strokeStart\].startsStroke)

        --strokeStart;

    \_count = strokeStart;

    setStatus("LAST STROKE REMOVED");

    invalidate(bounds());

    delay(63);

          

}



void clear() {



    delay(63);

    Serial.println("pressed the Clear Button \[5\]; send to espNOW");        

    strcpy(sendData.a, "B05");



    esp_err_t result = esp_now_send(MAC1, (uint8_t \*)&sendData, sizeof(sendData));

    if (result == ESP_OK) {

      Serial.println("Send success");

    }

    else {

      Serial.println("Send Fail");

    }

    delay(1801); 

    \_count = 0;

    \_drawing = false;

    setStatus("CANVAS CLEARED");

    invalidate(bounds());     

}

//  #@! Button

void B03() {

    \_count = 0;

    \_drawing = false;

    setStatus("Pressed B03");

    Serial.println("pressed the B03 Button; send to espNOW");

    invalidate(bounds());

    delay(63);

    strcpy(sendData.a, "B03");



    esp_err_t result = esp_now_send(MAC1, (uint8_t \*)&sendData, sizeof(sendData));

    if (result == ESP_OK) {

      Serial.println("Send success");

    }

    else {

      Serial.println("Send Fail");

    }

    delay(1801);           

}



void B04() {

    \_count = 0;

    \_drawing = false;

    setStatus("Pressed B04");

    Serial.println("pressed the B04 Button; send to espNOW");

    invalidate(bounds());

    delay(63);

    strcpy(sendData.a, "B4");



    esp_err_t result = esp_now_send(MAC1, (uint8_t \*)&sendData, sizeof(sendData));

    if (result == ESP_OK) {

      Serial.println("Send success");

    }

    else {

      Serial.println("Send Fail");

    }

    delay(1801);        

}



void B08() {

    \_count = 0;

    \_drawing = false;

    setStatus("Pressed B08");

    Serial.println("pressed the B08 Button; send to espNOW");

    invalidate(bounds());

    delay(63);

    strcpy(sendData.a, "B08");



    esp_err_t result = esp_now_send(MAC1, (uint8_t \*)&sendData, sizeof(sendData));

    if (result == ESP_OK) {

      Serial.println("Send success");

    }

    else {

      Serial.println("Send Fail");

    }

    delay(1801);        

}



void B09() {

    \_count = 0;

    \_drawing = false;

    setStatus("Pressed B09");

    Serial.println("pressed the B09 Button; send to espNOW");

    invalidate(bounds());

    delay(63);

    strcpy(sendData.a, "B09");



    esp_err_t result = esp_now_send(MAC1, (uint8_t \*)&sendData, sizeof(sendData));

    if (result == ESP_OK) {

      Serial.println("Send success");

    }

    else {

      Serial.println("Send Fail");

    }

    delay(1801);        

}



void B12() {

    \_count = 0;

    \_drawing = false;

    setStatus("Pressed B12");

    Serial.println("pressed the B12 Button; send to espNOW");

    invalidate(bounds());

    delay(63);

    strcpy(sendData.a, "B12");



    esp_err_t result = esp_now_send(MAC1, (uint8_t \*)&sendData, sizeof(sendData));

    if (result == ESP_OK) {

      Serial.println("Send success");

    }

    else {

      Serial.println("Send Fail");

    }

    delay(1801);        

}

private:

void setStatus(const char\* text) {

    snprintf(\_statusText, \_statusCapacity, "%s", text);

    \_status.setText(\_statusText);

    //Serial.printf("from setStatus-char: %s\\n", sendData.a);

}



void addPoint(int16_t x, int16_t y, bool startsStroke) {

    // Avoid filling the fixed buffer with multiple identical samples.

    if (\_count && !startsStroke) {

        const PaintPoint& previous = \_points\[\_count - 1\];

        const int32_t dx = static_cast<int32_t>(x) - previous.x;

        const int32_t dy = static_cast<int32_t>(y) - previous.y;

        if (dx \* dx + dy \* dy < 9) return;

    }



    bool compacted = false;

    if (\_count == kMaxPaintPoints) {

        const size_t keep = kMaxPaintPoints / 2;

        memmove(\_points, \_points + (kMaxPaintPoints - keep),

                keep \* sizeof(PaintPoint));

        \_count = keep;

        \_points\[0\].startsStroke = true;

        compacted = true;

    }



    PaintPoint& point = \_points\[\_count++\];  //  ############# point.x  point.y

    point.x = x;

    point.y = y;

    //sprintf(bufr, "x-point: (%d), y-point: (%d) ", x, y);  //  My Diagnostics

    //Serial.println(bufr);

    point.color = kPalette\[\_colorIndex\];

    point.startsStroke = startsStroke;



    snprintf(\_statusText, \_statusCapacity, "TOUCH %3d, %3d", x, y);   // ################  prints location

    \_status.setText(\_statusText);



    if (compacted) {

        invalidate(bounds());

    } else {                            //  ### UiRect area?

        UiRect dirty;

        dirty.x = x - 5;

        dirty.y = y - 5;

        dirty.w = 11;

        dirty.h = 11;

        //sprintf(bufr, "x-dirty: (%d), y-dirty: (%d) ", dirty.x,dirty.y);  //my Diagnostic

       //Serial.println(bufr);

        if (\_count > 1 && !startsStroke) {

            const PaintPoint& previous = \_points\[\_count - 2\];

            UiRect previousArea = {static_cast<int16_t>(previous.x - 5),

                                   static_cast<int16_t>(previous.y - 5), 11, 11};

            dirty = dirty.united(previousArea);

        }

        invalidate(dirty.intersection(bounds()));

    }

}



UiLabel& \_status;

char\* \_statusText;

size_t \_statusCapacity;

PaintPoint \_points\[kMaxPaintPoints\] = {};

size_t \_count = 0;

size_t \_colorIndex = 0;

bool \_drawing = false;

};

class TouchPaintScreen : public UiScreen {

public:

TouchPaintScreen()

    : \_status(\_statusText, 1),

      \_paintPad(\_status, \_statusText, sizeof(\_statusText)),

      \_colorButton("COLOR", {handleCommand, this, CommandColor}, 20),

      \_undoButton("UNDO", {handleCommand, this, CommandUndo}, 21),

      \_clearButton("CLEAR", {handleCommand, this, CommandClear}, 22),

      \_B12Button("12",  {handleCommand, this, CommandB12}, 23),

      \_B09Button("09",  {handleCommand, this, CommandB09}, 24),  //#@! Button

      \_B08Button("08",  {handleCommand, this, CommandB08}, 25),

      \_B04Button("04",  {handleCommand, this, CommandB04}, 26),

      \_B03Button("03",  {handleCommand, this, CommandB03}, 27)

       {

    \_root.setBackground(kBackground);

}

// GC9A01 240x240 display

UiStatus onCreate() override {

    \_status.setBounds({70, 65, 120, 16});  // x-val, y-val, width, height  Original: {72, 14, 120, 16}  print status on screen here

    \_status.setColor(0xA69F);

    \_paintPad.setBounds({73, 85, 100, 85});     // x, y; width, heitht ::smaller rectanlge Original ({38, 36, 164, 126});

    \_colorButton.setBounds({18, 174, 64, 40});

    \_undoButton.setBounds({88, 174, 64, 40});

    \_clearButton.setBounds({158, 174, 64, 40});

    \_B12Button.setBounds({90, 1, 64, 40});  //x,y loc; width & height

    \_B09Button.setBounds({1,60, 64, 40});  //x,y loc; width & height

    \_B08Button.setBounds({1,110, 64, 40});  //x,y loc; width & height

    \_B04Button.setBounds({176,110, 64, 40});  //x,y loc; width & height

    \_B03Button.setBounds({176,60, 64, 40});  //x,y loc; width & height



    \_root.addChild(\_status);

    \_root.addChild(\_paintPad);

    \_root.addChild(\_colorButton);

    \_root.addChild(\_undoButton);

    \_root.addChild(\_clearButton);

    \_root.addChild(\_B12Button);  // Need this also

    \_root.addChild(\_B09Button);  // Need this also

    \_root.addChild(\_B08Button);  // Need this also

    \_root.addChild(\_B04Button);  // Need this also;  //  #@! Button

    \_root.addChild(\_B03Button);  // 

    return UiStatus::Ok;

}



UiWidget& root() override { return \_root; }

private:

static void handleCommand(void\* context, uint16_t commandId) {

    TouchPaintScreen\* screen = static_cast<TouchPaintScreen\*>(context);     // #  Touch location "commands"

    if (commandId == CommandColor) screen->\_paintPad.cycleColor();

    else if (commandId == CommandUndo) screen->\_paintPad.undo();

    else if (commandId == CommandClear) screen->\_paintPad.clear();

    else if (commandId == CommandB03) screen->\_paintPad.B03();  //

    else if (commandId == CommandB04) screen->\_paintPad.B04();  //#@! Button

    else if (commandId == CommandB08) screen->\_paintPad.B08();

    else if (commandId == CommandB09) screen->\_paintPad.B09();

    else if (commandId == CommandB12) screen->\_paintPad.B12();

}



UiContainer \_root;

char \_statusText\[32\] = "Top";

UiLabel \_status;

PaintPad \_paintPad;

UiButton \_colorButton;

UiButton \_undoButton;

UiButton \_clearButton;

UiButton \_B12Button;  // NOTE need this for each widget

UiButton \_B09Button;  // NOTE need this for each widget

UiButton \_B08Button;  // NOTE need this for each widget  #@! Button

UiButton \_B04Button;  // NOTE need this for each widget

UiButton \_B03Button;  // NOTE need this for each widget

};

//Seeed_GFX display(Seeed_Product::Seeed_Round_Display_XIAO);  //Not needed for this script?

// PaintPad already filters samples closer than 3 px; match that threshold here

// so drag strokes stay continuous without forwarding duplicate touch noise.

TouchInput touch(display, 2, 600, 3);   //Threshhold

UiInputHub input;

UiTheme theme;

UiApplication application(display, input, theme);

TouchPaintScreen mainScreen;

bool uiReady = false;

void showFatalError(const char* message) {

display.fillScreen(TFT_RED);

display.setTextDatum(MC_DATUM);

display.setTextColor(TFT_WHITE);

display.setTextSize(1);

display.drawString(message ? message : "INITIALIZATION ERROR", 120, 120);

}

void printTouchBusDiagnostic() {

uint8_t frame\[5\] = {0};

const size_t received = Wire.requestFrom(static_cast<uint8_t>(0x2E),

                                         static_cast<uint8_t>(sizeof(frame)));

size_t consumed = 0;

while (Wire.available() && consumed < sizeof(frame)) {

    frame\[consumed++\] = static_cast<uint8_t>(Wire.read());

}

Serial.printf("Touch probe: I2C=%u/5 bytes, IRQ(D7)=%s, status=0x%02X\\n",

              static_cast<unsigned>(received),

              digitalRead(D7) == LOW ? "LOW" : "HIGH",

              static_cast<unsigned>(frame\[0\]));

}

void setup() {

Serial.begin(115200);

WiFi.mode(WIFI_STA);

//Init ESP-NOW

if (esp_now_init() != ESP_OK) {

Serial.println("Error initializing");

return;

}

//Register callback on data sent

//esp_now_register_send_cb(OnDataSent);  // board version 2; NOT WORK

//Change esp_now_register_send_cb(OnDataSent); to

esp_now_register_send_cb((esp_now_send_cb_t)OnDataSent);    //brd ver 3 - This works!! 101426

peerInfo.channel = 0;

peerInfo.encrypt = false;

//Register MAC1 Device

memcpy(peerInfo.peer_addr, MAC1, 6);

if (esp_now_add_peer(&peerInfo) != ESP_OK){

Serial.println("Failed to add peer");

return;

}

//Register callback on data received

esp_now_register_recv_cb(OnDataRecv);

//  *******  GFX2 init

if (!display.begin()) {

    Serial.println(display.lastResult().message);

    return;

}

display.setRotation(0);

Serial.println("Round Display initialized: CHSC6X uses SDA=D4, SCL=D5, IRQ=D7");

printTouchBusDiagnostic();



theme.colors.background = kBackground;

theme.colors.surface = kSurface;

theme.colors.textPrimary = TFT_WHITE;

theme.colors.textSecondary = 0x7BEF;

theme.colors.accent = 0x0451;

theme.colors.focus = kBorder;

theme.colors.disabled = 0x3186;

theme.bodyTextSize = 1;

theme.smallTextSize = 1;

theme.metrics.cornerRadius = 8;

theme.metrics.touchSlop = 5;

theme.metrics.minTouchTarget = 40;



if (!uiOk(input.add(touch))) {

    showFatalError("INPUT REGISTRATION FAILED");

    return;

}



const UiStatus status = application.begin(mainScreen);

if (!uiOk(status)) {

    Serial.printf("UI init failed: %u\\n", static_cast<unsigned>(status));

    showFatalError("UI INITIALIZATION FAILED");

    return;

}

uiReady = true;

Serial.println("Touch_UI ready - touch the drawing pad or a button");

}

void loop() {

  if (!uiReady) {

    delay(100);

    return;

}

const UiStatus status = application.tick(millis());

if (!uiOk(status)) {

    Serial.printf("UI tick error: %u\\n", static_cast<unsigned>(status));

    delay(100);

    

}

delay(10);

}


In case anybody else receives compiler errors moving from <ESP32_NOW.h> to the <esp_now.h> header, I will post a simple modification I found hiding. This statement is found in many /all examples from CoPilot and other sources: “esp_now_register_send_cb(OnDataSent);” It must be changed to: esp_now_register_send_cb((esp_now_send_cb_t)OnDataSent);
//Register callback on data sent
Reference: esp_now_register_send_cb(OnDataSent); compile problem · Issue #11675 · espressif/arduino-esp32 · GitHub

Since the touch functionality works, I’d first check whether the NACKs originate from the touch controller or another I²C device. Try running a minimal sketch that only initializes the Round Display and polls getTouchPoints(), without drawing or multitouch processing. If the warnings persist, check the touch controller’s I²C address, wiring, and initialization sequence in SEEED_GFX2. Also, does the same error occur with the library’s unmodified example? That would help distinguish a library/core compatibility issue from something specific to your code.