【13】リアルタイムなプログラム 1(ループ)
今回からプログラム作りも次のステップに上ります。いよいよ本格的なプログラムになってきます。
これまでのプログラムは単純なひとつの仕事をこなすだけのものでした。例えばLEDを点滅させるプログラムの場合、LEDを点灯させて、時間待ち処理を通り、それが終わったら消灯させる。そしてまた時間待ちをしてから点灯させる。あとはこの繰り返しになります。これが今までのプログラムでした。
実際、このような単純な動きのプログラムというのは少なく、簡易的な実験程度にしか使われません。本格的にプログラムで何かを制御しようとすると、イロイロな仕事を同時にこなさなくてはいけません。そのためにはどうするか・・・コレが次のステップ、“リアルタイムなプログラム”です。
▽ リアルタイム △
“リアルタイム”を辞書で引きますと、“実時間”と出てきます。でも意味が分からないですね。なぜ意味が分からないかというと、人間は実時間の中で生きていますので、いまさらそれを意識することがありませんので、意味が分かりにくいのだと思います。
実はPICなど含めてコンピュータというのは、実時間のモノではないのです。それらはデジタルの世界での時間枠で動いています。そういう意味ではWindowsパソコンとPICとの大きな違いは単にCPUクロックの速さだけです。
どちらも、CPUへ送られてくるクロックのパルスに押し出された命令を、ひとつずつ順番にこなしているだけです。人間の世界の実時間とは異なり、クロックの周期で歯車が回転する単純なカラクリ仕掛けのようなものです。
デジタルの時間枠で、食事の準備を表現するとこんな感じになります。
味噌汁を作るためにお湯を沸かします。お湯が沸いたらダシを溶いて味噌を入れて具を入れて味噌汁が完成です。そのままにしておくと危険なのでガスを消します。次に目玉焼きを作ります。目玉焼きが焼けたらガスを消します。お米を洗って水に浸けます・・・。
いつになったら食事にありつけることやら、これがデジタルの時間枠での動きです。
では、CPUクロックの速度を上げて速く動くようにしたら、早く食事が完成するかというと、そうでも有りません。コンピュータをいくら速く動かしても、実世界の事象の速度、つまり目玉焼きを焼く時間は変わらないからです。
少しでも早く食事ができるようにするにはどうするか、人間は実時間の生き物なのでこのあたりの事は心得ています。早く食べられるようにするために、動きをまとめて無駄の出ないようにして、それらを同時進行で行ないます。
このように、リアルタイムで仕事をするためには、いくつかの仕事を同時進行でこなさなければいけないことが分かります。そうしないと人間には理解しにくい不便なモノになってしまいます。
それでは、どうやれば単純な仕事しかできないプログラムを“リアルタイム”なプログラムにすることができるのでしょうか。
- 解決案 -
方法① ループを作り、仕事を順に進める。
方法② 決められた時間が来るたびに、仕事を切り替える
方法③ リアルタイム専用のOSを利用する
このうち③は規模が大きくなりすぎますので、小規模なPICでは無理です。可能性としては①と②が残ります。
②はPICの割り込み機能を利用すれば正確な"決められた時間"というモノを作ることができますので実現可能です。これについてはもう少し先でお話します。
①の“ループを作り、仕事を順番に進める”という方法は、割り込み機能の無いPICや、機能は搭載されているが、それほど正確性を求められるモノではない場合によく使われる手法です。
割り込み機能の無いFlash PICでも動きを追っていけますので、まずは①の“ループを作り、仕事を順番に進める”を取っ掛かりにして、次の割り込み機能を利用するへ、お話を進めていきましょう。
ループにするというのは繰り返すということです。何を? それが仕事=タスクです。
仕事を順に並べてそれを素早く繰り返えせば、たくさんの仕事を同時にこなしているように見えます。Windowsのプログラムも基本はこれと同じ動きをしています。具体的に説明しますと・・・。
① タスク-1
② タスク-2
③ ①へ戻る
こうなります。この時、注意することは、プログラムの流れを滞らさないことです。例えばタスク-1の中に別のループがあると、プログラムがそこを廻っているあいだ、タスク-2は待たされることになり、プログラムの流れが止まることになります。こうなると、リアルタイムなプログラムとはいえなくなります。
そこで実際に下記のようなプログラムを作って説明します。
・LEDを点滅させながら・・・。(点滅には意味は無いが、動作中ということで)
・マイクロスイッチを押した数を数える・・・。
ただし、マイクロスイッチは押した後、離された時点で1回と数える。
・スイッチはチャタリング対策も行って正確に数える
・そしてその回数を表示する・・・。
チャタリング対策というのは、機械式の接点を持つスイッチをOn/Offさせた瞬間、短い時間ですが、接点が振動する現象です。その対策を行わないと、押した回数よりも多く押したように検知してしまいますので、スイッチを使用する時は必須の処理です。
LEDを点滅させながら、スイッチを押した回数を数えて表示する、というプログラムです。
たったコレだけの仕事ですが、いろいろな問題点が浮き上がってきます。
・どこかでプログラムが止まると、LEDの点滅も止まるし、スイッチも無視される
・LEDを点滅させる → LEDの点滅は非常に遅い処理の連続だがどうする?
・スイッチの検知は高速に行う必要があるが → チャタリング対策をどうするか?
・ON、OFFで1回と数えるのでOFF検知までしないとだめ・・・
・それらを同時に行うにはどうするか?
リアルタイムなプログラムを作る時は、いきなり細かい部分から考えると途中で挫折します。大雑把でもOKです、問題を扱いやすくするために、箇条書きのようにタスク(=仕事)を大きく分類して、上から下へ流れるように記序すれば、内容が明確になり後の保守も楽になります。
スタート
↓
① 初期設定 (タスク1)
② マイクロスイッチのOn/Off検知をチャタリング対策付きで行う (タスク2)
③ LEDの点滅処理をする (タスク3)
④ ②へ戻る(②~④がループとなる)
大分類するとタスクは三つ。初期設定とスイッチ検知とLED点滅です。
初期設定以外は、ざっと考えただけでも内部にループが必要になりそうですが、リアルタイムプログラムではそれは許されません。ひとつのタスクを素早く処理して戻ってこなければ、次のタスクが待たされることになります。
そこで、次にひとつのタスクをさらに単純なタスクに分けます。まずはタスク2です。
[マイクロスイッチのOn/Off検知をチャタリング対策付きで行う](タスク2)
・・・を単純なタスクに分解して処理を並べる・・・
① チャタリング対策をする
② 押した回数を1加算して表示する
③ 終了
さらにタスクを細分化して処理を考えていきます。
[チャタリング対策をする]をさらに分解して流れを考える・・・
① ONしている時間を測って、短かい場合はチャタリングと判断
② 長い場合は、押していたとする
[押した回数を1加算して表示]をさらに分解して流れを考える・・・
③ 押した回数を+1する
④ 7セグに転送する
⑤ 終了
コレを繰り返して、さらに細分化していきます。
[チャタリング対策をする]
[ONしている時間を測って、短かい場合はチャタリングと判断]
[長い場合は、押していたとする]
① スイッチがOFFで、ONの記憶が無かったら、何もせずに⑧へ飛ぶ
ONしていた記憶が有ったら、そのON時間が規定値以上か調べる
② ON時間が規定値以下ならチャタリング現象として⑦へ飛ぶ
規定値以上なら④へ飛ぶ
③ スイッチがONならONしていたことを記憶して、ON時間を+1して、⑧へ飛ぶ
[押した回数を1加算して表示]
④ 正式なON/OFFと認めて、押し離した回数を+1する
⑤ 押し離した回数を7セグ用の表示データに変換する
⑥ 7セグに転送する
⑦ 次に備えて、すべてを初期状態に戻す(ON時間クリア/ONの記憶も消す)
⑧ 終了
ここまで分解すると、ようやくメモリがいくついるか、などが明確になってきます。
必要なメモリはONしている時間を計測するメモリと、ONを一度でも検知したかどうかの状態を表すメモリです。
前者の時間計測用のメモリですが、ここでいう時間とはスイッチがONしているあいだ、このタスクに侵入してきた回数になります。このようなメモリの使い方をカウンタと呼びます。
ループをどれぐらいの速さで廻っているかが分かれば、このメモリの数値を掛ければ大体の時間が分かります。
▽ フラグ △
後者の、状態を表すメモリは、ONを検知したか、していないか、の2値だけでいいので、1ビットを“1”か“0”にするだけで足ります。このような使い方をフラグと呼びます。1バイトのメモリがあれば8個のフラグが確保できます。
SFRの代表でもあるSTATUSレジスタにも“Carry”や“Zero”というフラグが存在します。
さて、さきほどの処理の流れをゆっくりと眼をこらして見ていると、いろいろと不具合の部分が見えてきます。
まず、① スイッチがOFFで、ONの記憶が無かったら・・・ と、なっていますが、ONの記憶がある場合のスイッチOFFを検知した、ということもありますので、このチャタリング対策はスイッチOFFを検知しないと次の段階、押した回数を+1するという処理に移行しないということになります。
具体的にいうと、マイクロスイッチがONのまま“ちきどん”が停止した場合は、押した数が+1される前段階で、回数が+1されません。しかし、課題はここを“押して離した”回数としているのでこれで正解にしました。また、“ちきどん”のマイクロスイッチ4番はONのまま停止することが無いので特に問題になりません。
次に・・・
③ スイッチがONならONしていたことを記憶して、ON時間を+1して、⑧へ飛ぶ
ON時間を+1して と書かれていますが、1バイトのメモリを+1ずつしていけば、もし、ON時間が長かった場合、0xFFを超えて、0x00へ戻ってしまいます。こうなると正確な時間計測ができません。チャタリング現象の時間は1~10mSほどで、それほど長い計測を行う必要も有りませんので、0~0xFFの範囲でじゅうぶんだと思われますが、数値が0xFFを越えないようにする必要があります。
次に④ 規定値を越えていたら、正式なONと認めて、押し離した回数を+1する で、押し離した回数もON時間の計測の時と同じで、0xFF(255)を越えると0に戻りますが、どちらにしても表示器は1桁しかありませんので、“0”~“F”の繰り返しになります。なのでここはそのままにしておいても問題はありません。
修正を施したタスク2は以下のようになります。
[マイクロスイッチのOn/Off検知をチャタリング対策付きで行う](タスク2)
[チャタリング対策をする]
③ スイッチがONならONしていたことを記憶して、ON時間を+1するが
それが=00でなければ⑧へ飛ぶ
ON時間を+1して、それが=00なら-1して、0xFFへ戻して、⑧へ飛ぶ
① スイッチがOFFで、ONの記憶が無かったら、何もせずに⑧へ飛ぶ
ONしていた記憶が有ったら、そのON時間が規定値以上か調べる
② ON時間が規定値以下ならチャタリング現象として⑦へ飛ぶ
規定値以上なら④へ飛ぶ
[押した回数を1加算して表示]
④ 正式なONと認めて、押し離した回数を+1する
⑤ 押し離した回数を7セグ用の表示データに変換する
⑥ 7セグに転送する
⑦ 次に備えて、すべてを初期状態に戻す(ON時間クリア/ONの記憶も消す)
⑧ 終了
使用メモリ:ON時間計測用メモリ…1バイト
使用フラグ:ON=1/OFF=0のフラグ
同じように、タスク3 LEDの点滅処理をする は次のように細分化できます。
[LEDの点滅処理をする](タスク3)
[時間待ちをする]
① タスク侵入カウンタを-1して、それが0なら②をスキップ
② 終了
③ タスク侵入待ち時間の固定値をタスク侵入カウンタへセットする
[点滅処理]
④ 現在の状態フラグが点灯中なら ⑩へ飛ぶ
[消灯から点灯へ]
⑤ 消灯カウンタを-1して、それが0なら⑥をスキップ
⑥ 終了
⑦ 次に備えて消灯時間の固定値を消灯カウンタへセットする
⑧ 消灯時間が終わったので点灯する
⑨ 状態フラグを現在点灯中にセットして終了
[点灯から消灯へ]
⑩ 点灯カウンタを-1して、それが0なら⑪をスキップ
⑪ 終了
⑫ 次に備えて点灯時間の固定値を点灯カウンタへセットする
⑬ 点灯時間が終わったので消灯する
⑭ 状態フラグを現在消灯中にセットして終了
使用メモリ:タスク侵入カウンタ用メモリ…1バイト
使用メモリ:点灯カウンタ用メモリ…1バイト
使用メモリ:消灯カウンタ用メモリ…1バイト
使用フラグ:点灯=1/消灯=0のフラグ
LEDの点滅処理は、ほとんどが時間待ち処理です。時間待ち処理はループ処理になりますが、リアルタイムプログラムでは許されません。そのため、タスク侵入カウンタと点灯カウンタ、消灯カウンタという三つのカウンタを使用して、時間待ち処理がリアルタイムになるように記序しています。タスク侵入カウンタの役割は、一定時間が来るまでLEDの点滅処理へ侵入しないようにしているだけです。
さらにタスク侵入カウンタ以外にもカウンタを設けているのは、実際のPICではCPU速度がFlash PICとは比較にならないほど速いために、タスク侵入カウンタと、もうひとつのカウンタで、二重カウンタを構成させています。
カウンター類は使用後 00になりますので、最初の状態に戻して次に備えなければいけませんので、固定値をあらかじめ決めておいて、必要に応じて初期設定に戻します。
この固定値はFlash PIC用のため小さな数値ですが、本物のPICの場合は大きな数値に変更する必要があります。
二重カウンタの二重目を二つに分けているのは、点灯時間と消灯時間を任意に変更できるようにしているためです。点灯時間を短く、消灯時間を長くすれば省電力にもなります。
ここまで文章で、処理の流れを書き込んでいましたが、これらがアルゴリズムを考えている段階です。問題解決のための工程を考えていたわけです。これが済むとプログラムはほとんど完成したようなものです。
大雑把にタスク分けから始まって、問題を細かく掘り下げて作り上げていくこのような方法をトップダウンプログラムとも呼ばれます。また、ひとつの問題解決に使用する処理をグループ化することをモジュール化と呼びます。モジュール化された処理をサブルーチンとして呼ぶことで、整理された分かりやすいプログラムができあがります。
実際のプラグラム作りでは、このような箇条書きではなく、“フローチャート”と呼ばれる、記号の中に処理を書き込んで、矢印などで流れを書き込んでいく方法が一般的です。しかしパソコン上で“フローチャート”を書き込む手段が少なく、それ専用のソフトを使用したり、ワードのテンプレートを使用すれば可能ですが、意識が“フローチャート”を綺麗に打ち込む方へ偏ってしまい、肝心の問題解決に集中できません。
しかし、この箇条書き方式ならプログラムを打ち込むエディッタ上で作成できますので、簡単な処理の時は箇条書き方式で済ませています。しかし複雑な処理の場合は、手書きの“フローチャート”が一番集中できます。
このあたりは、お好きなやり方でいいと思いますが、その後の保守を楽に済ますには、どのようにして考えたかというメモをできる限り細かく記録しておいた方がベストです。もちろんプログラム内のコメントも、丁寧な文章で多ければ多いほど保守が楽になります。
先ほどの箇条書きのアルゴリズムを実際にプログラムにコーディングしてみました。
I/0の割り当てなどはソースプログラムの先頭部分をご覧ください。
Flash PICの起動
|
プログラム転送後、“Build”してプログラム速度を10倍速以上で走らせてみて下さい。プログラム速度の変更はツールウィンドウの[RUN]ボタンの下にある、[+]ボタンをクリックします。
ちきどんの真ん中にある変形歯車でON/OFFを繰り返しているスイッチ4番が、押されるたびに回数を基板上の7セグ表示器に出します。そしてその仕事には影響されること無く、LED1が点滅を繰り返しています。点灯時と消灯時の間隔が異なっていることに注目してください。
スイッチの読み取りミスが発生する場合、ツールウィンドウでCPU速度を上げてみてください
続いて、ちきどんの[SPEED UP]ボタンを押して、ちきどんの回転速度を上げてみてください。スイッチ4番が頻繁にON/OFFを繰り返すようになるので、表示器の数値もそれにともなって、数値の変化が忙しくなりますが、LED1の点滅の周期は変化がありません。リアルタイムなプログラムが実行されているということですね。
2009.09.13
Copyright(C) 2004. D-Space Keyoss. All rights reserved