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