Here’s a rock-solid, no-mystery way to wire a momentary pushbutton to an STM32, plus simple code that “just works.”
Recommended wiring (active-low with internal pull-up)
Most STM32 pins have an internal pull-up. Use it—then the switch only needs two wires.
Connections
STM32 GPIO pin ────────┐
├─── momentary switch ─── GND
(Enable internal PULL-UP)
Optional noise hardening
- 100 Ω in series between GPIO and switch (limits ESD/spikes).
- 0.1 µF from GPIO to GND (with 10–50 kΩ pull-up → ~1–5 ms debounce RC).
Why this way?
Input idles at logic 1 via pull-up. Pressing the button shorts to GND → 0 (active-low). Fewer parts, safer if a wire comes loose.
STM32 GPIO is 3.3 V logic. Don’t let the pin ever see >3.6 V. Tie the other side of the switch to GND, not 5 V.
Alternative (active-high with pull-down)
If you prefer reading 1 when pressed:
VDD (3.3 V) ──┬── momentary ── GPIO
└─ 10 kΩ ─────── GND (external pull-down)
STM32s usually don’t have strong internal pull-downs; use an external 10 kΩ.
Minimal HAL code (polling + simple debounce)
#include "stm32f1xx_hal.h" // change series to match your MCU
#define BTN_GPIO_Port GPIOA
#define BTN_Pin GPIO_PIN_0 // your chosen pin
#define LED_GPIO_Port GPIOC
#define LED_Pin GPIO_PIN_13 // e.g., Nucleo LED
static uint32_t lastChange = 0;
static uint8_t stable = 1, prev = 1; // active-low input
int main(void) {
HAL_Init();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
// Button as input with internal pull-up
GPIO_InitTypeDef gi = {0};
gi.Pin = BTN_Pin;
gi.Mode = GPIO_MODE_INPUT;
gi.Pull = GPIO_PULLUP; // <-- enables internal pull-up
HAL_GPIO_Init(BTN_GPIO_Port, &gi);
// LED output (for testing)
gi.Pin = LED_Pin;
gi.Mode = GPIO_MODE_OUTPUT_PP;
gi.Pull = GPIO_NOPULL;
gi.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED_GPIO_Port, &gi);
while (1) {
uint8_t raw = HAL_GPIO_ReadPin(BTN_GPIO_Port, BTN_Pin); // 1 = released, 0 = pressed
uint32_t now = HAL_GetTick();
// 10 ms debounce
if (raw != prev) { prev = raw; lastChange = now; }
if (now - lastChange > 10 && raw != stable) {
stable = raw;
if (stable == 0) { // on press
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
}
}
}
Interrupt (EXTI) version (no polling)
Configure the pin as EXTI on falling edge (active-low press), then handle it in the callback.
// Enable clock as above…
// Configure EXTI on BTN pin (falling edge)
GPIO_InitTypeDef gi = {0};
gi.Pin = BTN_Pin;
gi.Mode = GPIO_MODE_IT_FALLING; // interrupt on press (to GND)
gi.Pull = GPIO_PULLUP;
HAL_GPIO_Init(BTN_GPIO_Port, &gi);
// Enable NVIC for the correct EXTI line
HAL_NVIC_SetPriority(EXTI0_IRQn, 5, 0); // if BTN_Pin is PA0
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
// IRQ handler (provided by HAL templates)
void EXTI0_IRQHandler(void) { HAL_GPIO_EXTI_IRQHandler(BTN_Pin); }
// HAL callback (debounce light with timestamp if needed)
void HAL_GPIO_EXTI_Callback(uint16_t pin) {
static uint32_t last = 0;
if (pin == BTN_Pin) {
uint32_t now = HAL_GetTick();
if (now - last > 10) { // 10 ms debounce
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
last = now;
}
}
}
Pick the correct EXTI IRQ for your pin (e.g., EXTI0 for Px0, EXTI1 for Px1, …, EXTI15_10 for Px10–Px15). On many Nucleo boards the user button is PC13, which uses EXTI15_10.
Practical tips
- Debounce: 5–20 ms is typical. Use either the RC shown above or software (or both for long cables).
- Long leads / noisy environments: add the 100 Ω series resistor and 0.1 µF to GND at the MCU side; run GND and signal as a twisted pair.
- Multiple buttons: give each its own pin; you can share the same pull-up and debounce separately in software.
- Pin choice: any GPIO with EXTI works; avoid pins with special boot functions unless you know their state at reset.
Top comments (0)