Skip to content

Chapter 06

Hongjayeong-02 edited this page Oct 4, 2026 · 3 revisions

06. CMSIS

Goal

Ch02에서 직접 주소를 계산해 register를 제어했던 방식을 CMSIS 구조체 기반 register access로 바꾼다.

핵심은:

CMSIS가 hardware address를 없애는 것이 아니라,
같은 주소와 같은 register를 더 읽기 쉬운 이름과 구조체로 표현한다는 점이다.


1. What is CMSIS?

CMSIS는:

Cortex Microcontroller Software Interface Standard

의 약자다.

ARM Cortex-M 기반 MCU에서 다음을 표준적으로 다룰 수 있게 해준다.

CPU Core Registers
NVIC
SysTick
Device Registers
Interrupt Definitions
Core Instructions

STM32에서는 ARM의 CMSIS Core와 ST의 device header가 함께 사용된다.


2. Before CMSIS

Ch02에서는 직접 register address를 정의했다.

Example:

#define GPIOB_MODER \
    (*(volatile unsigned int *)0x40020400UL)

그리고:

GPIOB_MODER &= ~(0x3UL << 0);
GPIOB_MODER |=  (0x1UL << 0);

처럼 사용했다.

이 방식은 hardware를 이해하기에는 좋지만 register가 많아지면 코드가 복잡해진다.

예:

0x40020400
0x40020404
0x40020410
0x40020414
...

각 주소가 어떤 register인지 직접 기억하거나 macro를 만들어야 한다.


3. CMSIS Style

CMSIS에서는 같은 register를:

GPIOB->MODER

처럼 표현한다.

Example:

GPIOB->MODER &= ~(0x3UL << 0);
GPIOB->MODER |=  (0x1UL << 0);

겉보기에는 훨씬 다르지만 실제 hardware access는 동일하다.


4. How Does GPIOB->MODER Work?

CMSIS device header 내부에는 peripheral register layout을 나타내는 구조체가 있다.

Concept:

typedef struct
{
    __IO uint32_t MODER;    /* Offset 0x00 */
    __IO uint32_t OTYPER;   /* Offset 0x04 */
    __IO uint32_t OSPEEDR;  /* Offset 0x08 */
    __IO uint32_t PUPDR;    /* Offset 0x0C */
    __IO uint32_t IDR;      /* Offset 0x10 */
    __IO uint32_t ODR;      /* Offset 0x14 */
    __IO uint32_t BSRR;     /* Offset 0x18 */
    /* ... */
} GPIO_TypeDef;

그리고 GPIOB base address를 해당 구조체 pointer로 만든다.

Concept:

#define GPIOB \
    ((GPIO_TypeDef *)GPIOB_BASE)

GPIOB base:

0x40020400

5. Structure Offset = Register Offset

구조체 member는 순서대로 memory에 배치된다.

Example:

GPIOB_BASE = 0x40020400

Structure:

MODER   offset 0x00
OTYPER  offset 0x04
OSPEEDR offset 0x08
PUPDR   offset 0x0C
IDR     offset 0x10
ODR     offset 0x14

따라서:

GPIOB->ODR

는 실제로:

GPIOB_BASE + ODR offset

즉:

0x40020400 + 0x14
=
0x40020414

를 access한다.


6. Raw Address vs CMSIS

Ch02:

#define GPIOB_ODR \
    (*(volatile unsigned int *)0x40020414UL)

Ch06:

GPIOB->ODR

둘 다 최종적으로 접근하는 주소:

0x40020414

이다.

즉:

Raw Address
     ↓
0x40020414
     ↓
GPIOB ODR

와:

CMSIS
     ↓
GPIOB->ODR
     ↓
0x40020414

는 같은 hardware register를 제어한다.


7. The -> Operator

C에서:

GPIOB->ODR

는:

(*GPIOB).ODR

와 같은 의미다.

즉:

GPIOB
↓
pointer to GPIO_TypeDef
↓
dereference
↓
access member ODR

이다.


8. Why Structure Mapping Works

C 구조체의 member가 register offset과 동일한 위치에 배치되기 때문이다.

Example:

typedef struct
{
    uint32_t A;   /* offset 0x00 */
    uint32_t B;   /* offset 0x04 */
    uint32_t C;   /* offset 0x08 */
} Example;

Base address:

0x40000000

이면:

A = 0x40000000
B = 0x40000004
C = 0x40000008

와 같은 방식이다.


9. What is __IO?

CMSIS register structure에서 자주 보이는:

__IO uint32_t

에서 __IO는 일반적으로:

volatile

의 의미를 포함한다.

Concept:

volatile uint32_t

즉 hardware register를 나타낸다.


10. Why volatile Is Still Important

CMSIS를 사용한다고 volatile 개념이 사라지는 것이 아니다.

오히려 CMSIS 내부에서 이미 처리해준다.

Example:

GPIOB->IDR

는 hardware input에 의해 CPU code와 상관없이 변할 수 있다.

Button press
    ↓
Electrical level changes
    ↓
GPIO IDR changes

따라서 compiler가:

"이 값은 전에 읽었으니까 다시 안 읽어도 된다"

라고 최적화하면 안 된다.


11. __I, __O, __IO

CMSIS에서는 access type을 구분한다.

Concept:

__I   → Read Only
__O   → Write Only
__IO  → Read / Write

Example:

__I  uint32_t IDR;
__IO uint32_t ODR;

실제 device header마다 표현 방식은 조금 다를 수 있지만 의미는 동일하다.


12. Device Header

STM32 project에서는 보통:

#include "stm32f4xx.h"

를 사용한다.

하지만 STM32F4 family에는 여러 MCU가 있다.

STM32F411
STM32F407
STM32F429
STM32F439
...

따라서 compiler에게 어떤 device를 사용하는지 알려줘야 한다.


13. Device Macro

NUCLEO-F439ZI에서는:

STM32F439xx

를 define한다.

Makefile:

-DSTM32F439xx

Example:

CFLAGS += -DSTM32F439xx

이 define을 기준으로 STM32F439용 register definition이 선택된다.


14. Header Selection Flow

Concept:

main.c
  ↓
#include "stm32f4xx.h"
  ↓
STM32F439xx defined?
  ↓
include STM32F439 device header
  ↓
Peripheral structures
IRQ definitions
Register bit masks

15. RCC Example

Ch02에서는 RCC register를 직접 만들었다.

#define RCC_AHB1ENR \
    (*(volatile unsigned int *)0x40023830UL)

CMSIS:

RCC->AHB1ENR

GPIOB clock enable:

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;

16. What is RCC_AHB1ENR_GPIOBEN?

이것도 결국 bit mask다.

Concept:

#define RCC_AHB1ENR_GPIOBEN \
    (1UL << 1)

따라서:

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;

는 Ch02의:

RCC_AHB1ENR |= (1UL << 1);

과 같은 동작이다.


17. Why Use Named Bit Masks?

Compare:

RCC->AHB1ENR |= (1UL << 1);

vs:

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;

두 코드는 같은 hardware operation이지만 두 번째 코드는 의미를 바로 알 수 있다.

bit 1

보다:

GPIOB Enable

이라는 이름이 훨씬 명확하다.


18. Practice Code

이 장에서는 CMSIS register definition을 이용해서 PB0의 LD1을 blink한다.

#include "stm32f4xx.h"

#define LD1_PIN (1UL << 0)

static void delay(volatile unsigned int count)
{
    while (count--)
    {
        __NOP();
    }
}

int main(void)
{
    /* Enable GPIOB clock */
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;

    /* PB0 -> General Purpose Output */
    GPIOB->MODER &= ~(0x3UL << (0 * 2));
    GPIOB->MODER |=  (0x1UL << (0 * 2));

    while (1)
    {
        GPIOB->ODR ^= LD1_PIN;

        delay(800000);
    }
}

19. Code Flow

main()
  ↓
Enable GPIOB Clock
  ↓
PB0 Output Mode
  ↓
while(1)
  ↓
Toggle ODR bit0
  ↓
Delay
  ↓
Repeat

20. Clock Enable

RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;

이 한 줄에서:

RCC

는 RCC register block pointer.

AHB1ENR

는 register member.

RCC_AHB1ENR_GPIOBEN

는 GPIOB clock enable bit mask다.

즉:

Peripheral
→ Register
→ Bit Field

구조가 코드에 그대로 보인다.


21. GPIO Mode

PB0 mode field:

MODER[1:0]

clear:

GPIOB->MODER &= ~(0x3UL << 0);

set output:

GPIOB->MODER |= (0x1UL << 0);

결과:

MODER[1:0] = 01

22. Output Toggle

GPIOB->ODR ^= LD1_PIN;

LD1_PIN:

#define LD1_PIN (1UL << 0)

즉 bit 0을 XOR한다.

0 XOR 1 = 1
1 XOR 1 = 0

따라서:

LOW
HIGH
LOW
HIGH
...

가 반복된다.


23. __NOP()

Ch02에서는:

__asm__("nop");

를 사용했다.

CMSIS에서는:

__NOP();

를 사용할 수 있다.

__NOP()는 Cortex-M core instruction intrinsic이다.

실제 의미:

No Operation

CPU가 한 instruction 동안 의미 있는 연산을 하지 않는다.


24. CMSIS Core Intrinsics

CMSIS는 register structure뿐 아니라 ARM core instruction도 C에서 사용할 수 있도록 intrinsic을 제공한다.

Examples:

__NOP();
__WFI();
__disable_irq();
__enable_irq();

이들은 이후 chapter에서도 등장한다.

__WFI()
→ low-power mode

__disable_irq()
→ global interrupt disable

__enable_irq()
→ global interrupt enable

25. CMSIS Does Not Mean HAL

매우 중요하다.

CMSIS:

GPIOB->ODR |= ...

HAL:

HAL_GPIO_WritePin(...)

는 abstraction level이 다르다.


26. Layer View

Application
    ↓
HAL
    ↓
CMSIS / Device Header
    ↓
Peripheral Registers
    ↓
Hardware

CMSIS는 register definition에 매우 가깝다.

HAL은 register 설정 과정을 function으로 더 많이 감싼다.


27. CMSIS Example

GPIOB->MODER

를 보면 여전히 programmer가 직접:

Which register?
Which bit?
Which mode?

를 결정해야 한다.

즉 register-level programming이다.


28. HAL Example

HAL에서는:

HAL_GPIO_WritePin(
    GPIOB,
    GPIO_PIN_0,
    GPIO_PIN_SET
);

처럼 더 높은 level의 API를 사용할 수 있다.

내부에서는 결국 register를 제어한다.


29. Why Use CMSIS in Bare-Metal Study?

Raw address 방식은 memory map을 이해하는 데 매우 좋다.

하지만 이후 peripheral이 많아지면:

UART
ADC
Timer
DMA
I2C
SPI
RTC

각 register address를 직접 정의하는 것은 코드가 너무 복잡해진다.

CMSIS를 이용하면:

주소 계산 원리는 이해한 상태에서
↓
register 이름을 이용해 직접 제어

할 수 있다.

그래서 학습 흐름은:

Ch02
Raw Address

↓

Ch06
CMSIS Register Mapping

↓

Later Chapters
Direct Peripheral Programming

이 자연스럽다.


30. CMSIS Still Requires the Reference Manual

CMSIS header에 register 이름이 있다고 해서 reference manual이 필요 없어지는 것은 아니다.

예:

USART3->CR1

이라는 이름은 알 수 있다.

하지만:

Which bit enables TX?
What does bit13 mean?
When can this bit be changed?
What is reset value?

는 reference manual을 봐야 한다.

즉:

CMSIS Header
→ How to access

Reference Manual
→ What it means

이라고 볼 수 있다.


31. Header vs Reference Manual

예를 들어:

RCC_APB1ENR_USART3EN

이라는 macro는 bit position을 알려준다.

하지만:

USART3가 어느 bus에 연결되어 있는가?
Clock frequency는 얼마인가?
언제 clock enable이 필요한가?

까지 모두 설명하는 것은 아니다.

그래서 embedded 개발에서는:

Code
Header
Reference Manual
Board Schematic

을 함께 본다.


32. Register Address Verification

CMSIS가 실제로 어떤 주소에 access하는지 확인하고 싶으면 debugger에서:

GPIOB

pointer 값을 확인할 수 있다.

Expected:

GPIOB = 0x40020400

그리고:

&GPIOB->ODR

개념상:

0x40020414

여야 한다.


33. Memory Mapping Review

GPIOB:

GPIOB_BASE
=
0x40020400

MODER:

GPIOB->MODER
=
0x40020400 + 0x00
=
0x40020400

ODR:

GPIOB->ODR
=
0x40020400 + 0x14
=
0x40020414

즉 Ch02 주소 계산과 완전히 연결된다.


34. Why This Is Useful for Debugging

만약:

GPIOB->ODR

가 예상대로 동작하지 않으면 다음 순서로 확인할 수 있다.

1. GPIOB base address correct?
2. GPIOB clock enabled?
3. MODER configured?
4. ODR bit correct?
5. Board LED actually connected to PB0?

CMSIS를 써도 debugging 사고방식은 register-level 그대로다.


35. FPU Options

STM32F439ZI의 Cortex-M4는 single-precision FPU를 지원한다.

CMSIS build에서 사용한 option:

-mfloat-abi=hard
-mfpu=fpv4-sp-d16

이 option은 floating-point code가 hardware FPU를 사용할 수 있게 한다.


36. ABI Compatibility

중요한 점:

Object A → hard-float ABI
Object B → soft-float ABI

처럼 서로 다른 ABI를 섞으면 linker가 호환성 문제를 발생시킬 수 있다.

따라서 같은 project의:

startup
main
drivers
libraries

는 가능한 한 동일한 ABI 설정을 사용해야 한다.


37. Program Size

기록된 Ch06 build result:

text = 664 bytes

이전 Ch02:

text = 252 bytes

더 커졌다.

하지만 이것을:

"CMSIS 문법이 무거워서"

라고 해석하면 안 된다.

Ch06부터:

CMSIS
SystemInit
Full startup support
Device headers
FPU ABI configuration

등 build 환경도 같이 변경되었다.


38. Structure Access Does Not Automatically Add Runtime Overhead

예:

GPIOB->ODR ^= 1U;

Compiler가 알고 있는 것은:

GPIOB base
+
ODR offset

이므로 최종적으로는 특정 memory address에 load/store instruction을 만들 수 있다.

즉 구조체 자체가 runtime에 거대한 object를 만드는 것이 아니다.

CMSIS structure는 주소를 표현하는 C-level mapping이다.


39. Verification

Build

make clean
make

Expected:

Build PASS

Board Test

Expected:

LD1 PB0 blinking

Debugger

다음 register를 Watch 또는 SFR view에서 확인한다.

RCC->AHB1ENR
GPIOB->MODER
GPIOB->ODR

Expected:

AHB1ENR.GPIOBEN = 1
MODER[1:0]      = 01
ODR[0]          = toggling

40. Address Verification

가능하면 debugger에서:

GPIOB pointer

와:

&GPIOB->MODER
&GPIOB->ODR

를 확인한다.

Expected:

GPIOB        = 0x40020400
GPIOB->MODER = 0x40020400
GPIOB->ODR   = 0x40020414

이 캡처가 있으면 Ch02와 Ch06의 연결을 시각적으로 보여줄 수 있다.


41. Screenshot to Add

images/ch06_build.png
images/ch06_gpio_registers.png
images/ch06_address_mapping.png
images/ch06_led.gif

가장 좋은 증거:

Debugger Register View
+
Actual LED Blink

42. Raw Address vs CMSIS Summary

Ch02
0x40020414
    ↓
volatile pointer
    ↓
ODR

Ch06
GPIOB
    ↓
GPIO_TypeDef
    ↓
ODR member
    ↓
0x40020414

결국 동일하다.


43. Key Questions

Q1. What is CMSIS?

ARM Cortex-M 기반 MCU에서 core와 register interface를 표준화한 software interface.


Q2. Does CMSIS remove register-level programming?

No.

CMSIS는 register access를 더 읽기 쉬운 이름과 구조체로 표현한다.


Q3. What does GPIOB->ODR mean?

GPIOB base address를 GPIO_TypeDef pointer로 보고 ODR offset의 register를 access한다.


Q4. Why does ODR become address 0x40020414?

GPIOB_BASE = 0x40020400
ODR offset = 0x14

0x40020400 + 0x14
= 0x40020414

Q5. What does __IO mean?

Hardware register가 read/write 가능하며 volatile 특성을 가진다는 의미로 사용된다.


Q6. What is the difference between CMSIS and HAL?

CMSIS
→ register/core definitions

HAL
→ higher-level driver API

Q7. Why define STM32F439xx?

Device header가 STM32F439ZI에 맞는 register/IRQ definition을 선택할 수 있게 하기 위해.


Q8. Is the Reference Manual still required?

Yes.

CMSIS는 register 이름과 access 방법을 제공하지만, bit behavior와 hardware timing은 reference manual을 봐야 한다.


44. Key Takeaways

Reference Manual
      ↓
Base Address / Offset / Bit Field
      ↓
CMSIS Device Header
      ↓
Peripheral Structure
      ↓
GPIOB->MODER
GPIOB->ODR
      ↓
Hardware

핵심은 다음과 같다.

  1. CMSIS는 hardware address를 숨겨 없애는 것이 아니다.
  2. Peripheral register map을 C 구조체로 표현한다.
  3. Structure member offset과 hardware register offset이 일치한다.
  4. GPIOB->ODR은 결국 0x40020414에 접근한다.
  5. __IO는 hardware register access를 위한 volatile semantics를 가진다.
  6. CMSIS와 HAL은 다른 abstraction level이다.
  7. CMSIS를 사용해도 reference manual은 계속 필요하다.
  8. Ch02의 raw-address 이해가 있어야 CMSIS 코드도 실제 hardware 관점에서 이해할 수 있다.

Clone this wiki locally