Programming GPIO on the ESP8266 with NodeMCU

By December 17, 2015ESP8266, IoT

IoT devices (a.k.a., Things) aren’t very interesting if they cannot interact with the world around them. As Makers, we need to hook up sensors, light up LEDs, and communicate with other devices that are wired up to our Things. To do this, we need IO functionality that can be programmed.

GPIO (General Purpose Input/Output) refers to a set of generic pins of a microcontroller that can be used for digital signaling. GPIO pins can be individually set to act as input or output, and values can be either logically high (1) or low (0).

The voltage value is ideally set to VCC for high and GND for low. But, the underlying technology determines the actual acceptable logic levels. For example, 3v3 CMOS commonly accepts anything above 2V as being high and anything below 0.8V as being logical low. Notice the range from 0.8 to 2V – this is the land of undefined logic, where unpredictable read results will occur from chip to chip.

GPIO on the ESP8266-12

While the ESP8266EX microcontroller itself has 17 GPIO pins, only 11 are available on the ESP8266-12 module because the chip is already connected to external SPI flash memory using some of the pins.

GPIO on the ESP8266EX is multiplexed with other functions, which may limit the availability of pins for GPIO usage. So, for example, while HSPI is enabled, the pins for GPIO12-15 are unavailable for GPIO use, and when you need to connect to the module via the UART, then GPIO1 and GPIO3 are unavailable for GPIO use.

All digital I/O pins are protected from over-voltage by means of a snap-back circuit between the pin and ground. The output devices are also protected from reverse voltages with diodes. This suggests that the ESP may be 5V-tolerant, but official guidance from Espressif says to stick to 3.3V to prevent damaging the chip. Note: I have accidentally connected 5V to my ESP’s GPIO without any noticeable damage.

The available pins in NodeMCU include:

0 3 X X X
1 10 X X X The is the TXD0 pin by default
2 4 X X X
3 9 X X X This is the RXD0 pin by default
4 2 X X X Caution: Sometimes misidentified as #5
5 1 X X X Caution: Sometimes misidentified as #4
12 6 X X X This is the HSPI MISO pin
13 7 X X X This is the HSPI MOSI pin
14 5 X X X This is the HSPI CLK pin
15 8 X X X This is the HSPI CSn pin
16 0 X X X Belongs to the RTC module, not the general GPIO module, so behaves differently


Note: The “ID” column shows the IO index that is used to identify GPIO pins in the Lua function calls. Yep, it’s confusing for everybody else, too… unless you happen to use the NodeMCU DevKit, and then the indexes are the same as the “Dx” pins that are labeled here: DevKit 1.0 Pinouts

Note: During reset, certain pins must be pulled high/low with an external resistor so that the chip enters the correct boot mode. For normal operations, GPIO1=1, GPIO2=1, GPIO15=0

Programming GPIO in NodeMCU (ESP8266)

First, the obvious: you will need a NodeMCU firmware image that includes the “gpio” module. Otherwise, you won’t have the API available to program against.

Let’s suppose that we want to add a button to our Thing that the user can press. So, we need to determine the default logic level for when the button is not pressed, and then the opposite logic level would represent when it is being pressed.

Default logic is often set by means of a pull-up or pull-down resistor. This is a relatively high value resistor (2k to 10k Ohm) that bridges the GPIO pin to either VCC (pulling the value up) or GND (pulling the value down).

A switch then will connect that GPIO to the opposite voltage. When the switch is not engaged, the only path from the GPIO pin is through the resistor, so you’ll get a 1 or 0 as a result (depending on whether the resistor leads to VCC or GND, respectively). But, when the switch is pressed, the path of least resistance goes through the switch, so you get its value instead.

Most of the GPIO pins on the ESP8266 have an internal pull-up resistor that can be enabled (GPIO16 is the oddball, because it actually belongs to a different part of the silicon than the rest of the GPIO). This means that we don’t need any external resistors in order to set a default value because it can be controlled programmatically. So, we configure the GPIO to be pulled high (default value=1), wire up a switch between the GPIO pin and GND (switched pressed=0), and our requirements are satisfied.

Add a switch to GPIO5 of the ESP8266

A button connected to GPIO5 will result in a logic value of 0 when pressed

From the table above, the index for GPIO5 is 1, so that needs to be provided in any function call that works with GPIO5:

How about controlling a LED? The ESP cannot source much current, but it can sink current to ground. So, hook a resistor (i.e., 300 ohm) between VCC and the anode (long lead) of a LED, then connect the cathode (short lead) of the LED to a GPIO pin. When a “1” is written to the pin, the LED will be off. When a “0” is written to the pin, the LED will light up.

And finally, how can we detect a change in a GPIO without constantly polling the value? For example, suppose that we want to turn the light on when the user presses the button.

To do this, we need to take advantage of the interrupts for GPIO. Once set up, a callback function will execute when the state of the GPIO pin changes. Note: Interrupts are not supported for that weird GPIO16 pin (index=0).

Documentation for the NodeMCU API can be found here:

If you are interested in seeing details about the lower-level C SDK API provided by Espressif, then look at this document: 8A_ESP8266_Interface_GPIO_EN_v0.5.pdf 

Also published on Medium.

The following two tabs change content below.
  • Pingback: Digi-Dot-Booster am esp8266 esp-12e | LED'sWork()

  • Aldo Castro Freire

    Hi, I am trying to use the last part of the code in other of my codes but it doesnt compile I mean at first time it works but when I reset it, it keeps reseting infinitely. I dont know how you can use that part of the code?

    • Jason Follas

      @aldocastrofreire:disqus : What version of NodeMCU Firmware are you using?

      • Aldo Castro Freire

        I am using the last version of the firmware, do you want to check my code ?

        • Jason Follas

          This blog post was written a few versions ago (pre 1.5.x), so the behavior may be different now. If you can show the relevant part of the code that crashes, I’ll try to replicate and see if I have the same results and/or can offer a fix. (I want to ensure that this blog post is updated if there is a problem with the latest firmware).

          • Aldo Castro Freire


            wifi.sta.config(“BELL464”, “BLACKSTORM356”)


            led1 = 3

            led2 = 4

            gpio.mode(led2, gpio.OUTPUT)

            gpio.mode(led1, gpio.INT)

            gpio.trig(led1, “both”, function(level)

            gpio.write(led2, level)




            conn:on(“receive”, function(client,request)

            local buf = “”;

            local _, _, method, path, vars = string.find(request, “([A-Z]+) (.+)?(.+) HTTP”);

            if(method == nil)then

            _, _, method, path = string.find(request, “([A-Z]+) (.+) HTTP”);


            local _GET = {}

            if (vars ~= nil)then

            for k, v in string.gmatch(vars, “(%w+)=(%w+)&*”) do

            _GET[k] = v



            buf = buf..” LIGHTS CONTROL “;

            buf = buf..”Aldo’s Room ON OFF“;

            local _on,_off = “”,””

            if( == “ON1”)then

            gpio.write(led2, gpio.HIGH) ;

            elseif( == “OFF1”)then

            gpio.write(led2, gpio.LOW) ;







          • Aldo Castro Freire

            I am suspecting there is a bug,but I cant find it

          • Aldo Castro Freire

            mm you know something about my problem ?

          • Jason Follas

            I’m not seeing anything that stands out. I recommend that you post your question to Stack Overflow, tagged with “ESP8266” and “NodeMCU”. The NodeMCU contributors and other developers are able to provide support there (read: there will be more eyes looking at your problem than you will receive in the comments of this blog post).

  • Elfège Leylavergne

    I’m lost, how do you get any gpio to be LOW by default? I tried adding a 10K from D3 to GND but since there’s already a pull up resistor it gave weird results, including frying, I think, two of my boards. Any help?