Hardware Prototyping

Monitoramento Ambiental com STM32 Nucleo L476RG e DHT22: Um Guia Prático

A construção de projetos de monitoramento ambiental é fundamental para diversas aplicações, desde a agricultura até residências inteligentes. Hoje, quero explorar a construção de um protótipo de um sistema de visualização de temperatura e umidade usando o microcontrolador STM32 Nucleo L476RG. Este projeto envolve a integração de sensores, um display TFT e o uso da programação 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 através de um display gráfico. Além disso, o sistema registra os valores máximos e mínimos atingidos nas ú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: Uma plataforma de prototipagem que oferece um equilíbrio entre desempenho e custo.
  • DHT22: Sensor de temperatura e umidade, conhecido por sua precisão e durabilidade.
  • MCUFRIEND_kbv: Display TFT compatível com diversos controladores gráficos.

Pinagem

Sensor DHT22
  • VCC: O pino de alimentação (VCC) do DHT22 é conectado ao pino de 3.3V da placa STM32 Nucleo L476RG.
  • GND: O pino de terra (GND) do DHT22 é conectado ao pino GND da placa STM32.
  • DHTPIN (PB7): Este pino é utilizado para a comunicação de dados entre o sensor DHT22 e a placa STM32. .
Conexão do Display MCUFRIEND_kbv
  • LCD_CS (A3): Pino de Chip Select, que é usado para ativar e desativar o sinal do display no bus de dados.
  • LCD_CD (A2): Pino de Command/Data.
  • LCD_WR (A1): Pino de escrita.
  • LCD_RD (A0): Pino de leitura.
  • LCD_RESET (A4): Pino de reset, utilizado para redefinir o display. Um pulso neste pino reinicializa o display.

Configuração do Projeto no VSCode com PlatformIO

O arquivo platformio.ini define as bibliotecas necessárias e a configuração do ambiente:

[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
INI

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 emprega 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]; // Armazena as leituras de temperatura
float humidityReadings[NUM_READINGS]; // Armazena as leituras de umidade
int readingIndex = 0; // Índice atual para inserção de nova leitura
C++
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 não preenchida
}
C++
Mapeamento de Cores de Acordo com os Valores

As cores das barras de progresso mudam de acordo com os valores de temperatura e umidade para uma interpretação visual imediata:

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);
}
C++
Uso do Indicador para Agrupar Elementos

O código utiliza uma função chamada drawIndicador para agrupar a exibição dos elementos no display, reduzindo a repetição de código:

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); // Limpa a área do indicador
  tft.setTextColor(cor);
  tft.print(indicador + ": " + String(value) + " " + unidade); // Valor atual
  tft.setTextColor(WHITE);
  tft.print("Min: " + String(min) + " | Max: " + String(max)); // Valores mínimo e máximo

  // Desenha a barra de progresso correspondente
  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));
  }
}
C++
Visualização de Dados com um gráfico

A visualização gráfica é uma parte fundamental deste projeto, permitindo uma compreensão rápida das tendências de temperatura e umidade ao longo das últimas 24h.

void drawTemperatureChart(int x, int y, int width, int height, float vector[], int tamanho, int iterator, uint16_t cor) {
  for (int i = 0; i < iterator; i++) {
    int y_ponto = static_cast<int>(round((vector[i] - 15) / (40 - 15) * height));
    int x_ponto = static_cast<int>(round(i / float(tamanho) * 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) {
  for (int i = 0; i < iterator; i++) {
    int y_ponto = static_cast<int>(round(vector[i] / 100.0 * height));
    int x_ponto = static_cast<int>(round(i / float(tamanho) * width));
    tft.drawPixel(x + x_ponto, y + height - y_ponto, cor);
  }
}
C++

Estas funções mapeiam as leituras armazenadas nos arrays em pontos que são desenhados no display TFT. O ajuste dos valores de y_ponto e x_ponto assegura que os gráficos se ajustem ao espaço disponível no display, enquanto preservam as relações de proporção adequadas.

Código completo

Você pode clonar o repositório do meu gitlab aqui

#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

// Define os pinos conforme seu hardware
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4

#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 dht(DHTPIN, DHTTYPE);
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;
  // Interpolação linear de azul para vermelho entre 10°C e 30°C
  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;
  // Interpolação linear de vermelho para azul entre 20% e 90%
  int blue = map(humidity, 20, 90, 0, 255);
  int red = map(humidity, 20, 90, 255, 0);
  return tft.color565(red, 0, blue);
}


// Função para desenhar a 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); // Área preenchida
  tft.fillRect(x + filledWidth, y, width - filledWidth, height, BLACK); // Área não preenchida
}

// Função para desenhar a barra de progresso
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.fillRect(10, 5, 320, 70, BLUE);
  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));
  }
}

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;

  //tft.fillRect(x, y, width, height, BLACK); // tft.fillRect(10, 5, 320, 70, BLUE);
  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;

  //tft.fillRect(x, y, width, height, BLACK);
  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); // Ajuste conforme a orientação desejada
  tft.fillScreen(BLACK);
  tft.setTextColor(WHITE);
  tft.setTextSize(2);

  Serial.begin(9600);
  while (!Serial); // Espera a conexão do serial
  delay(1000); // Pequeno atraso após abrir a conexão serial
  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("------------------------------------"));
  // Print humidity sensor details.
  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.print(tft.height()); Serial.println(F(""));
  Serial.print  (F("Display Width:  ")); Serial.print(tft.width()); Serial.println(F(""));

  delayMS = 60 * 1000;
}

void loop() {
  sensors_event_t event;
  dht.temperature().getEvent(&event);
  float tempValue = isnan(event.temperature) ? 0 : event.temperature;

  dht.humidity().getEvent(&event);
  float humidityValue = isnan(event.relative_humidity) ? 0 : event.relative_humidity;

  tempReadings[readingIndex] = tempValue;
  humidityReadings[readingIndex] = humidityValue;

  readingIndex = (readingIndex + 1) % NUM_READINGS;
  readingsCount = min(readingsCount + 1, NUM_READINGS);

  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]);
  }

  if (!isnan(event.temperature)) {
    drawIndicador(10,5,tempValue,tempMin,tempMax,"Temperatura","C", BLUE);
    drawTemperatureChart(10,160, 300, 60,tempReadings,NUM_READINGS, readingsCount, BLUE);
  }

  if (!isnan(event.relative_humidity)) {
    drawIndicador(10,80,humidityValue,humidityMin,humidityMax,"Umidade","%", GREEN);
    drawHumidityChart(10,160, 300, 60,humidityReadings,NUM_READINGS, readingsCount, GREEN);
  }

  delay(delayMS);
}
C++
Expand