※ 아래의 내용은 직접 작성한 내용이며, 경어를 사용하지 않았습니다.
읽으시는동안 불편하시더라도 이해 부탁드립니다.
그리고 테스트를 위한 보드는 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 코드의 reset_handler 코드를 분석하고자 한다.
1. Flash Map 과 Memory Map 비교
본격적으로 코드를 분석하기 이전에 실제 빌드된 binary의 Map과 Memory에 상주되는 Map의 차이를 알아보는 것도 의미가 있을 듯하다.
무엇보다 내가 작성한 코드가 어디에 저장되어 있으며, 동작할 때는 어디서 돌고 있는지 정도는 알아 두도록 하자. 특히 Start Up Code에서 FLASH영역에서 RAM영역으로 Copy동작이 일어나는데, 여기에 대한 이해를 도울 수 있다.
Flash Map |
Memory Map |
||||||
ld section name |
|||||||
|
FLASH_ORIGIN | ||||||
.text | |||||||
.ARM.extab |
code area |
code area |
|||||
.ARM.exidx | |||||||
__etext | |||||||
.data | |||||||
data area |
|||||||
RAM_ORIGIN | __data_start__ | ||||||
data area |
|||||||
__data_end__ | |||||||
bss area |
|||||||
heap area |
|||||||
stack area |
|||||||
1) Flash Map
코드를 빌드하면 elf가 생성되고 objcopy 명령을 이용하여 binary 를 생성할 수 있다. 이 때 생성된 binary가 flash에 저장된다.
binary에 저당되는 내용은 link script에서 선언된 FLASH 영역과 RAM 영역중 .data section이 저장되는데, 앞서 언급한 바와 같이 .data영역에는 0이 아닌 초기 값을 가지는 변수의 데이터들이 저장되어 있다.
그외 bss, heap, stack등은 코드 실행 중 생성할 수 있는 값들이기 때문에 따로 flash에 저장되는 binary에 포함 시키지 않는다.
2) Memory Map
Link Script에 선언된 주소 값에 의해 실제 메모리상에 배치되는 각 영역들의 주소 값을 가진다. Link Script에 의해 동작하는 Program Code 들은 FLASH_ORIGIN (본 예제에서는 0x80000) 부터 배치되며, read & writable한 영역들은 모두 RAM영역에 배치된다. 단 .data 영역은 0이 아닌 초기 값을 가져야 하므로 코드 시작시 Flash로 부터 복사해 와야 하며, bss영역은 0으로 초기화 되어야 하는데, 이런 일련의 과정들이 초기화 코드에 포함되어야 한다.
2. Reset Handler 분석
.text
.thumb
.thumb_func
.align 2
.globl Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
/* Loop to copy data from read only memory to RAM. The ranges
* of copy from/to are specified by following symbols evaluated in
* linker script.
* __etext: End of code section, i.e., begin of data sections to copy from.
* __data_start__/__data_end__: RAM address range that data should be
* copied to. Both must be aligned to 4 bytes boundary. */
ldr r1, =__etext
ldr r2, =__data_start__
ldr r3, =__data_end__
#if 1
/* Here are two copies of loop implemenations. First one favors code size
* and the second one favors performance. Default uses the first one.
* Change to "#if 0" to use the second one */
.flash_to_ram_loop:
cmp r2, r3
ittt lt
ldrlt r0, [r1], #4
strlt r0, [r2], #4
blt .flash_to_ram_loop
#else
subs r3, r2
ble .flash_to_ram_loop_end
.flash_to_ram_loop:
subs r3, #4
ldr r0, [r1, r3]
str r0, [r2, r3]
bgt .flash_to_ram_loop
.flash_to_ram_loop_end:
#endif
* of copy from/to are specified by following symbols evaluated in
* linker script.
* __bss_start__/__bss_end__: RAM address range that data should be
* filled to 0. Both must be aligned to 4 bytes boundary. */
movs r1, #0x0
ldr r2, =__bss_start__
ldr r3, =__bss_end__
.clear_bss_loop:
str r1, [r2], #0x4
cmp r2, r3
blt .clear_bss_loop
#ifndef __NO_SYSTEM_INIT // call SystemInit in main()
ldr r0, =SystemInit
blx r0
#endif
ldr r0, =main
bx r0
.pool
.size Reset_Handler, . - Reset_Handler
전체적인 구성은 지시자, 함수 선언, 함수 body, 그리고 함수의 코드 크기를 알려주는 지시자로 구성된다.
앞서 언급 한 것 처럼 함수 body는 크기 data 영역 복사, bss 초기화, 그리고 main함수 호출 순으로 구성되어 있다.
1) data 영역 복사
ldr r1, =__etext // Flash에 위치한 data의 시작 주소
ldr r2, =__data_start__ // RAM에 위치할 data의 시작 주소
ldr r3, =__data_end__ // RAM에 위치할 data의 마지막 주소
#if 1
.flash_to_ram_loop:
cmp r2, r3 // RAM의 시작 주소와 마지막 주소가 같은지 비교
ittt lt // r2 가 r3보다 작으면 if then 명령 수행
ldrlt r0, [r1], #4 // r2가 r3보다 작으면 r1에 저장된 주소값의 데이터를 r0로 복사하고
// r1에 저장된 주소를 4 만큼 증가
strlt r0, [r2], #4 // r2가 r3보다 작으면 r0에 저장된 값을 r2에 저장된 주소값에 복사하고
// r2에 저장된 주소값을 4 만큼 증가
blt .flash_to_ram_loop // r2가 r3보다 작으면 .flash_to_ram_loop로 jump
#else
subs r3, r2 // r3에서 r2를 빼면서 condition flag 설정
ble .flash_to_ram_loop_end // r3에서 r2를 뺀 값이 0보다 작으면 .flash_to_ram_loop_end 로 jump
.flash_to_ram_loop:
subs r3, #4 // r3에서 4를 뺌
ldr r0, [r1, r3] // r1+r3 주소값의 데이터를 읽어서 r0에 저장
str r0, [r2, r3] // r0의 데이터를 r2+r3 주소값에 저장
bgt .flash_to_ram_loop // r3가 0보다 클 경우 .flash_to_ram_loop로 jump
.flash_to_ram_loop_end:
#endif
__etext는 flash map에서 code의 끝 부분 즉 data의 시작부분을 의미하며, __data_start__ 및 __data_end__는 memory map에서 RAM영역의 data 시작 및 끝 부분을 나타낸다.
즉 위 asm 코드는 data 의 크기 만큼 __etext 에서 __data_start__로 복사하는 루틴이된다.
C로 나타내면 다음과 같다.
bin_addr = (unsigned int *) &__etext;
data_addr = (unsigned int) &__data_start__;
for(idx = 0 ; idx < (&__data_end__ - &__data_start__) ; idx++)
{
data_addr[idx] = bin_addr[idx];
}
2) bss 영역 초기화
bss란 코드에서 0으로 초기화 되는 영역들을 말하며, start up에서 0으로 초기화 해 주지 않으면 RAM에 남아있는 찌꺼기 값이 초기 값으로 설정되어 프로그램을 오동작 시킬 수 있다.
movs r1, #0x0 // r1에 0 대입
ldr r2, =__bss_start__ // r2에 bss의 시작 주소값 대입
ldr r3, =__bss_end__ // r3에 bss의 마지막 주소값 대입
.clear_bss_loop:
str r1, [r2], #0x4 // r2에 저장된 주소값에 r1의 값을 저장 후 r2의 주소값을 4 만큼 증가
cmp r2, r3 // r2 와 r3 비교
blt .clear_bss_loop // r2가 r3보다 작으면 .clear_bss_loop 로 jump
link script에서 predefine symbol인 __bss_start__에서 __bss_end__까지 0으로 채우는 루틴이다.
역시 C로 나타내면 다음과 같다.
for(idx = 0 ; idx < (&__bss_end__ - &__bss_start__) ; idx++)
{
bss_addr[idx] = 0;
}
3) main 함수로 jump
#ifndef __NO_SYSTEM_INIT // call SystemInit in main()
ldr r0, =SystemInit // r0에 Systeminit 함수 시작 주소 저장
blx r0 // r0 로 branch (즉 함수 호출)
#endif
ldr r0, =main // r0에 main 함수의 시작 주소 저장
bx r0 // r0 로 program count 이동. (main 함수 실행)
CMSIS에서 제공하는 start up코드에서는 main으로 jump하기 전에 Systemini 함수를 호출 하는 부분이 포함되어 있다.
cmsis에 포함된 sample 코드는 system_ARMCM3.c, st에서 제공하는 코드는 system_stm32f10x.c 이며, Clock 초기화 관련 코드들이 들어간다.
그러나 SystemInit 가 C로 구현된 함수 이므로, 여기서 호출하지 않고 main 함수의 시작 부분에서 호출해도 상관 없다.
즉... ST에서 제공하는 sample 코드분석시 main함수가 호출 되기 전에 system_stm32f10x.c 에 있는 SystemInit 함수가 호출 된다는 사실만 알고 있으면 된다.
여기까지가 시스템 시작 부터 main함수가 호출 되기 까지의 과정의 link script, startup_ARMCM3.S 를 통하여 살펴 보았다.
다음 포스팅에선 ST에서 제공하는 CM3 칩에서 Cortex-M3 가 담당하는 부분이 어디까지인지 살펴 보고 Cortex-M3 Core를 담당하는 CMSIS 라이브러리를 살펴보고자 한다.