Arduino UNO R4 - Estação Meteorológica Inteligente

Neste guia, vamos aprender a construir uma estação meteorológica inteligente com o Arduino UNO R4 WiFi e um display TFT LCD redondo de 1.28 polegada. A estação obtém o clima ao vivo pela Internet. Ela não precisa de nenhum sensor de temperatura ou de umidade. Em detalhes, vamos aprender:

Estação meteorológica inteligente com Arduino UNO R4 e display TFT LCD redondo

Hardware Necessário

1×Arduino UNO R4 WiFi
1×Alternatively, DIYables STEM V4 IoT
1×Alternatively, DIYables STEM V4B IoT
1×Cabo USB Tipo-A para Tipo-C (para PC com USB-A)
1×Cabo USB Tipo-C para Tipo-C (para PC com USB-C)
1×1.28 Inch Round Circular TFT LCD Display Module
1×Push Button
1×Button Module Alternatively,
1×Protoboard
1×Fios Jumper

You need only one of the two button parts. See the wiring section for both options.

1×(Recomendado) Shield com Bornes de Parafuso para Arduino Uno R4
1×(Recomendado) Sensors/Servo Expansion Shield for Arduino UNO R4
1×(Recomendado) Shield de Protoboard para Arduino Uno R4
1×(Recomendado) Case para Arduino Uno R4
1×(Recomendado) Divisor de Alimentação para Arduino Uno R4
1×(Recomendado) Placa Base de Prototipagem e Kit de Protoboard para Arduino Uno

Or you can buy the following kits:

1×DIYables STEM V4 IoT Starter Kit (Arduino included)
1×DIYables STEM V4B IoT Starter Kit (Arduino included)
1×DIYables Sensor Kit (18 sensors/displays)
Divulgação: Alguns dos links desta seção são links de afiliado da Amazon. Podemos receber uma comissão pelas compras feitas por meio desses links, sem custo adicional para você. Agradecemos o seu apoio.
Arduino Uno R4 WiFi Compatible Boards

※ Nota:

Este projeto precisa do Arduino UNO R4 WiFi. O Arduino UNO R4 Minima não tem WiFi, então ele não consegue acessar a API de clima.

Sobre a Estação Meteorológica Inteligente

Uma estação meteorológica comum usa um sensor para medir o ar ao redor. Esta funciona de outro jeito. O Arduino UNO R4 consulta um serviço de clima na Internet, e o serviço devolve o clima da sua cidade. Por isso, a estação também pode mostrar coisas que um sensor pequeno não consegue medir, como a chance de chuva para as próximas horas e a previsão para os próximos dias.

A estação tem oito páginas. Um botão passa de uma para a outra:

  • HOME - o ícone do clima, a temperatura, a descrição do céu e um relógio.
  • TEMP - a temperatura e a sensação térmica.
  • HUMIDITY - a umidade como um anel e como um número.
  • WIND - a velocidade do vento e a direção do vento como uma bússola.
  • RAIN - a chance de chuva com uma pequena animação de chuva.
  • NEXT HOURS - a temperatura e a chance de chuva para as próximas horas.
  • FORECAST - a temperatura máxima e mínima para os próximos 3 dias.
  • SYSTEM - o estado do WiFi, o nível do sinal e o estado da API.

O Arduino UNO R4 pede um novo clima a cada 10 minutos. Entre duas atualizações, o relógio da página inicial continua funcionando.

Sobre a API de Clima Open-Meteo

O Open-Meteo é um serviço de clima gratuito. Ele é uma boa escolha para uma placa pequena como o Arduino UNO R4:

  • Sem chave de API: você não precisa se cadastrar nem colar uma chave no código.
  • Gratuito para uso pessoal: sem custo para um projeto pequeno como este.
  • JSON simples: a resposta é um texto JSON curto que o Arduino UNO R4 consegue ler.
  • Qualquer lugar: você escolhe o lugar com uma latitude e uma longitude.

O Arduino UNO R4 envia uma requisição HTTPS e recebe uma resposta de cerca de 1.8 KB. A resposta traz o clima de agora, das próximas 12 horas e dos próximos 3 dias.

※ Nota:

A resposta também traz a hora local do lugar que você pediu. O código pega a hora dali, então a estação mostra um relógio sem cliente NTP e sem módulo RTC.

Diagrama de Ligação

O display TFT LCD redondo de 1.28 polegada usa os pinos SPI de hardware do Arduino UNO R4. Esses pinos são fixos: D13 é o clock e D11 é a linha de dados.

Pino do TFT LCD Arduino UNO R4 Descrição
VCC 5V Alimentação
GND GND Terra
SCL D13 Clock SPI (pino fixo)
SDA D11 MOSI SPI (pino fixo)
DC D9 Dados/Comando
CS D10 Chip Select
RST D8 Reset

Para o botão você pode escolher uma de duas peças. As duas funcionam do mesmo jeito no projeto. Só uma linha de código é diferente.

Ligação com um Botão de Pressão

Um botão de pressão tem dois pinos e nenhum resistor. Ele conecta o D2 ao GND quando você o pressiona. O Arduino UNO R4 liga o seu próprio resistor de pull-up interno, então você não precisa adicionar nenhum resistor.

Diagrama de ligação da estação meteorológica inteligente com Arduino UNO R4 e botão de pressão

This image is created using Fritzing. Click to enlarge image

Pino do Botão Arduino UNO R4 Descrição
Pino 1 D2 Entrada do botão com pull-up interno
Pino 2 GND Terra

Isto é o que o código usa por padrão:

ezButton button(PIN_BUTTON);

Ligação com um Módulo de Botão

Um módulo de botão tem três pinos e o seu próprio resistor na placa. Ele precisa de alimentação, e o pino OUT vai ao D2. O módulo dá LOW quando você não pressiona e HIGH quando você pressiona.

Diagrama de ligação da estação meteorológica inteligente com Arduino UNO R4 e módulo de botão

This image is created using Fritzing. Click to enlarge image

Veja A melhor forma de alimentar o Arduino Uno R4 e outros componentes.

Pino do Módulo de Botão Arduino UNO R4 Descrição
VCC 5V Alimentação
GND GND Terra
OUT D2 Sinal do botão, LOW quando solto, HIGH quando pressionado

O módulo já tem um resistor, então o interno não deve ser usado. Mude uma linha no código:

ezButton button(PIN_BUTTON, EXTERNAL_PULLDOWN);

Como Programar a Estação Meteorológica Inteligente

Inclua as cinco bibliotecas.

#include <WiFiS3.h> #include <ArduinoHttpClient.h> #include <ArduinoJson.h> #include <DIYables_TFT_Round.h> #include <ezButton.h>

Crie o objeto do display com os pinos de reset, dados/comando e chip select.

DIYables_TFT_GC9A01_Round tft(8, 9, 10);

Defina o nome do seu WiFi, a senha do seu WiFi e o seu lugar.

#define WIFI_SSID "YOUR_WIFI_SSID" #define WIFI_PASSWORD "YOUR_WIFI_PASSWORD" #define LATITUDE 37.5665 #define LONGITUDE 126.9780

Inicie o display e o WiFi.

tft.begin(); tft.setRotation(2); WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

Abra uma conexão HTTPS com o servidor de clima. No Arduino UNO R4, o WiFiSSLClient cuida da parte HTTPS. Os certificados raiz já estão dentro do firmware do WiFi, então você não precisa colar um certificado no código.

WiFiSSLClient sslClient; HttpClient http(sslClient, "api.open-meteo.com", 443);

Envie a requisição e leia a resposta.

http.get(path); int statusCode = http.responseStatusCode(); String payload = http.responseBody();

Leia a resposta JSON. O Arduino UNO R4 tem apenas 32 KB de RAM, então é usado um filtro. O filtro mantém os três blocos que o código precisa e descarta o resto.

JsonDocument filter; filter["current"] = true; filter["hourly"] = true; filter["daily"] = true; JsonDocument doc; deserializeJson(doc, payload, DeserializationOption::Filter(filter));

Pegue um valor da resposta.

float temperature = doc["current"]["temperature_2m"] | 0.0;

Desenhe-o no display redondo.

tft.setTextSize(3); tft.setTextColor(DIYables_TFT::colorRGB(255, 255, 255)); tft.setCursor(80, 100); tft.print(temperature, 1);

Código Arduino UNO R4 - Estação Meteorológica Inteligente

/* * Este código de Arduino UNO R4 foi desenvolvido por pt.newbiely.com * Este código de Arduino UNO R4 é disponibilizado ao público sem nenhuma restrição. * Para tutoriais completos e diagramas de ligação, visite: * https://pt.newbiely.com/tutorials/arduino-uno-r4/arduino-uno-r4-smart-weather-station */ /* ============================================================ SMART WEATHER STATION Arduino UNO R4 WiFi + GC9A01 Round TFT + Push Button Location: Seoul, South Korea (change it in USER CONFIGURATION) Weather API: Open-Meteo (free, no API key needed) ------------------------------------------------------------ GC9A01 Round TFT -> Arduino UNO R4 VCC -> 5V GND -> GND SCL -> D13 (hardware SPI clock, fixed) SDA -> D11 (hardware SPI MOSI, fixed) RST -> D8 DC -> D9 CS -> D10 Button D2 -> BUTTON -> GND ------------------------------------------------------------ BUTTON Short press -> Next page Long press -> Refresh weather Very long press -> Back to home page ------------------------------------------------------------ This sketch needs the Arduino UNO R4 WiFi. The UNO R4 Minima has no WiFi, so it cannot read the API. ============================================================ */ #include <WiFiS3.h> #include <ArduinoHttpClient.h> #include <ArduinoJson.h> #include <DIYables_TFT_Round.h> #include <ezButton.h> // ===== USER CONFIGURATION ===== #define WIFI_SSID "YOUR_WIFI_SSID" #define WIFI_PASSWORD "YOUR_WIFI_PASSWORD" #define LATITUDE 37.5665 #define LONGITUDE 126.9780 #define LOCATION_NAME "SEOUL" // Time zone name for the API, URL-encoded. // "/" must be written as "%2F". #define TIMEZONE "Asia%2FSeoul" // ===== API SERVER ===== const char API_HOST[] = "api.open-meteo.com"; const int API_PORT = 443; // ===== PIN CONFIGURATION ===== // SCK (D13) and MOSI (D11) are the hardware SPI pins of the // Arduino UNO R4. They are fixed and are not listed here. #define PIN_BUTTON 2 #define PIN_RST 8 #define PIN_DC 9 #define PIN_CS 10 // ===== TIMING ===== const unsigned long API_UPDATE_INTERVAL = 10UL * 60UL * 1000UL; const unsigned long WIFI_RETRY_INTERVAL = 10UL * 1000UL; const unsigned long WIFI_CONNECT_TIMEOUT = 15UL * 1000UL; const unsigned long BUTTON_DEBOUNCE = 40; const unsigned long LONG_PRESS_TIME = 600; const unsigned long VERY_LONG_PRESS_TIME = 2000; const unsigned long RAIN_ANIMATION_INTERVAL = 90; const unsigned long CLOCK_UPDATE_INTERVAL = 1000; // ===== TFT ===== DIYables_TFT_GC9A01_Round tft(PIN_RST, PIN_DC, PIN_CS); // ===== SCREEN ===== const int SCREEN_W = 240; const int SCREEN_H = 240; const int CENTER_X = 120; const int CENTER_Y = 120; // ===== COLORS ===== #define BLACK DIYables_TFT::colorRGB(0, 0, 0) #define WHITE DIYables_TFT::colorRGB(255, 255, 255) #define CYAN DIYables_TFT::colorRGB(0, 220, 255) #define BLUE DIYables_TFT::colorRGB(40, 120, 255) #define LIGHT_BLUE DIYables_TFT::colorRGB(90, 190, 255) #define GREEN DIYables_TFT::colorRGB(60, 230, 130) #define YELLOW DIYables_TFT::colorRGB(255, 210, 60) #define ORANGE DIYables_TFT::colorRGB(255, 150, 40) #define RED DIYables_TFT::colorRGB(255, 70, 70) #define PURPLE DIYables_TFT::colorRGB(180, 100, 255) #define GRAY DIYables_TFT::colorRGB(125, 125, 135) #define DARK_GRAY DIYables_TFT::colorRGB(42, 42, 52) #define DARK_BLUE DIYables_TFT::colorRGB(12, 18, 48) // ===== PAGE ===== enum Page { PAGE_HOME = 0, PAGE_TEMP, PAGE_HUMIDITY, PAGE_WIND, PAGE_RAIN, PAGE_HOURLY, PAGE_FORECAST, PAGE_SYSTEM, PAGE_COUNT }; Page currentPage = PAGE_HOME; // ===== WEATHER DATA ===== // The Arduino UNO R4 has only 32 KB of RAM, so short text is // kept in fixed char arrays instead of String objects. struct WeatherData { bool valid = false; float temperature = 0; float humidity = 0; float feelsLike = 0; float windSpeed = 0; float windDirection = 0; float pressure = 0; int weatherCode = 0; bool isDay = true; int currentRainChance = 0; float uvIndex = 0; // "HH:MM" char sunrise[6] = "--:--"; char sunset[6] = "--:--"; // NEXT HOURS float hourlyTemp[12]; int hourlyRainChance[12]; int hourlyWeatherCode[12]; // "HH:MM" char hourlyTime[12][6]; // DAILY // "YYYY-MM-DD" char dailyDate[3][11]; float dailyTempMax[3]; float dailyTempMin[3]; int dailyRainChance[3]; int dailyWeatherCode[3]; float dailyUV[3]; }; WeatherData weather; // ===== SOFTWARE CLOCK ===== // The Arduino UNO R4 has no NTP client in this sketch. // Instead, the local time that comes with the weather answer // is stored, then millis() moves the clock forward. // Seconds after midnight at the moment of the last answer. long clockBaseSecond = -1; unsigned long clockBaseMillis = 0; // ===== SYSTEM STATE ===== bool apiOnline = false; bool wifiOnline = false; bool screenDirty = true; bool isFetching = false; bool pendingRefresh = false; unsigned long lastApiUpdate = 0; unsigned long lastWifiRetry = 0; unsigned long wifiConnectStart = 0; unsigned long lastRainAnimation = 0; unsigned long lastClockUpdate = 0; bool wifiConnecting = false; // ===== BUTTON ===== // ezButton handles the debouncing, so the sketch only has to // measure how long the button was held down. // // The line below is for a BARE push button wired to GND. ezButton then // uses the internal pull-up resistor of the board, and the pin reads LOW // while the button is held down. ezButton button(PIN_BUTTON); // If you use a BUTTON MODULE instead, the module carries its own resistor // on the board, so the internal one must not be used. Comment the line // above and use this line instead: // ezButton button(PIN_BUTTON, EXTERNAL_PULLDOWN); unsigned long buttonPressStart = 0; // ===== RAIN ANIMATION ===== const int RAIN_DROP_COUNT = 16; int rainX[RAIN_DROP_COUNT]; int rainY[RAIN_DROP_COUNT]; bool rainInitialized = false; const int RAIN_AREA_X = 30; const int RAIN_AREA_Y = 125; const int RAIN_AREA_W = 180; const int RAIN_AREA_H = 78; // ===== WEATHER DESCRIPTION ===== String weatherDescription(int code) { switch (code) { case 0: return "CLEAR"; case 1: return "MAINLY CLEAR"; case 2: return "PARTLY CLOUDY"; case 3: return "OVERCAST"; case 45: case 48: return "FOG"; case 51: case 53: case 55: return "DRIZZLE"; case 56: case 57: return "FREEZING DRIZZLE"; case 61: case 63: case 65: return "RAIN"; case 66: case 67: return "FREEZING RAIN"; case 71: case 73: case 75: return "SNOW"; case 77: return "SNOW GRAINS"; case 80: case 81: case 82: return "SHOWERS"; case 85: case 86: return "SNOW SHOWERS"; case 95: return "THUNDERSTORM"; case 96: case 99: return "STORM"; default: return "UNKNOWN"; } } // ===== WEATHER COLOR ===== uint16_t weatherColor(int code) { if (code == 0) return YELLOW; if (code == 1 || code == 2) return CYAN; if (code == 3) return GRAY; if (code >= 51 && code <= 67) return BLUE; if (code >= 80 && code <= 82) return LIGHT_BLUE; if (code >= 95) return RED; return WHITE; } // ===== WIND DIRECTION ===== String windDirectionText(float degrees) { const char* directions[] = { "N", "NE", "E", "SE", "S", "SW", "W", "NW" }; int index = (int)((degrees + 22.5) / 45.0); index %= 8; return directions[index]; } // ===== CENTER TEXT ===== void drawCenteredText(String text, int y, int size, uint16_t color) { tft.setTextSize(size); tft.setTextColor(color); int width = text.length() * 6 * size; int x = CENTER_X - width / 2; if (x < 0) x = 0; tft.setCursor(x, y); tft.print(text); } // ===== HEADER ===== void drawHeader(String title, uint16_t color) { // Safe radius from screen center (slightly less than the // physical 120px radius, to keep a small margin from the // round bezel). const float SAFE_RADIUS = 116.0; int size = 2; int width = title.length() * 6 * size; int half = width / 2; int y; if (half >= SAFE_RADIUS) { // Too wide even at the lowest safe position -> use a // smaller font instead of clipping. size = 1; width = title.length() * 6 * size; half = width / 2; y = 14; } else { // Compute the minimum y (top of text) so the top corners // of the text stay inside the round visible area. float insideSpan = sqrt((SAFE_RADIUS * SAFE_RADIUS) - ((float)half * (float)half)); int minY = (int)(120.0 - insideSpan); y = (minY > 14) ? minY : 14; } drawCenteredText(title, y, size, color); int lineY = y + (size == 2 ? 16 : 8) + 6; tft.drawLine(45, lineY, 195, lineY, DARK_GRAY); } // ===== FOOTER ===== void drawFooter() { tft.drawLine(55, 214, 185, 214, DARK_GRAY); drawCenteredText(String(currentPage + 1) + "/" + String(PAGE_COUNT), 220, 1, GRAY); } // ===== SUN ICON ===== void drawSun(int x, int y, int radius) { tft.fillCircle(x, y, radius, YELLOW); for (int i = 0; i < 8; i++) { float angle = i * PI / 4.0; int x1 = x + cos(angle) * (radius + 7); int y1 = y + sin(angle) * (radius + 7); int x2 = x + cos(angle) * (radius + 14); int y2 = y + sin(angle) * (radius + 14); tft.drawLine(x1, y1, x2, y2, YELLOW); } } // ===== CLOUD ICON ===== void drawCloud(int x, int y) { tft.fillCircle(x - 20, y, 15, LIGHT_BLUE); tft.fillCircle(x, y - 8, 20, LIGHT_BLUE); tft.fillCircle(x + 20, y, 15, LIGHT_BLUE); tft.fillRoundRect(x - 35, y, 70, 20, 10, LIGHT_BLUE); } // ===== STATIC RAIN ICON ===== void drawRainIcon(int x, int y) { drawCloud(x, y); for (int i = 0; i < 5; i++) { int dx = -24 + i * 12; int dy = 28; tft.drawLine(x + dx, y + dy, x + dx - 4, y + dy + 10, BLUE); } } // ===== WEATHER ICON ===== void drawWeatherIcon(int code, int x, int y) { if (code == 0) { drawSun(x, y, 18); } else if (code <= 2) { drawSun(x - 12, y - 7, 13); drawCloud(x + 10, y + 10); } else if (code == 3) { drawCloud(x, y); } else if ((code >= 51 && code <= 67) || (code >= 80 && code <= 82)) { drawRainIcon(x, y); } else if (code >= 95) { drawCloud(x, y); tft.drawLine(x - 5, y + 28, x - 12, y + 43, YELLOW); tft.drawLine(x + 10, y + 28, x + 3, y + 43, YELLOW); } else { drawCloud(x, y); } } void drawWifiIcon(int cx, int cy, int level) { const int radii[3] = { 14, 27, 40 }; for (int arc = 0; arc < 3; arc++) { uint16_t color = (arc < level) ? CYAN : DARK_GRAY; for (int deg = -150; deg <= -30; deg += 5) { float angle = deg * PI / 180.0; int x = cx + (int)(cos(angle) * radii[arc]); int y = cy + (int)(sin(angle) * radii[arc]); tft.fillCircle(x, y, 2, color); } } // Base dot (the "device") tft.fillCircle(cx, cy, 5, CYAN); } // ===== PAGE 1 - HOME ===== void drawHome() { uint16_t bg = weather.isDay ? BLACK : DARK_BLUE; tft.fillScreen(bg); if (!weather.valid) { drawCenteredText("NO DATA", 90, 3, RED); drawCenteredText("CHECK CONNECTION", 135, 1, WHITE); drawFooter(); return; } // Weather icon drawWeatherIcon(weather.weatherCode, CENTER_X, 65); // Temperature drawCenteredText(String(weather.temperature, 1) + " C", 112, 3, WHITE); // Location drawCenteredText(LOCATION_NAME, 153, 2, CYAN); // Weather condition drawCenteredText(weatherDescription(weather.weatherCode), 178, 1, weatherColor(weather.weatherCode)); // Clock area drawHomeClock(); drawFooter(); } // ===== HOME CLOCK ===== void drawHomeClock() { uint16_t bg = weather.isDay ? BLACK : DARK_BLUE; // Only clear a small region. // This prevents full-screen flicker. tft.fillRect(82, 196, 76, 12, bg); drawCenteredText(getTimeString(), 197, 1, GRAY); } // ===== PAGE 2 - TEMPERATURE ===== void drawTemperature() { tft.fillScreen(BLACK); drawHeader("TEMPERATURE", ORANGE); int cx = 120; int cy = 125; int radius = 68; int value = constrain((int)weather.temperature, 0, 40); int filled = map(value, 0, 40, 0, 240); // Temperature gauge for (int i = 0; i < 240; i += 4) { float angle = (-135.0 + i) * PI / 180.0; int x = cx + cos(angle) * radius; int y = cy + sin(angle) * radius; uint16_t color = (i < filled) ? ORANGE : DARK_GRAY; tft.fillCircle(x, y, 2, color); } // Main value drawCenteredText(String(weather.temperature, 1), 101, 3, WHITE); drawCenteredText("C", 138, 2, ORANGE); drawCenteredText("FEELS " + String(weather.feelsLike, 1) + " C", 168, 1, CYAN); drawFooter(); } // ===== PAGE 3 - HUMIDITY ===== void drawHumidity() { tft.fillScreen(BLACK); drawHeader("HUMIDITY", BLUE); int cx = 120; int cy = 125; int radius = 68; int progress = constrain((int)weather.humidity, 0, 100); int filled = map(progress, 0, 100, 0, 270); for (int i = 0; i < 270; i += 3) { float angle = (-135.0 + i) * PI / 180.0; int x = cx + cos(angle) * radius; int y = cy + sin(angle) * radius; uint16_t color = (i < filled) ? BLUE : DARK_GRAY; tft.fillCircle(x, y, 3, color); } drawCenteredText(String((int)weather.humidity) + "%", 105, 3, WHITE); String comfort; if (weather.humidity < 40) comfort = "DRY"; else if (weather.humidity < 70) comfort = "COMFORTABLE"; else if (weather.humidity < 85) comfort = "HUMID"; else comfort = "VERY HUMID"; drawCenteredText(comfort, 153, 1, CYAN); drawFooter(); } // ===== PAGE 4 - WIND ===== void drawWind() { tft.fillScreen(BLACK); drawHeader("WIND", GREEN); int cx = 120; int cy = 104; int radius = 49; // Compass tft.drawCircle(cx, cy, radius, DARK_GRAY); drawCenteredText("N", 43, 1, WHITE); drawCenteredText("S", 157, 1, WHITE); tft.setCursor(62, 101); tft.setTextSize(1); tft.setTextColor(WHITE); tft.print("W"); tft.setCursor(172, 101); tft.print("E"); // Direction arrow float angle = weather.windDirection * PI / 180.0; int x2 = cx + sin(angle) * 38; int y2 = cy - cos(angle) * 38; tft.drawLine(cx, cy, x2, y2, GREEN); tft.fillCircle(cx, cy, 5, GREEN); // Wind speed drawCenteredText(String(weather.windSpeed, 1) + " km/h", 174, 2, WHITE); drawCenteredText(windDirectionText(weather.windDirection), 198, 1, CYAN); drawFooter(); } // ===== PAGE 5 - RAIN ===== void drawRain() { tft.fillScreen(BLACK); drawHeader("RAIN", LIGHT_BLUE); int rain = weather.currentRainChance; // Static weather icon if (rain >= 60) { drawRainIcon(CENTER_X, 68); } else { drawCloud(CENTER_X, 73); } // Rain percentage drawCenteredText(String(rain) + "%", 105, 3, WHITE); drawCenteredText("RAIN CHANCE", 140, 1, LIGHT_BLUE); // Status if (rain >= 60) { drawCenteredText("HIGH", 158, 1, RED); } else if (rain >= 30) { drawCenteredText("MODERATE", 158, 1, YELLOW); } else { drawCenteredText("LOW", 158, 1, GREEN); } // Animation area if (rain >= 20) { initializeRainAnimation(); } drawFooter(); } // ===== INITIALIZE RAIN ANIMATION ===== void initializeRainAnimation() { for (int i = 0; i < RAIN_DROP_COUNT; i++) { rainX[i] = RAIN_AREA_X + random(RAIN_AREA_W); rainY[i] = RAIN_AREA_Y + random(RAIN_AREA_H); } rainInitialized = true; } // ===== UPDATE RAIN ANIMATION ===== void updateRainAnimation() { if (currentPage != PAGE_RAIN) return; if (weather.currentRainChance < 20) return; if (millis() - lastRainAnimation < RAIN_ANIMATION_INTERVAL) { return; } lastRainAnimation = millis(); uint16_t bg = BLACK; // Erase old drops for (int i = 0; i < RAIN_DROP_COUNT; i++) { tft.drawLine(rainX[i], rainY[i], rainX[i] - 3, rainY[i] + 7, bg); } // Move drops for (int i = 0; i < RAIN_DROP_COUNT; i++) { rainY[i] += 5; if (rainY[i] > RAIN_AREA_Y + RAIN_AREA_H) { rainX[i] = RAIN_AREA_X + random(RAIN_AREA_W); rainY[i] = RAIN_AREA_Y; } tft.drawLine(rainX[i], rainY[i], rainX[i] - 3, rainY[i] + 7, BLUE); } } // ===== PAGE 6 - NEXT HOURS ===== void drawHourly() { tft.fillScreen(BLACK); drawHeader("NEXT HOURS", PURPLE); for (int i = 0; i < 5; i++) { int y = 52 + i * 31; // Row separator if (i > 0) { tft.drawLine(25, y - 8, 215, y - 8, DARK_GRAY); } // Time tft.setCursor(25, y); tft.setTextSize(1); tft.setTextColor(WHITE); tft.print(weather.hourlyTime[i]); // Weather int code = weather.hourlyWeatherCode[i]; String condition; if (code == 0) condition = "SUN"; else if (code <= 3) condition = "CLOUD"; else if (code >= 95) condition = "STORM"; else condition = "RAIN"; tft.setCursor(75, y); tft.setTextColor(weatherColor(code)); tft.print(condition); // Temperature tft.setCursor(142, y); tft.setTextColor(WHITE); tft.print(weather.hourlyTemp[i], 0); tft.print("C"); // Rain tft.setCursor(180, y); tft.setTextColor(LIGHT_BLUE); tft.print(weather.hourlyRainChance[i]); tft.print("%"); } drawFooter(); } // ===== PAGE 7 - FORECAST ===== // UI FIX: // The card border (drawRoundRect) previously spanned x=15..225 // (width 210). For the TOP row, the corners of that rectangle // fell outside the visible round area of the screen and were // clipped. The card width below was reduced to 190px // (x=25..215) which keeps every corner, on every row, safely // inside the round bezel. Text start x was nudged in slightly // to match the new card padding. void drawForecast() { tft.fillScreen(BLACK); drawHeader("3-DAY FORECAST", CYAN); const char* labels[] = { "TODAY", "TOMORROW", "DAY 3" }; // Card geometry (UI fix: narrower so it never touches // the round bezel, even on the top row). const int CARD_X = 25; const int CARD_W = 190; for (int i = 0; i < 3; i++) { int y = 62 + i * 48; // Card tft.drawRoundRect(CARD_X, y - 5, CARD_W, 40, 8, DARK_GRAY); // Day tft.setCursor(CARD_X + 8, y + 4); tft.setTextSize(1); tft.setTextColor(WHITE); tft.print(labels[i]); // Temperature tft.setCursor(CARD_X + 65, y + 4); tft.print(weather.dailyTempMin[i], 0); tft.print("/"); tft.print(weather.dailyTempMax[i], 0); tft.print("C"); // Rain tft.setCursor(CARD_X + 120, y + 4); tft.setTextColor(LIGHT_BLUE); tft.print(weather.dailyRainChance[i]); tft.print("%"); // Weather tft.setCursor(CARD_X + 155, y + 4); tft.setTextColor(weatherColor(weather.dailyWeatherCode[i])); int code = weather.dailyWeatherCode[i]; if (code == 0) tft.print("SUN"); else if (code <= 3) tft.print("CLD"); else if (code >= 95) tft.print("STM"); else tft.print("RAIN"); } drawFooter(); } // ===== PAGE 8 - SYSTEM ===== void drawSystem() { tft.fillScreen(BLACK); drawHeader("SYSTEM", CYAN); // WIFI tft.setCursor(25, 57); tft.setTextSize(1); tft.setTextColor(WHITE); tft.print("WIFI"); tft.setCursor(125, 57); if (WiFi.status() == WL_CONNECTED) { tft.setTextColor(GREEN); tft.print("ONLINE"); } else { tft.setTextColor(RED); tft.print("OFFLINE"); } // RSSI tft.setCursor(25, 82); tft.setTextColor(WHITE); tft.print("RSSI"); tft.setCursor(125, 82); if (WiFi.status() == WL_CONNECTED) { tft.setTextColor(CYAN); tft.print(WiFi.RSSI()); tft.print(" dBm"); } else { tft.print("--"); } // API tft.setCursor(25, 107); tft.setTextColor(WHITE); tft.print("API"); tft.setCursor(125, 107); if (apiOnline) { tft.setTextColor(GREEN); tft.print("ONLINE"); } else { tft.setTextColor(RED); tft.print("OFFLINE"); } // LOCATION tft.setCursor(25, 132); tft.setTextColor(WHITE); tft.print("LOCATION"); tft.setCursor(125, 132); tft.setTextColor(CYAN); tft.print(LOCATION_NAME); // TEMP tft.setCursor(25, 157); tft.setTextColor(WHITE); tft.print("TEMP"); tft.setCursor(125, 157); tft.print(weather.temperature, 1); tft.print(" C"); // STATUS tft.setCursor(25, 182); tft.setTextColor(WHITE); tft.print("STATUS"); tft.setCursor(125, 182); if (weather.valid) { tft.setTextColor(GREEN); tft.print("READY"); } else { tft.setTextColor(RED); tft.print("NO DATA"); } drawFooter(); } // ===== RENDER PAGE ===== void renderPage() { // Rain animation must be initialized // only when entering the page. rainInitialized = false; switch (currentPage) { case PAGE_HOME: drawHome(); break; case PAGE_TEMP: drawTemperature(); break; case PAGE_HUMIDITY: drawHumidity(); break; case PAGE_WIND: drawWind(); break; case PAGE_RAIN: drawRain(); break; case PAGE_HOURLY: drawHourly(); break; case PAGE_FORECAST: drawForecast(); break; case PAGE_SYSTEM: drawSystem(); break; } } // ===== WEATHER API PATH ===== // ArduinoHttpClient keeps the host name apart from the path, // so only the part after the host name is built here. String getWeatherPath() { String path = "/v1/forecast"; path += "?latitude=" + String(LATITUDE, 4); path += "&longitude=" + String(LONGITUDE, 4); path += "&current=" "temperature_2m," "relative_humidity_2m," "apparent_temperature," "weather_code," "wind_speed_10m," "wind_direction_10m," "pressure_msl," "is_day"; path += "&hourly=" "temperature_2m," "precipitation_probability," "weather_code"; path += "&daily=" "temperature_2m_max," "temperature_2m_min," "precipitation_probability_max," "weather_code," "sunrise," "sunset," "uv_index_max"; path += "&timezone=" TIMEZONE; path += "&forecast_days=3"; // Only the next 12 hours are asked for. // The answer then starts at the current hour, and it stays // small enough for the 32 KB RAM of the Arduino UNO R4. path += "&forecast_hours=12"; path += "&temperature_unit=celsius"; path += "&wind_speed_unit=kmh"; return path; } // ===== COPY "HH:MM" OUT OF AN ISO TIME TEXT ===== // Input : 2026-09-18T14:00 // Output: 14:00 void copyClockText(const char* isoTime, char* out) { if (isoTime != NULL && strlen(isoTime) >= 16) { out[0] = isoTime[11]; out[1] = isoTime[12]; out[2] = ':'; out[3] = isoTime[14]; out[4] = isoTime[15]; out[5] = '\0'; return; } strcpy(out, "--:--"); } // ===== SET THE SOFTWARE CLOCK FROM AN ISO TIME TEXT ===== void setClockFrom(const char* isoTime) { if (isoTime == NULL || strlen(isoTime) < 16) { return; } int hour = (isoTime[11] - '0') * 10 + (isoTime[12] - '0'); int minute = (isoTime[14] - '0') * 10 + (isoTime[15] - '0'); if (hour < 0 || hour > 23 || minute < 0 || minute > 59) { return; } clockBaseSecond = (long)hour * 3600L + (long)minute * 60L; clockBaseMillis = millis(); } // ===== PARSE WEATHER ===== bool parseWeather(const String& payload) { // A filter keeps only the three blocks that are used. // The "..._units" blocks of the answer are dropped, so the // document stays small in the RAM of the Arduino UNO R4. JsonDocument filter; filter["current"] = true; filter["hourly"] = true; filter["daily"] = true; JsonDocument doc; DeserializationError error = deserializeJson(doc, payload, DeserializationOption::Filter(filter)); if (error) { Serial.print("JSON ERROR: "); Serial.println(error.c_str()); return false; } // CURRENT JsonObject current = doc["current"]; if (current.isNull()) { Serial.println("CURRENT DATA MISSING"); return false; } weather.temperature = current["temperature_2m"] | 0.0; weather.humidity = current["relative_humidity_2m"] | 0.0; weather.feelsLike = current["apparent_temperature"] | 0.0; weather.weatherCode = current["weather_code"] | 0; weather.windSpeed = current["wind_speed_10m"] | 0.0; weather.windDirection = current["wind_direction_10m"] | 0.0; weather.pressure = current["pressure_msl"] | 0.0; int isDay = current["is_day"] | 1; weather.isDay = (isDay == 1); // The answer carries the local time of the location. // It is used to start the software clock. const char* currentTime = current["time"] | ""; setClockFrom(currentTime); // HOURLY // "forecast_hours=12" makes the list start at the current // hour, so index 0 is now and index 11 is 11 hours later. JsonArray hourlyTime = doc["hourly"]["time"]; JsonArray hourlyTemp = doc["hourly"]["temperature_2m"]; JsonArray hourlyRain = doc["hourly"]["precipitation_probability"]; JsonArray hourlyCode = doc["hourly"]["weather_code"]; for (int i = 0; i < 12; i++) { // Safety check if (i >= (int)hourlyTemp.size()) { weather.hourlyTemp[i] = weather.temperature; weather.hourlyRainChance[i] = 0; weather.hourlyWeatherCode[i] = weather.weatherCode; strcpy(weather.hourlyTime[i], "--:--"); continue; } weather.hourlyTemp[i] = hourlyTemp[i] | weather.temperature; weather.hourlyRainChance[i] = hourlyRain[i] | 0; weather.hourlyWeatherCode[i] = hourlyCode[i] | weather.weatherCode; copyClockText(hourlyTime[i] | "", weather.hourlyTime[i]); } weather.currentRainChance = weather.hourlyRainChance[0]; // DAILY JsonArray dailyMax = doc["daily"]["temperature_2m_max"]; JsonArray dailyMin = doc["daily"]["temperature_2m_min"]; JsonArray dailyRain = doc["daily"]["precipitation_probability_max"]; JsonArray dailyCode = doc["daily"]["weather_code"]; JsonArray dailyDate = doc["daily"]["time"]; JsonArray dailySunrise = doc["daily"]["sunrise"]; JsonArray dailySunset = doc["daily"]["sunset"]; JsonArray dailyUV = doc["daily"]["uv_index_max"]; for (int i = 0; i < 3; i++) { weather.dailyTempMax[i] = dailyMax[i] | 0.0; weather.dailyTempMin[i] = dailyMin[i] | 0.0; weather.dailyRainChance[i] = dailyRain[i] | 0; weather.dailyWeatherCode[i] = dailyCode[i] | 0; weather.dailyUV[i] = dailyUV[i] | 0.0; const char* dateText = dailyDate[i] | ""; strncpy(weather.dailyDate[i], dateText, 10); weather.dailyDate[i][10] = '\0'; } if (dailySunrise.size() > 0) { copyClockText(dailySunrise[0] | "", weather.sunrise); } if (dailySunset.size() > 0) { copyClockText(dailySunset[0] | "", weather.sunset); } if (dailyUV.size() > 0) { weather.uvIndex = dailyUV[0] | 0.0; } weather.valid = true; return true; } // ===== FETCH WEATHER ===== bool fetchWeather() { if (WiFi.status() != WL_CONNECTED) { Serial.println("WIFI NOT CONNECTED"); apiOnline = false; screenDirty = true; return false; } Serial.println(); Serial.println("================================"); Serial.println("FETCHING WEATHER"); Serial.println("================================"); isFetching = true; // Loading screen is shown only once. drawLoadingScreen("FETCHING WEATHER..."); // WiFiSSLClient does the HTTPS part on the Arduino UNO R4. // The root certificates live in the WiFi firmware, so there // is no certificate to paste into this sketch. WiFiSSLClient sslClient; HttpClient http(sslClient, API_HOST, API_PORT); http.setTimeout(15000); http.setHttpResponseTimeout(15000); String path = getWeatherPath(); Serial.print("GET https://"); Serial.print(API_HOST); Serial.println(path); int error = http.get(path); if (error != 0) { Serial.print("HTTP REQUEST FAILED: "); Serial.println(error); http.stop(); isFetching = false; apiOnline = false; screenDirty = true; return false; } int statusCode = http.responseStatusCode(); Serial.print("HTTP CODE: "); Serial.println(statusCode); if (statusCode != 200) { Serial.println("API ERROR"); http.stop(); isFetching = false; apiOnline = false; screenDirty = true; return false; } // responseBody() also takes care of a chunked answer. String payload = http.responseBody(); http.stop(); Serial.print("PAYLOAD SIZE: "); Serial.println(payload.length()); bool result = parseWeather(payload); // The text is not needed any more, so the RAM is given back // before the screen is drawn. payload = ""; isFetching = false; if (result) { apiOnline = true; lastApiUpdate = millis(); Serial.println("WEATHER UPDATED"); Serial.print("Temperature: "); Serial.println(weather.temperature); Serial.print("Humidity: "); Serial.println(weather.humidity); Serial.print("Weather: "); Serial.println(weatherDescription(weather.weatherCode)); Serial.print("Wind: "); Serial.println(weather.windSpeed); Serial.print("Rain Chance: "); Serial.println(weather.currentRainChance); screenDirty = true; return true; } apiOnline = false; screenDirty = true; return false; } // ===== LOADING SCREEN ===== void drawLoadingScreen(String message) { tft.fillScreen(BLACK); drawCenteredText("SMART WEATHER", 48, 2, CYAN); drawCenteredText("STATION", 73, 2, WHITE); // Loading ring for (int i = 0; i < 12; i++) { float angle = i * 2.0 * PI / 12.0; int x = CENTER_X + cos(angle) * 42; int y = CENTER_Y + sin(angle) * 42; uint16_t color = (i == 0) ? CYAN : DARK_GRAY; tft.fillCircle(x, y, 4, color); } drawCenteredText(message, 182, 1, WHITE); } // ===== WIFI CONNECT ===== bool connectWiFi() { Serial.println(); Serial.println("CONNECTING TO WIFI..."); tft.fillScreen(BLACK); drawCenteredText("CONNECTING", 26, 2, CYAN); drawWifiIcon(CENTER_X, 95, 0); drawCenteredText("WI-FI", 145, 2, WHITE); drawCenteredText(WIFI_SSID, 178, 1, GRAY); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); unsigned long start = millis(); int iconFrame = 0; while (WiFi.status() != WL_CONNECTED && millis() - start < WIFI_CONNECT_TIMEOUT) { drawWifiIcon(CENTER_X, 95, iconFrame % 4); iconFrame++; delay(250); Serial.print("."); } Serial.println(); if (WiFi.status() == WL_CONNECTED) { wifiOnline = true; wifiConnecting = false; drawWifiIcon(CENTER_X, 95, 3); Serial.println("WIFI CONNECTED"); Serial.print("IP: "); Serial.println(WiFi.localIP()); Serial.print("RSSI: "); Serial.print(WiFi.RSSI()); Serial.println(" dBm"); return true; } wifiOnline = false; Serial.println("WIFI CONNECTION FAILED"); return false; } // ===== START WIFI RECONNECT ===== void startWiFiReconnect() { Serial.println("START WIFI RECONNECT"); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); wifiConnecting = true; wifiConnectStart = millis(); } // ===== HANDLE WIFI ===== void handleWiFi() { int status = WiFi.status(); // Connected if (status == WL_CONNECTED) { if (!wifiOnline) { Serial.println("WIFI CONNECTED"); screenDirty = true; } wifiOnline = true; wifiConnecting = false; return; } wifiOnline = false; // Currently connecting if (wifiConnecting) { if (millis() - wifiConnectStart > WIFI_CONNECT_TIMEOUT) { Serial.println("WIFI CONNECT TIMEOUT"); WiFi.disconnect(); wifiConnecting = false; lastWifiRetry = millis(); } return; } // Start another attempt if (millis() - lastWifiRetry >= WIFI_RETRY_INTERVAL) { lastWifiRetry = millis(); startWiFiReconnect(); } } // ===== CHECK THE WIFI MODULE ===== bool checkWiFiModule() { if (WiFi.status() == WL_NO_MODULE) { Serial.println("WIFI MODULE NOT FOUND"); Serial.println("Use an Arduino UNO R4 WiFi board."); return false; } Serial.print("WIFI FIRMWARE: "); Serial.println(WiFi.firmwareVersion()); return true; } // ===== BUTTON HANDLER ===== void handleButton() { button.loop(); // MUST call the loop() function first // The moment the button goes down, remember when it happened. if (button.isPressed()) buttonPressStart = millis(); // The action is chosen when the button comes back up. if (button.isReleased()) { unsigned long duration = millis() - buttonPressStart; if (duration >= VERY_LONG_PRESS_TIME) { // VERY LONG PRESS -> back to the home page currentPage = PAGE_HOME; screenDirty = true; Serial.println("VERY LONG PRESS -> HOME"); } else if (duration >= LONG_PRESS_TIME) { // LONG PRESS -> ask the weather API for fresh data pendingRefresh = true; Serial.println("LONG PRESS -> REFRESH"); } else { // SHORT PRESS -> next page currentPage = (Page)((currentPage + 1) % PAGE_COUNT); screenDirty = true; Serial.print("SHORT PRESS -> PAGE "); Serial.println(currentPage + 1); } } } // ===== PROCESS MANUAL REFRESH ===== void handleManualRefresh() { if (!pendingRefresh) return; pendingRefresh = false; fetchWeather(); } // ===== AUTO API UPDATE ===== void handleApiUpdate() { if (!weather.valid) { return; } if (millis() - lastApiUpdate >= API_UPDATE_INTERVAL) { fetchWeather(); } } // ===== CLOCK ===== // The clock starts from the local time that came with the // weather answer, then millis() moves it forward. String getTimeString() { if (clockBaseSecond < 0) { return "--:--"; } unsigned long elapsed = (millis() - clockBaseMillis) / 1000UL; long nowSecond = clockBaseSecond + (long)elapsed; // Wrap around at midnight. nowSecond %= 86400L; int hour = nowSecond / 3600L; int minute = (nowSecond % 3600L) / 60L; char buffer[6]; sprintf(buffer, "%02d:%02d", hour, minute); return String(buffer); } // ===== HANDLE CLOCK ===== void handleClock() { if (currentPage != PAGE_HOME) { return; } if (millis() - lastClockUpdate < CLOCK_UPDATE_INTERVAL) { return; } lastClockUpdate = millis(); if (weather.valid && !isFetching) { drawHomeClock(); } } // ===== SETUP ===== void setup() { Serial.begin(9600); delay(500); Serial.println(); Serial.println("================================"); Serial.println("SMART WEATHER STATION"); Serial.println(LOCATION_NAME); Serial.println("================================"); // BUTTON // ezButton already set the pin to INPUT_PULLUP in its constructor. button.setDebounceTime(BUTTON_DEBOUNCE); // TFT tft.begin(); tft.setRotation(2); tft.fillScreen(BLACK); // BOOT SCREEN drawCenteredText("SMART WEATHER", 65, 2, CYAN); drawCenteredText("STATION", 92, 2, WHITE); drawCenteredText(LOCATION_NAME, 135, 2, YELLOW); delay(1500); // WIFI if (!checkWiFiModule()) { tft.fillScreen(BLACK); drawCenteredText("NO WIFI MODULE", 100, 2, RED); drawCenteredText("NEED UNO R4 WIFI", 135, 1, WHITE); // Nothing else can be done without the WiFi module. while (true) { delay(1000); } } if (connectWiFi()) { delay(500); fetchWeather(); } else { tft.fillScreen(BLACK); drawCenteredText("WIFI ERROR", 82, 2, RED); drawCenteredText("CHECK SETTINGS", 120, 1, WHITE); drawCenteredText("RETRYING...", 150, 1, GRAY); delay(1500); } screenDirty = true; } // ===== LOOP ===== void loop() { // Input handleButton(); // WiFi handleWiFi(); // Manual refresh handleManualRefresh(); // Automatic API update handleApiUpdate(); // Clock handleClock(); // Static page rendering if (screenDirty && !isFetching) { renderPage(); screenDirty = false; } // Rain animation updateRainAnimation(); }

Passos Rápidos

Siga estas instruções passo a passo:

  • Se esta é a sua primeira vez usando o Arduino Uno R4 WiFi, consulte o tutorial sobre como configurar o ambiente para o Arduino Uno R4 WiFi/Minima na Arduino IDE.
  • Ligue os componentes de acordo com o diagrama fornecido.
  • Conecte a placa Arduino Uno R4 ao seu computador usando um cabo USB.
  • Abra a Arduino IDE no seu computador.
  • Selecione a placa Arduino Uno R4 apropriada (Arduino Uno R4 WiFi) e a porta COM.
  • Vá ao ícone Libraries na barra esquerda da Arduino IDE.
  • Pesquise “DIYables TFT Round” e encontre a biblioteca DIYables_TFT_Round da DIYables.
  • Clique no botão Install para instalar a biblioteca.
Instalação da biblioteca de TFT LCD do Arduino UNO R4
  • Você pode ser solicitado a instalar dependências adicionais da biblioteca.
  • Clique no botão Install All para instalar todas as dependências da biblioteca.
Instalação das dependências do TFT LCD do Arduino UNO R4
  • Pesquise “ArduinoJson”, encontre a biblioteca ArduinoJson de Benoit Blanchon e clique em Install.
  • Pesquise “ArduinoHttpClient”, encontre a biblioteca ArduinoHttpClient da Arduino e clique em Install.
  • Pesquise “ezButton”, encontre a biblioteca ezButton de ArduinoGetStarted e clique em Install.
  • Copie o código acima e cole-o no editor da Arduino IDE.
  • Mude WIFI_SSID e WIFI_PASSWORD para o nome e a senha do seu próprio WiFi.
  • Mude LATITUDE, LONGITUDE, LOCATION_NAME e TIMEZONE para a sua própria cidade. Veja a seção abaixo.
  • Clique no botão Upload na Arduino IDE para carregar o código no Arduino UNO R4.
  • Abra o Serial Monitor na Arduino IDE e defina o baud rate como 9600.
  • Espere alguns segundos. O display mostra a tela de boot, depois a tela de WiFi e depois o clima.
  • Pressione o botão para ir para a próxima página.
  • Confira o resultado no Serial Monitor.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
Arduino Uno R4 WiFi
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'Arduino Uno R4 WiFi' on 'COM15')
New Line
9600 baud
================================ SMART WEATHER STATION SEOUL ================================ WIFI FIRMWARE: 0.5.0 CONNECTING TO WIFI... ..... WIFI CONNECTED IP: 192.168.0.24 RSSI: -47 dBm ================================ FETCHING WEATHER ================================ GET https://api.open-meteo.com/v1/forecast?latitude=37.5665&longitude=126.9780¤t=... HTTP CODE: 200 PAYLOAD SIZE: 1754 WEATHER UPDATED Temperature: 24.60 Humidity: 55.00 Weather: PARTLY CLOUDY Wind: 9.30 Rain Chance: 21 SHORT PRESS -> PAGE 2 SHORT PRESS -> PAGE 3 LONG PRESS -> REFRESH VERY LONG PRESS -> HOME
Ln 11, Col 1
Arduino Uno R4 WiFi on COM15
2

Como Usar o Botão

Um botão faz três tarefas. A diferença é quanto tempo você o mantém pressionado:

  • Toque curto (menos de 0.6 segundo): vai para a próxima página. Depois da última página, volta para a página inicial.
  • Toque longo (0.6 a 2 segundos): pede dados novos à API de clima agora mesmo.
  • Toque muito longo (mais de 2 segundos): volta direto para a página inicial.

O código usa a biblioteca ezButton para o botão. A biblioteca remove o bounce do contato sozinha, então um toque nunca é contado duas vezes, e o sketch só precisa medir por quanto tempo o botão ficou pressionado.

ezButton button(PIN_BUTTON);

No setup(), defina o tempo de debounce. A biblioteca já configurou o pino como INPUT_PULLUP quando o objeto foi criado, então nenhuma linha pinMode() é necessária.

button.setDebounceTime(40);

No loop, chame button.loop() primeiro e depois leia os dois eventos.

button.loop(); // MUST call the loop() function first if (button.isPressed()) buttonPressStart = millis(); if (button.isReleased()) { unsigned long duration = millis() - buttonPressStart; // duration decides the action }

Se você quiser saber mais sobre isso, veja o tutorial Arduino UNO R4 - Botão com Toque Longo e Toque Curto.

Como Mudar a Localização

O código vem com Seul, na Coreia do Sul. Para mostrar a sua própria cidade, mude quatro linhas no início do código:

#define LATITUDE 37.5665 #define LONGITUDE 126.9780 #define LOCATION_NAME "SEOUL" #define TIMEZONE "Asia%2FSeoul"
  • LATITUDE e LONGITUDE: a posição da sua cidade. Você pode encontrá-las em qualquer site de mapas.
  • LOCATION_NAME: o nome curto mostrado no display. Mantenha-o curto para que caiba na tela redonda.
  • TIMEZONE: o nome do fuso horário, por exemplo Europe/Berlin ou America/New_York. O caractere / deve ser escrito como %2F, porque o nome vai dentro de um endereço web. Então Europe/Berlin vira Europe%2FBerlin.

※ Nota:

A temperatura está em Celsius e a velocidade do vento está em km/h. Para mudá-las, edite as partes temperature_unit e wind_speed_unit na função getWeatherPath(). Por exemplo, use fahrenheit e mph.

Explicação do Código

Confira as explicações dadas nos comentários do código-fonte para cada linha!

Solução de Problemas

  • O display fica preto. Verifique os fios SCL e SDA. No Arduino UNO R4, eles devem ir ao D13 e ao D11. Esses dois pinos não podem ser trocados por outros pinos.
  • A tela mostra "NO WIFI MODULE". Você está usando um Arduino UNO R4 Minima. Este projeto precisa do Arduino UNO R4 WiFi.
  • A tela mostra "WIFI ERROR". Verifique o nome e a senha do WiFi. O Arduino UNO R4 WiFi só funciona com redes de 2.4 GHz, não de 5 GHz.
  • O Serial Monitor mostra um erro HTTP. Atualize o firmware do WiFi do seu Arduino UNO R4 WiFi. Um firmware antigo pode não ter o certificado raiz correto. Veja como atualizar o firmware do Arduino UNO R4 WiFi.
  • O relógio está errado. O relógio usa a hora que vem junto com a resposta do clima. Verifique se o valor de TIMEZONE corresponde à sua cidade.

Vídeo

Assista abaixo ao vídeo passo a passo deste projeto.

※ NOSSAS MENSAGENS

  • Fique à vontade para compartilhar o link deste tutorial. Porém, por favor, não use nosso conteúdo em outros sites. Investimos muito esforço e tempo para criar este conteúdo, por favor respeite nosso trabalho!