STM32 메모리 매핑 및 부트로드 프로세스에 대한 자세한 설명.
아래의 모든 예는 STM32F446에 대한 것이지만 기본 원칙은 대부분의 MCU에 적용됩니다.
STM32CUBEMX는 모든 탐색에 대한 참조로 볼 수있는 STM32F446RETX_FLASH.LD를 생성합니다.
링커는 컴파일러에서 생성 된 객체 파일을 가져 와서 최종 컴파일 출력을 생성합니다.이 경우 .ELF 바이너리입니다. 링커는 항상 링커 스크립트 파일을 사용합니다. 하나를 지정하지 않더라도 기본 스크립트가 사용됩니다.
링커 스크립트는 MCU 사양을 기반으로 한 메모리 만 설명하고 하드웨어 메모리가 연주되지 않습니다.
메모리 구조를 이해하지 않고는 부트 로더 프로세스에 대해 이야기 할 수 없습니다. 실제로 부트 로더의 목적은 응용 프로그램의 main() 메소드를 실행할 수있는 상태에서 메모리를 갖는 것이 목적입니다.
STM32F4의 프로그램 메모리, 데이터 메모리, 레지스터 및 I/O 포트는 동일한 선형 주소 공간 내에서 구성됩니다.
...
+ -- -- 0x2001FFFF -- -- +
| |
| RAM |
| |
+ -- -- 0x20000000 -- -- +
| ... |
+ -- -- 0x1FFF7A0F -- -- +
| |
| System |
| |
+ -- -- 0x1FFF0000 -- -- +
| ... |
+ -- -- 0x081FFFFF -- -- +
| |
| Flash |
| |
+ -- -- 0x08000000 -- -- +
| ... |
+ -- -- 0x001FFFFF -- -- +
| |
| Alias |
| |
+ -- -- 0x00000000 -- -- + Alias Memory가 BOOT0 핀에 따라 플래시, 시스템 또는 RAM 메모리를 가리키는 경우. 기본적으로 플래시입니다.
현재 우리에게 가장 관심이 있습니다.
그러나 실제로 기능 분할은 다를 수 있습니다. 예를 들어, 프로그램 바이너리 데이터를 RAM에로드하거나 그 반대로로드해야 할 수도 있습니다.
메모리 구조는 링커 파일에 반영됩니다. 플래시는 ORIGIN = 0x8000000 에서 시작하고 RAM은 ORIGIN = 0x20000000 에서 시작됩니다.
// linker file
MEMORY
{
RAM ( xrw ) : ORIGIN = 0x20000000 , LENGTH = 128 K
FLASH ( rx ) : ORIGIN = 0x8000000 , LENGTH = 512 K
}이것은 기본 구현 일 뿐이며 Flash와 RAM을 쉽게 분할하고 MCU 메모리 사양을 준수하자마자 고유 한 블록이나 섹션을 추가 할 수 있습니다.
그런 다음 메모리는 링커 파일에 섹션으로 분할되며 각 주소 및 대상 (예 : 플래시 또는 RAM)이 있습니다.
// linker file
SECTIONS
{
...
. text :
{
...
} > FLASH
...
}.ELF 기호 테이블을 탐색하기 위해 ARM-NONE-ABI-OBJDUMP 명령을 사용하고 모든 항목을 주소별로 정렬합니다 .
arm-none-eabi-objdump -t stm32-boot-explained.elf | sort이 프로그램은 매뉴얼에 설명 된대로 출력을 생성합니다.
Address Flag_Bits Section Size Name컴파일 된 프로그램 코드는 플래시 메모리 에서이 섹션으로 들어갑니다.
섹션은 플래시의 ORIGIN 주소에서 시작하여 _etext 는 섹션의 마지막 주소를 가리 킵니다.
08000000 l d . text 00000000 . text
08000 c00 g . text 00000000 _etext우리는 부트 로더 파일에서 사용하는 함수의 위치를 볼 수 있습니다.
080005 c8 g F . text 00000048 __libc_init_array
08000610 g F . text 000000d 8 main
080006e8 g F . text 00000064 Reset_Handler
08000b c4 g F . text 00000024 SystemInit 이 섹션은 RAM 메모리에 존재하며 정의 된 값의 모든 변수를 보유합니다. 주소 범위는 _sdata 에서 _edata 까지입니다.
20000000 g . data 00000000 _sdata
20000010 g . data 00000000 _edata이 섹션에서 main.c의 변수를 확인해 봅시다.
20000000 l O . data 00000004 static_data_int
20000008 g O . data 00000008 data_double 주소 _sidata 에서 플래시 메모리에서 데이터를 복사하여 실제 값을 부트 로더 스크립트로 채워야합니다.
08000 c08 g * ABS * 00000000 _sidata 블록 시작 기호 RAM 데이터가 할당 된 값이없는 모든 변수에 대한 데이터 섹션. 부트 로더는이 블록의 데이터를 0 으로 설정해야합니다.
메모리 범위는 _sbss 에서 _ebss 에 이릅니다.
20000010 g . bss 00000000 _sbss
20000044 g . bss 00000000 _ebss명시 적 값이없는 main.c의 모든 변수는이 섹션에 있습니다.
2000002 c l O . bss 00000004 static_bss_int
20000030 g O . bss 00000008 bss_double
20000038 g O . bss 00000008 bss_my_struct
20000040 g O . bss 00000004 bss_my_unionGDB 로 확인합시다 :
(gdb) p bss_double
$1 = 0
(gdb) p & bss_double
$2 = (double * ) 0x20000030 < bss_double > 변수에 약간의 값을 할당하면 주소가 변경되지 않으며 여전히 .bss 에 유지됩니다.
(gdb) p bss_double
$3 = 86
(gdb) p & bss_double
$4 = (double * ) 0x20000030 < bss_double > _end 위의 모든 RAM 메모리 및 _estack 이 힙 및 스택 메모리에 전념 할 때까지.
20000048 g . _user_heap_stack 00000000 _end
20020000 g . isr_vector 00000000 _estack _estack 주소는 ORIGIN(RAM) + LENGTH(RAM) 로 계산됩니다. 128kB RAM의 경우 :
_estack = 0x20000000 + 128 * 1024 # dec
= 0x20000000 + 0x20000 # hex
= 0x20020000 스택은 _estack 에서 시작하여 아래쪽으로 자라는 Lifo 구조입니다. 최소 스택 크기는 링커 파일에 _Min_Stack_Size 로 정의됩니다. 스택 메모리가 자동으로 해제됩니다.
+ -- -- 0x20020000 -- -- + < -- _estack
| |
| Stack |
| |
+ - - 0x2001FC00 - - + < -- - _Min_Stack_Size
| |
+ -- -- 0x200 sssss -- -- + < -- $msp register
| |
| |
| Free space |
| |
| |
+ -- -- 0x200 hhhhh -- -- + < -- Actual heap end
| |
| Heap |
| |
+ - - 0x20000248 - - + < -- + _Min_Heap_Size
| |
+ -- -- 0x20000048 -- -- + < -- _end 힙은 차례로 _end 에서 시작하여 malloc 요청할 때 _estack - _Min_Stack_Size 까지 위쪽으로 자랍니다.
Main.C.에 정의 된 stack_int 변수가 있습니다. 초기화 된 후 GDB 로 주소를 확인해 봅시다.
(gdb) p & stack_int
$1 = (unsigned short * ) 0x2001ffd6
(gdb) p $msp
$2 = (void * ) 0x2001ffd0 변수가 $msp 보다 높을 때 우리의 기대와 일치합니다.
임베디드 C 응용 프로그램에서 일반적으로 사용되는 표준 C 라이브러리 구현은 Newlib라는 것을 언급 할 가치가 있습니다. 이 라이브러리에는 특정 시스템 별 기능을 구현해야합니다. STM32Cubemx는 필요한 기본 구현으로 syscalls.c 파일을 생성합니다.
또한 프로그램 데이터 공간을 증가시키는 sbrk 호출의 경우도 마찬가지입니다. malloc 은이 기능을 사용하여 더 많은 힙 메모리를 할당합니다. sysmem.c에서 stm32cubemx에 의해 생성 된 구현을 찾을 수 있습니다. 단순히 화가에서 _end 에서 _estack에서 _estack - _Min_Stack_Size 까지 힙이 자랄 수 있습니다.
이제 MCUS 메모리를 이해하면 GDB를 사용하여 프로그램에 연결합시다. 여기에 첫 번째 출력이 있습니다.
...
Reading symbols from ./stm32-boot-explained.elf...
Remote debugging using localhost:61234
Reset_Handler () at %PATH%/bootloader.c:25
25 void Reset_Handler () {
(gdb) info registers
...
pc 0x80006e8 0x80006e8 < Reset_Handler >
... Reset_Handler 어떻게 든 응용 프로그램의 부트 포인트로 식별되었습니다.
링커 파일에 ENTRY 명령이 있음을 알 수 있습니다.
// linker file
ENTRY ( Reset_Handler ) 실제로 .ELF 파일 헤더의 Reset_Handler 함수 주소에 대한 참조를 저장합니다.
arm-none-eabi-objdump -t -f stm32-boot-explained.elf | grep " start address "
start address 0x080006e9 기호 테이블을 보면 Reset_Handler 다른 모든 기능과 마찬가지로 Flash .text 섹션에 있습니다.
080006e8 g F . text 00000064 Reset_Handler주소는 페이지 정렬되므로 .ELF 헤더와 실제 주소 사이에 불일치가있을 가능성이 있습니다.
이 정보는 주로 코드 내에서 진입 점 기호의 존재를 확인하기 위해 링커에 의해 주로 사용되지만 MCU에 대한 실질적인 의미는 없습니다.
STM32 사양에 따르면, CPU는 주소 0x00000000 에서 상단 스택 _estack 값을 가져온 다음 0x00000004 에서 시작하는 부팅 메모리에서 코드 실행을 시작합니다.
+ -- -- 0x001FFFFF -- -- +
| |
| Alias |
| |
+ -- -- 0x00000000 -- -- + 위에서 언급 한 별칭 메모리가 정의되는 곳입니다. 기본 구성을 사용하면 PIN 값 BOOT0 = 0 이면 0x8000000 에서 시작하는 플래시 메모리 블록과 별칭이됩니다.
BOOT0 및 BOOT1 기반으로 한 다른 옵션에는 내장 된 부트 로더 또는 RAM 메모리가 포함 된 시스템 메모리가 포함됩니다. 임베디드 부트 로더는 제작 중에이 매뉴얼의 범위를 벗어난 ST에 의해 프로그래밍됩니다.
그러나 Reset_Handler 플래시 메모리의 시작이 아닌 주소 0x08000524 가지고 있습니다. MCU는 어떻게 부트 스트랩 방법을 찾습니까?
벡터 테이블이 시작되는 곳은 다음과 같습니다.
08000000 g O . isr_vector 000001 c4 Vector_Table MCU는 메모리의 시작을 벡터 테이블로 취급하며, 이는 다양한 인터럽트 서비스 루틴에 대한 포인터 및 Reset_Handler 포함한 필수 시작 기능을 포함합니다. MCU가 0x00000000 에서로드 할 것으로 예상되는 정확한 테이블 구조를 보려면 사양을 참조하십시오. 실제 테이블은 부트 로더로 채워야합니다.
| 주소 | 이름 |
|---|---|
| 0x00000000 | 예약된 |
| 0x00000004 | 핸들러 재설정 |
| 0x00000008 | 마스크 할 수없는 인터럽트 |
| 0x00000012 | 어려운 잘못 |
| ... | 다른 인터럽트 |
이 Reset_Handler 보안 별 작업에서 펌웨어 자동 업데이트에 이르기까지 많은 응용 프로그램에 사용할 수있는 부트 로더 기능입니다. 여기서는 MCU의 메모리와의 상호 작용을 이해하기위한 기본 기본 구현을 살펴 보겠습니다.
기본적으로 Reset_Handler 메소드는 stm23cubemx에서 제공하는 startup_stm32f436xx.s asm 파일에 정의됩니다. 실제 bootloader.c 구현이 프로젝트의 구현은 명확성을 위해 C로 작성되었습니다.
링커 스크립트에 정의 된 변수는 C 코드에서 액세스 할 수 있습니다.
extern uint32_t _estack ;ASM 버전을 쉽게 복제 할 수 있습니다.
최소 로딩 프로세스는 다음 단계로 나눌 수 있습니다.
SystemInit() 기능).data 세그먼트 이니셜 라이더를 플래시에서 RAM으로 복사하십시오.bss 세그먼트를 채우십시오__libc_init_array() 함수)main() 함수)을 호출하십시오.1 단계 및 #4의 경우 STM32CUBEMX는 기능 구현을 제공하며 System_STM32F4XX.C에서 세부 사항을 확인할 수 있습니다.
이 프로젝트에는 STM32를 부팅하는 데 필요한 최소한의 파일 세트가 있습니다. ARM-NONE-ABI-OBJDUMP 의 출력을 확인하고 GDB 를 통과하기 위해 직접 시도해 볼 수 있습니다.
프로젝트를 구축하려면 ARM GNU 도구 체인이 필요합니다.
ARM-NONE-EABI-GCC , STM32_PROGRAMMER_CLI 및 ST-LINK_GDBSERVER 도구가 포함 된 올인원 STM32CUBECLT 명령 도구 패키지를 설치하는 것이 좋습니다.
cmake를 사용하여 프로젝트 구축 :
mkdir build ; cd build
cmake ../ -DPROGRAMMER_CLI=/opt/ST/STM32CubeCLT_1.15.1/STM32CubeProgrammer/bin
-DGDB_SERVER=/opt/ST/STM32CubeCLT_1.15.1/STLink-gdb-server/bin
make VERBOSE=1마지막 명령은 연결 단계입니다. 다른 모든 컴파일러 플래그를 제거하면 명령이 다음과 같이 보일 수 있습니다. 링커에 링커 스크립트를 사용하도록 지시받는 곳입니다.
arm-none-eabi-gcc
...
-T " %SCRIPT_DIR%/STM32F446RETx_FLASH.ld "
...
" %OBJ_DIR%/%OBJECT_NAME%.c.obj "
...
-o stm32-boot-explained.elfSTM32_PROGRAMMER_CLI는 SWD Procotol에 대해 미리 구성되어 있습니다.
make flash 0x08000000 주소를 시작점으로 사용하는 프로그래머 출력에 대한 메모를하십시오.
...
Memory Programming ...
Opening and parsing file: stm32-boot-explained.elf
File : stm32-boot-explained.elf
Size : 1,46 KB
Address : 0x08000000
...실제로 우리가 이미 알고있는 플래시 메모리의 시작 주소입니다.
st-link_gdbserver를 실행하기 위해 사전 구성된 사용자 정의 대상이 있습니다.
# start ST-Link gdb server
make gdb-server
# connect with gdb debugger
gdb -ex ' target remote localhost:61234 ' ./stm32-boot-explained.elf