Bare metal C Ch02 레지스터 직접정의 - Hongjayeong-02/Basic_SoC_implementation GitHub Wiki
CMSIS나 HAL 없이 Reference Manual의 주소 정보만 사용해서 GPIO 레지스터를 직접 정의하고 LED를 제어한다.
핵심은 다음 한 문장이다.
Peripheral register는 CPU 입장에서 특정 memory address에 배치된 32-bit 값이다.
이번 실습에서는 NUCLEO-F439ZI의 LD1(녹색 LED, PB0) 을 대상으로 한다.
항목 | 원서 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를 다시 계산했다.
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
RCC_AHB1ENR
│
└─ GPIOB clock enable
│
▼
GPIOB_MODER
│
└─ PB0 = General Purpose Output
│
▼
GPIOB_ODR
│
└─ PB0 Toggle
│
▼
LD1 LED
RCC_AHB1ENR의 bit1을 1로 설정한다.
RCC_AHB1ENR |= GPIOBEN;
STM32는 사용하지 않는 peripheral의 clock을 차단하는 clock gating을 사용하므로, GPIOB를 설정하기 전에 clock을 먼저 활성화해야 한다.
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));
GPIOB_ODR의 bit0은 PB0 output 값과 대응된다.
GPIOB_ODR ^= LD1_PIN;
XOR 연산으로 bit0을 반복해서 뒤집어 LED를 점멸한다.
실제 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);
}
}
volatile unsigned int
Hardware register나 delay counter처럼 컴파일러가 임의로 접근을 생략하면 안 되는 대상에 사용한다.
(*(volatile unsigned int *)ADDRESS)
해석 순서:
ADDRESS
↓
해당 숫자를 pointer로 변환
↓
volatile unsigned int *
↓
- 연산자로 역참조
↓
해당 주소의 32-bit register에 직접 접근
reg |= (1UL << n);
특정 bit를 1로 만든다.
reg &= ~(1UL << n);
특정 bit를 0으로 만든다.
reg ^= (1UL << n);
특정 bit를 0 ↔ 1로 반전한다.
예를 들어:
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 출력으로 이어진다.
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에서 별도로 분석한다.
코드 작성: ✅
STM32CubeIDE Build: ✅
ELF 생성: ✅
Register 직접 접근 구조 확인: ✅
Hardware 실기 검증 여부는 실제 보드 테스트 기록에 맞춰 별도로 갱신한다.
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다.