MaTouch_ESP32S3-SPI-TFT-with-AI-2.8-ST7789V--MQTT Application

by Lan_Makerfabs in Circuits > Arduino

69 Views, 1 Favorites, 0 Comments

MaTouch_ESP32S3-SPI-TFT-with-AI-2.8-ST7789V--MQTT Application

363CD302AC73C594A7D5B126765C1C57.jpg

What is MQTT?

MQTT (Message Queuing Telemetry Transport) is a lightweight publish/subscribe messaging protocol designed specifically for communication between IoT devices in low-bandwidth, high-latency, or unreliable network environments.

Implementing the MQTT protocol requires communication between a client and a server. During this communication, MQTT defines three roles: Publisher, Broker (server), and Subscriber. Both the publisher and subscriber are clients, while the broker acts as the server. A message publisher can also be a subscriber at the same time.

  1. Publisher

Responsible for publishing messages to a topic. A publisher can send data to only one topic at a time and does not need to be concerned about whether subscribers are online when publishing messages.

  1. Subscriber

Receives messages by subscribing to topics and can subscribe to multiple topics simultaneously. MQTT also supports shared subscriptions, enabling load balancing across multiple subscribers to the same topic.

  1. Broker

Responsible for receiving messages from publishers and forwarding them to the subscribers. In addition, the broker handles client requests such as connecting, disconnecting, subscribing, and unsubscribing.

In our project, we use the public MQTT broker provided by EMQX, This broker serves as the central server for message routing between clients in the MQTT communication model.

  1. Topic

A topic can have multiple subscribers, and the broker will forward messages on that topic to all of them. Similarly, a topic can have multiple publishers, and the broker forwards messages in the order they arrive. ( For example: makerfabs/out )


Application of MQTT in MaTouch

The MaTouch_ESP32S3-SPI-TFT-with-AI-2.8-ST7789V base on ESP32S3, featuring a 2.8-inch display with capacitive touch. It comes with a wide range of onboard peripherals, including dual-channel INMP441 microphone input, MAX98357 audio output, a MicroSD card slot, camera interface, WS2812 RGB LED, RTC (real-time clock), battery, and both USB-to-serial and native USB interfaces. With its rich feature set, this board is ideal for embedded applications such as voice recognition, graphical user interfaces.

The board's Wi-Fi capabilities enable it to function as a MQTT node within an IoT network. This article primarily focuses on how to synchronize UI(button) states across multiple devices using the MQTT protocol.

Supplies

MaTouch_ESP32S3-SPI-TFT-with-AI-2.8-ST7789V *2 (One acts as the publisher, the other as the subscriber.)

How the Code Works?

Publisher Code

#include <Arduino_GFX_Library.h>
#include <bb_captouch.h>
#include <lvgl.h>
#include <ArduinoMqttClient.h>
#include <WiFi.h>

#define TFT_BLK 45
#define TFT_RES -1
#define TFT_CS 40
#define TFT_MOSI 13
#define TFT_MISO 12
#define TFT_SCLK 48
#define TFT_DC 21

#define TOUCH_INT 14
#define TOUCH_SDA 39
#define TOUCH_SCL 38
#define TOUCH_RST 18

#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240

//The name and password of the connected WiFi network.
char ssid[] = "YOUR-SSID";
char pass[] = "YOUR-PIN";

WiFiClient wifiClient; //Create a WiFi client object.
MqttClient mqttClient(wifiClient); //Create an MQTT client and bind it to the WiFi client.

//Set the MQTT broker address and port number.
const char broker[] = "broker.emqx.io"; //"test.mosquitto.org";
int port = 1883;

//Publish to a topic.
const char outTopic[] = "makerfabs/in";

int btn_flag=0;
lv_state_t btn_state;

Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, TFT_MISO, HSPI, true);
Arduino_GFX *gfx = new Arduino_ST7789(bus, TFT_RES, 1 /* rotation */, true /* IPS */);
BBCapTouch bbct;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t *buf = (lv_color_t *)malloc(sizeof(lv_color_t) * SCREEN_WIDTH * 10);
lv_obj_t *btn;
lv_obj_t *label;
lv_obj_t *label1;

void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full,
area->x2 - area->x1 + 1,
area->y2 - area->y1 + 1);
lv_disp_flush_ready(disp);
}

void my_touch_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
uint16_t x = 0, y = 0;
if (get_touch(&x, &y))
{
data->state = LV_INDEV_STATE_PR;

/*Set the coordinates*/
data->point.x = x;
data->point.y = y;
}
else
{
data->state = LV_INDEV_STATE_REL;
}
}

void btn_event_cb(lv_event_t *e)
{
btn_flag=1;
Serial.println("Button clicked!");
}

void setup()
{
Serial.begin(115200);
delay(1000);
Serial.println("LVGL Demo Start");

pinMode(TFT_BLK, OUTPUT);
digitalWrite(TFT_BLK, 1);

gfx->begin();
gfx->fillScreen(BLACK);

bbct.init(TOUCH_SDA, TOUCH_SCL, TOUCH_RST, TOUCH_INT);

lv_init();

lv_disp_draw_buf_init(&draw_buf, buf, NULL, SCREEN_WIDTH * 10);

static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = SCREEN_WIDTH;
disp_drv.ver_res = SCREEN_HEIGHT;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);

static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touch_read;
lv_indev_drv_register(&indev_drv);

btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 160, 100);
lv_obj_center(btn);
lv_obj_add_flag(btn, LV_OBJ_FLAG_CHECKABLE | LV_OBJ_FLAG_SCROLL_ON_FOCUS);
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);

label = lv_label_create(btn);
lv_label_set_text(label, "Send to Subscriber!");
lv_obj_center(label);

label1 = lv_label_create(lv_scr_act());
lv_label_set_text(label1, "I'm Publisher!");
lv_obj_set_pos(label1, 10, 10);
lv_obj_set_style_text_font(label1, &lv_font_montserrat_20, 0);

WiFi.begin(ssid, pass); //Connect to WiFi.

while (WiFi.status()!= WL_CONNECTED)
{
Serial.print(".");
delay(1000);
}

//Connect to the MQTT broker.
if (!mqttClient.connect(broker, port))
{
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());

while (1);
}

Serial.println("You're connected to the MQTT broker!");
Serial.println();


Serial.println("Setup down!");

xTaskCreatePinnedToCore(Task_TFT, "Task_TFT", 40960, NULL, 2, NULL, 0);
xTaskCreatePinnedToCore(Task_main, "Task_main", 40960, NULL, 1, NULL, 1);
}

void loop()
{
}

void Task_TFT(void *pvParameters)
{
while (1)
{
lv_timer_handler();
vTaskDelay(5);
}
}

void Task_main(void *pvParameters)
{
while (1)
{
mqttClient.poll();

if(btn_flag==1)
{
bool retained = false;
int qos = 1;
bool dup = false;
String payload;
btn_state=lv_obj_get_state(btn);

//Read the button state.
if(btn_state & LV_STATE_CHECKED)
{
payload="Hello, I'm the publisher!";
}
else
{
payload="Waiting to receive...";
}

Serial.print("Sending message to topic: ");
Serial.println(outTopic);
Serial.println(payload);
mqttClient.beginMessage(outTopic, payload.length(), retained, qos, dup); //Begin constructing an MQTT message.
mqttClient.write((const uint8_t *)payload.c_str(), payload.length()); //Write the button state message.
mqttClient.endMessage(); //Send the message.

btn_flag=0;
}

vTaskDelay(50);
}
}

int get_touch(uint16_t *x, uint16_t *y)
{
TOUCHINFO ti;
if (bbct.getSamples(&ti))
{
*x = map(ti.y[0], 0, 320, 0, 320);
*y = map(ti.x[0], 240, 0, 0, 240);

return 1;
}
else
return 0;
}

Key part of the code

  1. Set the MQTT broker address and port number.
const char broker[] = "broker.emqx.io";
int port = 1883;
  1. Publish to a topic.
const char outTopic[] = "makerfabs/out";
  1. Connect to the MQTT broker.
if (!mqttClient.connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
while (1);
}
Serial.println("You're connected to the MQTT broker!");
  1. Check button state and publish message
if(btn_state & LV_STATE_CHECKED)
{
payload="Hello, I'm the publisher!";
}
else
{
payload="Waiting to receive...";
}

Serial.print("Sending message to topic: ");
Serial.println(outTopic);
Serial.println(payload);
mqttClient.beginMessage(outTopic, payload.length(), retained, qos, dup);
mqttClient.write((const uint8_t *)payload.c_str(), payload.length());
mqttClient.endMessage();

Subscriber Code

#include <Arduino_GFX_Library.h>
#include <bb_captouch.h>
#include <lvgl.h>
#include <ArduinoMqttClient.h>
#include <WiFi.h>

#define TFT_BLK 45
#define TFT_RES -1
#define TFT_CS 40
#define TFT_MOSI 13
#define TFT_MISO 12
#define TFT_SCLK 48
#define TFT_DC 21

#define TOUCH_INT 14
#define TOUCH_SDA 39
#define TOUCH_SCL 38
#define TOUCH_RST 18

#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240

//The name and password of the connected WiFi network.
char ssid[] = "YOUR-SSID";
char pass[] = "YOUR-PIN";

WiFiClient wifiClient; //Create a WiFi client object.
MqttClient mqttClient(wifiClient); //Create an MQTT client and bind it to the WiFi client.

//Set the MQTT broker address and port number.
const char broker[] = "broker.emqx.io"; //"test.mosquitto.org";
int port = 1883;

//Subscribe to a topic.
const char inTopic[] = "makerfabs/in";

int btn_flag=0;
lv_state_t btn_state;

Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, TFT_MISO, HSPI, true);
Arduino_GFX *gfx = new Arduino_ST7789(bus, TFT_RES, 1 /* rotation */, true /* IPS */);
BBCapTouch bbct;

static lv_disp_draw_buf_t draw_buf;
static lv_color_t *buf = (lv_color_t *)malloc(sizeof(lv_color_t) * SCREEN_WIDTH * 10);
lv_obj_t *btn;
lv_obj_t *label;
lv_obj_t *label1;
lv_obj_t *label2;
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full,
area->x2 - area->x1 + 1,
area->y2 - area->y1 + 1);
lv_disp_flush_ready(disp);
}

void my_touch_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
uint16_t x = 0, y = 0;
if (get_touch(&x, &y))
{
data->state = LV_INDEV_STATE_PR;

/*Set the coordinates*/
data->point.x = x;
data->point.y = y;
}
else
{
data->state = LV_INDEV_STATE_REL;
}
}

void setup()
{
Serial.begin(115200);
delay(1000);
Serial.println("LVGL Demo Start");

pinMode(TFT_BLK, OUTPUT);
digitalWrite(TFT_BLK, 1);

gfx->begin();
gfx->fillScreen(BLACK);

bbct.init(TOUCH_SDA, TOUCH_SCL, TOUCH_RST, TOUCH_INT);

lv_init();

lv_disp_draw_buf_init(&draw_buf, buf, NULL, SCREEN_WIDTH * 10);

static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = SCREEN_WIDTH;
disp_drv.ver_res = SCREEN_HEIGHT;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);

static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touch_read;
lv_indev_drv_register(&indev_drv);

btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 160, 100);
lv_obj_center(btn);
lv_obj_add_flag(btn, LV_OBJ_FLAG_CHECKABLE | LV_OBJ_FLAG_SCROLL_ON_FOCUS);

label = lv_label_create(btn);
lv_label_set_text(label, "Receive");
lv_obj_center(label);

label1 = lv_label_create(lv_scr_act());
lv_label_set_text(label1, "I'm Subscriber!");
lv_obj_set_pos(label1, 10, 10);
lv_obj_set_style_text_font(label1, &lv_font_montserrat_20, 0);

label2 = lv_label_create(lv_scr_act());
lv_label_set_text(label2, "Waiting to receive...");
lv_obj_set_pos(label2, 10, 180);
lv_obj_set_style_text_font(label2, &lv_font_montserrat_20, 0);

WiFi.begin(ssid, pass);

while (WiFi.status()!= WL_CONNECTED)
{
Serial.print(".");
delay(1000);
}

if (!mqttClient.connect(broker, port))
{
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());

while (1);
}

Serial.println("You're connected to the MQTT broker!");
Serial.println();

// set the message receive callback
mqttClient.onMessage(onMqttMessage);

Serial.print("Subscribing to topic: ");
Serial.println(inTopic);
Serial.println();

int subscribeQos = 1; //Set the subscription QoS level to 1.

mqttClient.subscribe(inTopic, subscribeQos); //Subscribe to the specified topic.

Serial.print("Waiting for messages on topic: ");
Serial.println(inTopic);
Serial.println();

Serial.println("Setup down!");

xTaskCreatePinnedToCore(Task_TFT, "Task_TFT", 40960, NULL, 2, NULL, 0);
xTaskCreatePinnedToCore(Task_main, "Task_main", 40960, NULL, 1, NULL, 1);
}

void loop()
{
}

void Task_TFT(void *pvParameters)
{
while (1)
{
lv_timer_handler();
vTaskDelay(5);
}
}

void Task_main(void *pvParameters)
{
while (1)
{
mqttClient.poll();
vTaskDelay(50);
}
}

void onMqttMessage(int messageSize)
{
String receive;
// we received a message, print out the topic and contents
Serial.print("Received a message with topic '");
Serial.print(mqttClient.messageTopic());
Serial.print("', duplicate = ");
Serial.print(mqttClient.messageDup() ? "true" : "false");
Serial.print(", QoS = ");
Serial.print(mqttClient.messageQoS());
Serial.print(", retained = ");
Serial.print(mqttClient.messageRetain() ? "true" : "false");
Serial.print("', length ");
Serial.print(messageSize);
Serial.println(" bytes:");

//Wait for message reception.
while (mqttClient.available())
{
char c = (char)mqttClient.read();
receive += c;
}
Serial.println("Received payload: " + receive);
Serial.println();

if(receive=="Hello, I'm the publisher!")
{
lv_obj_add_state(btn, LV_STATE_CHECKED);
char buffer[128];
snprintf(buffer, sizeof(buffer), "Received message: \n%s", receive.c_str());
lv_label_set_text(label2, buffer);
}
else if(receive=="Waiting to receive...")
{
lv_obj_clear_state(btn, LV_STATE_CHECKED);
lv_label_set_text(label2, receive.c_str());
}
Serial.println();
}

int get_touch(uint16_t *x, uint16_t *y)
{
TOUCHINFO ti;
if (bbct.getSamples(&ti))
{
*x = map(ti.y[0], 0, 320, 0, 320);
*y = map(ti.x[0], 240, 0, 0, 240);

return 1;
}
else
return 0;
}

Key part of the code

  1. Set the MQTT broker address and port number.
const char broker[] = "broker.emqx.io";
int port = 1883;
  1. Subscribe to a topic.
const char inTopic[] = "makerfabs/in";
  1. Connect to the MQTT broker.
if (!mqttClient.connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
while (1);
}
Serial.println("You're connected to the MQTT broker!");
  1. Set the message receive callback.
mqttClient.onMessage(onMqttMessage);
  1. Handle all MQTT communication events.
mqttClient.poll();
  1. When a message is received, check its content and synchronize the button accordingly.
if(receive=="Hello, I'm the publisher!")
{
lv_obj_add_state(btn, LV_STATE_CHECKED);
char buffer[128];
snprintf(buffer, sizeof(buffer), "Received message: \n%s", receive.c_str());
lv_label_set_text(label2, buffer);
}
else if(receive=="Waiting to receive...")
{
lv_obj_clear_state(btn, LV_STATE_CHECKED);
lv_label_set_text(label2, receive.c_str());
}

Arduino Setup

GFX Library for Arduino.png
lvgl.png
QQ20250527-145722.png
TOOLS.png
  1. Install the Arduino IDE.
  2. Install ESP32 Board, you can follow the steps in this guide to get started quickly.
  3. Install the library according to the following version.
  4. GFX Library for Arduino v1.5.6
  5. bb_captouch v1.3.1
  6. ArduinoMqttClient v0.1.8
  7. Download and copy the lvgl library into the arduino library.(Usually located at C:\Users\Username\Documents\Arduino\libraries)


Upload the Code

  1. Copy the Publisher code and create a new sketch in Arduino.
  2. Modify your WiFi name and password in the code.
  3. Use Type-C USB cable to connect the board and PC, and select the development board "ESP32S3 Dev Module" and the port.
  4. Select "Tools > board:"xxx" > ESP32 Arduino > ESP32S3 Dev Module".
  5. Select "Tools > Port", Select the port number of the board.
  6. Select Flash Size is 16MB(128MB), Partition Scheme is 16M Flash (3MB APP/9.9MB FATFS), PSRAM is OPI PSRAM.
  7. Click the Upload button in the Arduino IDE and wait for the code to upload.
  8. Prepare the other board, copy the Subscriber code and create a new sketch in Arduino., and upload it following the same steps.

Result

IMG20250604101801.jpg
IMG20250604101734.jpg