先週は、アドレッシングの話で、#、!、$が飛交い、少し 構造化プログラム
混乱していた哲さん。今週も、また様々な記号に悩まされる
ような気がして、足取りが少し重いのです。しかし、ロボは
元気いっぱいで、哲さんを引っ張っていきます。腹を決めて
今日も哲二さんの部屋のドアをノックするのでした。
哲 :こんちは。先週は、アドレッシングの話だったなあ。 今週も同じかと思うと、少し気が重いぞ。 啓二:そうかなあ、慣れの問題だと思うけれど。 文系なんだから、言葉や記号については、違和感ないだろう。 哲 :いや、文章や日常使い慣れていることなら、問題ない。が、マイコン のアセンブリ言語ときたら、やたら決め事が多くてさ。 啓二:まあ、コンピュータは融通が利かないから、しょうがないなあ。 アセンブリ言語である程度話を進めるが、苦しいようなら、Cに変えるよ。 哲 :その方がありがたい。ところで、今日の話は何だ。 啓二:構造化プログラムだよ。機械に近いレベルのアセンブリ言語だと、構造を しっかり造ってからコーディングしないと、滅茶苦茶になってしまう。 そこで、構造化プログラムの技法を、最大限に利用するのさ。 哲 :そうか。構造化プログラムでなくて、オブジェクト指向プログラムには まだまだ遠いのかな。 啓二:やってやれないことはないが、オブジェクト指向プログラムはメモリを 大量に消費するから、マイコン向きではないのさ。 マウスとの情報交換に、オブジェクト指向プログラムは、大げさだし、 構造化プログラムで充分さ。何でも、オブジェクト指向でプログラム する必要はない。F1カーを、渋滞の道で走らせるようなこともない。 まず、質問だ。適正プログラムとは、何だ。 哲 :適正プログラムだって。それは、入口1カ所、出口1カ所の構造を もったプログラムのことだ。 啓二:次の質問だ。適正プログラムの中で利用されている制御構造は、何だ。 哲 :順次(sequence)、選択(selection)、反復(repeatition)の3つだ。 啓二:じゃあ、構造化定理は、どういうものだ。簡単に、述べよ。 哲 :適正プログラムは、順次、選択、反復の3制御構造だけで記述可能である。 こんな程度かな。これが、アセンブリ言語でプログラムすることと関係が あるのか。 啓二:大あり食い虫だ。順次、選択、反復の3つを利用してプログラムすると 非常にわかりやすいのさ。冗長な部分を、減らせることもある。 哲 :そうか。それならば、何か例で示してくれよ。 啓二:いいよ。それじゃ、ハードウエアをテストするプログラムを作ってみよう。 これに、構造化プログラムを適用してみるか。 まずは、出力から調べてみることにしよう。 哲 :そんな単純なことに、構造化プログラムを適用するのは、大げさじゃないか。 啓二:アセンブリ言語で、複雑な内容を作成しても、今のお前じゃ消化不良になる だろう。だから、簡単な例で説明するんだよ。 哲 :何か、騙されているような気がしないでも。 啓二:騙してなんかない。ポート2の4ビットにLEDを接続していると仮定して LEDを順次点灯する。要するに、半田付けが正しいかを確認してみる。 哲 :じゃあ、まず仕様を作るか。 これで、どうだ。 啓二:いいんじゃない。最初にすべきことは、ポート2を出力に設定すること。 そして、全LEDを消灯しておくことだ。ただし、ポートの方向初期化 と値設定は、Appliletにパラメータを渡せば、必要なコードを生成して くれる。だから、不要だ。 哲 :VC++のWizardみたいだなあ。 啓二:あれよりも賢いさ。次に、どう動かすかだ。 哲 :ええっと、4つのLEDを右端から左端に点灯していき、戻ってくる。 これを繰返せばいいな。 啓二:入口1カ所、出口1カ所にするには、どうしたらいいんだ。 哲 :入口では、右端のLEDを点灯して、他は消灯だな。 0を点灯、1を消灯とすると、0001をポート2に出力する。 右端LEDの一つ左を点灯しておいて、出口に向かう。 啓二:これで、適正プログラムのカタチにはなった。次に、中をどう構成するか。 順次、選択、反復のみで実現するんだ。 哲 :半田付けが確実かどうかを調べるんだから、繰返し、即ち反復だな。 なら、出口と入口を結んで、ループを構成すればいい。 フローチャートもどきを作ってみるか。 初期化 ポート2に、0001を出力 ポート2に、0010を出力 ポート2に、0100を出力 ポート2に、1000を出力 ポート2に、0100を出力 ポート2に、0010を出力 もどる 啓二:フローチャートもどきにすると、順次と反復だけを適用しているなあ。 選択も使ってみようぜ。 哲 :選択を使うならば、変数が必要になるな。変数を0〜5の範囲で動かして 変数値に対応したビットパターンを取出す。それをポート2に出力すると できあがるな。 啓二:どうやら、構造化プログラムになったようじゃあないか。 もう少しフローを美しくしてみよう。 哲 :そうだな。順次、選択、反復を利用したカタチにしたいよな。 step 0 変数iに対応したビットパターンを取出す step 1 変数iを+1する step 2 変数iが6ならば、0とする step 3 step 0に戻る 啓二:ここまで出来れば、ゆっくりとアセンブリ言語で記述する。 哲 :わからないことがあるんだ。いいかな。 啓二:何だ。 哲 :まず、ビットパターンを確保するには、どう書けばいいのかな。 啓二:データセグメント内に、ディレイティブを使って、列挙すればいい。 C言語の配列では unsigned char ledtab[6] = { 1,2,4,8,4,2 }; アセンブリ言語では、こんな風にだ。 DSEG LEDTAB: DB 0001b ; 0 DB 0010b ; 1 DB 0100b ; 2 DB 1000b ; 3 DB 0100b ; 4 DB 0010b ; 5 データセグメントの宣言は、DSEGを利用する。この後は、新たにセグメント を定義するまで、有効になる。 ラベルLEDTABで、ビットパターンテーブルのエントリポイントを指定する。 哲 :「:」(コロン)は、ラベルならば、必須なのか。 啓二:そうだよ。これ以降は、DB(Define Byte)で、1つ1つビットパターンを 書きこんでいけばいい。「;」(セミコロン)から右は、コメントとして アセンブラは無視する。だから、エントリポイントからのオフセットを コメントにしておいた。 哲 :数字列の最後にある「b」は、2進であることを示すんだな。 啓二:そう。先週も話したが、hが16進、bが2進、何もつけないと10進になる。 4ビットして定義しなかったけれど、上位は、アセンブラが補足するんだ。 哲 :わからないことの2つ目。変数は、メモリ上にラベルを利用して確保するのか。 啓二:今回は、必要ないよ。レジスタを変数として利用すればいい。 哲 :そういう方法もあるんだ。アセンブリ言語でも、結構柔軟性があるなあ。 啓二:少なくともプログラミング言語なんだから、それくらいの柔軟性がないとね。 そうそう78kの場合、算術、論理演算に利用するレジスタは、Aレジスタ に限定していると考えた方がよい。命令の一覧表があれば、一目でわかるが ないから、注意しておくよ。それじゃ、コーディングしてみるか。 哲 :よし、やってみるか。 step 0 変数iに対応したビットパターンを取出す この部分のコーディングになるな。ビットパターンは、テーブルに入っている から、テーブルのアドレスを設定する必要があるなあ。 MOVW HL,#LEDTAB としておけば、いいんだな。 で、変数iは、レジスタBに割当てるとすると、HLにBの値を加算する。 Bは、0〜5までの範囲だから、レジスタLにBの値を加えればいいんだ。 おい、加算命令ってADDか。 啓二:そうだよ。それで、やってみなよ。 哲 :簡単、簡単。ADD L,Bでどうだ。 啓二:ブー!ADD命令の転送先は、レジスタAでないとならない。 哲 :そんじゃ、一度レジスタLの値をレジスタAに転送して処理するんだ。面倒。 MOV A,L ADD A,B MOV L,A これでいいだろう。 啓二:ピンポン。HLレジスタペアが、変数iに対応したビットパターンを格納した アドレスをもつから、データを取出しておかないとならない。 それは、MOV A,[HL]とする。 次に、ポート2に出力するには、MOV P2,Aと書けばいい。 じゃあ、全体を書いてみる。 哲 :よし。 step 0 変数iに対応したビットパターンを取出す MOVW HL,#LEDTAB MOV A,L ADD A,B MOV L,A MOV A,[HL] MOV P2,A 啓二:あっと、忘れていた。回路図を見て、0を出力して点灯ならば、ビットを反転 しないとならない。ビット反転には、排他的論理和を利用する。ポート2へと 出力する前にビット反転する。XOR A,#0fhとすればよい。 哲 :じゃあ、この命令を入れて、全体は、こうだな。 MOVW HL,#LEDTAB MOV A,L ; HL <- HL + offset ADD A,B MOV L,A MOV A,[HL] ; get bit pattern XOR A,#0fh ; invert MOV P2,A 啓二:次の処理は、変数のインクリメントだな。 先週話したように、便利なincがある。これは、レジスタBでも使える。 哲 :よし。 step 1 変数iを+1する INC B となるなあ。 次は step 2 変数iが6ならば、0とする で、6と一致するかどうかか。これもレジスタAに入れて 比較しないとならないのかな。 啓二:そうだ。比較には、cmp命令を使う。 哲 :それならば、こうするか。 step 2 変数iが6ならば、0とする MOV A,B ; A <- B CMP A,#6 比較命令のときは、内部で減算しているようだな。 ああ、そうか、ここでPSWのZフラグを利用するんだな。 啓二:そういうことだ。Zフラグの値で、分岐先を決めてしまう。 分岐先は、ラベルを利用して、そこに移動するんだ。 分岐命令は、一致したときZフラグがセットされるから、BZを 不一致のときは、BNZを利用する。 ラベルは、アドレスにつけた名札だから、$記号を前につけて指定する。 Bは、Branchの頭文字からとったんだ。 哲 :じゃあ、こんな風にするのか。 MOV A,B ; A <- B CMP A,#6 BNZ $LOOP_END LOOP_END: 啓二:うまい、うまい。でも、レジスタBをクリアする命令がないぞ。 哲 :おっと、分岐命令に気を取られていた。 MOV A,B ; A <- B CMP A,#6 BNZ $LOOP_END MOV B,#0 ; B <- 0 LOOP_END: 啓二:レジスタBの値が確定したところで、元に戻ればいい。 これもラベルで、戻る場所を指定する。 MOV A,B ; A <- B CMP A,#6 BNZ $LOOP_END MOV B,#0 ; B <- 0 LOOP_END: BR $LOOP_START 哲 :全体をまとめてみるか。 MOVW HL,#LEDTAB ; LOOP_START: MOV A,L ; HL <- HL + offset ADD A,B MOV L,A MOV A,[HL] ; get bit pattern XOR A,#0fh ; invert MOV P2,A ; INC B ; MOV A,B ; A <- B CMP A,#6 BNZ $LOOP_END MOV B,#0 ; B <- 0 LOOP_END: BR $LOOP_START 啓二:ラベルLOOP_STARTが入口、ラベルLOOP_ENDが出口。確かに、 適正プログラムになっている。で、順次、選択、反復もある。 今日は、ここまでにだな。 哲 :疲れたビー。ビールが飲みたい。 啓二:まったく、もう。お前、体にではなく、頭に栄養を入れろよ。