Construa uma Estação Meteorológica Wireless Confiável com Arduino
Neste tutorial, aprenderemos a fazer uma Estação Meteorológica Wireless baseada em Arduino. Você pode assistir ao vídeo a seguir ou ler o tutorial escrito abaixo.
Visão Geral
A temperatura e umidade externas são medidas usando o sensor DHT22 e estes dados são enviados wirelessmente para a unidade interna usando os módulos transceptores NRF24L01. Na unidade interna, há também outro sensor DHT22 para medir a temperatura e umidade internas, bem como um módulo DS3231 Real Time Clock que mantém o controle do tempo mesmo se o Arduino perder energia. Todos esses dados são exibidos em um display OLED de 0,96".

Diagrama de Circuito da Estação Meteorológica Wireless
Vamos ver o diagrama de circuito e como este projeto funciona. Note que já tenho tutoriais detalhados sobre como cada um destes módulos funciona, então para mais detalhes você pode conferir: Tutorial NRF24L01, Tutorial DHT22, Tutorial DS3231.

Você pode obter os componentes necessários para este projeto nos links abaixo:
- Módulo Transceptor NRF24L01
- Sensor DHT22
- DS3231 Real Time Clock
- Arduino Nano
Tanto o módulo de relógio em tempo real quanto o display OLED usam o protocolo I2C para comunicação com o Arduino, então eles estão conectados aos pinos I2C ou pinos analógicos 4 e 5 no Arduino Nano. Ao lado do módulo transceptor NRF24L01 há um capacitor para manter a alimentação mais estável. Há também um resistor pull-up conectado ao pino de dados do DHT22 para que o sensor funcione corretamente.
Quanto à alimentação, usei um adaptador DC 12V para a unidade interna, e do outro lado, para alimentar a unidade externa usei duas baterias Li-ion produzindo cerca de 7,5V. Com esta configuração, a unidade externa pode funcionar por cerca de 10 dias antes das baterias descarregarem, porque transmitimos dados periodicamente e, enquanto isso, colocamos o Arduino em modo sleep, onde o consumo de energia é de apenas cerca de 7mA.
PCB Personalizado
Para manter os componentes eletrônicos organizados, de acordo com o diagrama de circuito, projetei um PCB personalizado usando o software gratuito de design de circuitos online EasyEDA. Podemos notar que o mesmo PCB pode ser usado tanto para a unidade interna quanto para a externa, apenas a placa Arduino deve ser programada de forma diferente.

Uma vez finalizado o design, podemos simplesmente exportar o arquivo Gerber usado para fabricação do PCB. Você pode conferir os arquivos do projeto EasyEDA da estação meteorológica wireless Arduino aqui.
Então podemos pedir nosso PCB na JLCPCB, que é na verdade o patrocinador deste vídeo.

Aqui podemos simplesmente arrastar e soltar o arquivo Gerber e, uma vez carregado, podemos revisar nosso PCB no visualizador Gerber. Se tudo estiver certo, podemos prosseguir, selecionar as propriedades que queremos para nosso PCB, e então pedir nosso PCB a um preço razoável. Note que se for seu primeiro pedido na JLCPCB, você pode obter até 10 PCBs por apenas $2.

Após alguns dias, os PCBs chegaram. A qualidade dos PCBs é ótima e tudo é exatamente igual ao design.

Comecei a montar os componentes eletrônicos deste projeto soldando headers de pinos no PCB. Dessa forma, podemos conectar e desconectar os componentes facilmente quando necessário.

Então também inseri e soldei o capacitor e o resistor pull-up. Com esta etapa concluída, agora podemos simplesmente anexar os componentes nos headers de pinos do PCB.

Em seguida, passei a fazer os gabinetes para o projeto. Para isso, usei placa MDF de 8mm e, usando uma serra circular, cortei todas as peças no tamanho certo.

Para ter medições precisas de temperatura e umidade, as laterais dos gabinetes devem permitir a entrada de ar. Então, usando uma furadeira e uma lima, fiz vários slots nos painéis laterais tanto na unidade interna quanto na externa.

Também fiz um slot para o display OLED no painel frontal, bem como cortei um pequeno pedaço de alumínio no tamanho certo que depois fixarei no painel frontal como decoração.

Para montar os gabinetes, usei cola de madeira e alguns grampos, bem como alguns parafusos.

Pintei os gabinetes usando tinta spray. Usei tinta branca para a unidade externa e preta para a interna. Após a tinta secar, simplesmente inseri os PCBs nos gabinetes.

Na parte traseira da unidade interna, inseri um conector de energia e um interruptor, e na unidade externa usei um simples jumper como interruptor de energia.

E pronto, nossa estação meteorológica wireless Arduino está funcionando, mas o que resta neste vídeo é ver como o programa funciona.
Código da Estação Meteorológica Wireless Arduino
Código da unidade externa (Transmissor)
/* Arduino Wireless Communication Tutorial
Outdoor unit - Transmitter
by Dejan Nedelkovski, www.HowToMechatronics.com
Libraries: NRF24L01 - TMRh20/RF24, https://github.com/tmrh20/RF24/
DHT22 - DHTlib, https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
LowPower - https://github.com/rocketscream/Low-Power */
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <dht.h>
#include <LowPower.h>
#define dataPin 8 // Pino de dados do DHT22
dht DHT; // Cria um objeto DHT
RF24 radio(10, 9); // CE, CSN
const byte address[6] = "00001";
char thChar[32] = "";
String thString = "";
void setup() {
radio.begin();
radio.openWritingPipe(address);
radio.setPALevel(RF24_PA_MIN);
radio.stopListening();
}
void loop() {
int readData = DHT.read22(dataPin); // Lê os dados do sensor
int t = DHT.temperature; // Obtém o valor da temperatura
int h = DHT.humidity; // Obtém o valor da umidade
thString = String(t) + String(h);
thString.toCharArray(thChar, 12); // Envia os dados wirelessmente para a unidade interna
for (int i = 0; i <= 3; i++) { // Envia os dados 3 vezes
radio.write(&thChar, sizeof(thChar));
delay(50);
}
// Sleep por 2 minutos, 15*8 = 120s
for (int sleepCounter = 15; sleepCounter > 0; sleepCounter--) {
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
}
Descrição: A unidade externa é o transmissor da comunicação wireless, então aqui primeiro precisamos incluir a biblioteca RF24, a biblioteca DHT, bem como a biblioteca LowPower usada para colocar o Arduino em modo sleep.
Após definir suas instâncias, os pinos aos quais os módulos estão conectados e algumas variáveis, na seção setup() precisamos inicializar o endereço da comunicação wireless. Então no loop(), primeiro lemos os dados do sensor DHT22 — temperatura e umidade. Inicialmente esses valores são inteiros e separados, então os converto em uma única variável String, coloco no array de caracteres e, usando a função radio.write(), envio esses dados para a unidade interna. Usando o loop for, enviamos os dados 3 vezes para garantir que o receptor receberá os dados caso o controlador esteja ocupado no momento do envio.
No final, colocamos o Arduino em modo sleep por um período específico para minimizar o consumo de energia.
Código da unidade interna (Receptor)
/* Arduino Wireless Communication Tutorial
Indoor unit - Receiver
by Dejan Nedelkovski, www.HowToMechatronics.com
Libraries: DS3231 - https://www.rinkydinkelectronics.com/library.php?id=73
U8G2 - https://github.com/olikraus/u8g2 */
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <dht.h>
#include <DS3231.h>
#include <U8g2lib.h>
#include <Wire.h>
#define dataPin 8 // Sensor DHT22
dht DHT; // Cria um objeto DHT
DS3231 rtc(SDA, SCL);
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
RF24 radio(10, 9); // CE, CSN
const byte address[6] = "00001";
char text[6] = "";
int readDHT22, t, h;
String inTemp, inHum, outTemp, outHum;
String rtcTime, rtcDate;
int draw_state = 0;
unsigned long previousMillis = 0;
long interval = 3000;
#define Temperature_20Icon_width 27
#define Temperature_20Icon_height 47
static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0xc0, 0xe1, 0x00, 0x00,
0xe0, 0xc0, 0x01, 0x00, 0x60, 0x80, 0xf9, 0x03, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00,
0x60, 0x80, 0x79, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0xf9, 0x03,
0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x8c, 0x79, 0x00, 0x60, 0x9e, 0x01, 0x00,
0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0xf9, 0x03, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00,
0x60, 0x9e, 0x79, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0xf9, 0x03,
0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x70, 0x9e, 0x03, 0x00,
0x38, 0x1e, 0x07, 0x00, 0x18, 0x3e, 0x0e, 0x00, 0x1c, 0x3f, 0x0c, 0x00, 0x0c, 0x7f, 0x18, 0x00,
0x8c, 0xff, 0x18, 0x00, 0x8e, 0xff, 0x38, 0x00, 0xc6, 0xff, 0x31, 0x00, 0xc6, 0xff, 0x31, 0x00,
0xc6, 0xff, 0x31, 0x00, 0x8e, 0xff, 0x38, 0x00, 0x8c, 0xff, 0x18, 0x00, 0x0c, 0x7f, 0x1c, 0x00,
0x3c, 0x1c, 0x0e, 0x00, 0x78, 0x00, 0x06, 0x00, 0xe0, 0x80, 0x07, 0x00, 0xe0, 0xff, 0x03, 0x00,
0x80, 0xff, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
#define Humidity_20Icon_width 27
#define Humidity_20Icon_height 47
static const unsigned char Humidity_20Icon_bits[] U8X8_PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00,
0x00, 0xf8, 0x00, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00, 0xdc, 0x01, 0x00, 0x00, 0x8e, 0x01, 0x00,
0x00, 0x86, 0x03, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x03, 0x07, 0x00, 0x80, 0x03, 0x06, 0x00,
0x80, 0x01, 0x0c, 0x00, 0xc0, 0x01, 0x1c, 0x00, 0xc0, 0x00, 0x18, 0x00, 0xe0, 0x00, 0x38, 0x00,
0x60, 0x00, 0x30, 0x00, 0x70, 0x00, 0x70, 0x00, 0x30, 0x00, 0xe0, 0x00, 0x38, 0x00, 0xc0, 0x00,
0x18, 0x00, 0xc0, 0x01, 0x1c, 0x00, 0x80, 0x01, 0x0c, 0x00, 0x80, 0x03, 0x0e, 0x00, 0x80, 0x03,
0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, 0x06,
0x03, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x06, 0x63, 0x00, 0x00, 0x06, 0x63, 0x00, 0x00, 0x06,
0x63, 0x00, 0x00, 0x06, 0xe3, 0x00, 0x00, 0x06, 0xc7, 0x00, 0x00, 0x06, 0xc6, 0x01, 0x00, 0x07,
0x86, 0x03, 0x00, 0x03, 0x0e, 0x1f, 0x00, 0x03, 0x0e, 0x1e, 0x80, 0x01, 0x1c, 0x00, 0xc0, 0x01,
0x38, 0x00, 0xe0, 0x00, 0x78, 0x00, 0x70, 0x00, 0xf0, 0x00, 0x38, 0x00, 0xe0, 0x07, 0x1f, 0x00,
0x80, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00
};
void setup() {
radio.begin();
radio.openReadingPipe(0, address);
radio.setPALevel(RF24_PA_MIN);
radio.startListening();
u8g2.begin();
rtc.begin();
}
void loop() {
if (radio.available()) {
radio.read(&text, sizeof(text)); // Lê dados de entrada
outTemp = String(text[0]) + String(text[1]) + char(176) + "C"; // Temperatura Externa
outHum = String(text[2]) + String(text[3]) + "%"; // Umidade Externa
}
unsigned long currentMillis = millis();
if (currentMillis - previousMillis > interval) {
previousMillis = currentMillis;
u8g2.firstPage();
do {
switch (draw_state ) {
case 0: drawDate(); break;
case 1: drawInTemperature(); break;
case 2: drawInHumidity(); break;
case 3: drawOutTemperature(); break;
case 4: drawOutHumidity(); break;
}
} while ( u8g2.nextPage() );
draw_state++;
if (draw_state > 4) { draw_state = 0; }
}
}
void drawDate() {
String dowa = rtc.getDOWStr();
dowa.remove(3);
rtcDate = dowa + " " + rtc.getDateStr();
u8g2.setFont(u8g2_font_timB14_tr);
u8g2.setCursor(0, 15);
rtcTime = rtc.getTimeStr(); // Hora do DS3231 RTC
rtcTime.remove(5);
u8g2.print(rtcDate);
u8g2.setFont(u8g2_font_fub30_tf);
u8g2.setCursor(8, 58);
u8g2.print(rtcTime);
}
void drawInTemperature() {
readDHT22 = DHT.read22(dataPin); // Lê os dados do sensor
t = DHT.temperature; // Obtém o valor da temperatura
inTemp = String(t) + char(176) + "C";
u8g2.setFont(u8g2_font_helvR14_tr);
u8g2.setCursor(24, 15);
u8g2.print("INDOOR");
u8g2.setFont(u8g2_font_fub30_tf);
u8g2.setCursor(36, 58);
u8g2.print(inTemp);
u8g2.drawXBMP( 0, 17, Temperature_20Icon_width, Temperature_20Icon_height, Temperature_20Icon_bits);
}
void drawInHumidity() {
h = DHT.humidity; // Obtém o valor da umidade
inHum = String(h) + "%";
u8g2.setFont(u8g2_font_helvR14_tr);
u8g2.setCursor(24, 15);
u8g2.print("INDOOR");
u8g2.setFont(u8g2_font_fub30_tf);
u8g2.setCursor(36, 58);
u8g2.print(inHum);
u8g2.drawXBMP( 0, 17, Humidity_20Icon_width, Humidity_20Icon_height, Humidity_20Icon_bits);
}
void drawOutTemperature() {
u8g2.setFont(u8g2_font_helvR14_tr);
u8g2.setCursor(12, 15);
u8g2.print("OUTDOOR");
u8g2.setFont(u8g2_font_fub30_tf);
u8g2.setCursor(36, 58);
u8g2.print(outTemp);
u8g2.drawXBMP( 0, 17, Temperature_20Icon_width, Temperature_20Icon_height, Temperature_20Icon_bits);
}
void drawOutHumidity() {
u8g2.setFont(u8g2_font_helvR14_tr);
u8g2.setCursor(12, 15);
u8g2.print("OUTDOOR");
u8g2.setFont(u8g2_font_fub30_tf);
u8g2.setCursor(36, 58);
u8g2.print(outHum);
u8g2.drawXBMP( 0, 17, Humidity_20Icon_width, Humidity_20Icon_height, Humidity_20Icon_bits);
}
Descrição: Do outro lado, na unidade interna ou receptora, precisamos incluir mais duas bibliotecas: uma para o módulo DS3231 real time clock e outra para o display OLED, a biblioteca U8G2. Da mesma forma que anteriormente, precisamos definir as instâncias, os pinos e algumas variáveis necessárias para o programa abaixo. Também aqui precisamos definir os ícones de temperatura e umidade como bitmaps.
Bitmap do ícone de temperatura:
#define Temperature_20Icon_width 27
#define Temperature_20Icon_height 47
static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM = { ... };
Para este propósito, podemos usar o GIMP, um editor de imagens open source, através do qual podemos desenhar qualquer coisa e depois exportar como bitmap (.xbm).

Então podemos abrir este arquivo usando um bloco de notas e copiar o bitmap para o código Arduino.

Note que aqui podemos definir o bitmap como constante usando o modificador de variável PROGMEM, e dessa forma o bitmap será armazenado na memória flash em vez da SRAM da placa Arduino.
static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM // Salva na memória Flash static unsigned char Temperature_20Icon_bits[] // Salva na SRAM
Na seção setup(), precisamos inicializar a comunicação wireless, bem como inicializar o display OLED e o módulo real time clock.
Então no loop(), verificamos constantemente se há dados de entrada disponíveis para serem lidos via módulos NRF24L01. Se verdadeiro, usando a função radio.read(), lemos e armazenamos os dois primeiros caracteres na variável String de temperatura, e os próximos dois caracteres na variável String de umidade.
Então usamos a função millis() para exibir os diversos dados no display em intervalos definidos com a variável interval, que defini como 3 segundos. Usamos millis() porque assim o resto do código pode ser executado repetidamente, enquanto se usássemos delay(), o programa esperaria por aquele período e provavelmente perderíamos os dados de entrada da unidade externa.
Em seguida, usando as funções firstPage() e nextPage() da biblioteca U8G2, imprimimos as cinco telas diferentes definidas com as funções personalizadas.
A função personalizada drawDate() obtém as informações de data e hora do módulo real time clock e imprime apropriadamente no display. A função drawInTemperature() lê a temperatura interna e imprime apropriadamente no display. Na verdade, o mesmo método é usado para imprimir todas as telas no display.
É isso, espero que você tenha gostado deste projeto Arduino e aprendido algo novo. Sinta-se à vontade para fazer qualquer pergunta na seção de comentários abaixo.
Processo de manufatura
- Estação Meteorológica Solar DIY com Raspberry Pi
- Construindo Estação Meteorológica Externa com Raspberry Pi 2 e ADS‑WS1
- Construa Estação Meteorológica Raspberry Pi que Envia Dados Diários por Email
- Estação Meteorológica Avançada v2.0: Monitoramento em Tempo Real de Temperatura, Pressão, Umidade e Altitude
- Detector de Frequência de Áudio com Arduino – Meça Picos de Som com Módulo de Alta Sensibilidade
- Controlador Inteligente de Interruptor de Luz Sem Fio: Manipulador Remoto Baseado em Arduino
- Jogo da Velha com Arduino, Matriz LED MAX7219 e Caixa de Papelão
- Construa um Temporizador Regressivo Confiável com Arduino e Display de 7 Segmentos SparkFun
- Estação Meteorológica Portátil Arduino por $10 – DIY com Nano, DHT22, BMP180 e LCD
- eDOT: Relógio de Precisão e Estação Meteorológica Arduino com Controle IR Embutido