Chapter 06 - Hongjayeong-02/Basic_SoC_implementation GitHub Wiki
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
핵심은 다음과 같다.
- CMSIS는 hardware address를 숨겨 없애는 것이 아니다.
- Peripheral register map을 C 구조체로 표현한다.
- Structure member offset과 hardware register offset이 일치한다.
GPIOB->ODR은 결국0x40020414에 접근한다.__IO는 hardware register access를 위한 volatile semantics를 가진다.- CMSIS와 HAL은 다른 abstraction level이다.
- CMSIS를 사용해도 reference manual은 계속 필요하다.
- Ch02의 raw-address 이해가 있어야 CMSIS 코드도 실제 hardware 관점에서 이해할 수 있다.