出力から入力へでつくろう

先週は、動作仕様を決め、具体的なフローまで作成した哲さん
でしたが、プログラマの経験から、啓二さんに聞きたいことが
出てきたようです。そこで、今日は、疑問に思っていることを
まず聞いてみることにしました。
哲  :押忍!今日は、聞きたいことがあるから、それからだ。
啓二:ほほう、何かな。
哲  :コンピュータの基本動作は、入力、処理、出力だよな。
      マイコンのプログラムは、どの部分からつくるのかなと思ってさ。
啓二:たいていは、出力から作っていくなあ。
哲  :どうしてかな。
啓二:マイコンでは、はじめてプログラムをつくると、大抵動かない。
      Windowsマシンのように、動作保証がないんで、簡単なプログラム
      を使い、出力できるかどうかを確認するんだ。
哲  :その出力だけのプログラムって、どういうのかな。
      イメージがわかないんだ。
啓二:簡単だよ。特定のビットを出力に設定して、0と1を交互に出す。
      これで、クロックで動作していることがわかるし、配線ミスがない
      と確信できる。
哲  :外界に対して、何かできれば、動きがわかるっていうことか。
啓二:そう。よくやる手は、Knight2000のノーズエンブレムにある
      LEDのように、左右に点灯させていく。
哲  :そんな簡単なので、いいのか。
啓二:テストだけならば、これで充分さ。0と1を交互に出している
      から、こんなツールで音でマイコンが動いているか判断できる。


哲  :これは、どういう原理なんだ。
啓二:0と1を交互に出しているということは、クロック出力に相当する。
      クロックは周波数をもつ。そこで分周して、可聴領域まで周波数を
      落として、耳で0と1が出ていることを確認できるようにする。
      ピンヘッダで、どれくらい分周するかは、指定しないと駄目だが。
哲  :音でどの程度で動いているのかがわかるのか。こりゃ、便利だ。

啓二:このツールの回路は、これ。


      単純に、クロックを分周して、圧電ブザーを鳴らしているだけ。
      さて、スイッチの処理を作ろうか。
      スイッチが押されたなら、フラグをセットする。 
哲  :こんな風にだな。
         swi_flg = ON ;
      スイッチが押されているか、どうかは、割込みの中で
      ポートの値を見ればいいんだな。だとすると、こうだ。
         unsigend char tmp ;

         sw_state = P4 & 0x07 ;
      2つを合体させて、完成だ。

         unsigend char tmp ;

         sw_state = P4 & 0x07 ;
         swi_flg = ON ;
         if ( sw_state == 7 ) swi_flg = OFF ;

啓二:変数sw_stateには、どのスイッチが押されたかを、情報として
      保存するのか。うまいじゃないか。やっぱ、ソフト屋だ。
哲  :あったり前田のクラッカーってね。
      この段階で、変更できる変数があるよな。indexだ。

         if ( (sw_state & 1) == 0 ) {
             index++ ;
             index %= 4 ;
         }

      とすればいいなあ。
      他は、数値の更新と確定か。この2つは、少し考えないとな。
      この処理の中で、数値の更新をして、確定したならば、該当
      の時、分を更新するのが、いいなあ。
啓二:処理がだんだん、複雑になってきているぞ。一度、変数その他
      まとめて整理しよう。ソフト屋は、構造を考えないで、一気に
      書く人間が多いのか、バグ発生を調べていったら、構造欠陥と
      ということも、よく経験させられる。全部がそうだとは、言わ
      ないが、結構多いな。
哲  :ならば、変数を整理してみるか。
      時計動作の時分は、hour、minuteで、確定してある。
      タイマー動作開始時刻と終了時刻が必要だな。
      簡単に、begin_hour、begin_minute、finish_hour、finish_minuteと
      しておくか。動作用のフラグが必要だよな。スイッチ入力とタイマー
      割込みがあったことを示すフラグが。これらを、swi_flg、tim_flgと
      しておこう。
      ブリンクするかどうかのフラグもあったなあ。blk_flgにするか。
啓二:これまで出た変数で、どのくらいのバイト数かというと、9バイト。
      スタック処理に1024バイト取られても、まだ1000バイトも
      余裕がある。
哲  :この他にも、表示処理のために8バイト必要だ。
      digit、pdigitが配列で4バイトずつあるからなあ。
      あれ、qdigitの4バイトが漏れている。
啓二:その4バイトは、カウントしないでいい。レジスタを利用して
      動かしてしまえば、メモリは食わない。
哲  :よし、ここまでの変数は、グローバルだな。他にグローバルに
      しなければならないのは、index、sw_stateがあるな。
      ところで、グローバル変数は、あまり使うなと言うけれど、
      マイコンのプログラムでは、どうなんだ。
啓二:場合にもよるけれど、減らすようにはする。が、それで開発効率
      が落ちたり、動作スピードが落ちるならば、使うさ。
      重要なのは、動くプログラムを作ることだから。
      動けば、徐々に減らして、テスト、デバッグ、メンテナンスを
      しやすようにする。
哲  :やっぱり、そうだよなあ。じゃあ、変数の整理、整頓を続けるか。
      時計処理を除く、3つのモードのときは、7セグメントLED
      に転送するビットパターンを、一度取り出さないといけないな。
      時刻設定のときは、hourとminute。タイマー動作開始時刻と終了時刻
      では、begin_hourとbegin_minute、finish_hourとfinish_minute。
啓二:出力するのは、これらの値だから、メイン処理の中で、モードを
      判断し、digitに入れてやればいい。
哲  :こうして見ると、出力する内容を考えて、それに対して、入力は
      どうすればよいかを考えているな。お前の言うとおり、出力から
      入力へになっている。
啓二:実際にコードを考えてみると、よくわかるだろう。
哲  :うん。
      時計動作を考えると、次のシーケンスになる。
        hourの10の位、1の位を、digit[0]、digit[1]に転送。
        minuteの10の位、1の位を、digit[2]、digit[3]に転送。
      残りは、他の処理がやってくれる。
啓二:そうそう。それでいいなあ。他の3つの処理も考えてみようぜ。
      他の3つの処理は、数値の更新だから、じっくりやろう。
哲  :そうだな、このプログラムの中で、大きな部分だな。
      時刻設定を考えると、まずは、数値を転送だ。
        hourの10の位、1の位を、now[0]、now[1]に転送。
        minuteの10の位、1の位を、now[2]、now[3]に転送。

      けた移動のときは、変数indexだけの処理だから、何もせずに
      配列nowの内容を、表示処理に渡す。

      確定のときは、配列nowからの算出値を、hour、minuteに戻すのか。

        hour   = 10 * now[0] + now[1] ;
        minute = 10 * now[2] + now[3] ;

      モードによって、配列nowからの算出値を、格納する変数が違うな。
      それならば、これでどうだ。
        tmpH = 10 * now[0] + now[1] ;
        tmpL = 10 * now[2] + now[3] ;

        switch ( mode ) {
            case 1 : 
                hour   = tmpH ;
                minute = tmpL ;
                break ; 
            case 2 : 
                begin_hour   = tmpH ;
                begin_minute = tmpL ;
                break ;
            case 3 : 
                begin_hour   = tmpH ;
                begin_minute = tmpL ;
                break ;
            default : 
                break ;   
        }

      数値を+1するときには、少し工夫が必要だな。
      けたによって、0に戻す境界値が異なる。
啓二:そうだなあ。ところで、時計は24時間、12時間表示の
   どっちかに、決めていなかったな。
   24時間表示でいくか。
哲  :そうなると、配列nowの値は、次のように剰余系を変えないとな。
        now[0] -> 0、1、2なので、3の剰余系
        now[1] -> 0から9なので、10の剰余系
        now[2] -> 0から5なので、6の剰余系
        now[3] -> 0から9なので、10の剰余系

      ここまで突き詰めれば、後はコードにするだけだ。
      数値を+1する指令が来たときは、次のようにする。

        switch ( index ) {
            case 0  : now[0]++ ; now[0] %= 3  ; break ;
            case 1  : now[1]++ ; now[1] %= 10 ; break ;
            case 2  : now[2]++ ; now[2] %= 6  ; break ;
            case 3  : now[3]++ ; now[3] %= 10 ; break ;
            default :                           break ;
        }

      冗長だなあ。計算は、なるべくやりたくない。
      少し、変えてみるか。

        switch ( index ) {
            case 0  : div =  3 ; break ;
            case 2  : div =  6 ; break ;
            default : div = 10 ; break ;   
        }
        now[index]++ ;
        now[index] %= div ;

      こんなもんか。
啓二:どうやら、大変な内容も、コードに出来たようだ。
      続きは、来週にするか。
哲  :そうしよう。でも、一応スイッチ処理のコードを
      まとめておくか。

      void proc_switch_handling(void)
      {
          UBYTE mode,now[4] ;

          /* モード入力 */
          mode = get_mode();
          /* 時、分を、4けたの数字に分解 */
          transfer_digit(now,mode);
          /* シフトボタン処理 */
          if ( (sw_state & 1) == 0 ) update_digit_logation();
          /* +1ボタン処理 */
          if ( (sw_state & 2) == 0 ) increment_digit(&now[index]);
          /* 確定 */
          if ( (sw_state & 4) == 0 ) confirm_digit(now,mode);
      }

      /* 時、分を、4けたの数字に分解 */
      void transfer_digit(UBYTE *x,UBYTE md)
      {
          blk_flg = ON ;
          switch ( md ) {
              case 1 : /* 時刻 */
                  divide(x,hour,minute);
                  break ;
              case 2 : /* タイマー開始時刻 */
                  divide(x,begin_hour,begin_minute);
                  break ;
              case 3 : /* タイマー終了時刻 */
                  divide(x,finish_hour,finish_minute);
                  break ;
              default : /*  */
                  blk_flg = OFF ;
                  break ;
          }
      }

      void divide(UBYTE *x,UBYTE y0,UBYTE y1)
      {
          *(x+0) = y0 / 10 ;
          *(x+1) = y0 % 10 ;
          *(x+2) = y1 / 10 ;
          *(x+3) = y1 % 10 ;
      }

      /* シフトボタン処理 */
      void update_digit_logation(void)
      {
          index++ ;
          index %= 4 ;
      }

      /* +1ボタン処理 */
      void increment_digit(UBYTE *x)
      {
          UBYTE div,tmp ;

          switch ( index ) {
              case  0 : div = 3  ; break ;
              case  2 : div = 6  ; break ;
              default : div = 10 ; break ;
          }
          tmp = *x ;
          tmp++ ;
          tmp %= div ;
          *x = tmp ;
      }

      /* 確定 */
      void confirm_digit(UBYTE *x,UBYTE md)
      {
          UBYTE tmpH,tmpL ;

          tmpH  = 10 * (*x) ;
          x++ ;
          tmpH += (*x) ;
          x++ ;
          tmpL  = 10 * (*x) ;
          x++ ;
          tmpL += (*x) ;

          blk_flg = OFF ;
          switch ( md ) {
              case  1 : 
                  hour   = tmpH ;
                  minute = tmpL ;
                  break ;
              case  2 : 
                  begin_hour   = tmpH ;
                  begin_minute = tmpL ;
                  break ;
              case  3 :
                  finish_hour   = tmpH ;
                  finish_minute = tmpL ;
                  break ;
              default : 
                  blk_flg = ON ;
                  break ;
          }
      }
啓二:マネージャーレベルから、ブレークダウンしているなあ。
      これぞ、トップダウンというところか。

戻る