Chapter 02 - Hongjayeong-02/Basic_SoC_implementation GitHub Wiki
02. Peripheral Registers
Goal
Control a hardware peripheral without HAL or CMSIS register definitions.
In this chapter:
- Read the STM32 memory map
- Calculate peripheral register addresses
- Map a physical address into C
- Understand
volatile - Read and modify register bit fields
- Control the NUCLEO-F439ZI LD1 LED through
GPIOB
Target:
Board : NUCLEO-F439ZI
MCU : STM32F439ZI
LED : LD1
Pin : PB0
1. Memory-Mapped I/O
1.1 What is a Peripheral Register?
GPIO, UART, Timer, ADC, RCC and other peripherals contain internal registers.
From the CPU's point of view, these registers are assigned normal addresses in the memory address space.
This architecture is called:
Memory-Mapped I/O (MMIO)
CPU
โ
โ address + read/write
โผ
Bus
โ
โโโ SRAM
โโโ FLASH
โโโ GPIO
โโโ UART
โโโ TIMER
โโโ ADC
์ฆ CPU๋ GPIO๋ฅผ ์ ์ดํ๊ธฐ ์ํด ํน๋ณํ GPIO instruction์ ์คํํ๋ ๊ฒ์ด ์๋๋ค.
CPU๊ฐ ํ๋ ์ผ์ ๊ฒฐ๊ตญ:
ํน์ ์ฃผ์๋ฅผ ์ฝ๋๋ค
ํน์ ์ฃผ์์ ๊ฐ์ ์ด๋ค
๋ฟ์ด๋ค.
์๋ฅผ ๋ค์ด PB0์ ์ถ๋ ฅ ๊ฐ์ ๊ฒฐ์ ํ๋ GPIOB_ODR์ ์ฃผ์๋:
0x40020414
์ด๋ค.
CPU๊ฐ ์ด ์ฃผ์์ bit 0์ 1๋ก ๋ง๋ค๋ฉด GPIOB hardware๊ฐ PB0 ์ถ๋ ฅ์ High๋ก ๋ณ๊ฒฝํ๋ค.
CPU
โ
โ write bit0 = 1
โผ
0x40020414
โ
โผ
GPIOB_ODR
โ
โผ
PB0 = HIGH
โ
โผ
LD1 ON
2. Where Do the Addresses Come From?
Bare-metal programming์์๋ ์ฃผ์๋ฅผ ์ธ์ฐ๋ ๊ฒ์ด ์๋๋ผ ๋ฌธ์์์ ์ฐพ์ ๊ณ์ฐํด์ผ ํ๋ค.
STM32๋ฅผ ์ฌ์ฉํ ๋ ์ฃผ๋ก ๋ณด๋ ๋ฌธ์๋ ๋ค์๊ณผ ๊ฐ๋ค.
Reference Manual
Peripheral์:
- Base address
- Register offset
- Bit field
- Reset value
- Register operation
์ ํ์ธํ๋ค.
STM32F439ZI์์๋ RM0090 Reference Manual์ด ํต์ฌ์ด๋ค.
Datasheet
์ฃผ๋ก ๋ค์์ ํ์ธํ๋ค.
- Pinout
- Alternate Function
- Electrical characteristics
- Package information
Board User Manual / Schematic
MCU pin์ด ์ค์ board์ ๋ฌด์๊ณผ ์ฐ๊ฒฐ๋์๋์ง ํ์ธํ๋ค.
์:
LD1 โ PB0
B1 โ PC13
์ฆ:
Board Manual
โ
Which pin?
Reference Manual
โ
Which register?
Memory Map
โ
Which address?
Register Description
โ
Which bit?
์์๋ก ์ฝ์ผ๋ฉด ๋๋ค.
3. STM32F4 Peripheral Address Mapping
STM32F4 peripheral ์์ญ์ ๋ค์ base address์์ ์์ํ๋ค.
PERIPH_BASE = 0x40000000
์ด๋ฒ ์ค์ต์์ GPIOB์ RCC๋ AHB1 bus์ ์ฐ๊ฒฐ๋์ด ์๋ค.
Peripheral Base
0x40000000
โ
โ + 0x00020000
โผ
AHB1 Peripheral Base
0x40020000
C์์๋:
#define PERIPH_BASE (0x40000000UL)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000UL)
๋ฐ๋ผ์:
AHB1PERIPH_BASE
= 0x40000000 + 0x00020000
= 0x40020000
4. Peripheral Base Address
4.1 GPIOB
GPIOB offset from AHB1 base:
0x0400
Therefore:
GPIOB_BASE
= AHB1PERIPH_BASE + 0x0400
= 0x40020000
+ 0x00000400
= 0x40020400
C:
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400UL)
4.2 RCC
RCC offset:
0x3800
Therefore:
RCC_BASE
= AHB1PERIPH_BASE + 0x3800
= 0x40020000
+ 0x00003800
= 0x40023800
C:
#define RCC_BASE (AHB1PERIPH_BASE + 0x3800UL)
5. Base Address + Register Offset
Peripheral base address๋ peripheral ์ ์ฒด์ ์์ ์ฃผ์๋ค.
์ค์ register ์ฃผ์๋ฅผ ์ป์ผ๋ ค๋ฉด:
Register Address
=
Peripheral Base Address
+
Register Offset
๋ฅผ ๊ณ์ฐํ๋ค.
์ด๋ฒ ์ค์ต์์๋ ์ธ ๊ฐ์ register๋ฅผ ์ฌ์ฉํ๋ค.
RCC_AHB1ENR
GPIOB_MODER
GPIOB_ODR
6. RCC_AHB1ENR
6.1 Address
RCC base:
0x40023800
AHB1ENR offset:
0x30
๋ฐ๋ผ์:
RCC_AHB1ENR
= RCC_BASE + 0x30
= 0x40023800
+ 0x00000030
= 0x40023830
C:
#define RCC_AHB1ENR \
(*(volatile unsigned int *)(RCC_BASE + 0x30UL))
6.2 GPIOB Clock Enable
RCC_AHB1ENR์์ GPIOB clock์ bit 1์ด๋ค.
bit 1 = GPIOBEN
Mask:
#define GPIOBEN (1UL << 1)
๊ณ์ฐํ๋ฉด:
1 << 1
0000 0001
โ shift
0000 0010
= 0x00000002
Clock enable:
RCC_AHB1ENR |= GPIOBEN;
CPU ๊ด์ ์์๋ ์ฌ์ค์:
READ 0x40023830
โ
OR 0x00000002
โ
WRITE 0x40023830
๋ฅผ ์ํํ๋ ๊ฒ์ด๋ค.
7. Why Enable the Peripheral Clock?
STM32๋ power consumption์ ์ค์ด๊ธฐ ์ํด ์ฌ์ฉํ์ง ์๋ peripheral์ clock์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฐจ๋จํ๋ค.
์ด๋ฅผ Clock Gating์ด๋ผ๊ณ ํ๋ค.
System Clock
โ
โผ
RCC
โ
โโโ GPIOA Clock
โโโ GPIOB Clock
โโโ GPIOC Clock
โโโ ...
Reset ์งํ GPIOB clock์ด disabled ์ํ๋ผ๋ฉด:
CPU
โ
GPIOB register write
โ
GPIOB clock OFF
โ
Peripheral does not operate
๋ฐ๋ผ์ GPIO register๋ฅผ ์ค์ ํ๊ธฐ ์ ์ ๋จผ์ :
RCC_AHB1ENR |= GPIOBEN;
๋ฅผ ์ํํ๋ค.
Peripheral configuration์ ์ผ๋ฐ์ ์ธ ์์๋ ๋น์ทํ๋ค.
Enable Peripheral Clock
โ
Configure Peripheral Registers
โ
Enable Peripheral
โ
Run
8. GPIOB_MODER
8.1 Address
GPIOB base:
0x40020400
MODER offset:
0x00
Therefore:
GPIOB_MODER
= 0x40020400 + 0x00
= 0x40020400
C:
#define GPIOB_MODER \
(*(volatile unsigned int *)(GPIOB_BASE + 0x00UL))
9. GPIO MODER Bit Mapping
Each GPIO pin uses 2 bits in MODER.
Pin MODER Bits
PB0 [1:0]
PB1 [3:2]
PB2 [5:4]
PB3 [7:6]
...
PB15 [31:30]
General formula:
Pin n
โ bits [(2n+1):(2n)]
For PB0:
n = 0
bit position
= 0 ร 2
= 0
PB0 โ MODER[1:0]
Mode encoding:
00 = Input
01 = General Purpose Output
10 = Alternate Function
11 = Analog
We need:
PB0 = Output
MODER[1:0] = 01
10. Setting a Multi-Bit Field
์๋ชป๋ ๋ฐฉ์:
GPIOB_MODER |= 1;
ํ์ฌ ๊ฐ์ด ๋ฌด์์ธ์ง ๋ชจ๋ฅด๋ ์ํ์์๋ ์์ ํ์ง ์๋ค.
์๋ฅผ ๋ค์ด ๊ธฐ์กด ๊ฐ์ด:
11
์ด๋ฉด:
11 OR 01 = 11
์ด๋ฏ๋ก Output mode๊ฐ ๋์ง ์๋๋ค.
๋ฐ๋ผ์ ์ผ๋ฐ์ ์ผ๋ก:
CLEAR
โ
SET
๋ ๋จ๊ณ๋ก ์ฒ๋ฆฌํ๋ค.
Step 1 โ Clear
GPIOB_MODER &= ~(0x3UL << (0 * 2));
0x3:
binary = 11
PB0 ์์น๋ 0์ด๋ฏ๋ก:
0x3 << 0
= 00000011
NOT:
~00000011
=
11111100
ANDํ๋ฉด:
xxxx xxxx
AND
1111 1100
-----------
xxxx xx00
PB0 field๋ง 00์ด ๋๋ค.
Step 2 โ Set
GPIOB_MODER |= (0x1UL << (0 * 2));
01
์ ORํ๋ค.
๊ฒฐ๊ณผ:
MODER[1:0] = 01
๋ฐ๋ผ์:
PB0 = General Purpose Output
11. GPIOB_ODR
11.1 Address
GPIOB base:
0x40020400
ODR offset:
0x14
Therefore:
GPIOB_ODR
= 0x40020400
+ 0x14
= 0x40020414
C:
#define GPIOB_ODR \
(*(volatile unsigned int *)(GPIOB_BASE + 0x14UL))
12. ODR Bit Mapping
ODR uses one bit per output pin.
ODR bit 0 โ PB0
ODR bit 1 โ PB1
ODR bit 2 โ PB2
...
ODR bit 15 โ PB15
For LD1:
LD1 = PB0
Mask:
#define LD1_PIN (1UL << 0)
which becomes:
0x00000001
13. C Pointer Mapping
Now look at this expression carefully.
#define GPIOB_ODR \
(*(volatile unsigned int *)0x40020414UL)
์ด ํ ์ค์ด bare-metal register programming์์ ๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ ์ค ํ๋๋ค.
๋ถํดํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
13.1 Address Constant
0x40020414UL
์ด ๋จ๊ณ์์๋ ๋จ์ํ integer constant์ด๋ค.
Value = 0x40020414
CPU address๋ผ๋ ์๋ฏธ๋ฅผ C compiler๊ฐ ์๋์ผ๋ก ์๋ ๊ฒ์ ์๋๋ค.
13.2 Pointer Cast
(volatile unsigned int *)0x40020414UL
์ ์ 0x40020414๋ฅผ:
pointer to volatile unsigned int
๋ก ๋ณํํ๋ค.
์ฆ:
0x40020414
โ
โผ
"์ด ์ฃผ์์๋ 32-bit register๊ฐ ์๋ค"
๋ผ๊ณ C compiler์๊ฒ ์๋ ค์ฃผ๋ ๊ฒ์ด๋ค.
13.3 Dereference
์์ *๋ฅผ ๋ถ์ธ๋ค.
*(volatile unsigned int *)0x40020414UL
Pointer ์์ฒด๊ฐ ์๋๋ผ ๊ทธ pointer๊ฐ ๊ฐ๋ฆฌํค๋ ์ค์ ๊ฐ์ ์ ๊ทผํ๋ค.
pointer
0x40020414
โ
โผ
+------------------+
| GPIOB_ODR |
| 32-bit register |
+------------------+
๋ฐ๋ผ์:
GPIOB_ODR = 1;
์ ์ฌ์ค์:
*(volatile unsigned int *)0x40020414 = 1;
์ด๋ค.
์ฆ CPU๊ฐ ํด๋น address์ ๊ฐ์ writeํ๋ค.
14. Why volatile?
Hardware register์๋ volatile์ด ๋งค์ฐ ์ค์ํ๋ค.
์ผ๋ฐ ๋ณ์:
unsigned int value;
๋ compiler ์ ์ฅ์์ ํ๋ก๊ทธ๋จ ์ฝ๋๊ฐ ๋ณ๊ฒฝํ์ง ์์ผ๋ฉด ๊ฐ์ด ๋ฐ๋์ง ์๋๋ค๊ณ ๊ฐ์ ํ ์ ์๋ค.
ํ์ง๋ง peripheral register๋ ๋ค๋ฅด๋ค.
CPU๊ฐ ์ง์ ๊ฐ์ ์ฐ์ง ์์๋ hardware๊ฐ ๋ฐ๊ฟ ์ ์๋ค.
Examples:
UART receives byte
โ
RX flag changes
Timer counts
โ
Counter changes
ADC conversion completes
โ
Status flag changes
Button pressed
โ
GPIO input changes
๋ฐ๋ผ์:
volatile unsigned int
๋ compiler์๊ฒ:
์ด ๊ฐ์ ํ๋ก๊ทธ๋จ ์ฝ๋ ์ธ๋ถ์ ์์ธ์ผ๋ก ๋ฐ๋ ์ ์์ผ๋ฏ๋ก ์ ๊ทผํ ๋๋ง๋ค ์ค์ memory๋ฅผ ์ฝ๊ฑฐ๋ ์จ๋ผ.
๋ผ๋ ์๋ฏธ๋ฅผ ์ค๋ค.
15. UL Suffix
์ฃผ์์ bit mask์์:
0x40000000UL
1UL << 1
๊ฐ์ ํํ์ ์ฌ์ฉํ๋ค.
UL means:
Unsigned Long
์ฆ ์์๋ฅผ unsigned integer ๊ณ์ด๋ก ์ทจ๊ธํ๋๋ก ๋ช ์ํ๋ค.
Register address์ bit mask์์๋ sign extension ๋ฑ์ ๋ฌธ์ ๋ฅผ ํผํ๊ณ ์๋๋ฅผ ๋ช ํํ๊ฒ ํ๊ธฐ ์ํด ์์ฃผ ์ฌ์ฉํ๋ค.
16. Bit Manipulation
Register programming์์ ๊ฐ์ฅ ์์ฃผ ์ฌ์ฉํ๋ ๋ค ๊ฐ์ง ํจํด์ด๋ค.
Set
REG |= (1UL << n);
bit n์ 1๋ก ๋ง๋ ๋ค.
Clear
REG &= ~(1UL << n);
bit n์ 0์ผ๋ก ๋ง๋ ๋ค.
Toggle
REG ^= (1UL << n);
bit n์ ๋ฐ์ ํ๋ค.
0 โ 1
1 โ 0
Read
if (REG & (1UL << n))
{
}
bit n์ด 1์ธ์ง ํ์ธํ๋ค.
17. Read-Modify-Write
Expression:
REG |= MASK;
์ ํ ๋ฒ์ ๋จ์ write์ฒ๋ผ ๋ณด์ด์ง๋ง ๊ฐ๋ ์ ์ผ๋ก๋:
1. READ register
2. MODIFY value
3. WRITE register
์ด๋ค.
Example:
RCC_AHB1ENR |= GPIOBEN;
Conceptually:
tmp = RCC_AHB1ENR;
tmp = tmp | GPIOBEN;
RCC_AHB1ENR = tmp;
์ด ๋ฐฉ์์ Read-Modify-Write๋ผ๊ณ ํ๋ค.
๋ค๋ฅธ bit ๊ฐ์ ์ ์งํ๋ฉด์ ํน์ bit๋ง ๋ณ๊ฒฝํ ์ ์๋ค.
18. Complete Address Map
์ด๋ฒ ์ค์ต์ ์ฌ์ฉ๋ ์ฃผ์๋ฅผ ํ๋์ tree๋ก ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
PERIPH_BASE
0x40000000
โ
โโโ AHB1PERIPH_BASE
0x40020000
โ
โโโ GPIOB_BASE
โ 0x40020400
โ โ
โ โโโ MODER + 0x00
โ โ = 0x40020400
โ โ
โ โโโ ODR + 0x14
โ = 0x40020414
โ
โโโ RCC_BASE
0x40023800
โ
โโโ AHB1ENR + 0x30
= 0x40023830
์ ๋ฆฌํ๋ฉด:
RCC_AHB1ENR = 0x40023830
GPIOB_MODER = 0x40020400
GPIOB_ODR = 0x40020414
19. Full Practice Code
The following code directly controls LD1 on PB0 without HAL register APIs.
#define PERIPH_BASE (0x40000000UL)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000UL)
#define RCC_BASE (AHB1PERIPH_BASE + 0x3800UL)
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400UL)
/* Register mapping */
#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))
/* Bit definitions */
#define GPIOBEN (1UL << 1)
#define LD1_PIN (1UL << 0)
static void delay(volatile unsigned int count)
{
while (count--)
{
__asm__("nop");
}
}
int main(void)
{
/* Enable GPIOB peripheral clock */
RCC_AHB1ENR |= GPIOBEN;
/* PB0 -> General Purpose Output mode */
GPIOB_MODER &= ~(0x3UL << (0 * 2));
GPIOB_MODER |= (0x1UL << (0 * 2));
while (1)
{
/* Toggle PB0 */
GPIOB_ODR ^= LD1_PIN;
delay(800000);
}
}
20. Code Execution Flow
The program executes in this order.
main()
โ
โผ
Enable GPIOB Clock
RCC_AHB1ENR bit1 = 1
โ
โผ
Configure PB0
GPIOB_MODER[1:0] = 01
โ
โผ
while(1)
โ
โโโ Toggle GPIOB_ODR[0]
โ
โโโ Delay
โ
โโโ Repeat
21. What Happens at Hardware Level?
Step 1
RCC_AHB1ENR |= GPIOBEN;
Hardware:
CPU
โ
AHB1
โ
RCC_AHB1ENR
โ
GPIOB clock enabled
Step 2
GPIOB_MODER &= ~(0x3UL << 0);
GPIOB_MODER |= (0x1UL << 0);
Hardware:
GPIOB_MODER[1:0]
โ
01
โ
PB0 configured as output
Step 3
GPIOB_ODR ^= LD1_PIN;
First execution:
ODR bit0
0 XOR 1
โ
1
PB0 becomes High.
Next execution:
1 XOR 1
โ
0
PB0 becomes Low.
Therefore:
HIGH โ LOW โ HIGH โ LOW ...
and the LED blinks.
22. Why Use a Delay?
Without delay:
while (1)
{
GPIOB_ODR ^= LD1_PIN;
}
the CPU toggles the pin extremely quickly.
The human eye cannot observe this directly.
Therefore the practice uses:
delay(800000);
to slow down the toggle rate.
static void delay(volatile unsigned int count)
{
while (count--)
{
__asm__("nop");
}
}
nop means:
No Operation
The CPU executes an instruction without performing useful work.
23. Why Is count Volatile?
volatile unsigned int count
is used because an optimizing compiler may determine that an empty delay loop has no externally visible effect.
volatile prevents aggressive removal of accesses to count.
However, this method is only useful for simple experiments.
It is not an accurate timer.
The actual delay depends on:
- CPU clock
- Compiler optimization
- Instruction timing
- Pipeline behavior
Precise timing should use:
SysTick
or
Hardware Timer
which will be covered in later chapters.
24. ODR vs BSRR
This practice intentionally uses:
GPIOB_ODR ^= LD1_PIN;
because it clearly demonstrates register read-modify-write.
For production GPIO set/reset operations, STM32 also provides BSRR.
Why?
ODR |= or ODR &= performs:
READ โ MODIFY โ WRITE
If another execution context changes the same register between the read and write, a race can occur.
BSRR provides atomic bit set/reset operation.
Concept:
ODR
READ โ MODIFY โ WRITE
BSRR
single WRITE
For this single-threaded LED experiment, ODR ^= ... is sufficient.
25. Register-Level View
The entire example can be reduced to three registers.
RCC_AHB1ENR
โ
โโโ Enable GPIOB clock
GPIOB_MODER
โ
โโโ Configure PB0 as output
GPIOB_ODR
โ
โโโ Drive PB0 High / Low
This pattern appears repeatedly in embedded programming:
Clock
โ
Configuration
โ
Data / Control
UART, ADC, Timer and I2C also follow similar initialization patterns.
26. Important Difference: MCU vs Board
The MCU is:
STM32F439ZI
The board is:
NUCLEO-F439ZI
The MCU reference manual tells us:
What is GPIOB?
Where is GPIOB?
What does MODER mean?
The board schematic/user manual tells us:
Which MCU pin is connected to LD1?
For this board:
LD1 โ PB0
Therefore both documents are required.
MCU Reference Manual
+
Board Schematic
โ
Correct Bare-Metal Code
27. Build Result
The original Ch02 practice was successfully built with the GNU ARM toolchain.
Recorded result:
.text : 252 bytes
.data : 0 bytes
.bss : 0 bytes
At this stage, temporary startup and linker files were reused.
The linker script and startup sequence are covered separately in Chapter 04.
Verification status: build verified. The original Ch02 note did not record an actual board-flash verification for this specific experiment.
28. Key Questions
Q1. What is a register in C?
A register is not a special C construct.
It can be represented as:
*(volatile unsigned int *)ADDRESS
์ฆ ํน์ hardware address๋ฅผ ๊ฐ๋ฆฌํค๋ pointer๋ฅผ ๋ง๋ค๊ณ ์ด๋ฅผ ์ญ์ฐธ์กฐํ์ฌ ์ ๊ทผํ๋ค.
Q2. Why is GPIOB clock enabled first?
Because STM32 uses clock gating.
RCC_AHB1ENR |= GPIOBEN;
Without a peripheral clock, GPIOB cannot operate normally.
Q3. Why does one GPIO pin use two MODER bits?
Four modes are required.
00 Input
01 Output
10 Alternate Function
11 Analog
Four states require:
2 bits
Q4. Why is PB0 controlled by ODR bit 0?
GPIO output data bits map directly to pin numbers.
PB0 โ ODR bit0
PB1 โ ODR bit1
...
PB15 โ ODR bit15
Q5. What does 1UL << 1 mean?
1 = 0001
shift left 1
0010
Therefore it creates a mask for bit 1.
Q6. What is Read-Modify-Write?
Read existing register
โ
Modify selected bits
โ
Write result back
Example:
RCC_AHB1ENR |= GPIOBEN;
29. Key Takeaways
Peripheral
โ
Base Address
โ
Register Offset
โ
Physical Address
โ
Pointer Cast
โ
Dereference
โ
Bit Mask
โ
Hardware Control
Bare-metal register programming can be summarized as:
Address + Offset + Bit Field
The important point is not memorizing hexadecimal addresses.
The important skill is being able to:
- Find the peripheral in the memory map
- Find its base address
- Find the register offset
- Find the target bit field
- Convert the address into a C register expression
- Modify only the required bits
- Predict the resulting hardware behavior
That is the basic workflow used throughout the remaining chapters.