ステートマシンで簡単にプログラム

前回、構造化プログラムでポート2に接続したLEDを点灯
するプログラムを作成した哲さん。今日は、Appliletを使い
アセンブル、リンクまでするのかと思い巡らしながら、歩き
啓二さんの部屋の前までやってきました。ロボは、おすわり
で、部屋のドアが開くのを待っています。ドアをノックして
ノブを回し、部屋へと入ります。
哲  :押忍。来たぞ。今日は、Appliletを使うのか。
啓二:いや、まだだ。
哲  :先週作ったプログラムを、動かしてみたいんだがなあ。
啓二:慌てるなって。前回のプログラムを、別の視点で眺めてみるのさ。
      あのプログラムは、ステートマシンを使っていることになる。
哲  :ステートマシンって何だよ。
啓二:ステートの意味は、状態。その状態を、遷移させて、プログラムに
      なるからステートマシンさ。ある状態になったら、状態に与えられた
      動作をする。ステートマシンは、シーケンサと呼んでもいいが。
哲  :シーケンサねえ。そう言えば、マイクロプログラムのときにも、そんな
      言葉が出てきたぞ。
啓二:そうだなあ。ステートマシンは、どちらかというと、ハードウエアよりの
      用語かも知れない。シーケンサならば、意味はわかるだろう。
哲  :わかる、わかる。ラダーシーケンサを少し勉強したからな。
啓二:よし、よし。じゃあ、前回のプログラムを見てみよう。

          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

哲  :あれれれ、1週間たったら、何をしているのか、わからなくなったぞ。
啓二:だろう。そういうときのために、ステートマシンを図で描いておく。
      特にアセンブリ言語で書くとプログラムは、わからなくなる。
哲  :何をやるのかは、仕様でわかっているのに、コードが理解できない。
啓二:じゃあ、ステートマシンをかくか。


      これで、わかるようになっただろう。
哲  :うん、うん、良くわかるぞ。ビットパターンを用意して、それを
      ポート2に順次送っていくんだ。そして、全ビットパターンを送
      り終えたら、また最初から同じ動作を続けるんだ。
啓二:ステートマシンを使うときには、各ステートに数値を与えて、その
      数値を順番にたどっていく。そして、ある数値から最初に戻る。
      これならば、動作を追加、削除も簡単だ。
哲  :装置のボタンを押して、指定の動作を間違いなく実行させたい
      とき、ステートマシンが便利だな。
啓二:マイコン応用装置に、大抵、ステートマシンが入っている。
      マイコンを使わないで、CPLDやFPGAを利用した装置でも、
      ハードで、ステートマシンが入れていることが多い。
哲  :じゃあ、もう一度ステートマシンの観点で、LED点灯テスト
      プログラムを見てみるか。
啓二:よし。まず、ステートマシンを構成するときには、ステートを
      表現する変数が必要なんだ。上のアセンブリ言語プログラムなら
      コメントに入れてあるoffsetが、ステート変数だ。
哲  :offsetで、状態を記述してあるから、対応動作を決められる。
      コンピュータは、数値しか扱えないから、この方法が無駄がないね。
啓二:で、ステート変数に対応した動作を記述する。詳細は、後で
      ゆっくり考えればいい。動きを分解して、その動作を記述する。
      機械的な変換作業にブレークダウンできるから、バグも出にくい。
哲  :ステートマシンは、システムに唯一なのか。
啓二:いや、そんなこともないよ。自分の経験では、3つ程度の階層
      構造にして、層によっていくつかステートマシンを用意してもよい。
      層構造を取れば、そのステートマシンが実現する範囲が明確になる。
      通信のプロトコルスタックを実現するときには、OSIは7層用意
      しているよな。あれを思い浮かべると、わかりやすいぞ。
哲  :ステートマシンと言っても、実際に利用されているんだなあ。
      コンピュータの扱う対象が違うだけで、必須となる概念は、驚く
      くらい少ないようだし。
啓二:そうかも知れない。ステートマシンは、マイコンの中で、たくさん
      利用されているし、ハードウエア動作に向いた概念かな。
哲  :ところで、LED点灯テストプログラムを、C言語で記述すると
      こんなのかな。

      state = 0 ;
      while (1) {
          val = get_pattern( state );
          send_pattern( val );
          state++ ;
          state %= 6 ;
      }

啓二:そんなところでいいんじゃないか。関数で、ハードウエアを
      見えなくしているのが、お前らしい。
哲  :いやあ、そうでもない。ただ、単純にMS-DOSやLinuxの環境で
      動作論理を調べられるようにするには、どうしたらいいかと
      考えたら、自然にこうなったんだ。
啓二:じゃあ、LSICで動作するような、プログラムを作成してみるか。
哲  :そんなことできるのか。
啓二:簡単さ。ハードウエア部分を画面でエミュレートすればいい。
      ちょっと待っていろよ。
 (10分ほどで完成)

      #include <stdio.h>

      unsigned char get_pattern(int x)
      {
          unsigned char pat[6] = {1,2,4,8,4,2};

          return *(pat+x) ;
      }

      void send_pattern(int x)
      {
          int i ;

          for ( i = 7 ; i > -1 ; i-- ) {
              putchar('0'+((x >> i) & 1));
          }
          putchar('\n');
      }

      int main(void)
      {
          int state,val;

          state = 0 ;
          while (1) {
              val = get_pattern( state );
              send_pattern( val );
              state++ ;
              state %= 6 ;
          }
          return 0 ;
      }

      ほいよ。
哲  :できたか。あれま、えらく単純。
啓二:そうさ。単純な方が理解しやすいだろう。
哲  :ビットパターンは、配列から添字を使って引き出すのか。
      で、2進表現にするため、シフトと論理演算を利用。
      いや、本当に簡単だ。
啓二:これを、コンパイルして、コンパイラにアセンブリ言語の
      コードを作成させてもいい。
哲  :何と、そういう手があったか。確かに、コンパイルのオプ
      ションスイッチに、そんなのがあったなあ。
啓二:今でも、ソースコードが、思った通りの動作に変換されて
      いるかどうかを、アセンブリ言語で見ているプロも多い。
哲  :それで、何かメリットがあるのか。
啓二:あるよ。ソースコード記述によっては、冗長なことをして
      いることもあり得る。それを見つけて、単純に、高速にと
      記述の仕方を変える。気にいらなければ、そっくり、アセ
      ンブリ言語で記述してしまう。
哲  :そんな細かいことまで、担当するのか。
啓二:チューニングにより、目的の性能が出るなら、何でもするさ。
      C言語にしてわかったんだが、あのプログラムじゃ、テスト
      できないなあ。
哲  :何故だ。
啓二:速すぎて、人間の目では、認識できない。
      ウエイトを入れないと、動作がわからない。
哲  :ああ、そうだなあ。どうしようか。
啓二:サブルーチンで、空ループを回してディレイにしよう。
      来週、それをやろうぜ。じゃあ、ここまでにしよう。

戻る