Community project
Um Projeto De Mini Gps Com Tela Redonda
wigorlancer
Published July 29, 2026 · Updated July 29, 2026

This project builds a portable GPS navigator with a round 1.28-inch display, perfect for tracking location and heading in real-time. The ESP32 microcontroller reads position data from a NEO-6M GPS module and renders an interactive map on the GC9A01 circular TFT screen, with audio alerts from the buzzer for proximity warnings.
The guide provides a complete wiring diagram showing how to connect the round display, GPS receiver, and buzzer to the ESP32, along with a full parts list and step-by-step assembly instructions. Firmware based on TinyGPS++ and Adafruit graphics libraries is included, featuring map rendering, heading display, and alert system integration via WiFi configuration.
Wiring diagram
Interactive · read-only
Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.
Parts list
Bill of materials| Component | Qty | Notes |
|---|---|---|
| GC9A01 Round TFT LCD 1.28 inch 240x2401.28 in, 240×240 | 1 | 1.28 inch round IPS TFT LCD display module with GC9A01/GC9A01A driver IC. 240x240 RGB resolution, 4-wire SPI interface, and 3.3V/5V module input. Module-side labels are VCC, GND, DIN, CLK, CS, DC, RST, and BL/BLK. The display is write-only over SPI, so no MISO line is required for the LCD. Supported by Adafruit GC9A01A and TFT_eSPI libraries in Arduino-compatible firmware projects. |
| NEO-6M GPS Moduleu-blox NEO-6M | 1 | u-blox NEO-6M based GPS receiver module. Outputs NMEA sentences (GGA, RMC, etc.) over UART at 9600 baud by default. Provides latitude, longitude, altitude, speed, and time. The NEO-6M module itself is a 3.3V-class device; many GY-NEO6MV2 breakout boards accept 5V on their VCC header through an onboard regulator, but UART I/O remains 3.3V-domain and must not be driven above 3.6V. Features an on-board patch antenna footprint and an SMA/IPEX connector for external active antenna (preferred for faster lock acquisition). Supply current ~45 mA in acquisition, ~11 mA in tracking. |
| BuzzerPiezo 3.3 V | 1 | Piezo buzzer for sound output |
Assembly
5 stepsDesligue tudo e prepare a alimentação USB
Com o ESP32 desconectado, posicione o ESP32 DevKit v1, a tela GC9A01, o NEO-6M e o buzzer. A alimentação continua sendo pela porta USB do ESP32.
- Tip: Use jumpers curtos para a tela SPI.
- Tip: Todos os módulos devem compartilhar o mesmo GND.
- ⚠ Não conecte 5 V aos pinos de sinal do GPS ou da tela; o ESP32 usa sinais de 3,3 V.
Conecte a tela redonda
Ligue VCC e BL/BLK da tela a 3V3, e GND ao GND. Ligue SCK/CLK ao GPIO18, MOSI/DIN ao GPIO23, CS ao GPIO19, DC ao GPIO26 e RST ao GPIO25.
- Tip: MOSI pode estar marcado como DIN ou SDA; SCK pode estar marcado como CLK ou SCL.
- Tip: Confira a serigrafia do seu módulo antes de energizar.
- ⚠ O pino SCL/CLK da GC9A01 é clock SPI e deve ir ao GPIO18, não ao GPIO22.
Conecte o receptor GPS
Ligue VCC do NEO-6M a 3V3 e GND ao GND. Ligue TX do GPS ao GPIO16 do ESP32 e RX do GPS ao GPIO17.
- Tip: A serial é cruzada: TX de um equipamento vai ao RX do outro.
- Tip: Para obter sinal inicial, deixe a antena cerâmica voltada para o céu em uma área aberta.
- ⚠ Se o seu breakout especificar somente alimentação de 5 V, confirme a documentação do modelo antes de alterar esta ligação.
Conecte o buzzer
Ligue SIGNAL do buzzer ao GPIO4 e GND ao GND do ESP32.
- Tip: Use um buzzer piezo de 3,3 V.
- ⚠ Não conecte um buzzer de alta potência diretamente ao GPIO; use um driver se ele exceder a corrente suportada pelo pino.
Use a ponte Wi-Fi do mapa
Após usar Deploy, conecte o ESP32 à rede Wi-Fi pelo portal MiniGPS-Setup. No telefone, abra o endereço IP do ESP32 na mesma rede. A página permite enviar latitude, longitude, direção e linhas de ruas; o dispositivo desenha as linhas em preto e branco e mantém a seta no centro.
- Tip: O formato das ruas é lat,lon|lat,lon para cada rua, separando ruas com ponto e vírgula.
- Tip: Uma automação ou aplicativo complementar do telefone pode enviar POST para /map com os campos lat, lon, heading e roads.
- ⚠ O Google Maps não oferece a tela nem sua geometria de ruas diretamente ao ESP32. Esta ponte recebe dados que uma automação/app autorizado no telefone obtiver e simplificar.
- ⚠ Use o mapa apenas como apoio visual e mantenha atenção total ao trânsito.
Pin assignments
Board wiring reference| Pin | Connection | Type |
|---|---|---|
| 3V3 | display_1 VCC | power |
| GND | display_1 GND | ground |
| GPIO 18 | display_1 SCK | spi |
| GPIO 23 | display_1 MOSI | spi |
| GPIO 26 | display_1 DC | digital |
| GPIO 25 | display_1 RST | digital |
| 3V3 | display_1 BL | power |
| 3V3 | gps_1 VCC | power |
| GND | gps_1 GND | ground |
| GPIO 16 | gps_1 TX | uart |
| GPIO 17 | gps_1 RX | uart |
| GND | buzzer_1 GND | ground |
| GPIO 4 | buzzer_1 SIGNAL | digital |
| GPIO 19 | display_1 CS | spi |
Firmware
ESP32#include <Arduino.h>
#include <SPI.h>
#include <WiFi.h>
#include <WebServer.h>
#include <WiFiManager.h>
#include <TinyGPS++.h>
#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>
// Hoisted type definitions
struct Geo { double lat,lon; };
struct Road { Geo p[MAX_POINTS]; uint8_t n; };
struct Alert { double lat,lon; uint16_t radius; const char *label; };
// Forward declarations
bool parsePoint(String s,Geo &p);
void parseRoads(String data);
void project(Geo p,int &x,int &y);
void arrow(float h);
void drawMap();
const Alert* nearest(const Alert *points,size_t n,double &d);
void alerts();
String page();
void receiveMap();
constexpr int TFT_SCK=18,TFT_MOSI=23,TFT_CS=19,TFT_DC=26,TFT_RST=25;
constexpr int GPS_RX_PIN=16,GPS_TX_PIN=17,BUZZER_PIN=4;
constexpr uint16_t BLACK=0x0000,WHITE=0xFFFF,GRAY=0x8410,DARK_GRAY=0x4208;
constexpr uint8_t MAX_ROADS=16,MAX_POINTS=12;
const Alert radars[1]={{0,0,0,"RADAR"}}, dangers[1]={{0,0,0,"PERIGO"}};
constexpr size_t RADAR_COUNT=0,DANGER_COUNT=0;
Adafruit_GC9A01A tft(TFT_CS,TFT_DC,TFT_RST);
TinyGPSPlus gps; HardwareSerial gpsSerial(2); WebServer server(80);
Road roads[MAX_ROADS]; uint8_t roadCount=0;
double mapLat=0,mapLon=0; float mapHeading=0; bool phoneMap=false,redraw=true,alarmOn=false; uint32_t lastDraw=0;
bool parsePoint(String s,Geo &p){int c=s.indexOf(',');if(c<1)return false;double a=s.substring(0,c).toDouble(),b=s.substring(c+1).toDouble();if(a < -90 || a > 90 || b < -180 || b > 180)return false;p={a,b};return true;}
void parseRoads(String data){roadCount=0;int start=0;while(start<data.length()&&roadCount<MAX_ROADS){int end=data.indexOf(';',start);if(end<0)end=data.length();String line=data.substring(start,end);roads[roadCount].n=0;int ps=0;while(ps<line.length()&&roads[roadCount].n<MAX_POINTS){int pe=line.indexOf('|',ps);if(pe<0)pe=line.length();Geo p;if(parsePoint(line.substring(ps,pe),p))roads[roadCount].p[roads[roadCount].n++]=p;ps=pe+1;}if(roads[roadCount].n>=2)roadCount++;start=end+1;}}
void project(Geo p,int &x,int &y){double north=(p.lat-mapLat)*111320.0,east=(p.lon-mapLon)*111320.0*cos(mapLat*DEG_TO_RAD);x=120+(int)(east*.75);y=120-(int)(north*.75);}
void arrow(float h){float a=(h-90)*DEG_TO_RAD;int x1=120+(int)(15*cosf(a)),y1=120+(int)(15*sinf(a));int x2=120+(int)(-10*cosf(a)-8*sinf(a)),y2=120+(int)(-10*sinf(a)+8*cosf(a));int x3=120+(int)(-10*cosf(a)+8*sinf(a)),y3=120+(int)(-10*sinf(a)-8*cosf(a));tft.fillTriangle(x1,y1,x2,y2,x3,y3,WHITE);tft.drawTriangle(x1,y1,x2,y2,x3,y3,BLACK);}
void drawMap(){tft.fillScreen(BLACK);tft.drawCircle(120,120,116,WHITE);tft.drawCircle(120,120,115,WHITE);tft.setTextColor(WHITE);tft.setTextSize(1);tft.setCursor(116,9);tft.print("N");if(!phoneMap&&!gps.location.isValid()){tft.setCursor(44,116);tft.print("AGUARDANDO GPS");return;}for(uint8_t r=0;r<roadCount;r++)for(uint8_t i=1;i<roads[r].n;i++){int x1,y1,x2,y2;project(roads[r].p[i-1],x1,y1);project(roads[r].p[i],x2,y2);tft.drawLine(x1,y1,x2,y2,WHITE);tft.drawLine(x1+1,y1,x2+1,y2,DARK_GRAY);}tft.drawCircle(120,120,18,GRAY);tft.fillCircle(120,120,16,BLACK);arrow(mapHeading);tft.setCursor(8,225);tft.print(phoneMap?"MAPA DO TELEFONE":"GPS LOCAL");}
const Alert* nearest(const Alert *points,size_t n,double &d){const Alert *best=nullptr;d=1e9;for(size_t i=0;i<n;i++){double v=TinyGPSPlus::distanceBetween(mapLat,mapLon,points[i].lat,points[i].lon);if(v<d){d=v;best=&points[i];}}return best;}
void alerts(){if(!phoneMap&&!gps.location.isValid())return;double a,b;const Alert *p=nearest(dangers,DANGER_COUNT,a);if(!(p&&a<=p->radius))p=nearest(radars,RADAR_COUNT,b);bool warn=p&&((p==&dangers[0]&&a<=p->radius)||(p!=&dangers[0]&&b<=p->radius));if(warn!=alarmOn){ledcWriteTone(BUZZER_PIN,warn?2200:0);alarmOn=warn;}}
String page(){return R"HTML(<!doctype html><meta name="viewport" content="width=device-width,initial-scale=1"><body style="font-family:Arial;max-width:600px;margin:auto;padding:16px"><h2>Mini GPS - ponte de mapa</h2><p>O Google Maps nao entrega diretamente sua tela ou ruas ao ESP32. Envie a posicao, direcao e linhas de ruas de uma automacao/app do telefone para este mapa minimalista.</p><p>Formato de ruas: <code>lat,lon|lat,lon</code>; separe ruas por <code>;</code>.</p><form method="post" action="/map">Latitude <input name="lat" required><br>Longitude <input name="lon" required><br>Direcao (0=N, 90=L) <input name="heading" value="0"><br>Ruas <textarea name="roads" rows="6" style="width:100%"></textarea><br><button>Enviar mapa</button></form><p>API: POST /map com lat, lon, heading e roads. Telefone e dispositivo devem estar no mesmo Wi-Fi.</p></body>)HTML";}
void receiveMap(){if(!server.hasArg("lat")||!server.hasArg("lon")){server.send(400,"text/plain","Envie lat e lon.");return;}Geo p;if(!parsePoint(server.arg("lat")+","+server.arg("lon"),p)){server.send(400,"text/plain","Coordenadas invalidas.");return;}float h=server.hasArg("heading")?server.arg("heading").toFloat():0;if(h<0||h>=360){server.send(400,"text/plain","Direcao entre 0 e 359.");return;}mapLat=p.lat;mapLon=p.lon;mapHeading=h;if(server.hasArg("roads"))parseRoads(server.arg("roads"));phoneMap=true;redraw=true;server.send(200,"application/json","{\"ok\":true,\"roads\":"+String(roadCount)+"}");}
void setup(){Serial.begin(115200);ledcAttach(BUZZER_PIN,2000,8);ledcWriteTone(BUZZER_PIN,0);SPI.begin(TFT_SCK,-1,TFT_MOSI,TFT_CS);tft.begin();tft.setRotation(0);gpsSerial.begin(9600,SERIAL_8N1,GPS_RX_PIN,GPS_TX_PIN);WiFiManager wm;wm.setConfigPortalTimeout(180);wm.autoConnect("MiniGPS-Setup");server.on("/",HTTP_GET,[](){server.send(200,"text/html",page());});server.on("/map",HTTP_POST,receiveMap);server.on("/status",HTTP_GET,[](){server.send(200,"application/json","{\"ip\":\""+WiFi.localIP().toString()+"\",\"mapActive\":"+String(phoneMap?"true":"false")+"}");});server.begin();}
void loop(){while(gpsSerial.available())gps.encode(gpsSerial.read());server.handleClient();uint32_t now=millis();if(!phoneMap&&gps.location.isValid()){double a=gps.location.lat(),b=gps.location.lng();float h=gps.course.isValid()?gps.course.deg():mapHeading;if(fabs(a-mapLat)>.00001||fabs(b-mapLon)>.00001||fabs(h-mapHeading)>3){mapLat=a;mapLon=b;mapHeading=h;redraw=true;}}if(redraw&&now-lastDraw>=50){lastDraw=now;redraw=false;drawMap();}alerts();}“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.
Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.