●チュートリアルの目標
それではPICNIC+周辺拡張オプションキットを用いたチュートリアルを開始します。
このチュートリアルでは、
・dummyモジュールによるcall/return(実験1、2)
・ドアセンサ→LED・リレー出力(実験3〜7)
についてのプログラミングを実装例を示しながら行っていきます。
最後に、課題0(実験8)として、
「在宅・留守モードを考慮したドア開放警報アラーム」
機能を実装し、その課題提出を行ってもらいます。
●メモリ使用状況とPAGE間ジャンプ
さて、メインループmain0〜main99の間に、直接、課題0までのコードを実装していきたくなる、逸る気持ちは判りますが、ちょっと待ってください。
よし、と思って、main0の直後にイソイソと実装してみると、ある程度までコードを書いた後、web制御画面の反応が無くなったり、HTML制御画面が途中までしか表示されなくなったり、ブラウザの「受信中」アイコンが、ずーっと受信中になったりして、症状だけを見ると「不安定」になったように見えます。
<障害の原因>
現状のファームウェアの実装では、プログラムメモリPAGE#0は境界、ギリギリ一杯まで使用してしまっています。
メモリ使用状況:
PAGE#0 : 0x0000-0x07FF : 0x0000--0x07EA 使用中 PAGE#1 : 0x0800-0x0FFF : 0x0820--0x0FED 使用中 PAGE#2 : 0x1000-0x17FF : 0x1000--0x1240、0x1300--0x13FB、0x1400--0x14EB 0x1700--0x17FF 使用中 PAGE#3 : 0x1800-0x1FFF : 0x1800--0x185A、0x1A00--0x1B19、0x1D00--0x1DA5 0x1F00--0x1FC1 使用中
|
たとえば、PAGE#1において、put_socket_statモジュール(Webブラウザにソケットステータスを送る(いわゆるnetstat
-n))がコメントアウトされていますが、これは、PAGE#1が既に一杯で、これを削らないとPAGE#1内の他モジュールが収納できないためです。ファームウェアの開発は、メモリの1ワードは血の一滴と同じ、と捉えて、限りあるメモリ空間をギリギリまで有効利用していくものです。
<解決方法>
現在の機能を保全したままで、追加モジュールを入れられる場所としては、
PAGE#3 1E00h--1EFFh 256ワード
の領域くらいしかありません。
これも、webメッセージのバンク#4の直後なので、このバンク#4のメッセージ量が256ワードを超えないように、十分に注意する必要があります。
以下のチュートリアルでは、この1E00hから、実験用のモジュールを実装していき、動作確認のため、main0ラベル直後に、この実験用モジュールを「PAGE間を跨いで」callするコードを書きます。
(PIC16F87xは4つのプログラムメモリページを有する。何のことか判らない人は、ここへ戻って復習せよ(11ページ参照))
それでは、具体的に実装をすすめて行きましょう。
●ファームウェアver1.2.0.4(b)について (2004/10/30修正)
第4章 ファームウェアの更新作業 において、私たちはオリジナルのver1.2.0.0(v12.asm)から、
・ファームウェアバージョン番号の更新
・web制御画面のフッタ部分(webメッセージ・バンク#4の最後)へ一行追加
の2点の改良を行い、ver1.2.0.1としてupdateしたのでした。
ver1.2.0.0 ※配布オリジナル
ver1.2.0.1 変更履歴
0.0→0.1
この後、種々の軽微な変更が加えられ、このチュートリアルのための「出発点」となるバージョン ver1.2.0.4(b) としてupdateがなされました。
ver1.2.0.2 変更履歴
0.1→0.2
ver1.2.0.3 変更履歴
0.2→0.3
ver1.2.0.4 変更履歴 0.3→0.4
ver1.2.0.4(a) 変更履歴 0.4→0.4(a)
ver1.2.0.4(b) 変更履歴 0.4(a)→0.4(b)
最新バージョンにおけるweb制御画面の様子はここから
※http://192.168.1.200:8080/として取得
以下のチュートリアルでは、このver1.2.0.4(b)版のファームウェアに対する改造を前提としています。もちろん、オリジナルのファームウェア(v12.asm)から改造を施しても同様に動作しますから安心してください。
最新バージョンのファームウェアのダウンロードはここから(2004年10月30日改訂・v1204org.asm)
※注意点
defaultのIPアドレス:httpポート番号から、
・IPアドレス:192.168.1.200(0.200ではない)
・httpポート:8080(80ではない)
に変更しています。
このファームウェア雛形を利用する場合には、操作用PCのネットワークセグメントを
・IPアドレス:192.168.1.10
・ネットマスク:255.255.255.0
などとして運用してください。(何のことか判らない人は、ここへ戻って復習せよ)
※2003/10/12修正(a):
2003年9月8日作成のv1204org.asmでは、HTMLレスポンス部分で\r\nの後ろに0x00のゴミを出すようになっており、この影響で、一部のwebブラウザ(Netscape7.1など)が
text/htmlのメッセージタイプであると解釈できず、誤動作する報告がありました。このため、HTMLレスポンス部分をDA行で一気に出力するように変更しました。受講生の皆様には、ご迷惑をおかけして申し訳ございませんでした。
※2004/10/30修正(b):
v1204org(a).asmでは、556行目にMPASM疑似命令が含まれており、MacOSX向けGNUPIC (gpasm) アセンブラではエラーとなっておりましたので、これを削除しました。
●dummyモジュールによるcall/return
【実験1】dummyモジュールの作成
【実験2】STATUSレジスタ値保全版 dummy2モジュールの作成
e0.4.1 【実験1】dummyモジュールの作成
dummyモジュールを作って実験してみましょう。
<仕様>
このモジュールは、別ページ(PAGE#3, 1E00h〜?)に dummyモジュール(何もせずに返ってくる)を作って、これに対してPAGE切り替え(PCLATH設定)して、call
し、ラベルmain1へ戻るように仕向けてみるものです。
<動作>
特に変化はありません。
<処理概要>
プログラムメモリのPAGE間を跨ぐcallについて、その基本的な処理手順としては、
・callするモジュールのPAGE位置(#0〜#3)を知る
・PAGE制御レジスタのセット
・モジュール呼び出し(call)
・PAGE制御レジスタのリセット(現在のPAGE位置依存)
となります。
<実装>
具体的な実装は以下のようになります。
+++++++++++++++++++++++++++++ PAGE#0 +++++++++++++++++++++++++++++ main0
movlw HIGH (dummy) ; dummy >> 8 PAGE位置取得 movwf PCLATH ; PAGE制御レジスタセット call dummy ; dummyモジュール呼び出し clrf PCLATH ; PAGE制御レジスタリセット
main1 ......
+++++++++++++++++++++++++++++ PAGE#3 +++++++++++++++++++++++++++++ org 1E00h
dummy nop return
+++++++++++++++++++++++++++++
|
【call dummy呼び出し側】
main0ラベルは、860行目付近で見つかります(メモリバンク#0)。main0ラベルから、main99ラベル直前のget_packetモジュールへのジャンプまでの間が、メインループになっています(定期的にこの間のコードが実行される)。main0ラベルの直後に、上記の4ステップによる呼び出し処理を追加します。
※注意点:dummyモジュール呼び出し側で追加する4行については、既存のmain0ラベルの直後に必ず配置してください。この位置であれば、メインループによって、必ず定期的な実行が行われます。(2005/5/3追記)
【dummyモジュールの実装場所】
dummyモジュールを配置する場所は、7243行目付近の、メモリバンク#3
Webブラウザ用・メッセージここまで [ver0.4]
と
EEPROMデータ初期値
の間が、org 1E00h するのに適当な位置なので、この間に実装していきます。dummyモジュールは、nop命令(何もしない)を実行後、returnするだけの処理です。
【実験1】
main0からの呼び出し、ならびにdummyモジュールを実装し、正常動作を確かめよ。
dummyを呼び出しても何も作業をせずに戻ってくるだけであるから、従前のver1.2.0.4と動作は変化しないはずである。
|
e0.4.2 【実験2】STATUSレジスタ値保全版 dummy2モジュールの作成
実験1がうまく動作したら、次に、STATUSレジスタ値保全版 dummy2モジュールの作成を行いましょう。
<仕様>
割り込みルーチンと違い、main0付近では Wレジスタ、FSRなど変更されても問題ないので、dummy内部でのコンテクスト保存・復帰処理は当座は必要ないように思われますが、STATUSレジスタの変更は注意を要する場面が出てくるので、これだけcall直後に保存、return直前に復帰という処理を入れます。
<動作>
特に変化はありません。
<処理概要>
STATUSレジスタ保存には、共通変数 wk2 を使用します(wk2 07FH)。
wk2は、どのレジスタバンクBANK#0--#3からでも同じように見えていて便利です。
microchip/30292a-j.pdf#page=13
割り込みルーチン内でwk2を初期化なしに使用しているような場合は、wk2の退避処理を行ってからwk2を使う、というイタチごっこな実装になりますが、現状のファームウェアでは、このdummy2がcallされている間にwk2は使用されていないので、wk2は退避せず、STATUSレジスタの退避に使うこととします(←ややこしいが理解せよ)。
当然のことながら、wk2へSTATUSレジスタの値を退避しているのであるから、STATUSを復帰させるまでの間、wk2は変更不可です。
<実装>
具体的な実装は以下のようになります。
+++++++++++++++++++++++++++++ PAGE#0 +++++++++++++++++++++++++++++ main0
movlw HIGH (dummy2) ; dummy2 >> 8 PAGE位置取得 movwf PCLATH ; PAGE制御レジスタセット call dummy2 ; dummy2モジュール呼び出し clrf PCLATH ; PAGE制御レジスタリセット
main1 ......
+++++++++++++++++++++++++++++ PAGE#3 +++++++++++++++++++++++++++++ org 1E00h
dummy2 ; STATUS待避処理 swapf STATUS, 0 ; STATUSレジスタを保存 clrf STATUS ; STATUSを0にする movwf wk2 ; wk2へ退避
nop ; 何もしない
; STATUS復帰処理 swapf wk2,0 movwf STATUS ; statusを復帰
return
+++++++++++++++++++++++++++++
|
これで、STATUSレジスタを退避させている間(nopを行っている位置)では、好きなだけSTATUSのRP0,RP1ビットを操作しても構わない状態になっています。これで、以下の実験向けの雛形が出来上がりました
:-)
【実験2】
main0からの呼び出し、ならびにSTATUSレジスタ値保全版のdummy2モジュールを実装し、正常動作を確かめよ。
dummy2を呼び出しても何も作業をせずに戻ってくるだけであるから、従前のver1.2.0.4と動作は変わりないはずである。
|
●ドアセンサ→LED・リレー出力
【実験3】RB4(LED#4)への固定値("H")出力 test1モジュールの作成
【実験4】RB0(ドアセンサ)→RB4(LED#4)へ出力 test2モジュールの作成
【実験5】RB0(ドアセンサ)→RB4(LED#4)へ負論理出力 test2aモジュールの作成
【実験6】RB0(ドアセンサ)→RB7(Relay#2)へ負論理出力 test2bモジュールの作成
【実験7】RB0(ドアセンサ)→RB4へ正論理&RB7へ負論理出力 test2cモジュールの作成
e0.5.1 【実験3】RB4(LED#4)への固定値("H")出力 test1モジュールの作成
PORTBへの値出力の練習として、RB4(LED#4)への固定値("H")出力を行うような、test1モジュールを作成しましょう。
<仕様>
RB4(LED#4)への固定値("H")出力
<動作>
動作としては、リセット後、常に RB4=High(LED#4点灯)となります。
<処理概要>
RB4は、レジスタバンクBANK#0 or #2 のPORTB[bit4]に割り当てられている。
microchip/30292a-j.pdf#page=13
レジスタバンクBANK#0 or #2 への切り替えは、STATUSレジスタのRP0ビットをクリアすればOK。
PORTB[bit4]をHにセットするには、movfでまずPORTBをリードしてからbit4をOR論理で強制的にHにし、その後、PORTBへ値を書き戻す(ライト)すれば良い
<実装>
具体的な実装は以下のようになります。
なお、以後、main0側の変更点、ならびにモジュール先頭・最後でのSTATUS保全処理については省略します。以下の4行は、dummy2 の
nop と入れ替えます。
当然のことながら、モジュールエントリラベルはdummy2でなくtest1にして下さい。
org 1E00h
test1
............ ; STATUS退避
bcf STATUS,RP0 ; レジスタBANK#0/2 movf PORTB,0 ; PORTBを読む→regW iorlw B'00010000' ; RB4をHにする movwf PORTB ; regW→PORTBへ出力
............ ; STATUS復帰
return
|
これで、?RB4=Lを指示しても、常にHを保持するようになりました。
(web制御画面から指示すると一瞬は "L" になるようだ)
【実験3】
main0からの呼び出し、ならびにtest1モジュールを実装し、正常動作を確かめよ。
RB4が常にHを保持ことを確認せよ。それ以外は、何も変更をせずに戻ってくるだけであるから、従前のver1.2.0.4と動作は変わりないはずである。
|
e0.5.2 【実験4】RB0(ドアセンサ)→RB4(LED#4)へ出力 test2モジュールの作成
次に、条件分岐命令の復習として、RB0(ドアセンサ)の状態値を、そのままRB4(LED#4)へ出力するような、test2モジュールを作成してみましょう。
<仕様>
※ポート割り当て:
PORTB[0](RB0):入力(ドアセンサ・ドア閉鎖状態のとき"H")
PORTB[4](RB4):出力(LED#4 ドア状態表示ランプ・ドア閉鎖状態のとき"点灯")
RB0(ドアセンサ)→RB4(LED#4)へ出力(正論理)
<動作>
動作としては、
・ドア開放(RB0=="L")→LED#4消灯(RB4:="L")
・ドア閉鎖(RB0=="H")→LED#4点灯(RB4:="H")
のように、ドアセンサの状態を常に反映した出力となります。
<処理概要>
RB0,4は、レジスタバンクBANK#0 or #2 のPORTB[bit0]ならびに[bit4]に割り当てられている。
microchip/30292a-j.pdf#page=13
レジスタバンクBANK#0 or #2 への切り替えは、実験3同様、STATUSレジスタのRP0ビットをクリアすればOK。
PORTB[0](RB0)の状態を調べる if (PORTB[0](RB0) == H) PORTB[4](RB4) := "H" else PORTB[4](RB4) := "L" endif
|
PORTB[bit0]の状態を調べるには、movfでまずPORTBをリードして作業メモリ wk1 へコピーしてから、そのbit検査を行う(btfsc命令など)(Wレジスタを破壊したくないため)
Wレジスタ保存には、共通変数 wk1 を使用する(wk1 07EH)。
wk1は、どのレジスタバンクBANK#0--#3からでも同じように見える。
microchip/30292a-j.pdf#page=13
・PORTB[bit4]をHにセットする方法は、実験3の通り(bit4をOR論理で強制的にH)。
・PORTB[bit4]をLにセットする方法は、bit4をAND論理によるビットマスクで強制的にLにした後、PORTBへ値を書き戻す(ライト)すれば良い。
<実装>
具体的な実装は以下のようになります。
org 1E00h
test2
............ ; STATUS退避
bcf STATUS,RP0 ; レジスタBANK#0/2 movf PORTB,0 ; PORTBを読む→regW movwf wk1 ; wk1にコピー
btfsc wk1,0 ; RB0のビット検査 goto test2_01
andlw B'11101111' ; RB4をLにする goto test2_02
test2_01 iorlw B'00010000' ; RB4をHにする test2_02 movwf PORTB ; regW→PORTBへ出力
............ ; STATUS復帰
return
|
これで、RB0(ドアセンサ)の状態値が、そのままRB4(LED#4)へ出力されるようになった。(つまり、ドアが閉じていると、LED#4が点灯する)
【実験4】
main0からの呼び出し、ならびにtest2モジュールを実装し、正常動作を確かめよ。
RB0(ドアセンサ)の状態が、そのままRB4(LED#4)へ出力される(ドアが閉じていると、LED#4が点灯する)ことを確認せよ。
|
e0.5.3 【実験5】RB0(ドアセンサ)→RB4(LED#4)へ負論理出力 test2aモジュールの作成
test2モジュールを改造して、RB0(ドアセンサ)の状態値を、負論理としてRB4(LED#4)へ出力するような、test2aモジュールを作成する。つまり、セキュリティシステムとしては、test2とは逆の意味付けをして、
ドアが開放された時(危険な時)にランプが点灯する(=ドア開放警告)
としてみる。
<仕様>
RB0(ドアセンサ)→RB4(LED#4)へ出力(負論理)
<動作>
動作としては、
・ドア開放(RB0=="L")→LED#4点灯(RB4:="H")
・ドア閉鎖(RB0=="H")→LED#4消灯(RB4:="L")
のように、「ドア開放時の警告ランプ」としての出力となります。
PORTB[0](RB0)の状態を調べる if (PORTB[0](RB0) == L) PORTB[4](RB4) := "H" else PORTB[4](RB4) := "L" endif
|
【実験5】
上記のような仕様のtest2aモジュールを実装し、「ドア開放時の警告ランプ」としての正常動作を確かめよ。
|
<ヒント>
ドア状態値に応じた条件ジャンプの論理を逆にすればよい。
(変更前)
btfsc wk1,0 ; RB0のビット検査
(変更後)
btfss wk1,0 ; RB0のビット検査
e0.5.4 【実験6】RB0(ドアセンサ)→RB7(Relay#2)へ負論理出力 test2bモジュールの作成
<仕様>
ドアが開放された時(危険な時)にLED#7ランプが点灯し、警報アラームを鳴らす(=ドア開放警告アラーム)
<処理>
PORTB[0](RB0)の状態を調べる if (PORTB[0](RB0) == L) PORTB[7](RB7) := "H" else PORTB[7](RB7) := "L" endif
|
【実験6】
上記のような仕様のtest2bモジュールを実装し、「ドア開放時の警告アラーム」としての正常動作を確かめよ。
|
<ヒント>
RB4へのビット操作の代わりに、RB7へのビット操作とすればよい。
(変更前)
andlw B'11101111' ; RB4をLにする
iorlw B'00010000' ; RB4をHにする
(変更後)
andlw B'01111111' ; RB7をLにする
iorlw B'10000000' ; RB7をHにする
e0.5.5 【実験7】RB0(ドアセンサ)→RB4へ正論理&RB7へ負論理出力 test2cモジュールの作成
<仕様>
RB0(ドアセンサ)の状態を監視して、
ドア閉鎖状態でランプ点灯 ⇔ ドア開放状態で警報アラーム鳴動
という動作を行う。
RB0(ドアセンサ)
→RB4(LED#4)へは正論理 (ドア閉鎖状態でランプ点灯)
→RB7(Relay#2)へは負論理(ドア開放状態で警報アラーム鳴動)
<処理>
PORTB[0](RB0)の状態を調べる if (PORTB[0](RB0) == L) PORTB[7](RB7) := "H" PORTB[4](RB4) := "L" else PORTB[7](RB7) := "L" PORTB[4](RB4) := "H" endif
|
【実験7】
上記のような仕様のtest2cモジュールを実装し、RB0(ドアセンサ)の状態を監視して、
ドア閉鎖状態でランプ点灯 ⇔ ドア開放状態で警報アラーム鳴動
としての正常動作を確かめよ。
|
<ヒント>
test2bをベースとした場合、RB7へのビット操作と併せて、RB4へのビット操作を行えばよい。
(変更前)
andlw B'01111111' ; RB7をLにする
(変更後)
andlw B'01111111' ; RB7をLにする
iorlw B'00010000' ; RB4をHにする
(変更前)
iorlw B'10000000' ; RB7をHにする
(変更後)
iorlw B'10000000' ; RB7をHにする
andlw B'11101111' ; RB4をLにする
●在宅・留守モードの導入
【実験8】(課題0)在宅・留守モード による警報動作切り替え test3モジュールの作成
リセット時初期値(RB5==Low)のとき「留守モード」とすることの考察
e0.6.1 【実験8】在宅・留守モード による警報動作切り替え test3モジュールの作成
<仕様>
RB5(Mode#2)を、在宅・留守モード の状態切り替えとします。
Low のとき:留守モード(リセット時初期値)
Highのとき:在宅モード
この在宅・留守モードに応じて、
RB0(ドアセンサ)
→RB4(LED#4):ドア閉鎖状態でランプ点灯(モード関係無し)
→RB7(Relay#2):RB0==Low && RB5 == Low のとき、ドア開放状態で警報アラーム鳴動(留守モード時)
という動作を行いたい。
<動作>
在宅・留守モードに応じて、
・在宅時には、ドア状態がLED#4で確認できる。
・留守時には、ドア開放されたら、警報アラームを作動させる。
【実験8】(課題0)
上記のような仕様の test3モジュールを作成し、動作を確かめてください。
|
※プログラム作成時の注意:アセンブリコードの各行には、必ず、その行の動作を表す短いコメントをつけるようにしてください。プログラムした本人でも、2〜3日経つと忘れてしまったり、特に分岐命令の名称に紛らわしいものが多い(btfsc,
btfssなど)ので、分岐条件などは特に慎重にコメントを付けておいてください。コメント付けは一見無駄なように思えますが、プログラム開発の流れの中でも、重要な要素の一つです(→Javadocの例など参考にせよ)
e0.6.2 リセット時初期値(RB5==Low)のとき「留守モード」とすることの考察
(A)初期値(Low)の時、「在宅モード」とした場合
留守中に何らかの原因でソフトリセットがかかったり、停電が起きた場合、復帰後、初期値Lowで「在宅」ということになり、ドア開放警報機能が働かなくなる。
(B)初期値(Low)の時、「留守モード」とした場合
(A)に対して、ソフトリセットあるいは停電復帰後についても、初期値Lowで「留守」ということになり、ドア開放警報機能が継続する。
(C)停電中のバッテリバックアップについて
もしACアダプタを商用電源コンセントから直接給電していた場合、停電中、ドア開放アラームが「鳴らない」。このため、PICNIC+周辺拡張オプションキット+ネットワーク接続環境のための、無停電電源装置(UPS)を装備すべきである。
●課題0の提出
(1)§e0.6.1 【実験8】在宅・留守モード による警報動作切り替え test3モジュールの作成 を行いなさい。
(2)仕様通りの動作が行われることを、ドアセンサ状態、web制御画面からのRB5状態変化の機能を用いて確認せよ。
(3)アセンブルソースファイルのバージョン番号を、
ver1.2.0.4 --> ver1.2.0.5
へ変更せよ。また、web制御画面のフッタ部分を、各自の学籍番号等に適宜変更せよ。
(4)完成したアセンブルソースファイルを v1205e0.asm という名前で保存せよ(全て半角英数字・アルファベットは小文字、半角カタカナ・全角文字はファイル名には決して使用しない)。
(5)以下のレポート提出システムから、v1205e0.asm をアップロードせよ。レポート受領後、採点が行われる。
<提出方法>
作成したプログラムは以下のレポートシステム(ファイルアップロード機能付き)を用いて、アセンブル・ソースファイル(拡張子が *.asm のもの。*.hexの方ではない)を提出します。
提出されたファイルは、こちらで動作を確認した後、動作に不備があった場合には再提出、動作がOKの場合には修了の判定を行います。
レポート課題0(課題番号:picnic-exp00)の提出はここからどうぞ
(6)「判定待ち」となります。少々お待ちください。なかなか判定結果が出ない場合は、こちらへメールして下さい。
(7)再提出判定がなされた場合には指摘された不具合を修正し、v1205e0a.asmなどとファイル名を替えて、再提出する。
(8)(7)でない場合(修了判定)、レポート課題0は受理され、完了となった。
|