A construção de projetos de monitoramento ambiental é fundamental para diversas aplicações, desde a agricultura até residências inteligentes. Neste artigo, exploramos a construção de um protótipo de sistema de visualização de temperatura e umidade usando o microcontrolador STM32 Nucleo L476RG, um sensor DHT22 e um display TFT MCUFRIEND_kbv, programados em C++ com o framework Arduino.
Objetivo do Projeto
O código proposto permite monitorar continuamente a temperatura e a umidade do ambiente, exibindo os resultados em tempo real em um display gráfico. Além disso, o sistema registra os valores máximos e mínimos das últimas 24h e apresenta um gráfico simples que ajuda a visualizar as tendências ao longo do tempo.
Hardware Necessário
- STM32 Nucleo L476RG – plataforma de prototipagem com bom equilíbrio entre desempenho e custo.
- DHT22 – sensor de temperatura e umidade, conhecido por precisão e durabilidade.
- Display TFT MCUFRIEND_kbv – compatível com diversos controladores gráficos, usado para visualização.
Pinagem

Sensor DHT22
- VCC: conectado ao pino 3.3V da placa STM32 Nucleo L476RG.
- GND: conectado ao pino GND da placa STM32.
- DHTPIN (PB7): pino de dados entre o DHT22 e a STM32.
Conexão do Display MCUFRIEND_kbv
- LCD_CS (A3): Chip Select, ativa/desativa o display no barramento.
- LCD_CD (A2): Command/Data.
- LCD_WR (A1): pino de escrita.
- LCD_RD (A0): pino de leitura.
- LCD_RESET (A4): pino de reset do display.
Configuração do Projeto no VSCode com PlatformIO
O arquivo platformio.ini define o ambiente e as bibliotecas necessárias:
[env:nucleo_l476rg]
platform = ststm32
board = nucleo_l476rg
framework = arduino
lib_deps =
adafruit/DHT sensor library@^1.4.6
adafruit/Adafruit GFX Library@^1.11.9
prenticedavid/MCUFRIEND_kbv@^3.1.0-Beta
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.3
Análise do Código-Fonte
Armazenamento de Dados das Últimas 24 Horas
Para monitorar as condições ambientais ao longo de um dia, o código utiliza arrays para armazenar as leituras de temperatura e umidade:
const int NUM_READINGS = 1440; // Total de leituras para 24 horas com intervalos de um minuto
float tempReadings[NUM_READINGS]; // Leituras de temperatura
float humidityReadings[NUM_READINGS]; // Leituras de umidade
int readingIndex = 0; // Índice atual para nova leitura
int readingsCount = 0; // Qtde efetiva de leituras registradas
Cada minuto uma nova leitura é inserida, e o índice é atualizado em esquema circular, mantendo sempre as últimas 24h de dados.
Visualização de Dados com Barras de Progresso
Para facilitar a visualização das leituras atuais em relação aos valores mínimos e máximos, o código utiliza barras de progresso dinâmicas:
void drawProgressBar(int x, int y, int width, int height,
int value, int max, uint16_t barColor) {
int filledWidth = (int)((width * value) / max);
tft.fillRect(x, y, filledWidth, height, barColor); // Área preenchida
tft.fillRect(x + filledWidth, y, width - filledWidth, height, BLACK); // Área vazia
}
Mapeamento de Cores para Temperatura e Umidade
As cores das barras mudam de acordo com os valores lidos, permitindo uma leitura visual rápida:
uint16_t getTemperatureColor(int temp) {
if (temp >= 30) return RED;
if (temp < 10) return BLUE;
int red = map(temp, 10, 30, 0, 255);
int blue = map(temp, 10, 30, 255, 0);
return tft.color565(red, 0, blue);
}
uint16_t getHumidityColor(int humidity) {
if (humidity <= 30) return RED;
if (humidity > 60) return BLUE;
int blue = map(humidity, 20, 90, 0, 255);
int red = map(humidity, 20, 90, 255, 0);
return tft.color565(red, 0, blue);
}
Uso de um Indicador Composto
A função drawIndicador agrupa elementos comuns para exibir o valor, mínimo, máximo e barra correspondente de forma compacta:
void drawIndicador(int x, int y,
float value, float min, float max,
String indicador, String unidade,
uint16_t cor) {
tft.fillRect(x, y, 320, 70, BLACK);
tft.setTextSize(2);
tft.setCursor(x, y + 5);
tft.setTextColor(cor);
tft.print(indicador + ": " + String(value) + " " + unidade);
tft.setTextColor(WHITE);
tft.setCursor(x, y + 30);
tft.setTextSize(1);
tft.print("Min: " + String(min) + " | Max: " + String(max));
if (indicador == "Temperatura") {
drawProgressBar(x, y + 45, 300, 20, value, 40, getTemperatureColor(value));
} else if (indicador == "Umidade") {
drawProgressBar(x, y + 45, 300, 20, value, 100, getHumidityColor(value));
}
}
Visualização Gráfica das Últimas 24h
A visualização gráfica é fundamental para entender tendências de temperatura e umidade nas últimas 24 horas:
void drawTemperatureChart(int x, int y, int width, int height,
float vector[], int tamanho, int iterator,
uint16_t cor) {
int y_ponto, x_ponto = 0;
for (int i = 0; i < iterator; i++) {
y_ponto = static_cast<int>(round((static_cast<float>(vector[i] - 15)
/ (40 - 15)) * height));
x_ponto = static_cast<int>(round((static_cast<float>(i)
/ static_cast<float>(tamanho)) * static_cast<float>(width)));
tft.drawPixel(x + x_ponto, y + height - y_ponto, cor);
}
}
void drawHumidityChart(int x, int y, int width, int height,
float vector[], int tamanho, int iterator,
uint16_t cor) {
int y_ponto, x_ponto = 0;
for (int i = 0; i < iterator; i++) {
y_ponto = static_cast<int>(round((static_cast<float>(vector[i])
/ 100.0) * height));
x_ponto = static_cast<int>(round((static_cast<float>(i)
/ static_cast<float>(tamanho)) * static_cast<float>(width)));
tft.drawPixel(x + x_ponto, y + height - y_ponto, cor);
}
}
Essas funções mapeiam os vetores de leituras para coordenadas de pixels, permitindo um gráfico compacto dentro dos limites físicos do display.
Código Completo
Abaixo está o código completo de exemplo usado no projeto. Ele integra leitura do DHT22, renderização no display TFT e armazenamento de um histórico de 24 horas:
#include <Arduino.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
#include <Adafruit_GFX.h> // Biblioteca gráfica base
#include <MCUFRIEND_kbv.h>
#define DHTPIN PB7
#define DHTTYPE DHT22
// Pinos do display
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4
// Definição de cores
#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define RGB(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3))
#define GREY RGB(127, 127, 127)
#define DARKGREY RGB(64, 64, 64)
#define TURQUOISE RGB(0, 128, 128)
#define PINK RGB(255, 128, 192)
#define OLIVE RGB(128, 128, 0)
#define PURPLE RGB(128, 0, 128)
#define AZURE RGB(0, 128, 255)
#define ORANGE RGB(255, 128, 64)
// DHT_Unified para obter metadados do sensor
DHT_Unified dht(DHTPIN, DHTTYPE);
MCUFRIEND_kbv tft;
uint32_t delayMS;
const int NUM_READINGS = 1440;
float tempReadings[NUM_READINGS];
float humidityReadings[NUM_READINGS];
int readingIndex = 0;
int readingsCount = 0;
float tempMin = 500;
float tempMax = -100;
float humidityMin = 100;
float humidityMax = 0;
// Função para obter a cor baseada na temperatura
uint16_t getTemperatureColor(int temp) {
if (temp >= 30) return RED;
if (temp < 10) return BLUE;
int red = map(temp, 10, 30, 0, 255);
int blue = map(temp, 10, 30, 255, 0);
return tft.color565(red, 0, blue);
}
// Função para obter a cor baseada na umidade
uint16_t getHumidityColor(int humidity) {
if (humidity <= 30) return RED;
if (humidity > 60) return BLUE;
int blue = map(humidity, 20, 90, 0, 255);
int red = map(humidity, 20, 90, 255, 0);
return tft.color565(red, 0, blue);
}
// Barra de progresso
void drawProgressBar(int x, int y, int width, int height,
int value, int max, uint16_t barColor) {
int filledWidth = (int)((width * value) / max);
tft.fillRect(x, y, filledWidth, height, barColor);
tft.fillRect(x + filledWidth, y, width - filledWidth, height, BLACK);
}
// Indicador completo (valor + min/max + barra)
void drawIndicador(int x, int y, float value, float min, float max,
String indicador, String unidade, uint16_t cor) {
tft.fillRect(x, y, 320, 70, BLACK);
tft.setTextSize(2);
tft.setCursor(x, y + 5);
tft.setTextColor(cor);
tft.print(indicador + ": " + String(value) + " " + unidade);
tft.setTextColor(WHITE);
tft.setCursor(x, y + 30);
tft.setTextSize(1);
tft.print("Min: " + String(min) + " | Max: " + String(max));
if (indicador == "Temperatura") {
drawProgressBar(x, y + 45, 300, 20, value, 40, getTemperatureColor(value));
} else if (indicador == "Umidade") {
drawProgressBar(x, y + 45, 300, 20, value, 100, getHumidityColor(value));
}
}
// Gráfico de temperatura
void drawTemperatureChart(int x, int y, int width, int height,
float vector[], int tamanho, int iterator,
uint16_t cor) {
int y_ponto, x_ponto = 0;
for (int i = 0; i < iterator; i++) {
y_ponto = static_cast<int>(round((static_cast<float>(vector[i] - 15)
/ (40 - 15)) * height));
x_ponto = static_cast<int>(round((static_cast<float>(i)
/ static_cast<float>(tamanho)) * static_cast<float>(width)));
tft.drawPixel(x + x_ponto, y + height - y_ponto, cor);
}
}
// Gráfico de umidade
void drawHumidityChart(int x, int y, int width, int height,
float vector[], int tamanho, int iterator,
uint16_t cor) {
int y_ponto, x_ponto = 0;
for (int i = 0; i < iterator; i++) {
y_ponto = static_cast<int>(round((static_cast<float>(vector[i])
/ 100) * height));
x_ponto = static_cast<int>(round((static_cast<float>(i)
/ static_cast<float>(tamanho)) * static_cast<float>(width)));
tft.drawPixel(x + x_ponto, y + height - y_ponto, cor);
}
}
void setup() {
tft.reset();
uint16_t identifier = tft.readID();
tft.begin(identifier);
tft.setRotation(1);
tft.fillScreen(BLACK);
tft.setTextColor(WHITE);
tft.setTextSize(2);
Serial.begin(9600);
while (!Serial);
delay(1000);
dht.begin();
Serial.println(F("Iniciando DHT22"));
sensor_t sensor;
dht.temperature().getSensor(&sensor);
Serial.println(F("------------------------------------"));
Serial.println(F("Temperature Sensor"));
Serial.print (F("Sensor Type: ")); Serial.println(sensor.name);
Serial.print (F("Driver Ver: ")); Serial.println(sensor.version);
Serial.print (F("Unique ID: ")); Serial.println(sensor.sensor_id);
Serial.print (F("Max Value: ")); Serial.print(sensor.max_value); Serial.println(F("°C"));
Serial.print (F("Min Value: ")); Serial.print(sensor.min_value); Serial.println(F("°C"));
Serial.print (F("Resolution: ")); Serial.print(sensor.resolution); Serial.println(F("°C"));
Serial.println(F("------------------------------------"));
dht.humidity().getSensor(&sensor);
Serial.println(F("Humidity Sensor"));
Serial.print (F("Sensor Type: ")); Serial.println(sensor.name);
Serial.print (F("Driver Ver: ")); Serial.println(sensor.version);
Serial.print (F("Unique ID: ")); Serial.println(sensor.sensor_id);
Serial.print (F("Max Value: ")); Serial.print(sensor.max_value); Serial.println(F("%"));
Serial.print (F("Min Value: ")); Serial.print(sensor.min_value); Serial.println(F("%"));
Serial.print (F("Resolution: ")); Serial.print(sensor.resolution); Serial.println(F("%"));
Serial.println(F("------------------------------------"));
Serial.print (F("Min Delay: ")); Serial.print(sensor.min_delay / 1000 / 1000); Serial.println(F("s"));
Serial.println(F("------------------------------------"));
Serial.print (F("Display Height: ")); Serial.println(tft.height());
Serial.print (F("Display Width: ")); Serial.println(tft.width());
delayMS = 60 * 1000;
}
void loop() {
sensors_event_t event;
// Temperatura
dht.temperature().getEvent(&event);
float tempValue = isnan(event.temperature) ? 0 : event.temperature;
// Umidade
dht.humidity().getEvent(&event);
float humidityValue = isnan(event.relative_humidity) ? 0 : event.relative_humidity;
// Atualiza buffers circulares
tempReadings[readingIndex] = tempValue;
humidityReadings[readingIndex] = humidityValue;
readingIndex = (readingIndex + 1) % NUM_READINGS;
readingsCount = min(readingsCount + 1, NUM_READINGS);
// Recalcula min e max
tempMin = tempMax = tempReadings[0];
humidityMin = humidityMax = humidityReadings[0];
for (int i = 0; i < readingsCount; i++) {
tempMin = min(tempMin, tempReadings[i]);
tempMax = max(tempMax, tempReadings[i]);
humidityMin = min(humidityMin, humidityReadings[i]);
humidityMax = max(humidityMax, humidityReadings[i]);
}
// Desenho no display
if (!isnan(tempValue)) {
drawIndicador(10, 5, tempValue, tempMin, tempMax, "Temperatura", "C", BLUE);
drawTemperatureChart(10, 160, 300, 60, tempReadings, NUM_READINGS, readingsCount, BLUE);
}
if (!isnan(humidityValue)) {
drawIndicador(10, 80, humidityValue, humidityMin, humidityMax, "Umidade", "%", GREEN);
drawHumidityChart(10, 160, 300, 60, humidityReadings, NUM_READINGS, readingsCount, GREEN);
}
delay(delayMS);
}
Conclusão
Este projeto mostra como combinar o STM32 Nucleo L476RG, o sensor DHT22 e um display TFT para criar um sistema completo de monitoramento ambiental com histórico de 24h e visualização gráfica. A mesma abordagem pode ser estendida para outros sensores e formas de comunicação, como Wi-Fi, MQTT e dashboards em nuvem.