The aim of this project is to build a very complete weather station with forecast, clock and date.
This device measures the most relevant ambient parameters, like:
-
Temperature
-
Pressure
-
Humidity
(all three in & out) -
Air quality index - tVOC (with textual indication).
-
Heat index (Humidex)
-
Battery voltage and percentage (both in & out)
Illuminance is intentionally not measured because of the need to expose the control unit to the direct sun.
In addition a single light sensor would not be able to give a correct reading because of the parabolic trajectory of the sun.
Furthermore, the movement path of the sun is not the same during an year nor in different latitudes.Similar argumentation for the wind gusts and direction: very bulky sensor and usually not so relevant (at least in my location).
The core of all is an ESP-32 Dev.board but an external wireless "probe", created via an ESP8266 D1 Mini, is used to outside ambient surveys.
By connecting to its SoftAP wireless network, this smaller control unit will answer to HTTP requests and send sensor data in the payload to the "master".
In addition to the local captured data, a weather forecast is downloaded by a free online weather API such as OpenWeatherMap.
All the information will be displayed on a TFT screen to achieve a fine eye-looking result.
The project is developed in Arduino code language (Wiring), to get an easy to understand and multi-platform environment.
All my code is free and open source but...
Any redistribution or reproduction of any part of the contents, in any form is prohibited, following this rules:
- It can be used just for non-commercial use and by indicating my name @Alesimattia and my references alesimattia@gmail.com
- It is in any case prohibited to transmit it or store it in any other website or other form of electronic retrieval system for commercial purposes.
All the libraries I used are free and installable by the Arduino library manager.
You can freely contact me for any questions or improvements at my email alesimattia@gmail.com
I'm not an English native speaker so there may be some grammatical errors sometimes. 😆
- ESP32 Dev-board (or whatever microcontroller you like).
- ESP8266 D1 Mini → External
- BME280: temperature, pressure and humidity.
- BME680: temperature, pressure, humidity, total volatile-organic-compounds → External
- DS3231 Real time clock
- TP4065 li-ion battery charger (x2) + 1N4007 diode (not the best)
- 18650 Battery (x2)
- TFT Display - 4.0" ST7796S (or whatever you like)
- 1 MOhm resistors (x4)
- Two housing cases
- Matrix board (optional)
All components can be bought on the most famous sites: cheap Aliexpress or Adafruit
- Aliexpress scammed me so I got a BMP280 (no humidity) so in this project will be used a BMP280 coupled with a HDC1080.
Code would even be simpler (and lightweight) with a single ambient sensor. - Real time clock might be superfluous with an ESPxx but it has got an embedded button cell battery (CR2032) so time keeping is guaranteed. Thus no need to reprogram with a PC even in case of completely loosing power.
Portrays the components properties and functions.
Even if we are not describing a real object language classes, this diagram gives an overview of the components and their interactions.
In this project i'm using a 4.0" 480x320px TFT display with ST7796S controller (maybe not the most widespread, but good price-to-size on AliEx.)
More documentation on http://www.lcdwiki.com/4.0inch_SPI_Module_ST7796.
After have tested Adafruit_GFX (plus Adafruit_TFTSPI) library I found I got very low performances during screen refresh.
At the moment, the best and optimized (for ESPs) library is TFT_eSPI which also supports a lot of display controllers.
Compared to the Adafruit one is blazing fast!
The configuration header can be found in User_Setup.h
- In Hardware SPI we get better performance so pay attention to this.
- For somewhat reason I couldn't reach full 80Mhz speed but 79'99999Hz is working perfectly.
- Touch screen has not been used: maybe I will implement a touch display-dimming function in the future.
- Display dimming is code-scheduled to reduce eyestrain during the night hours.
Picture are stored in the ESP filesystem by mapping in RGB565 format (uint16_t array).
const unsigned short img[] = PROGMEM {}
const uint16_t img[] = PROGMEM {}
You can use Image2Bitmap software for the mapping, then
TFT_eSPI::pushImage( )
is the public method for displaying to the screen.
Another online software is: http://www.rinkydinkelectronics.com/t_imageconverter565.php
The main aim is to display a small icon of the weather forecast: this data is downloaded by a free web service such as OpenWeatherMap with an HTTP call.
Note that images size can't be dinamically resized and when displayed on the screen it must be "printed" with its actual size: the pushImage() method must get as parameters full image size.
I.e. you can't (map in array) a 1920x1080px image and then scale down to 480x320px to fit the screen → resize image before mapping in array.
Background-filling colors and font colors also, are expressed in Hex RGB565 and can be converted with http://drakker.org/convert_rgb565.html or using a 24bit rgb:
TFT_eSPI::setTextColor( TFT_eSPI::color565(255,255,255) );
Stock included fonts may not be the best suitable basing on the display size or aims so we can include our custom font.
I got some nice ones (I definitely HATE serif fonts ones 😅 ) on this font converter: http://oleddisplay.squix.ch/#/home
Select: "Library version: Adafruit GFX font"
I also created a custom header file which suits my display size.
You only have to copy that in the TFT_eSPI main folder, and include it.
You can leave Custom_font.h in project folder, and not in display-library folder.
I suggest to NOT USE
TFT_eSPI::setTextSize(n);
with n>1 as it results in more aliased text displaying.
At cost of a bit more memory, generate the font with the correct size.
Since ADC2 can't be used while the WiFi transceiver is active, the 12-bit resolution of the ADC1 will map the input voltage from 0 to 3.3 Vdc to byte values of 0 to 4,095.
Max input voltage on all GPIO can be 3.3V so we have to reduce the 5.1V (Usb chargers output) to an acceptable value.
To minimize current draw we add a voltage divider with R1=R2=1M Ohm.
Thus, the readings are attenuated by a factor of R2/(R1+R2) and because of equal resistors, the resulting attenuation is 2 → we multiply the readings by 2.
This section is vaild for both external and internal ambient-measurement sensors.
It's mandatory to uncomment BMPxxx::takeForcedMeasurement both in .h and .cpp to set the sensor in the right tuning for ambient measurement which makes use of forced measurements and then turn back to sensor-sleep (not ESP sleep).
See the spreadsheet with few samplings of a (cheap) commercially-available weather station.
Maybe not the best, but it is a starting point: SAMPLINGS
-
Temperature is affected by enclosure and ESP self-heathing in an upward shift.
-
On the other side, humidity relevations are a bit tricky.
Since my 'reference' weather station is not so sensitive (it's not even got decimal precision) we see that BME(P)280 has got more measurement excursion thus we're lead to think that its ambient estimations are 'more true'.
We note that in the external sensor the reference measurements are pretty low and not even rose while raining (nor snowing) so we'll never apply correction to the humidity readings.
By using a BME680 sensor we can both work by implementing the easy Adafruit library or the more advanced BSEC library
In this project I'm taking advantage of BME680 by estimating air quality too, will be used thus the BSEC for Arduino library.
In addition to importing the library, it's mandatory to edit the configuration platform.txt located in:
%LocalAppData%\Arduino15\packages\esp8266\hardware\esp8266\**\platform.txt
as reported in the official repository documentation
In the past, this procedure was barely descripted in this Bosh forum.
While estimating air-quality two sample rates only are allowed:
3 seconds (LOW_POWER mode) and 5 minutes (ULTRA_LOW_POWER mode).
This "power-mode" is set with:
BSEC::updateSubscription( sensorList[n] )
Pass an array containing the virtual sensors to be enabled on the physical BME.
At the first cycle a prebuilt configuration file must be loaded to initialize properly the library:
const uint8_t bsec_config_iaq[] =
{ #include "config/generic_AAv_BBBs_CCCd/bsec_iaq.txt" };
You must select the appropriate file basing on:
- (AA) Voltage applied: 18 (1.8V) or 33 (3.3V)
- (BBB) Power mode: 3s (LP) or 300s (ULP)
- (CCC) History for the automatic background calibration: 4days or 28days
While sleeping the ESP ram is powered off so the BSEC library immediately loses the "current state" of the ambient estimations.
I implemented a persistency-mechanism to retain the sensor state over the sleepings. Data is stored in the EMMC (EEPROM library) and read-back in the wakeup (the setup() method is reiterated).
To make matters worse the BSEC library needs an absolute timestamp for being time-aware.
A huge 64bit variable zeroed the firs upload only, contains the instantaneous timestamp => we need to store this in the EMMC too.
Properly helper-functions are used to read/store/print this 64bit variables.
Hence, the EMMC is configured as shown below:
1 (bytes) | 139 | 8 |
---|---|---|
0--0 | 1-----------------------139 | 140--------147 |
Control_byte | BSEC_state | Timestamp |
This control unit communicates with main weather_station through HTTP GET calls.
Measured data are sent in the querystring of the get call:
Fixed wifi BSSID and Channel dramatically improves connecting speed from about 3500ms to 1050ms
Static IP configuration is useful to avoid DHCP negotiation and achieve the fastest connection with the main station
This board has got just a single analog input pin 'A0'.
An internal voltage divider with 220KOhm and 100KOhm, applied in A0-ADC is used to upscale 1.1V maximum ADC voltage to 3.3V.
In this case too, we add an external voltage divier to expand the measurable range.
As we can see in the circuit simulator, the effective down-scaling ratio is (4.2/0.819512 = 5.125) so we can measure up to 3.3V*5.125 = 16.9V
Since map() function is using integer calculations
vPercent = map(voltage, 3.20 , 4.20, 0, 100);
Will not work. So I implemented a custom voltage-mapping function with lower voltage bound 3.2V (as 0%) and 4.2V (100%) as upper bound.
The main weather-station will always be plugged to an Usb power suppy, so it is showing voltage only (and not percentage).