Bare metal C Ch00 필수개념 - Hongjayeong-02/Basic_SoC_implementation GitHub Wiki
Ch00. Bare-Metal C 필수 개념
목표
이후 모든 Chapter에서 반복해서 등장하는 Bare-Metal C의 공통 개념을 먼저 정리한다.
Memory-Mapped I/O,volatile, pointer, bit manipulation, build flow, startup/vector table을 이해하는 것이 핵심이다.
1. Bare-Metal이란?
Bare-Metal programming은 운영체제나 HAL 같은 고수준 추상화 계층에 의존하지 않고, CPU가 MCU의 peripheral register를 직접 제어하는 방식이다.
Application
│
▼
C code
│
▼
Memory-Mapped Register
│
▼
Peripheral Hardware
HAL 방식:
HAL_GPIO_WritePin(...);
Bare-Metal 방식:
GPIOB->ODR |= (1UL << 0);
둘 다 결국 hardware register를 제어하지만, Bare-Metal에서는 register와 bit를 직접 다룬다.
2. Memory-Mapped I/O
MCU 내부의 GPIO, UART, Timer, ADC 같은 peripheral은 CPU 관점에서 memory address를 가진 register 집합으로 보인다.
예를 들어 NUCLEO-F439ZI의 GPIOB:
GPIOB Base Address : 0x4002_0400
ODR Offset : 0x14
GPIOB_ODR
= 0x4002_0400 + 0x14
= 0x4002_0414
CPU가 이 주소를 읽거나 쓰는 것이 곧 GPIO hardware를 제어하는 것이다.
CPU
│
│ Load / Store
▼
AHB/APB Bus
│
▼
Address Decoder
│
▼
Peripheral Register
│
▼
Hardware
3. C에서 Register Address를 표현하는 방법
직접 주소 방식의 대표적인 표현:
#define GPIOB_ODR (*(volatile unsigned int *)0x40020414UL)
이를 순서대로 해석하면:
0x40020414UL
│
└─ 주소값
(volatile unsigned int *)0x40020414UL
│
└─ 이 숫자를 32-bit register pointer로 취급
*(...)
│
└─ pointer가 가리키는 실제 memory location 접근
따라서:
GPIOB_ODR |= (1UL << 0);
은 실제로 0x40020414 주소의 bit0을 set하는 것이다.
4. volatile
Hardware register는 CPU code 외부의 hardware에 의해 값이 바뀔 수 있다.
예:
- GPIO input
- UART RX data
- ADC conversion complete flag
- Timer counter
- Interrupt status flag
따라서 compiler가 값을 register/cache에 보관하고 memory read를 생략하면 안 된다.
volatile unsigned int *reg;
volatile은 compiler에게:
이 값은 예측할 수 없는 시점에 바뀔 수 있으므로 실제 memory access를 생략하지 마라.
라는 의미를 전달한다.
5. Bit Manipulation 기본 패턴
Bit Set
REG |= (1UL << n);
특정 bit를 1로 만든다.
Bit Clear
REG &= ~(1UL << n);
특정 bit만 0으로 만든다.
Bit Toggle
REG ^= (1UL << n);
해당 bit를 0 ↔ 1로 반전한다.
Bit Test
if ((REG & (1UL << n)) != 0U)
{
...
}
특정 bit가 1인지 확인한다.
여러 bit field 설정
REG &= ~(0x3UL << shift);
REG |= (value << shift);
먼저 field를 clear한 뒤 원하는 값을 넣는다.
6. GPIO 예시
PB0을 output으로 설정:
GPIOB->MODER &= ~(0x3UL << (0 * 2));
GPIOB->MODER |= (0x1UL << (0 * 2));
MODER[1:0]:
00 = Input
01 = Output
10 = Alternate Function
11 = Analog
7. Build Flow
main.c
│
▼
Preprocessing
│
▼
main.i
│
▼
Compilation
│
▼
main.s
│
▼
Assembly
│
▼
main.o
│
▼
Linking
│
▼
firmware.elf
│
▼
objcopy
│
▼
firmware.hex / firmware.bin
대표 도구:
arm-none-eabi-gcc
arm-none-eabi-objdump
arm-none-eabi-objcopy
arm-none-eabi-size
arm-none-eabi-nm
8. Linker Script와 Startup File
Linker Script
FLASH
├─ Vector Table
├─ .text
└─ .data 초기값
RAM
├─ .data
├─ .bss
├─ Heap
└─ Stack
Startup File
Reset
│
▼
Initial MSP 설정
│
▼
.data : Flash → RAM
│
▼
.bss : 0으로 초기화
│
▼
SystemInit()
│
▼
main()
9. Interrupt 기본 개념
ISR
void EXTI15_10_IRQHandler(void)
{
...
}
Vector Table
각 exception/interrupt의 handler 주소를 저장한다.
NVIC
NVIC_EnableIRQ(EXTI15_10_IRQn);
으로 특정 IRQ를 활성화할 수 있다.
10. 이 프로젝트의 보드 기준
| 항목 | NUCLEO-F439ZI |
|---|---|
| MCU | STM32F439ZIT6 |
| CPU Core | ARM Cortex-M4F |
| User LED | LD1 = PB0 |
| User Button | B1 = PC13 |
| VCP UART | USART3 |
| UART TX/RX | PD8 / PD9 |
| Flash | 2 MB |
| SRAM | 128 KB + CCM 64 KB |
11. 이후 Chapter에서 반복되는 공통 패턴
1. RCC에서 peripheral clock enable
2. GPIO mode / Alternate Function 설정
3. Peripheral configuration register 설정
4. Peripheral enable
5. Status flag polling 또는 interrupt
6. Data register read/write
12. 핵심 정리
- Peripheral register는 memory address에 존재한다.
- Bare-Metal C는 그 주소를 C pointer/구조체로 직접 접근한다.
volatile은 hardware register 접근에서 중요하다.- Bit set/clear/toggle/test 패턴을 반복적으로 사용한다.
- Firmware build는 preprocessing → compilation → assembly → linking 순서로 진행된다.
main()전에 startup code가 실행된다.- Interrupt는 Vector Table + NVIC + ISR 구조로 동작한다.
네비게이션: 전체 목차 · 다음: Ch01. Toolchain 설정