ESP32 input pullup

Do the ESP32 I/O pins support pinMode(input pullup) ?

Welcome to ESP32 Arduino Core’s documentation — Arduino-ESP32 2.0.6 documentation (espressif.com)

Specifically:
GPIO — Arduino-ESP32 2.0.6 documentation (espressif.com)

PortA yes. portB no.

Looking at the GPIO Matrix and Pin Mux docs, as well as the two that you reference, I see one place the implies that all I/O pins have pullup and pulldown capability, but no reference to "ports," as in the A, B, C, and D ports in the Nano, for example. Also, no reference to how you'd engage the pulldown resistor. I assume the same way you can do the pullup in the AVR Arduinos, eg, write either a 1 or 0 to the pin when it's in the input mode.

I usually prefer port operations, so I'd like to find the ESP32 docs that covers them. Thanks.

You do understand that the ESP8266/ESP32 are not Official Arduino cores?

The Espressif chips are somewhat special; they were introduced for IDF programming:
Doc:
ESP-IDF Programming Guide - ESP32 - — ESP-IDF Programming Guide latest documentation (espressif.com)

Code:
GitHub - espressif/esp-idf: Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Arduino is a "wrapper" for the IDF code base, developed independently for the ESP8266, but the author went to work for Espressif and developed the ESP32 core:
GitHub - espressif/arduino-esp32: Arduino core for the ESP32

image

Now, without me digging into the docs for you, you have 2 ways to approach this that I can suggest:

  • Dig into the IDF docs
  • Dig into the Arduino wrapper

Teaser:
Search · pinmode · GitHub

extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode")));
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "esp32-hal-gpio.h"
#include "hal/gpio_hal.h"
#include "soc/soc_caps.h"

// It fixes lack of pin definition for S3 and for any future SoC
// this function works for ESP32, ESP32-S2 and ESP32-S3 - including the C3, it will return -1 for any pin
#if SOC_TOUCH_SENSOR_NUM >  0
#include "soc/touch_sensor_periph.h"

int8_t digitalPinToTouchChannel(uint8_t pin) 
{
    int8_t ret = -1;
    if (pin < SOC_GPIO_PIN_COUNT) {
        for (uint8_t i = 0; i < SOC_TOUCH_SENSOR_NUM; i++) {
            if (touch_sensor_channel_io_map[i] == pin) {
                ret = i;
                break;
            }
        }
    }
    return ret;
}
#else
// No Touch Sensor available
int8_t digitalPinToTouchChannel(uint8_t pin) 
{
    return -1;
}
#endif

#ifdef SOC_ADC_SUPPORTED
#include "soc/adc_periph.h"

int8_t digitalPinToAnalogChannel(uint8_t pin) 
{
    uint8_t channel = 0;
    if (pin < SOC_GPIO_PIN_COUNT) {
        for (uint8_t i = 0; i < SOC_ADC_PERIPH_NUM; i++) {
            for (uint8_t j = 0; j < SOC_ADC_MAX_CHANNEL_NUM; j++) {
                if (adc_channel_io_map[i][j] == pin) {
                    return channel;
                }
                channel++;
            }
        }
    }
    return -1;
}

int8_t analogChannelToDigitalPin(uint8_t channel) 
{
    if (channel >= (SOC_ADC_PERIPH_NUM * SOC_ADC_MAX_CHANNEL_NUM)) {
        return -1;
    }
    uint8_t adc_unit = (channel / SOC_ADC_MAX_CHANNEL_NUM);
    uint8_t adc_chan = (channel % SOC_ADC_MAX_CHANNEL_NUM);
    return adc_channel_io_map[adc_unit][adc_chan];
}
#else
// No Analog channels availible
int8_t analogChannelToDigitalPin(uint8_t channel) 
{
    return -1;
}
#endif

typedef void (*voidFuncPtr)(void);
typedef void (*voidFuncPtrArg)(void*);
typedef struct {
    voidFuncPtr fn;
    void* arg;
    bool functional;
} InterruptHandle_t;
static InterruptHandle_t __pinInterruptHandlers[SOC_GPIO_PIN_COUNT] = {0,};

#include "driver/rtc_io.h"

extern void ARDUINO_ISR_ATTR __pinMode(uint8_t pin, uint8_t mode)
{
#ifdef RGB_BUILTIN
    if (pin == RGB_BUILTIN){
        __pinMode(RGB_BUILTIN-SOC_GPIO_PIN_COUNT, mode);
        return;
    }
#endif

    if (!GPIO_IS_VALID_GPIO(pin)) {
        log_e("Invalid pin selected");
        return;
    }
    
    gpio_hal_context_t gpiohal;
    gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);

    gpio_config_t conf = {
        .pin_bit_mask = (1ULL<<pin),                 /*!< GPIO pin: set with bit mask, each bit maps to a GPIO */
        .mode = GPIO_MODE_DISABLE,                   /*!< GPIO mode: set input/output mode                     */
        .pull_up_en = GPIO_PULLUP_DISABLE,           /*!< GPIO pull-up                                         */
        .pull_down_en = GPIO_PULLDOWN_DISABLE,       /*!< GPIO pull-down                                       */
        .intr_type = gpiohal.dev->pin[pin].int_type  /*!< GPIO interrupt type - previously set                 */
    };
    if (mode < 0x20) {//io
        conf.mode = mode & (INPUT | OUTPUT);
        if (mode & OPEN_DRAIN) {
            conf.mode |= GPIO_MODE_DEF_OD;
        }
        if (mode & PULLUP) {
            conf.pull_up_en = GPIO_PULLUP_ENABLE;
        }
        if (mode & PULLDOWN) {
            conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
        }
    }
    if(gpio_config(&conf) != ESP_OK)
    {
        log_e("GPIO config failed");
        return;
    }
}

extern void ARDUINO_ISR_ATTR __digitalWrite(uint8_t pin, uint8_t val)
{
    #ifdef RGB_BUILTIN
        if(pin == RGB_BUILTIN){
            //use RMT to set all channels on/off
            const uint8_t comm_val = val != 0 ? RGB_BRIGHTNESS : 0;
            neopixelWrite(RGB_BUILTIN, comm_val, comm_val, comm_val);
            return;
        }
    #endif
	gpio_set_level((gpio_num_t)pin, val);
}

extern int ARDUINO_ISR_ATTR __digitalRead(uint8_t pin)
{
	return gpio_get_level((gpio_num_t)pin);
}

static void ARDUINO_ISR_ATTR __onPinInterrupt(void * arg) {
	InterruptHandle_t * isr = (InterruptHandle_t*)arg;
    if(isr->fn) {
        if(isr->arg){
            ((voidFuncPtrArg)isr->fn)(isr->arg);
        } else {
        	isr->fn();
        }
    }
}

extern void cleanupFunctional(void* arg);

extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional)
{
    static bool interrupt_initialized = false;

    if(!interrupt_initialized) {
    	esp_err_t err = gpio_install_isr_service((int)ARDUINO_ISR_FLAG);
    	interrupt_initialized = (err == ESP_OK) || (err == ESP_ERR_INVALID_STATE);
    }
    if(!interrupt_initialized) {
    	log_e("GPIO ISR Service Failed To Start");
    	return;
    }

    // if new attach without detach remove old info
    if (__pinInterruptHandlers[pin].functional && __pinInterruptHandlers[pin].arg)
    {
    	cleanupFunctional(__pinInterruptHandlers[pin].arg);
    }
    __pinInterruptHandlers[pin].fn = (voidFuncPtr)userFunc;
    __pinInterruptHandlers[pin].arg = arg;
    __pinInterruptHandlers[pin].functional = functional;

    gpio_set_intr_type((gpio_num_t)pin, (gpio_int_type_t)(intr_type & 0x7));
    if(intr_type & 0x8){
    	gpio_wakeup_enable((gpio_num_t)pin, (gpio_int_type_t)(intr_type & 0x7));
    }
    gpio_isr_handler_add((gpio_num_t)pin, __onPinInterrupt, &__pinInterruptHandlers[pin]);


    //FIX interrupts on peripherals outputs (eg. LEDC,...)
    //Enable input in GPIO register
    gpio_hal_context_t gpiohal;
    gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
    gpio_hal_input_enable(&gpiohal, pin);
}

extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type)
{
	__attachInterruptFunctionalArg(pin, userFunc, arg, intr_type, false);
}

extern void __attachInterrupt(uint8_t pin, voidFuncPtr userFunc, int intr_type) {
    __attachInterruptFunctionalArg(pin, (voidFuncPtrArg)userFunc, NULL, intr_type, false);
}

extern void __detachInterrupt(uint8_t pin)
{
	gpio_isr_handler_remove((gpio_num_t)pin); //remove handle and disable isr for pin
	gpio_wakeup_disable((gpio_num_t)pin);

    if (__pinInterruptHandlers[pin].functional && __pinInterruptHandlers[pin].arg)
    {
    	cleanupFunctional(__pinInterruptHandlers[pin].arg);
    }
    __pinInterruptHandlers[pin].fn = NULL;
    __pinInterruptHandlers[pin].arg = NULL;
    __pinInterruptHandlers[pin].functional = false;

    gpio_set_intr_type((gpio_num_t)pin, GPIO_INTR_DISABLE);
}


extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode")));
extern void digitalWrite(uint8_t pin, uint8_t val) __attribute__ ((weak, alias("__digitalWrite")));
extern int digitalRead(uint8_t pin) __attribute__ ((weak, alias("__digitalRead")));
extern void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode) __attribute__ ((weak, alias("__attachInterrupt")));
extern void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void * arg, int mode) __attribute__ ((weak, alias("__attachInterruptArg")));
extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt")));
1 Like
void setup()
{
  gpio_config_t io_cfg = {}; // initialize the gpio configuration structure
  io_cfg.mode = GPIO_MODE_OUTPUT;
  io_cfg.pin_bit_mask = ( (1ULL << GPIO_NUM_0) ); //bit mask of the pins to set, assign gpio number to be configured
  gpio_config(&io_cfg);
  gpio_set_level( GPIO_NUM_0, LOW); // 

Using the ESP32's API in the Arduino IDE to program a GPIO pin.

The GPIO structure offers a way to set ESP32's GPIO settings.

Structures
struct gpio_config_t
Configuration parameters of GPIO pad for gpio_config function.

Public Members

uint64_t pin_bit_mask
GPIO pin: set with bit mask, each bit maps to a GPIO

gpio_mode_t mode
GPIO mode: set input/output mode

gpio_pullup_t pull_up_en
GPIO pull-up

gpio_pulldown_t pull_down_en
GPIO pull-down

gpio_int_type_t intr_type
GPIO interrupt type

Macros
1 Like

Thanks. Your reply does give me some help, but my issue with almost all the ESP32 docs that I've found is that they don't show the cross point matrix between the functional units on the left side of your graphic and the hardware pins. Obviously there is a default arrangement of connections to the pins, but the details of how to reprogram/modify the matrix are nearly inscrutable (to me, at least) without examples of how to connect pinA to functionB.

Oh, my.
I went off into the Internet and did a thimble-full of queries. Found this from an Espressif engineer:

I don't think they're properly documented as they're internal shortcuts for writing to and reading from memory that makes it clear to the reader it's a peripheral register we're accessing; there's no real functionality in them. If you're asking about the arguments (the registers and the values), you want the ESP32 TRM for that.

Buy a case of beer, refrigerate, and download the TRM:
esp32_technical_reference_manual_en.pdf (espressif.com)


Super! thanks, this graphic is actually useful. The fundamental issue is, you gotta know enough to Google usefully.

BTW, it's a case of wine, not beer. I drank all the beer I ever needed to in Vietnam. :grin:

Now that you've motivated me, I did some searching on the I/O pins related to the RTC at this page: GPIO & RTC GPIO — ESP-IDF Programming Guide v3.0-dev-1474-gf8bda32 documentation

API Reference - RTC GPIO
...
Return
true if GPIO is valid for RTC GPIO use. talse otherwise.
Parameters
gpio_num: GPIO number

and I found this new logic level "talse." That's new to me.

Now, seriously, the ESP32 has more capability than I will ever have the time to learn. Using just the default I/O setup, I'd like the IDE to be able to use the pins that aren't dedicated to the OLED or LoRa, based on what the default GPIO matrix and the I/O Mux are set to. So far the only available pin that's a problem is 22--it won't handle OneWire(0) functions. I have repeated this on several devices and I don't see anything in the I/O docs that say that pin22 is weird.

Not to put too fine a point on it, but I still haven't found anything that helps me understand Port A and Port B (per the original pullup question). I can infer that I/O pins up to about 31 have pullups and 32-39 do not. It would be great if a short sentence under Table 1-6 above said that in plain language. 'Nuff said.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.