How does one control a digital pin directly avoiding the slow digitalWrite or digitalRead?

  • 2
    One reads the datasheet.
    – Majenko
    Sep 8, 2017 at 9:42
  • in what manner is it slow? you know the clock speed of an ESP right?
    – dandavis
    Sep 8, 2017 at 12:52
  • 2
    digitalWrite and digitalRead have a lot of overhead since the input pin is a variable. I just tried the following code void loop() { digitalWrite(D3,HIGH); digitalWrite(D3,LOW); } and it runs with 160kHz on a Wemos D1 mini. This is very slow for a micro controller running at 80MHz
    – qubit
    Sep 8, 2017 at 14:35
  • Your question needs more detail.
    – sa_leinad
    Sep 8, 2017 at 17:31
  • 2
    @sa_leinad - no, it really doesn't need more detail - it's perfectly clear as is. This is a pretty basic, and ordinary question. It's quite well known how to do the analogous thing on an ATmega, and it makes perfect sense to have a question about doing it on the ESP8266, if that chip is going to be considered on-topic as an Arduino target at all. Sep 10, 2017 at 22:21

3 Answers 3


After a weekend of searching and trying, here is what I was looking for:

It is slightly different than for the AVR chips. There is one register for setting the pins to HIGH and another one to set the pins to low.

Setting the pin HIGH:

GPOS = (1 << PIN_OUT);

Setting the pin LOW:

GPOC = (1 << PIN_OUT);

using this, I managed to speed up my code by a factor of three. It is not as dramatic as for the AVR chips, since the digitalWrite function is much more simple for the ESP8266.

Edit Further experiments show it is 10x faster: 910nsec for digitalWrite vs. 88nsec using GPOC or GPOS!


Unfortunately, Ratchet Freak's answer is specific to AVR, and isn't how the ESP8266 works.

It looks like the ESP8266 does indeed have registers for doing exactly this, which would let you set all 16 GPIO pins (GPIO0 through GPIO15, 16 is a different thing entirely) with a single operation. There's also a SET register, which sets the pins corresponding to 1 bits, and ignores pins corresponding to 0 bits, and a CLEAR register, which is the other way around.

Something I found on a forum said a user went from ~800KHz to ~5.7MHz toggling pins using these direct registers, that sounds about right.

One version of this is found in eagle_soc.h. The relevant bits from the header to get you started:

#define GPIO_REG_READ(reg)                         READ_PERI_REG(PERIPHS_GPIO_BASEADDR + reg)
#define GPIO_REG_WRITE(reg, val)                 WRITE_PERI_REG(PERIPHS_GPIO_BASEADDR + reg, val)
#define GPIO_OUT_ADDRESS                         0x00
#define GPIO_OUT_W1TS_ADDRESS             0x04
#define GPIO_OUT_W1TC_ADDRESS             0x08

So, if I've understood this correctly (and I haven't done any real testing, just going off the spec sheet and the header and posts elsewhere):


would set GPIO 4-7 and 12-15 to high, and 0-3 and 8-11 to low. In one operation. But there's more! Look at those W1TS and W1TC names. Those are set and clear registers. Which means you don't have to mask things. Instead of grabbing the current value, masking a bit in or out, and then writing it back, you can use those:


This will set GPIO0 high, and not affect any other bits.

This is also offered in a cleaner/simpler (??) form in esp8266_peri.h, which simplifies it to:

GPOS = 0x1;

for setting a bit. This is what's actually called by the arduino library's digitalWrite (for pins other than GPIO16):

extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
  if(pin < 16){
    if(val) GPOS = (1 << pin);
    else GPOC = (1 << pin);

But definitely be aware of this if you're doing anything fast, because writing multiple pins with a single operation is going to be way faster than multiple function calls. Without this, this chip can't even toggle bits as fast as a 16MHz Arduino.


In the spec sheet you will find the details for your device but most of the time it's pretty simple.

There will be global variables to control them, each will control various aspects of the pins.

For example you will have a register to control whether it's input or output, a register to control the output state, ... Again read the spec for your device to know what is what.

After the pin has been set up the instruction to set a pin high is:


where Pinx_MASK is a bit mask with a single bit set according to the pin it represents.

To set the pin low it's similar:


To set multiple pins on the port to different values you can do the following:

PORTA = (PORTA & ~pinsMask) | pinValues;

where pinsMask is a bitmask where all the pins you want to change are set and pinValues is the bitmask of which pins should be set high.

It pays to know how to manipulate bits in bitmasks but that's out of scope for this answer.

The SDK will be doing direct port manipulation itself, if you follow the definitions you should be able to figure it out.

  • 3
    this looks like AVR code, did you test it on the ESP?
    – dandavis
    Sep 8, 2017 at 12:51
  • 2
    This works for a platform with Atmel AVR chips like the Arduino Uno, Nano,..., but it does not not work for ESP8266. I am fully aware of exactly this trick and want to know if it exists for the ESP8266.
    – qubit
    Sep 8, 2017 at 14:47
  • "There will be global variables to control them". This are not global variables, these are registers. When you did this to a variable no pin will change.
    – Codebeat
    Apr 13, 2020 at 3:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.