Switching between OUTPUT and INPUT_PULLUP using registers

MrBean
Posts: 11
Joined: Wed Aug 17, 2022 1:55 pm

Switching between OUTPUT and INPUT_PULLUP using registers

Postby MrBean » Tue Nov 11, 2025 2:56 pm

I have written a couple of functions to set pull-ups and switch between GPIO pins between input and although they work on Wokwi, they don't work on a real ESP32. I am curious as to what I may be missing?

The genGpioMask() function is creating the pin mask correctly, setting the appropriate bits corresponding to the pin numbers assigned. If I uncomment the pinMode() code in readyGpibDbus() everything works, which suggests that either gpio_pullup_en() and gpio_pullup_dis() functions or the REG_WRITE statements are not working, or some other setting needs to be applied?

Code: Select all

struct gpioregister_t {
  uint32_t reg0 = 0;
  uint32_t reg1 = 0;
};

const uint8_t databus[8] = { DIO1_PIN, DIO2_PIN, DIO3_PIN, DIO4_PIN, DIO5_PIN, DIO6_PIN, DIO7_PIN, DIO8_PIN };
const uint8_t ctrlreg[8] = { IFC_PIN, NDAC_PIN, NRFD_PIN, DAV_PIN, EOI_PIN, REN_PIN, SRQ_PIN, ATN_PIN };

gpioregister_t gpioDbMask;
gpioregister_t gpioCtrlMask;


void printRegMaskBin(gpioregister_t& gpioreg){
  Serial.print(gpioreg.reg1, BIN);
  Serial.println(gpioreg.reg0, BIN);
}


/***** Generate register bitmask *****/
void genGpioMask(gpioregister_t& gpioreg, const uint8_t buspins[], uint8_t bitmask) {
  for (uint8_t i=0; i<8; i++) {
    if (bitmask & (1 << i)) {
      if (buspins[i] > 31) {
        gpioreg.reg1 |= ( 1ULL << (buspins[i]-32) );
      }else{
        gpioreg.reg0 |= ( 1ULL << buspins[i] );
      }
    }
  }
}


/***** Init GPIO registers *****/
void initGpioRegMasks(){
  // Generate masks
  genGpioMask(gpioDbMask, databus, 0xFF);
  genGpioMask(gpioCtrlMask, ctrlreg, 0xFF);
}


/***** Set pull-ups *****/
/*
 * bus to set, mask to set, enable/disable  
 */
void gpioSetPullupsMasked(const uint8_t bus[], uint8_t mask, bool enable){
  for (uint8_t i=0; i<8; i++) {
    if (mask & (1<<i)) {
      if (enable) {
        if ( (gpio_num_t)bus[i] ){
          gpio_set_pull_mode((gpio_num_t)bus[i], GPIO_PULLUP_ONLY);
          gpio_pullup_en( (gpio_num_t)bus[i] );
        }
      }else{
        if ( (gpio_num_t)bus[i] ) {
          gpio_pullup_dis( (gpio_num_t)bus[i] );
        }
      }
    }
  }
}


/**** Set the GPIB data bus to input pullup *****/
void readyGpibDbus(uint8_t state = INPUT_PULLUP) {

  // Set pins to OUTPUT
  if (state == OUTPUT) {
    Serial.println(F("Disabling pull-ups..."));
    // Disable pullups
    gpioSetPullupsMasked(databus, 0xFF, false);
//    for (uint8_t i=0; i<8; i++){
//      pinMode(databus[i], OUTPUT);
//    }

    // Set to output
    REG_WRITE( GPIO_ENABLE_W1TS_REG, gpioDbMask.reg0 );
    REG_WRITE( GPIO_ENABLE1_W1TS_REG, gpioDbMask.reg1 );
    // Set to HIGH
    REG_WRITE( GPIO_OUT_W1TS_REG, gpioDbMask.reg0 );
    REG_WRITE( GPIO_OUT1_W1TS_REG, gpioDbMask.reg1 );
    delayMicroseconds(20);
    return;
  }
  // Set pins to INPUT_PULLUP
  REG_WRITE( GPIO_ENABLE_W1TC_REG, gpioDbMask.reg0 );
  REG_WRITE( GPIO_ENABLE1_W1TC_REG, gpioDbMask.reg1 );
  // Enable pullups
  gpioSetPullupsMasked(databus, 0xFF, true);
}


/***** Read the GPIB data bus wires to collect the byte of data *****/
uint8_t readGpibDbus() {
  uint64_t gpioall = 0;
  // Read the byte of data on the bus`
  gpioall = REG_READ(GPIO_IN_REG);
  gpioall |= ( (unsigned long long)REG_READ(GPIO_IN1_REG) << 32 );

  uint8_t result = 0;
  for (uint8_t i=0; i<8; i++) {
    if ( gpioall & (1ULL<<databus[i]) ) result = (result | (1U<<i));
  }
  return ~result;
}

MicroController
Posts: 2661
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Switching between OUTPUT and INPUT_PULLUP using registers

Postby MicroController » Tue Nov 11, 2025 4:28 pm

You never seem to enable input for any of the pins.
However, it is necessary to enable the input in the IO MUX by setting the FUN_IE bit in the IO_MUX_x_REG register corresponding to pin X

MrBean
Posts: 11
Joined: Wed Aug 17, 2022 1:55 pm

Re: Switching between OUTPUT and INPUT_PULLUP using registers

Postby MrBean » Tue Nov 11, 2025 5:16 pm

You never seem to enable input for any of the pins.
However, it is necessary to enable the input in the IO MUX by setting the FUN_IE bit in the IO_MUX_x_REG register corresponding to pin X
Funnily enough, I did wonder whether I had to set the GPIO function somewhere. Having had a look at the datasheet and manual, I can't figure out yet how to set/unset FUN_IE. Can this be set for multiple bits at once or just individually? Examples would be appreciated.

MicroController
Posts: 2661
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Switching between OUTPUT and INPUT_PULLUP using registers

Postby MicroController » Tue Nov 11, 2025 8:56 pm

Every pin has its own IO_MUX_... register, see "6.12.2 IO MUX Register Summary" and "6.13.2 IO MUX Registers" in the TRM v5.5.
Notice that you can just set FUN_IE once (e.g. via gpio_input_enable()) and then forget about it while still being able to independently enable/disable output, pull-ups, &c at any time.

MrBean
Posts: 11
Joined: Wed Aug 17, 2022 1:55 pm

Re: Switching between OUTPUT and INPUT_PULLUP using registers

Postby MrBean » Tue Nov 11, 2025 10:13 pm

Every pin has its own IO_MUX_... register, see "6.12.2 IO MUX Register Summary" and "6.13.2 IO MUX Registers" in the TRM v5.5.
Will check those sections out.
Notice that you can just set FUN_IE once (e.g. via gpio_input_enable()) and then forget about it while still being able to independently enable/disable output, pull-ups, &c at any time.
I appreciate the feedback, but sorry, but that makes absolutely no sense to me at all. I need a group of pins to be bi-directional, operating as input with pull-ups, or as an ouput with pull-ups disabled. They need to switch between the two modes as needed. I am not looking for this to be a fixed input mode with pull-ups. Unless perhaps, gpio_input_enable() is in some way synonymous to setting the pin in GPIO mode (as opposed to PWM, analog, DAC or whatever)? Maybe it will make more sense one I have had a look at those sections.

The gpio_config_t struct looks promising. I see that one can set a 64-bit mask for the pin. So does that mean that if I set a 64 bit mask that sets a bit for, say, 4 GPIO pins, that they would all be configured in one operation with the provided parameters?

MicroController
Posts: 2661
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Switching between OUTPUT and INPUT_PULLUP using registers

Postby MicroController » Wed Nov 12, 2025 11:48 pm

I appreciate the feedback, but sorry, but that makes absolutely no sense to me at all.
You'll get there eventually ;-)

Btw.:
Are you sure you actually need to activate/de-activate output on the pins? It sounds like the other end is using open-drain outputs, in which case the ESP should do the same (i.e. GPIO_MODE_INPUT_OUTPUT_OD).

MrBean
Posts: 11
Joined: Wed Aug 17, 2022 1:55 pm

Re: Switching between OUTPUT and INPUT_PULLUP using registers

Postby MrBean » Thu Nov 13, 2025 12:02 pm

I appreciate the feedback, but sorry, but that makes absolutely no sense to me at all.
You'll get there eventually ;-)
Thank you!
Btw.:
Are you sure you actually need to activate/de-activate output on the pins? It sounds like the other end is using open-drain outputs, in which case the ESP should do the same (i.e. GPIO_MODE_INPUT_OUTPUT_OD).
I am not sure.The "other end" is a legacy 8-bit bus. I re-created my functions using gpio_config() instead of gpio_input_enable() and writing each pin individually in a loop. For most part everything works except the last function shown here (supporting functions and structs included for reference):

Code: Select all

struct gpioregister_t {
  uint32_t reg0 = 0;
  uint32_t reg1 = 0;
};


const uint8_t databus[8] = { DIO1_PIN, DIO2_PIN, DIO3_PIN, DIO4_PIN, DIO5_PIN, DIO6_PIN, DIO7_PIN, DIO8_PIN };
const uint8_t ctrlreg[8] = { IFC_PIN, NDAC_PIN, NRFD_PIN, DAV_PIN, EOI_PIN, REN_PIN, SRQ_PIN, ATN_PIN };

gpioregister_t gpioDbMask;
gpioregister_t gpioCtrlMask;


void printRegMaskBin(gpioregister_t& gpioreg){
  Serial.print(gpioreg.reg1, BIN);
  Serial.println(gpioreg.reg0, BIN);
}


/***** Generate register bitmask *****/
void genGpioMask(gpioregister_t& gpioreg, const uint8_t buspins[], uint8_t bitmask) {
  for (uint8_t i=0; i<8; i++) {
    if (bitmask & (1 << i)) {
      if (buspins[i] > 31) {
        gpioreg.reg1 |= ( 1ULL << (buspins[i]-32) );
      }else{
        gpioreg.reg0 |= ( 1ULL << buspins[i] );
      }
    }
  }
}


void setGpibCtrlDir(uint8_t bits, uint8_t mask){

  uint8_t obits = bits & mask;
  uint8_t ibits =  ~(bits | ~mask);

  gpio_config_t gpioconf;
  const gpio_config_t * cfgptr = &gpioconf;
  gpioconf.pull_down_en = GPIO_PULLDOWN_DISABLE;
  gpioconf.intr_type =  GPIO_INTR_DISABLE;

  if (obits){
  
    // Derrive register output bits
    uint64_t gpioObitsM = genGpioMask(ctrlreg, obits);
    gpioregister_t gpioObits;
    mask64ToReg(gpioObits, gpioObitsM);
    
    /*
    for (uint8_t i=0; i<8; i++){
      if (obits&(1<<i)) gpio_iomux_out(ctrlreg[i], 1, false);
    }    
    */
 
    // Set pin mode and pull-ups
    gpioconf.pin_bit_mask = gpioObitsM;
    gpioconf.mode = GPIO_MODE_OUTPUT;
    gpioconf.pull_up_en = GPIO_PULLUP_DISABLE;
    gpio_config(cfgptr);
//    delayMicroseconds(200);

    // Set to HIGH
//    if (gpioObits.reg0) REG_WRITE( GPIO_OUT_W1TS_REG, gpioObits.reg0 );
//    if (gpioObits.reg1) REG_WRITE( GPIO_OUT1_W1TS_REG, gpioObits.reg1 );

/*
    for (uint8_t i=0; i<8; i++) {
      if (obits&(1<<i)) pinMode(ctrlreg[i], OUTPUT);
    }
*/

  }


  if (ibits){
    // Derrive register input bits
    uint64_t gpioIbitsM = genGpioMask(ctrlreg, ibits);

    // Set pin mode and pullups
    gpioconf.pin_bit_mask = gpioIbitsM;
    gpioconf.mode = GPIO_MODE_INPUT;
    gpioconf.pull_up_en = GPIO_PULLUP_ENABLE;
    gpio_config(cfgptr);
  }
  
}
Outputs are HIGH by default and pulled low to assert. Not sure whether I need the commented out bit under the 'Set to HIGH' comment that sets the pins to a HIGH state after changing them to outputs, but there is a separate function that sets the required bit state as required. I have tried it with and without and it makes no difference.

This direction switching function manipulates the control signals and needs to be able to selectively switch between 2 and 4 bits simultaneously, which is the reason for the masking at the start. The mask and difference do get calculated correctly before the common parameters for gpio_config_t are set. The remaining parameters get set depending on whether the pin needs to be an input or output.

I also tried adding the gpio_iomux_out() loop that is shown commented out. It made no difference, but I am not sure whether I am doing this correctly? There doesn't seem to be a list of enums for pin function values, but the Espressif reference shows a value of '1' used for 'GPIO' in the gpio_dump_io_configuration() example code. However, it also says that these values can be different depending on ESP variant?

If I substitute the pinMode() loop for the gpio_config() code then it works. This means that the input switching works fine and the problem is with the output switching. The logic analyzer shows the pins going high, but can't tell me whether these are being driven high, or pulled-up high.

There is a function that dumps the pin state but Arduino does not have a concept of a FILE type, so can't print to stdout, so unfortunately I am unable to use this to understand what is actually happening with the pins.

MicroController
Posts: 2661
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Switching between OUTPUT and INPUT_PULLUP using registers

Postby MicroController » Thu Nov 13, 2025 2:22 pm

I don't really understand your code, but I think it may be overcomplicating things a little, because:
a) There's basically no reason to ever disable the input functionality of a pin. Enable input on any pins you may want to read input from at some point, and that's it.
b) There's no reason to disable the pull-ups in your case. Just keep them enabled.
c) Unless you need a strong 'high' output, you should just be using open-drain mode (with pull-up!). You'd set up your pins with GPIO_MODE_INPUT_OUTPUT_OD and again be done with it. Logically, you then only either output a 'low' (0) or you don't. Whenever you don't, i.e. you 'output' a 'high', only the pull-up takes the line 'high'. This is the state which you use when you want to send a '1' and equally the state in which you can receive input on the ESP. Whether you're sending a '1' or reading input at any specific moment is just up to the interpretation by your code. You wouldn't need any re-configuring of the IO pins at any time, just write output and read input as needed (make sure output is '1'/'high' when you want to read).

MrBean
Posts: 11
Joined: Wed Aug 17, 2022 1:55 pm

Re: Switching between OUTPUT and INPUT_PULLUP using registers

Postby MrBean » Thu Nov 13, 2025 6:31 pm

a) There's basically no reason to ever disable the input functionality of a pin. Enable input on any pins you may want to read input from at some point, and that's it.
b) There's no reason to disable the pull-ups in your case. Just keep them enabled.
c) Unless you need a strong 'high' output, you should just be using open-drain mode (with pull-up!). You'd set up your pins with GPIO_MODE_INPUT_OUTPUT_OD and again be done with it. Logically, you then only either output a 'low' (0) or you don't. Whenever you don't, i.e. you 'output' a 'high', only the pull-up takes the line 'high'. This is the state which you use when you want to send a '1' and equally the state in which you can receive input on the ESP. Whether you're sending a '1' or reading input at any specific moment is just up to the interpretation by your code. You wouldn't need any re-configuring of the IO pins at any time, just write output and read input as needed (make sure output is '1'/'high' when you want to read).
Perhaps the ESP32 works differently to an AVR in this regard, although I did notice there is an GPIO_MODE_INPUT_OUTPUT setting possible in the gpio_config. I just wasn't sure about how to go about using it. AVR does not have such an option. I also wasn't sure whether pull-ups can be left in place. The less switching, the faster it should be, so I will experiment. I did try GPIO_MODE_OUTPUT_OD which made no difference, but not GPIO_MODE_INPUT_OUTPUT_OD.

I am struggling a little bit with the concepts as to how a pin can be an output and an input_pullup at the same time, since setting output to zero must override the pull-up and also pull down the signal at the other end of the wire?

MicroController
Posts: 2661
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Switching between OUTPUT and INPUT_PULLUP using registers

Postby MicroController » Fri Nov 14, 2025 9:23 am

how a pin can be an output and an input_pullup at the same time, since setting output to zero must override the pull-up and also pull down the signal at the other end of the wire?
https://en.wikipedia.org/wiki/Open_collector#Open_drain

Who is online

Users browsing this forum: Baidu [Spider], meta-externalagent, PerplexityBot, PetalBot and 6 guests