先週は、動作仕様を決め、具体的なフローまで作成した哲さん 出力から入力へでつくろう
でしたが、プログラマの経験から、啓二さんに聞きたいことが
出てきたようです。そこで、今日は、疑問に思っていることを
まず聞いてみることにしました。
哲 :押忍!今日は、聞きたいことがあるから、それからだ。 啓二:ほほう、何かな。 哲 :コンピュータの基本動作は、入力、処理、出力だよな。 マイコンのプログラムは、どの部分からつくるのかなと思ってさ。 啓二:たいていは、出力から作っていくなあ。 哲 :どうしてかな。 啓二:マイコンでは、はじめてプログラムをつくると、大抵動かない。 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 ; } } 啓二:マネージャーレベルから、ブレークダウンしているなあ。 これぞ、トップダウンというところか。