ESP8266 GPIO16 behaves differently: no interrupt, no PWM and the deep sleep wake link

GPIO16 is special on the ESP8266. It has no interrupt or PWM, and it is used for deep sleep wake-up.

What the message means

The pin belongs to a different part of the chip. Code that works on other pins fails silently on it.

Fix it in this order

  1. Do not attach an interrupt to GPIO16.
  2. Do not use it for PWM or software I2C.
  3. Use it for plain digital input and output only.
  4. To use deep sleep, connect GPIO16 to the RST pin as the guide for your board describes.
  5. Disconnect that link while you upload if the upload fails.
  6. On NodeMCU, GPIO16 is the pin labelled D0.

Why it happens

On the ESP8266 GPIO16 is driven by the RTC part of the chip, not the main GPIO block that serves the other pins. That block provides the interrupt and PWM features, so GPIO16 has neither, and the Arduino core does not support them on it. The pin is wired for one special job: during deep sleep the RTC timer pulls it low, and with GPIO16 connected to RST that pulse resets the chip and wakes it.

The same link is why uploads can fail: the board is held in reset by the pin. Remove the wire for the upload, then fit it again. On NodeMCU boards GPIO16 is the pin marked D0.

If you see this, do this

You seeDo this
Interrupt never fires on D0GPIO16 has none.
Cannot upload with the sleep linkRemove the link for the upload.

NodeMCU D pins, ESP32 deep sleep wake.

Last reviewed: November 2026. These are the usual causes and the order that finds them fastest. Test on your own board; clones and cables differ.

Why this happens

On the ESP8266, GPIO 0 to 15 belong to the main GPIO block, which provides interrupts, PWM and the other pin functions. GPIO 16 belongs to the RTC part of the chip instead and is a simpler pin. It can be a digital input or output, and it has no interrupt or PWM function in the Arduino core. A call to attachInterrupt on it does not work, and analogWrite does nothing useful.

Its special job is deep sleep. The chip’s RTC timer pulls GPIO 16 low when the sleep time ends. If GPIO 16 is wired to the reset pin, that pulse resets the chip and wakes it. The link must be fitted for wake-up to work, and it is also why uploads sometimes fail while the link is fitted.

How to confirm the cause

  • Check the pin label. On NodeMCU boards, D0 is GPIO 16.
  • If an interrupt never fires on D0, print digitalRead(16) in loop to see if the input changes. If it does, the pin works and only the interrupt is the issue.
  • If you use deep sleep, check that GPIO 16 is joined to RST with a wire, and that the board resets at the end of the time.
  • If an upload fails with the link fitted, remove the wire, upload, and fit it again.
  • For an input with a pull-down, use the INPUT_PULLDOWN_16 mode defined for this pin. The ordinary pull-up mode is not available on it.

Worked example

The first sketch shows a safe use of the pin as a plain input read by polling. The second wakes the board after ten seconds when GPIO 16 is linked to RST. The sleep time is given in microseconds. Use one at a time.

// polling instead of an interrupt
void setup() { Serial.begin(115200); pinMode(16, INPUT); }
void loop() { Serial.println(digitalRead(16)); delay(200); }

// deep sleep with GPIO16 linked to RST
void setup() {
  Serial.begin(115200);
  Serial.println("awake");
  ESP.deepSleep(10e6);   // 10 seconds
}
void loop() {}

Common mistakes

  • Using D0 for an encoder or other interrupt-driven input.
  • Trying to use D0 for PWM or software I2C.
  • Leaving the wake link fitted during upload.
  • Forgetting the link, so the board sleeps and never wakes.

If this did not fix it

  • The reset wiring: a poor joint on RST stops the wake-up.
  • Another pin, because most boards have enough interrupt-capable pins to choose from.
  • The supply: a board that wakes on a weak supply may reset again.