본문 바로가기
Robot/MCU

Cortex-M3 Start Up Code 분석 1부

by lifeseed 2013. 9. 3.

※ 아래의 내용은 직접 작성한 내용이며, 경어를 사용하지 않았습니다.

읽으시는동안 불편하시더라도 이해 부탁드립니다.

그리고 테스트를 위한 보드는 http://oroca.org 에서 공동구매한 stm32 smart robot board를 사용하였습니다.

 

cf) 관련 글 목록 

0. STM32F103CB 개발을 위한 ST Micro Resource 활용 

http://lifeseed.tistory.com/57 

1. Cortex-M3 Link Script 

 http://lifeseed.tistory.com/55

3. Cortex-M3 Start Up Code 분석 2부 

 http://lifeseed.tistory.com/58

 

 

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의 구성

 

- Architecture 설정
     .syntax unified
    .arch armv7-m

 

- Stack Section define

    .section .stack  

 

- Heap Section define 

    .section .heap

 

- isr_vection define 

    .section .isr_vector

 

- Text Area 

    .text  

 

- 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 설정

 

     .syntax unified
    .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

    .section .stack
    .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_dummy (COPY):
 {
  *(.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 :
 {
   . . .
  __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에 정의된 함수들의 기본 형 정의를 살펴보자.

 

    .macro    def_irq_handler    handler_name
    .align 1
    .thumb_func
    .weak    \handler_name
    .type    \handler_name, %function
\handler_name :
    b    .
    .size    \handler_name, . - \handler_name
    .endm

 

 

    def_irq_handler    NMI_Handler
    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에 대해 살펴보기로 하자.