An ESP32 based weather station investigating ESP32's Deep Sleep and ESP-NOW
To make the experience fit your profile, pick a username and tell us what interests you.
We found and based on your interests.
I received my PCB for my Weather Base Station in record time; one week from China. It bugs me a bit that the boards cost $2 and shipping was $12, but $14 for 5 PCBs still seems like a deal to me.
This is the PCB populated in my development configuration. The connector on the left attaches to the display connector, and the PCB sticks out to the right of the display for easy access to the test point connector on the right. There are a couple of jumpers which control where the ESP32 and display are powered, and a connector for an HTU21D temperature humidity sensor. The unpopulated footprints are for the "Production" mode, where the PCB will lie behind the display, and instead of an ESP32 dev module, an ESP32 WROOM 32U module will be used.
This is the development setup in use. As I suspected, the much cleaner layout of the PCB allows me to use the full 20MHz SPI frequency reliably. Here, the oscilloscope is probing the capacitive touch screen interrupt test point. The touch screen is the next thing I need to figure out, and will be the subject of the next log.
I took a little hiatus on this project as I got my Ham Radio License (AC3HG). During that time, I had the base station receiving data, storing it to InfluxDB and and displaying it on Graphana:
During this time, I fixed a few bugs, but for the most part, as the Weather Station sat outside during the winter, it was pleasantly stable.
I'm now starting to work on a dedicated base station for the weather station, and I've selected an East Rising 7" Capacitive Touchscreen from buydispay.com as the display. These are some notes as I worked to get the screen functioning.
These screens draw a decent amount of current; on the order of 250 - 300 mA. This is way more current than the on board 3.3V regulator on an ESP32 development module can supply. You need to power the display from another power supply. Don't as me how long it took me banging my head against a wall before I figured this out.
I'm using the Adafruit RA8875 Library to drive the display. The problem is, this library was created for an Atmel based Arduino, and not the ESP32, and this caused some problems.
When writing fonts, especially when you are enlarging fonts to 2 and 3 times their normal size, it takes a bit of time for the display to render the fonts. If you send the text too quickly, the display driver will skip characters; for example, sending tft.textWrite("12345") will only display "135". The Adafruit library just has a delay(1) to give the display driver time to render the character, but this isn't enough for the ESP32 to render reliably. The RA8875 display driver has a WAIT pin that the Adafruit library does not implement, which is driven low when the driver is busy, and raised high when it is ready (see below). I had to implement reading the value on that pin in code to get the text rendering to work well.
The library also defaults to 4 MHz speed for SPI. If there is a #define variable called ARDUINO_ARCH_ARC32 (intended to indicate the Arduino 101 board), the SPI frequency will be set to 12 MHz, so a work around is simply to add
build_flags = -D ARDUINO_ARCH_ARC32=1
in platformio.ini, so SPI is running at higher speed. A big problem, though, is there is a bug in the library when doing a bulk data transfer in the drawPixels, the speed is set to 4 MHz regardless of the board architecture. This is used when you want to draw a background image, where you will be writing to all 800 x 480 pixels, and without the fast SPI, it takes several seconds to transfer the data to the display. I just had to recode that function call. According to the RA8875 documentation, I should be able to set the SPI speed to 20 MHz, but the data transfer isn't very reliable at that speed. This is probably because of the rat's nest of wires on the breadboard right now, so I just sent a bunch of gerber files to China to get a PCB for further development.
Speaking of the background image, storing a full 800 x 480 pixels, at 2 bytes for color data each pixel, takes 768K. Storing that in flash drove me up to using 91% of my flash space, without really much code. To free up space, I changed the ESP32's partition table. By default, the flash space in the ESP32 is divided up into 2 1.3M partitions for the application, and 1.5M for the SPIFFS partition. There are 2 application partitions for over the air flash update, where an application running in app0 loads an OTA update into app1, and then reboots into app1. Then when that application is running in app1, and loads an OTA update, it goes into app0. As OTA updates are sent to the ESP32, the ESP32 switches between booting from app0 and app1. The SPIFFS partition is a where various static content can be stored, usually for a web interface. Since I'm not using that feature, I can select the min_spiffs partition table, which gives 1.96 M to the apps, and only 196 k to SPIFFS. In platformio, the partition table can be selected...
Read more »After a bunch of 3D printing, the weather station has been put outside to do some live testing.
The electronics are housed in a La Crosse Sensor Protection Shield. This housing has sort of a bayonet mount which allows whatever La Crosse sells as a sensor unit to slide over to hold the sensors in place. I 3D printed a case which does the same, to hold my sensor board.
A housing to hold the RJ11 plugs for the Wind and Rain sensors is glued to the bottom of the housing, so the assembled sensor housing assembly looks like:
Some 3D Printed brackets to hold the housing and solar cells to my garden post allows the housing to be secured, and a green 3D printed plug gives the Sparkfun weather apparatus something to hold onto.
I bought some spray on Conformal Coating and covered the PCBs with a thin coating to armor the circuits against some of the weather, although, in the future, I will probably use the paint on type instead. While the spray gave a nice even coat, I had to tape up all the connectors, and the little hole in the BMP280, before I sprayed them. With the paint on version, I could have just painted around the connectors, and I would have been able to get under the Buck/Boost converter PCB more easily.
The first problem I ran across was, once the temperature dropped below 16°C, I started getting spurious counts from the rain gauge. After the normal debugging routine, I figure out it was a flaky solder joint on the ESP32 causing the problem, and resoldering that fixed the issue.
The second issue was a bit of a education in how WiFi routers work. For some reason, my Wifi starting acting flaking in my entire house, so I powered cycled the router. That fixed my house Wifi problem, but my receiver then stopped receiving any data from the weather station. What happened was, when the WiFi router restarted, it changed the channel WiFi clients were connecting on. Since the ESP32 cannot connect to WiFi on a different channel than it is using with ESPNow, the receiver could no longer hear the weather station transmitting on the old WiFi channel. A simple reset of the station fixed that problem, but since there is no external access to the reset button, I had to remove the 7 screws of the case to reset the ESP32. As luck would have it, a few days later, a brief power outage hit, resetting the WiFi router, and, of course, changing the channel, requiring more disassembly and a reset. If I have to rebuild the main PCB, I will mount the reset button on the back of the PCB, and put a hole in the case to be able to access from outside. Since the solar cells are recharging the battery so well, I have some extra power budget, so I also added a requery of the weather station SSID every hour, so a channel change will eventually be caught by the station, and the problem will fix itself. I've also noticed I'm dropping an ESPNow packet occasionally, so, since I've got this extra power to spare, I will probably transmit each data packet twice (with a sequence number of something, so the receiver can recognize the duplicate packet) to try to minimize the packet loss.
I got the main board PCB back from China, and assembled it, and surprisingly, it worked. The only mistake I found was I forgot to make a debug GPIO easily accessible, and I messed up the silk screen because I was in a hurry to get the board in before China's Golden week.
I was having a problem with the brownout detector often popping off, so I put a bunch of capacitors footprints in various locations. While I was waiting for the PCBs to ship, I realized my problem was the current shunt in the ammeter. When the ammeter was in the uA range measuring sleep current, there was a large shunt in the power circuit, but as soon as the ESP32 woke up, the meter didn't auto-range quickly enough to put a smaller shunt in series, and there was a substantial voltage drop across the shut, and hence a brownout level of voltage on the uC. Long story short, I didn't populate any of the large electrolytic caps I put on the PCB when I thought my buck/boost converter might be having trouble supplying the instantaneous burst of current the ESP32 called for when it work from deep sleep.
The other problem I have discovered is with ESP Now. Every time the the weather station sends a packet, the base station receives it, but the weather station's send callback function indicates every transmission is an error. While the actual communication works, the problem seems to be that the weather station sender expects an ACK message from the base station, which, apparently, isn't happening. There isn't a lot of information around this ACK except for some casual mention in the documentation and some ESP32 message board talk that the ACK happens automatically is not something you have to code. This error doesn't happen when the packet is sent to the broadcast address, because it wouldn't be expecting anyone to acknowledge it. I need to dig into this error, but for now, it doesn't seem to break anything.
I slapped everything into a waterproof box, and did some outdoor testing.
The 5V solar cells connect to an Aliexpress CN3065 solar charger, which powers a 2000 mAh Lipo and my PCB. The solar charging works better than I expected, with the Lipo getting fully charged in one hour of morning sunlight, after a day running on the battery. I could have probably gotten away with just one cell. instead of the 2 in parallel I'm using.
Following the sage advice of john.r.sheahan, I implemented a watchdog timeout in the firmware, to recover from a system hang (which I have seen with I2C when something won't let go of the clock line). The code for the weather station proper is pretty much complete at this point, and is up on the github repo. The base station code just takes the ESP Now packets and drops them on MQTT, for Node Red, InfluxDB and Grafana to do their things, but I do have plans to put and LCD display in it as well. That code is up on github as well.
The next step is to 3D print some mounting hardware.
This project will run off a Lipo battery, which means the voltage will vary from 4.2 V to about 2.5 V. This voltage is well outside the range of the ESP32 and other chips on the weather station, so I build a buck/boost converter to keep the supply voltage at 3.3 V, regardless of what the battery voltage is.
I originally based this supply on the TPS63001 from Texas Instruments. It is a fixed 3.3 V buck/boost converter and this is the schematic I created:
And this is the PCB.
The TPS63001 is a truly painful chip to solder, being a WSON package, with no leads that extend from the chip's body; the leads are just (very small) pads that sit under the chip. I decided I would get the board made by JLCPCB, and try out their SMD assembly service, so I wouldn't have to deal with that buck/boost converter package.
Unfortunately, they were out of stock of the TPS63001, but they did have the lower current, pin compatible TPS63031 , so I substituted that IC. One thing to note, while I didn't have to change the PCB layout for the replacement converter, I did have to make the PCB slightly larger, since the SMD Assembly service's minimum PCB size was 20 mm square.
I was quite pleased with the SMD Assembly service, although the first PCB I fired up and gave 3.3 V initially, it then almost immediately stopped working. I spent a good bit of time trying to understand what I did wrong, until I just grabbed another board, and it worked fine.
I decided to also have OshPark make me a set of 3 boards on which I would solder the TPS63001 on, so I could compare the 2 chips.
On top is the JLCPCB assembled PCB, and the purple board at the bottom is, of course, the OshPark PCB.
I plugged both boards into my Maynuo electronic load to find out how the TPS63001 and TPS63031 fared at various input voltages and output currents. I would measure input power and output power to calculate how efficient the converter was, and I found that, at low currents, I was getting more power out than I was putting in. While I was dreaming about winning the Nobel Prize, I decided to hook the current meter up to the Maynuo, and I found it current setting had a bit of an offset. At the 10 mA setting the Maynuo was really only drawing about 8 mA, and the 2 mA offset was consistent across all the currents I was testing. My tests were no longer breaking the laws of Physics with this adjustment.
At 4.2 Volts on the input, the TPS63001 was able to source 1.1 A of current before the output voltage dropped precipitously, while the TPS63031 gave up at around 750 mA. More importantly for this project, though, was at less than 300 mA, the TPS63031 was more efficient, converting over 90% of the input power to output power.
At 3.7 Volt s input, the pattern held, with the TPS63001 being able to supply 750 mA, while the TPS63031 could only supply about 500mA, but in the current range of this project, the TPS63031 was more efficient.
At 3.0 V, the TPS63001 was able to supply 500mA, while the TPS63031 was only able to get to about 350ma, but once again, it was the efficiency winner for most of the current range of interest.
With 0 current draw, the TPS63031 also bested the TPS63001 with a quiescent current of about 68uA compared to 80 uA. In order to get this low Iq, you must set the PS pin on both chips to low to put it in Power Saving mode. This allows the chip, under low current conditions, to basically shut off for long periods, at the cost in about 100mV in ripple. Since that doesn't affect the ESP32 when it is sleeping (and adding a bunch of capacitance helps mitigate that), it is a price worth paying, because without power saving mode, the quiescent current is about 3 mA, and too high for a battery operated circuit.
Since it performed better in the current ranges that this project will draw, I decided to go with the TPS63031.
I whipped up a PCB for the sensor board and sent it to China to be made. Should be here in a week or so.
The Schematic:
The PCB:
The KiCad file can be found at https://github.com/kevinkessler/WeatherStation-PCB
Because the ESP32 can't wake quickly enough to keep up the the Anemometer, I decided to use a hardware counter to handle that task. The 2 chips I have that can do this are the PCF8593 from NXP and the S-35770 from Ablic. I set them both up to count anemometer rotations, so I could decide which on to use.
Both of these chips use the I2C interface, and in single chip quantities the PCF8593 is $2.87 and the automotive spec'ed S-35770 is $5.83. So price is in favor of the NXP chip, unless you need the 125 C temperature range of the S-35700.
In operation, the S-35770 does a better job of counting. If the rise and falls times are not quick enough, the PCF8593 will have multiple counts during the slow edges, and, putting a low pass filter on the anemometer to debounce it can slow down the rise times enough to cause problems. The S-35770 has a Schmitt Trigger buffer in front of its counter, so it doesn't matter how close the transition times are, it will only generate one count.
The PCF8593 has pre-existing libraries that work, even though that library is for the PCF8583. You have to code up your own library for the S-35770, but it is very easy. The I2C interface to the S-35770 is weird and not really like most I2C implementations where you send a register address and then you read or write; they just kind of do their own thing. It's weird, but it is pretty straight-forward to code to.
The PCF8593 is in a bigger package, so it is easier to solder. In operation, the current use is nearly identical, with the PCF8593 drawing 11.3uA and the S-35770 drawing 9.9uA.
The biggest advantage to the PCF8593 is the fact that it is a real-time-clock with event counter functionality. In fact, that is the main reason I chose to use the S-35770 in this project. I now have some PCF8593s in my parts bin which I can use as a RTC or a counter, while the S-35770 is a one trick pony, so I better use them when all I need is a counter. If I were to use the PCF8593, I would probably put a Schmitt buffer in front of the input like a 74LVC1G17, to square up the rising edge, and fix the counting problem.
The basic design of the Weather Station is a set of outside sensors, run by an ESP32 whose values are transmitted to a base station inside. The data is transmitted through ESP NOW, and when received by the base station, that data will be unpacked and dropped on an MQTT broker, where it can be consumed by Home Assistant, Node Red, Grafana, and anything else that might be fun.
There are many good ESP NOW introductions and one of the best I found is right here on Hackaday.io: Hello World for ESP-NOW. It gets trickier when you need to have ESP NOW and WiFi coexist at the same time on the ESP32, which is what needs to happen for the Base Station to operate properly. This coexistence is not supported on the ESP8266.
For most of my projects, I like to include ArduinoOTA and WiFiManager. ArduinoOTA allows for Over-The-Air flash of firmware to the ESP32. It is very simple to setup and once initialized, it just requires you to call
ArduinoOTA.handle();
in the loop(). Flashing can be done in PlatformIO by specifying the hostname as upload_port in platform.ini, and can be done through the Arduino IDE as well. This does not interfere with ESP NOW and can be used normally.
WiFiManager is a nice way to configure your WiFi credentials, and other configuration elements. In it's simplest form, in the setup code you call WiFiManager.autoConnect() and, if the WiFi credentials are already stored in flash, the ESP will connect to WiFi. If there are no credentials stored in flash, the WiFiManager will start up an Access Point to which you can connect on a device like your cell phone. Once connect to the access point, you bring up a browser and go to address 192.168.4.1 and a configuration menu is displayed where you can enter your WiFi credentials, which are then stored and used to connect to WiFi on all subsequent connection attempts.
For a little more complex scenario, you can add configuration elements to the menu by creating a WiFiManagerParameter and then using WiFiManager.addParameter() to put these parameters on the configuration menu. For example, to get the MQTT Server configuration, I use the following code to setup the parameters:
WiFiManagerParameter mqtt_server("server", "MQTT Server", mqttServer, MQTT_SERVER_LENGTH);
char port_string[6];
itoa(mqttPort, port_string,10);
WiFiManagerParameter mqtt_port("port", "MQTT port", port_string, 6);
WiFiManagerParameter mqtt_topic("topic", "MQTT Topic", mqttTopic,MQTT_TOPIC_LENGTH);
wfm.addParameter(&mqtt_server);
wfm.addParameter(&mqtt_port);
wfm.addParameter(&mqtt_topic);
When the configuration portal is show, these 3 new parameters will be on the form. When the form is submitted, the values for the MQTT server and be retrieved with
strncpy(mqttServer, mqtt_server.getValue(), MQTT_SERVER_LENGTH);
strncpy(mqttTopic, mqtt_topic.getValue(), MQTT_TOPIC_LENGTH);
mqttPort = atoi(mqtt_port.getValue());
It is important to note that these custom parameters are not stored automatically, like the WiFi credentials are. You must store these values in EEPROM programatically if you wish to to have them the next time the ESP is rebooted.
When using ESP NOW, you cannot use WiFiManager.autoConnect() to manage the WiFi connection because you need a little more control of the connections to have both WiFi and ESP NOW coexist. You can, though, use WiFiManager to handle the parameters, because you can call WiFiManager.startConfigPortal manually. Since the WiFi credentials are stored at the same location that the ESPs store them normally, after the portal stores the credentials, a standard WiFi.begin() will use them. I have the base station programmed to automatically bring up the portal if the MQTT information is missing in EEPROM, and I have also configured a button which start the portal on demand if the button is pressed more than 1 second.
In order for 2 ESPs to communicate over ESP NOW, they both must be transmitting and listening on the same WiFi Channel....
Read more »Both the anemometer and the rain gauge use reed switches to send pulses to the microcontroller indicate the value of the sensor. The anemometer closes the switch on each rotation, so that a 2.4 km/h breeze will cause the switch to close once per second. The tip bucket rain gauge will cycle the reed switch on every .2794mm of rain. My initial approach was to connect each of these to a GPIO that would wake the ESP32, which would then update the appropriate counter and go back to sleep.
The ESP32 wakes from deep sleep is level triggered instead of edge triggered, and if you set the uC to wake on a low input, for example, and the input is still low when you the ESP is put to sleep, it will immediately wake. The result of this is in order to get an accurate measurement of counts you need to emulate an edge triggered interrupt. To do this, you need to set the wakeup signal to a low on the GPIO, programmatically note the low event when it occurs, then set the the wakeup signal to a high on the GPIO. When the high wakes the ESP32, the count can be recorded. The switch is hardware debounced with a low pass filter, and it would have been a good idea to put some time based software debouncing in, but it turns out that the ESP32 is so slow to wake, it isn't necessary.
On doing some initial testing, I found that it takes the ESP32 around 192ms to wake from deep sleep. Having to wake twice on every count would limit the ESP32's anemometer reading to about 6km/h. Assuming 100km/h as the max wind speed I need to measure (pretty strong wind which might tear the station off its poll), I would require the ability to handle about 84 wake events per second, so this is not close to meeting the requirement. The fastest rainfall recorded in the US was 17.5mm in one minute, which generate a wake event about twice a second, which would be doable (although, under that much rain, something else would probably break). I set about trying to decrease the wake time.
To measure the baseline bootloader time, I wrote a program which toggled a GPIO, set a wake up timer for 1uS, and went to sleep:
#include <Arduino.h>
void setup() {
pinMode(GPIO_NUM_12,OUTPUT);
digitalWrite(GPIO_NUM_12,1);
esp_sleep_enable_timer_wakeup(1);
digitalWrite(GPIO_NUM_12,0);
esp_deep_sleep_start();
}
void loop() {
// put your main code here, to run repeatedly:
}
Then I could monitor the GPIO with my oscilloscope to determine the speed at which the ESP32 can cycle through deep sleep.
Since 18uS pulses are not very visible in the image with 50mS per division, I turned on measurements, and you can see the period between program runs is 191 mS (lower left).
This is a list of things I've done to try to speed things up:
I'm using PlatformIO as my IDE, and the first 2 settings are made with the platformio.ini options
board_build.flash_mode = qio board_build.f_flash = 80000000L
The rebuild of the bootloader consisted loading up the Espressif IDF native SDK, and running menuconfig. Under PlatformIO this just consists of creating an ESP32 project with the espidf framework, so the hard work is done for you. You then can run:
pio run -t menuconfig
In order to get all the bootloader configuration options, though, you need to be using the newest espressif32 platform, so the platform directive in platformio should read:
platform = https://github.com/platformio/platform-espressif32.git
in order to get the yet to be released 4.1.0 version.
In menuconfig, under "Bootloader Config", I enabled "Skip Image Validation when waking from Deep Sleep" (which I think made the most difference), set the compiler optimization to -O2 for speed, and turned off all...
Read more »
Create an account to leave a comment. Already have an account? Log In.
I'm actually using https://lowpowerlab.com/guide/moteino/
The 328p board mostly with rfm69HCW radios on the back. I added a few LORA boards to the mix last year. I have no experience with ESP32. For reasons I don't fully understand the LORA boards are flaky on short distance, work fine 500m away. So I'm happy for now with the mix. maybe 20 spread around the place now. The M0 boards now would make more sense to start with.
I figure LORA won't be so good when a lot of them are out there, But I'm remote now. HCW is slightly cheaper and faster (bandwidth).
I'm no spring chicken either, but I'm still OK on a ladder. My anemometer is half way up a mast a couple of metres above the barn peak, must be 9 or 10m off the ground. Rain gauge is near to it.
I used to run that sort of thing with RS485 drivers for comms, and would lose one to esd induced zapping every year. And I have more lightning here now and more sensors, 2 trees hit in the home paddock in 2 years. radio links + a bit of solar is better.
Something like a tank water level sensor, mostly sleeping, gets by with a 500mah lipo, a 6v solar cell in the 1w range, and a charge controller chip. Reminds me - next board rev I suggest you add an adc channel to monitor batter voltage. Send that back to base say 4 times per day. Its helpful to throw into a grafana widget.
And enable the chip watchdog and pat it. One board I have near a water tank with wires that must be less than a foot long will crash in any decent summer storm. The watchdog usually recovers things, although I have to remove its battery a couple of times a year to really reset it.
I've done something a bit similar which has evolved over a few years. I'd question whether combining too many sensors into one node is the best way to do things. Wind and rainfall sensors tend to be up a pole, on a mast above the roof ridge, or somewhere else exposed. The temperature sensor should be in a Stevenson screened box about 1m above the ground. My current location has some lightning issues, and when I had one box wired to wind, rain, temperature, humidity, pressure sensors, the induced voltage in the longish connecting wires smoked the node on a nearby lightning strike.
Using more than one node, each close to its sensors, works more reliably for me. Radio links (I've used motino's successfully) not wires to the logger, with solar cell + LiPo battery. Keep wires shortish. I needed a radio repeater on the roof as some water level sensors are a long way off (farm distance)
Grafana works well for the display. Even better if I could get a windrose display.
You are correct that your way would be the better way to architect the weather station, but I'm old and not to fond of heights, so the Anemometer and wind vane are not going up on my chimney. You make a good point about ESD, though. I have some uClamp TVS diode arrays I used for another project, so if I have to create another PCB, I will have to include them to protect the longer cables.
This is really a technology demonstration for me, and if I was really serious I would probably use an STM32 M0+ low power uController, and Lora, like I did with my mailbox sensor. In fact, Lora would certainly be able to handle "farm distance" without any repeater; Andreas Spiess (a highly recommended you tuber, if you are not familiar with him) got over 200km with Lora.
@john.r.sheahan Do you have a link to the radio links you used or any further information about the nodes?
Become a member to follow this project and never miss any updates
I'm looking to make a weather station with the same sensors and an ESP32 to send MQTT datas to my networks, your project looks interessant!
My only problem now is how to be sure to measure the good wind speed and rain quantity, can you say me how you do the count, for wind you have a timer and count the nomber of pulse of sensor ? and the same for the rain ? If yes what is your
values ?