« 2014年6月 | トップページ | 2014年8月 »

2014年7月31日 (木)

「天文屋のためのマイコン入門」7セグメントLEDとロータリースイッチの追加

前回までのプログラムで、骨格はほぼ完成しています。完成まであと少し、今日は、7セグメントLEDによる残り露出時間の表示とロータリースイッチによる露出設定を追加します。

追加点を順をおって説明します。まずは定義部分です。

1407271

SKIP_IF_ZEROはもうすでにあるので、SKIP_IF_NOT_ZEROを追加しておきます。演算結果が0でない場合に次の命令をスキップする命令です。

続いて、割り込み処理部分の追加です。ここで、ロータリースイッチから入力した数値(0~9)を7セグメントLEDに表示するプログラムを追加します。

1407272

BTFSS MODE , 1 命令によりモード2露出中の場合は、ロータリースイッチの値の表示はキャンセルされます。露出中は7セグメントLEDの表示は、残り露出時間を表示するのであって、ロータリースイッチの設定を表示するわけでないからです。スイッチの設定を表示するのは、モード0停止中またはモード1インターバル中のみです。

なお、この部分は1/32秒間隔で呼ばれるので、ロータリースイッチを回せば、ほぼリアルタイムで7セグメントLEDの表示が変わります。これは重要なことです。

サブルーチンInputSW_and_Dispはロータリースイッチの値を入力し、7セグメントLEDに表示するサブルーチンです。詳細は後で説明します。

次に割り込み処理の終盤、各モード別の動作のところ追加します。最初にモード0停止中のときプッシュスイッチが押され、モード2露出中に移行する部分の変更です。

1407273

ここでは、サブルーチンInputSWを呼んで、スイッチの値を取得し、レジスタMINUTEに格納しています。前回までここは無条件に1を代入していた部分です。

なお、

SKIP_IF_NOT_ZERO
ADDLW 10

この2行によりロータリースイッチの値が0のときは10分に変更しています。これにより、1分から10分まで設定できるようになっています。サブルーチンInputSWは以前紹介したサブルーチンとまったく同じです。

次に、時間をカウントする部分の追加です。

1407274

ここでは、モード2露出中のとき、MINUTEがデクリメントされるタイミングで、その値を7セグメントLEDに表示します。サブルーチンDisp7Segはだいぶ前に紹介したサブルーチンですがそのまま利用します。このサブルーチンは表示させたい数値を汎用レジスタの20h番地に格納しておくのでした。忘れた人は前の記事に戻って思い出してください。

次に初期化部の追加です。

1407275

最初にLEDを光らせるポートを0にクリアするのを今まで忘れていました。遅くなってしまいましたが、ここで追加しておきます。実はこの初期化ですが、この場所はあまりよくありません。また後日修正します。とりあえず、このままで。

最後にサブルーチンの追加です。

1407276

3つのサブルーチンを追加しますが、InputSWと、Disp7Segの2つは、だいぶ前に紹介したサブルーチンのままです。以前の記事を参照してください。

最初のInputSW_and_Disp7segは、のこりの2つを組み合わせたものです。スイッチの値を入力し、そのまま表示するサブルーチンです。

今日はここまで、あと、カメラのシャッターを開閉するためのアナログスイッチといわれるICを追加するだけです。完成までもう少し。

 

| | コメント (0)

2014年7月29日 (火)

遠征行って来ます

八千穂高原に行こうと思います。

今日はBORG67FL+α7sを試してみたいと思います。

いちおう、白鳥座あたりを。

それでは。

| | コメント (0)

2014年7月26日 (土)

「天文屋のためのマイコン入門」3モードをさらに発展

前回までのプログラムで3つのモードを扱うプログラムを完成させました。

モード0 LED消灯
モード1 LED点灯
モード2 LED点滅(1秒間隔)

このモードに意味を持たせれば、目的とするインターバルシャッターのプログラムに近づきます。次のように意味を持たせてみます。

モード0 LED消灯------------->停止中
モード1 LED点灯------------->インターバル中(露出と露出の間)
モード2 LED点滅------------->露出中

ただ、それぞれの状態遷移は単純ではありません。それを図示したのが以下です。

1407261

最初はモード0停止中です。ここでプッシュスイッチを押すとモード2露出中に移行します。設定時間経過すると、モード1インターバル中に移行します。そして3秒経過するとまた露出中に移行し、これを永遠に繰り返します。

モード1インターバル中、モード2露出中、いずれの場合もプッシュスイッチを押せば、モード0停止中に移行します。

これをプログラムにしてみましょう。また変更のあった部分のみ説明します。プログラムはの主要部分はすべて割込み処理の中にあります。割込み処理を前半、後半に分けて説明します。

まずは前半部分です。

1407262

赤枠の部分は、プッシュSWが押されたことを検出する部分です。これは前回とまったく同じです。

青枠の部分はプッシュSWが押された場合にモードを変化させる部分です。SWが押されなければ素通りされます。

青枠の中の緑枠の部分が、モード0停止中の時に、SWが押された場合で、モード2露出中に移行する準備をしています。MINUTEに露出時間をとりあえず1分に設定しています。それ以外のデータは0クリアして初期化しています。

黄色の枠の中は、モード1インターバル中またはモード2露出中にプッシュSWが押された場合で、LEDを消し、モード0へ移行しています。

次に後半部分です。

1407263

赤枠の部分は時間をカウントする部分です。前回より拡張しています。この部分でやっていることは、

1 TIME_COUNTをカウントし32になったら0に戻し、SECONDをインクリメントする
2 SECONDをカウントし、60になったら0に戻し、MINUTEをデクリメントする

ただし、プログラムではSECONDが10になった場合にMINUTEをデクリメントしています。ここは60が正解です。プログラムを早く動かした方がデバックしやすいので1分を10秒に設定しました。プログラムが完成したら60に戻します。

この割込み処理の後半部分ですが、81行目までは、1/32秒間隔のタイマー割込み処理のすべてで実行されます。82行目以降は、32回に一回しか実行されません。つまり1秒間隔でした実行されません。

もともと1/32秒間隔にしたのは、プッシュSWのチャタリングの影響を除去するためで、プッシュSWの処理はもう終わっているので、1秒ごとでよいのです。

さて、赤枠移行は各モードごとの処理です。

まずは緑枠のモード0停止中の場合ですが、これは何もしませんので、割込み処理の出口にジャンプします。

次に、紫枠モード1インターバル中の場合ですが、SECONDが3になったかどうか調べています。3ではなければ、そのまま何もしません。3になった場合はモード2露出中に移行しますので、CUR_MODE_0へジャンプします。CUR_MODE_0は割込み処理の前半部分にあります。モード0でスイッチが押された場合の処理とまったく同じなので、ここへジャンプさせています。

最後、青枠はモード2露出中の処理です。ここではMINUTEを調べて0かどうか判断します。0なら露出終了なので、モード1インターバル中に移行する準備をします。まだ露出中なら、黄色の枠の中、LEDを点滅させる処理をします。

説明はここまでです。

さて、もうほとんど完成に近いですね。あとは、MINUTEの初期化にロータリースイッチの値を読み込み、残り露出時間、つまりMINUTEの値を7セグメントLEDに表示する処理を加えるだけです。あ、あとシャッター信号もありますね。いずれにせよもう少しです。

| | コメント (0)

2014年7月25日 (金)

「天文屋のためのマイコン入門」3モードへの拡張

前回までのプログラムでは次のような動作でした。プッシュスイッチを押すたびにモード0とモード1が入れ替わり、モード0の場合はLEDを消灯、モード1の場合はLEDを点灯するというプログラムでした。

今回はこれを拡張し、新たにモード2を追加します。各モードの動作は次の通りです。

モード0 LED消灯
モード1 LED点灯
モード2 LED点滅(1秒間隔)

1407250

プログラムは追加部分だけ説明します。まずは、MODE_CHANGEの部分です。

1407251

MODEをインクリメントする部分は同じですが、今度はモード2の次は3なってしまいますので、0に戻す必要があります。そのためビット1とビット0を調べて両方とも1の場合、つまり3の場合0にします。

次に、CONTINUEの部分ですが、秒数をカウントする部分を追加します。秒数カウントはLEDを点滅させるために必要です。

1407252_2

1/32秒間隔の割込み処理の度にTIME_COUNTをインクリメントします。そしてそれが32になったら、つまり32を減算して0になったら、今度はSECONDをインクリメントし、TIME_COUNTをクリアします。この繰り返しでSECONDは秒数をカウントします。

2番目の赤枠の部分は、モード2が追加になったので、そのための追加です。その下のGOTO MODE_2も追加です。

3番目の赤枠はモード2の動作の追加です。SECONDが奇数か偶数かを、最下位ビットが0か1かで判断しています。奇数ならLEDを消灯させ、偶数なら点灯させます。これによりLEDが点滅します。

| | コメント (0)

2014年7月22日 (火)

「天文屋のためのマイコン入門」スイッチ検出プログラム

  前回までに1/32秒間隔で発生するタイマー割り込みのプログラムを作りました。もう一度、掲載します。

1407201_2

これをもとに、プログラムを追加していき、最終的に完成させます。まずは、スイッチが押されたことを検出するプログラムを追加します。そのプログラムを以下に掲載します。順に説明していきます。

まずは、定義部です。

1407202

最初の赤枠部分で、便利な置き換えにいくつか追加してます。

SKIP_IF_ZERO
これは、演算の結果が0なら次の命令をスキップする命令の便利な置き換えです。

SKIP_IF_SW_ON
これは、スイッチがON状態なら次の命令をスキップする命令の置き換えです。

SKIP_IF_SW_OFF
これは、スイッチがOFF状態なら次の命令をスキップする命令の置き換えです。

2番目の赤枠の中はプログラム中で使うデータの汎用レジスタへの割り付けです。単なる名前の定義ですが、

TIME_COUNT
このデータは1/32秒間隔の割込みごとにインクリメントさせます。つまりTIME_COUNTが32になったら1秒です。

SECOND
秒数をカウントをするデータです。

MINUTE
分数をカウントするデータです。ほんとは、分数など使わずすべて秒数で管理した方が良いのですが、レジスタは8ビットなので255秒までしか格納できません。これでは実用的でないので、秒数、分数で時間を管理することにします。

SW_STATE
前回説明したとおり、スイッチが押されたことを検出するには1/32秒間隔で3回スイッチの状態を読み込みOFF-ON-ONを検出すれば良いのでした。そこで、SW_STATEはスイッチがOFFの時は0、OFF->ONになったら1、さらにOFF->ON->ONとなったら2とすることにします。

MODE
このプログラムはLEDを点けたり、消したりするプログラムですが、LEDが消えた状態をモード0、LEDが点灯した状態をモード1とします。なんでわざわざ「モード」なる概念を導入したかというと、今後の発展のためです。

プログラムを上から説明していくと次は割込み処理ですが、プログラムの流れ的には、次は初期化部を説明する方が分かりやすいです。

1407203_2 

定義部で名前をつけたデータをここで初期化しています。すべて0にしています。

次に、本筋の割込み処理を説明します。

1407204

割込み処理はおおまかに3つの部分からなります。赤枠の部分でスイッチがOFF-ON-ONに変化するのを検出しています。で、検出したら、青枠の部分で、モードを変えています。そして、緑の部分で、モードに応じてLEDを点灯または消灯させています。

最初に赤枠の部分を説明します。

1407205

最初はスイッチはOFFです。つまりSW_STATE=0です。この状態でスイッチが押されたとします。上のグラフの1の部分です。このとき、SW_STATEは0なので、ビット0、ビット1も0なので矢印の部分にプログラムが移動します。

ここでSW_STATEが1になります。

1407206

さらにSWがONなら、つまり上のグラフの2の部分ですね。このときはプログラムは2の矢印の部分に移動し、次の青枠の部分のモードチェンジに移動します。

1407207

ここでは、モードを0なら1、1なら0にするのですが、つまり反転。しかし実際にはインクリメントしています。インクリメントでも最下位ビットだけ見れば0->1->0->1を繰り返すので、反転と同じことです。

1407208

緑の枠の中ではLEDを変化させています。モードが0なら、消灯させ、モード1なら点灯させています。

今日はここまで。

| | コメント (0)

2014年7月20日 (日)

「天文屋のためのマイコン入門」1/32秒間隔の割込みを作る

1秒間隔のタイマー割込み発生プログラムは既に作成済みです。これをもとに1/32秒間隔の割り込みを発生させるため、どこをどのように修正するか説明します。

まず、命令を実行させるためにクロックですが、これを仮にシステムクロックと呼びます。システムクロックは現在31.25KHzに設定されています。何も設定しなければ最低のこの値になります。この値を現在まで使ってきました。

クロックを最大の8MHzまで上げて見ましょう。クロックを上げると命令が早く実行されます。オーバーヘッドが減り、処理にも余裕が出てきます。クロックを上げるには、OSCCONというレジスタを操作します。

1407193

ビット6,5,4の3ビットでクロックの値を決定します。すべて1にすると8MHzになります。初期値はすべて0なので次のような3命令で111にします。

BSF     OSCCON , 6
BSF     OSCCON , 5
BSF     OSCCON , 4  ;クロック8MHz

BSFやBCFなどのビットをセット、クリアする命令は便利です。すすんで利用すべきです。なぜならこれらの命令は他のビットに影響を与えないからです。転送命令だとすべてのビットに影響しますから、レジスタのすべてのビットを理解しなければなりません。

さて、システムクロックは1/4に分周され、さらにプリスケーラーで分周されタイマーレジスタTMR0のカウントクロックになります。

1407195_2 

プリスケーラーで何分周すれば良いか計算して見ましょう。TMR0の最小カウント値は1で最大カウントは256ですから、この範囲に入るようにします。1/32秒間隔ということは32Hzに注目すると、

最小値1の場合の分周比 -> 32Hz = 2MHz*X
X = 32/2000000=1/62500

最大値256の場合の分周比 -> 32Hz*256 = 2MHz*X
X = 32*256/2000000=1/244

さて、カウント値が高い方が誤差が少ないですから、1/244に一番近い分周比を選ぶと1/256分周になります。

したがって、プリスケーラーの分周比は1/256になります。このときのTMR0のカウント値はいくつになるかというと

32Hz*X = 2MHz/256

X = 2000000 / 128 / 32 = 244

となります。1秒間隔のプログラムとまったく同じです。

さて、プリスケーラーの分周比はOPTION_REGで変更可能です。

1407196

下位3ビットで分周比を決定します。1/256分周は111ですが、これは初期値のままなので、特別な命令を記述する必要はありません。

1/32秒間隔で発生するタイマー割り込みのプログラムは以下の通りです。

1407201

| | コメント (0)

2014年7月19日 (土)

「天文屋のためのマイコン入門」始めの一歩

今日から、インターバルシャッターのプログラムを作り始めます。何から手を付けていけばよいか?

今までの説明をちゃんと読んでくれた方なら、このアプリケーションプログラムはタイマー割り込みベースのプログラムと想像できます。それでは、1秒間隔でタイマー割り込みが入るプログラムとすればよさそうです。

しかし、もっと重要な問題があります。プッシュスイッチです。このスイッチはインタバールシャッターの開始、停止をするスイッチですが、プッシュスイッチは、スライドスイッチやロータリースイッチと決定的に違う部分があります。

スライドスイッチやロータリースイッチはその状態が重要です。ONかOFFか、0~9のどれか。

一方、プッシュスイッチはONかOFFかの状態ではなく、OFF->ONとなる変化が重要です。状態ではなく動作です。このような場合は、割り込みが役立ちます。外部の信号の変化により割込みを発生させることができます。このような割り込みを外部割込みといいます。

しかし、PICの悲しいところでピン数が制約されているため、この外部割込みが使えるポートBにプッシュスイッチを付けることができませんでした。ポートBはほとんど7セグメントLEDに使われています。

それでは、割り込みが使えないとして、どのようにプッシュスイッチOFF->ONの変化をキャッチするかです。

単純に考えて、常にスイッチの状態を読み込んでおいて、前回の読み込みがOFFで、今回の読み込みがONなら、スイッチが押されたと判断してよさそうです。

ところが話はそう簡単ではありません。プッシュスイッチのような機械的なものは、押すときに非常に短い時間にON/OFFを繰り返す現象が発生します。

1407191

このような現象をチャタリングといいます。このチャタリングのため、OFF->ONを単純に検知するプログラムでは一回押しただけなのに、複数回押されたと判断してしまうミスが起きます。

このチャタリングの問題を回避するため、特別な回路を組むこともありますが、プログラム的になんとかする方法もあります。

そのためには、ある程度の時間間隔たとえば1/32秒とします。この間隔でスイッチの状態を読み込み、OFF->ON->ONという状態変化を検知したときのみ、スイッチが一回押されたと判断します。

1407192

なぜ、1/32秒かは、経験と実際にやってみて判断するしかありません。スイッチの種類によっても違います。短すぎると複数回押されたと判断されますし、長いとスイッチの反応がおそくなり、使っていてイライラします。

ということで、1/32秒間隔で発生するタイマー割り込みが主体のプログラムになることが期待されます。

それで、インタバルシャッターのプログラムの始めの一歩として次のようなプログラムを次回以降作成します。

プログラム

プッシュスイッチを一回押すとLEDが点灯する。もう一回押すと消灯する以下、繰り返す。

文章で書くと実に簡単なプログラムですが、実際作るのは大変です。このプログラムを作るためには1/32秒間隔でタイマー割込みを発生させますが、1秒間のタイマー割込みのプログラムは既に作成済みです。それをどう変更するか? 次回以降説明します。

| | コメント (0)

2014年7月15日 (火)

星太朗さんへの回答

画像処理に関して質問を受けましたが、コメント欄では狭いので、本記事にて回答します。

> ほんまかさん、こんばんは!
> 回答ありがとうございます!
>
> レベル調整で真ん中のスライダーを下げること=トーンカーブを山なりに持ち上げること
>
> これは、納得できますが、明るさをあげるとは、
> たとえば、ヒストグラム上のy=xを、傾きを変えずに原点だけ持ち上げる、たとえば、y=x+50、とかに。で、白とびが増える、つまり、全体的に持ち上げる、ということかな、と理解しています。

昔のフォトショップではそのとおりでしたが、現在では違います。(現在も従来方式の明るさコントラストのオプションがあります)

現在は、ほぼレベル調整やトーンカーブと同等になっています。これは次の原則をフォトショップが貫いたためだと思われます。

原則
輝度0は0に、255は255に

トーンカーブで言えば、ちょうど両端が固定されている感じです。y=x+50の方式ではこの原則が実現できません。256の諧調を有効に使うということでしょうか。

そのため、現在では、明るさ、レベル調整、トーンカーブ、すべて同等と思っていいと思います。つまりトーンカーブが万能で、レベル調整がそのサブセット仕様、明るさコントラストがさらにそのサブセットという感じです。


> コントラストを変化させるとは、(128,128)の点を中心に直線を回転させることで、コントラストをあげると、白とびと黒つぶれが、同時に起こる、ということ、と理解しています。
>
> 間違っていますか?

フォトショップの内部仕様がどうなっているかわかりませんが、ほぼその通りだと思います。

> コントラストは、情報が失われる割合が大きいので使いたくない、ってことですよね?今まで私は、バンバン使っていましたが・・・(笑)。

違います。ここは誤解があります。

まず、コントラストを上げること自体は情報落ちはありません。コントラストアップは、
1、2、3、4と並んだ諧調を1、4、6、8とするようなもんで諧調は荒くなりますが、情報落ちはありません。

いっぽう、コントラストダウンは、1、2、3、4と並んだ諧調を1、1、2、2と慣らすようなものです。こちらは、滑らかになりますが、情報落ちが発生します。

したがって、コントラストを上げること自体は情報落ちはありませんが、0から255の諧調のうち、どこかのコントラストを上げるということは、どこかのコントラスト下げなければならない、または切り捨てなければならなくなるので、結果として情報落ちが発生します。

私が、フォトショップの明るさ・コントラストコマンドのコントラストを使わないのは、その仕様が中間点127を中心にコントラストアップをしているからです。ほとんど自由度がないからです。コントラストを上げる処理自体を嫌がっているわけではありません。

> フォトショップの本、読み始めています。ほんまかさんのブログも、少しずつ読み始めています!
> 新しいことを知ると、すぐに試してみたくなるので、そのつど、再処理して、ブログの記事にしています。時間がありましたら、のぞいてみて下さいね。

はい、そうさせていただきます。

> 処理の回数が増えると、ざらざらになるのが、理解できるようになりましたが、これは、露出時間を増やしたり、枚数を増やしたりすることで、なめらかになるので、回避できるようになるのでしょうか?

コンポジット枚数を増やせば解消されます。ただ、処理の回数が増えるからザラザラになるわけではありません、次の質問で説明します。

> それとも、画像処理を進めることで現れるザラザラは、撮影とは別のものでしょうか?
> また、別物であれば、どのように対処されているのでしょうか?ステラナビには、バックグラウンドスムースと言うのがありますが、うまくいきません。これは使いこなしが難しいですね。

ザラザラになるのは、その画像が持ってるポテンシャル(コンポジット枚数とか露出時間とかノイズの少なさとか諧調の豊かさ)以上の強調処理(コントラストアップ)をその画像に課しているからです。画像が悲鳴を上げてます。

解消するには、画像のポテンシャルを上げるしかありません。早い話が露出時間とコンポ枚数です。または荒れるほど強調しないことです。

ただ、画像処理でごまかすことはできます。たとえば、背景で言えば、背景のコントラストを下げることです。トーンカーブにおいて、左側の曲線を寝かせるのはそのためです。ただ、これをやると淡い部分が背景に同化したり、暗黒星雲が消えてしまったりします。その辺の微妙な按配が難しいです。

あと、背景にぼかしなど加える方法もありますが、あくまでもごまかしで、以前はそれではなんとかなったのですが、最近の天体写真の主流としてそのようなごまかしは通用しなくなってきています。

とりあえず、ひとつの撮影対象に2~3時間かける覚悟がないとなかなかみなのように綺麗な写真にはなりません。

> 長々とすみません・・・。
> よろしくお願いします。

これからもがんばってください。

| | コメント (4)

2014年7月13日 (日)

「天文屋のためのマイコン入門」割込み最終説明続き

前回の復習です。

STATUSレジスタというのはゼロフラグのような各種フラグを格納しており、命令の結果により値がしゅっちゅう書き換わっています。このような不安定なレジスタを値を変えずに保存用のレジスタに格納しなければなりません。

そのため、次のような命令は許されません。

MOVF   STATUS,W

なぜなら、MOVFという命令は転送する値が0か0以外でどちらにしてもSTATUSレジスタの値が変わってしまいます。しかも、転送する前に値が変わってしまうのです。

そこで、フラグを変化させない命令でSTATUSレジスタをワーキングレジスタに転送させる必要があります。そのような命令があるか? 実はあるんです。(ないわけないです。ないならマイコンの命令セットとしては不完全ですから)

スワップという命令です。スワップというのは交換という意味です。スワップ命令はレジスタ単独間の命令とレジスタとワーキングレジスタ間の命令があります。まずは、レジスタとワーキングレジスタ間の命令を説明します。

1407131

転送命令とほとんど同じですが、転送するときに、レジスタfの上位4ビットと下位4ビットを交換してWへ転送します。交換して転送するというのが面白いです。で、この命令はフラグに影響を与えません。

もう一つは、上の命令のWをFに変えたもので、これはレジスタ単独の交換命令になります。

1407132

このスワップ命令はフラグを変化させません。この命令を使えば、STATUSレジスタをSTATUS_BUFに転送させることができます。

まず、ワーキングレジスタを保存しましょう。

MOVWF     W_BUF          ;W -> W_BUF   Wを保存

この命令の実行でフラグは変化しません。次に、STATUSレジスタを保存します。スワップ命令でワーキングレジスタに転送します。

SWAPF     STATUS , W   ;STATUS -> W  STATUSをWに交換転送

下位4ビットと上位4ビットが入れ替わり、ワーキングレジスタに保存されますが、実害はありません。戻すに、もう一度入れ替わるからです。

さて、STATUSレジスタの内容がWに転送されましたので、それをSTATUS_BUFに保存します。

MOVWF    STATUS_BUF    ;W -> STATUS_BUF

これで完成です。割り込みの先頭では次のように記述します。

ORG 4H
MOVWF     W_BUF
SWAPF      STATUS , W
MOVWF     STATUS_BUF

さて、次は割込み処理の出口です。ここでは上で保存したワーキングレジスタとSTATUSレジスタの内容を戻してやります。やはり、これらの値を変化させてはいけません。

まず、STATUSレジスタの内容を元に戻します。これを復帰といいます。STATUSを復帰させます。

最初にスワップ命令でSTATUS_BUFに保存してあったSTATUSの内容をWに戻します。

SWAPF     STATUS_BUF , W

Wの値が変化しますが、問題ありません、Wはまだ復帰していないからです。Wに入ったSTATUS_BUFの内容をSTATUSに転送します。

MOVWF    STATUS

これで、無事STATUSが復帰しました。フラグを変化させる命令は一切使っていません。

次にWを復帰させますが、既にSTATUSは復帰しているので、フラグを変化させる命令を使ってはいけません。そこでスワップ命令を使わざるを得ないのですが、

SWAPF    W_BUF , W

ただ、これだと、上位4ビットと下位4ビットが入れ替わってしまいます。そこでこの命令の前にもう一回スワップ命令をやって、予め4ビットを入れ替えておきます。

SWAPF    W_BUF , F
SWAPF    W_BUF , W

図で説明しましょう。

1407133

これで完成です。まとめです。割込み処理は先頭と終わりに次のような命令を記述します。これを記述することにより、WとSTATUSは割込み処理で値が変化することがないので、メインの処理に影響を与えることはありません。

STATUSの中にはバンク番号も入っているので、割込み処理の中でバンクを変化させても大丈夫です。

W_BUF              EQU     xxh
STATUS_BUF     EQU     xxh

;ここから割込み処理
ORG  4
;入り口の処理
MOVWF     W_BUF
SWAPF      STATUS , W
MOVWF     STATUS_BUF



;出口の処理
SWAPF    STATUS_BUF , W
MOVWF   STATUS
SWAPF    W_BUF , F
SWAPF    W_BUF , W
RETFIE

それにしてもめんどくさいですPICマイコン。普通のマイコンはこんな面倒ではないです。さて、割り込みの説明はこれで終わりですが、難しかったですか? 難しいなら、もう丸暗記するしかないですね。上記、入り口出口の処理は定型文として覚えるしかないですね。

次回、いよいよインターバルシャッターのプログラムを作ります。

| | コメント (0)

2014年7月12日 (土)

「天文屋のためのマイコン入門」割込み最終説明

今日は少し間が空きましたが、割り込みについて最後の説明をします。割り込みは一種のマルチタスクですが、マルチタスクには必ず「資源の競合」という問題がおきます。

今、通常のメイン処理で、PORTAレジスタに値10を入れるため、ワーキングレジスタに10を代入する命令と、それをPORTAに代入する2つの命令があるとします。

1407121

この2つの命令の間に、割り込みが入り、割込み処理でワーキングレジスタの値が書き換わったとします。この場合、PORTAには10の値は入らず、何か違う値になっています。これはまずいですね。このような不都合を資源の競合といいます。

同じようなことがゼロフラグでも起こります。レジスタをデクリメントしてそれが0かどうかでスキップするか否かの命令があるとします。ちょうどこの瞬間に割り込みが入り、フラグが書き換わったら(ほとんどの命令はフラグを書き換える)、元のメイン処理に戻っても正常な処理はできないでしょう。

1407122

また同じようなことがバンク番号や他の特殊なレジスタでも起こります。

このような不都合を避けるには、メイン処理と割込み処理の両方でワーキングレジスタまたはフラグを使わないことです。ただそれは無理です。ワーキングレジスタを使わなければ、できない処理もありますし、ほとんどの命令はフラグの値を変化させます。

ちなみに前回出したタイマー割込みの例ではメイン処理で何もしていなかったので、正常にプログラムが動作したのでした。

ではどうすれば良いか。

その一つ解決方法が、割込み処理の先頭で、ワーキングレジスタとフラグ(つまりSTATUSレジスタ)の値をどこかに保存しておいて、割り込み処理の最後に元の値に戻してやります。

具体的な方法はこうです。

まず、ワーキングレジスタとSTATUSレジスタの保存先を、126番地と127番地の汎用レジスタとします。そこでシンボルを次のように定義します。

W_BUF            EQU  126
STATUS_BUF   EQU  127

そして、割込み処理の先頭でワーキングレジスタとSTATUSレジスタをW_BUFとSTATUS_BUFに転送しなければならないのですが、これが実はやっかいなのです。

ORG  4
W -> W_BUF
STATUS -> STATUS_BUF

なぜなら、その過程でワーキングレジスタとSTATUSレジスタの値を変化させてはいけないからです。特にSTATUSレジスタの方がやっかいです。というのはほとんどの命令がフラグを変化させるからです。つまりSTATUSレジスタが変化する。

その命令がフラグを変化させるかどうかは、PICのデータシートの命令表のところを見ます。

1407123

この表の赤枠の部分が変化するフラグです。ここは空白の命令だけ使えます。

たとえば割込み処理の先頭で次のような命令を書いたらどうでしょう?

ORG 4
MOVWF    W_BUF           ;1 W -> W_BUF    ワーキングレジスタ保存
MOVF      STATUS, W     ;2 STATUS -> W  STATUSレジスタをWに転送
MOVWF   STAUS_BUF     ;3 W -> STATUS_BUF    それを保存

1と3のMOVWF命令はフラグを変化させないので問題ありません、ところが2のMOVF命令は問題です。この命令はその結果によって、たとえば転送した値が0かどうかによってフラグが変化します。つまりSTATUSレジスタの値が変わってしまいます。

1407124

つまり、STATUSレジスタの値を保存したいのに、その保存する命令によってもともとの値が変わってしまうのです。これはすごい自己矛盾です。

実は、STATUSレジスタのフラグが変化するタイミングが転送後なら、何にも問題なかったのです。転送する前に、その値によってSTATUSレジスタのフラグの値が変わってしまいます。変わった後のSTATUSレジスタの値を保存したって、何の役にもたちません。メイン処理には、STATUSレジスタを元の状態に戻して返してやる必要があるのです。

それでは、この問題をどうやって解決するか次回、説明します。

| | コメント (0)

2014年7月 5日 (土)

「天文屋のためのマイコン入門」割り込みを掘り下げる

実際に割り込みプログラムを組んで見ましたが、いかがだったでしょうか。インターバルシャッターのプログラムも、モータードライブのプログラムもタイマー割り込みをベースとしたプログラムになります。
今日は割り込み処理について、深く考察してみみます。

まず、サブルーチンとの比較をしてみます。割り込み処理はサブルーチンと似ています。違いはCALL命令がないことです。サブルーチンはCALL命令でプログラム中に意図的に記述しないと呼ばれませんが、割り込み処理はハードウェアにより自動的に呼ばれます。

サブルーチンはCALL命令により、戻り番地が保存され、RETURN命令により、その戻り番地に復帰します。割り込み処理も同じような機構が必要です。そうでないと、やはり元の場所に復帰できません。実は、割り込み処理も、割り込みが発生するとハードにより、戻り番地が保存されます。しかもこの保存先は、サブルーチンと共通です。
とういうことは、サブルーチンのネスト呼び出しは8段までという制約は、割り込み処理も含まれます。たとえばサブルーチン実行中はネストレベル1とします。ここで別のサブルーチンを呼ぶとネストレベルは2になります。ここで割り込みが発生するとネストレベルは3になります。

1407051

サブルーチンも割り込み処理も同じ場所に戻り番地が保存されるなら、割り込み処理もRETURN命令で戻れるはずです。なぜ割り込み処理は専用のRETFIE命令を使うのでしょうか。実は、割り込み処理には独得の決まりがあります。これについてはこれから説明します。

ところで、割り込み処理中に、別の割り込みが発生するでしょうか。たとえばタイマー割り込みでも割り込み処理に時間がかかると、次のオーバーフローが発生して、割り込み処理中にまた割り込みが発生する懸念があります。
また、違う種類の割り込みを同時に許可している場合もあります。
この答えはノーです。割り込み処理中に別の割り込みは発生しません。正確にいうと、発生を禁止しています。

INTCONレジスタのGIEビットを思い出してください。このビットを0にすると、全ての割り込みが禁止されます。実は割込みが発生すると、このビットが自動でクリアされ、割り込みが禁止されます。
GIEビットを0にすることにより割り込みを禁止しているので、割り込み処理の中でGIEビットを1にセットすれば割り込みは発生します。これを多重割り込みといいます。ただ多重割り込みを許可すると、後で説明する資源の競合という面倒な問題が起こるので、普通はGEビットは0のままにして多重割り込み禁止にします。

さて、割り込み処理ではGIEビットが自動で0にクリアされ、割り込みが禁止されますが、このままでは、永久に割り込みが禁止され2回目の割り込みが発生しなくなります。ですから割り込み処理から戻る前にGIEビットを1に戻して再度割り込み許可状態にする必要があります。しかし、RETFIE命令はこの処理を自動でやってくれます。ここがサブルーチンのRETURN命令とちがうところです。
つまり、

RETFIF
||
BSF INTCON,7
RETURN


ということができます。試しに前回のプログラムを上記のように変えてやってみてください。ちゃんと動くはずです。

1407052

(訂正GIではなくGIEです。)

ここでひとつ注意があります。割り込み処理の中で禁止しているのは実際の割り込みがであって、割り込み要求まで禁止しているわけではありません。どうゆうことかというと、割り込み禁止状態であっても、タイマーがオーバーフローすれば、タイマーオーバーフロービットは1にセットされます。
この場合、割り込み処理から抜けた瞬間、つまりRETFIF命令直後にすぐさま次の割り込みが発生します。だから、割り込み禁止状態であっても、割り込み要求が無視されるわけではありません。 待たされるのです。
もっとも、この待たされている間に、また割り込み要求がでれば、つまり3番目の割り込みは、本当に無視されます。病院の待合室の椅子は一席までということです。

さて、今日は割り込みについてちょっと掘り下げてみましたが、次回さらに検討してみます。割り込みは奥が深いです。いますぐ必要な知識ではありませんが、いつかきっと役立ちます。

| | コメント (0)

2014年7月 4日 (金)

α7sリモートコマンダーの改造

α7sのリモートコマンダーです。名前が立派ですね。

1407042

分解します。

1407043

スイッチに線を半田付けします。

1407044

線にコブをつけて、ストラップ留め金の穴から出します。

1407045

完成!

1407046

これでマイコンからシャッターがきれるようになりました。

おしまい。

| | コメント (0)

「天文屋のためのマイコン入門」タイマー割込みのプログラム

それでは、実際に割込みのプログラムを作って見ましょう。以前出したLEDが一秒間隔で点滅するプログラムですが、これを割込み仕様で記述してみます。

その前に、復習です。

1 タイマー0オーバーフロー割り込みは次の2行で初期化できる
   BSF   INTCON , 5   ;TMR0IE=1
   BSF   INTCON , 7   ;GE =1

(前回出した時は5のところが誤って2となっていました。ごめん)

2 割込み処理は4番地から記述する

これを踏まえてプログラムを掲載します。ただし、最初のシンボル定義の部分は前回と同じなので、省略しています。

1407041

上から説明していきます。

;秒数をカウントする
TIME_COUNT EQU 70h
(上の図では0f0hとなっていますが間違えです)

70h番地の汎用レジスタを秒数を保持する記憶領域として使用することとします。70hから7fh番地までの汎用レジスタはどのバンクからでもアクセスできるので、プログラム中で使う記憶領域はこの領域から選ぶと良いでしょう。この汎用レジスタにTIME_COUNTと名前をつけています。

次に、割込み処理部について説明します。

MOVLW -244
MOVWF TMR0  ;TMR0を再初期化

まず、割込み処理の一番最初でTMR0の再初期化を行っています。再び、TMR0に-244を代入することにより、1秒後に再び割込みが入るようにします。

INCF TIME_COUNT ;タイムカウントを増やす

次に、先ほどシンボルを定義したTIME_COUNTをインクリメントして秒数をカウントしています。

BTFSS TIME_COUNT,0;奇数ならスキップ

この一行は何をしているかというと、秒数が奇数か偶数の判別をしています。秒数が奇数ならLEDを点灯させて、偶数なら消灯させるためです。これによりLEDが点滅して見えます。奇数かどうかの判定は、一番下のビットで判断できます。一番下のビットが1なら奇数、0なら偶数です。

GOTO INT_SKIP1
  BCF  PORTA,4 ;LED消灯
  GOTO INT_END
INT_SKIP1
  BSF  PORTA,4 ;LED点灯
INT_END

奇数ならLEDを点灯し、偶数なら消灯させる部分です。

次に、割込み処理の終了部分ですが、これが重要です。

BCF  INTCON,2 ;重要! オーバーフローフラグクリア

オーバーフローフラグ、またはオーバーフロー割込み要求フラグとも言いますが、それをクリアしています。これは自動でクリアされないので、このようにプログラムでクリアしてやる必要があります。

これをクリアしないと、割込み処理から抜けた後、すぐまた割り込みが入ってしまいます。マイコンはタイマーがオーバーフローしたから割り込みを入れるのではありません。オーバーフローにより割込み要求フラグがセットされたから割込みが入るのです。マイコンは割込み要求フラグだけを見て、割り込みを入れているのです。ですから、このフラグは必ずクリアさせておきます。

最後に割込み処理から抜けるのですが、サブルーチンから戻るにはRETURN命令を使いました。割込み処理から抜けるには、

RETFIE    ;割込みからのリターン

の命令を記述します。サブルーチンのRETURN命令とほぼ同じ動作をします。割込み処理の最後には必ずこれを記述します。

次に、初期化部の説明ですが、前半の部分は以前と同じなので、説明を省略します。

CLRF TIME_COUNT ;タイムカウント0クリア

秒数を0にクリアしています。

MOVLW -244
MOVWF TMR0  ;TMR0初期化

タイマーレジスタTMR0に-244を代入しています。オーバーフローするまで1秒に設定します。なぜ、244かは前回説明しました。

;タイマー0オーバーフロー割り込みの初期化
   BSF   INTCON , 5   ;TMR0IE=1
   BSF   INTCON , 7   ;GE =1

この部分は割込みの許可です。最初の命令でタイマー0オーバーフロー割込みを個別に許可しています。2番目の命令で、全割込み禁止を解除して、割り込みが発生できるようにしています。これ以後、タイマーがオーバーフロー次第、割込みが入ります。

最後にメイン処理部の説明ですが、

LOOP
GOTO LOOP

何もしてません、ただ無限ループを繰り返して、割込みが入るのを待っています。このプログラムは実質的には割込み処理がすべてです。

割込み処理の実際、いかがでしょうか? 実はこの割込み処理は不完全です。もう少し割込み処理について考えてみる必要があります。あとは次回に。

| | コメント (0)

2014年7月 3日 (木)

「天文屋のためのマイコン入門」割り込みを使おう

マイコンのプログラムでは、あるきっかけに反応して、今までやっていた処理を中断して、別の処理を強制的にやりたい時があります。

この別の処理を割り込み処理といいます。そのまんまのネーミングですね。あるきっかけとは、たとえば、外部からの信号が変化した、タイマーがオーバーフローした、シリアル通信でデータを受信した。などです。

1407031

割り込みを使うのは簡単です。INTCONという割込みに関係したレジスタを操作するだけです。

割り込み処理は、4番地から記述する決まりがあります。マイコンは割り込みが発生すると強制的に4番地にジャンプします。ですから、4番地にちゃんと割り込み処理を記述しておかないとプログラムは暴走します。

4番地にプログラムを記述するにはどうしたらいいでしょうか。実は次のような擬似命令を記述します。

ORG  4

ORGはOriginの略で、この擬似命令の後に命令を記述すれば、4番地からプログラムが記述できます。

しかし、ここで困ったことがあります。割り込み処理は4番地から記述しますが、通常のプログラムは0番地から始まります。つまり、通常のプログラムの真っ只中に割り込み処理があることになります。ほんとうに割り込みですね。そこで通常は0番地の最初の命令はGOTO命令を記述して、割り込み処理を飛び越しさせます。

1407032

INTCONレジスタの説明

INTCONレジスタは実はタイマーの説明でもう出てきましたね。そう、タイマーがオーバーフローした時にセットされるフラグを有するレジスタでした。その復習も含めて以下に説明します。

1407033

例としてタイマー0オーバーフロー割込みについて説明します。タイマー0がオーバーフローするとbit2のTMR0IFが1にセットされます。これはタイマーのところで説明しましたね。このビットのことを割込み要求フラグと言います。つまり、タイマーがオーバーフローしたので、割り込みを発生してくれという要求のためのビットです。

ただ、割込み要求フラグがセットされていても無条件で割込みが発生するわけではありません。

割り込みが実際に発生するためには、bit5のTMR0IEが1で、かつbit7のGIEも1でなくてはなりません。

TMR0IEはタイマー0オーバーフロー割込みを個別に許可するビットで、GIEは割込み全体を許可するビットです。GIEが0の場合は他のビットがどうであれ、すべての割込みは禁止されます。

したがって、タイマー0オーバーフロー割り込みを使うには、GIEとTMR0IEを両方とも1にセットする命令を記述しなければなりません。

;タイマー0オーバーフロー割り込みの初期化
  BSF   INTCON , 2   ;TMR0IE=1
  BSF   INTCON , 7   ;GE =1

2番目の命令を実行した時点で割込みが発生する可能性が出てきますので、2番目の命令を記述する場所は特に注意する必要があります。必ずしも最初の初期化の部分に書けば良いというもではありません。

割込みはマイコンの概念の中では難しい部類です。PICの場合は特に難しいです。あせらず、3回くらいに分けて説明します。今日はここまで。

| | コメント (0)

2014年7月 2日 (水)

「天文屋のためのマイコン入門」シンボルプログラミング

もういい加減、シンボルを使わないとつらくなってきました。ここでシンボルプログラミングを説明したいと思います。もう一部使っているんですが、ラベルがそうです。

たとえば、レジスタの番地、これは今まで、次のように数値を直接記述してきました。

BSF     5h,4

これだと、5番地がどのレジスタを意味しているか、いちいち調べなければなりません。あるいは覚えていないといけません。

そこで、この数値をもっとわかりやすい記号、またはシンボルに割り当てるということができます。

このシンボルはユーザがわかりやすいように自由に名づけることができます。ただ多少、決まりがあります。使える文字は、英小文字、大文字、数字、下線(_)などです。ただし、先頭が数字であってはいけません。なぜなら数字あるいは16進数と区別できなくなるからです。

たとえば、上記の例の場合、ポートAなので、これをPORTAと名づけることにします。PORT_AでもPAでも良いのですが、レジスタの場合はPICのデータシートに書かれた名称を使うのが通例です。

さて、PORTAというシンボルに数値の5を割り当てます。次のような擬似命令を記述します。

PORTA  EQU  5h

EQUというのが擬似命令です。一般的な書式は次の通りです。

シンボル EQU  数(10進数でも2進数でも16進数でも何でも良い、式もOK)

この文は擬似命令ですが、先頭に空白を入れてはいけません。(この仕様はチグハグですがシンボルがラベルだという認識のためでしょう。でもこれはラベルではありません、言語仕様に一貫性がありません)

この記述はこのシンボルを実際に使うより前に記述しなければなりません。普通はファイルの先頭のほうに記述します。

この記述をすることによりPORTAは数値の5とまったく同等に扱えます。数値ですから、次のように式の中に使うこともできます。

PORTA+1

さて、このようにシンボルを使って書き換えたプログラムを以下に示します。赤枠の部分がシンボルの定義部分です。

1407011

EQUは、シンボルに数値を割り当てる擬似命令でした。シンボルに任意の文字列を割り当てる機能もあります。

たとえば、バンク1に移行する命令は

BSF    3h,5

ですが、この文字列"BSF   3h,5"をBANK1というシンボルに割り当てます。

#define    BANK1   BSF  3h,5

一般的な書式は

#define   シンボル 任意の文字列

です。この割り当てによって、バンク1に移行する命令は

BANK1

で記述できます。

割り当てられるのは任意の文字列ですから、何でもかまいません。もちろん数値もOKです。たとえば、前述の

PORTA EQU 5h

は次のように文字列として割り当てることも可能です。

#define  PORTA  5h

こうすると、#defineのほうが万能でEQUは必要ないように思えますが、数値は数値として割り当てたほうが、安全です。たとえば、次のような例を見てください。

A   EQU    5+1

このAを、たとえば2*Aという式の中で使うとします。(*は掛け算)

EQU 数値で割り当てた場合
2*A --> 2*6 --> 12

文字列で割り当てた場合
2*A --> 2*5+1 --> 11

おそらくプログラマの意図としては12が正解と思いますので、やはり数値はEQUで割り当てるの安心です。文字列として割り当てるなら、次のように括弧をつければよいです。

#define  A   (5+1)

さて、この#defineを使ったプログラム例を以下に示します。

1407012

今日はここまで、次回は割り込みについて。

| | コメント (0)

2014年7月 1日 (火)

湯沢遠征

いって来ます。

今日は、BORG67FL+α7S

曇られました。 遠征失敗。

| | コメント (0)

« 2014年6月 | トップページ | 2014年8月 »