-->
Page 1 of 1

GPIO control register questions

PostPosted: Mon Sep 04, 2017 6:17 am
by Kaisha
I've been digging through the ESP8266 technical reference, eagle_soc.h, esp8266_peri.h, and core_esp8266_wiring_digital.c to better under stand the GPIO registers. Its left me a bit confused, so I have a few questions about the GPIO control registers and how they really work.

Its my understanding that GPIO pins can be in one of 5 'modes'. Input floating, input pullup, input pulldown (only 1 pin supports this, GPIO16 or something I think), output push pull, and output open drain. So I've been looking through the source code files to try to understand which registers I need to write (and what values) to enable each of the 4 modes (ignoring input pulldown) on the GPIO pins.

Now the discrepancy is with the GPIO control registers. The ESP8266 techincal reference doesn't even mention the control registers in the documentation (despite describing all the other registers in detail), the only small acknowledgement of their existence is buried deep in Appendix 1. Even the examples of how to use the GPIO pins fails to mention their existence (but it also has references to macros that I can't find anywhere in code or documentation either so... I dunno... its like sections are just missing). eagle_soc.h likewise has no mention of them, no code for them, macros, etc...

Now the arduino source files, esp8266_peri.h and core_esp8266_wiring_digital.c, define and use these control registers, and for the most part it makes sense. Here's one function in particular:

Code: Select allextern void __pinMode(uint8_t pin, uint8_t mode) {
  if(pin < 16){
    if(mode == SPECIAL){
      GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
      GPEC = (1 << pin); //Disable
      GPF(pin) = GPFFS(GPFFS_BUS(pin));//Set mode to BUS (RX0, TX0, TX1, SPI, HSPI or CLK depending in the pin)
      if(pin == 3) GPF(pin) |= (1 << GPFPU);//enable pullup on RX
    } else if(mode & FUNCTION_0){
      GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
      GPEC = (1 << pin); //Disable
      GPF(pin) = GPFFS((mode >> 4) & 0x07);
      if(pin == 13 && mode == FUNCTION_4) GPF(pin) |= (1 << GPFPU);//enable pullup on RX
    }  else if(mode == OUTPUT || mode == OUTPUT_OPEN_DRAIN){
      GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
      GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
      if(mode == OUTPUT_OPEN_DRAIN) GPC(pin) |= (1 << GPCD);
      GPES = (1 << pin); //Enable
    } else if(mode == INPUT || mode == INPUT_PULLUP){
      GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
      GPEC = (1 << pin); //Disable
      GPC(pin) = (GPC(pin) & (0xF << GPCI)) | (1 << GPCD); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
      if(mode == INPUT_PULLUP) {
          GPF(pin) |= (1 << GPFPU);  // Enable  Pullup
      }
    } else if(mode == WAKEUP_PULLUP || mode == WAKEUP_PULLDOWN){
      GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
      GPEC = (1 << pin); //Disable
      if(mode == WAKEUP_PULLUP) {
          GPF(pin) |= (1 << GPFPU);  // Enable  Pullup
          GPC(pin) = (1 << GPCD) | (4 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(LOW) | WAKEUP_ENABLE(ENABLED)
      } else {
          GPF(pin) |= (1 << GPFPD);  // Enable  Pulldown
          GPC(pin) = (1 << GPCD) | (5 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(HIGH) | WAKEUP_ENABLE(ENABLED)
      }
    }
  } else if(pin == 16){
    GPF16 = GP16FFS(GPFFS_GPIO(pin));//Set mode to GPIO
    GPC16 = 0;
    if(mode == INPUT || mode == INPUT_PULLDOWN_16){
      if(mode == INPUT_PULLDOWN_16){
        GPF16 |= (1 << GP16FPD);//Enable Pulldown
      }
      GP16E &= ~1;
    } else if(mode == OUTPUT){
      GP16E |= 1;
    }
  }
}


Now the control registers define a 'driver' setting for each pin which is described in the documentation as: "1: open drain; 0: normal". I was assuming this was meant for output, and that normal was push-pull, while open drain meant just that, open drain output. Except as you can see in the above Arduino code, open drain is specifically being used for input GPIO modes. Why? Is my understanding of GPIO 'modes' wrong? Can you have an input that is also open drain? Is it something to do with the hardware I don't understand? Is it a mistake in the Arduino source code?

Also, is it possible to have interrupts enabled on an output pin? This seems counter-intuitive, so why would the Arduino code strive to leave interrupts unchanged if they cannot be used?

Last of all, what is a 'sigma-delate' pin source?

Re: GPIO control register questions

PostPosted: Mon Sep 11, 2017 3:01 am
by Solomon Candy
You seem like a novice with espressif documentation. Wiser men have given up complaining about it long ago. I have lost many a hair reading their docs and code. Here though I'd contest your assertion by saying that the Appendix-1 you mention has all the information about using GPIO's.
You can have open-drain or normal mode. Normal should mean drain/source (personally unconfirmed) (unless espressif has used some weird vocabulary). Open-drain is often used in conjunction with the pull-up which, as you say, is also available. Lastly there is an option to use sigma_delta driver to drive the pin. Sigma-delta register is also right there in the Appendix-1. What it does is: it lets you choose a frequency and a duty ratio for the signal; I use it to dim LED's. There is only one sigma-delta driver and therefore only one register to program it, but it can drive as many pins as you want. I can't confirm if using sigma-delta preserves the other settings such as open-drain or pull-up, but I have an inkling it does. As for using open-drain on input, I think the programmer might have OCD and want to do things a certain way because I don't think output driver settings matter upon for input mode (pull-up will be effective though, if chosen). Search for a sigma_delta.c and .h for an example code. If you can't find it, ask me for it.
Also interrupts can be enabled for output mode. It is, I think, used sometimes to trigger an interrupt programatically by changing the output state, though I don't know why. It can also be helpful if you are driving something and the source current or drain current increases beyond hardware limit driving the output pin high/low and thereby triggering an interrupt.

Re: GPIO control register questions

PostPosted: Mon Sep 11, 2017 9:24 am
by Kaisha
I appreciate the response.

Re: GPIO control register questions

PostPosted: Thu Sep 28, 2017 8:58 am
by eriksl
The programming of the GPIO's isn't that strange if you compare it to e.g. the GPIO's of the ATmega. And indeed, there you can also provoke an interrupt by writing a value to an "input" pin. The write changes the latch but the latch isn't connected to the pin at that moment, but *does* generate the interrupt.

It would have been so helpful if Espressif would have documented this all properly.