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:

  1. Find the peripheral in the memory map
  2. Find its base address
  3. Find the register offset
  4. Find the target bit field
  5. Convert the address into a C register expression
  6. Modify only the required bits
  7. Predict the resulting hardware behavior

That is the basic workflow used throughout the remaining chapters.