機能分割した仕様を決めよう

先週は、時計+タイマーの大まかな仕様を決めました。あれ
から1週間が経過しています。仕様も虫干しされて、少しは
より単純な仕様に落とせるようになったでしょう。ロボの足
の爪を獣医さんに切ってもらい、哲さんは、啓二さんの仕事
部屋にやってきました。すると、何やら鍋でグツグツと音が
しています。
哲  :こんちは。今日は、ロボを獣医さんのところへ連れて
      行った帰りなんで、遅くなった。
啓二:うん、まあ、いいよ。
哲  :鍋でグツグツやっているのは何だ。
啓二:温泉卵を作っているんだよ。ガスを使わず、高周波加熱
      だから、火が出ないし安心さ。火を使うと、Aiboの
      動きが激しく、危ないんだ。
哲  :そうか。今日は何をするんだ。
啓二:時計の機能を、分割してソフトウエア仕様を決めよう。
哲  :この前の仕様で、できるだろう。
啓二:まだ、駄目だよ。あれでコーディングできるか。
哲  :ちょっと苦しいか。
啓二:そうだろう。まず、出力から仕様を決めていこう。
      最初に、4個のシフトレジスタにビットパターンの16
      ビットを転送する仕様をきめるか。
哲  :実現できるかな。どれどれ、動作を書き出してみるか。
      タイマー割込みで、時刻が確定したとする。
      時刻は、メモリのhour、minuteに10進数で入っている
      と仮定しよう。
啓二:時、分ともに10進数をBCDコードに変換して、ビット
      パターンを引き出すようにするか。
      例えば、12:34であれば、時は12、分は34が
      入っている。1けたにつき、8ビットのビットパターンを
      テーブル参照で引き出すか。
哲  :テーブル参照なんて、エレガントじゃないなあ。
啓二:そんなことはないさ。ビットパターンを確実に定義して
      おけば、数値をオフセットにして、簡単にパターンを引き
      出して使える。

               Pgfedcba
          0  : 00111111
          1  : 00000110
          2  : 01011011
          3  : 01001111
          4  : 01100110
          5  : 01101101
          6  : 01111101
          7  : 00000111
          8  : 01111111
          9  : 01101111

哲  :12:34のときは、まず、4つの変数を用意して、数値を
      分割して入れておくか。まあ、配列を使うと出来るな。
      12:34 -> digit[0] = 1 / digit[1] = 2 /
                    digit[2] = 3 / digit[3] = 4
      のように、入れるのか。
      ああ、そうか。ここで、論理演算とシフト演算が必要なんだ。
啓二:そう言うこと。  
哲  :digit[0]の内容を決めるときは、変数hourの値を10で
      割って、商を格納する。
        digit[0] = hour / 10 ;
      digit[1]は、10で割り余りを格納する。
      同じ方式で、digit[2]、digit[3]を決定できる。
      結局、こうだな。

        digit[0] = hour / 10 ;
        digit[1] = hour % 10 ;
        digit[2] = minute / 10 ;
        digit[3] = minute % 10 ;

啓二:ビットパターンは、digitの内容で取り出すといい。
哲  :後でアセンブリ言語で記述するとして、まずは、C言語で
      論理だけを考えるとするか。じゃあ、便利な関数get_pattern
      があるとして、上の処理に続けて
        digit[0] = get_pattern(digit[0]) ;
        digit[1] = get_pattern(digit[1]) ;
        digit[2] = get_pattern(digit[2]) ;
        digit[3] = get_pattern(digit[3]) ;
      でいいさ。
啓二:あまり、関心しないなあ。
哲  :ソフト開発を生業にしている人間に、ケチつけるのか。
啓二:関数get_patternや他の処理にバグがあったとき、どう
      やって見つけるんだよ。お前のやり方だと、必要となる
      情報を破壊してしまうから、デバッグの効率が下がる。
      もうひとつ配列を用意して、次のようにする。

        pdigit[0] = get_pattern(digit[0]) ;
        pdigit[1] = get_pattern(digit[1]) ;
        pdigit[2] = get_pattern(digit[2]) ;
        pdigit[3] = get_pattern(digit[3]) ;

哲  :これじゃ、メモリを食ってしまうだろう。
啓二:うん、でも、2kバイト程度の中の4バイト利用
      しても害はないさ。むしろ、デバッグのことを考えたら、
      4バイトをケチってしまう方が問題じゃないか。
哲  :そうか。78kのメモリに制約が多いというので、4バイト
      でも削れればと思ったんだが、デバッグを考えると、この方
      がいいなあ。
      関数の使用前後を比較すると、不具合を見つけやすいしな。
啓二:pdigitの内容を、出力していけば、7セグメントLEDに
      数字が出てくる。8ビットごとに送信するルーチンを考えて
      みろよ。
哲  :まず、8ビット出力するから、for文でループを回すよな。
      MSBから出力すると仮定するか。あっ、MSBから出力する
      としたら、ハードウエアをそうしないと駄目なんだ。
啓二:そう。だから、ラフなプログラムを作成してみることも
      必要なんだ。
哲  :まず、8ビット出力するから、for文でループを回すよな。
      for ( i = 7 ; i > -1 ; i-- ) {
      }
      次に、pdigitの値を、出力して、クロックに1、0を与える。
      for ( i = 7 ; i > -1 ; i-- ) {
          P2.2 = (pdigit[0] >> i) & 1 ;
          P2.3 = 1;
          P2.3 = 0;
      }
      これは、時の上位けただけだから、分の上位けたの処理も入れる。

      for ( i = 7 ; i > -1 ; i-- ) {
          P2.2 = (pdigit[0] >> i) & 1 ;
          P2.0 = (pdigit[2] >> i) & 1 ;
          P2.3 = 1;
          P2.1 = 1;
          P2.3 = 0;
          P2.1 = 0;
      }

      次に、下位けたのビットパターンを送ればいいな。

      for ( i = 7 ; i > -1 ; i-- ) {
          P2.2 = (pdigit[1] >> i) & 1 ;
          P2.0 = (pdigit[3] >> i) & 1 ;
          P2.3 = 1;
          P2.1 = 1;
          P2.3 = 0;
          P2.1 = 0;
      }

啓二:2回のfor文は、醜いぞ。一つにまとめた方がいい。
哲  :そうか。そうすると、16ビットを転送すればいいから、
      時と分の16ビットを作って、ループで回すか。
      変数を2つ用意してと。

      tmpH = pdigit[0] * 256 + pdigit[1] ;
      tmpL = pdigit[2] * 256 + pdigit[3] ;
      for ( i = 16 ; i > -1 ; i-- ) {
          P2.2 = (tmpH >> i) & 1 ;
          P2.0 = (tmpL >> i) & 1 ;
          P2.3 = 1;
          P2.1 = 1;
          P2.3 = 0;
          P2.1 = 0;
      }

啓二:C言語だから掛け算処理を簡単にできるけれど、
      アセンブリ言語じゃ面倒だ。
      シフトを使って書き直せよ。
哲  :あれ、そういうもんか。ならば、こうか。
      tmpH   = pdigit[0] ;
      tmpH <<= 8 ;
      tmpH  &= 0xff00 ;
      tmpH  |= pdigit[1] ;
      tmpL   = pdigit[2] ;
      tmpL <<= 8 ;
      tmpL  &= 0xff00 ;
      tmpL  |= pdigit[3] ;
啓二:いんでないのかい。次は、動作表示LEDの点滅だなあ。
      これは、タイマー割込みで起動してしまえばいい。
      タイマー割込みは、後から定義するとして、点灯、消灯を
      実行させるコードを考えよう。
哲  :まず、3つの状態があるんだよな。
      点灯、消灯、点滅。これを、まず2つにわけるか。
      点滅とそれ以外。
啓二:場合分けに出たな。数学の余事象の考え方を使うと、結構
      楽だ。
哲  :2つの場合を分けるには、フラグを利用すればいいか。
      よし、blk_flgというフラグを用意して、こうするか。
      if ( blk_flag == ON ) {}
      else                  {}
      あれ、動作中でなければ、消灯だけにすればいいんだよなあ。
      だとすると、こうか。
      if ( blk_flag == ON ) { /* 点滅 */ }
      else                  { /* 消灯 */ }
      おい、点灯と消灯って、1と0にどう対応させるんだ。
啓二:普通は、0で点灯、1で消灯にする。その方がマイコンの
      出力回路の負担が少ないから。
哲  :じゃ、こうすればいいのか。
      if ( blk_flag == ON ) { /* 点滅 */ }
      else                  { P13.0 = 1 ; }
      ああ、点滅はどうしようか。
啓二:簡単だ。変数を用意して、次のようにすればいい。

      blk_cnt++ ;
      blk_cnt %= 2 ;
      if ( blk_flag == ON ) { P13.0 = (blk_cnt & 1) ; }
      else                  { P13.0 = 1 ; }

哲  :あらら、タイマー割込みごとに、変数blk_cntが0か1に
      なるから、それを出力するってか。
啓二:こういうときの定石なんだ。フラグblk_flagで点滅か
      否かを決めてあるので、使う方は、フラグだけに気を
      つければよし。そして、ハードウエアに点滅をやらせて
      しまえば、後は知らん顔できる。
哲  :ハードを知っているから、こんな芸当ができるのか。
      残りは、何だっけ。
啓二:タイマー動作中の出力を決めるところ。これは、単純に
      オンかオフの指令を与えだけでいい。
哲  :じゃあ、こんな程度でいいのか。

      P4.5 = 0 ;
      if ( out_flg == ON ) P4.5 = 1;

啓二:いいんじゃないかあ。
      今日は、ここまでにする。温泉卵ができたようだし。
哲  :あれ、あの高周波調理器は、自作なのか。
啓二:そう。誘導コイルを小さい寸胴にまいて、タイマーを
      つけてある。それで、時間がきたら高周波をかけるのを
      やめるのさ。温泉卵なら30分でできるぞ。
哲  :じゃあ、今日は、温泉卵を頂いていくか。
啓二:何だよ。自分の家で作れよ。
(料理のレシピ  その4)
  A 食材の用意
     豆腐:1丁 かつお節:少々 なめ茸:少々
  B 料理方法
     豆腐は、一口大に包丁で切れ目をいれる。
     小鉢に豆腐をいれる。
     かつお節、なめ茸を豆腐の上にトッピング。
  C 盛り付け
     皿に、フライパンに納豆をうつす
     好みで、醤油をかける

戻る