The ESP32 can be set to wake up from sleep due to one of several pins going high. Which pin caused it to wake up? This has been causing me a few headaches with my combined mailbox and driveway monitor because sometimes multiple pins can be high at once. While researching this I also discovered that there is a far easier way to setup the pins than calculating a bitmask value.
The project I am working on has three sensors; one for detecting a mail flap being lifted, another for when the mailbox door is opened and finally a PIR movement sensor being triggered by movement. All of these will wake the ESP32. It then checks what woke it up, sends some data to another ESP32 in the house using ESPNOW and then goes back to sleep.
There are a couple of problems. Firstly, sometimes multiple sensor pins can be high at once. This usually occurs because the PIR driveway sensor stays high for a while after being triggered. It is often while it is high that mail is delivered. So, it’s important not just to know what pin caused it to wake up, but also what combination of pins were high. I wasn’t able to find easy to understand info about this. I found some things that may have described it, but not in simple enough language for me to understand. It’s now starting to make sense.
Which pins woke it up
To learn how to use deep sleep and determine what woke it up I started with this useful article by Random Nerd Tutorials ESP32 Deep Sleep with Arduino IDE and Wake Up Sources. I find Random Nerd Tutorials to be a great resource. I’m using an external wake up using ext1 set to trigger if any of the pins (27, 32 and 33 in my case) are high. In the sketch this is done using these lines:
define BUTTON_PIN_BITMASK 13019119616
esp_sleep_enable_ext1_wakeup(bitmask, ESP_EXT1_WAKEUP_ANY_HIGH)
This got me started, and it includes a method of determining which pin triggered the wakeup using this:
esp_sleep_get_ext1_wakeup_status()
And this function uses it to return a number of base 2 that should be the GPIO pin:
void print_GPIO_wake_up(){
uint64_t GPIO_reason = esp_sleep_get_ext1_wakeup_status();
Serial.print("GPIO that triggered the wake up: GPIO ");
Serial.println((log(GPIO_reason))/log(2), 0);
}
However, this is not reliable with my circuit as this is intended only for a single pin to trigger at once. In my project multiple pins could be high at once. I added in a print statement to see what values for GPIO_reason was being returned and discovered they were bitmask values for the pins that were high. For example, 4429185024 is the bitmask value for pins 27 and 32, that is 2^27 + 2^32. The print_GPIO_wake_up function prints 32 (log(4429185024))/log(2) = 32.0443941194) for this. In my tests it usually returns one of the high pins, but I think it is just due to rounding the returned value.
I found in the ESPRESSIF ESP-IDE programming guide: Checking Sleep Wakeup Cause esp_sleep_get_ext1_wakeup_status() returns a bit mask. It looks like I’m on the right path.
Get the bit mask of GPIOs which caused wakeup (ext1) If wakeup was caused by another source, this function will return 0. Returns bit mask, if GPIOn caused wakeup, BIT(n) will be set.
Now that I believed that GPIO_reason was returning a bitmask value, it allowed me to use it get more detailed information about the status of the pins at wakeup, for example, this prints out the pin or combination of pins:
switch (GPIO_reason)
{
case 134217728 : Serial.println("Only GPIO 27"); break; // GPIO 27
case 4294967296 : Serial.println("Only GPIO 32"); break; // GPIO 32
case 8589934592 : Serial.println("Only GPIO 33"); break; // GPIO 33
case 4429185024 : Serial.println("Both GPIO 27 + 32"); break; // GPIO 27 + 32
case 8724152320 : Serial.println("Both GPIO 27 + 33"); break; // GPIO 27 + 33
case 12884901888 : Serial.println("Both GPIO 32 + 33"); break; // GPIO 32 + 33
case 13019119616 : Serial.println("All GPIO 27 + 32 + 33"); break; // GPIO 27 + 32 + 33
default : Serial.println("Unknown pin"); break;
}
Predefined GPIO bitmask values
I found an interesting comment on Reddit, Deep sleep ext1 arduino by focojs that asked about creating bitmask values and later answered their own question.
Nevermind, it turns out you don't have to use a bitmask at all. I kept digging and found an example where someone used this:
esp_sleep_enable_ext1_wakeup(GPIO_SEL_26 | GPIO_SEL_27, ESP_EXT1_WAKEUP_ANY_HIGH);
I changed it to 26 and 27 and it works great. With that ability I guess I don't understand why anyone would mess with the bitmask. I would still be interested in learning how the bitmask works though.
Why indeed. I couldn’t find an example that they may be referring to, but I decided to explore this. Eventually I found that there are definitions built into the ESP32 library. If you include a definition in a sketch, for example GPIO_SEL_27, right click on it in the sketch and select ‘Go to definition’ from the menu.

A new tab should open and in this case is called gpio_types.h. This lists lots of pin definitions including the GPIO_SEL_x definitions.
Using the predefined definitions
I found two ways to use them. Firstly, they can be used to set up the BUTTON_PIN_BITMASK definition. I was using this in the define:
define BUTTON_PIN_BITMASK 13019119616 // This defines wake up source pins. For pins 27, 32 and 33 = 2^27 + 2^32 + 2^33 = 134217728 + 4294967296 + 8589934592 = 13019119616
Along with this in the sleep function
esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);
Instead of those two lines I can just have this in the sleep function:
esp_sleep_enable_ext1_wakeup(GPIO_SEL_27 | GPIO_SEL_32 | GPIO_SEL_33, ESP_EXT1_WAKEUP_ANY_HIGH);
The vertical bar symbol | is the bitwise OR operator.
The other way I found to use them was to determine which pins were high during wakeup. Intead of using the bitmask value as I did above, I used the built in definitions.
switch (GPIO_reason)
{
case GPIO_SEL_27 : Serial.println("Only GPIO 27"); break; // GPIO 27
case GPIO_SEL_32 : Serial.println("Only GPIO 32"); break; // GPIO 32
case GPIO_SEL_33 : Serial.println("Only GPIO 33"); break; // GPIO 33
case GPIO_SEL_27 | GPIO_SEL_32 : Serial.println("Both GPIO 27 + 32"); break; // GPIO 27 + 32
case GPIO_SEL_27 | GPIO_SEL_33 : Serial.println("Both GPIO 27 + 33"); break; // GPIO 27 + 33
case GPIO_SEL_32 | GPIO_SEL_33 : Serial.println("Both GPIO 32 + 33"); break; // GPIO 32 + 33
case GPIO_SEL_27 | GPIO_SEL_32 | GPIO_SEL_33 : Serial.println("All GPIO 27 + 32 + 33"); break; // GPIO 27 + 32 + 33
default : Serial.println("Unknown pin"); break;
}
There is a bitwise AND operator ‘&’. I don’t understand bitwise operators and so don’t understand why the OR operator ‘|’ works here instead of the AND one. Post a comment if you know.
Example sketch
Here is a simple example sketch. It is written to use ESP32 pins 27, 32 and 33, but you could use others. It will sleep until any one of the pins goes high. It then prints what awoke it, and if it was a pin, which of these pins were high at wakeup. It prints the raw bitmask and the wake up pins using three different methods; using the Random Nerd Tutorials log method, using the bitmask value and finally using the built-in definitions.
The circuit diagram is simple. Just three push buttons or switches (normally open) with pull down resistors. For each, connect between a GPIO pin and ground, with an open button switch connected between GPIO and 3.3v.
This is my test setup. It was built to allow me to test the mailbox alert. It’s more permanent than I would normally make it, but this is due the ongoing issues I have with it.

And this is the sketch
/*
Simple Deep Sleep Using EXT1 and Multple Pins
=============================================
This code shows deep sleep using ext1 with external wake up from multiple GPIOs
This code is under Public Domain License.
Expands on the sketch by Random Nerd Tutorials:
ESP32 Deep Sleep with Arduino IDE and Wake Up Sources
https://randomnerdtutorials.com/esp32-deep-sleep-arduino-ide-wake-up-sources/
Author: Pranav Cherukupalli <cherukupallip@gmail.com>
*/
#define BUTTON_PIN_BITMASK 13019119616 // This defines wake up source pins. For pins 27, 32 and 33 = 2^27 + 2^32 + 2^33 = 134217728 + 4294967296 + 8589934592 = 13019119616
//#define BUTTON_PIN_BITMASK GPIO_SEL_27 | GPIO_SEL_32 | GPIO_SEL_33
// Method to print the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason(){
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
// Print if if it was a GPIO or something else
switch(wakeup_reason)
{
case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break;
case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break;
case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break;
case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break;
default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break;
}
// If it is due to the a GPIO pin find out which one/s
if (wakeup_reason == ESP_SLEEP_WAKEUP_EXT1) {
uint64_t GPIO_reason = esp_sleep_get_ext1_wakeup_status();
// Print the raw value returned by esp_sleep_get_ext1_wakeup_status. This is the bitmask of the pin/s that triggered wake up
Serial.print("Raw bitmask value returned: ");
Serial.println(GPIO_reason);
// Using log method to work out trigger pin. This is the method used by Random Nerd Tutorials
Serial.print("GPIO that triggered the wake up calculated using log method: ");
int wakeupPin = log(GPIO_reason)/log(2);
Serial.println(wakeupPin);
// Use bitmask to find which pin/s triggered wakeup
Serial.print("GPIO that triggered the wake up using raw bitmask value: ");
switch (GPIO_reason)
{
case 134217728 : Serial.println("Only GPIO 27"); break; // GPIO 27
case 4294967296 : Serial.println("Only GPIO 32"); break; // GPIO 32
case 8589934592 : Serial.println("Only GPIO 33"); break; // GPIO 33
case 4429185024 : Serial.println("Both GPIO 27 + 32"); break; // GPIO 27 + 32
case 8724152320 : Serial.println("Both GPIO 27 + 33"); break; // GPIO 27 + 33
case 12884901888 : Serial.println("Both GPIO 32 + 33"); break; // GPIO 32 + 33
case 13019119616 : Serial.println("All GPIO 27 + 32 + 33"); break; // GPIO 27 + 32 + 33
default : Serial.println("Unknown pin"); break;
}
// Use defined pins bitmask to find which pin/s triggered wakeup
Serial.print("GPIO that triggered the wake up using built in definitions: ");
switch (GPIO_reason)
{
case GPIO_SEL_27 : Serial.println("Only GPIO 27"); break; // GPIO 27
case GPIO_SEL_32 : Serial.println("Only GPIO 32"); break; // GPIO 32
case GPIO_SEL_33 : Serial.println("Only GPIO 33"); break; // GPIO 33
case GPIO_SEL_27 | GPIO_SEL_32 : Serial.println("Both GPIO 27 + 32"); break; // GPIO 27 + 32
case GPIO_SEL_27 | GPIO_SEL_33 : Serial.println("Both GPIO 27 + 33"); break; // GPIO 27 + 33
case GPIO_SEL_32 | GPIO_SEL_33 : Serial.println("Both GPIO 32 + 33"); break; // GPIO 32 + 33
case GPIO_SEL_27 | GPIO_SEL_32 | GPIO_SEL_33 : Serial.println("All GPIO 27 + 32 + 33"); break; // GPIO 27 + 32 + 33
default : Serial.println("Unknown pin"); break;
}
}
}
void setup(){
// Start serial monitor and give it time to start
Serial.begin(115200);
delay(250);
Serial.println("-------------------------------------------");
// Check the wakeup reason
print_wakeup_reason();
// Now go to sleep
goToSleep();
}
void goToSleep() {
// Prepare to sleep
// Set to wake up if any of the set pins go high
//esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK,ESP_EXT1_WAKEUP_ANY_HIGH);
esp_sleep_enable_ext1_wakeup(GPIO_SEL_27 | GPIO_SEL_32 | GPIO_SEL_33, ESP_EXT1_WAKEUP_ANY_HIGH);
Serial.println("===========================================");
Serial.flush();
esp_deep_sleep_start();
Serial.println("This will never be printed");
}
void loop(){
//This is not going to be called
}
A reminder that I am just a hobbyist. Let me know if you see any mistakes, have more info or have tried this and how it went.
Next, I intend to do a post looking at what I’m using to determine how long the ESP32 was asleep, sot that I can tell if it is a ‘bounce’ trigger.
We’re adjacent kinds of nerds, from CB radios to ESP32. I have a load of your articles open in tabs right now to read, so I might as well make an account so I can offer some help. You asked for it.
> I don’t understand bitwise operators and so don’t understand why the OR operator ‘|’ works here instead of the AND one.
Bits work with 1s and 0s or the presence of a thing and the absence of a thing. Bytes are collections of bits. Words are bigger collections of bits. You can also think of them, not coincidentally, as something one might do with 7400-series chips to 5V signals. Let’s name one bit A and one bit B. We’ll call “true” to be 1 and false to be 0. Again, this works both at the electrical and (mathematical) boolean definitions.
A and B returns true if A AND B are true.
A OR B returns true if either A OR B are true.
PLUS and AND seem like they should be similar, but they’re different. You might have to generate a carry condition for addition, but never for a logical and. That’s the difference “why the OR operator ‘|’ works here instead of the AND one.” I think you’re reading “AND” in the colloquial “if have one apple AND another apple, how many do you have” sense, not really in the mathematical sense.
So we can have strings of bits and use these operators. (Look down the columns, like we did in grade school for math for this to make sense) Note that there is never a “carry” when performing an “and”.
0101 AND
1110 =
0100
0101 OR
1110 =
1111
So there’s that, but I think you’re stuck on two more humps that you might not see coming.
Hump 1:
switch(something) {
caseGPIO_SEL_27 |GPIO_SEL_32 : do_something(); break;In C-like languages, SEL_27 | SEL_32 reduces to a mathematical constant, the bitwise result of SEL_27 (logical or) SEL_32. It is not “matching this one or that one”, it’s “Matching the bitwise result of OR-ing those two values”. This returns an operand (an integer) that is thus a constant. GOod thing because…
Hump 2:
switch matches only and exactly one matching value and takes that branch. Well, except for “default:” which is the inverse of matching anything else. So what you’re looking at isn’t generating code looking for a case of SEL_27 OR a case of SEL_32, it’s a case of those two operands mathmatically ORed together.
This makes switches great for enumerated things, like a radio button on a 1970’s car radio – it’s on channel A or it’s on channel E or it’s on channel B.. but absolutely unusable for things that can be in multiple combinations at the same time. The room might be hot. The light might be on. The paint might be blue. The floor might be wet. The room might be on fire. All five of those things can either be or not be and they’re all independent of the other. They are logically independent. It might be convenient to pack those five bits of information (see what I did there?) into one number to carry it around conveniently to see how likely I would be to rent that hotel room, for example.
An ESP32-S3 (or P4) can have more than 32 different GPIO pins. Imagine that any of them were used as inputs for signals or that they were interrupt sources. If they were, say, piano keys or just plain old keyboard keys, more than one of them could be pressed, right? So you would never try to switch every combination of KEY0 | KEY1 | KEY2 .. KEY31. First, you’d lose your mind typing it because there are about 4.2billion combinations. (Don’t be too impressed. 2^32 is one of those numbers that computer people have to know.) So your scheme might work for two bits where you can generate all four combinations (neither, both, only a, only b) but it falls apart for lots of bits. There, you really MIGHT have to test each one separately, testing if what you read AND a constant is set:
if (bits & KEY0) { service_key0(); }
if (bits & key1) …
Now in reality, you tend to have a few exceptional case and a few that are logically grouped so you can test them with
if (bits & (KEY4 | KEY5) { service_modifier_keys(keys); }
…and then let that function fondle keys to see which bits were set in case it needs to differentiate them further somehow, such as to turn on caps lock LED vs control state or whatever.
If you ever look at the lowest value of interrupt handler of a RISC-style architecture (and Espressif’s XTensa and RISC-V both qualify) you’ll see this mixture is pretty much how they work. The CPU gets an exception that tells is that one of a buch of weird things just happened. Some things, like a divide by zero or a write to a non-existent memory error – just need to be redirected to a crash. Some things, like timer interrupts on different pins, can be kind of batched in a clump together and then shipped off to another function for further discrimination.
log2() returns a double, but you were stuffing the result into an integer, so there was no rounding; it was truncation to the next lowest integer. To keep the trivia train rolling, most ESP32’s perform double precision arithmetic via emulating it in software which is hella slow (by computer standards) but MOST esp32’s have hardware single precison so log2f() would have been faster in a case that mattered.
But I think that log2 (or log2f) of a number less than 10 can return 0, which would give you a division by zero which results in a CPU crash, so if your input range could have allowed a zero there, you were in for a bad day.
Oh, and writing those big numbers as decimals is just bizarre. You just don’t see pros doing that, uhm, ever. If you look at those numbers as hex, the patterns become much more clear.
$ bc -l
>>> obase = 16
>>> 13019119616 (27 + 32 + 33)
308000000
>>> 12884901888 (32 OR 33
300000000
>>> 134217728 (only 27 – I added leading zero padding)
008000000
Oh, we can do this directly as binary. (You may have to copy tis someplace with fixed width characters)
$ bc -l
>>> obase = 2
>>> 13019119616
1100001000000000000000000000000000
>>> 12884901888
1100000000000000000000000000000000
>>> 134217728
0000001000000000000000000000000000
That might make it clearer that these aren’t magic numbers. Those if you take the …888 number and take the results of that number OR the …728 number, you get the …616 number.
I know there’s a ton of words here. I just shoved about two weeks of computer science at you, but hopefully by couching it in an example that you provided and in answering questions you asked and with an example I knew so I could provide the pieces that I think you didn’t know to ask, that maybe that proverbial light bulb will go on for you.
I have 17 more tabs of yours open right now to read tonight. I promise to not leave War and Peace comments on all of them. It’s not my blog! 🙂
Happy hacking.
LikeLike
Hi Robert, thank for taking the time to explain that. It makes it clearer. I’m, always concerned when I put something online that I have struggled to understand that I could be wrong or lead people astray. It’s great to get some feedback form someone who has a better understanding.
LikeLike