Por: Julio Cesar Sandria Reynoso, David Rojas Bosada y Fernando Méndez Reyes
4 de octubre de 2026
Desde hace unos meses, nuestro alumno del Instituto de Robótica de Xalapa (iRoX), David, nos trajo el reto de hacer funcionar unas pantallas de 2.8 pulgadas TFT con chip ILI9341 para su proyecto del robot BB8. Nuestro alumno Fernando, llevó a cabo las primeras investigaciones, la hizo funcionar y le proporcionó a David el esquema para hacer una placa PCB muy sencilla. En las últimas semanas, llegamos al punto de ya requerir instalar las pantallas en la unidad BB8 y retomamos el tema de las pantallas.
En este artículo publicamos lo necesario para replicar el inicio del uso de la pantalla 2.8″ TFT 240xRGBx320 V1.1 con circuito integrado ILI9341 en PCB roja, con Arduino Nano (genérico) programado en Arduino IDE 2.3.10.


En una placa de prototipos PCB se soldaron los componentes para colocar Arduino Nano y la Pantalla 2.8 TFT, haciendo las divisiones de voltaje de 5V a 3.3V con resistencias de 2.5 y 5 Kohms.

Como se menciona al principio, empezamos a trabajar con esta pantalla algunos meses atrás (4 ó 5), por lo que al retomar el proyecto, usando la placa PCB ya soldada, y colocándole el Arduino Nano y la pantalla, los ejemplos de código en C++ en Arduino IDE que sabíamos que ya habían funcionado, pues no funcionaban. Dado que ya no teníamos a la mano el diagrama original, rehicimos el esquema electrónico para analizar bien las conexiones entre Arduino Nano y la Pantalla.

NOTA: Para probar los programas de ejemplo de Adafruit hay que instalar en Arduino IDE las librerías Adafruit ILI9341 y Adafruit GFX.
Una vez analizado cómo se había soldado la placa PCB y algunos ejemplos de programas disponibles de Adafruit para una pantalla 2.8 TFT con chip ILI9341, llegamos al siguiente programa para mostrar un «Hola iRoX» en la pantalla.
/*
Instituto de Robótica de Xalapa - 4-Octubre-2026
Programa: Pantalla-2.8-TFT_Hola_iRoX.ino (C++)
Basado en: Simple "Hello World" for ILI9341 LCD
https://wokwi.com/arduino/projects/308024602434470466
y complementado con ejemplos de Adafruit ILI9341
Autor: Julio César Sandria Reynoso
Colaboradores: David Rojas Bosada, Fernando Méndez Reyes
Hardware:
+ Pantalla 2.8" TFT 240xRGBx320 V1.1 (PCB Roja) chip ILI9341
+ Arduino NANO
+ PCB propio con divisores de voltaje de 5V a 3.3V
*/
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
// Asegura las siguientes conexiones al Arduino NANO:
// Pines 8 a 13 con divisores de voltaje de 5V a 3.3V
// NOTA: las macros (#define) tienen los nombres de pin
// de la pantalla y pueden variar respecto a los
// ejemplo originales de ADAFRUIT.
// PIN TFT VCC 5V Arduino
// PIN TFT GND GND Arduino
#define TFT_RESET 8
#define TFT_DC 9
#define TFT_CS 10
#define TFT_SDI 11
#define TFT_LED 12
#define TFT_SCK 13
Adafruit_ILI9341 tft = Adafruit_ILI9341(
TFT_CS, TFT_DC, TFT_SDI, TFT_SCK, TFT_RESET, TFT_LED);
void setup() {
tft.begin();
}
void loop(void) {
for(uint8_t rotacion=0; rotacion<4; rotacion++) {
tft.setRotation(rotacion);
texto(rotacion);
delay(1000);
}
}
void texto(uint8_t rotacion) {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(10, 20);
tft.setTextColor(ILI9341_RED);
tft.setTextSize(3);
tft.println("Hola iRoX!");
tft.setCursor(10, 60);
tft.setTextColor(ILI9341_GREEN);
tft.setTextSize(2);
tft.println("Julio Sandria");
tft.setCursor(10, 100);
tft.setTextColor(ILI9341_YELLOW);
tft.setTextSize(1);
tft.print("rotacion: ");
tft.println(rotacion);
}
Este programa ejecuta un ciclo for(…) dentro de la función loop(), imprimiendo el texto de la función texto(…) en cuatro orientaciones de la pantalla, que seguimos llamando rotaciones para conservar la terminología que usa la librería Adafruit ILI9341, con el método setRotation() del objeto tft.




Y ya habiendo dominado las conexiones y algunos programas ejemplos de Adafruit ILI9341, nos pusimos a jugar un rato haciendo programas con ayuda de una inteligencia artificial generativa, cargando el programa al Arduino Nano y obteniendo algunas imágenes como la siguiente:

El programa funciona como un radar simulando que detecta objetos y mostrándolos como círculos rojos. A continuación está el programa que trabajamos Deepseek, David Rojas y Julio Sandria.
/*
Instituto de Robótica de Xalapa - 4-Octubre-2026
Programa: BB8_pant-2.8_radar_horizontal.ino (C++)
Desarrollado por Julio César Sandria Reynoso, David Rojas Bosada y DeepSeek.
Basado en: Pantalla-2.8-TFT_Hola_iRoX.ino
Hardware:
+ Pantalla 2.8" TFT 240xRGBx320 V1.1 (PCB Roja) chip ILI9341
+ Arduino NANO
+ PCB propio con divisores de voltaje de 5V a 3.3V
*/
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
// Asegura las siguientes conexiones al Arduino NANO:
// Pines 8 a 13 con divisores de voltaje de 5V a 3.3V
// NOTA: las macros (#define) tienen los nombres de pin
// de la pantalla y pueden variar respecto a los
// ejemplo originales de ADAFRUIT.
// PIN TFT VCC 5V Arduino
// PIN TFT GND GND Arduino
#define TFT_RESET 8
#define TFT_DC 9
#define TFT_CS 10
#define TFT_SDI 11
#define TFT_LED 12
#define TFT_SCK 13
Adafruit_ILI9341 tft = Adafruit_ILI9341(
TFT_CS, TFT_DC, TFT_SDI, TFT_SCK, TFT_RESET, TFT_LED);
// ---- Colores ----
#define RADAR_BG 0x0000 // Negro
#define RADAR_GRID 0x0320 // Verde oscuro
#define RADAR_RING 0x0640 // Verde medio
#define RADAR_TEXT 0x07E0 // Verde brillante
#define RADAR_BLIP 0xF800 // Rojo
#define RADAR_BLIP2 0xFFE0 // Amarillo
#define PANEL_BG 0x0000 // Fondo del panel
#define PANEL_LINE 0x0640 // Línea separadora (verde)
#define PANEL_WHITE 0xFFFF // Blanco
#define HUD_COLOR 0x07E0 // Verde brillante para el HUD
// ---- Layout horizontal (320x240) ----
const int16_t SCREEN_W = 320;
const int16_t SCREEN_H = 240;
const int16_t PANEL_W = 70;
const int16_t RADAR_AREA_X0 = PANEL_W;
const int16_t RADAR_AREA_W = SCREEN_W - PANEL_W; // 250 px
// Centro del radar dentro del área disponible
const int16_t CX = RADAR_AREA_X0 + RADAR_AREA_W / 2; // ≈195
const int16_t CY = SCREEN_H / 2 + 6; // ≈126
const int16_t R = 95;
// ---- Barrido ----
int16_t angle = 0;
const int16_t SWEEP_STEP = 3;
const int16_t TRAIL = 20;
// ---- Objetivos ----
struct Target {
int16_t angle;
int16_t dist;
uint16_t color;
};
Target targets[] = {
{ 35, 85, RADAR_BLIP },
{ 120, 55, RADAR_BLIP2 },
{ 200, 70, RADAR_BLIP },
{ 275, 40, RADAR_BLIP2 },
{ 320, 90, RADAR_BLIP },
};
const uint8_t NUM_TARGETS = sizeof(targets) / sizeof(targets[0]);
int16_t targetLastHit[NUM_TARGETS];
// ---- HUD ----
int16_t lastHudAngle = -1; // Para no redibujar si no cambia
// ============================================================
// Utilidades
// ============================================================
float deg2rad(int16_t deg) { return deg * PI / 180.0; }
int16_t polarX(int16_t deg, int16_t radius) {
return CX + (int16_t)(cos(deg2rad(deg)) * radius);
}
int16_t polarY(int16_t deg, int16_t radius) {
return CY + (int16_t)(sin(deg2rad(deg)) * radius);
}
// Devuelve el ancho en píxeles de una cadena con el tamaño de texto actual.
// Útil para alinear a la derecha sin depender del font.
int16_t textWidthPx(const char* s, uint8_t size) {
return strlen(s) * 6 * size; // fuente estándar Adafruit: 6x8 px por carácter
}
// ============================================================
// Panel lateral izquierdo
// ============================================================
void drawSidePanel() {
// Fondo del panel
tft.fillRect(0, 0, PANEL_W, SCREEN_H, PANEL_BG);
// Línea separadora vertical
tft.drawFastVLine(PANEL_W - 1, 0, SCREEN_H, PANEL_LINE);
// Encabezado
tft.setTextSize(1);
tft.setTextColor(RADAR_TEXT);
tft.setCursor(6, 6);
tft.print("== iRoX ==");
tft.drawFastHLine(4, 18, PANEL_W - 8, PANEL_LINE);
// Etiquetas con línea en blanco entre "Rojas" e "iRoX"
struct Row { const char* text; uint16_t color; uint8_t gap; };
Row rows[] = {
{ "BB8", RADAR_TEXT, 0 },
{ "David", RADAR_TEXT, 0 },
{ "Rojas", RADAR_TEXT, 6 }, // <-- espacio extra (línea en blanco) después
{ "iRoX", RADAR_TEXT, 0 },
{ "Julio", RADAR_TEXT, 0 },
{ "Sandria", RADAR_TEXT, 0 },
};
const uint8_t n = sizeof(rows) / sizeof(rows[0]);
int16_t y = 28;
for (uint8_t i = 0; i < n; i++) {
tft.setTextColor(rows[i].color);
tft.setTextSize(1);
tft.setCursor(6, y);
tft.print(rows[i].text);
y += 14 + rows[i].gap;
}
// ---- Después de "Sandria": línea separadora + fecha ----
y += 2; // pequeño respiro
tft.drawFastHLine(4, y, PANEL_W - 8, PANEL_LINE);
y += 6;
tft.setTextColor(RADAR_TEXT);
tft.setTextSize(1);
tft.setCursor(6, y);
tft.print("Octubre");
y += 12;
tft.setCursor(6, y);
tft.print("3, 2026");
// Pie del panel
tft.drawFastHLine(4, SCREEN_H - 14, PANEL_W - 8, PANEL_LINE);
tft.setTextColor(RADAR_GRID);
tft.setCursor(6, SCREEN_H - 10);
tft.print("v1.0");
}
// ============================================================
// HUD: indicador de ángulo dentro del panel
// ============================================================
void drawHudFrame() {
// Etiqueta "AZ" y línea base; el valor lo actualiza updateHudAngle()
tft.setTextSize(1);
tft.setTextColor(HUD_COLOR);
// Zona del HUD (parte baja del panel, encima del pie)
int16_t yHud = SCREEN_H - 46;
// Título del HUD
tft.setCursor(6, yHud);
tft.print("AZIMUTH");
// Caja del valor
tft.drawRect(4, yHud + 12, PANEL_W - 8, 20, HUD_COLOR);
}
void updateHudAngle(int16_t a) {
if (a == lastHudAngle) return;
lastHudAngle = a;
int16_t yHud = SCREEN_H - 46;
int16_t boxX = 4;
int16_t boxY = yHud + 12;
int16_t boxW = PANEL_W - 8;
int16_t boxH = 20;
// Borra el interior de la caja
tft.fillRect(boxX + 1, boxY + 1, boxW - 2, boxH - 2, PANEL_BG);
// Formatea "137" + símbolo de grado. El símbolo ° se dibuja como un pequeño círculo
char buf[8];
snprintf(buf, sizeof(buf), "%3d", a);
tft.setTextSize(2);
tft.setTextColor(HUD_COLOR);
// Centrar el número dentro de la caja
int16_t numW = textWidthPx(buf, 2);
int16_t tx = boxX + (boxW - numW) / 2 - 2; // -2 para dejar sitio al símbolo °
int16_t ty = boxY + (boxH - 16) / 2 + 1;
tft.setCursor(tx, ty);
tft.print(buf);
// Dibujar el símbolo de grado como un pequeño círculo
int16_t degX = tx + numW + 2;
int16_t degY = ty + 2;
tft.drawCircle(degX, degY, 2, HUD_COLOR);
}
// ============================================================
// Fondo estático del radar
// ============================================================
void drawRadarBackground() {
// Borra solo el área del radar
tft.fillRect(RADAR_AREA_X0, 0, RADAR_AREA_W, SCREEN_H, RADAR_BG);
// Círculos concéntricos
for (int r = 25; r <= R; r += 25) {
tft.drawCircle(CX, CY, r, RADAR_RING);
}
// Líneas radiales cada 30°
for (int a = 0; a < 360; a += 30) {
tft.drawLine(CX, CY, polarX(a, R), polarY(a, R), RADAR_GRID);
}
// Etiquetas de grados cada 30°
tft.setTextSize(1);
tft.setTextColor(RADAR_TEXT);
for (int a = 0; a < 360; a += 30) {
int16_t x = polarX(a, R + 10);
int16_t y = polarY(a, R + 10);
char buf[5];
snprintf(buf, sizeof(buf), "%d", a);
tft.setCursor(x - 6, y - 4);
tft.print(buf);
}
// Cruz central
tft.drawFastHLine(CX - 4, CY, 8, RADAR_TEXT);
tft.drawFastVLine(CX, CY - 4, 8, RADAR_TEXT);
// Título "RADAR" alineado a la DERECHA del área del radar
tft.setTextSize(2);
tft.setTextColor(RADAR_TEXT);
const char* title = "RADAR";
int16_t titleW = textWidthPx(title, 2);
int16_t titleX = SCREEN_W - titleW - 6; // 6 px de margen derecho
int16_t titleY = 6;
tft.setCursor(titleX, titleY);
tft.print(title);
}
// ============================================================
// Barrido
// ============================================================
void drawSweep(int16_t currentAngle) {
tft.drawLine(CX, CY, polarX(currentAngle, R), polarY(currentAngle, R), RADAR_TEXT);
for (int i = 1; i <= TRAIL; i++) {
int16_t a = currentAngle - i * SWEEP_STEP;
if (a < 0) a += 360;
uint16_t c;
if (i < TRAIL / 3) c = 0x07E0;
else if (i < 2 * TRAIL / 3) c = 0x03E0;
else c = 0x01A0;
tft.drawLine(CX, CY, polarX(a, R), polarY(a, R), c);
}
}
void eraseSweep(int16_t currentAngle) {
int16_t a = currentAngle - TRAIL * SWEEP_STEP;
if (a < 0) a += 360;
tft.drawLine(CX, CY, polarX(a, R), polarY(a, R), RADAR_BG);
redrawOverlayAtAngle(a);
}
void redrawOverlayAtAngle(int16_t deg) {
for (int r = 25; r <= R; r += 25) {
tft.drawPixel(polarX(deg, r), polarY(deg, r), RADAR_RING);
}
for (int a = 0; a < 360; a += 30) {
int16_t diff = abs(deg - a);
if (diff < 3 || diff > 357) {
tft.drawLine(CX, CY, polarX(a, 25), polarY(a, 25), RADAR_GRID);
break;
}
}
}
// ============================================================
// Blips
// ============================================================
void drawBlips() {
for (uint8_t i = 0; i < NUM_TARGETS; i++) {
int16_t deg = targets[i].angle;
int16_t r = targets[i].dist;
int16_t x = polarX(deg, r);
int16_t y = polarY(deg, r);
int16_t diff = abs(angle - deg);
if (diff > 180) diff = 360 - diff;
if (diff < SWEEP_STEP * 2) targetLastHit[i] = angle;
if (targetLastHit[i] >= 0) {
int16_t age = angle - targetLastHit[i];
if (age < 0) age += 360;
if (age < 60) {
tft.fillCircle(x, y, 3, targets[i].color);
} else if (age < 120) {
tft.drawCircle(x, y, 3, targets[i].color);
} else {
tft.fillCircle(x, y, 3, RADAR_BG);
for (int rr = 25; rr <= R; rr += 25) {
if (abs(r - rr) < 3) tft.drawPixel(x, y, RADAR_RING);
}
targetLastHit[i] = -1;
}
}
}
}
// ============================================================
// Setup / Loop
// ============================================================
void setup() {
Serial.begin(9600);
Serial.println("Radar ILI9341 horizontal iniciando...");
tft.begin();
tft.setRotation(1); // 1 = apaisado (320x240)
drawSidePanel();
drawHudFrame();
drawRadarBackground();
updateHudAngle(0);
for (uint8_t i = 0; i < NUM_TARGETS; i++) targetLastHit[i] = -1;
Serial.println("Radar listo.");
}
void loop() {
eraseSweep(angle);
drawSweep(angle);
drawBlips();
updateHudAngle(angle);
angle += SWEEP_STEP;
if (angle >= 360) angle -= 360;
delay(20);
}
Si tienes un proyecto por hacer con Lego Mindstorms, Arduino, Esp32, Raspberry Pi, Jetson Nano o necesitas ayuda con la programación en C/C++, Python, Java, JavaScript, o con el uso de herramientas de inteligencia artificial, no dudes en contactarnos en el Instituto de Robótica de Xalapa a través del sitio web www.irox.mx.














