Embedded C track

Lesson 15 · Part 3: STM32 (Keil MDK-ARM / STM32F103C8, direct register control)

GPIO Input, Pull-up/Pull-down

IDR and internal pull-ups

Part 3: STM32 (Keil MDK-ARM / STM32F103C8, direct register control)6 min readFree lesson

If in Part 14 we sent signals through the pin, this time we read signals from the pin. Inputs like buttons and switches are a basic feature of embedded systems, but there is a trap that beginners often fall into. This is floating input — an input pin that is not connected to anything is in a floating state, neither 0 nor 1, so its value fluctuates depending on noise. To prevent this, we use a Pull-up/Pulldown resistor, and the STM32 has this built into the chip, which can be enabled via a register.

In this part, we set PA0 as input, enable the internal pull-up, then read the button state via IDR to control the PC13 LED. You will learn 4-bit settings for input mode, the method of selecting through Pull-up/Pulldown ODR, and active logic (active low/high).

Learning Objectives

  • Set GPIO to input mode and read pin status with IDR.
  • Explain the danger of floating input and the necessity of Of-pull-up/pull-down.
  • On F1, select via internal Pull-up/Pulldown ODR bits.
  • Implement judgment logic according to active Low/High button wiring.
  • Turn the LED on and off with button input.

Target Environment

  • Compiler: Keil MDK-ARM (Arm Compiler 6).
  • Device: STM32F103C8 (μVision simulator).
  • Verification: μVision simulator (force input on GPIOA IDR, observe GPIOC). Judgment logic is PC gcc(pc_test.c).

Key Concepts

1) Input Mode 4 Bits

Input has MODE=00, and CNF determines the type of input.

CNF Input Type 4-bit Code
00 Analog 0x0
01 Floating input (reset default) 0x4
10 Pull-up/Pulldown input 0x8

Button input usually uses Pull-up/Pulldown input (0x8).

2) Why floating is dangerous

If there is nothing connected to the input pin, the pin voltage floats between 0 and 1 according to surrounding noise. This causes chattering, making it read as if pressed even when the button is not pressed. Setting a default using pull-up (normally fixed at 1) or pull-down (normally fixed at 0) solves this problem.

3) Pull-up/Pulldown selection by ODR

When CNF=10 (Pull-up/Pulldown input) on F1, which one it is is determined by the ODR bit.

ODR bit Effect
1 Internal pull-up (normally IDR=1)
0 Internal pull-down (normally IDR=0)
GPIOA->CRL |= (0x8u << 0);   /*PA0 = Pull-up/Pull-down input*/
GPIOA->ODR |= (1u << 0);     /*ODR=1 → Pull-up*/

4) Reading — IDR

The corresponding bit of IDR (Input Data Register) is the pin status. Mask it to see just one bit.

if ((GPIOA->IDR & (1u << 0)) == 0u) { /*PA0 is 0 (pressed)*/ }

5) Active logic — pull-up + GND button = Active low

If you place the button between PA0 and GND and enable the pull-up:

  • Not pressed → pull-up keeps 1 → IDR=1
  • Pressed → connected to GND → IDR=0

That is, Pressed = 0 (Active Low). If it is a pull-down + VCC button, it is the opposite: Pressed = 1 (Active High).

Creating a μVision Project

  1. Project → New µVision Project… → name it stm32_gpio_in.
  2. Device: STM32F103C8.
  3. Manage Run-Time Environment → CMSIS:CORE, Device:Startup.
  4. After adding main.c, copy and paste.
  5. Apply STM32F10X_MD to C/C++ → Define.
  6. Debug → Use Simulator, -pSTM32F103C8.
  7. Rebuild(F7) → Debug(Ctrl+F5).

Examples

Example 1 — main.c: Control LED with Button

#include "stm32f10x.h"

/*
 *Lecture 15 Example — Controlling LED with Button Input.
 *Target: STM32F103C8, Compiler: Keil MDK-ARM.
 *
 *Set PA0 as 'Input + Internal Pull-up' and connect the button between PA0 and GND.
 *- Not pressed: pull-up pulls it to 1 → IDR bit = 1
 *- Pressed: connected to GND → IDR bit = 0 (active-low button)
 *Turn on PC13 LED when the button is pressed (LED is also active-low, so pin=0 is ON).
 */

#define BTN_PIN   0u     /* PA0 */
#define LED_PIN   13u    /* PC13 */

int main(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN;

    /*PA0: Input + Pull-up/Pull-down. CNF=10, MODE=00 → 4-bit 0x8.
     *Select pull-up or pull-down using ODR bit (ODR=1 → pull-up).*/
    GPIOA->CRL &= ~(0xFu << (BTN_PIN * 4u));
    GPIOA->CRL |=  (0x8u << (BTN_PIN * 4u));
    GPIOA->ODR |=  (1u << BTN_PIN);                    /*Select pull-up*/

    /*PC13: Push-pull output 2MHz = 0x2 (CRH)*/
    GPIOC->CRH &= ~(0xFu << ((LED_PIN - 8u) * 4u));
    GPIOC->CRH |=  (0x2u << ((LED_PIN - 8u) * 4u));

    while (1) {
        if ((GPIOA->IDR & (1u << BTN_PIN)) == 0u) {
            GPIOC->BRR  = (1u << LED_PIN);   /*Pressed: LED ON (=0)*/
        } else {
            GPIOC->BSRR = (1u << LED_PIN);   /*Not pressed: LED OFF (=1)*/
        }
    }
}

Example 2 — pc_test.c: Input Judgment Logic Check

#include <stdio.h>
#include <stdint.h>

/*
 *Lecture 15 PC Checking — Button press determination and LED decision logic in pull-up input.
 *When the pull-up + button is connected to GND, if not pressed 1 (pull-up), if pressed 0 (active low).
 *Check the determination for various IDR values without hardware.
 */

static int is_pressed(uint32_t idr, unsigned int pin)
{
    return ((idr & (1u << pin)) == 0u) ? 1 : 0;   /*If the bit is 0, it is pressed.*/
}

int main(void)
{
    uint32_t samples[4] = { 0x0001u, 0x0000u, 0x0021u, 0x0020u };
    unsigned int pin = 0u;
    int i;

    for (i = 0; i < 4; i++) {
        int pressed = is_pressed(samples[i], pin);
        printf("IDR=0x%04lX -> %s -> LED %s\n",
               (unsigned long)samples[i],
               pressed ? "PRESSED" : "released",
               pressed ? "ON" : "OFF");
    }
    return 0;
}

Checking with Simulator

  • Since there is no button in the simulator, directly change the IDR of Peripherals → GPIOA or force the pin with a debug command. Example: In the Command window, PORTA &= ~0x0001(PA0=0, pressed) / PORTA |= 0x0001(PA0=1, not pressed).
  • When PA0=0, check via GPIOC ODR if PC13 is 0 (LED ON); when PA0=1, PC13 should be 1 (LED OFF).
  • Expected Behavior: PC13 immediately follows in the opposite state according to the PA0 input.

Verification on a PC

gcc pc_test.c -o pc_test && ./pc_test

Expected output (stdout):

IDR=0x0001 -> released -> LED OFF
IDR=0x0000 -> PRESSED -> LED ON
IDR=0x0021 -> released -> LED OFF
IDR=0x0020 -> PRESSED -> LED ON

Common Mistakes

Q. The value fluctuates randomly even when the button is not pressed.

A. Pull-up/Pulldown is not turned on, so it is in a floating state. Set CNF=10 (0x8) and select pull-up (1) or pull-down (0) with ODR.

Q. Even with CNF=10, the pull-up does not work.

A. On the F1, Pull-up/Pulldown is selected with the ODR bit. In input mode, ODR=1 means pull-up, 0 means pull-down. Do not forget to set ODR.

Q. Will the pin change if I write to IDR?

A. IDR is read-only. Output is done with With-ODR/BSRR/BRR. IDR only reads the current input state of the pin.

Q. Sometimes the button acts as if it was pressed twice.

A. Mechanical buttons have contact bounce. This section deals only with simple reading, and debouncing is properly handled with a state machine in section 21.

Summary

  • Input type is determined by MODE=00, CNF (0x8 = Pull-up/Pulldown input).
  • To avoid floating, turn on Pull-up/Pulldown, and F1 selects that output to ODR.
  • Pin state is read via the read-only IDR.
  • Pull-up + GND button = pressed state is 0 (active low).
  • Chattering is handled in debouncing (section 21).

Try it yourself

Solve the assignment below. The reference solution is shown below.

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/stm32_gpio_in/main.cC
#include "stm32f10x.h"

/*
 *Lecture 15 Example — Controlling LED with Button Input.
 *Target: STM32F103C8, Compiler: Keil MDK-ARM.
 *
 *Set PA0 as 'Input + Internal Pull-up' and connect the button between PA0 and GND.
 *- Not pressed: pull-up pulls it to 1 → IDR bit = 1
 *- Pressed: connected to GND → IDR bit = 0 (active-low button)
 *Turn on PC13 LED when the button is pressed (LED is also active-low, so pin=0 is ON).
 */

#define BTN_PIN   0u     /* PA0 */
#define LED_PIN   13u    /* PC13 */

int main(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN;

    /*PA0: Input + Pull-up/Pull-down. CNF=10, MODE=00 → 4-bit 0x8.
     *Select pull-up or pull-down using ODR bit (ODR=1 → pull-up).*/
    GPIOA->CRL &= ~(0xFu << (BTN_PIN * 4u));
    GPIOA->CRL |=  (0x8u << (BTN_PIN * 4u));
    GPIOA->ODR |=  (1u << BTN_PIN);                    /*Select pull-up*/

    /*PC13: Push-pull output 2MHz = 0x2 (CRH)*/
    GPIOC->CRH &= ~(0xFu << ((LED_PIN - 8u) * 4u));
    GPIOC->CRH |=  (0x2u << ((LED_PIN - 8u) * 4u));

    while (1) {
        if ((GPIOA->IDR & (1u << BTN_PIN)) == 0u) {
            GPIOC->BRR  = (1u << LED_PIN);   /*Pressed: LED ON (=0)*/
        } else {
            GPIOC->BSRR = (1u << LED_PIN);   /*Not pressed: LED OFF (=1)*/
        }
    }
}
src/stm32_gpio_in/pc_test.cC
#include <stdio.h>
#include <stdint.h>

/*
 *Lecture 15 PC Checking — Button press determination and LED decision logic in pull-up input.
 *When the pull-up + button is connected to GND, if not pressed 1 (pull-up), if pressed 0 (active low).
 *Check the determination for various IDR values without hardware.
 */

static int is_pressed(uint32_t idr, unsigned int pin)
{
    return ((idr & (1u << pin)) == 0u) ? 1 : 0;   /*If the bit is 0, it is pressed.*/
}

int main(void)
{
    uint32_t samples[4] = { 0x0001u, 0x0000u, 0x0021u, 0x0020u };
    unsigned int pin = 0u;
    int i;

    for (i = 0; i < 4; i++) {
        int pressed = is_pressed(samples[i], pin);
        printf("IDR=0x%04lX -> %s -> LED %s\n",
               (unsigned long)samples[i],
               pressed ? "PRESSED" : "released",
               pressed ? "ON" : "OFF");
    }
    return 0;
}

Assignment

Problem 1 is pure logic (PC gcc), Problem 2 is MDK (STM32F103C8, μVision simulator).


Problem 1 — LED Toggle on Button Press (Edge)

  • File name: homework_01/main.c
  • Key Concept: Rising edge detection, state retention
  • Platform: Common (PC gcc)

Requirements

  • Process 8 sequential press samples {0,1,1,0,1,0,0,1} (1=pressed).
  • Toggle the LED only at the moment of new press (rising edge: previous 0 → current 1). LED starts OFF.
  • Output the current press state and LED state for each sample. The format must match the expected output exactly, character by character.

Expected output

sample 0: pressed=0 LED=OFF
sample 1: pressed=1 LED=ON
sample 2: pressed=1 LED=ON
sample 3: pressed=0 LED=ON
sample 4: pressed=1 LED=OFF
sample 5: pressed=0 LED=OFF
sample 6: pressed=0 LED=OFF
sample 7: pressed=1 LED=ON

Hint

  • Remember the previous state prev, and only when pressed && !prev, do led = !led.
  • Do not toggle while the button is held down (continuous 1) — only toggle on the edge.

Problem 2 — Pulldown Input (Active High) Button

  • File name: homework_02/main.c
  • Key Concept: Pulldown selection (ODR=0), active high detection
  • Platform: MDK (STM32F103C8)

Requirements

  • Set PA1 as input with internal pulldown (button between PA1 and VCC, pressed=1).
  • Turn on PC13 LED (active low) when the button is pressed (IDR bit=1).

Expected Operation (Simulator)

  • PA1 position of GPIOA->CRL (bit[7:4]) becomes 0x8, and ODR bit1=0 (pulldown).
  • In the command window, if PORTA |= 0x0002 (PA1=1, pressed) then PC13=0 (LED ON), if PORTA &= ~0x0002 then PC13=1 (OFF).

Hint

  • GPIOA->ODR &= ~(1u << 1); → pulldown.
  • Judgment: if-GPIOA--IDR-1u-1/Pressed/part.

Check your answers

After solving it yourself, compare it with the reference solution below. The solution files include comments on key points and common mistakes.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

homework_01/main.cC
#include <stdio.h>

/*
 *Answer 1 — Toggle LED on button press (rising edge)
 *
 *Key Points:
 *- Only toggle the LED on the 'rising edge,' when it changes from released (0) → pressed (1).
 *- The key is to catch the 'moment of new press,' not just 'being pressed.'
 *(Seed of debouncing and FSM in episode 21).
 *
 *Common mistake:
 *- If toggling every sample while pressed makes the LED flicker → toggle only on the edge.
 *- If updating prev is missed, the edge cannot be caught.
 */

int main(void)
{
    int samples[8] = { 0, 1, 1, 0, 1, 0, 0, 1 };
    int prev = 0;       /*Previous button state*/
    int led  = 0;       /* 0=OFF, 1=ON */
    int i;

    for (i = 0; i < 8; i++) {
        int pressed = samples[i];
        if (pressed && !prev) {     /*Rising edge*/
            led = !led;
        }
        prev = pressed;
        printf("sample %d: pressed=%d LED=%s\n",
               i, pressed, led ? "ON" : "OFF");
    }
    return 0;
}
homework_02/main.cC
#include "stm32f10x.h"

/*
 *Answer 2 (MDK, STM32F103C8) — Pulldown input (active high) button
 *
 *Key Points:
 *- Input + pull-up/pull-down = CNF 10, MODE 00 → 0x8. Pulldown is selected with ODR bit = 0.
 *- If you connect the button between PA1 and VCC, the IDR bit = 1 when pressed (active high).
 *
 *Common mistake:
 *- A pulldown becomes a pull-up if ODR is set to 1.
 *- Confusing it with active low (=0 pressed) can lead to opposite judgment.
 *
 *Expected behavior (simulator): GPIOA->CRL at PA1 = 0x8, ODR bit 1 = 0.
 *If PA1 = 1 then PC13 = 0 (LED ON), if PA1 = 0 then PC13 = 1 (OFF).
 */

#define BTN_PIN   1u     /* PA1 */
#define LED_PIN   13u    /* PC13 */

int main(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN;

    GPIOA->CRL &= ~(0xFu << (BTN_PIN * 4u));
    GPIOA->CRL |=  (0x8u << (BTN_PIN * 4u));   /*Input + pull-up/pull-down*/
    GPIOA->ODR &= ~(1u << BTN_PIN);            /*ODR = 0 → pulldown*/

    GPIOC->CRH &= ~(0xFu << ((LED_PIN - 8u) * 4u));
    GPIOC->CRH |=  (0x2u << ((LED_PIN - 8u) * 4u));

    while (1) {
        if (GPIOA->IDR & (1u << BTN_PIN)) {    /*Pulldown: 1 when pressed*/
            GPIOC->BRR  = (1u << LED_PIN);     /*LED ON (active low)*/
        } else {
            GPIOC->BSRR = (1u << LED_PIN);     /* LED OFF */
        }
    }
}

Learn this lesson with a mentor

Work through the Embedded C track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now