Skip to content

Chapter 07

Hongjayeong-02 edited this page Oct 5, 2026 · 3 revisions

07. GPIO

Goal

NUCLEO-F439ZI의 LD1(PB0) 과 B1(PC13) 을 직접 제어한다.

이번 장에서 확인할 핵심:

PB0  → Output → LED
PC13 → Input  → Push Button

사용 register:

RCC->AHB1ENR
GPIOB->MODER
GPIOB->ODR
GPIOB->BSRR
GPIOC->MODER
GPIOC->PUPDR
GPIOC->IDR

1. GPIO 핵심

GPIO pin은 MODER에서 동작 모드를 결정한다.

00 = Input
01 = Output
10 = Alternate Function
11 = Analog

Pin 하나당 2bit를 사용한다.

Pin n
→ MODER[(2n+1):(2n)]

따라서:

PB0  → MODER[1:0]
PC13 → MODER[27:26]

2. Board Mapping

NUCLEO-F439ZI:

LD1 Green LED
→ PB0

B1 User Button
→ PC13

이번 board에서 B1은:

Released → 0
Pressed  → 1

즉 active-HIGH로 사용한다.


3. GPIO Data Registers

IDR

GPIOC->IDR

Input pin의 실제 logic level을 읽는다.

PC13:

GPIOC->IDR & (1U << 13)

ODR

GPIOB->ODR

Output state를 저장한다.

PB0 toggle:

GPIOB->ODR ^= (1U << 0);

BSRR

특정 output pin을 한 번의 write로 set/reset할 수 있다.

BSRR[15:0]
→ SET

BSRR[31:16]
→ RESET

PB0:

GPIOB->BSRR = (1U << 0);   /* SET   */
GPIOB->BSRR = (1U << 16);  /* RESET */

ODR |= ...처럼 Read-Modify-Write를 하지 않아도 되므로 단순 set/reset에는 BSRR이 유리하다.


4. Practice Code

이번 장부터 GPIO 코드를 driver 형태로 분리한다.

Inc/
└── gpio.h

Src/
├── gpio.c
└── main.c

gpio.h

#ifndef GPIO_H__
#define GPIO_H__

#include <stdbool.h>
#include "stm32f4xx.h"

/*
 * LED control functions
 *
 * LD1 on NUCLEO-F439ZI is connected to PB0.
 */
void led_init(void);
void led_on(void);
void led_off(void);
void led_toggle(void);

/*
 * Push button functions
 *
 * B1 on NUCLEO-F439ZI is connected to PC13.
 * In this project:
 *
 * Released = LOW
 * Pressed  = HIGH
 */
void button_init(void);
bool get_btn_state(void);

#endif

5. gpio.c

#include "gpio.h"

/*
 * ============================================================
 * Pin Definitions
 * ============================================================
 */

/*
 * LD1 is connected to PB0.
 *
 * BSRR lower 16 bits are used to SET GPIO pins.
 *
 * bit0 = PB0 set
 */
#define LED_SET      (1U << 0)

/*
 * BSRR upper 16 bits are used to RESET GPIO pins.
 *
 * PB0 reset:
 *
 * pin number 0 + 16 = bit16
 */
#define LED_RESET    (1U << 16)

/*
 * ODR bit0 corresponds to PB0.
 *
 * Used when toggling the LED.
 */
#define LED_PIN      (1U << 0)

/*
 * B1 user button is connected to PC13.
 *
 * IDR bit13 represents PC13 input state.
 */
#define BTN_PIN      (1U << 13)


/*
 * ============================================================
 * LED Initialization
 * ============================================================
 */

void led_init(void)
{
    /*
     * Step 1
     * Enable GPIOB peripheral clock.
     *
     * GPIO peripheral registers cannot be used properly
     * before its clock is enabled.
     *
     * RCC_AHB1ENR_GPIOBEN = GPIOB clock enable bit.
     */
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;

    /*
     * Step 2
     * Configure PB0 as General Purpose Output.
     *
     * GPIO MODER uses 2 bits for each pin.
     *
     * PB0:
     * MODER[1:0]
     *
     * 00 = Input
     * 01 = Output
     * 10 = Alternate Function
     * 11 = Analog
     */

    /*
     * Clear PB0 mode field first.
     *
     * 0x3 = binary 11
     *
     * ~(0x3 << 0)
     * clears MODER[1:0].
     */
    GPIOB->MODER &= ~(0x3UL << (0 * 2));

    /*
     * Set MODER[1:0] = 01.
     *
     * PB0 becomes General Purpose Output.
     */
    GPIOB->MODER |= (0x1UL << (0 * 2));
}


/*
 * ============================================================
 * LED ON
 * ============================================================
 */

void led_on(void)
{
    /*
     * Set PB0 using BSRR.
     *
     * BSRR lower 16 bits:
     * writing 1 sets the corresponding GPIO output.
     *
     * PB0 → bit0
     *
     * GPIOB->BSRR = 0x00000001
     *
     * Result:
     * PB0 = HIGH
     * LD1 = ON
     */
    GPIOB->BSRR = LED_SET;
}


/*
 * ============================================================
 * LED OFF
 * ============================================================
 */

void led_off(void)
{
    /*
     * Reset PB0 using BSRR.
     *
     * BSRR upper 16 bits:
     * writing 1 resets the corresponding GPIO output.
     *
     * PB0 reset → bit16
     *
     * GPIOB->BSRR = 0x00010000
     *
     * Result:
     * PB0 = LOW
     * LD1 = OFF
     */
    GPIOB->BSRR = LED_RESET;
}


/*
 * ============================================================
 * LED Toggle
 * ============================================================
 */

void led_toggle(void)
{
    /*
     * XOR the current ODR bit0.
     *
     * 0 XOR 1 = 1
     * 1 XOR 1 = 0
     *
     * Therefore:
     *
     * LOW  → HIGH
     * HIGH → LOW
     *
     * Note:
     * ODR toggle requires Read-Modify-Write.
     * BSRR is preferred when only explicit SET/RESET is needed.
     */
    GPIOB->ODR ^= LED_PIN;
}


/*
 * ============================================================
 * Button Initialization
 * ============================================================
 */

void button_init(void)
{
    /*
     * Step 1
     * Enable GPIOC peripheral clock.
     *
     * B1 is connected to PC13.
     */
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;

    /*
     * Step 2
     * Configure PC13 as input.
     *
     * Pin 13 uses:
     *
     * MODER[27:26]
     *
     * Input mode = 00
     *
     * Clear both bits.
     */
    GPIOC->MODER &= ~(0x3UL << (13 * 2));

    /*
     * Step 3
     * Disable internal pull-up / pull-down.
     *
     * PUPDR also uses 2 bits per pin.
     *
     * PC13:
     * PUPDR[27:26]
     *
     * 00 = No Pull
     */
    GPIOC->PUPDR &= ~(0x3UL << (13 * 2));
}


/*
 * ============================================================
 * Read Button State
 * ============================================================
 */

bool get_btn_state(void)
{
    /*
     * Read PC13 from GPIOC Input Data Register.
     *
     * BTN_PIN = (1U << 13)
     *
     * If bit13 is HIGH:
     *     GPIOC->IDR & BTN_PIN != 0
     *
     *     return true
     *
     * If bit13 is LOW:
     *     GPIOC->IDR & BTN_PIN == 0
     *
     *     return false
     *
     * NUCLEO-F439ZI B1:
     *
     * Released → LOW
     * Pressed  → HIGH
     */
    return ((GPIOC->IDR & BTN_PIN) != 0U);
}

6. main.c

#include <stdbool.h>
#include "gpio.h"

int main(void)
{
    /*
     * Variable used to store the current
     * PC13 button input state.
     */
    bool btn_state;

    /*
     * Initialize PB0 as output.
     *
     * PB0 → LD1
     */
    led_init();

    /*
     * Initialize PC13 as input.
     *
     * PC13 → B1 user button
     */
    button_init();

    while (1)
    {
        /*
         * Read current PC13 input level.
         *
         * false → button released
         * true  → button pressed
         */
        btn_state = get_btn_state();

        /*
         * B1 on NUCLEO-F439ZI is active-HIGH.
         *
         * Pressed:
         * PC13 = HIGH
         * → turn LED on
         *
         * Released:
         * PC13 = LOW
         * → turn LED off
         */
        if (btn_state)
        {
            led_on();
        }
        else
        {
            led_off();
        }
    }
}

7. Code Flow

전체 software flow:

main()
 │
 ├─ led_init()
 │    │
 │    ├─ GPIOB Clock ON
 │    └─ PB0 Output
 │
 ├─ button_init()
 │    │
 │    ├─ GPIOC Clock ON
 │    ├─ PC13 Input
 │    └─ No Pull
 │
 └─ while(1)
      │
      ├─ Read GPIOC->IDR[13]
      │
      ├─ Pressed?
      │
      ├─ YES → BSRR Set PB0
      │
      └─ NO  → BSRR Reset PB0

Hardware flow:

B1
 ↓
PC13
 ↓
GPIOC IDR[13]
 ↓
CPU
 ↓
if()
 ↓
GPIOB BSRR
 ↓
PB0
 ↓
LD1

8. Why Use BSRR?

다음 코드는:

GPIOB->ODR |= (1U << 0);

CPU 입장에서는 보통:

Read ODR
   ↓
Modify bit0
   ↓
Write ODR

가 된다.

이것을:

Read-Modify-Write

라고 한다.

반면:

GPIOB->BSRR = (1U << 0);

은 원하는 bit를 직접 set하는 write command다.

Write BSRR
   ↓
PB0 SET

따라서 다른 GPIO output bit에 영향을 주지 않고 특정 pin만 변경하기에 적합하다.


9. Polling

현재 main loop:

while (1)
{
    btn_state = get_btn_state();
}

는 CPU가 계속 button을 확인한다.

Read
 ↓
Read
 ↓
Read
 ↓
Read
 ↓
...

이를 Polling이라고 한다.

장점:

Simple
Easy to debug

단점:

CPU continuously checks the peripheral

Ch14에서는 같은 button을 EXTI Interrupt 방식으로 변경한다.


10. Button Bounce

Mechanical switch는 실제로 한 번 눌렀을 때 signal이 완벽하게 한 번만 바뀌지 않을 수 있다.

Ideal

LOW ──────────┐
              └──── HIGH

실제 switch에서는:

LOW ───┐ ┌─┐ ┌──── HIGH
       └─┘ └─┘

처럼 짧게 여러 번 변할 수 있다.

이를:

Switch Bounce

라고 한다.

이번 실습은 단순히:

현재 button level
→ LED level

을 연결하기 때문에 큰 문제가 없지만,

Button Counter
LED Toggle Once
Interrupt

같은 동작에서는 debounce 처리가 필요하다.


11. Board Porting

원서에서 사용한 board와 현재 board의 LED pin이 다르다.

NUCLEO-F411
LD2 → PA5

NUCLEO-F439ZI
LD1 → PB0

따라서 변경:

GPIOA
→ GPIOB

bit5
→ bit0

Button은 PC13을 사용하지만 polarity를 board 회로에 맞게 확인해야 한다.

이번 project에서는:

B1 Press
→ PC13 HIGH

이다.


12. Verification

Build

make clean
make

확인:

Build PASS

Board

Expected:

B1 Released
→ LD1 OFF

B1 Pressed
→ LD1 ON

Debugger

다음 register를 확인한다.

RCC->AHB1ENR
GPIOB->MODER
GPIOB->ODR
GPIOC->MODER
GPIOC->IDR

Button released:

GPIOC->IDR bit13 = 0

Button pressed:

GPIOC->IDR bit13 = 1

13. Recommended Debug Test

Breakpoint를:

btn_state = get_btn_state();

다음 줄에 걸고 B1을 눌러본다.

Watch:

btn_state
GPIOC->IDR
GPIOB->ODR

확인:

Released

btn_state = false
IDR[13]  = 0
ODR[0]   = 0
Pressed

btn_state = true
IDR[13]  = 1
ODR[0]   = 1

이렇게 나오면:

Physical Button
→ Input Register
→ Software
→ Output Register
→ Physical LED

전체 흐름을 확인한 것이다.


14. Screenshots to Add

images/ch07_build.png
images/ch07_button_released.png
images/ch07_button_pressed.png
images/ch07_idr_debug.png

가장 좋은 구성:

Button Released / LED OFF
+
Button Pressed / LED ON
+
Debugger IDR[13]

15. Key Points

PC13
 ↓
IDR
 ↓
Software
 ↓
BSRR
 ↓
PB0
 ↓
LED
  • GPIO를 사용하기 전에 RCC clock을 enable한다.
  • MODER는 GPIO pin 하나당 2bit를 사용한다.
  • PB0의 mode field는 MODER[1:0]이다.
  • PC13의 mode field는 MODER[27:26]이다.
  • Input 상태는 IDR에서 읽는다.
  • Output state는 ODR로 확인할 수 있다.
  • 특정 output의 set/reset에는 BSRR을 사용할 수 있다.
  • NUCLEO-F439ZI의 B1은 이번 실습에서 active-HIGH로 사용한다.
  • 현재 방식은 polling이며 이후 EXTI interrupt로 확장한다.

Clone this wiki locally