« 78KマイコンでLED温度計  | メイン | OSBDMで使うデバッグ・モード導入装置 (5) »

HC08マイコンの使い方QY4A編 -
《71》スタック・ポインタとスタック領域(2)


 シミュレータを使ってスタック・ポインタ(SP) の動きを確認します。 最初はアセンブリ言語を使います。
 

hc08_stack01.gif

 
◆ スタックへの PUSH, PULL を行う
 スタック・ポインタ(SP)  は CPUレジスタの一つですから、その動きを確認するために少しだけアセンブリ言語を使ってみることにします。

 命令を一つずつ単独で動かすより、ちょっとしたストーリがあったほうがイメージがつかめると思います。
 「 H:X レジスタを使用中、一時的に H:X レジスタを他の用途に使いたくなったと仮定して、いったん H:X の内容をスタックに保存してからほかの操作を行い、その後またスタックから H:X の内容を復帰させる」 というストーリで進めてみることにします。
 

22      LDHX    #$1234
23      PSHX
24      PSHH
25
26      LDHX    #0    ; ← 簡単ですが 「ほかの用途」 。
27
28      PULH
29      PULX

 便宜上行番号を付けていますが、ソース・コードには書かれていません。

 このプログラム例と、次に示すプログラム例を合わせたプロジェクトを hc08_stack01.zip として用意しています。 この後の説明に従って、実際にステップ実行で動かすことによりレジスタや RAMの変化を確認してみましょう。

 CodeWarrior で hc08_stack01 を開き、Debug ボタンをクリックするとシミュレータ画面が開きます。 右下の Memory ペインの中で右クリック、【Address...】 を選択してください(一番上の画像)。 そこで半角英数で F0 と入力します。 【Hex Format】 にチェックが入っているのを確認して、【OK】 をクリックします。 すると Memory ペインに $00F0 ~$00FF あたりが表示されます。 最初の時点では RAM の内容が不定(未定義)のため、uu と表示されているはずです。

 ここで、CPUレジスタの値を確認しておいてください。 右上の Assembly ペインを見ると、最初の命令は $EE00 番地から始まっています。 ですから PC の値が EE00(16進数) になっているはずです。 SP の値は初期値 FF ($00FF の意味) なので、次にスタックへデータを格納するときは $00FF 番地に入れられるということがわかります。

 Single Step または Assembly Step ボタンをクリックして、1命令だけ実行してください。
22行目 LDHX  #$1234  を実行しました。(H:X に即値 $1234 を格納の意味)
H:X レジスタに $1234 が入りました。
PC は次の命令のある $EE03 を示しています。

 同様にして、ステップ実行で1命令だけ進めてください。
23行目 PSHX  を実行しました。 (X レジスタをプッシュする、の意味)
X レジスタが $34 なので、スタックに $34 という値が積まれます。
Memory ペインの $00FF 番地に $34 が入ったことを確認します。
そのとき、SP の値は一つ減らされて $00FE になっています。
つまり、次にスタックへデータを格納するときは $00FE 番地に入れられるということがわかります。

 ステップ実行で1命令だけ進めてください。
24行目 PSHH  を実行しました。 (H レジスタをプッシュする、の意味)
H レジスタが $12 なので、スタックに $12 という値が積まれます。
Memory ペインの $00FE 番地に $12 が入ったことを確認します。
そのとき、SP の値は一つ減らされて $00FD になっています。

 ステップ実行で1命令だけ進めてください。
26行目 LDHX  #0  を実行しました。(H:X に即値 0 を格納の意味)
H:X レジスタに $0000 が入りました。

 ステップ実行で1命令だけ進めてください。
28行目 PULH  を実行しました。 (H レジスタにプルする、の意味)
この命令を実行する時点で SP の値が $00FD だったということは、最後に入れたデータは $00FE 番地にあるという意味です。 $00FE の内容は $12 なので、H レジスタに $12 が入ります。 つまり H:X レジスタは $1200 になります。
Memory ペインをよく見ると、$00FE 番地の $12 が消えてなくなるわけではない、ということが理解できます。
スタックからデータを一つ取り出したので、SP の値は一つ増えて $00FE になっています。

 ステップ実行で1命令だけ進めてください。
29行目 PULX  を実行しました。 (X レジスタにプルする、の意味)
この命令を実行する時点で SP の値が $00FE だったということは、次に取り出すデータは $00FF 番地にあるという意味です。 $00FF の内容は $34 なので、X レジスタに $34 が入ります。 これで H:X レジスタは $1234 となり、復帰できました。
Memory ペインを見ると、$00FF 番地の $34 が消えてなくなるわけではない、ということが理解できます。
スタックからデータを一つ取り出したので、SP の値は一つ増えて $00FF になっています。


 このように、複数のデータをスタックへ退避・復帰させるときは、プッシュしたときとは逆の順番でプルしてやる 必要があります。 大事なポイントなので覚えておきましょう。

 

◆ サブルーチンの呼び出し、復帰を行う
 C言語を使っていると、よく関数というものを使います。 ある処理を行う ”ひとまとまり” のプログラムを関数(function) という形でまとめておけば、必要なときにほかの場所から呼び出して使うことができるという便利なものです(本当はもっと便利な使い方もあるのですが)。
 アセンブリ言語でも、同じようなことができます。 ある処理を行う ”ひとまとまり” のプログラムを サブルーチン という形でまとめておけば、必要なときにほかの場所から呼び出して使うことができます。
 

subroutine_image.gif このイメージ図では1回しか呼び出していませんが、プログラム内で同一の関数(またはサブルーチン)を何度も呼び出すことも可能です。

 HC08 のアセンブリ言語でサブルーチンを呼び出す命令は JSR 開始アドレス です。 そして、サブルーチンの最後には、必ず 呼び出し元への復帰命令 を書いておきます。 この命令は RTS です。
 おや? RTS 戻り先アドレス にしなくてもよいのでしょうか? はい。 サブルーチン側としては、プログラムのどこから呼び出されるか前もって把握できませんから、戻り先アドレスを記述することはできないのです。 それに、もし書いてしまっていれば、二か所以上から呼び出されるときにどこへ戻ればよいかわからなくなってしまいます。

 というわけで実際には、サブルーチンを呼び出す命令を実行すると、戻り先のアドレス(2バイト)をスタックに積んでからサブルーチンの先頭アドレスへジャンプ するようになっています。 一方サブルーチンの最後には、必ず復帰命令を置きます。(*1) この復帰命令を実行すると、スタックから 2バイトを取り出して PC に格納、つまりそこへジャンプする という仕組みになっています。 よくできていますね!

 下記の非常に短いプログラムを使って CPUレジスタの動きを確認しましょう。 このプログラムは、先ほどのプログラムの続きになっています。

 あまり意味はないのですが 「 プログラムでアキュムレータをゼロクリアして、呼び出したサブルーチンでアキュムレータに何らかの値を設定し、呼び出し元に復帰してからアキュムレータをインクリメントする」 というストーリで進めます。
 

31                CLRA
32                JSR     subroutine01
33           
34    mainLoop:
35                INCA
36                BRA    mainLoop
37
38    subroutine01:
39                LDA     #$AB
40                RTS

 

先ほどのプログラムの続きとして操作を説明します。

 ステップ実行で1命令だけ進めてください。
31行目 CLRA  を実行しました。(A をゼロクリアの意味)
A レジスタは現在 0 になっています。
PC の値は次の命令の位置 $EE0B を指しています。
SP の値は $00FF のままです。

 ステップ実行で1命令だけ進めてください。
32行目 JSR  subroutine01  を実行しました。(下図クリックで拡大)
     (subroutine01 をサブルーチン・コールするという意味)
スタックに $EE0E という 2バイトの値が格納されました。(*2) これはサブルーチン呼び出し命令の次に置かれている命令のアドレスです。 サブルーチンから復帰してくるときに、ここへ帰ってきます。
SP の値は 2減って $00FD です。 先ほどの PUSH、PULL の実験で書き込まれた内容は消えてなくなっています。
サブルーチンの先頭アドレスにジャンプしたので、PC の値が $EE11 になりました。
 

hc08_stack02.gif

 
 現在サブルーチン subroutine01 の先頭です。
 ステップ実行で1命令だけ進めてください。
39行目 LDA  #$AB  を実行しました。(A に即値 $ABを格納の意味)
A レジスタに $AB が入りました。
PC の値は次の命令の位置 $EE13 を指しています。
SP の値は $00FD のままです。

 ステップ実行で1命令だけ進めてください。
40行目 RTS  を実行しました。(呼び出し元に戻るという意味)
スタックから 2バイト分の内容 $EE0E が読み出されて PC に格納されました。 つまりこれでもう呼び出し元に戻ったということです。
SP の値は 2つ増えて $00FF になっています。
A の値はサブルーチンの中で設定した $AB のままです。

 ステップ実行で1命令だけ進めてください。
35行目 INCA  を実行しました。(A をインクリメントの意味)
A の値が 1増えて $AC になりました。


 アセンブリ言語を使ったスタックの実習はここまでです。 次回は C言語を使って SP, PC など CPUレジスタの動きを確認します。 ここまでの内容を充分理解しておいてください。


(*1) C言語の関数では、プログラムの最後に復帰命令を書きません。 しかし、生成されるアセンブリ言語または機械語のコードとしては復帰命令が必要なので、コンパイラが自動的に生成するようになっています。 C言語では関数の最後などで return 文を書けるようになっていますが、これは本文で説明している復帰命令の概念とは少し異なり、なんらかの値を関数の呼び出し元に返して復帰するというものです(値は省略可能)。 ちなみにパソコン用の C言語で見かける exit 文というのはプログラム全体を終了するときに使うもので、マイコン用のプログラムでは通常 exit 文は使いません。

(*2) このときスタックの中身を見ると、$00FE 番地に $EE 、$00FF 番地に $0E が入っています。 このようにして $EE0E のような複数バイトのデータを格納するとき、アドレスの小さい位置に上位のバイトを格納する方式を ビッグ・エンディアン といいます。 JAVA 仮想マシンや昔ながらのモトローラ系プロセッサはビッグ・エンディアンです。 ちなみにこの反対を リトル・エンディアン といい、インテルのプロセッサなどで採用されてきました。

 

 『参考文献』
「試しながら学ぶHC08マイコン入門」 (CQ出版)
  第1章  マイコン電子工作を始めよう
  第10章 統合開発環境CodeWarriorを使ってみる
筆者のホームページ 『マイコン工作の実験室』

組み込みエンジニア KAWANO

カテゴリ:

トラックバック

このエントリーのトラックバックURL:
http://www.eleki-jack.com/mt/mt-tb.cgi/3854

コメントを投稿

(いままで、ここでコメントしたことがないときは、コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。承認されるまではコメントは表示されません。そのときはしばらく待ってください。)

カテゴリ

会社案内
情報セキュリティおよび個人情報の取り扱いについて

コメントとトラックバックは、spamを予防するために、編集担当が公開の作業をするまで非公開になっています。
コメントはそれぞれ投稿した人のものです。

Powered by
Movable Type 4.1