STM32のSystickサンプル
シンプルな構造なので、マイコンプログラムのはじめの一歩によさそう。
一定間隔でLEDの点灯消灯を繰り替えす
一定間隔はDelay関数で。
Delay関数はつまり、whileループである。
whileループから抜けるのはカウンタがゼロになったとき。
カウンタの減算をSystickで行っている。
つまり、割り込みベクタテーブルで宣言しているSysTick_Handler内で
直接アプリコードのタイマ変数を減算している。正確にはmain.cの
TimingDelay_Decrement関数をコールしている。
STM32マイコンのリンカスクリプト
いろいろなところで説明されていることを収集しつつ、考えてみる。
STM32F4discovery(STM32F407VGT6)のリンカスクリプトサンプルがベース。
リンカスクリプトとスタートアップアセンブリは一対。
サンプルアセンブリには、.cpu cortex-m3とある。
F4discoveryはcortex-m4なので、スタートアップアセンブリはm3のままで
動作する、ということか。
FLASH : ORIGIN = 0x08000000とある。これはARMのメモリマップの指針。
32ビットマイコンのメモリアドレスMAXを使うための構造。
SECTIONSの先頭をみると .isr_vector :{}>FLASHとある。
FLASHのORIGINは0x08000000なので、つまり、isr_vectorはFLASH領域の
先頭0x08000000から配置される。
従って、スタートアップアセンブリファイル(以後、.sと略)のどこに .isr_vectorの記載が
あろうと、それはメモリ上の位置との相関関係はない。
.isr_vectorのコメントにはStartup codeとある。これは割り込みテーブル→リセット
ハンドラすなわち、最初に起動するコードということ。
isr_vectorの次に配置されるのが、.text、つまり命令コード。
ここには定数(read only variable)も含まれる。
アセンブラコードをCから呼び出す
アセンブラコードはマイコンの動作を理解するために、さけて通れない道。
さて、手持ちのfrdmk64fでどう学習すればよいのか。
インラインアセンブラというのは、どこかでチラっと読んだ気がするが、
gccによるアセンブルの結果であって、armasmによる純粋なアセンブラコード(*.s)の動作とは異なるものらしい。
それも気になるが、:[xx] "=r" (xx)の構文をそもそも避けたい事情がある。
というのも、IAR EWARMの逆アセンブル結果をみると、グローバル変数が
位置するアドレスを一時的にR0なり、R1なりに突っ込んで動作している事があって、インラインアセンブラでコーディングしているR0, R1の実装を汚染してしまう。
それなら、素直に*.sで用意したらどうだろう、と思った次第。
しかしたぶん、これ、大変だと思う。IAR EWARM以外のfrdmk64fのプログラムダウンロード以外の方法を知らないので、正しいアプローチでないかもだけど、
まずはIARのサンプルとARMのヘルプセンターの情報をもとに、strcopy的な*.sを
IAR EWARMプロジェクトに追加してみた。コードは以下。
PUBLIC strcopy
SECTION .text:CODE:ROOT(2)
//ARM
strcopy:
LDRB R2, [R1], #1
STRB R2, [R0], #1
CMP R2, #0
BNE strcopy
BX lr
END
//ARMとあるのは、Cortex-M系ではそもそもARM32ビット命令をサポートしていない?ので、アセンブラに怒られてコメントアウトしたものである。
IARのサンプルは cp15.sという cortex-A系のコプロセッサを制御するサンプルだった
ので、さもありなんとゆーことか。
ちなみに、ARMのヘルプセンターには
PRESERVE8 AREA SCopy, CODE, READONLY EXPORT strcopy strcopy ; R0 はデスティネーション文字列を指す ; R1 はソース文字列を指す LDRB R2, [R1],#1 ; バイトをロードし、アドレスを更新する STRB R2, [R0],#1 ; バイトを格納し、アドレスを更新する CMP R2, #0 ; NULL 終端文字をチェックする BNE strcopy ; NULL でない場合は続行する BX lr ; 復帰 END
のように、「Cからのアセンブリ言語の呼び出し」には書いてあったけど、そうか、これ、
ARM Compiler 4 and 5 => Version 4.1 日本語版 => ARMコンパイラツールチェーン ARMプロセッサをターゲットとしたソフトウェア開発 => C、C++、およびアセンブリ言語の混用 => Cからのアセンブリ言語の呼び出し
に乗っていた情報であり、ver4系は古いのか?
そのせいか、IARのビルドでは「Bad instruction」が出てエラーとなっていた。
いや違うな、ver5でも、上記と同じサンプルが記載されている。
IARが独自でアセンブラ ディレクティブを削除した、とゆーことか。
呼び出し側は(当然ながら)ver4だろうが、C側なんで問題なく、
extern void strcopy(char* d, const char *s);
const char *srcstr = "First sring - source ";
char dststr[] = "Second string - destination ";
strcopy(dststr, srcstr);
PRINTF(" dst = %s\n", dststr);
でフツーに動いた、やった!!
ちなみにcのグローバル変数には以下のようにアクセスできた。
PUBLIC strcopy
SECTION .text:CODE:ROOT(2)
IMPORT q_ready
//ARM
strcopy:
MOV R3, #1
LDR R0, =q_ready
STR R3, [R0, #0]
BX lr
END
さて、SECTIONについても、IARアセンブラリファレンスガイドを調べてみた。
(くそ、なんで、ARMインフォメーションセンターだけで済まないんだ)
ROOT、NOROOT ROOT(デフォルトモード)は、セクション フラグメントを破 棄してはならないことを示します。 NOROOT は、このセクション フラグメント内のシンボルが参 照されない場合、セクション フラグメントがリンカによ り破棄されることを意味します。通常、起動コードと割 込みベクタを除くすべてのセクション フラグメントで、 このフラグを設定する必要があります。
なるほど、NOROOT(2)に変えておいた。数字はアライン指定らしい。しかしなんで2なんだろう? CprtexM系は32ビットマイコンなのに・・Thumb命令だから?
FRDM-KL25Z その7
スタートアップコードについて、前回少しさがしてみたが、いったん
仕切りなおそう。
まず、IAR向けのnxpサンプルアプリをDLすると、アプリ内にそもそも
スタートアップアセンブリが含まれている。
C:\FRDM-KL25Z\SDK_2.2_FRDM-KL25Z\devices\MKL25Z4\iar\startup_MKL25Z4.s
つまりこういうことではないか。
IARは自身の製品でcortex-M向けのスタートアップアセンブリを公開している。
これをnxpがFRDM向けにカスタムしてアプリサンプルコードに含めている
のではないか。つまりIAR側のスタートアップアセンブリで動作している
わけではない。
そこで、これ、startup_MKL25Z4.sだ。先頭を見ると、
MODULE ?cstartup
とある。モジュール名 cstartupだ。
そこで、IARワークベンチのインストールフォルダ下を "*.s"ファイル、
文字列"MODULE"でグレップしてみると、
以下の4種類しかないことがわかる。
cstartup, cp15, vector_table_vorago, vector_table
flashloaderフォルダ下と、lib下の両方に転がっているので悩むが、まずはlib側
だとしよう。lib/armと、lib/thumbとあり、cortex-Mなのでtumb側だとしよう。
C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.0\arm\src\lib\thumb\cstartup_M.s
さて、両者の違いはどこにあるのか。
1.PUBLIC __iar_program_start / EXTERN __iar_program_start
なるほどこれはわかる気がする。iarのスタートアップモジュールなので、
nxpのサンプルコードにはない、という意味でnxp側がEXTERNなわけだ。
ちなみに、
を参照すると以下のように説明がある。IMPORT
シンボルを無条件にインポートします。EXTERN
シンボルが現在のアセンブリで参照される場合にのみ、インポートします。
なるほど、IMPORTは使わなくってもモジュール定義するし、EXTERNは使う場合
のみ、という事か。別のオブジェクトファイルを参照する、とあるので、その
モジュールがc言語で定義されていようとアセンブリで定義されていようと、
どちらでもよい、という事か。
ちなみに、EXTERNのようなディレクトリの一覧はここ
なぜかここにPUBLICの説明はない。
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.kui0005a/a51_st_public.htm
ここの説明をざっと解釈するとEXTERNと同じだが、別のモジュールで
使用されるのがEXTERNとの違いか?
とはいえ、PUBLICも、同じアセンブリの中で FUNCALL __iar_program_start
している。
ちなみに同ファイルで BL __iar_init_coreとある。FUNCALLとBLの違いは何か?
というか、FUNCALLって何? → 疑似命令らしい。
この記述は、アセンブラの CFI FUNCALL と同じ意味で、マニュアルからは削除されておりますが、
互換性のため残されている記述です。
うむ、しかし、そのCFIって何? IAR ワークベンチのヘルプ→アセンブラ
リファレンスガイド で確認してみると、
CFI => コールフレーム情報、とある。
コールフレームは関数呼び出しのスタックに関するものであり、
「リターンアドレス、局所データ、引数」をひっくるんでコールフレームと
呼ぶらしい。よくわかってないが・・
まぁ、いいや、IARのアーキテクチャではそっちを使うらしい。
nxpのサンプルでは以下なのであまり追及しない。
LDR R0, =__iar_program_start
BX R0
素直にBXで分岐(ブランチ)している。
まぁ、いいや。なんにせよその、__iar_program_startが、iar側のどこにあるのか。
MODULE + "*.s"でサーチして見つからないので、c言語で定義されているのだろう。
C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.0\arm\src\lib\thumb\cstartup_M.c
たくさんヒットするが、どうも↑らしい。前述の cstartup_M.sと同じ場所である。
とはいえ、nxpのサンプルは直接iarのコードを取り込んでいるわけではない。
(ビルドメッセージにこのファイルは出てこない)
なのでたぶん、ライブラリをリンクしているのだろう。
nxpはiarの機能を取り込むため、EXTERN __iar_program_startしているのだろう。
以上!!
なんにせよ、このC:\FRDM-KL25Z\SDK_2.2_FRDM-KL25Z\devices\MKL25Z4\iar\startup_MKL25Z4.s
をいじれば好きなことができるはずだ!!
一旦すべて削除し、わけわかってないが以下のようにしてみた。
MODULE ?cstartup
;; Forward declaration of sections.
SECTION CSTACK:DATA:NOROOT(3)
EXTERN SystemInit
DATA
__vector_table
DCD sfe(CSTACK)
DCD Reset_Handler
__Vectors_End
THUMB
PUBWEAK Reset_Handler
;SECTION .text:CODE:REORDER:NOROOT(2)
Reset_Handler
CPSID I ; Mask interrupts
LDR R0, =0xE000ED08
LDR R1, =__vector_table
STR R1, [R0]
LDR R2, [R1]
MSR MSP, R2
LDR R0, =SystemInit
BLX R0
CPSIE I ; Unmask interrupts
;LDR R0, =__iar_program_start
;BX R0
さて、この実験から何がわかるのか。
(ちなみにMODULEとENDしかないコードだと、*.oが作れないらしく怒られた)
1. main関数はちゃんと呼ばれる。
なぜだろう? __iar_program_startにブランチしていないのに。
2.main関数が無限ループして呼ばれる。
リセットハンドラで__iar_program_startにブランチしないからか、
main関数のPRINTF("xxxxhello world---.\r\n");が繰り返し実行される。
ちなみにサンプルコードは以下。
char ch;
/* Init board hardware. */
BOARD_InitPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
PRINTF("xxxxhello world---.\r\n");
while (1)
{
ch = GETCHAR();
PUTCHAR(ch);
}
GETCHAR/PUTCHARはちゃんと動作していて、ターミナルでキー入力
するとちゃんとエコーバックされる。
どういうことかしら・・・
ちゅーか、だれがmain関数をコールしているのかしら・・
SystemInitを呼ばなくするとどうなるかしら。ウォッチドッグタイマの
制御しかしてなさそうなんだけど・・
うむ、やはり同じ結果になる・・
FRDM-KL25Z その6
さて、FRDM-KL25Zのプログラミングのために、IAR Embedded Workbench IDEを
選んでるが、Eclipseベースのフリーな環境もあるらしい。
今はコード量が問題ではないので、コード制限評価版として IAR を選択した。
至れり尽くせりなのは、FRDM-KL25Zメーカ(nxp)のサイトで、各社ワークベンチ向け
のサンプルコードがダウンロードできるところ。
IAR向けのサンプルでLチカを試すことができた。
さて、それはさておきスタートアップはどうなってるのか。
IARのマニュアルを確認してみる。IARのFAQでスタートアップに関するものがあり、
そこからPDFがダウンロードできる。
PDFの中身を確認すると、
Cortex-M0/M3/M4向けのベクタテーブルが抜き出されている。
IARのインストールディレクトリを確認すると・・・あった。
C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.0\arm\src\lib\thumb\vector_table_M.s
------------------------------------------------------
__vector_table
DCD sfe(CSTACK)
DCD __iar_program_start
DCD NMI_Handler
・・・
------------------------------------------------------
ふむふむ、IAR社のCortexマイコン用スタートアップだから、iar_...なるほど。
ちなみにDCD命令はARMサイトで以下のように説明されている。DCD
ディレクティブは、ワード単位でメモリを割り当てて 4 バイト境界で整列させ、実行時のメモリの初期内容を定義します。
ここでは関数アドレスを示す__jar_program_startをメモリに割り当てている感じか。
このコードの手前に
------------------------------------------------------
EXTERN __iar_program_start
PUBLIC __vector_table
------------------------------------------------------
とあるので、__iar_program_startは別の場所に定義されている。
同じフォルダに、cstartup_M.cがあり、内容を確認すると、あった・・
------------------------------------------------------
void __iar_program_start( void )
{
__iar_init_core();
__iar_init_vfp();
__cmain();
}
そういえばこれ、アセンブラコードとc言語コードの両方の記述が可能であり
(Cortex-Mプロセッサがそーいう風にできている)、
同じ名前のアセンブラファイル cstartup_M.sに以下のように定義されている。
__iar_program_start:
FUNCALL __iar_program_start, __iar_init_core
BL __iar_init_core
FUNCALL __iar_program_start, __iar_init_vfp
BL __iar_init_vfp
FUNCALL __iar_program_start, __cmain
BL __cmain
REQUIRE __vector_table
やってることは一緒である。さて、まず__iar_init_coreはどこで定義されているのか。
cstartup_M.cで以下のように宣言されているので
__weak void __iar_init_core( void );
乱暴に言えばなくても怒られないexternである。
C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.0\arm\src\lib\arm\coreinit.s
ここに定義されているっぽい。アプリ側のフルgrepでは見つからなかったので、
唯一ここにあるだけか?__iar_init_core_v6とあるので、ARMv6アーキテクチャ向け。
MRC/MCRでステータスレジスタを初期化してる感じ。あまり踏み込まないでおこう。
__cmainはどうだろう? ここからmain関数がコールされるので
ユーザアプリは動くわけだけど、さて、pdfに書かれているようなcmain.sが
存在しない。
cライブラリを含めたIARの便利ライブラリをロードしつつ、アプリを動かす
のだとすると、その部分(おそらくCortexプロセッサをどう初期化するか含めて)
IAR資産なのかと推測。
とりあえずリセット割り込みから、Cortexプロセッサの標準的な初期化を行い、LEDチカまでの最小限の流れが追えるとよいのだが、便利なものはシステムが肥大化し、
内部は隠蔽されるの法則か・・
Cで全てを抽象化するなら、Linuxアプリコードを書くのと大して変わらず面白くない・・・
うーむ、このへんにしておこう。
FRDM-KL25Z その5
サンプルコードを確認すると以下のようになっている。
int main(void)
{
char ch;
BOARD_InitPins();
BOARD_BootClockRUN();
BOARD_InitDebugConsole();
PRINTF("hello world.\r\n");
while(1)
{
ch = GETCHAR();
PUTCHAR(ch)
}
}
ハードウェア初期化の部分はともかく・・・
おおおお、普通にC言語だww
シリアル通信でキーボード入力をエコーバックする、という話が
GETCHAR, PUTCHARで抽象化されている。おぉぉぉ。
ふつうだ、まったくもってふつうだwwww
感動ww