Repository navigation
Chapter 07
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
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]
NUCLEO-F439ZI:
LD1 Green LED
→ PB0
B1 User Button
→ PC13
이번 board에서 B1은:
Released → 0
Pressed → 1
즉 active-HIGH로 사용한다.
GPIOC->IDRInput pin의 실제 logic level을 읽는다.
PC13:
GPIOC->IDR & (1U << 13)GPIOB->ODROutput state를 저장한다.
PB0 toggle:
GPIOB->ODR ^= (1U << 0);특정 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이 유리하다.
이번 장부터 GPIO 코드를 driver 형태로 분리한다.
Inc/
└── gpio.h
Src/
├── gpio.c
└── main.c
#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#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);
}#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();
}
}
}전체 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
다음 코드는:
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만 변경하기에 적합하다.
현재 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 방식으로 변경한다.
Mechanical switch는 실제로 한 번 눌렀을 때 signal이 완벽하게 한 번만 바뀌지 않을 수 있다.
Ideal
LOW ──────────┐
└──── HIGH
실제 switch에서는:
LOW ───┐ ┌─┐ ┌──── HIGH
└─┘ └─┘
처럼 짧게 여러 번 변할 수 있다.
이를:
Switch Bounce
라고 한다.
이번 실습은 단순히:
현재 button level
→ LED level
을 연결하기 때문에 큰 문제가 없지만,
Button Counter
LED Toggle Once
Interrupt
같은 동작에서는 debounce 처리가 필요하다.
원서에서 사용한 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
이다.
make clean
make확인:
Build PASS
Expected:
B1 Released
→ LD1 OFF
B1 Pressed
→ LD1 ON
다음 register를 확인한다.
RCC->AHB1ENR
GPIOB->MODER
GPIOB->ODR
GPIOC->MODER
GPIOC->IDR
Button released:
GPIOC->IDR bit13 = 0
Button pressed:
GPIOC->IDR bit13 = 1
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
전체 흐름을 확인한 것이다.
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]
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로 확장한다.