Bare metal C Ch02 레지스터직접정의 - Hongjayeong-02/Basic_SoC_implementation GitHub Wiki
Chapter 02 — Memory-Mapped Register Access
1. 목표
CMSIS나 HAL 없이 Reference Manual의 주소 정보만 사용해서 GPIO 레지스터를 직접 정의하고 LED를 제어한다.
핵심은 다음 한 문장이다.
Peripheral register는 CPU 입장에서 특정 memory address에 배치된 32-bit 값이다.
이번 실습에서는 NUCLEO-F439ZI의 LD1(녹색 LED, PB0) 을 대상으로 한다.
2. 원서와 실제 실습 보드 차이
| 항목 | 원서 NUCLEO-F411 | 실습 NUCLEO-F439ZI |
|---|---|---|
| MCU | STM32F411RE | STM32F439ZI |
| LED | LD2 / PA5 | LD1 / PB0 |
| GPIO Port | GPIOA | GPIOB |
| GPIO Clock Enable | RCC_AHB1ENR bit0 | RCC_AHB1ENR bit1 |
| Reference Manual | STM32F411 계열 | RM0090 |
따라서 원서 코드를 그대로 복사하지 않고 PB0 기준으로 주소와 bit를 다시 계산했다.
3. Register 주소 계산
STM32F4의 peripheral 영역은 0x4000_0000에서 시작한다.
PERIPH_BASE
= 0x4000_0000
AHB1PERIPH_BASE
= PERIPH_BASE + 0x0002_0000
= 0x4002_0000
GPIOB와 RCC의 base address:
GPIOB_BASE
= AHB1PERIPH_BASE + 0x0400
= 0x4002_0400
RCC_BASE
= AHB1PERIPH_BASE + 0x3800
= 0x4002_3800
필요한 register:
RCC_AHB1ENR
= RCC_BASE + 0x30
= 0x4002_3830
GPIOB_MODER
= GPIOB_BASE + 0x00
= 0x4002_0400
GPIOB_ODR
= GPIOB_BASE + 0x14
= 0x4002_0414
핵심 공식
Register Address = Peripheral Base Address + Register Offset
4. 구현 순서
RCC_AHB1ENR
│
└─ GPIOB clock enable
│
▼
GPIOB_MODER
│
└─ PB0 = General Purpose Output
│
▼
GPIOB_ODR
│
└─ PB0 Toggle
│
▼
LD1 LED
Step 1 — GPIOB Clock Enable
RCC_AHB1ENR의 bit1을 1로 설정한다.
RCC_AHB1ENR |= GPIOBEN;
STM32는 사용하지 않는 peripheral의 clock을 차단하는 clock gating을 사용하므로, GPIOB를 설정하기 전에 clock을 먼저 활성화해야 한다.
Step 2 — PB0 Output Mode
GPIOx_MODER는 pin 하나당 2bit를 사용한다.
00 : Input
01 : General-purpose output
10 : Alternate function
11 : Analog
PB0은 MODER의 bit [1:0]을 사용한다.
먼저 2bit field를 00으로 clear한 뒤 01을 설정한다.
GPIOB_MODER &= ~(0x3UL << (0 * 2));
GPIOB_MODER |= (0x1UL << (0 * 2));
Step 3 — PB0 Toggle
GPIOB_ODR의 bit0은 PB0 output 값과 대응된다.
GPIOB_ODR ^= LD1_PIN;
XOR 연산으로 bit0을 반복해서 뒤집어 LED를 점멸한다.
5. 전체 코드
실제 Ch02_register/Src/main.c에서 사용한 코드다.
#define PERIPH_BASE (0x40000000UL)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000UL)
#define RCC_BASE (AHB1PERIPH_BASE + 0x3800UL)
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400UL)
#define RCC_AHB1ENR (*(volatile unsigned int *)(RCC_BASE + 0x30UL))
#define GPIOB_MODER (*(volatile unsigned int *)(GPIOB_BASE + 0x00UL))
#define GPIOB_ODR (*(volatile unsigned int *)(GPIOB_BASE + 0x14UL))
#define GPIOBEN (1UL << 1)
#define LD1_PIN (1UL << 0)
static void delay(volatile unsigned int count)
{
while (count--) { __asm("nop"); }
}
int main(void)
{
RCC_AHB1ENR |= GPIOBEN;
GPIOB_MODER &= ~(0x3UL << (0 * 2));
GPIOB_MODER |= (0x1UL << (0 * 2));
while (1)
{
GPIOB_ODR ^= LD1_PIN;
delay(800000);
}
}
6. 코드 문법 핵심
volatile
volatile unsigned int
Hardware register나 delay counter처럼 컴파일러가 임의로 접근을 생략하면 안 되는 대상에 사용한다.
Register 직접 접근
(*(volatile unsigned int *)ADDRESS)
해석 순서:
ADDRESS
↓
해당 숫자를 pointer로 변환
↓
volatile unsigned int *
↓
* 연산자로 역참조
↓
해당 주소의 32-bit register에 직접 접근
Bit Set
reg |= (1UL << n);
특정 bit를 1로 만든다.
Bit Clear
reg &= ~(1UL << n);
특정 bit를 0으로 만든다.
Bit Toggle
reg ^= (1UL << n);
특정 bit를 0 ↔ 1로 반전한다.
7. Hardware 관점에서 코드 읽기
예를 들어:
GPIOB_ODR ^= LD1_PIN;
은 단순 C 변수 연산이 아니다.
Cortex-M4
│
│ Memory-Mapped Write
▼
AHB1 Bus
│
▼
GPIOB Peripheral
│
▼
ODR bit0 Toggle
│
▼
PB0 HIGH / LOW
│
▼
LD1 ON / OFF
즉 C 코드 → bus transaction → peripheral register → 실제 pin 출력으로 이어진다.
8. 프로젝트에서 확인한 파일
Ch02_register/
├── Src/
│ ├── main.c
│ ├── syscalls.c
│ └── sysmem.c
├── Startup/
│ └── startup_stm32f439zitx.s
├── STM32F439ZITX_FLASH.ld
└── STM32F439ZITX_RAM.ld
Chapter 02에서는 핵심 학습 대상이 main.c이며, startup/linker 관련 파일은 이후 Chapter 04에서 별도로 분석한다.
9. 검증 상태
- 코드 작성: ✅
- STM32CubeIDE Build: ✅
- ELF 생성: ✅
- Register 직접 접근 구조 확인: ✅
Hardware 실기 검증 여부는 실제 보드 테스트 기록에 맞춰 별도로 갱신한다.
10. 핵심 정리
- Memory-Mapped I/O에서는 peripheral register가 memory address에 배치된다.
- STM32 peripheral 사용 전에는 RCC를 통해 해당 peripheral clock을 활성화한다.
- Register 주소는
Base Address + Offset으로 계산한다. MODER는 GPIO pin의 mode를 설정한다.ODR은 output pin의 실제 HIGH/LOW 상태를 제어한다.volatile은 hardware register 접근에서 필수적으로 이해해야 하는 C qualifier다.
네비게이션: 이전: Ch01. Toolchain 설정 · 전체 목차 · 다음: Ch03. 빌드 과정과 GNU ARM Toolchain