-
Display PCB
04/22/2021 at 18:14 • 0 commentsI 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.
-
Base Station Display
04/08/2021 at 17:07 • 0 commentsI 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 with
board_build.partitions = min_spiffs.csv
in platformio.ini. Hopefully this will give me enough space for this project.
-
Field Testing
11/03/2020 at 22:12 • 0 commentsAfter 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.
-
Testing Assembled PCB
10/16/2020 at 02:22 • 0 commentsI 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.
-
Buck/Boost Power Supply
10/08/2020 at 22:17 • 0 commentsThis 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.
-
Weather Sensor PCB
09/26/2020 at 20:08 • 0 commentsI 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
-
Low Power Counters
09/25/2020 at 17:33 • 1 commentBecause 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.
-
ESP NOW Implementation
09/23/2020 at 14:33 • 2 commentsThe 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. For the station that is using both WiFi and ESP NOW both protocols must use the same channel because having WiFi use one Channel and ESP NOW use another is not supported. Since the channel that WiFi uses is determined by the WiFi router in the home, that same channel is used for ESP NOW and you really can't pre-configure it. In order to communicate this channel to the other end of the ESP NOW communication, the base station can start a soft access point.
WiFi.mode(WIFI_AP_STA); WiFi.begin(); if(WiFi.softAP(STATION_NAME,"1234567890",1,0)) { Serial.println("Soft AP Success)"); } else { Serial.println("SoftAP Fail"); } while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Establishing connection to WiFi.."); }
The trick here is that the WiFi.mode must be WIFI_AP_STA, even if you are not starting an access point, or you can't get WiFi and ESP NOW to simultaneously function. That took a while for me to figure out. In this case, it doesn't matter, since you will be connecting both as a station, and you will be bringing up an access point. Once the mode is set, WiFi.begin() connects to the home wifi router configured earlier with the WiFiManager. It then starts the softAP.
Once this is done, ESP NOW is initialized by
esp_now_init(); esp_now_register_recv_cb(espnow_recv_cb);
Once the base station is up and broadcasting it's SSID, the outside ESP32 can look for the base station SSID and get its WiFi Channel and mac address.
WiFi.mode(WIFI_STA); WiFi.disconnect(); if(scan) { espnow_channel = DEFAULT_WIFI_CHANNEL; memcpy(macAddr, broadcast_mac, 6); uint8_t networks = WiFi.scanNetworks(); for(int n=0;n<networks;n++) { Serial.printf("%d %s %d %d %s \n", n,WiFi.SSID(n).c_str(),WiFi.RSSI(n), WiFi.channel(n), WiFi.BSSIDstr(n).c_str()); for(int i=0;i<8;i++) { Serial.print(WiFi.BSSID(n)[i]); Serial.print(" "); } Serial.println(); if(WiFi.SSID(n).indexOf(STATION_NAME) == 0) { Serial.println("Found"); memcpy(macAddr, WiFi.BSSID(n), 6); espnow_channel = WiFi.channel(n); } } WiFi.scanDelete(); }
It first sets the mode to WIFI_STA, which works when only ESP NOW must be supported, and disconnects anything from WiFi to make sure everything is clean. It first setup some default values for the channel and address in case it doesn't find the base station SSID, which are channel 3 (which is what my AP is on) and the mac broadcast address of 0xFFFFFFFFFFFF.
The SSID scan is initiated and the code looks for the SSID of the base station (STATION_NAME= "WeatherBase"). If it find it, it copies it's mac address and channel into global variables with the RTC_DATA_ATTR attribute on them, so they survive a deep sleep. When the wakes from deep sleep, this function is call with scan=false, so the scan is not preformed and the saved values are used. The WiFI.scanDelete() frees up the memory used by the WiFi scanNetworks().
esp_wifi_set_promiscuous(true); esp_wifi_set_channel(espnow_channel,WIFI_SECOND_CHAN_NONE); esp_wifi_set_promiscuous(false); if (esp_now_init() != 0) { return; } esp_now_peer_info_t peer_info; peer_info.channel = espnow_channel; memcpy(peer_info.peer_addr, macAddr, 6); peer_info.encrypt = false; peer_info.ifidx = ESP_IF_WIFI_STA; esp_err_t status = esp_now_add_peer(&peer_info); if (ESP_OK != status) { Serial.println("Could not add peer"); handle_error(status); } status = esp_now_register_send_cb(msg_send_cb); if (ESP_OK != status) { Serial.println("Could not register send callback"); handle_error(status); }
With mac address and channel in hand, the channel is set with the turning on promiscuous mode, setting the channel with esp_wifi_set_channel, and turning off promiscuous mode. The target address of the base station is configured as a peer to be used as a target for the ESP NOW transmission.
void send_msg(sensor_data_t * msg) { // Pack uint16_t packet_size = sizeof(sensor_data_t); uint8_t msg_data[packet_size]; memcpy(&msg_data[0], msg, sizeof(sensor_data_t)); esp_err_t status = esp_now_send(macAddr, msg_data, packet_size); if (ESP_OK != status) { Serial.println("Error sending message"); handle_error(status); } }
After the weather station polls its sensors, the data from the sensors is flattened into an array of bytes, and is transmitted with the esp_now_send call to the base station mac address. The peer_info structure configured in the preceding code snippet is used internally by the ESP code, for if it is not configured correctly, the esp_now_send call will fail.
Once the weather station transmits its data, it goes into deep sleep until the timer wakes it up to do another poll of its sensors. In order to get the lowest sleep current, the WiFi radio must be turned off with
WiFi.disconnect(true); WiFi.mode(WIFI_OFF);
Back on the base station, the receive message call back is called, and the base station unpacks the received bytes back into the sensor data struct.
static void espnow_recv_cb(const uint8_t *mac_addr, const uint8_t *data, int len) { if (len == sizeof(sensor_data_t)) { memcpy(&sensorData, data, len); Serial.printf("Temperature=%f *C\n",sensorData.temperature); Serial.printf("Pressure=%d Pa\n",sensorData.pressure); Serial.printf("Humidity=%f\n",sensorData.humidity); Serial.printf("Battery Volts=%f mV\n",sensorData.battery_millivolts); Serial.printf("Direction=%d\n",sensorData.direction); Serial.printf("Rain Count=%d\n", sensorData.rain_count); Serial.printf("Anenomoeter Count=%d\n", sensorData.anemometer_count); Serial.printf("Count=%d\n",count++); dataValid=true; } else { Serial.println("Received something of wrong length"); } }
For some reason, the MQTT transmission of the data cannot occur in the callback function, so a global variable is set to true, and, in loop(), this variable is continually polled, so that MQTT transmission can occur in the loop context.
void loop() { if(buttonLongPress) { Serial.println("Config Button"); callWFM(false); buttonLongPress = false; } // Fails if I send data to MQTT in call back if(dataValid) { dataValid=false; sendMQTTData(); } ArduinoOTA.handle(); }
-
Bootloader Wake Time Improvements
09/19/2020 at 02:50 • 3 commentsBoth 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:
- Enabled qio mode on the SPI flash decreased the sleep wake period to 179ms
- Increased SPI flash speed to 80MHz with qio drop bootloader time to 146mS
- Tying GPIO15 to ground to disable boot logging did not change bootloader time at all
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
- Rebuilt the bootloader with menuconfig with speed optimizations dropped the period to 79mS
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 logging. Under the "Serial Flasher Config", I set the mode to QIO and speed to 80MHz, and under "Component Config" I set log output to None. Build the project and you end up with a bootloader.bin in the project_folder/.pio/build/esp32dev directory.
To get this bootloader into an Arduino Framework project, you first must understand how Platformio (and probably the Arduino IDE as well) find a bootloader to load into your ESP32. Normally a group of prebuilt bootloaders are found under $HOME/.platformio/packages/framework-arduinoespressif32/tools/sdk/bin, and they are named with the convention bootloader_mode_flashspeed.bin, so with the configuration of mode=qio and flash speed=80MHz, the targeted bootloader is bootloader_qio_80m.bin.
Now you could copy the newly compiled bootloader.bin to the folder under the home directory, but these bootloaders are shared by all PlatformIO projects, and I didn't want to screw up some other project. You can create a private copy of the packages directory with the following directive in platformio.ini:
[platformio] packages_dir=./packages_dir
This way you can copy the new bootloader.bin to project_dir/ packages_dir/framework-arduinoespressif32/tools/sdk/bin/bootloader_qio_80m.bin, and the next time the ESP32 is flashed, it will get the new bootloader.
The final test I ran was to rewrite my Arduino test code into native ESP IDF code:
#include <stdio.h> #include "driver/gpio.h" #include "esp_sleep.h" #include "sdkconfig.h" void app_main() { gpio_reset_pin(GPIO_NUM_12); gpio_set_direction(GPIO_NUM_12,GPIO_MODE_OUTPUT); gpio_set_level(GPIO_NUM_12,1); esp_sleep_enable_timer_wakeup(1); gpio_set_level(GPIO_NUM_12,0); esp_deep_sleep_start(); }
This code ran with a period of 28mS, which means that there is still something inside the Arduino code which is eating up a lot of time. The actual time between the GPIO going high and going low in the Arudino Code was 18uS and the IDF code was 7uS, so the time is being lost somewhere less obvious. Even 28mS is not fast enough for the requirements of the anemometer, so the anemometer counter will have to be accomplished with some sort of event counting IC.