Pull-Up, Pull-Down& Floating Inputs-Embedded C Training Institute in Hyderabad

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Tutorial 04 — GPIO

Pull-Up, Pull-Down
& Floating Inputs

Why floating inputs are dangerous, the PUPDR register bit by bit, open-drain vs push-pull, and software button debouncing with real examples.

⚡ PUPDR Register⏱ ~20 min read🔴 Floating Input Dangers📈 Debounce Techniques

The Floating Input Problem

When a GPIO pin is configured as an input but nothing is connected to it (or a switch is open), the pin is said to be floating (also called high-impedance or tri-state). A floating pin is extremely dangerous in embedded systems.

⚠️ A floating input acts like an antenna. The pin will pick up noise from nearby circuits, your hand, power supply ripple, or even electromagnetic fields in the room. It may read HIGH or LOW randomly — and it can oscillate thousands of times per second, causing the CPU to trigger false interrupts and consuming significant power.

Why Does This Happen?

A GPIO input buffer has extremely high input impedance (millions of ohms). With no definite voltage source connected, even picoamps of leakage current through nearby PCB traces create enough voltage to tip the comparator inside the buffer to either state. The behavior is completely unpredictable.

Floating Input — Dangerous Scenario
VDD (3.3V)
(no connection)
GPIO PIN
Input Buffer
→ IDR bit (reads 0 or 1 randomly!)
(no connection)
GND
Result: Pin floats → IDR bit oscillates → False interrupts!

Pull-Up vs Pull-Down — The Solution

The fix is to connect a weak resistor (typically 10–100kΩ) from the pin to either VDD (pull-up) or GND (pull-down). This provides a defined “default” state when nothing drives the pin.

Diagram: Pull-Up vs Pull-Down — The Solution
❌ Floating Input
  • No defined state when switch is open
  • Reads random 0s and 1s
  • Triggers false interrupts
  • Higher current consumption
  • Never do this intentionally
✅ Pull-Up Resistor
  • Resistor from VDD to pin
  • Default state = HIGH (1) when open
  • Button connects pin to GND → reads LOW
  • Active-LOW button logic
  • Most common for buttons/switches
⚠ Pull-Down Resistor
  • Resistor from GND to pin
  • Default state = LOW (0) when open
  • Button connects pin to VDD → reads HIGH
  • Active-HIGH button logic
  • Used on PA0 (USER button on Discovery)
Pull-Up Configuration (Active-LOW Button)
VDD (3.3V)
[R] 10kΩ
← Pull-up resistor
GPIO PIN
Input Buffer
[SW] Button
GND
Button open → Pin = HIGH (3.3V) → IDR reads 1
Button closed → Pin = LOW (0V) → IDR reads 0
Pull-Down Configuration (Active-HIGH Button — like PA0 on Discovery)
VDD (3.3V)
[SW] Button
GPIO PIN
Input Buffer
[R] 10kΩ
← Pull-down resistor
GND
Button open → Pin = LOW (0V) → IDR reads 0
Button closed → Pin = HIGH (3.3V) → IDR reads 1

PUPDR Register — Bit-by-Bit

On STM32F407, both pull-up and pull-down resistors are built into the chip. You enable them through the GPIOx_PUPDR register without needing external components (though external resistors are better for noise immunity and power efficiency).

GPIOx_PUPDR (Offset: 0x0C) — 2 bits per pin. Reset value: 0x64000000 for GPIOA (due to SWD pins), 0x00000000 for others.
31:30
PU15[1:0]
29:28
PU14[1:0]
27:26
PU13[1:0]
25:24
PU12[1:0]
23:22
PU11[1:0]
21:20
PU10[1:0]
19:18
PU9[1:0]
17:16
PU8[1:0]
15:14
PU7[1:0]
13:12
PU6[1:0]
11:10
PU5[1:0]
9:8
PU4[1:0]
7:6
PU3[1:0]
5:4
PU2[1:0]
3:2
PU1[1:0]
1:0
PU0[1:0]
PUPDRy[1:0]ConfigurationTypical Resistor ValueWhen to Use
00No pull-up, no pull-downN/AActive-driven signals (UART RX from another MCU), analog inputs (always use this for ADC)
01Pull-up enabled~40kΩ (internal)Active-low buttons, I2C lines (though external 4.7kΩ preferred), unused pins
10Pull-down enabled~40kΩ (internal)Active-high signals, CAN bus recessive level, ensure LOW when not driven
11Reserved—Never use — reserved by STMicroelectronics
💡 PUPDR also affects outputs: Pull-up/pull-down can also be configured on output pins. For open-drain outputs, an internal pull-up provides the “passive high” state without an external resistor — useful for slow I2C but not recommended for high-speed applications due to slower rise times.
/* ---- Configure PA1 with internal pull-up (active-low button) ---- */
/* Step 1: Enable GPIOA clock */
RCC_AHB1ENR |= (1U << 0);

/* Step 2: Set PA1 as input (MODER bits [3:2] = 00) */
GPIOA->MODER &= ~(0x3U << 2);   /* bits [3:2] = 00 */

/* Step 3: Enable pull-up on PA1 (PUPDR bits [3:2] = 01) */
GPIOA->PUPDR &= ~(0x3U << 2);   /* clear bits [3:2] */
GPIOA->PUPDR |=  (0x1U << 2);   /* set  bits [3:2] = 01 (pull-up) */

/* Reading: button NOT pressed = 1, button pressed = 0 */
uint8_t btn = (1 - (GPIOA->IDR >> 1) & 0x1U);

/* ---- Configure PA2 with pull-down ---- */
GPIOA->MODER &= ~(0x3U << 4);   /* input */
GPIOA->PUPDR &= ~(0x3U << 4);   /* clear */
GPIOA->PUPDR |=  (0x2U << 4);   /* bits [5:4] = 10 (pull-down) */

/* ---- Configure PC0 as analog (ADC) — MUST disable pull resistors ---- */
GPIOC->MODER |=  (0x3U << 0);   /* analog mode */
GPIOC->PUPDR &= ~(0x3U << 0);   /* NO pull resistors on analog pin! */

Internal vs External Resistors

When should you use the STM32’s internal ~40kΩ pull-up/pull-down vs adding an external resistor?

AspectInternal (~40kΩ)External (1k–10kΩ)
SetupJust set PUPDR register — zero componentsRequires physical resistor on PCB
Rise/Fall timeSlow (RC with pin capacitance) — bad for fast signalsFaster with lower resistance
Noise immunityPoor — high resistance means less current, more noise susceptibilityBetter — stronger pullup means more stable
Power consumptionVery low (~80µA per pin at 3.3V)Depends on value — 10kΩ = 330µA at 3.3V
Use for I2COnly for very short buses at slow speedsRequired — 4.7kΩ pull-ups mandatory
Use for buttonsFine — 40kΩ is adequate for most buttonsBetter noise rejection in industrial environments
Best forPrototyping, development boards, unused pinsProduction PCBs, I2C, long cable runs

Open-Drain Output with Pull-Up

Open-drain outputs (OTYPER bit = 1) are used when multiple devices share a signal line — like I2C SDA and SCL. Each device can only pull the line LOW; the pull-up resistor brings it HIGH when nobody is driving it. This is how I2C achieves wire-AND logic.

Open-Drain + Pull-Up (I2C Principle)
VDD (3.3V)
[R] 4.7kΩ
← External pull-up (or internal if enabled in PUPDR)
SDA line
GPIO (MCU, open-drain output)
GPIO (Sensor, open-drain output)
When MCU drives LOW → Line = 0V (pulled to GND through transistor)
When nobody drives → Line = 3.3V (pull-up wins)
Both can “speak” on same wire without short-circuit!
/* Configure PB6 (I2C1_SCL) and PB7 (I2C1_SDA) as open-drain outputs */
/* with internal pull-up (use external 4.7kΩ in production!) */

RCC_AHB1ENR |= (1U << 1);  /* enable GPIOB clock */

/* Set as alternate function (I2C1 = AF4) */
GPIOB->MODER &= ~((0x3U<<12) | (0x3U<<14));
GPIOB->MODER |=  ((0x2U<<12) | (0x2U<<14));  /* AF mode */

/* Open-drain (mandatory for I2C) */
GPIOB->OTYPER |= (1U<<6) | (1U<<7);

/* High speed for I2C fast mode (400 kHz) */
GPIOB->OSPEEDR &= ~((0x3U<<12)|(0x3U<<14));
GPIOB->OSPEEDR |=  ((0x2U<<12)|(0x2U<<14));  /* Fast speed */

/* Internal pull-up (replace with external 4.7kΩ for production) */
GPIOB->PUPDR &= ~((0x3U<<12)|(0x3U<<14));
GPIOB->PUPDR |=  ((0x1U<<12)|(0x1U<<14));  /* Pull-up */

/* Set alternate function to AF4 (I2C1) in AFRL */
GPIOB->AFR[0] &= ~((0xFU<<24)|(0xFU<<28));  /* clear AF for PB6,PB7 */
GPIOB->AFR[0] |=  ((0x4U<<24)|(0x4U<<28));  /* AF4 = I2C1 */

Button Debouncing

A mechanical button doesn’t make clean, instant contact. When you press it, the metal contacts bounce several times in the first few milliseconds, generating rapid pulses that look like dozens of presses. You must debounce to get a single clean press event.

Button Bounce — What IDR actually sees (exaggerated)
IDR reading over time when button pressed:
OPEN  1

PRESS 0
← bounce (5-20ms) →
↳ stable LOW
Your code must ignore the bouncing and only detect the final stable state!

Software Debounce — Delay Method

/* Simple delay-based debounce (polling approach) */
#define DEBOUNCE_DELAY  20  /* 20 ms debounce window */

uint8_t button_get_state(void) {
    uint8_t current = (GPIOA->IDR >> 0) & 1U;  /* PA0, active HIGH */

    if (current == 1) {           /* Button appears pressed */
        delay_ms(DEBOUNCE_DELAY);  /* Wait through bounce */
        current = (GPIOA->IDR >> 0) & 1U;
        return (current == 1);      /* Confirm it's still pressed */
    }
    return 0;
}

Better Debounce — State Machine (Non-Blocking)

/* Non-blocking debounce using SysTick tick count */
typedef enum {
    BTN_IDLE = 0,
    BTN_PRESSED,
    BTN_RELEASED
} BtnState;

static BtnState btn_state = BTN_IDLE;
static uint32_t btn_timestamp = 0;

/* Call this from SysTick_Handler or polling loop every 1ms */
BtnState button_update(uint32_t tick_ms) {
    uint8_t raw = (GPIOA->IDR >> 0) & 1U;  /* PA0 raw reading */

    switch(btn_state) {
        case BTN_IDLE:
            if (raw == 1) {           /* Potential press detected */
                btn_timestamp = tick_ms;
                btn_state = BTN_PRESSED;
            }
            break;
        case BTN_PRESSED:
            if (raw == 0) {           /* Released during bounce window */
                btn_state = BTN_IDLE;   /* False press — reset */
            } else if ((tick_ms - btn_timestamp) > 20) {
                btn_state = BTN_RELEASED;  /* Stable for 20ms — real press! */
                return BTN_PRESSED;
            }
            break;
        case BTN_RELEASED:
            if (raw == 0) {
                btn_state = BTN_IDLE;
                return BTN_RELEASED;
            }
            break;
    }
    return BTN_IDLE;
}

Practical Configuration Guide

ScenarioMODEROTYPERPUPDRNotes
Button with external pull-up00 (Input)N/A00 (None)External resistor does the job
Button without resistor (pull-up in SW)00 (Input)N/A01 (Pull-up)Active LOW — reads 0 when pressed
PA0 USER button on Discovery00 (Input)N/A00 (None)Board has 10kΩ pull-down hardware
LED output (push-pull)01 (Output)0 (PP)00 (None)Never pull-up/down on driven outputs
I2C SDA/SCL10 (AF)1 (OD)01 (PU)Use external 4.7kΩ in production
UART TX10 (AF)0 (PP)00 or 01Some use pull-up to keep line IDLE HIGH
ADC input11 (Analog)N/A00 (None)NEVER enable pull resistors on analog!
Unused GPIO pin01 (Output)0 (PP)00 (None)Drive to known state (LOW) to prevent floating
⚠️ Always configure unused pins! Leave unused GPIO pins as outputs driven to a known state (preferably LOW) with no pull resistor. Alternatively, configure as input with pull-down. Floating unused pins can increase current consumption by up to 1mA per pin in some conditions.

Complete Code — All Button Configurations

#include <stdint.h>

typedef struct {
    volatile uint32_t MODER,OTYPER,OSPEEDR,PUPDR,IDR,ODR,BSRR,LCKR;
    volatile uint32_t AFR[2];
} GPIO_TypeDef;

#define GPIOA  ((GPIO_TypeDef*)0x40020000UL)
#define GPIOD  ((GPIO_TypeDef*)0x40020C00UL)
#define RCC_AHB1ENR (*(volatile uint32_t*)0x40023830UL)

void gpio_configure_button_pulldown(void) {
    /* PA0: Input, no pull (board has hardware pull-down) */
    RCC_AHB1ENR |= (1U<<0);
    GPIOA->MODER  &= ~(0x3U<<0);  /* input */
    GPIOA->PUPDR  &= ~(0x3U<<0);  /* no pull (00) */
}

void gpio_configure_button_sw_pullup(void) {
    /* PB0: Input, software pull-up, active LOW */
    RCC_AHB1ENR |= (1U<<1);
    GPIOB->MODER  &= ~(0x3U<<0);  /* input */
    GPIOB->PUPDR  &= ~(0x3U<<0);
    GPIOB->PUPDR  |=  (0x1U<<0);  /* pull-up (01) */
}

int main(void) {
    gpio_configure_button_pulldown();  /* PA0 with active HIGH */

    /* Configure PD12 as output LED */
    RCC_AHB1ENR |= (1U<<3);
    GPIOD->MODER &= ~(0x3U<<24);
    GPIOD->MODER |=  (0x1U<<24);
    GPIOD->PUPDR &= ~(0x3U<<24);  /* no pull on output */

    while(1) {
        if ((GPIOA->IDR & (1U<<0)) != 0) {
            GPIOD->BSRR = (1U<<12);   /* LED ON  when button held */
        } else {
            GPIOD->BSRR = (1U<<28);   /* LED OFF when released */
        }
    }
}
Tutorial 04 Key Takeaways
  • Floating inputs read random values — always define a pull-up or pull-down
  • PUPDR register: 00=None, 01=Pull-up (~40kΩ), 10=Pull-down, 11=Reserved
  • Discovery board PA0 has external hardware pull-down — set PUPDR=00
  • Internal ~40kΩ is fine for buttons; use external 4.7kΩ for I2C
  • Open-drain output MUST have pull-up (internal or external) to reach HIGH
  • Always debounce mechanical buttons — 20ms delay or state machine
  • Never enable PUPDR on analog pins (ADC/DAC) — it biases the measurement

STM32F407 Discovery Tutorial Series — Tutorial 04: Pull-Up, Pull-Down & Floating Inputs

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *