※ 아래의 내용은 직접 작성한 내용이며, 경어를 사용하지 않았습니다.
읽으시는동안 불편하시더라도 이해 부탁드립니다.
그리고 테스트를 위한 보드는 http://oroca.org 에서 공동구매한 stm32 smart robot board를 사용하였습니다.
cf) 관련 글 목록
0. 시작하기 전에
Cortex-M3 Link Script 의 0. 시작하기전에.. chapter에 설명한 것과 동일하게 CMSIS에서 제공하는 startup_ARMCM3.S 에 stm과 관련된 사항을 포팅하였다.
vector table에 정의된 handler함수들을 제외하고는 startup_ARMCM3.S와 동일한 내용으로 구성되어 있다.
여기서는 startup_ARMCM3.S 코드를 분석하고자 한다.
해당 파일은 Assembler로 되어 있으며, Assembler 명령어를 제외한 지시자에 대한 내용들은 아는 만큼 설명하겠지만 아래 사이트를 한번 보면 도움이 될 듯한다.
http://sourceware.org/binutils/docs/as/ARM-Directives.html
Assembler에 대한 두려움이 있는 사람들이 많은 걸로 알고 있는데, ARM assembler는 굉장히 쉽다.... 라고 말은 할수 없다. 쉬운 사람에게는 처음 봐도 이해가 될 것 같고, 어려운 사람에게는 수 십번을 봐도 이해가 안되는게 assembler 인 듯하다.
그러나 다행히도 Cortex-M 계열은 startup.S 파일에서 명령어 관련 수정사항이 거의 없고, 또 필요하다면 assembler없이 바로 c코드로 코딩이 가능하다. (Cortex-M 만의 큰 장점중에 하나인 듯하다.)
여기에 대해서는 나중에 기회가 되면 다시 언급하도록 하겠다.
1. Start Up Code의 구성
.arch armv7-m
- Stack Section define
- Heap Section define
- isr_vection define
- Text Area
- Macro & predefine for each ISR Handler
.macro def_irq_handler handler_name
...
.endm
def_irq_handler NMI_Handler
def_irq_handler ...
...
end
Start Up 코드는 6 개의 part로 구성된다.
첫째가 Architecture에 대한 Assembler 지시자, Stack, Heap, ISR Vection Section 정의 그리고 text 영역(실제 동작 코드를 말함), 마지막으로 ISR Vection에서 호출되는 ISR Handler들에 대한 기본형 정의부분이다.
각 영역에 대한 세부 내용들을 살펴보자.
2. Architecture 설정
.arch armv7-m
1) .syntax
Instruction set의 syntax 에대한 설정이다. default값으로 divided 값을 가지는데, 이는 ARM 코드와 Thumb 코드와의 관계를 설명한다.
Cortex-M의 경우 unified로 설정해야 한다.
2) .arch
Cortex-M 시리즈가 지원하는 Architecture에 대한 설정이다. Cortex-M3의 경우 armv7-m Architecture를 가진다.
3. Stack & Heap Section Define
1) Stack Area
.align 3
#ifdef __STACK_SIZE
.equ Stack_Size, __STACK_SIZE
#else
.equ Stack_Size, 0x00000400
#endif
.globl __StackTop
.globl __StackLimit
__StackLimit:
.space Stack_Size
.size __StackLimit, . - __StackLimit
__StackTop:
.size __StackTop, . - __StackTop
stack section에 설정이다.
Stack Size를 define한 후 Stack Size에 대한 __StackLimit 와 __StackTop 사이에 .space Stack_Size라는 크기의 공간으로 Section을 설정한다.
여기서 Link Script의 Stack 관련 부분을 살펴 보면 다음과 같이 .stack section이 .stack_dummy 영역에 놓이게 된다.
{
*(.stack*)
} > RAM
__StackTop = ORIGIN(RAM) + LENGTH(RAM);
__StackLimit = __StackTop - SIZEOF(.stack_dummy);
PROVIDE(__stack = __StackTop);
즉.. Link Script의 __StackTop = ORIGIN(RAM) + LENGTH(RAM); 에 의해서 __StackTop이라는 Symbol은 RAM의 마지막 Address값을 가지게 되고, __StackLimit = __StackTop - SIZEOF(.stack_dummy); 에 의해서 .space Stack_Size 라는 공간이 Stack 영역으로 할당되게 된다.
2) Heap Area
.section .heap
.align 3
#ifdef __HEAP_SIZE
.equ Heap_Size, __HEAP_SIZE
#else
.equ Heap_Size, 0x00000C00
#endif
.globl __HeapBase
.globl __HeapLimit
__HeapBase:
.if Heap_Size
.space Heap_Size
.endif
.size __HeapBase, . - __HeapBase
__HeapLimit:
.size __HeapLimit, . - __HeapLimit
Stack Area가 이해되었다면 Heap Area에 대한 내용도 그리 어렵지 않을 것으로 보인다.
단 Stack와의 차이는 Heap Base Address의 위치가 될 터인데, 이것 역시 Link Script를 통해 어떻게 정의되는지 알 수 있다.
{
. . .
__bss_end__ = .;
} > RAM
.heap (COPY):
{
__end__ = .;
end = __end__;
*(.heap*)
__HeapLimit = .;
} > RAM
즉,heap의 base address는 bss영역이 끝나는 __bss_end__ (혹은 __end__)가 되고, Heap의 크기는 ".space Heap_Size" 에 의해 Heap_Size가 되어, __HeapLimit라는 symbol값이 확정된다.
4. ISR Handler Vector Table
.section .isr_vector
.align 2
.globl __isr_vector
__isr_vector:
.long __StackTop /* Top of Stack */
.long Reset_Handler /* Reset Handler */
.long NMI_Handler /* NMI Handler */
.long HardFault_Handler /* Hard Fault Handler */
.long MemManage_Handler /* MPU Fault Handler */
.long BusFault_Handler /* Bus Fault Handler */
.long UsageFault_Handler /* Usage Fault Handler */
.long 0 /* Reserved */
.long 0 /* Reserved */
.long 0 /* Reserved */
.long 0 /* Reserved */
.long SVC_Handler /* SVCall Handler */
.long DebugMon_Handler /* Debug Monitor Handler */
.long 0 /* Reserved */
.long PendSV_Handler /* PendSV Handler */
.long SysTick_Handler /* SysTick Handler */
/* External interrupts */
.long WDT_IRQHandler /* 0: Watchdog Timer */
.long RTC_IRQHandler /* 1: Real Time Clock */
.long TIM0_IRQHandler /* 2: Timer0 / Timer1 */
.long TIM2_IRQHandler /* 3: Timer2 / Timer3 */
.long MCIA_IRQHandler /* 4: MCIa */
.long MCIB_IRQHandler /* 5: MCIb */
.long UART0_IRQHandler /* 6: UART0 - DUT FPGA */
.long UART1_IRQHandler /* 7: UART1 - DUT FPGA */
.long UART2_IRQHandler /* 8: UART2 - DUT FPGA */
.long UART4_IRQHandler /* 9: UART4 - not connected */
.long AACI_IRQHandler /* 10: AACI / AC97 */
.long CLCD_IRQHandler /* 11: CLCD Combined Interrupt */
.long ENET_IRQHandler /* 12: Ethernet */
.long USBDC_IRQHandler /* 13: USB Device */
.long USBHC_IRQHandler /* 14: USB Host Controller */
.long CHLCD_IRQHandler /* 15: Character LCD */
.long FLEXRAY_IRQHandler /* 16: Flexray */
.long CAN_IRQHandler /* 17: CAN */
.long LIN_IRQHandler /* 18: LIN */
.long I2C_IRQHandler /* 19: I2C ADC/DAC */
.long 0 /* 20: Reserved */
.long 0 /* 21: Reserved */
.long 0 /* 22: Reserved */
.long 0 /* 23: Reserved */
.long 0 /* 24: Reserved */
.long 0 /* 25: Reserved */
.long 0 /* 26: Reserved */
.long 0 /* 27: Reserved */
.long CPU_CLCD_IRQHandler /* 28: Reserved - CPU FPGA CLCD */
.long 0 /* 29: Reserved - CPU FPGA */
.long UART3_IRQHandler /* 30: UART3 - CPU FPGA */
.long SPI_IRQHandler /* 31: SPI Touchscreen - CPU FPGA */
.size __isr_vector, . - __isr_vector
1) Link Script와의 관계
__isr_vector 라는 symbol로 정의되는 ISR Handler Vector Table에 대한 정의다.
본 내용을 살펴보기에 앞서 Link Script와의 관계를 살펴보자.
.text :
{
KEEP(*(.isr_vector))
*(.text*)
. . . . . .
*(.rodata*)
} > FLASH
.text가 SECTION의 제일 처음에 정의되는데, .text를 보면 .isr_vector이 가장 먼저 정의된다.
즉, startup.S의 .section .isr_vector 이라고 정의된 vector handler table이 생성되는 Binary의 Base Address에 위치하게 되는 것이다. 그리고 다음에 살펴볼 .text라고 정의된 영역이 .isr_vector 이 후로 배치된다.
다시 말해 Cortex-M3가 제일 먼저 만나게 되는 Code가 Vector Table이 된다.
2) StackTop Address & ResetHandler
Cortex-M의 경우 Vector Table은 Core가 사용하기 위해 미리 정의된 16개의 값과 Chip Vendor가 추가 가능한 여러개의 External Handler로 구성된다.
여기서 주의 깊게 살 펴볼 것은 기존의 ARM Family와는 다르게, Vector Table에서 제일 먼저 위치한 값이 ISR Handler가 아닌 StackTop이라는 주소값이다.
그렇다. Cortex-M3는 부팅시 Vector Table의 0번지에 있는 4byte 값으로 Stack값을 설정하고, 그 다음의 4byte 값을 ResetHandler Address로 인식하여 ResetHandler로 Program Counter를 설정하게 된다.
앞서 언급했던 Cortex-M시리즈가 Assembler 없이 C코드로 구현 가능한 이유가 여기에 있다.
C코드의 경우 Function Call들로 이루어 지는데, Function Call에서 필수적으로 사용되는 것이 Stack이다. 기존의 ARM은 Start Up에서 해주는 역할중 중요한 하나가 Stack을 설정하는 것이고, 그 후 main으로 jump를 하게 되는데, Cortex-M의 경우 부팅시 초기 4byte의 값으로 stack이 설정되어 버리니, assembler 없이 C코드로 start up 코드를 구설 할 수 있게 되었다.
3) ISR Call방식
그리고 Cortex-M은 ISR발생시 따로 분기를 위한 코드가 존재 하지 않는다. ISR발생할 경우, ISR 설정시 부여하는 ISR number에 해당하는 Table의 코드가 실행된다.
Reset_Handler 부터 Systick_Handler 까지는 Cortex-M Core가 제공하는 Handler이며, 그 이후에 존재하는 Handler들은 Chip Vendor에서 필요에의해 추가된 ISR Handler가 된다.
이에 대한 사용법은 CMSIS 코드 분석시 논의가 될 수 있겠다.
Vector Handler에서 알아야 할 것은 3가지다.
1. 처음 4byte가 Stack의 Top Address를 가르킨다.
2. 그 다음 4byte가 프로그램의 시작인 ResetHandler를 가르킨다.
3. ISR 발생시 따로 분기 코드가 필요없이 해당 ISR number의 Handler로 Jump하게 된다.
5. ISR Handler MACRO & Function preDefine
ResetHandler를 포함하는 .text 영역을 살펴보기 전에 Vector Handler에 정의된 함수들의 기본 형 정의를 살펴보자.
.align 1
.thumb_func
.weak \handler_name
.type \handler_name, %function
\handler_name :
b .
.size \handler_name, . - \handler_name
.endm
def_irq_handler HardFault_Handler
def_irq_handler MemManage_Handler
def_irq_handler BusFault_Handler
def_irq_handler UsageFault_Handler
def_irq_handler SVC_Handler
def_irq_handler DebugMon_Handler
def_irq_handler PendSV_Handler
def_irq_handler SysTick_Handler
def_irq_handler Default_Handler
def_irq_handler WDT_IRQHandler
def_irq_handler RTC_IRQHandler
def_irq_handler TIM0_IRQHandler
def_irq_handler TIM2_IRQHandler
def_irq_handler MCIA_IRQHandler
def_irq_handler MCIB_IRQHandler
def_irq_handler UART0_IRQHandler
def_irq_handler UART1_IRQHandler
def_irq_handler UART2_IRQHandler
def_irq_handler UART3_IRQHandler
def_irq_handler UART4_IRQHandler
def_irq_handler AACI_IRQHandler
def_irq_handler CLCD_IRQHandler
def_irq_handler ENET_IRQHandler
def_irq_handler USBDC_IRQHandler
def_irq_handler USBHC_IRQHandler
def_irq_handler CHLCD_IRQHandler
def_irq_handler FLEXRAY_IRQHandler
def_irq_handler CAN_IRQHandler
def_irq_handler LIN_IRQHandler
def_irq_handler I2C_IRQHandler
def_irq_handler CPU_CLCD_IRQHandler
def_irq_handler SPI_IRQHandler
StackTop과 Reset_Handler를 제외한 .isr_vector에 정의된 table의 값들이 모두 def_irq_handler로 정의되어 있다. vector에서 Symbol을 사용하고 있으니 당연히 함수 원형이 필요하다.
이는 Cortex-M이 ISR호출시 해당되는 Table의 ISR Handler로 바로 jump하기 때문에 사용하고자 하는 마지막 ISR Number에 해당하는 Handler까지 미리 Define이 되어 있어야 하기 때문이다.
그런데 def_irq_handler 내부를 살펴보면 "b . " 이라는 assembler로 되어 있으며, 이는 C 코드의 "while(1);" 과 동일하게 무한 루프를 도는 코드이다.
이렇게 되면 사용자가 필요한 ISR함수를 구현한 후, 동일한 ISR 함수를 startup.S 에서 제거 해야하는 것일까?
(일반적으로 동일한 symbol값이 정의되어 있으며 compile시 redefinition error가 발생한다.)
답은 아니다 이다.
그 이유는 def_irq_handler 가 week 속성(.weak \handler_name)으로 정의 되어 있기 때문이다.
GCC는 weak 속성을 가지는 symbol과 동일한 이름을 가지는 symbol이 있을때 weak 속성의 symbol을 스스로 제거하고 그렇지 않는 symbol을 사용한다.
즉 NMI_Handler를 다른 코드에서 정의하고 구현할 경우 startup.S 에 정의된 def_irq_handler 로 정의된 NMI_Handler를 사용하지 않고 새롭게 정의된 코드를 사용하게 된다.
여기서 1부를 마감하고 2부에서는 .text에 구현된 ResetHandler에 대해 살펴보기로 하자.