Je souhaite, à partir de la souris de mon PC, diriger un curseur sur un petit système embarqué peu cher. Pour cela, je choisis une carte ESP32 équipée du WiFi et d’un écran (TTGO T Display) à moins de 10€.
La transmission sera de type client serveur. Elle se fera en UDP. Nous utiliserons les IDE Processing et Arduino ainsi que les librairies suivantes :
- IDE Arduino 1.8.12 :
- IDE Processing 3.5.4 :
- Librairie hypermedia.net 0.1
Sommaire
Principe
Ici, le but est de créer un IoT capable de traiter en temps réel (latence faible) un flux rapide de données (60Hz au moins). Un tel IoT est notamment utile pour des applications robotiques ou interactives.
Relation client serveur

Le client sera ici le logiciel Processing. Il récupérera la position xy de la souris et la renverra au serveur UDP. Les trames transmises seront simples et de la forme :
xxx yyy\0.
Le serveur, lui, positionnera sur son écran un carré à la position xy indiquée par le client en temps réel (60 Hz). reproduira en temps réel et sur son écran la position de la souris, d’ou le choix d’une carte ESP32 équipée d’un écran (TTGO T Display).
Le routeur, enfin, sert de relais entre les deux
Transmission UDP asynchrone local
Le protocole UDP est un des protocoles de base d’un réseau Ethernet avec le TCP. Facile à mettre en œuvre :
- il offre une connexion point à point entre deux hôtes
- il est moins lourd que le TCP
Un server asynchrone est un serveur dont les fonctions sont non bloquantes. Il n’attend pas d’avoir reçu les données d’un client pour traiter celles d’un autre. Il renvoie les réponses le plus vite possible sans les regrouper. Ce mode de fonctionnement est essentiel pour une réaction temps réel de la carte.
Le terme local signifie que le serveur (ESP32) et le client (Processing sur PC) sont sur le même réseau.
Une vidéo est à suivre.
Installation
Préparons le matériel et les structures logicielles nécessaires avant de passer à la suite.
Matériel
Achetez les deux éléments suivants :
- Carte ESP32 équipé d’un écran TFT : TTGO T Display
- Cable USB-C pour relier votre carte à votre PC
IDE Processing
Pour préparer l’IDE Processing, rendez-vous à cette Adresse. Installez l’exécutable. Puis :
- Installez la librairie UDP pour processing
Sketch ˃ Importer une librairie ˃ Ajoutez une librairie:
Une fenêtre s’ouvreLibrairies:
Tapez UDP dans la barre de recherche,
Choisissez la version de Stephane Cousot
IDE Arduino
Pour préparer l’IDE Arduino, rendez-vous à cette Adresse. Installez l’exécutable, puis :
- Installez le compilateur de cartes ESP32 : lancez l’IDE
Fichier ˃ Préférences:
Ajoutez https://dl.espressif.com/dl/package_esp32_index.json;Outils ˃ Type de carte ˃ Gestionnaire de cartes:
recherchez et installez esp32Outil ˃ Type de carte:
Choisissez ESP32 Dev Module
- Installez les librairies : Fermez l’IDE
- Préparez l’écran TFT :
TFT_eSPI ˃ User_Setup_Select.h:
Commentez#include ˂User_Setup.h˃
Décommentez#include ˂User_Setups/Setup25_TTGO_T_Display.h˃
Programme
Vous pouvez voir ici les deux programmes répartis sur 5 onglets.
Serveur local UDP
Le programme du serveur local UDP est dans les onglets 2 à 5. Il doit être téléversé dans la puce TTGO T Display à l’aide de l’IDE Arduino. Pour cela :
- Créez un dossier
UDPserver - Créez 4 fichiers texte dans lesquels vous copierez les codes respectifs :
TCPserver.inoconfig.hscreen.inoudp.ino
Outil ˃ Type de carte: ChoisissezESP32 Dev Module- Versez le code dans la puce TTGO T Display :
- Branchez la puce à votre PC
Outil ˃ Port: Sélectionnez le nouveau port apparu- 1e icone à gauche : Téléversez le programme
Seul le fichier config.h est à modifier en fonction des paramètres de connexions de votre routeur / box sur lequel est connecté le client. Le programme est très robuste :
- Plusieurs clients UDP peuvent se connecter à la fois
- Un client UDP peut se déconnecter, puis se reconnecter sans problèmes
- Les logs des activités sont renvoyés sur le port série (115200 bits/s)
Client local UDP
Le programme du client local UDP est dans le premier onglet. Il est à copier-coller dans l’IDE Processing. Seul la variable String ip_server est à modifier (et le port si vous souhaitez le modifier) en y rentrant l’ip du serveur. Moins robuste, ce programme ne doit être lancé qu’après que le serveur soit prêt (affiche une IP)
Code
// Variables et librairies
import hypermedia.net.*;
UDP client;
int screen_x = 3; //Agrandissement de l'écran
int screen_h = 240; //Taille de l'écran horizontal de la puce TTGO T Display
int screen_v = 135; //Taille de l'écran vertical de la puce TTGO T Display
int old_x;
int old_y;
// Elément à changer selon l'IP du serveur, son port, et la fréquence des données
String ip_server = "xxx.xxx.xxx.xxx"; // IP du serveur distant
int port_server = 1234; // Port du serveur distant
int port_client = 6000; // valeur aléatoire, normalement non utilisée
int frequence = 100; // fréquence d'actualisation (oui j'ai mis, 100 Hz, pas 60 Hz)
// Lancement de l'écran (se lance avant "setup()")
void settings()
{
size(screen_v*screen_x, screen_h*screen_x);
}
// Lancement du client, connexion au serveur et fixation du framerate
void setup()
{
frameRate(frequence);
client = new UDP(this, port_client);
client.listen(true);
}
// Rafraichissement de l'écran et envoi de la position de la souris au serveur
void draw()
{
int x = mouseX;
int y = mouseY;
if(old_x != x || old_y != y)
{
old_x = x;
old_y = y;
background(20);
square(x, y, 3*screen_x);
// Contrairement au TCP, il n'est pas nécessaire de voir si le client est actif
// Car on ne vérifie pas si les données sont bien reçues en UDP
println(str(x) + " " + str(y));
client.send( str(x/screen_x) + " " + str(y/screen_x) + "\0" , ip_server, port_server );
}
}
// Ecoute des informations rentrant par le "port_client"
void receive( byte[] data, String ip, int port )
{
data = subset(data, 0, data.length-2);
String message = new String( data );
println( "receive: \""+message+"\" from "+ip+" on port "+port );
}//Librairies nécessaires au serveur TCP
#include "WiFi.h"
#include "AsyncUDP.h"
#include "config.h"
//Librairies et paramètres de base nécessaires à l'écran
#include <TFT_eSPI.h>
#include <SPI.h>
#include "esp_adc_cal.h"
#ifndef TFT_DISPOFF
#define TFT_DISPOFF 0x28
#endif
#ifndef TFT_SLPIN
#define TFT_SLPIN 0x10
#endif
#define TFT_MOSI 19
#define TFT_SCLK 18
#define TFT_CS 5
#define TFT_DC 16
#define TFT_RST 23
#define TFT_BL 4 // Display backlight control pin
TFT_eSPI tft = TFT_eSPI(135, 240); // Invoke custom library
AsyncUDP udp;
//Initialisation
void setup()
{
//Initialisation connexion série (pour les logs)
Serial.begin(115200);
Serial.println("\n\nHello World");
//Initilisation écran
tft_init();
//Connection WiFi à un routeur
WiFi.mode(WIFI_STA);
WiFi.begin(ROUTEUR_SSID, ROUTEUR_PASSWORD);
if (WiFi.waitForConnectResult() != WL_CONNECTED)
{
tft_echec_connexion();
while(1)
{
Serial.println("WiFi Failed");
delay(1000);
}
}
Serial.printf("Connected to %s, IP %s \n", ROUTEUR_SSID, WiFi.localIP().toString().c_str());
tft_show_ip(WiFi.localIP());
//Lancement du serveur
if(udp.listen(SERVER_HOST_PORT)) //Si le serveur UDP peut écouter sur le port "SERVER_HOST_PORT"
{
udp_listen(); //Démarrer l'écoute du serveur
}
}
//Loop, peut être laissé vide
void loop()
{
delay(1000);
udp.broadcast("Anyone here?");
}#ifndef CONFIG_H
#define CONFIG_H
#define ROUTEUR_SSID "ssid" //ssid de votre routeur
#define ROUTEUR_PASSWORD "password" //mot de passe de votre routeur
#define SERVER_HOST_PORT 1234
#define SERVER_HOST_NAME "esp_server"
#endif // CONFIG_H//Initialisation de l'écran
void tft_init()
{
tft.init();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
tft.setTextSize(2);
tft.setTextColor(TFT_RED);
tft.setCursor(0, 0);
tft.setTextDatum(MC_DATUM);
tft.println("Hello World\n");
}
//Simulation de la souris à l'écran à partir de la trame data
void tft_mouse(char* data_, size_t len)
{
static int vx = 0;
static int vy = 0;
int old_vx = vx;
int old_vy = vy;
char temp[20] = "";
strncpy(temp, data_, len);
temp[len] = 0;
sscanf(temp, "%d %d", &vx, &vy);
if(old_vx != vx || old_vy != vy)
{
sprintf(temp, "%d %d %d %d", old_vx, old_vy, vx, vy);
tft.fillRect(old_vx, old_vy, 3, 3, 0x000070);
tft.fillRect(vx, vy, 3, 3, TFT_GREEN);
}
}
//Affichage de l'IP sur l'écran
void tft_show_ip(IPAddress data_)
{
tft.setTextSize(2);
tft.setTextColor(TFT_RED);
tft.setCursor(0, 16);
tft.println(data_);
}
//Affichage de l'IP sur l'écran
void tft_echec_connexion()
{
tft.setTextSize(2);
tft.setTextColor(TFT_RED);
tft.setCursor(0, 16);
tft.println("echec de connexion au WiFi");
}long temps = 0;
//Configuration du serveur
void udp_listen()
{
Serial.printf("Server UDP %s Listening on IP : %s", SERVER_HOST_NAME, WiFi.localIP().toString().c_str());
//Activation de l'évènement : Je reçoit une trame UDP
udp.onPacket([](AsyncUDPPacket packet)
{
int len = packet.length();
if(len > 8) len = 8; //protection
float delai = (float)(millis() - temps);
temps = millis();
//Données reçues (log)
Serial.printf("\nData from %s (%2.0f Hz) : ", packet.remoteIP().toString().c_str(), 1000.0/delai );
Serial.write(packet.data(), len);
//udp_log(&packet); //activez ceci si vous voulez un log détaillé et envoyer une réponse au client
//Envoi des données à l'écran
tft_mouse((char*)packet.data(), packet.length());
});
}
//Log UDP, non activé par défaut, voir fonction précédente
void udp_log(AsyncUDPPacket* packet)
{
//Lecture complète de la trame UDP reçue
Serial.print(" UDP Packet Type: ");
Serial.print(packet->isBroadcast()?"Broadcast":packet->isMulticast()?"Multicast":"Unicast");
Serial.print(", From: ");
Serial.print(packet->remoteIP());
Serial.print(":");
Serial.print(packet->remotePort());
Serial.print(", To: ");
Serial.print(packet->localIP());
Serial.print(":");
Serial.print(packet->localPort());
Serial.print(", Length: ");
Serial.print(packet->length());
Serial.print(", Data: ");
Serial.write(packet->data(), packet->length());
Serial.println();
//Réponse au client
packet->printf("Got %u bytes of data", packet->length());
}