Projeto de estação meteorológica sem fio Arduino
Neste tutorial vamos aprender como fazer uma estação meteorológica sem fio baseada em Arduino. Você pode assistir ao vídeo a seguir ou ler o tutorial escrito abaixo.
Visão geral
A temperatura externa e a umidade são medidas usando o sensor DHT22 e esses dados são enviados sem fio para a unidade interna usando os módulos transceptores NRF24L01. Na unidade interna, há também outro sensor DHT22 para medição de temperatura e umidade interna, além de um módulo DS3231 Real Time Clock que pode acompanhar o tempo mesmo que o Arduino perca energia. Todos esses dados são impressos em um display OLED de 0,96”.

Diagrama de circuito da estação meteorológica sem fio do Arduino
Vamos dar uma olhada no diagrama de circuito e como este projeto funciona. Observe que já tenho tutoriais detalhados de como cada um desses 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…………….……………………
- Relógio em tempo real DS3231………………….. Amazon / Banggood / AliExpress
- Arduino Nano …………………………….……..
Tanto o módulo de relógio em tempo real quanto o display OLED utilizam o protocolo I2C para comunicação com o Arduino para que sejam conectados aos pinos I2C ou aos pinos analógicos número 4 e 5 na placa Arduino Nano. Ao lado do módulo transceptor NRF24L01 há um capacitor para manter a alimentação do mesmo mais estável. Há também um resistor pull-up conectado ao pino de dados DHT22 para que o sensor funcione corretamente.
Quanto à fonte de alimentação, usei um adaptador de alimentação de 12V DC para a unidade interna e, por outro lado, para alimentar a unidade externa, usei duas baterias Li-on produzindo cerca de 7,5V. Com esta configuração a unidade externa poderia funcionar por cerca de 10 dias antes das baterias descarregarem, pois transmitimos dados periodicamente e enquanto isso colocamos o Arduino em modo de suspensão, onde o consumo de energia é de apenas 7mA.
PCB de design personalizado
A fim de manter os componentes eletrônicos organizados, de acordo com o diagrama de circuito, projetei uma PCB personalizada usando o software de design de circuito online gratuito EasyEDA. Podemos notar que a mesma placa de circuito impresso pode ser usada tanto para a unidade interna quanto para a unidade externa, apenas a placa Arduino deve ser programada de forma diferente.

Uma vez que terminamos o projeto aqui, podemos simplesmente exportar o arquivo Gerber que é usado para fabricar o PCB. Você pode verificar os arquivos do projeto EasyEDA da estação meteorológica sem fio Arduino aqui.
Então podemos encomendar nosso PCB da 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 bem, podemos continuar, selecionar as propriedades que queremos para o nosso PCB, e então podemos encomendar o nosso PCB a um preço razoável. Observe que, se for seu primeiro pedido da JLCPCB, você poderá obter até 10 PCBs por apenas US $ 2.

No entanto, depois de vários 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 os pinos na placa de circuito impresso. Desta forma, podemos conectar e desconectar facilmente os componentes quando necessário.

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

Em seguida, passei a fazer os casos para o projeto. Para isso usei uma placa de MDF de 8mm e com uma serra circular cortei todas as peças à medida.

Para obter medições precisas de temperatura e umidade, as laterais das caixas devem permitir que o ar entre na caixa. Assim, usando uma furadeira e uma grosa, fiz várias ranhuras nos painéis laterais das unidades interna e 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 que mais tarde anexarei no painel frontal como decoração.

Para montar os estojos usei cola de madeira e alguns grampos, além de alguns parafusos.

Pintei as caixas com tinta spray. Usei tinta branca para a unidade externa e preta para a unidade interna. Depois que a tinta secou, simplesmente inseri os PCBs nas caixas.

Na parte de trás da unidade interna, inseri um conector de alimentação e um interruptor de alimentação e, na unidade externa, usei um simples fio jumper como interruptor de alimentação.

E é isso, nossa estação meteorológica sem fio Arduino já está funcionando, mas o que resta neste vídeo é dar uma olhada em como o programa funciona.
Código da estação meteorológica sem fio do Arduino
Código da unidade externa da estação meteorológica Arduino:
/*
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 // DHT22 data pin
dht DHT; // Creates a DHT object
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); // Reads the data from the sensor
int t = DHT.temperature; // Gets the values of the temperature
int h = DHT.humidity; // Gets the values of the humidity
thString = String(t) + String(h);
thString.toCharArray(thChar, 12);
// Sent the data wirelessly to the indoor unit
for (int i = 0; i <= 3; i++) { // Send the data 3 times
radio.write(&thChar, sizeof(thChar));
delay(50);
}
// Sleep for 2 minutes, 15*8 = 120s
for (int sleepCounter = 15; sleepCounter > 0; sleepCounter--)
{
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
}
Code language: Arduino (arduino)
Descrição: A unidade externa é o transmissor da comunicação sem fio, então aqui primeiro precisamos incluir a biblioteca RF24, a biblioteca DHT, bem como a biblioteca LowPower que é usada para colocar o Arduino no modo de suspensão.
Após definir suas instâncias, os pinos aos quais os módulos estão conectados e algumas variáveis, na seção de configuração precisamos inicializar o endereço de comunicação sem fio. Então, na seção de loop, primeiro lemos os dados do sensor DHT22 ou são a temperatura e a umidade. Inicialmente esses valores são inteiros e separados, então eu os converto em uma única variável String, coloco-os no array de caracteres, e usando a função radio.write(), enviei esses dados para a unidade interna. Usando o loop for enviamos os dados 3 vezes para ter certeza de que o receptor receberá os dados caso o controlador esteja ocupado no momento do envio.
Ao final colocamos o Arduino em modo de suspensão por um determinado período de tempo para minimizar o consumo de energia.
Código da unidade interna da estação meteorológica Arduino:
/*
Arduino Wireless Communication Tutorial
Indoor unit - Receiver
by Dejan Nedelkovski, www.HowToMechatronics.com
Libraries:
DS3231 - http://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 // DHT22 sensor
dht DHT; // Creats a DHT object
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)); // Read incoming data
outTemp = String(text[0]) + String(text[1]) + char(176) + "C"; // Outdoor Temperature
outHum = String(text[2]) + String(text[3]) + "%"; // Outdoor Humidity
}
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(); // DS3231 RTC time
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); // Reads the data from the sensor
t = DHT.temperature; // Gets the values of the temperature
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; // Gets the values of the humidity
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);
}
Code language: Arduino (arduino)
Descrição: Por outro lado, na unidade interna ou no receptor, precisamos incluir mais duas bibliotecas, uma para o módulo de relógio em tempo real DS3231 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 = {
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
};
Code language: Arduino (arduino)
Para isso podemos usar o GIMP, um editor de imagens de código aberto, através do qual podemos desenhar qualquer coisa e depois exportá-la como bitmap(.xbm).

Então podemos abrir este arquivo usando um bloco de notas e a partir daí podemos copiar o bitmap no código do Arduino.

Observe que aqui podemos definir o bitmap como constante usando o modificador de variável PROGMEM, e desta forma o bitmap será armazenado na memória flash ao invés da SRAM da placa Arduino.
static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM // Save in the Flash memory static unsigned char Temperature_20Icon_bits[] // Save in the SRAM
Na seção de configuração, precisamos inicializar a comunicação sem fio, bem como inicializar o display OLED e o módulo do relógio em tempo real.
Em seguida, na seção de loop, verificamos constantemente se há dados de entrada disponíveis para serem lidos pelos módulos NRF24L01. Se for 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.
Em seguida, usamos a função millis() para exibir os vários dados no display em intervalos definidos com a variável interval que eu configurei para 3 segundos. Estamos usando a função millis() porque desta forma o resto do código pode ser executado repetidamente, enquanto no caso de usarmos a função delay(), o programa espera por esse período, então provavelmente perderíamos os dados recebidos da unidade externa.
Em seguida, usando a função firstPage() e nextPage() da biblioteca U8G2, imprimimos as cinco telas diferentes que são definidas com as funções personalizadas.
A função customizada drawDate() obtém as informações de data e hora do módulo de relógio em tempo real e as imprime na tela apropriadamente. A função drawInTemperature() lê a temperatura interna e a imprime apropriadamente no display. Na verdade, o mesmo método é usado para imprimir todas as telas da tela.
Então isso seria tudo, 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 baseada no Raspberry Pi
- Estação meteorológica Raspberry Pi 2
- Estação meteorológica Raspberry Pi
- Estação meteorológica V 2.0
- Detector de frequência de áudio
- Manipulador de interruptor de luz sem fio
- Tech-TicTacToe
- Cronômetro de contagem regressiva do Arduino
- $ 10 Estação meteorológica portátil Arduino (AWS)
- eDOT - Relógio de precisão baseado em Arduino e estação meteorológica