Sommaire
Introduction
Les écrans TFT tactiles 2.4 et 3.5 pouces permettent de transformer une simple carte Arduino Uno en véritable interface graphique interactive. Grâce à leur affichage couleur, leur dalle tactile et, pour certains modèles, leur lecteur microSD intégré, ils ouvrent la voie à de nombreux projets visuels et ludiques.
Dans cet article, nous présentons plusieurs exemples concrets réalisés avec Arduino : un album photo affichant des images depuis une carte microSD, un jeu de dames tactile, un morpion et un démineur. Ces projets permettent de découvrir progressivement l’affichage graphique, la gestion du tactile, la calibration de l’écran, la lecture de fichiers BMP et la logique de programmation d’un jeu.
Montage et programmation
Nous allons réaliser une série d’applications et de jeux à partir des écrans 2.4 et 3.5 pouces et une carte Arduino UNO.
Librairies nécessaires
- MCUFRIEND_kbv.h
- TouchScreen.h
-
Adafruit_GFX.h
-
SD.h
-
SPI.h
Matériel nécessaire :
1 Carte Arduino UNO
1 Capteur GUVA S12SD
1 Câble USB Type C
- 1 Carte Micro-SD de 4Go à 32GO
Câbles de connexion M-F 10 cm
-
Écran Couleur TFT de 3.5 pouces pour Arduino UNO/MEGA
17,99 € (14,99 € HT)
Vous économisez Ajouter au panier -
Câble USB Type C pour Arduino et ESP32 – Transfert Fiable et Rapide pour Projets Créatifs
4,99 € (4,16 € HT)
Vous économisez Ajouter au panier -
Carte UNO R3 – Microcontrôleur ATmega328 – compatible Arduino
8,99 € (7,49 € HT)
Vous économisez Ajouter au panier
Le montage
Branchez votre écran TFT 2.4 pouces ou 3.5 pouces sur la carte Arduino Uno, puis connectez la carte à votre ordinateur à l’aide du câble USB Type-C.
Les Codes Arduino
Le code Arduino suivant permet d’afficher l’indice UV mesuré par la capteur GUVA S12SD et de définir les niveaux de risque (faible, modéré, élevé, très élevé, extrême) .
Attention : le module GUVA-S12SD fonctionne principalement avec les rayons ultraviolets émis par la lumière du soleil et ne réagit pas correctement à l’éclairage artificiel classique.
Album photo avec un écran tactile TFT 3.5 pouces Arduino
Ce code permet d’afficher des images stockées sur une carte micro SD sur l’écran 3.5 pouces et de créer un album photo avec navigation tactile.
Procédure d’enregistrement des images dans la carte Micro SD:
- Insérer la carte dans un lecteur de carte Micro-SD connecté à un PC.
- Formater la carte microSD au format FAT32. (Clic droit → Formater).
- Ajuster les images au dimensions 480 x 320 (avec un logiciel comme Photoshop, Photofiltre, Paint, etc…).
- Enregistrer les images au format BMP 24 bits.
- Renommer les images IMG + Chiffre. Ex: IMG1, IMG2,IMG3…IMG10, IMG11, etc…
- Insérer la carte micro SD dans le connecteur associé sur l’écran.
Pour ce code nous avons enregistré 10 images. si vous souhaitez enregistrer plus ou moins d’images, veuillez modifier les noms des images dans le code (Voir à partir de la ligne 35) :
/*
Site : www.atelierdelarobotique.fr
Date: 19/06/2026
*/
#include
#include
#include
#include
#include
MCUFRIEND_kbv tft;
// Pins tactiles de l'écran 3.5"
#define XP 8
#define XM A2
#define YP A3
#define YM 9
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
#define MINPRESSURE 10
#define MAXPRESSURE 1000
// Calibration de l'écran tactile
#define TOUCH_X_LEFT 890
#define TOUCH_X_RIGHT 130
#define TOUCH_Y_TOP 860
#define TOUCH_Y_BOTTOM 120
// SD
#define SD_CS_PRIMARY 10
#define SD_CS_ALT 4
// Images de l'album
const char *photos[] = {
"IMG1.BMP",
"IMG2.BMP",
"IMG3.BMP",
"IMG4.BMP",
"IMG5.BMP",
"IMG6.BMP",
"IMG7.BMP",
"IMG8.BMP",
"IMG9.BMP",
"IMG10.BMP",
};
const int photoCount = sizeof(photos) / sizeof(photos[0]);
int currentPhoto = 0;
// Lecture BMP
#define BUFFPIXEL 40
// Couleurs
#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define GREEN 0x07E0
#define BLUE 0x001F
#define GREY 0x8410
int sdCS = SD_CS_PRIMARY;
void setup() {
Serial.begin(9600);
//Initialisation Ecran
uint16_t ID = tft.readID();
Serial.print(F("ID ecran : 0x"));
Serial.println(ID, HEX);
if (ID == 0xD3D3 || ID == 0xFFFF || ID == 0x0000) {
ID = 0x9486;
}
tft.begin(ID);
tft.setRotation(1);
tft.fillScreen(BLACK);
showBootScreen();
setupSD();
displayPhoto(currentPhoto);
}
void loop() {
TSPoint p = ts.getPoint();
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
int x, y;
convertTouch(p, x, y);
Serial.print(F("Touch X="));
Serial.print(x);
Serial.print(F(" Y="));
Serial.print(y);
Serial.print(F(" P="));
Serial.println(p.z);
// Zone gauche : image précédente
if (x < tft.width() / 3) {
previousPhoto();
}
// Zone droite : image suivante
else if (x > (tft.width() * 2) / 3) {
nextPhoto();
}
// Zone centrale : réafficher l'info
else {
showOverlay();
}
delay(350);
}
}
// --------------------------------------------------
// INITIALISATION
// --------------------------------------------------
void showBootScreen() {
tft.fillScreen(BLACK);
tft.setTextColor(WHITE);
tft.setTextSize(3);
tft.setCursor(55, 80);
tft.println(F("ALBUM PHOTO"));
tft.setTextSize(2);
tft.setCursor(80, 130);
tft.println(F("TFT 3.5 pouces"));
tft.setTextSize(1);
tft.setCursor(95, 180);
tft.println(F("Lecture BMP depuis microSD"));
}
void setupSD() {
pinMode(10, OUTPUT);
digitalWrite(10, HIGH);
pinMode(4, OUTPUT);
digitalWrite(4, HIGH);
#if defined(__AVR_ATmega2560__)
pinMode(53, OUTPUT);
digitalWrite(53, HIGH);
#endif
tft.setTextSize(2);
tft.setTextColor(WHITE);
tft.setCursor(100, 230);
tft.println(F("SD..."));
if (!SD.begin(SD_CS_PRIMARY)) {
Serial.println(F("SD D10 echouee, essai D4"));
if (!SD.begin(SD_CS_ALT)) {
tft.fillScreen(BLACK);
tft.setTextColor(RED);
tft.setTextSize(3);
tft.setCursor(70, 110);
tft.println(F("ERREUR SD"));
tft.setTextSize(1);
tft.setTextColor(WHITE);
tft.setCursor(60, 160);
tft.println(F("Verifiez carte microSD FAT32"));
tft.setCursor(60, 175);
tft.println(F("Essayez CS D10 ou D4"));
while (true);
}
sdCS = SD_CS_ALT;
} else {
sdCS = SD_CS_PRIMARY;
}
Serial.print(F("SD OK sur CS="));
Serial.println(sdCS);
tft.setTextColor(GREEN);
tft.setCursor(100, 255);
tft.println(F("SD OK"));
delay(800);
}
// --------------------------------------------------
// NAVIGATION ALBUM
// --------------------------------------------------
void nextPhoto() {
currentPhoto++;
if (currentPhoto >= photoCount) {
currentPhoto = 0;
}
displayPhoto(currentPhoto);
}
void previousPhoto() {
currentPhoto--;
if (currentPhoto < 0) {
currentPhoto = photoCount - 1;
}
displayPhoto(currentPhoto);
}
void displayPhoto(int index) {
tft.fillScreen(BLACK);
if (!drawBMP(photos[index], 0, 0)) {
showError(photos[index]);
} else {
showOverlay();
}
}
void showOverlay() {
// Bandeau bas
tft.fillRect(0, 285, 480, 35, BLACK);
tft.drawRect(0, 285, 480, 35, WHITE);
tft.setTextColor(WHITE);
tft.setTextSize(1);
tft.setCursor(10, 294);
tft.print(F("< precedent"));
tft.setCursor(185, 294);
tft.print(photos[currentPhoto]);
tft.setCursor(390, 294);
tft.print(F("suivant >"));
tft.setCursor(200, 308);
tft.print(currentPhoto + 1);
tft.print(F("/"));
tft.print(photoCount);
}
void showError(const char *filename) {
tft.fillScreen(BLACK);
tft.setTextColor(RED);
tft.setTextSize(3);
tft.setCursor(70, 100);
tft.println(F("IMAGE ERREUR"));
tft.setTextSize(2);
tft.setTextColor(WHITE);
tft.setCursor(90, 150);
tft.println(filename);
tft.setTextSize(1);
tft.setCursor(50, 190);
tft.println(F("Verifier : BMP 24 bits non compresse"));
tft.setCursor(50, 205);
tft.println(F("Nom court : IMG1.BMP, IMG2.BMP..."));
}
// --------------------------------------------------
// TACTILE
// --------------------------------------------------
void convertTouch(TSPoint p, int &x, int &y) {
x = map(p.y, TOUCH_X_LEFT, TOUCH_X_RIGHT, 30, tft.width() - 30);
y = map(p.x, TOUCH_Y_TOP, TOUCH_Y_BOTTOM, 30, tft.height() - 30);
x = constrain(x, 0, tft.width() - 1);
y = constrain(y, 0, tft.height() - 1);
}
// --------------------------------------------------
// LECTURE BMP
// --------------------------------------------------
uint16_t read16(File &f) {
uint16_t result;
result = f.read();
result |= f.read() << 8;
return result;
}
uint32_t read32(File &f) {
uint32_t result;
result = f.read();
result |= (uint32_t)f.read() << 8;
result |= (uint32_t)f.read() << 16;
result |= (uint32_t)f.read() << 24;
return result;
}
bool drawBMP(const char *filename, int16_t x, int16_t y) {
File bmpFile;
int32_t bmpWidth;
int32_t bmpHeight;
uint8_t bmpDepth;
uint32_t bmpImageoffset;
uint32_t rowSize;
bool flip = true;
uint8_t sdbuffer[3 * BUFFPIXEL];
uint16_t lcdbuffer[BUFFPIXEL];
uint8_t buffidx = sizeof(sdbuffer);
if ((x >= tft.width()) || (y >= tft.height())) {
return false;
}
Serial.print(F("Ouverture : "));
Serial.println(filename);
bmpFile = SD.open(filename);
if (!bmpFile) {
Serial.println(F("Fichier introuvable"));
return false;
}
if (read16(bmpFile) != 0x4D42) {
Serial.println(F("Ce n'est pas un BMP"));
bmpFile.close();
return false;
}
read32(bmpFile);
read32(bmpFile);
bmpImageoffset = read32(bmpFile);
read32(bmpFile);
bmpWidth = (int32_t)read32(bmpFile);
bmpHeight = (int32_t)read32(bmpFile);
if (read16(bmpFile) != 1) {
bmpFile.close();
return false;
}
bmpDepth = read16(bmpFile);
uint32_t compression = read32(bmpFile);
if (bmpDepth != 24 || compression != 0) {
Serial.println(F("BMP non supporte"));
bmpFile.close();
return false;
}
if (bmpHeight < 0) {
bmpHeight = -bmpHeight;
flip = false;
}
rowSize = (bmpWidth * 3 + 3) & ~3;
int16_t w = bmpWidth;
int16_t h = bmpHeight;
if ((x + w - 1) >= tft.width()) {
w = tft.width() - x;
}
if ((y + h - 1) >= tft.height()) {
h = tft.height() - y;
}
for (int16_t row = 0; row < h; row++) {
uint32_t pos;
if (flip) {
pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
} else {
pos = bmpImageoffset + row * rowSize;
}
if (bmpFile.position() != pos) {
bmpFile.seek(pos);
buffidx = sizeof(sdbuffer);
}
tft.setAddrWindow(x, y + row, x + w - 1, y + row);
bool first = true;
uint8_t lcdidx = 0;
for (int16_t col = 0; col < w; col++) {
if (buffidx >= sizeof(sdbuffer)) {
bmpFile.read(sdbuffer, sizeof(sdbuffer));
buffidx = 0;
}
uint8_t b = sdbuffer[buffidx++];
uint8_t g = sdbuffer[buffidx++];
uint8_t r = sdbuffer[buffidx++];
lcdbuffer[lcdidx++] = tft.color565(r, g, b);
if (lcdidx >= BUFFPIXEL) {
tft.pushColors(lcdbuffer, lcdidx, first);
first = false;
lcdidx = 0;
}
}
if (lcdidx > 0) {
tft.pushColors(lcdbuffer, lcdidx, first);
}
}
bmpFile.close();
return true;
}
Le résultat attendu
Jeu de dames sur écran TFT 3.5 pouces Arduino
Ce code permet de réaliser un jeu de dames tactile à deux joueurs sur un écran TFT 3.5 pouces connecté à une carte Arduino. Le plateau s’affiche directement sur l’écran et chaque joueur peut sélectionner ses pions, effectuer ses déplacements et réaliser des prises simplement en touchant les cases.
Le programme gère les déplacements des pions, les prises avant et arrière, la promotion en dame ainsi que le déplacement des dames sur plusieurs cases. Grâce au grand format de l’écran 3.5 pouces, l’interface est plus confortable, avec un plateau lisible, une zone d’information pour le tour du joueur et un bouton tactile permettant de relancer une nouvelle partie.
/*
Site : www.atelierdelarobotique.fr
Date: 19/06/2026
Jeu de DAMES
*/
#include
#include
#include
MCUFRIEND_kbv tft;
// Pins tactiles fréquents pour TFT 3.5" shield
#define XP 8
#define XM A2
#define YP A3
#define YM 9
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
#define MINPRESSURE 10
#define MAXPRESSURE 1000
// Calibration approximative du tactile 3.5"
#define TOUCH_X_LEFT 916
#define TOUCH_X_RIGHT 227
#define TOUCH_Y_TOP 855
#define TOUCH_Y_BOTTOM 239
// Couleurs RGB565
#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define GREEN 0x07E0
#define BLUE 0x001F
#define YELLOW 0xFFE0
#define CYAN 0x07FF
#define GREY 0x8410
#define DARKSQUARE 0x7BEF
#define LIGHTSQUARE 0xEF5D
// Écran 3.5" en paysage : 480 x 320
#define BOARD_X 0
#define BOARD_Y 0
#define CELL 40
#define BOARD_SIZE 320
#define PANEL_X 330
// Pièces
#define EMPTY 0
#define WHITE_MAN 1
#define WHITE_KING 2
#define BLACK_MAN -1
#define BLACK_KING -2
int8_t board[8][8];
bool whiteTurn = true;
int selectedX = -1;
int selectedY = -1;
void setup() {
Serial.begin(9600);
uint16_t ID = tft.readID();
Serial.print(F("ID ecran : 0x"));
Serial.println(ID, HEX);
if (ID == 0xD3D3 || ID == 0xFFFF || ID == 0x0000) {
ID = 0x9486; // Très courant sur TFT 3.5"
}
tft.begin(ID);
tft.setRotation(1); // Paysage 480 x 320
tft.fillScreen(BLACK);
newGame();
}
void loop() {
TSPoint p = ts.getPoint();
// Important : les pins tactiles sont partagées avec le TFT
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
int sx, sy;
convertTouch(p, sx, sy);
Serial.print(F("X="));
Serial.print(sx);
Serial.print(F(" Y="));
Serial.print(sy);
Serial.print(F(" P="));
Serial.println(p.z);
handleTouch(sx, sy);
delay(150);
}
}
// --------------------------------------------------
// NOUVELLE PARTIE
// --------------------------------------------------
void newGame() {
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
board[y][x] = EMPTY;
}
}
// Noirs en haut
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 8; x++) {
if ((x + y) % 2 == 1) {
board[y][x] = BLACK_MAN;
}
}
}
// Blancs en bas
for (int y = 5; y < 8; y++) {
for (int x = 0; x < 8; x++) {
if ((x + y) % 2 == 1) {
board[y][x] = WHITE_MAN;
}
}
}
whiteTurn = true;
selectedX = -1;
selectedY = -1;
drawAll();
}
// --------------------------------------------------
// GESTION TACTILE
// --------------------------------------------------
void handleTouch(int sx, int sy) {
// Bouton NEW
if (sx >= PANEL_X + 20 && sx <= PANEL_X + 130 && sy >= 260 && sy <= 305) {
newGame();
return;
}
// Hors plateau
if (sx < BOARD_X || sx >= BOARD_X + BOARD_SIZE || sy < BOARD_Y || sy >= BOARD_Y + BOARD_SIZE) {
return;
}
int bx = (sx - BOARD_X) / CELL;
int by = (sy - BOARD_Y) / CELL;
if (bx < 0 || bx > 7 || by < 0 || by > 7) return;
int8_t touchedPiece = board[by][bx];
// Première sélection
if (selectedX == -1 || selectedY == -1) {
if (isCurrentPlayerPiece(touchedPiece)) {
selectedX = bx;
selectedY = by;
drawCell(bx, by);
}
return;
}
// Changer de pièce sélectionnée
if (isCurrentPlayerPiece(touchedPiece)) {
int oldX = selectedX;
int oldY = selectedY;
selectedX = bx;
selectedY = by;
drawCell(oldX, oldY);
drawCell(selectedX, selectedY);
return;
}
int fromX = selectedX;
int fromY = selectedY;
int capX = -1;
int capY = -1;
if (tryMove(fromX, fromY, bx, by, capX, capY)) {
selectedX = -1;
selectedY = -1;
drawCell(fromX, fromY);
drawCell(bx, by);
if (capX >= 0 && capY >= 0) {
drawCell(capX, capY);
}
whiteTurn = !whiteTurn;
drawPanel();
checkWinner();
} else {
selectedX = -1;
selectedY = -1;
drawCell(fromX, fromY);
}
}
// Tactile inversé pour ton écran 3.5"
void convertTouch(TSPoint p, int &x, int &y) {
x = map(p.y, TOUCH_X_LEFT, TOUCH_X_RIGHT, 30, tft.width() - 30);
y = map(p.x, TOUCH_Y_TOP, TOUCH_Y_BOTTOM, 30, tft.height() - 30);
x = constrain(x, 0, tft.width() - 1);
y = constrain(y, 0, tft.height() - 1);
}
// --------------------------------------------------
// RÈGLES DU JEU
// --------------------------------------------------
bool tryMove(int fromX, int fromY, int toX, int toY, int &capX, int &capY) {
capX = -1;
capY = -1;
if (toX < 0 || toX > 7 || toY < 0 || toY > 7) return false;
if (board[toY][toX] != EMPTY) return false;
int8_t piece = board[fromY][fromX];
if (!isCurrentPlayerPiece(piece)) return false;
int dx = toX - fromX;
int dy = toY - fromY;
int absDx = abs(dx);
int absDy = abs(dy);
bool isKing = abs(piece) == 2;
// -------------------------
// DAME
// -------------------------
if (isKing) {
if (absDx != absDy) return false;
if (absDx == 0) return false;
int stepX = signInt(dx);
int stepY = signInt(dy);
int x = fromX + stepX;
int y = fromY + stepY;
int opponentCount = 0;
int opponentX = -1;
int opponentY = -1;
while (x != toX && y != toY) {
int8_t current = board[y][x];
if (current != EMPTY) {
if (isCurrentPlayerPiece(current)) {
return false;
}
if (isOpponentPiece(current)) {
opponentCount++;
opponentX = x;
opponentY = y;
if (opponentCount > 1) {
return false;
}
}
}
x += stepX;
y += stepY;
}
// Déplacement simple de dame
if (opponentCount == 0) {
movePiece(fromX, fromY, toX, toY);
return true;
}
// Prise longue de dame
if (opponentCount == 1) {
board[opponentY][opponentX] = EMPTY;
capX = opponentX;
capY = opponentY;
movePiece(fromX, fromY, toX, toY);
return true;
}
return false;
}
// -------------------------
// PION SIMPLE
// -------------------------
int forward = whiteTurn ? -1 : 1;
// Déplacement simple : uniquement vers l'avant
if (absDx == 1 && absDy == 1) {
if (dy == forward) {
movePiece(fromX, fromY, toX, toY);
promoteIfNeeded(toX, toY);
return true;
}
}
// Prise : avant ou arrière
if (absDx == 2 && absDy == 2) {
int midX = (fromX + toX) / 2;
int midY = (fromY + toY) / 2;
if (isOpponentPiece(board[midY][midX])) {
board[midY][midX] = EMPTY;
capX = midX;
capY = midY;
movePiece(fromX, fromY, toX, toY);
promoteIfNeeded(toX, toY);
return true;
}
}
return false;
}
void movePiece(int fromX, int fromY, int toX, int toY) {
board[toY][toX] = board[fromY][fromX];
board[fromY][fromX] = EMPTY;
}
void promoteIfNeeded(int x, int y) {
if (board[y][x] == WHITE_MAN && y == 0) {
board[y][x] = WHITE_KING;
}
if (board[y][x] == BLACK_MAN && y == 7) {
board[y][x] = BLACK_KING;
}
}
bool isCurrentPlayerPiece(int8_t piece) {
if (piece == EMPTY) return false;
if (whiteTurn && piece > 0) return true;
if (!whiteTurn && piece < 0) return true;
return false;
}
bool isOpponentPiece(int8_t piece) {
if (piece == EMPTY) return false;
if (whiteTurn && piece < 0) return true;
if (!whiteTurn && piece > 0) return true;
return false;
}
int signInt(int v) {
if (v > 0) return 1;
if (v < 0) return -1;
return 0;
}
// --------------------------------------------------
// AFFICHAGE
// --------------------------------------------------
void drawAll() {
tft.fillScreen(BLACK);
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
drawCell(x, y);
}
}
tft.drawRect(BOARD_X, BOARD_Y, BOARD_SIZE, BOARD_SIZE, WHITE);
drawPanel();
}
void drawCell(int x, int y) {
uint16_t color;
if ((x + y) % 2 == 0) {
color = LIGHTSQUARE;
} else {
color = DARKSQUARE;
}
int px = BOARD_X + x * CELL;
int py = BOARD_Y + y * CELL;
tft.fillRect(px, py, CELL, CELL, color);
drawPiece(x, y);
if (selectedX == x && selectedY == y) {
tft.drawRect(px + 2, py + 2, CELL - 4, CELL - 4, YELLOW);
tft.drawRect(px + 3, py + 3, CELL - 6, CELL - 6, YELLOW);
}
}
void drawPiece(int x, int y) {
int8_t piece = board[y][x];
if (piece == EMPTY) return;
int cx = BOARD_X + x * CELL + CELL / 2;
int cy = BOARD_Y + y * CELL + CELL / 2;
if (piece > 0) {
tft.fillCircle(cx, cy, 15, WHITE);
tft.drawCircle(cx, cy, 15, BLACK);
tft.drawCircle(cx, cy, 12, GREY);
} else {
tft.fillCircle(cx, cy, 15, BLACK);
tft.drawCircle(cx, cy, 15, WHITE);
tft.drawCircle(cx, cy, 12, GREY);
}
// Marquage des dames
if (abs(piece) == 2) {
tft.setTextSize(2);
if (piece > 0) {
tft.setTextColor(BLACK);
} else {
tft.setTextColor(WHITE);
}
tft.setCursor(cx - 6, cy - 8);
tft.print(F("D"));
}
}
void drawPanel() {
tft.fillRect(PANEL_X, 0, 150, 320, BLACK);
tft.setTextSize(2);
tft.setTextColor(WHITE);
tft.setCursor(PANEL_X + 10, 15);
tft.println(F("DAMES"));
tft.setTextSize(1);
tft.setCursor(PANEL_X + 12, 45);
tft.println(F("TFT 3.5 pouces"));
tft.drawLine(PANEL_X, 65, 479, 65, WHITE);
tft.setTextSize(2);
tft.setTextColor(WHITE);
tft.setCursor(PANEL_X + 10, 90);
tft.println(F("Tour:"));
tft.setCursor(PANEL_X + 10, 120);
if (whiteTurn) {
tft.setTextColor(WHITE);
tft.println(F("BLANC"));
} else {
tft.setTextColor(RED);
tft.println(F("NOIR"));
}
tft.setTextSize(1);
tft.setTextColor(WHITE);
tft.setCursor(PANEL_X + 10, 170);
tft.println(F("Pion: avance"));
tft.setCursor(PANEL_X + 10, 185);
tft.println(F("Prise avant/arriere"));
tft.setCursor(PANEL_X + 10, 210);
tft.println(F("Dame: diagonale"));
tft.setCursor(PANEL_X + 10, 225);
tft.println(F("sur plusieurs cases"));
// Bouton NEW
tft.fillRect(PANEL_X + 20, 260, 110, 45, BLUE);
tft.drawRect(PANEL_X + 20, 260, 110, 45, WHITE);
tft.setTextSize(2);
tft.setTextColor(WHITE);
tft.setCursor(PANEL_X + 52, 275);
tft.println(F("NEW"));
}
// --------------------------------------------------
// FIN DE PARTIE
// --------------------------------------------------
void checkWinner() {
int whiteCount = 0;
int blackCount = 0;
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
if (board[y][x] > 0) whiteCount++;
if (board[y][x] < 0) blackCount++;
}
}
if (whiteCount == 0) {
showWinner(false);
}
if (blackCount == 0) {
showWinner(true);
}
}
void showWinner(bool whiteWins) {
tft.fillRect(60, 110, 220, 100, BLUE);
tft.drawRect(60, 110, 220, 100, WHITE);
tft.setTextSize(3);
tft.setTextColor(WHITE);
tft.setCursor(95, 125);
tft.println(F("VICTOIRE"));
tft.setCursor(105, 165);
if (whiteWins) {
tft.println(F("BLANCS"));
} else {
tft.println(F("NOIRS"));
}
delay(2500);
newGame();
}
Le résultat attendu
Jeu de dames sur écran TFT 2.4 pouces Arduino
Ce code permet de réaliser un jeu de dames tactile à deux joueurs sur un écran TFT 2.4 pouces connecté à une carte Arduino. Malgré le format plus compact de l’écran, le plateau de jeu reste lisible et chaque joueur peut sélectionner ses pions, déplacer ses pièces et effectuer des prises directement avec le tactile.
Le programme gère les déplacements des pions, les prises, la promotion en dame ainsi que le déplacement des dames sur plusieurs cases. L’interface est adaptée à la taille de l’écran 2.4 pouces, avec un plateau optimisé, l’affichage du joueur en cours et un bouton tactile permettant de relancer une nouvelle partie.
/*
Site : www.atelierdelarobotique.fr
Date: 19/06/2026
Jeu de Dames
*/
#include
#include
#include
MCUFRIEND_kbv tft;
// Pins tactiles courants pour shield TFT 2.4" Arduino Uno
#define YP A1
#define XM A2
#define YM 7
#define XP 6
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
#define MINPRESSURE 10
#define MAXPRESSURE 1000
// Calibration tactile approximative
// À ajuster si le toucher est décalé
#define TS_MINX 199
#define TS_MAXX 922
#define TS_MINY 223
#define TS_MAXY 920
// Couleurs RGB565
#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define GREEN 0x07E0
#define BLUE 0x001F
#define YELLOW 0xFFE0
#define GREY 0x8410
#define DARKSQUARE 0x7BEF
#define LIGHTSQUARE 0xEF5D
// Écran en paysage : 320 x 240
#define BOARD_X 0
#define BOARD_Y 0
#define CELL 30
#define BOARD_SIZE 240
#define PANEL_X 245
// Pièces
#define EMPTY 0
#define WHITE_MAN 1
#define WHITE_KING 2
#define BLACK_MAN -1
#define BLACK_KING -2
int8_t board[8][8];
bool whiteTurn = true;
int selectedX = -1;
int selectedY = -1;
void newGame();
void drawAll();
void drawBoard();
void drawPieces();
void drawPiece(int x, int y);
void drawPanel();
void convertTouch(TSPoint p, int &x, int &y);
void handleTouch(int sx, int sy);
bool isCurrentPlayerPiece(int8_t piece);
bool isOpponentPiece(int8_t piece);
bool tryMove(int fromX, int fromY, int toX, int toY);
void movePiece(int fromX, int fromY, int toX, int toY);
void promoteIfNeeded(int x, int y);
void checkWinner();
void showWinner(bool whiteWins);
void setup() {
Serial.begin(9600);
uint16_t ID = tft.readID();
Serial.print(F("ID ecran : 0x"));
Serial.println(ID, HEX);
if (ID == 0xD3D3 || ID == 0xFFFF || ID == 0x0000) {
ID = 0x9341; // ILI9341 courant
}
tft.begin(ID);
tft.setRotation(1);
tft.fillScreen(BLACK);
newGame();
}
void loop() {
TSPoint p = ts.getPoint();
// Important : les pins tactiles sont partagées avec le TFT
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
int sx, sy;
convertTouch(p, sx, sy);
Serial.print(F("X="));
Serial.print(sx);
Serial.print(F(" Y="));
Serial.print(sy);
Serial.print(F(" P="));
Serial.println(p.z);
handleTouch(sx, sy);
delay(180);
}
}
void newGame() {
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
board[y][x] = EMPTY;
}
}
// Noirs en haut
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 8; x++) {
if ((x + y) % 2 == 1) {
board[y][x] = BLACK_MAN;
}
}
}
// Blancs en bas
for (int y = 5; y < 8; y++) {
for (int x = 0; x < 8; x++) {
if ((x + y) % 2 == 1) {
board[y][x] = WHITE_MAN;
}
}
}
whiteTurn = true;
selectedX = -1;
selectedY = -1;
drawAll();
}
void drawAll() {
tft.fillScreen(BLACK);
drawBoard();
drawPieces();
drawPanel();
}
void drawBoard() {
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
uint16_t color;
if ((x + y) % 2 == 0) {
color = LIGHTSQUARE;
} else {
color = DARKSQUARE;
}
tft.fillRect(BOARD_X + x * CELL, BOARD_Y + y * CELL, CELL, CELL, color);
}
}
tft.drawRect(BOARD_X, BOARD_Y, BOARD_SIZE, BOARD_SIZE, WHITE);
}
void drawPieces() {
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
drawPiece(x, y);
}
}
if (selectedX >= 0 && selectedY >= 0) {
int px = selectedX * CELL;
int py = selectedY * CELL;
tft.drawRect(px + 1, py + 1, CELL - 2, CELL - 2, YELLOW);
tft.drawRect(px + 2, py + 2, CELL - 4, CELL - 4, YELLOW);
}
}
void drawPiece(int x, int y) {
int8_t piece = board[y][x];
if (piece == EMPTY) return;
int cx = BOARD_X + x * CELL + CELL / 2;
int cy = BOARD_Y + y * CELL + CELL / 2;
if (piece > 0) {
// Pion blanc
tft.fillCircle(cx, cy, 11, WHITE);
tft.drawCircle(cx, cy, 11, BLACK);
} else {
// Pion noir
tft.fillCircle(cx, cy, 11, BLACK);
tft.drawCircle(cx, cy, 11, WHITE);
}
// Affichage des dames
if (abs(piece) == 2) {
tft.setTextSize(2);
if (piece > 0) {
tft.setTextColor(BLACK);
} else {
tft.setTextColor(WHITE);
}
tft.setCursor(cx - 6, cy - 8);
tft.print(F("D"));
}
}
void drawPanel() {
tft.fillRect(PANEL_X, 0, 75, 240, BLACK);
tft.setTextSize(1);
tft.setTextColor(WHITE);
tft.setCursor(PANEL_X + 5, 10);
tft.println(F("DAMES"));
tft.drawLine(PANEL_X, 30, 319, 30, WHITE);
tft.setCursor(PANEL_X + 5, 45);
tft.println(F("Tour:"));
tft.setTextSize(2);
tft.setCursor(PANEL_X + 5, 65);
if (whiteTurn) {
tft.setTextColor(WHITE);
tft.println(F("BLANC"));
} else {
tft.setTextColor(RED);
tft.println(F("NOIR"));
}
tft.setTextSize(1);
tft.setTextColor(WHITE);
tft.setCursor(PANEL_X + 5, 110);
tft.println(F("Toucher"));
tft.setCursor(PANEL_X + 5, 123);
tft.println(F("piece"));
tft.setCursor(PANEL_X + 5, 136);
tft.println(F("puis case"));
// Bouton nouvelle partie
tft.fillRect(PANEL_X + 5, 190, 65, 35, BLUE);
tft.drawRect(PANEL_X + 5, 190, 65, 35, WHITE);
tft.setTextSize(1);
tft.setTextColor(WHITE);
tft.setCursor(PANEL_X + 18, 203);
tft.println(F("NEW"));
}
/*void convertTouch(TSPoint p, int &x, int &y) {
x = map(p.y, TS_MINY, TS_MAXY, 0, tft.width());
y = map(p.x, TS_MINX, TS_MAXX, tft.height(), 0);
x = constrain(x, 0, tft.width() - 1);
y = constrain(y, 0, tft.height() - 1);
} */
void convertTouch(TSPoint p, int &x, int &y) {
x = map(p.y, TS_MINY, TS_MAXY, tft.width(), 0);
y = map(p.x, TS_MINX, TS_MAXX, tft.height(), 0);
x = constrain(x, 0, tft.width() - 1);
y = constrain(y, 0, tft.height() - 1);
}
void handleTouch(int sx, int sy) {
// Bouton NEW
if (sx >= PANEL_X + 5 && sx <= PANEL_X + 70 && sy >= 190 && sy <= 225) {
newGame();
return;
}
// Hors plateau
if (sx < BOARD_X || sx >= BOARD_X + BOARD_SIZE || sy < BOARD_Y || sy >= BOARD_Y + BOARD_SIZE) {
return;
}
int bx = (sx - BOARD_X) / CELL;
int by = (sy - BOARD_Y) / CELL;
if (bx < 0 || bx > 7 || by < 0 || by > 7) return;
int8_t touchedPiece = board[by][bx];
// Première sélection
if (selectedX == -1 || selectedY == -1) {
if (isCurrentPlayerPiece(touchedPiece)) {
selectedX = bx;
selectedY = by;
drawAll();
}
return;
}
// Changer de pièce sélectionnée
if (isCurrentPlayerPiece(touchedPiece)) {
selectedX = bx;
selectedY = by;
drawAll();
return;
}
// Tenter le déplacement
if (tryMove(selectedX, selectedY, bx, by)) {
selectedX = -1;
selectedY = -1;
whiteTurn = !whiteTurn;
drawAll();
checkWinner();
} else {
selectedX = -1;
selectedY = -1;
drawAll();
}
}
bool isCurrentPlayerPiece(int8_t piece) {
if (piece == EMPTY) return false;
if (whiteTurn && piece > 0) return true;
if (!whiteTurn && piece < 0) return true;
return false;
}
bool isOpponentPiece(int8_t piece) {
if (piece == EMPTY) return false;
if (whiteTurn && piece < 0) return true;
if (!whiteTurn && piece > 0) return true;
return false;
}
int signInt(int v) {
if (v > 0) return 1;
if (v < 0) return -1;
return 0;
}
bool tryMove(int fromX, int fromY, int toX, int toY) {
if (toX < 0 || toX > 7 || toY < 0 || toY > 7) return false;
if (board[toY][toX] != EMPTY) return false;
int8_t piece = board[fromY][fromX];
if (!isCurrentPlayerPiece(piece)) return false;
int dx = toX - fromX;
int dy = toY - fromY;
int absDx = abs(dx);
int absDy = abs(dy);
bool isKing = abs(piece) == 2;
// Les blancs montent, les noirs descendent
int forward = whiteTurn ? -1 : 1;
// -----------------------------------
// DEPLACEMENT DES DAMES
// -----------------------------------
if (isKing) {
// Une dame doit rester sur une diagonale
if (absDx != absDy) return false;
int stepX = signInt(dx);
int stepY = signInt(dy);
int x = fromX + stepX;
int y = fromY + stepY;
int opponentCount = 0;
int opponentX = -1;
int opponentY = -1;
// On analyse toutes les cases entre depart et arrivee
while (x != toX && y != toY) {
int8_t current = board[y][x];
if (current != EMPTY) {
// Si c'est une piece a nous, mouvement impossible
if (isCurrentPlayerPiece(current)) {
return false;
}
// Si c'est une piece adverse
if (isOpponentPiece(current)) {
opponentCount++;
opponentX = x;
opponentY = y;
// Une seule piece peut etre prise a la fois
if (opponentCount > 1) {
return false;
}
}
}
x += stepX;
y += stepY;
}
// Deplacement simple de plusieurs cases
if (opponentCount == 0) {
movePiece(fromX, fromY, toX, toY);
return true;
}
// Prise longue : une seule piece adverse sur la diagonale
if (opponentCount == 1) {
board[opponentY][opponentX] = EMPTY;
movePiece(fromX, fromY, toX, toY);
return true;
}
return false;
}
// -----------------------------------
// DEPLACEMENT DES PIONS SIMPLES
// -----------------------------------
// Deplacement simple
if (absDx == 1 && absDy == 1) {
if (dy == forward) {
movePiece(fromX, fromY, toX, toY);
promoteIfNeeded(toX, toY);
return true;
}
}
// Prise simple
/* if (absDx == 2 && absDy == 2) {
int midX = (fromX + toX) / 2;
int midY = (fromY + toY) / 2;
if (isOpponentPiece(board[midY][midX])) {
if (dy == 2 * forward) {
board[midY][midX] = EMPTY;
movePiece(fromX, fromY, toX, toY);
promoteIfNeeded(toX, toY);
return true;
}
}
} */
// Prise simple
if (absDx == 2 && absDy == 2) {
int midX = (fromX + toX) / 2;
int midY = (fromY + toY) / 2;
if (isOpponentPiece(board[midY][midX])) {
board[midY][midX] = EMPTY;
movePiece(fromX, fromY, toX, toY);
promoteIfNeeded(toX, toY);
return true;
}
}
return false;
}
/*bool tryMove(int fromX, int fromY, int toX, int toY) {
if (toX < 0 || toX > 7 || toY < 0 || toY > 7) return false;
if (board[toY][toX] != EMPTY) return false;
int8_t piece = board[fromY][fromX];
if (!isCurrentPlayerPiece(piece)) return false;
int dx = toX - fromX;
int dy = toY - fromY;
int absDx = abs(dx);
int absDy = abs(dy);
bool isKing = abs(piece) == 2;
// Blancs montent, noirs descendent
int forward = whiteTurn ? -1 : 1;
// Déplacement simple
if (absDx == 1 && absDy == 1) {
if (isKing || dy == forward) {
movePiece(fromX, fromY, toX, toY);
promoteIfNeeded(toX, toY);
return true;
}
}
// Prise simple
if (absDx == 2 && absDy == 2) {
int midX = (fromX + toX) / 2;
int midY = (fromY + toY) / 2;
if (isOpponentPiece(board[midY][midX])) {
if (isKing || dy == 2 * forward) {
board[midY][midX] = EMPTY;
movePiece(fromX, fromY, toX, toY);
promoteIfNeeded(toX, toY);
return true;
}
}
}
return false;
}*/
void movePiece(int fromX, int fromY, int toX, int toY) {
board[toY][toX] = board[fromY][fromX];
board[fromY][fromX] = EMPTY;
}
void promoteIfNeeded(int x, int y) {
if (board[y][x] == WHITE_MAN && y == 0) {
board[y][x] = WHITE_KING;
}
if (board[y][x] == BLACK_MAN && y == 7) {
board[y][x] = BLACK_KING;
}
}
void checkWinner() {
int whiteCount = 0;
int blackCount = 0;
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
if (board[y][x] > 0) whiteCount++;
if (board[y][x] < 0) blackCount++;
}
}
if (whiteCount == 0) {
showWinner(false);
}
if (blackCount == 0) {
showWinner(true);
}
}
void showWinner(bool whiteWins) {
tft.fillRect(20, 80, 200, 80, BLUE);
tft.drawRect(20, 80, 200, 80, WHITE);
tft.setTextSize(2);
tft.setTextColor(WHITE);
tft.setCursor(45, 95);
tft.println(F("VICTOIRE"));
tft.setCursor(55, 125);
if (whiteWins) {
tft.println(F("BLANCS"));
} else {
tft.println(F("NOIRS"));
}
delay(2500);
newGame();
}
Le résultat attendu
Le jeu du Morpion sur écran TFT 3.5 pouces Arduino
Dans ce code Arduino, nous recréons le célèbre jeu du Morpion sur un écran TFT tactile 3.5 pouces. Deux joueurs s’affrontent tour à tour en plaçant leur symbole, croix ou rond, dans une grille de 3 cases par 3.
L’objectif est simple : être le premier à aligner trois symboles identiques horizontalement, verticalement ou en diagonale. Le programme gère l’affichage de la grille, la détection des zones tactiles, le changement de joueur, la vérification automatique du gagnant ainsi que le match nul. Un bouton tactile permet également de relancer facilement une nouvelle partie :
/*
Site: atelierdelarobotique.fr
Date: 19/06/2026
MORPION
*/
#include
#include
#include
MCUFRIEND_kbv tft;
// Pins tactiles de ton écran TFT 3.5"
#define XP 8
#define XM A2
#define YP A3
#define YM 9
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
#define MINPRESSURE 10
#define MAXPRESSURE 1000
// Mets ici tes vraies valeurs de calibration
#define TOUCH_X_LEFT 916
#define TOUCH_X_RIGHT 227
#define TOUCH_Y_TOP 855
#define TOUCH_Y_BOTTOM 239
// Couleurs RGB565
#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define GREEN 0x07E0
#define BLUE 0x001F
#define YELLOW 0xFFE0
#define CYAN 0x07FF
#define GREY 0x8410
#define ORANGE 0xFD20
// Écran 480 x 320 en paysage
#define BOARD_X 10
#define BOARD_Y 10
#define CELL 100
#define BOARD_SIZE 300
#define PANEL_X 325
char board[3][3];
char currentPlayer = 'X';
bool gameOver = false;
void setup() {
Serial.begin(9600);
uint16_t ID = tft.readID();
Serial.print(F("ID ecran : 0x"));
Serial.println(ID, HEX);
if (ID == 0xD3D3 || ID == 0xFFFF || ID == 0x0000) {
ID = 0x9486; // fréquent sur TFT 3.5"
}
tft.begin(ID);
tft.setRotation(1); // paysage 480 x 320
tft.fillScreen(BLACK);
newGame();
}
void loop() {
TSPoint p = ts.getPoint();
// Important : les pins tactiles sont partagées avec l'écran
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
int sx, sy;
convertTouch(p, sx, sy);
Serial.print(F("Touch X="));
Serial.print(sx);
Serial.print(F(" Y="));
Serial.print(sy);
Serial.print(F(" P="));
Serial.println(p.z);
handleTouch(sx, sy);
delay(250);
}
}
// --------------------------------------------------
// NOUVELLE PARTIE
// --------------------------------------------------
void newGame() {
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
board[y][x] = ' ';
}
}
currentPlayer = 'X';
gameOver = false;
drawAll();
}
// --------------------------------------------------
// AFFICHAGE
// --------------------------------------------------
void drawAll() {
tft.fillScreen(BLACK);
drawGrid();
drawPanel();
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
drawCell(x, y);
}
}
}
void drawGrid() {
tft.drawRect(BOARD_X, BOARD_Y, BOARD_SIZE, BOARD_SIZE, WHITE);
// Lignes verticales
tft.drawLine(BOARD_X + CELL, BOARD_Y, BOARD_X + CELL, BOARD_Y + BOARD_SIZE, WHITE);
tft.drawLine(BOARD_X + CELL * 2, BOARD_Y, BOARD_X + CELL * 2, BOARD_Y + BOARD_SIZE, WHITE);
// Lignes horizontales
tft.drawLine(BOARD_X, BOARD_Y + CELL, BOARD_X + BOARD_SIZE, BOARD_Y + CELL, WHITE);
tft.drawLine(BOARD_X, BOARD_Y + CELL * 2, BOARD_X + BOARD_SIZE, BOARD_Y + CELL * 2, WHITE);
}
void drawCell(int x, int y) {
int px = BOARD_X + x * CELL;
int py = BOARD_Y + y * CELL;
// Efface la case sans effacer les lignes
tft.fillRect(px + 2, py + 2, CELL - 4, CELL - 4, BLACK);
if (board[y][x] == 'X') {
drawX(px, py);
} else if (board[y][x] == 'O') {
drawO(px, py);
}
}
void drawX(int px, int py) {
int margin = 22;
for (int i = -2; i <= 2; i++) {
tft.drawLine(px + margin, py + margin + i,
px + CELL - margin, py + CELL - margin + i, RED);
tft.drawLine(px + CELL - margin, py + margin + i,
px + margin, py + CELL - margin + i, RED);
}
}
void drawO(int px, int py) {
int cx = px + CELL / 2;
int cy = py + CELL / 2;
for (int r = 30; r <= 34; r++) {
tft.drawCircle(cx, cy, r, CYAN);
}
}
void drawPanel() {
tft.fillRect(PANEL_X, 0, 155, 320, BLACK);
tft.setTextColor(WHITE);
tft.setTextSize(2);
tft.setCursor(PANEL_X + 10, 15);
tft.println(F("MORPION"));
tft.setTextSize(1);
tft.setCursor(PANEL_X + 12, 45);
tft.println(F("TFT 3.5 pouces"));
tft.drawLine(PANEL_X, 65, 479, 65, WHITE);
tft.setTextSize(2);
tft.setTextColor(WHITE);
tft.setCursor(PANEL_X + 10, 90);
tft.println(F("Tour:"));
tft.setTextSize(4);
tft.setCursor(PANEL_X + 55, 125);
if (currentPlayer == 'X') {
tft.setTextColor(RED);
tft.println(F("X"));
} else {
tft.setTextColor(CYAN);
tft.println(F("O"));
}
// Bouton nouvelle partie
tft.fillRect(PANEL_X + 20, 250, 110, 50, BLUE);
tft.drawRect(PANEL_X + 20, 250, 110, 50, WHITE);
tft.setTextSize(2);
tft.setTextColor(WHITE);
tft.setCursor(PANEL_X + 55, 267);
tft.println(F("NEW"));
}
void showMessage(const char *line1, const char *line2, uint16_t color) {
tft.fillRect(PANEL_X, 70, 155, 170, BLACK);
tft.setTextColor(color);
tft.setTextSize(2);
tft.setCursor(PANEL_X + 10, 95);
tft.println(line1);
tft.setCursor(PANEL_X + 10, 125);
tft.println(line2);
tft.setTextColor(WHITE);
tft.setTextSize(1);
tft.setCursor(PANEL_X + 10, 185);
tft.println(F("Appuyez sur NEW"));
}
// --------------------------------------------------
// TACTILE
// --------------------------------------------------
void handleTouch(int sx, int sy) {
// Bouton NEW
if (sx >= PANEL_X + 20 && sx <= PANEL_X + 130 && sy >= 250 && sy <= 300) {
newGame();
return;
}
if (gameOver) {
return;
}
// Hors grille
if (sx < BOARD_X || sx >= BOARD_X + BOARD_SIZE || sy < BOARD_Y || sy >= BOARD_Y + BOARD_SIZE) {
return;
}
int bx = (sx - BOARD_X) / CELL;
int by = (sy - BOARD_Y) / CELL;
if (bx < 0 || bx > 2 || by < 0 || by > 2) {
return;
}
if (board[by][bx] != ' ') {
return;
}
board[by][bx] = currentPlayer;
drawCell(bx, by);
char winner = checkWinner();
if (winner == 'X' || winner == 'O') {
gameOver = true;
if (winner == 'X') {
showMessage("VICTOIRE", "JOUEUR X", RED);
} else {
showMessage("VICTOIRE", "JOUEUR O", CYAN);
}
drawWinningLine(winner);
return;
}
if (isBoardFull()) {
gameOver = true;
showMessage("MATCH", "NUL", YELLOW);
return;
}
// Changement de joueur
if (currentPlayer == 'X') {
currentPlayer = 'O';
} else {
currentPlayer = 'X';
}
drawPanel();
}
// Fonction avec tactile inversé + calibration
void convertTouch(TSPoint p, int &x, int &y) {
x = map(p.y, TOUCH_X_LEFT, TOUCH_X_RIGHT, 30, tft.width() - 30);
y = map(p.x, TOUCH_Y_TOP, TOUCH_Y_BOTTOM, 30, tft.height() - 30);
x = constrain(x, 0, tft.width() - 1);
y = constrain(y, 0, tft.height() - 1);
}
// --------------------------------------------------
// LOGIQUE DU JEU
// --------------------------------------------------
char checkWinner() {
// Lignes
for (int y = 0; y < 3; y++) {
if (board[y][0] != ' ' && board[y][0] == board[y][1] && board[y][1] == board[y][2]) {
return board[y][0];
}
}
// Colonnes
for (int x = 0; x < 3; x++) {
if (board[0][x] != ' ' && board[0][x] == board[1][x] && board[1][x] == board[2][x]) {
return board[0][x];
}
}
// Diagonales
if (board[0][0] != ' ' && board[0][0] == board[1][1] && board[1][1] == board[2][2]) {
return board[0][0];
}
if (board[0][2] != ' ' && board[0][2] == board[1][1] && board[1][1] == board[2][0]) {
return board[0][2];
}
return ' ';
}
bool isBoardFull() {
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
if (board[y][x] == ' ') {
return false;
}
}
}
return true;
}
void drawWinningLine(char winner) {
uint16_t color;
if (winner == 'X') {
color = YELLOW;
} else {
color = GREEN;
}
// Lignes
for (int y = 0; y < 3; y++) {
if (board[y][0] == winner && board[y][1] == winner && board[y][2] == winner) {
int cy = BOARD_Y + y * CELL + CELL / 2;
tft.drawLine(BOARD_X + 15, cy, BOARD_X + BOARD_SIZE - 15, cy, color);
tft.drawLine(BOARD_X + 15, cy + 1, BOARD_X + BOARD_SIZE - 15, cy + 1, color);
return;
}
}
// Colonnes
for (int x = 0; x < 3; x++) {
if (board[0][x] == winner && board[1][x] == winner && board[2][x] == winner) {
int cx = BOARD_X + x * CELL + CELL / 2;
tft.drawLine(cx, BOARD_Y + 15, cx, BOARD_Y + BOARD_SIZE - 15, color);
tft.drawLine(cx + 1, BOARD_Y + 15, cx + 1, BOARD_Y + BOARD_SIZE - 15, color);
return;
}
}
// Diagonale haut gauche -> bas droite
if (board[0][0] == winner && board[1][1] == winner && board[2][2] == winner) {
tft.drawLine(BOARD_X + 20, BOARD_Y + 20,
BOARD_X + BOARD_SIZE - 20, BOARD_Y + BOARD_SIZE - 20, color);
tft.drawLine(BOARD_X + 21, BOARD_Y + 20,
BOARD_X + BOARD_SIZE - 19, BOARD_Y + BOARD_SIZE - 20, color);
return;
}
// Diagonale haut droite -> bas gauche
if (board[0][2] == winner && board[1][1] == winner && board[2][0] == winner) {
tft.drawLine(BOARD_X + BOARD_SIZE - 20, BOARD_Y + 20,
BOARD_X + 20, BOARD_Y + BOARD_SIZE - 20, color);
tft.drawLine(BOARD_X + BOARD_SIZE - 21, BOARD_Y + 20,
BOARD_X + 19, BOARD_Y + BOARD_SIZE - 20, color);
return;
}
}
Le résultat attendu
Le démineur sur écran TFT 3.5 pouces Arduino
Dans ce code Arduino, nous recréons le célèbre jeu du Démineur sur un écran TFT tactile 3.5 pouces. Le joueur doit ouvrir les cases de la grille tout en évitant les mines cachées, en s’aidant des nombres affichés qui indiquent combien de mines se trouvent autour de chaque case.
Le programme gère l’affichage de la grille, la génération aléatoire des mines, le premier clic sécurisé, l’ouverture automatique des zones vides, l’affichage des numéros, la pose de drapeaux, ainsi que la détection de la victoire ou de la défaite. Des boutons tactiles permettent de changer de mode entre ouvrir une case et placer un drapeau, mais aussi de relancer une nouvelle partie facilement :
/*
Site : www.atelierdelarobotique.fr
Date: 19/06/2026
*/
#include
#include
#include
MCUFRIEND_kbv tft;
// Pins tactiles de l'écran TFT 3.5"
#define XP 8
#define XM A2
#define YP A3
#define YM 9
TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);
#define MINPRESSURE 10
#define MAXPRESSURE 1000
// Calibration écran tactile
#define TOUCH_X_LEFT 916
#define TOUCH_X_RIGHT 227
#define TOUCH_Y_TOP 855
#define TOUCH_Y_BOTTOM 239
// Couleurs RGB565
#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define GREEN 0x07E0
#define BLUE 0x001F
#define YELLOW 0xFFE0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define ORANGE 0xFD20
#define GREY 0x8410
#define DARKGREY 0x4208
#define LIGHTGREY 0xC618
// Écran 480 x 320 en paysage
#define BOARD_X 0
#define BOARD_Y 0
#define CELL 32
#define COLS 10
#define ROWS 10
#define BOARD_SIZE 320
#define PANEL_X 330
#define MINE_COUNT 15
// États des cases
#define COVERED 0
#define REVEALED 1
#define FLAGGED 2
// -1 = mine, 0 à 8 = nombre de mines autour
int8_t field[ROWS][COLS];
uint8_t state[ROWS][COLS];
bool minesPlaced = false;
bool gameOver = false;
bool flagMode = false;
int explodedX = -1;
int explodedY = -1;
void setup() {
Serial.begin(9600);
uint16_t ID = tft.readID();
Serial.print(F("ID ecran : 0x"));
Serial.println(ID, HEX);
if (ID == 0xD3D3 || ID == 0xFFFF || ID == 0x0000) {
ID = 0x9486;
}
tft.begin(ID);
tft.setRotation(1);
tft.fillScreen(BLACK);
randomSeed(analogRead(A5) + millis());
newGame();
}
void loop() {
TSPoint p = ts.getPoint();
// Important : les pins tactiles sont partagées avec l'écran
pinMode(XM, OUTPUT);
pinMode(YP, OUTPUT);
if (p.z > MINPRESSURE && p.z < MAXPRESSURE) {
int sx, sy;
convertTouch(p, sx, sy);
Serial.print(F("Touch X="));
Serial.print(sx);
Serial.print(F(" Y="));
Serial.print(sy);
Serial.print(F(" P="));
Serial.println(p.z);
handleTouch(sx, sy);
delay(250);
}
}
// --------------------------------------------------
// NOUVELLE PARTIE
// --------------------------------------------------
void newGame() {
for (int y = 0; y < ROWS; y++) {
for (int x = 0; x < COLS; x++) {
field[y][x] = 0;
state[y][x] = COVERED;
}
}
minesPlaced = false;
gameOver = false;
flagMode = false;
explodedX = -1;
explodedY = -1;
drawAll();
}
// --------------------------------------------------
// AFFICHAGE
// --------------------------------------------------
void drawAll() {
tft.fillScreen(BLACK);
for (int y = 0; y < ROWS; y++) {
for (int x = 0; x < COLS; x++) {
drawCell(x, y);
}
}
tft.drawRect(BOARD_X, BOARD_Y, BOARD_SIZE, BOARD_SIZE, WHITE);
drawPanel();
}
void drawCell(int x, int y) {
int px = BOARD_X + x * CELL;
int py = BOARD_Y + y * CELL;
if (state[y][x] == COVERED || state[y][x] == FLAGGED) {
tft.fillRect(px, py, CELL, CELL, BLUE);
tft.drawRect(px, py, CELL, CELL, WHITE);
tft.drawRect(px + 2, py + 2, CELL - 4, CELL - 4, DARKGREY);
if (state[y][x] == FLAGGED) {
drawFlag(px, py);
}
return;
}
// Case révélée
tft.fillRect(px, py, CELL, CELL, LIGHTGREY);
tft.drawRect(px, py, CELL, CELL, GREY);
if (field[y][x] == -1) {
drawMine(px, py, x, y);
return;
}
if (field[y][x] > 0) {
drawNumber(px, py, field[y][x]);
}
}
void drawFlag(int px, int py) {
int poleX = px + 11;
int poleY = py + 8;
tft.drawLine(poleX, poleY, poleX, py + 24, WHITE);
tft.fillTriangle(poleX + 1, poleY, poleX + 1, poleY + 12, px + 24, poleY + 6, RED);
tft.drawLine(px + 8, py + 25, px + 24, py + 25, WHITE);
}
void drawMine(int px, int py, int x, int y) {
uint16_t bg = LIGHTGREY;
if (x == explodedX && y == explodedY) {
bg = RED;
tft.fillRect(px, py, CELL, CELL, RED);
}
int cx = px + CELL / 2;
int cy = py + CELL / 2;
tft.fillCircle(cx, cy, 9, BLACK);
tft.drawLine(cx - 12, cy, cx + 12, cy, BLACK);
tft.drawLine(cx, cy - 12, cx, cy + 12, BLACK);
tft.drawLine(cx - 8, cy - 8, cx + 8, cy + 8, BLACK);
tft.drawLine(cx + 8, cy - 8, cx - 8, cy + 8, BLACK);
tft.fillCircle(cx - 3, cy - 3, 2, WHITE);
}
void drawNumber(int px, int py, int n) {
uint16_t color = getNumberColor(n);
tft.setTextSize(2);
tft.setTextColor(color);
tft.setCursor(px + 11, py + 8);
tft.print(n);
}
uint16_t getNumberColor(int n) {
switch (n) {
case 1: return BLUE;
case 2: return GREEN;
case 3: return RED;
case 4: return MAGENTA;
case 5: return ORANGE;
case 6: return CYAN;
case 7: return BLACK;
case 8: return DARKGREY;
}
return BLACK;
}
void drawPanel() {
tft.fillRect(PANEL_X, 0, 150, 320, BLACK);
tft.setTextColor(WHITE);
tft.setTextSize(2);
tft.setCursor(PANEL_X + 8, 15);
tft.println(F("DEMINEUR"));
tft.setTextSize(1);
tft.setCursor(PANEL_X + 10, 45);
tft.println(F("TFT 3.5 pouces"));
tft.drawLine(PANEL_X, 65, 479, 65, WHITE);
tft.setTextSize(2);
tft.setCursor(PANEL_X + 10, 85);
tft.print(F("Mines:"));
tft.setCursor(PANEL_X + 95, 85);
tft.print(MINE_COUNT);
tft.setCursor(PANEL_X + 10, 115);
tft.print(F("Flags:"));
tft.setCursor(PANEL_X + 95, 115);
tft.print(countFlags());
tft.setTextSize(1);
tft.setCursor(PANEL_X + 10, 150);
tft.println(F("Mode actuel:"));
tft.setTextSize(2);
tft.setCursor(PANEL_X + 10, 170);
if (flagMode) {
tft.setTextColor(RED);
tft.println(F("FLAG"));
} else {
tft.setTextColor(GREEN);
tft.println(F("OPEN"));
}
// Bouton MODE
tft.fillRect(PANEL_X + 15, 205, 120, 40, ORANGE);
tft.drawRect(PANEL_X + 15, 205, 120, 40, WHITE);
tft.setTextColor(BLACK);
tft.setTextSize(2);
tft.setCursor(PANEL_X + 45, 217);
tft.println(F("MODE"));
// Bouton NEW
tft.fillRect(PANEL_X + 15, 265, 120, 40, BLUE);
tft.drawRect(PANEL_X + 15, 265, 120, 40, WHITE);
tft.setTextColor(WHITE);
tft.setTextSize(2);
tft.setCursor(PANEL_X + 55, 277);
tft.println(F("NEW"));
}
void showMessage(const char *line1, const char *line2, uint16_t color) {
tft.fillRect(PANEL_X, 70, 150, 125, BLACK);
tft.setTextColor(color);
tft.setTextSize(2);
tft.setCursor(PANEL_X + 10, 90);
tft.println(line1);
tft.setCursor(PANEL_X + 10, 120);
tft.println(line2);
tft.setTextColor(WHITE);
tft.setTextSize(1);
tft.setCursor(PANEL_X + 10, 160);
tft.println(F("Appuyez sur NEW"));
}
// --------------------------------------------------
// TACTILE
// --------------------------------------------------
void convertTouch(TSPoint p, int &x, int &y) {
// Fonction basée sur ta calibration 3.5" inversée
x = map(p.y, TOUCH_X_LEFT, TOUCH_X_RIGHT, 30, tft.width() - 30);
y = map(p.x, TOUCH_Y_TOP, TOUCH_Y_BOTTOM, 30, tft.height() - 30);
x = constrain(x, 0, tft.width() - 1);
y = constrain(y, 0, tft.height() - 1);
}
void handleTouch(int sx, int sy) {
// Bouton MODE
if (sx >= PANEL_X + 15 && sx <= PANEL_X + 135 && sy >= 205 && sy <= 245) {
if (!gameOver) {
flagMode = !flagMode;
drawPanel();
}
return;
}
// Bouton NEW
if (sx >= PANEL_X + 15 && sx <= PANEL_X + 135 && sy >= 265 && sy <= 305) {
newGame();
return;
}
if (gameOver) {
return;
}
// Hors plateau
if (sx < BOARD_X || sx >= BOARD_X + BOARD_SIZE || sy < BOARD_Y || sy >= BOARD_Y + BOARD_SIZE) {
return;
}
int bx = (sx - BOARD_X) / CELL;
int by = (sy - BOARD_Y) / CELL;
if (!inBounds(bx, by)) {
return;
}
if (flagMode) {
toggleFlag(bx, by);
} else {
openCell(bx, by);
}
}
// --------------------------------------------------
// LOGIQUE DU JEU
// --------------------------------------------------
void toggleFlag(int x, int y) {
if (state[y][x] == REVEALED) {
return;
}
if (state[y][x] == COVERED) {
state[y][x] = FLAGGED;
} else if (state[y][x] == FLAGGED) {
state[y][x] = COVERED;
}
drawCell(x, y);
drawPanel();
}
void openCell(int x, int y) {
if (state[y][x] == FLAGGED || state[y][x] == REVEALED) {
return;
}
// Premier clic sécurisé
if (!minesPlaced) {
placeMines(x, y);
calculateNumbers();
minesPlaced = true;
}
if (field[y][x] == -1) {
explodedX = x;
explodedY = y;
gameOver = true;
revealAllMines();
showMessage("PERDU", "BOOM !", RED);
return;
}
if (field[y][x] == 0) {
floodReveal(x, y);
} else {
state[y][x] = REVEALED;
drawCell(x, y);
}
if (checkWin()) {
gameOver = true;
flagAllMines();
showMessage("VICTOIRE", "BRAVO !", GREEN);
}
}
void placeMines(int safeX, int safeY) {
int placed = 0;
while (placed < MINE_COUNT) {
int x = random(0, COLS);
int y = random(0, ROWS);
if (field[y][x] == -1) {
continue;
}
// On évite la première case et les cases autour
if (abs(x - safeX) <= 1 && abs(y - safeY) <= 1) {
continue;
}
field[y][x] = -1;
placed++;
}
}
void calculateNumbers() {
for (int y = 0; y < ROWS; y++) {
for (int x = 0; x < COLS; x++) {
if (field[y][x] == -1) {
continue;
}
int count = 0;
for (int yy = -1; yy <= 1; yy++) {
for (int xx = -1; xx <= 1; xx++) {
if (xx == 0 && yy == 0) {
continue;
}
int nx = x + xx;
int ny = y + yy;
if (inBounds(nx, ny) && field[ny][nx] == -1) {
count++;
}
}
}
field[y][x] = count;
}
}
}
void floodReveal(int startX, int startY) {
uint8_t qx[ROWS * COLS];
uint8_t qy[ROWS * COLS];
int head = 0;
int tail = 0;
state[startY][startX] = REVEALED;
qx[tail] = startX;
qy[tail] = startY;
tail++;
while (head < tail) {
int x = qx[head];
int y = qy[head];
head++;
drawCell(x, y);
if (field[y][x] != 0) {
continue;
}
for (int yy = -1; yy <= 1; yy++) {
for (int xx = -1; xx <= 1; xx++) {
int nx = x + xx;
int ny = y + yy;
if (!inBounds(nx, ny)) {
continue;
}
if (state[ny][nx] != COVERED) {
continue;
}
if (field[ny][nx] == -1) {
continue;
}
state[ny][nx] = REVEALED;
if (tail < ROWS * COLS) {
qx[tail] = nx;
qy[tail] = ny;
tail++;
}
}
}
}
}
void revealAllMines() {
for (int y = 0; y < ROWS; y++) {
for (int x = 0; x < COLS; x++) {
if (field[y][x] == -1) {
state[y][x] = REVEALED;
drawCell(x, y);
}
}
}
}
void flagAllMines() {
for (int y = 0; y < ROWS; y++) {
for (int x = 0; x < COLS; x++) {
if (field[y][x] == -1) {
state[y][x] = FLAGGED;
drawCell(x, y);
}
}
}
}
bool checkWin() {
int revealedCount = 0;
int totalSafe = ROWS * COLS - MINE_COUNT;
for (int y = 0; y < ROWS; y++) {
for (int x = 0; x < COLS; x++) {
if (field[y][x] != -1 && state[y][x] == REVEALED) {
revealedCount++;
}
}
}
return revealedCount == totalSafe;
}
int countFlags() {
int count = 0;
for (int y = 0; y < ROWS; y++) {
for (int x = 0; x < COLS; x++) {
if (state[y][x] == FLAGGED) {
count++;
}
}
}
return count;
}
bool inBounds(int x, int y) {
return x >= 0 && x < COLS && y >= 0 && y < ROWS;
}
Le résultat attendu
Conclusion
Ces différents exemples montrent que les écrans TFT tactiles 2.4 et 3.5 pouces ne servent pas uniquement à afficher du texte ou quelques valeurs de capteurs. Ils peuvent devenir de véritables interfaces de commande, de jeu ou de visualisation pour vos montages Arduino.
À partir des bases présentées dans cet article, il est possible d’imaginer de nombreux autres projets : une station météo tactile, un tableau de bord pour capteurs, une interface de contrôle pour robot, un menu domotique, un lecteur d’images, un quiz interactif, un puissance 4, un memory, un casse-briques, un petit terminal de contrôle ou encore une interface de supervision pour un système électronique. Avec un peu de créativité, ces écrans deviennent un excellent support pour rendre vos projets Arduino plus visuels, plus pratiques et plus agréables à utiliser.








