スタートアップスクリプトを見てみる

突然ですが、main関数は一体誰が呼んでいるのでしょうか。
普通に考えるとプログラムはROMの0番地から始まりますから0番地にmainがあるのでしょうか。
違います。mainは別の場所にあります。0番地ではない。
実は今まで僕らがマイコンに書き込んできたプログラムには「main関数を呼ぶ別のプログラム」がまざっていて、そいつが最初に動いています。
しかもそいつはクロックの源を決めたり速度まで変更してくれちゃってます。
正体を確かめておきましょう。短いプログラムです。

犯人は誰なのか

CMSISを覚えてるでしょうか。
僕が「CoIDEってすごいよ!」とか騒いでる時に、プロジェクトを作って最初にやったことです。ここです。
ここで外部からプログラムを取り込みました。前回まで使ってたプロジェクトのプロジェクトナビゲーションにも入っています。

stm32f4_project

これらは僕らのmain.cといっしょにマイコンに書き込まれます。
既にstm32f407xx.hについてはレジスタが沢山defineされてるんだよ〜っていう話をしました。
他にもいろいろいますが、大体.hというヘッダファイルですから定義であってプログラムではありません。
ただ、そんな中

  • startup_stm32f407xx.S
  • system_stm32f4xx.c

という連中がいます。上はアセンブリ言語、下はC言語のプログラムです。
実はこのアセンブリがmainを呼ぶ実体です。

startup_stm32f407xx.Sを見る

このアセンブリを開いて見てみるとどうやら上から順番に実行されるわけではありません。
「この辺は0番地にこの辺は100番地に」という番地指定がされているのでややこしくなっています。実際のプログラムは0番地から実行されるわけですから0番地から見てみましょう。

途中を省略していますがこれが0番地からのプログラムです。
実はこれプログラムではないです。
0番地は _eramで指定される32bitの数字を、1番地にはReset_handelrで指定される32bitの数字を置いておくというただの固定値です。
これは割り込みベクターテーブルと言って「割り込みが起きた時にどのアドレスに飛ぶか」を示したものです。他のマイコンを触ったことがある人なら知っているはず。
ここではそれがこんな風に書かれているわけです。
ちなみに最初の_eramというのはスタックポインタの初期値です。
リンク時にスタックポインタの最初のアドレスが決まりそれが_eramとなります。
ARM-Cortex-Mでは0番地はプログラムでなくスタックポインタのアドレスというのは決まっています。起動した直後からスタックを使えるようにするため特別になっています。
さて、ROMの上ののほうが割り込みベクタなのはわかりました。ではマイコンは起動後どう動くかというと

  1. 0番地のアドレスを読みスタックポインタを設定する
  2. 割り込みベクターテーブルのリセットハンドラへ飛ぶ

ここで割り込みベクターテーブルのリセットハンドラとはリセット割り込みがかかった時にジャンプする先のことで、ベクターテーブルの一番上です。つまりResetHandlerのことです。
これはラベルと言ってC言語では関数みたいなもんです。
これはどこなのかとこのアセンブリを調べると上の方にありました

こっちはちゃんとプログラムです。
見えてきましたね。アセンブリが詳しくない人のために解説するとここでやっていることは

  1. 初期値のあるグローバル変数の値をセット
  2. 初期値のないグローバル変数に0をセット
  3. SystemInit を呼ぶ
  4. __libc_init_arrayを呼ぶ
  5. mainを呼ぶ

となっています。そう!下の方にmain!。いや〜、mainを読んでいるところを見つけましたね!
ただ、mainまでにもいろいろやってるみたいです。
まず最初の1と2はRAMに初期値を入れていますね。
変数はRAMなんですが、RAMというのは起動後の値は不明な値が入ってます。0ですらないし、もちろん自分の欲しい値を入れておくためにはどっかでセットしないといけないのです。
グローバル変数(C言語でどの関数からも使える変数)に初期値がある場合はmainが始まっちゃう前にセットしないといけないわけです。
なのでその値はROMにセットされていて(この辺はリンカがやってくれる)起動後のこのプログラムでRAMにセットするようになっています。
そして初期値のないグローバル変数には0を入れる処理が入っています。

SystemInit

グローバル変数の初期化が終わったらmainを呼べるのですが、ここでSystemInitってのを呼んでます。これはsystem_stm32f4xx.cの中にある関数です。
このcファイルはは基本的に「クロックを決める」機能しかありません。
実際system_stm32f4xx.hをみるとこの中にあるのは

  • SystemCoreClock コアのクロック数が保存してあるグローバル変数
  • SystemInit クロックの設定用関数
  • SystemCoreClockUpdate 呼ぶと各種レジスタを読んでSystemCoreClock変数をupdateする

となっています。さっそくSystemInit関数を見てみると

などといろいろなレジスタをいじっていて、いろいろな設定を指定ますが基本は
内臓クロックをメインクロックとして利用する
という設定をしています。

Discoveryボードには外付けの8Mhzクリスタルがあるのに、マイコンに内蔵されているクロックを使うようになってるわけです。そして、この設定が終わったらアセンブリに一度戻ってmainに戻っています。

結局起動するまでにしていること

mainまでの流れを見てみると

  1. (startup_stm32f407xx.S)グローバル変数の値をセット(初期値のあるものと0のもの)
  2. (system_stm32f4xx.c)SystemInitでクロックなどを設定
  3. (startup_stm32f407xx.S)main関数を呼ぶ

となっていたわけですね。
これでCMSISで持ってきたこのプログラムの意味と最初のクロックの設定がわかりました。

そしてどうやらクロックは内臓のものをつかうようになっているようですし、168Mhzでもうごいてなさそうです。
次回はせっかくある外部クロックへの切り替えと168Mhzで動かすための設定をしてみましょう。

まとめ

  • startupスクリプトとしてアセンブリとcのファイルがある
  • アセンブリは割り込みベクターテーブルとグローバル変数の初期化がある
  • cのsysteminit関数がmainの前に呼ばれている

 

コメントを残す