STM32メモリマッピングとブートロードプロセスの詳細な説明。
以下の例はすべてSTM32F446のものですが、基本原則はほとんどのMCUに適用されます。
STM32CUBEMXは、STM32F446RETX_FLASH.LDを生成します。これは、すべての調査のリファレンスとして検討します。
リンカーは、コンパイラによって生成されたオブジェクトファイルを取得し、最終的なコンパイル出力を生成します。この場合、 Ell Binaryです。リンカーは常にリンカースクリプトファイルを使用します。 1つを指定しなくても、デフォルトのスクリプトが使用されます。
重要なことに、リンカースクリプトはMCUの仕様に基づいてメモリを記述し、ハードウェアメモリがアドレスを変更しないことに注意してください。
メモリ構造を理解せずにブートローダープロセスについて話すことはできません。実際、アプリケーションのmain()メソッドを実行する準備ができている状態のメモリを作成することは、ブートローダーの目的です。
STM32F4のプログラムメモリ、データメモリ、レジスタ、I/Oポートは、同じ線形アドレス空間内で編成されています。
...
+ -- -- 0x2001FFFF -- -- +
| |
| RAM |
| |
+ -- -- 0x20000000 -- -- +
| ... |
+ -- -- 0x1FFF7A0F -- -- +
| |
| System |
| |
+ -- -- 0x1FFF0000 -- -- +
| ... |
+ -- -- 0x081FFFFF -- -- +
| |
| Flash |
| |
+ -- -- 0x08000000 -- -- +
| ... |
+ -- -- 0x001FFFFF -- -- +
| |
| Alias |
| |
+ -- -- 0x00000000 -- -- +エイリアスメモリは、 BOOT0ピンに応じてフラッシュ、システム、またはRAMメモリを指しています。デフォルトではフラッシュです。
今のところ、2つのスペースが私たちにとって最も興味深いものです。
ただし、実際には、機能部門はさまざまです。たとえば、プログラムバイナリデータを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
...
}Exlを探索するには、ARM-eabi-objdumpコマンドを使用し、アドレスですべてのエントリを並べ替えます。
arm-none-eabi-objdump -t stm32-boot-explained.elf | sortこのプログラムは、マニュアルの詳細に説明されているように出力を生成します。
Address Flag_Bits Section Size Nameコンパイルされたプログラムコードは、フラッシュメモリでこのセクションに移動します。
セクションは、Flashの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の上および_estackまで、すべてのRAMメモリは、ヒープとスタックメモリ専用です。
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で確認しましょう。
(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によって生成された実装を見つけることができます。単にヒープが_estack - _Min_Stack_Sizeに_endから成長することを可能にします。
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 、他のすべての関数と同様に、フラッシュ.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のメモリとの相互作用を理解するために、基本的なデフォルトの実装を調べます。
デフォルトでは、stm23cubemxが提供するstartup_stm32f436xx.s asmファイルでReset_Handlerメソッドが定義されています。このプロジェクトでの実際のbootloader.cの実装は、明確にするためにCで記述されています。
リンカースクリプトで定義されている変数は、Cコードでアクセスできることに注意してください。
extern uint32_t _estack ;そのため、ASMバージョンを簡単に複製できます。
最小限の負荷プロセスは、次の手順に分割できます。
SystemInit()関数)を初期化します.dataセグメント初期化剤をコピーします.bssセグメントを埋めます__libc_init_array()関数を呼び出すmain()関数)を呼び出す手順#1および#4の場合、STM32CUBEMXは機能の実装を提供します。System_Stm32F4XX.Cで詳細を確認できます。
このプロジェクトには、STM32を起動するために必要な最小限のファイルセットがあります。 ARM-None-eabi-objdumpの出力をチェックしてGDBでステップスルーするために、自分で試してみることをお勧めします。
ARM GNUツールチェーンは、プロジェクトを構築するために必要です。
ARM-NONE-EABI-GCC 、 STM32_Programmer_Cli 、およびST-Link_GDBSERVERツールを備えたAll-in-One-One 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