この記事は すごくなりたいがくせいぐるーぷ #GWアドベントカレンダー の 1日目(枠)の記事です。
すごくなりたいがくせいぐるーぷ | GWアドベントカレンダー
1日目にやって書こうと思ってたら執筆中にビルド環境がぶっ壊れて直してたら2日目でしたごめんなさい。
TL;DR
この記事は正直 too longです
- Nucleo-F303K8上で、BareMetalからLチカができます。
- BareMetalをやったことがあり、303k8でのやり方を探している方は、とりあえずYuseiito/stm32-blink見たら全部わかります。
Introduction
Hello,World!
Yusei Itoと申します。
BareMetalを愉しむ系高校生です(←と言いながら先週までNuxt書いてたフロントエンド野郎は私ですごめんなさい)
私は過去に、セキュリティキャンプ全国大会で、BareMetalなRaspberry pi上で動くOSを書くゼミに参加してことがあり、それ以降ベアメタルのコンピュータを時々触って遊んでいます。
この記事では、BareMetal超入門として、初めてのBareMetalな方が BareMetalなNucleo F303K8ボードで、LED Blink(Lチカ)を動かすことを目指します。
BareMetal is 何
BareMetal(ベアメタル)とは、OSやドライバといったソフトウェアを介さずに、本当に何もソフトウェアが書き込まれていない状態のコンピュータを指します。
例えば、今回の場合、便利な Free RTOS,mbedOSのような便利なOS, HAL(Hardware Abstruction Layer)といったものを全く用いずに書いていきます。
組み込みOSの開発などはこの状態から行われます。
この体験を通して、main関数に届くまでに行われていることを理解し、コンピュータ(とりわけARM)のより深い理解ができるはずです。
NucleoF303K8 is 何
NUCLEO-F303K8は、STM32F303K8T6というSoC(System on Chip, 雑に言えばワンチップのコンピュータ)が載ったワンボードマイコンです。
STMicroinstrumentsによる製品です。
国内だと秋月電子通商なんかで手に入ります。
ARMマイコンの一つで、 ARMv7mアーキテクチャのCortex-M4というCPUがチップ内に搭載されています。
今回はこのマイコンで、LEDを点滅させる、いわゆる「Lチカ」を行います。
環境要件
この記事は、以下の環境で検証されています。
- Macbook Pro Late 2013 (2013つらwぴえん🥺)
- MacOS 10.15.3
- Homebrewを使います
$ brew --version
Homebrew 2.2.13
Homebrew/homebrew-core (git revision b8af; last commit 2020-04-28)
Homebrew/homebrew-cask (git revision 511a2; last commit 2020-04-28)
- VSCode(を使ってほしいですこれは布教です)
人間側の要件
- 日本語読解の技能
- C言語に対する一定程度の理解
- 気合い
開発環境構築
gcc-arm-none-eabi
ARM32用のGNUツールチェーンです。
パソコン上でマイコン用のコードをビルドするためのクロスコンパイル環境を作ります。
コンパイラ gcc
、リンカld
、 アセンブラas
、デバッガ gdb
などなどのツールが一気に入ります。
なお、 gcc-arm-none-eabi
はこのツールチェーンの総称、arm-none-eabi-gcc
はコンパイラです。ややこしいので気をつけてください。
$ brew update
$ brew upgrade
$ brew tap ArmMbed/homebrew-formulae
$ brew install gcc-arm-none-eabi
stlink
Nucleoボード上に搭載されたROMライタ兼デバッガであるST-LINKV2のドライバ的なやつです。
使わなくてもUSBドライブに.bin
を放り込めば使えるようにNucleoはできているので、なくてもいいのですが、この記事ではバイナリの書き込みに使ってます。
デバッグするときにGDBサーバとしても使うので、一応入れておくといいと思います。
$ brew install stlink
これで準備は完了です。
参考までに、この記事を執筆している環境は
$ arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907]
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ arm-none-eabi-as --version
GNU assembler (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 2.30.0.20180329
Copyright (C) 2018 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or later.
This program has absolutely no warranty.
This assembler was configured for a target of 'arm-none-eabi'.
$ arm-none-eabi-ld --version
GNU ld (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 2.30.0.20180329
Copyright (C) 2018 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) a later version.
This program has absolutely no warranty.
$ arm-none-eabi-objcopy --version
GNU objcopy (GNU Tools for Arm Embedded Processors 7-2018-q2-update) 2.30.0.20180329
Copyright (C) 2018 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) any later version.
This program has absolutely no warranty.
$ st-util --version
v1.6.0-256-ge87bdff
環境ができたら、早速作業を初めましょう!
なお、この記事は読めばコードの全体像が再現できるようには 書かれていません
各部の解説を優先したかったので、一部の入れ子構造などが不明瞭になっている部分があります。
適宜 リポジトリからコードを読んで理解してください。
条件
この記事では、Cで書けるところはなるべくCで書きつつ 、タイマ等は用いずにできる範囲の 、最小構成でLチカを実現することにします。
本当の最小構成にすればアセンブリだけで書けるのですが、辛いのでなるべくCで書きます。
リンカスクリプトを書く
メモリレイアウトを決める
今回使うSTM32F303K8は、データシートの1ページによれば64KBytesの Flash memory(ROM) と、12KBytesのSRAM(RAM) が搭載されています。
(このほかに、4KBytesのCCMもありますが、必須ではないので本稿では扱いません。)
それらを、C言語を実行する上で必要になるいくつかのセクションに分割します。
section | description | location |
---|---|---|
isr_vector | 割り込みベクタテーブルです。 | Flash |
text | 機械語プログラム本体を格納するセクション。 | Flash |
bss | 初期値のない(または0の)変数 | RAM |
rodata | 定数 | Flash |
data | 初期値のある変数 | Flash→RAM |
heap | いわゆるヒープ領域です。mallocとかするときに取られる部分ですね。 | RAM |
stack | いわゆるスタック領域です。レジスタの内容を保存しておくときに使用される部分です。 | RAM |
- 割り込みベクタテーブルの設定は、最小でやる上では必要ないマイコンもありますが、今回の場合は必要です。理由は後述します。
今回はこれらを、以下のように分割します。
特に、isr_vector
はメモリ上でのアドレスがデータシートで規定されているので、必ず先頭に配置される必要があります。
この記述には、リンカスクリプトを用います。
既存のOSなら勝手に準備されてるものですが、BareMetalなのでこれから準備していきます
。
リンカスクリプトを書く
プロジェクトディレクトリ内に .ld
拡張子をつけたファイルで記述します。
Entrypointを指定する
まず、ENTRY()
ディレクティブを使ってはじめに実行される位置をリンカに伝えます。
/* Entry Point */
ENTRY(_start)
ここでは、あとで設定する _start
というラベルの位置を、entrypointに設定しています。
メモリ領域を設定する
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 12K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 64K
}
次に、メモリの領域を設定します。
領域名(アクセス種類) : ORIGIN = <領域の初めのアドレス>, LENGTH = <領域の長さ>
の形で指定します
リンカスクリプトを書く際は、イコールや演算子の両側に必ずスペースを入れてください。以前それでハマったことがあります。
セクションの分割
次に、前述したセクションの分割をやります。
セクションの記述は、
SECTIONS{
}
の中で行います。
FLASH
(ROM)内の設定
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
.text :
{
. = ALIGN(4);
_stext = .; /* Start of code */
/* text sections (contains code) */
*(.text)
*(.text*)
. = ALIGN(4);
_etext = .; /* End of code */
} >FLASH
.rodata :
{
. = ALIGN(4);
/* rodata sections(contains constants) */
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} >FLASH
.
は、その行が表すメモリ上の位置を表します。
左辺にあればカーソルを動かすようなイメージ、右辺にあればその位置をあるラベルで表すことにするようなイメージです。
ここで、. = ALIGN(4);
はメモリアライメントの設定をしています。
マイコンなのであまり大きな領域があるわけでもないので、今回は4
と小さくしています。
4
の単位はByteです。
*(.section_name*)
のような記述のうち、初めのアスタリスクはそのセクションのデータをここに配置することを表し、二つ目のアスタリスクはセクション名のワイルドカードとして機能します。
リンカスクリプトにおいて、 /* */
は見ての通りコメントです。
それぞれかっこの後に>FLASH
とすることで、MEMORY
で指定したFLASH
の領域上の話であることを示しています。
dataセクションの設定
_sidata = LOADADDR(.data);
.data :
{
. = ALIGN(4);
_sdata = .;
*(.data)
*(.data*)
. = ALIGN(4);
_edata = .;
} >RAM AT> FLASH
data
セクションは、初期値があります(=>ROMで保存しておきたい)が、変数領域なので.rodata
などとは区別して扱われるセクションで、実行時には書き換えが発生します(=>RAM上にいて欲しい)。そこで、 ROM上に持っておいて後からRAMにコピーするという手段を取ります。
そういう取り扱いをすることを表すのが >RAM AT> FLASH
です。
コピーする処理は、後からスタートアップコードとしてアセンブリで書きます。
そのときに、FLASH上の(初期値が保存された)位置が必要になるので、_sidata = LOADADDR(.data);
として _sidata
(Start of Initial DATA)ラベルをつけています。
LOADADDR
は、そのセクションをRAMに読んでくる元になるFLASH上のアドレスを返します。
RAM
上の設定
. = ALIGN(4);
.bss :
{
_sbss = .;
*(.bss)
*(.bss*)
. = ALIGN(4);
_ebss = .;
} >RAM
.heap : {
_sheap = .;
/* 1KiB heap */
. = . + 1024 ;
. = ALIGN(4);
_eheap = .;
}> RAM
.stack : {
_estack = .;
_stack = _estack + 1024;
}> RAM
ここまでのリンカスクリプトを読んできているので、特に難しいところはないと思います。
stack
セクションは無論LIFOですから、アドレスが減る方向に生えていきます。(上に積んでいくため).
それゆえ、先に_estack
がきてから、_sstack
がくるようになっています。
これで、リンカスクリプトは完成です。
スタートアップコードを書く
これまでは設定という感じでしたが、ここからはARMのアセンブリを書いていきます。
といってもアセンブリは大変なので、なるべく減らして、残りは全てCからするように頑張ります。
.syntax unified
.cpu cortex-m4
.fpu softvfp
.thumb
.global _start
一行目から説明が厄介です。
これは、ARMv7mの命令セットとThumb命令が混ざった、統合アセンブリ言語の書き方をするときに使っているようです。私はこの書き方に関しては今回初めて知りました。
.cpu
はプロセッサの指定、.fpu
は浮動小数ユニットの種類を指定しています。
今回はデフォルトの、ソフトウェアを用いた浮動少数方式を用いています。
.thumb
はこれ以降をthumb命令として解釈することを示します。
.global
は外部に公開するラベルであることを示しています。
今回の場合、リンカスクリプトで記述した_start
の位置を外部に公開するために用いています。
ここからしっかりアセンブリになります。
アセンブリ言語そのものの説明はここでは省略しますが、コメントを読んで理解してください。
めちゃくちゃ丁寧につけてます。
2020/ 5 /2追記
ここからのアセンブリ、あまりこなれたコードにはなってません。完全に私のアセンブリ力の不足が現れてます。
非常に有用な指摘をいただいているので、ぜひコメント欄の @fujitanozomu さんのコメントを参考になさってください。私も非常に勉強になりました。
私の「こなれてない」コードは悪い例として残しておきますが、一部書き方の問題ではなく不具合が含まれていた箇所があったため、それに関しては本日記事中でも修正を致しました。
@fujitanozomu さんのご指摘に感謝します。
また、末尾にある資料たち を色々見ると役立つと思います。
.section .text.start /* specify section */
_start:
ldr sp, =_estack
bl data_init /* Initialize data section */
bl bss_clear /* Zero-clear BSS section */
bl main /* Run main() */
LoopForever:
b LoopForever /* Jump to LoopForever */
data_init: /* Initialize data section function */
ldr r1, =_sdata /* load addreses to Rn registers */
ldr r2, =_sidata
ldr r3, =_edata
movs r4, 0 /* Set R4 as 0 */
1:
cmp r1,r3 /* Compare R1 and R3 */
beq 2f /* When equal, jump to foward-closest '2' label */
ldr r5, [r2, r4] /* Load the value at r2(_sidata)+r4 */
str r5, [r1] /* Store the value at r2(_sdata)+r4 */
adds r4, r4, #4 /* Increment offset */
adds r1, r1, #4 /* Increment address */
bl 1b /* Loop. Jump to '1' label closest backward. */
2: /* if r1 and r3 was equal, comes here*/
bx lr /* Return */
bss_clear: /* Clear BSS section function */
ldr r1, =_sbss /* Load address of begin to r1 */
ldr r2, =_ebss /* Load address of end to r2 */
1: /* Label for loop */
cmp r1,r2 /* Compare r1 and r2 */
beq 2f /* If equal, jump to label '2' foward */
movs r3,0 /* store zero to r3 */
str r3,[r1] /* Store r3's value(=zero) to address where r1 points */
add r1, r1,4 /* increment addrsss */
b 1b /*Jump to '1' label backward (loop) */
2: /* Loop exit here */
bx lr /* Return */
.section .isr_vector,"a" /* Specify another section */
.word _estack /* SP(Stack pointer) initial address */
.word _start /* Entrypoint */
.isr_vector
の理解が厄介です。
ここは割り込みベクタで、ある割り込みが入ったときに飛ぶ先を指定しておきます。
メモリの先頭に置かれる領域です。
ARMの場合ははじめがスタックポインタ、次にエントリポイントのアドレスを置くと決まっていて、それ以外は省略しても他の割り込みがかからない限り安全に動かせますが、これらは初めのSP(Stack pointer),PC(Program counter) の内容を指定する上で重要ですので、記述します。1
ここまでで、スタートアップコードが書き終わりました。
ようやくCの世界へ
皆様、お疲れ様でした。
これで、アセンブリやリンカといった楽しいがやたら重い部分はとりあえず終わりです。
Cを使うのに必要な設定が終わったので、これから先はペリフェラルの設定などをCで書いていきます。
なお、この記事はあくまでベアメタル超入門で、C超入門ではないので、細かい文法にはいちいち触れません。特に、ポインタの扱いは極めて重要なので、確認しておいてください。
GPIOの設定
Cの世界ではじめに呼ばれるのは、無論main()関数です。
startupからそう指定したので当然ですね。
今回Lチカするのに使うのは、 PB_3
ポートです。
この緑のLEDにつながっているのがPB_3ポートです。
このことは、NUCLEO-303K8のユーザマニュアルから確認できます。
このmain()関数内で、Lチカまでにやるべきことは、以下のことになります。
- RCC(Reset and Clock Control) の設定からGPIOにクロックを供給
- GPIO本体の設定
これらの設定は、全てあるアドレスのレジスタに値を書き込むようにして行います。
それを楽に実装するため、今回は以下のようなマクロ関数を用意しておきます。
#define mem(ADDR) *((volatile uint32_t *)(ADDR))
これは、アドレスを uint32_t
型のポインタに変換し、その値へのアクセスを 外側の*()で取得しているようなマクロ関数です。
また、各アドレスも先にプリプロセッサ指令で書いてしまいます。
きちんと書くときは構造体などを使うことできれいに書けるのですが、今回は雑にいくので、各レジスタをプリプロセッサで記述します。
#define GPIOB_PORT_BASE 0x48000400U
#define GPIOB_MODER GPIOB_PORT_BASE + (uint32_t)0x00
#define GPIOB_OTYPER GPIOB_PORT_BASE + (uint32_t)0x04
#define GPIOB_PUPDR GPIOB_PORT_BASE + (uint32_t)0x0C
#define GPIOB_ODR GPIOB_PORT_BASE + (uint32_t)0x14
#define RCC_BASE 0x40021000U
#define RCC_AHBENR RCC_BASE + (uint32_t)0x14
*_BASE
というのはGPIOBやRCCといった各ペリフェラルのレジスタの初めのアドレスで、 Boundary AddressとしてSTM32F303K8のマニュアルなどに記載されています。
そして、その Boundary Addresssからのオフセットを加えることで各レジスタのアドレスを表現しています。
そのオフセットは、マニュアルの各レジスタの説明に書いてあります。
の部分でGPIOBの開始アドレス 0x4800 0400
を見つけることができ、
の部分から、 Address Offsetが 0x00
であると知ることができるので、
記述するのは #define GPIOB_MODER GPIO_PORT_BASE + 0x00
というようになります。
ただし、ビット幅によって加算が意図したように行われないと困るので、実際の記述では uint32_t
にキャストしています。
ここまで来れば、あとは各レジスタに値を登録するだけになります。
まずはRCCからGPIOBにクロックを供給します。
mem(RCC_AHBENR) |= (1 << 18);
レジスタの説明によれば、18bit目にIOPBをENableする設定があるので、 18ビット左シフトした1を入れています。それ以外のビットの設定は既存のままにしておくので、 OR論理で |=
としています。
mem(GPIOB_MODER) = (0b01 << 2 * 3); // Set '01' to GPIOB port 3
くどくなるので以降画像を省略しますが、GPIOB_MODER
はGPIOB上の各ピンに対して2bitつづつ対応しているので、2bitの01
を3つシフトしてGPIO3の場所に格納します。
mem(GPIOB_OTYPER) = 0x0000; //All-push oull
GPIOB_OTYPER
は出力部分の回路構成を設定しています。
詳細は 「マイコン プッシュプル オープンドレイン」とかで調べれば出てくると思います。
今回はすべてのpinに対してプッシュプル構成になるようにしました。
下位ビットしか使わないレジスタなので16bit長になっています。
mem(GPIOB_PUPDR) = 0x000000000; //No pullup-pulldown
最後に設定するのはGPIOB_PUPDR
で、プルアップやプルダウンの設定をしています。
今回はどちらにもPullしないことにしています。
これも回路的な話なので、興味のある方はググって解決してください。
いざ、Lチカ
int i=0;
while (1)
{
if (i > 500000)
{
i = 0;
if (mem(GPIOB_ODR) & (1 << 3))
{
mem(GPIOB_ODR) &= ~(1 << 3);
}
else
{
mem(GPIOB_ODR) |= (1 << 3);
}
}
i++;
}
Lチカは、無限ループ内でカウントし、500000
に達したらポートの出力を反転しているだけのシンプルなものです。
これまで読んできたコードに比べればどうってことないと思いませんか?
これをタイマーを使って実装するようにするとまた面白いのですが、今回は最小でやるので割愛です。
Build & Run
先にやっておいた環境構築ができていれば、
% arm-none-eabi-as -c startup.s -o startup.o
$ arm-none-eabi-gcc -c main.c -o main.o -mcpu=cortex-m4 -Os
$ arm-none-eabi-ld -T ldscript.ld main.o startup.o -o kernel.out
$ arm-none-eabi-objcopy kernel.out -I ihex -O binary kernel.bin
$ st-flash write kernel.bin 0x8000000
のようにすることでビルド・書き込みできます。
一行目では、ASを用いてstartup.s
をアセンブルして、オブジェクトファイルstartup.o
を作っています。
次に二行目で、 GCCを使って main.c
をプリプロセス&コンパイルしてmain.o
を作っています。
-mcpu=cortex-m4
を忘れると動作しないので気をつけてください。
三行目では LDを使って main.c
と main.o
をリンクしています。
ldscript.ld
リンカスクリプトも忘れず指定してください。
リンクされると、 kernel.out
が書き出されます。
が、これだけではボードに直接書き込めないので、 objcopyを使って .bin
ファイルを作るのが、4行目です。
最後に st-flash
でkernel.bin
を0x8000000
番地から書き込んで完了です。
0-04-30T19:55:20 INFO common.c: Starting Flash write for VL/F0/F3/F1_XL core id
2020-04-30T19:55:20 INFO flash_loader.c: Successfully loaded flash loader in sram
1/1 pages written
2020-04-30T19:55:20 INFO common.c: Starting verification of write complete
2020-04-30T19:55:20 INFO common.c: Flash written and verified! jolly good!
みたいなログが出れば書き込み完了です。 Lチカがはじまっていることと思います。
おつかれさまでした。
後記
今回は、タイマーも使わないLチカを、書いていてだるくなるくらい丁寧に説明しました。
実のところ、今回のコードにはかなり色々な問題があるのですが、それでも立派にベアメタルで動かしたことに変わりはありませんので、これが初めてのベアメタルの方は喜んでいただくといいかと思います。
高いレイヤの進化がかなり早いのは確かですが、こうした低レイヤの話はなかなか変わりませんので、これからも楽しんでやっていただけると幸いです。
長文をお読みいただき誠にありがとうございました。
What's Next???
- 内部割り込みを使えるようにしましょう
- printfできるようにしてみましょう
- echoできるようにしてみましょう
- そこまでできたらOSとか書いてみるといいんじゃないでしょうか。
私はここにkozosを移植するつもりです。
情報について
正確性に万全を期してはおりません。
ベアメタル超入門を目指して書いた記事ですので、とにかく簡単に理解できて、嘘を言っていない程度に正確なように書いています。
ぜひ、これをやってみてベアメタルに惹かれた方は、他のものできちんと入門されると良いと思います。
建設的な改善の提案や、明らかな誤りの指摘を歓迎します。
ソースコード
文中でも紹介しましたが、GitHubにあります。
Yuseiito/stm32-blink
参考に(した||なるかも)情報
一次情報
ARMやSTM公式の情報。
とりあえずこれを信じてください。
title&link | description | 得られる情報 |
---|---|---|
ARM®v7-M ArchitectureReference Manual | アーキテクチャのリファレンスマニュアル | ARM命令などアセンブリ言語について |
Reference Manual : RM0316-STM32F303xB/C/D/E, STM32F303x6/8, STM32F328x8, STM32F358xC, STM32F398xEadvanced ARM®-based MCUs | SoCのリファレンスマニュアル | 各レジスタのアドレスなど |
Datasheet:DS9866 - STM32F303x6/x8 Datasheet | SoCのデータシート | 記憶域の大きさやアドレスをさくっと把握したいときに |
Nucleo 303K8 User manual | Nucleo Boradのユーザーマニュアル | オンボードLEDに繋がっているピンを把握する |
Cortex-M4テクニカルリファレンスマニュアル | CPUのリファレンスマニュアル | 存在するレジスタについて。特にフラグレジスタなど。アセンブリ命令についても。 |
Cortex-M4 Devices Generic User Guide | CPUのユーザガイド | 上のより平たく書いてある。 |
n>1 次情報
title & link | 得られる情報 |
---|---|
.syntax, コメント、ローカルラベル|デバイスビジネス開拓団 | Thumb命令とは何か、ローカルラベルの意味など |
RaspberryPiでSTM32開発環境の構築 | STM32の開発環境構築。 |
セクションとか.textとか | 各セクションの役割など |
リンカスクリプトの書き方 | リンカスクリプトの書き方。特に ENTRY など初期設定の部分 |
debugging with gdb | GDBでデバッグしていてわからなくなったときの心の支え |
アセンブラ指令 - 2019年度 システムプログラミング |
.word 擬似命令など、アセンブリ言語のディレクティブについて |
フリーソフトウェア徹底活用講座(7) |
.weak 擬似命令などについて |
STM32 bare-metal made easy | 近いことをやっているので流し読みしました。 |
“Bare Metal” STM32 Programming | (同上) |
オペレーション部の記述方法 | CS+ V4.01.00 |
.section 擬似命令の属性について |
STM32 Nucleo Boardスタートアップルーチン | ★特に参考にしました。スタートアップの仕方など |
macOS】ARMのGCCコンパイル環境を構築する(brewから公式「GNU Arm Embedded Toolchain」をインストール) | brewによるgcc-arm-none-eabi環境構築 |
加えて、 @ tnishinaga さんによるセキュリティキャンプの講義資料も復習の参考にさせていただきました。ありがとうございました。
-
なお、正確な割り込みベクタの取り扱いは、 Cortex-M4のマニュアル+STM32F303k8のマニュアルでアドレスの番地と割り込みの対応を、STM32F303K8のデータシートでFlashの先頭アドレスを確認することで情報が得られます。 ↩