Flash PICの起動

 【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の起動

;
;
;** リアルタイムなプログラムを作ろう **
;LED1を点滅させながら、"ちきどん"のマイクロスイッチ4が
;ON/OFFした回数を数えてそれを表示器に表示させる。
;FLashPICの速度は10倍速以上にしてください。
;(ツールウインドウの[+]ボタンをクリックします)
;
INDF		EQU	0x00
TMR0		EQU	0x01	;(TMR0は、まだ使用できません)
PCL		EQU	0x02
STATUS		EQU	0x03
FSR		EQU	0x04
TRIS_A		EQU	0x05
TRIS_B		EQU	0x06
TRIS_C		EQU	0x07
PORT_A		EQU	0x08	;PORT_A,4がマイクロ SW4
PORT_B		EQU	0x09	;7セグメント表示器へ(Hで点灯)
PORT_C		EQU	0x0A	;PORTC_0=LED1を点滅用にする

FLG		equ	PORT_C+1
		;Bit0をSW4の検知状態フラグとして使用する
		;=1…スイッチがONだった
		;=0…まだONになっていない
		;
		;Bit1をLEDの点滅状態フラグとして使用する
		;=1…LEDを点灯した
		;=0…LEDを消灯した
		;
		;Bit2~Bit7 は空き
;
;↓↓ SW4のON期間の経過値
C_CNT		equ	FLG+1

;↓↓ スイッチが押して離した回数
SW_CNT		equ	C_CNT+1


;↓↓ LEDタスクへの侵入カウンター
LED_RC		equ	SW_CNT+1

;↓↓ LEDの点灯(ON)期間のカウンタ
LED_ON		equ	LED_RC+1

;↓↓ LEDの消灯(OFF)期間のカウンタ
LED_OF		equ	LED_ON+1


;↓↓ Flash PICの場合の各種固定値
;↓↓ 実際のPICでは調整が必要。
;↓↓ コメント( )内は4MHzPICの場合の予想値
CHAT		equ	0x07		;チャタリング時間(0xFD)
JOBCNT		equ	0x04		;点滅タスク侵入カウンタ(0x00)
ON_TIM		equ	0x02		;LEDの点灯期間(0x25)
OF_TIM		equ	0x08		;LEDの消灯期間(0x60)
; 
		org	0x00

;ここからプログラムコードを書きます。
		MOVLW	0x3F
		MOVWF	TRIS_A		;PORT_A,3がマイクロ SW4
		CLRW
		MOVWF	TRIS_B
		MOVWF	TRIS_C
		MOVWF	PORT_B
		MOVWF	PORT_C		;PORTC_0=LED1を点滅用にする

; ***  SW4検知の初期化
		bcf	FLG,0
		clrf	SW_CNT		;スイッチ検知初期化
		clrf	C_CNT		;チャタ検知用のカウンタ初期

; ***  LED点滅の初期化(CPU速度で調整必要)
		movlw	ON_TIM		;点灯期間の時間をセット
		movwf	LED_ON
		movlw	OF_TIM		;消灯期間の時間をセット
		movwf	LED_OF
		movlw	JOBCNT		;タスク侵入カウンタをセット
		movwf	LED_RC
		bcf	FLG,1		;状態をOFFにセット


; ***  ちきどん作動
		bcf	PORT_A,6	

; *********** Main loop  ******************************
M_LOOP:
		CALL CHK_SW		;スイッチ検知タスク
		CALL LED_BL		;LEDの点滅タスク
		goto M_LOOP
;******************************************************

;***                                              ***
;***         SW4の ON/OFF 検知タスク              ***
;***                                              ***
;***  規定時間より短いON/OFFはチャタリングノイズ  ***
;***  として、無視する                            ***
;***                                              ***
CHK_SW:
		btfss	PORT_A,4
		goto	M_INN		;SW4 ON検知
;  ***  SW4 検知がOffになった。
		btfss	FLG,0		;前回 SW4のONを検知済み?
		return			;No= 何もしない

		movlw	CHAT		;チャタリング時間以上の数値
		subwf	C_CNT,to_W	;それ以上?
		btfsc	STATUS,0
		goto	OK_SW		;それ以上の時
;  ***  以下の時はチャタリング中と判断して初期に戻す ***
NOISE:
		clrf	C_CNT		;ON 期間計測カウンタをクリア
		bcf	FLG,0		;ON 検知なしに戻す
		return

;  ***  SW4 ONを検知している時
M_INN:
		bsf	FLG,0		;SW4 検知済みに
		incfsz	C_CNT,to_F	;ON期間計測カウンタを+1する
		return			;0でなければ終了
		decf	C_CNT,to_F	;+1して0なら0xFFに戻す
		return
;  *** 規定値以上、SW4がONしていた時
OK_SW:
		incf	SW_CNT,to_F	;0~0xFFまで数えるが
		movf	SW_CNT,to_W	;表示は0~0x0Fまでになる
		CALL	GETDIG		;表示用の出データを求める
		movwf	PORT_B		;表示器へ出力
		goto	NOISE		;最初に戻す





;***                     ***
;***  LED1点滅タスク     ***
;***                     ***
LED_BL:
		decfsz	LED_RC,to_F
		return			;侵入カウンタが0でないので
					;何もしない
;侵入カウンタが0になったのでLED点滅処理に入る
		movlw	JOBCNT
		movwf	LED_RC		;侵入カウンタを初期化

		btfss	FLG,1		;LED点灯中?
		goto	ON_RDY		;LED消灯中なので点灯準備へ
		decfsz	LED_ON,to_F	;点灯期間終了?
		return			;まだ点灯期間なので無視
;点灯期間終了、消灯する
		movlw	ON_TIM
		movwf	LED_ON		;次に備えて点灯期間をセット
		bcf	PORT_C,0	;LED1を消灯
		bcf	FLG,1		;状態フラグを消灯に
		return

ON_RDY:
		decfsz	LED_OF,to_F	;消灯期間終了?
		return			;まだ消灯期間なので無視
;消灯期間終了、点灯する
		movlw	OF_TIM
		movwf	LED_OF		;次に備えて消灯期間をセット
		bsf	PORT_C,0	;LED1を点灯
		bsf	FLG,1		;状態フラグを点灯に
		return






;***                                       ***
;***  7セグメントの数値表示データテーブル  ***
;***                                       ***
GETDIG:
		andlw	0x0F
		addwf	PCL,to_F
		retlw	0x3F		;0の時の表示データ
		retlw	0x06		;1の時
		retlw	0x5B		;2の時
		retlw	0x4F		;3の時
		retlw	0x66		;4の時
		retlw	0x6D		;5の時
		retlw	0x7D		;6の時
		retlw	0x27		;7の時
		retlw	0x7F		;8の時
		retlw	0x6F		;9の時
		retlw	0x77		;〝A〟
		retlw	0x7C		;〝B〟
		retlw	0x39		;〝C〟
		retlw	0x5E		;〝D〟
		retlw	0x79		;〝E〟
		retlw	0x71		;〝F〟


			

プログラム転送後、“Build”してプログラム速度を10倍速以上で走らせてみて下さい。プログラム速度の変更はツールウィンドウの[RUN]ボタンの下にある、[+]ボタンをクリックします。

 ちきどんの真ん中にある変形歯車でON/OFFを繰り返しているスイッチ4番が、押されるたびに回数を基板上の7セグ表示器に出します。そしてその仕事には影響されること無く、LED1が点滅を繰り返しています。点灯時と消灯時の間隔が異なっていることに注目してください。

 スイッチの読み取りミスが発生する場合、ツールウィンドウでCPU速度を上げてみてください


 続いて、ちきどんの[SPEED UP]ボタンを押して、ちきどんの回転速度を上げてみてください。スイッチ4番が頻繁にON/OFFを繰り返すようになるので、表示器の数値もそれにともなって、数値の変化が忙しくなりますが、LED1の点滅の周期は変化がありません。リアルタイムなプログラムが実行されているということですね。

2009.09.13