HC08マイコンの CPUレジスタの図
アキュムレータ, インデックス・レジスタ,プログラム・カウンタ は
第12回 で、コンディション・コード・レジスタ は 第61回 で説明済み
これまでにも何度か書いていますが 、本来 CPU レジスタというのは C言語を使う上では意識する必要のないものです。 とはいっても、ちょっと困ったような場面ではそういう部分まで足を踏み入れて調べることもあります。 具体例として、「RAMが足りなくてグローバル変数があと 一つ確保できない」 といった場面で、スタック領域が減らせるなら減らして RAM に余裕を作り解決できる場合があります。
「意識する必要がない」 というのと 「知らないから意識しない」 とでは異なりますので、ここでしっかり スタック とスタック・ポインタについて理解しておきましょう。
◆ スタック・ポインタ ( SP )
スタック・ポインタは、RAM のスタック領域(後述) で次に書き込みが行われるアドレスを保持する 16ビット・レジスタです。 リセット時は $00FF に設定されます。
スタック・ポインタは、データがスタックに入れられる(PUSH される)とデクリメント(減少)され、データがスタックから取り出される(PULL される)とインクリメント(増加)されます。
CPU がスタック・ポインタ・リセット(RSP)命令を実行すると、スタック・ポインタの下位バイトが $FF に設定されます(上位バイトは変化しない)。
また、ある種のアドレッシング・モードではスタック・ポインタがスタック上のデータをアクセスする際のインデックス・レジスタとしても機能します(*1)。
◆ スタックって何だろう?
先にスタック・ポインタの説明をしましたが、ここであらためてスタックについて説明しておきます。
スタックというのはメモリ構造の一種で、必要に応じて用意したある大きさをもっています。 その使い方には特徴があり、スタックに保存したいデータを順次入れていくと、その順番どおりに積み重ねるようにしてデータが格納されます。 そして取り出すときは逆の順番、つまり最後に入れたものから順に取り出されます。 いわゆる LIFO(ライフォ、リーフォ: Last In, First Out) または FILO (ファイロ、フィーロ: First In, Last Out) と呼ばれる構造の代表的な応用例です。
スタックへのデータの出し入れに際しては、一般的なメモリ・アクセスとは異なりアドレスでは管理しません。 常に最新のデータ位置に対して読み書きが行われるわけです(*2)。 イメージ的には下図のような感じです。
ちなみにスタック(stack) というのは積み重ねるという意味があります。 イスなどのスタッキングと言ったほうがなじみがあるかもしれません。
スタックに入れたデータのイメージ図
最後に入れた物から見る(取り出す)ことができる
なお、スタックに溜めたデータをすべて読み出さなくても、途中まで読み出した段階でまた別のデータを追加して入れていくこともできます。
◆ スタック領域について
今までの説明で出てきたハードウェア資源とは別にスタックというものがあるわけではありません。 スタックの実体は RAM の中に確保した連続する領域 であり、このマイコンではリセット後に $00FF 番地からアドレスの小さいほうに向かって用意されます。 下図で見ると、$00FF と書かれたところから上に向かってスタック領域として使われます。
それでは $00FF からアドレスの小さいほうに向かってどこまでがスタック(スタック領域)なのでしょうか? 実は、それはハードウェアでは管理していないのです。 作成するプログラム側の責任において、あるサイズまでをスタックとして使用するように 「自己管理」 しなくてはなりません。 とはいっても、完全にユーザまかせにするとスタックの管理がうまくできないことが多いでしょうから、コンパイラで 「ここまで」 と指定できるようになっています(*3)。 CodeWarrior で QY4A の C言語プロジェクトを新規作成した場合、デフォルトで $30 バイト(48バイト)の RAM がスタック領域($00D0 ~ $00FF番地)として割り当てられます。 このサイズは必要に応じて変更することもできます(下図を参照)。スタック・サイズは Project.prm ファイルで変更できる (クリックで拡大)
このようにスタックで使うサイズをあらかじめ決めておくことで、自由に使うことができる RAM のサイズが決まるようになっています。 この例では 128 - 48 = 80バイトが自由に使える RAM ($0080 ~ $00CF番地) ということになります。 もしもプログラム内で 80バイトより多い変数を定義した場合は、コンパイラがリンク・エラーを出します。
#include <hidef.h>
#include "derivative.h"char datablock[ 81 ];
void main( void ){
datablock[ 0 ] = 0;
for( ; ; ){
__RESET_WATCHDOG( );
}
}
上記プログラムで確認してみます。 QY4A ではこのままだとリンク・エラーになりますが、変数の配列を 81バイトから 80バイトに変更するとエラーが出なくなります。 また、81 バイトのままでも前述の方法でスタック・サイズを減らすとエラーが出なくなります。 これは非常に簡単な例でしたが、もっと複雑なプログラムになるとユーザが明示的に指示していない変数をコンパイラが用意する場合もあるでしょう。 そのときは自由に使える RAM サイズがもっと減ることも考えられます(コンパイラ次第です)。
ところで、もしも繰り返し処理などで際限なくスタックにデータを入れ続けるとどうなるでしょう? もともと多くはない RAM を底からどんどん食いつぶしていって、普通の変数などが入っているところまで侵食してしまいます。 そうなるとプログラムは異常な動作をしてしまいますから、大変な問題です。 そうならないようにスタック・サイズはゆとりを持って設定しましょう。
ここで 「それならスタック領域と普通の RAM を別々の場所にもたせた構造のほうが安心じゃないの?」 という疑問を持つ方もいるでしょう。 しかし、スタックを別に設けたとしてもそのサイズはやはり有限なので、そこにデータを入れ続けると結局いつかは入りきらなくなります。 すると最初に入れたデータまたは最後に入れたデータが消えてしまうでしょうから、データをどんどん出し入れするうちに正しくないデータを読み出すことになり、やはりシステムとしては異常になってしまいます。 結局、用意したスタックのサイズに収まる範囲でデータを出し入れしなければならない、という点はどちらの構造でも同じと言えます。
次回はスタックの使用例の予定です。
(*1) 機械語とアセンブリ言語のニーモニックの種類は、かならずしも一対一に対応していません。 同じ操作を行う命令でも、その対象の性質によっていくつかの指定方法(アドレッシング・モード)が選べる場合があります。 SP1 および SP2 アドレッシング・モードでは、スタック・ポインタの示す位置から数えて何バイト目のデータ、という具合に指定することができます(詳しくはデータシートを参照)。 その際、スタック・ポインタは変化しません。
(*2) 「常に最新のデータ位置に対して読み書きする」 と書きましたが、(*1) で示したように例外的な方法もあります。 しかし基本的なことからしっかり理解することが大切なので、本文ではあえてこのような書き方にしています。
(*3) 本文では C言語の場合を説明しました。 ここでアセンブリ言語の場合を説明します。 プロジェクトの新規作成をするときによく見ると、Absolute Assembly と Relocatable Assembly の二種類があり前者の場合はスタック・サイズの指定はありません。 後者の場合は、C言語とまったく同じ方法で指定できます。 デフォルトで $30 バイト(48バイト)というのも同じです。
『参考文献』
「試しながら学ぶHC08マイコン入門」 (CQ出版)
第1章 マイコン電子工作を始めよう
第10章 統合開発環境CodeWarriorを使ってみる
筆者のホームページ 『マイコン工作の実験室』
組み込みエンジニア KAWANO
