Bare metal C 00 필수개념 - Hongjayeong-02/Basic_SoC_implementation GitHub Wiki
목표
이후 모든 Chapter에서 반복해서 등장하는 Bare-Metal C의 공통 개념을 먼저 정리한다.
Memory-Mapped I/O,volatile, pointer, bit manipulation, build flow, startup/vector table을 이해하는 것이 핵심이다.
Bare-Metal programming은 운영체제나 HAL 같은 고수준 추상화 계층에 의존하지 않고, CPU가 MCU의 peripheral register를 직접 제어하는 방식이다.
Application
│
▼
C code
│
▼
Memory-Mapped Register
│
▼
Peripheral HardwareHAL 방식:
HAL_GPIO_WritePin(...);Bare-Metal 방식:
GPIOB->ODR |= (1UL << 0);둘 다 결국 hardware register를 제어하지만, Bare-Metal에서는 register와 bit를 직접 다룬다.
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직접 주소 방식의 대표적인 표현:
#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하는 것이다.
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를 생략하지 마라.
라는 의미를 전달한다.
REG |= (1UL << n);특정 bit를 1로 만든다.
REG &= ~(1UL << n);특정 bit만 0으로 만든다.
REG ^= (1UL << n);해당 bit를 0 ↔ 1로 반전한다.
if ((REG & (1UL << n)) != 0U)
{
...
}특정 bit가 1인지 확인한다.
REG &= ~(0x3UL << shift);
REG |= (value << shift);먼저 field를 clear한 뒤 원하는 값을 넣는다.
PB0을 output으로 설정:
GPIOB->MODER &= ~(0x3UL << (0 * 2));
GPIOB->MODER |= (0x1UL << (0 * 2));MODER[1:0]:
00 = Input
01 = Output
10 = Alternate Function
11 = Analogmain.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-nmFLASH
├─ Vector Table
├─ .text
└─ .data 초기값
RAM
├─ .data
├─ .bss
├─ Heap
└─ Stack
Reset
│
▼
Initial MSP 설정
│
▼
.data : Flash → RAM
│
▼
.bss : 0으로 초기화
│
▼
SystemInit()
│
▼
main()void EXTI15_10_IRQHandler(void)
{
...
}각 exception/interrupt의 handler 주소를 저장한다.
NVIC_EnableIRQ(EXTI15_10_IRQn);으로 특정 IRQ를 활성화할 수 있다.
| 항목 | 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 |
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/writePeripheral 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 구조로 동작한다.