PICNIC

チュートリアル(課題0)

■ チュートリアル(課題0)

1. チュートリアルの目標

2. メモリ使用状況とPAGE間ジャンプ

3. ファームウェアver1.2.0.4について

4. dummyモジュールによるcall/return

5. ドアセンサ→LED・リレー出力

6. 在宅・留守モードの導入

7. 課題0の提出

■レポート提出システム

 

→質問やお気づきの点はこちら

←目次へ戻る

2007年8月6日 17:51 更新

 

●チュートリアルの目標

それでは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は受理され、完了となった。

信州大学インターネット大学院

Creative Commons License

IT技術演習(院)−PICNIC− by Katsumi WASAKI is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.1 Japan License.