PICNIC

プログラミングTips

■プログラミングTips

0. PIC16F877Aバージョンの不具合について ←NEW

1. プログラミングのための資料

2. PICNICファームウェア概説

3. プログラミングTips

 

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

←目次へ戻る

2007年8月6日 17:49 更新

 


●PIC16F877Aバージョンの不具合について(2004/4/29追記)


IT技術演習のPICプログラミングに関する、一連の不具合について、原因と当面の回避方法がわかりましたので、以下に示します。受講生におかれては、ご自身が手にされているPICマイコンの種類をよくご確認の上、実習に望んでください。

<書き込み不良の原因>
トライステート社が出荷しているPICNICキットに付属している、PICマイコンが、ある時点からPIC16F877-20/P から PIC16F877A-1/Pへ変更されてしまった。このため、貸し出し中PICライタ ver.3 (書き込みソフト ver.2.9.5)では、この877Aには対応していなかった。

<回避方法>
2通りの方法があります
(1)拡張オプションキットに付属の、PIC16F877-20/P を使用して、最初から最後まで実習を行う。PICNICキットに付属の877Aは使わない。
(2)秋月電子から、書き込み器ROMをver.4にバージョンアップするキットを追加購入する。

<まとめ>
以下の組み合わせで、正常に書き込めます。
・書き込みソフトver2.9.5+ライタver.3+PIC16F877-20/P
・書き込みソフトver2.9.21(最新)+ライタver.4バージョンアップROM+PIC16F877/877A


●プログラミングのための資料

Microchip PICシリーズ周辺LSIデータシート書籍・他プロジェクトサイト・FAQ


6.1.1 Microchip PICシリーズ

→全ての資料へのリンクはここからどうぞ

Microchip社サイト

米国マイクロチップ社
マイクロチップ社 日本支社


6.1.2 周辺LSIデータシート

→全ての資料へのリンクはここからどうぞ

Ethernet-NIC (Realtek RTL-8019AS)
Serial-EEPROM (ATMEL 93C46)
摂氏直読温度センサIC (NS LM35(DZ))
LCDディスプレイパネル (SUNLIKE SC1602BSLB 日立HD44780)


6.1.3 書籍・他プロジェクトサイト・FAQ

→全ての資料へのリンクはここからどうぞ

書籍

PICマイコン活用ハンドブック(CQ出版社)
PICマイコンによるシーケンス制御(CQ出版社)
マイクロチップ社参考書籍リスト

他プロジェクトサイト・FAQ

Eric's PIC Page
PIC is fun
マイクロチップ社FAQリスト
PIC FAQのページ

●PICNICファームウェア概説

内蔵プロトコルスタックダウンロード各ブロックの概要動作について初期設定シーケンス


6.2.1 内蔵プロトコルスタック

本PICNICのファームウェアに実装されているプロトコルスタックは、以下の通り。

・Ethernetフレーム送受信処理
・ARP受信処理
・IP受信処理
・ICMP受信処理
・UDP受信処理
・TCP受信処理
・HTTP受信処理


6.2.2 ダウンロード

PICNIC ver.2用のMPASMアセンブラ向けファームウェア(ver1.2)は以下からダウンロードしてください。

アセンブリソースファイル(v12.asm)

アセンブル済みHEXファイル(v12.hex) リスティングファイル(v12.lst) エラーファイル(v12.err)

※参考 PIC16F877用インクルードヘッダファイル P16F877.inc


6.2.3 各ブロックの概要

ファームウェア全体では7380行もコード量があります。
大まかなブロックは以下のように構成されています。

21〜37行目:各種定数の定義
42〜53行目:EEPROMメモリ領域
61〜466行目:プロトコルスタック向け各種環境変数の定義

470行目:プログラム開始点(ORG 0)
482〜621行目:割り込みルーチン(受信処理、タイムアウト)
628〜815行目:各種機能関数(TCPフレーム番号管理、エラー復帰)

820〜889行目:メインループ

902〜1018行目:Ethernetフレーム受信処理(RTL8019ASバッファ管理)
1025〜1068行目:ARPプロトコル処理
1172〜1341行目:IPプロトコル処理(受信バッファ管理)
1349〜1434行目:ICMPプロトコル処理
1441〜1565行目:UDPプロトコル処理(パラレルポート出力管理)
1571〜1682行目:DHCP(BOOTP)プロトコル処理(IPアドレス取得関係)
1690〜2323行目:TCPプロトコル処理(サーバ機能のみ、パケット出力ルーチン含む)

2328〜2393行目:A/D変換入力処理
2398〜2500行目:入出力状態の前処理ルーチン
2504〜2558行目:ショートメッセージ解析処理
2563〜2764行目:ネットワークソケットの作成、チェックサム計算

2771〜3032行目:RTL8019ASの初期化関係
3037〜3214行目:チェックサム計算、パケット送信処理(NICレベル)
3221〜3344行目:Ethernetフレーム、IPフレーム作成

3352〜3578行目:UDP→パラレルI/O制御、A/D変換機能
3584〜3560行目:UDPチェックサム計算
3655〜3873行目:シリアルインターフェース制御

3879〜4024行目:ソケット管理
4030〜4290行目:Ethernetブロードキャスト処理

4295〜4369行目:EEPROMへのデータ書き込み関係
4555〜4577行目:EEPROM初期化
6063〜6130行目:EEPROM関係ライブラリ
7266〜7306行目:EEPROMデフォルト値セット

4379〜4548行目:URL指定CGI解析処理
4583〜4643行目:URL引数によるパラレルI/O出力値の変更

4651〜4919行目:スタートアップルーチン(電源投入時設定関係)
4625〜5036行目:LCDディスプレイ関係ライブラリ
5042〜5292行目:各種演算ライブラリ関数群

5297〜5404行目:ブートストラップモード・初期化ルーチン
5411〜5944行目:シリアル端末操作コマンド関係
7015〜7054行目:ブートストラップモード・端末表示
7311〜7374行目:ブートストラップモード・コマンド解析用データ

5952〜6056行目:現在のソケットステータスの通知処理(現在未使用)

6139〜6266行目:取得したIPアドレスをLCDパネルへ表示する処理
6431〜6697行目:DHCPサーバからのIPアドレス取得処理

6273〜6424行目:シリアルポート→UDP送信処理
6703〜7009行目:UDP受信→シリアルポート出力、LCDパネル表示

7061〜7258行目:WebブラウザHTML形式の応答メッセージ・形式格納エリア


6.2.4 動作について

既に実装されている各種プロトコルスタック自体は、ユーザ側で変更する場面はほとんどありません。制御のためにwebブラウザに表示されるページの変更は、7061行目から始まる、HTML形式の応答メッセージ・形式格納エリアの部分を改造することになります。

Ethernetフレーム長の制限のため、1回で全てのHTML形式テキストを送出することができないため、4つのブロック(ESTAB0からESTAB9に渡る4トランジション遷移)に分割して送出しています。この送出処理が、1690行目からのTCPパケット処理のシーケンサとして実装されています。

Ethernetケーブルの切断、ネットワークの途絶、各種プロトコル処理中のフリーズ状態からの回復のため、タイマ割り込みルーチン内には、自動リセット機能が実装されています。例えばフリーズの後、約15秒後にTCPのLISTEN状態に強制的に戻すように設計されています。

HTTPプロトコルのGETメソッドにあたる部分だけが実装されています。
簡易HTTPサーバとして存在しており、webブラウザからのリクエストにより、まず送られたURL中のパラメータ(?の後)を解析し、PIC内部のEEPROMに制御情報を保存、そして各種の外部インターフェースの制御を行います。その後、現在のPICNICの情報(上記HTMLテキスト)をブラウザへ送出します。

<参考>
ハイパーテキストトランスファープロトコール(HTTP)第1.0版仕様書
TCPの接続状態遷移図


6.2.5 初期設定シーケンス

ファームウェアのスタートアップルーチン start:(電源投入時設定関係、4651行目〜)のシーケンスは以下のようになっています。

(1)内蔵EEPROMから初期設定に必要なデータ(IPアドレス設定、など)をRAMにコピー

(2)LCD液晶ディスプレイパネルを初期化

(3)EthernetNIC(RTL8019AS)を初期化し、CONFIGレジスタをNICへロード

(4)外部シリアルEEPROM 93C46 から、MACアドレスを取得

(5)(4)で取得したMACアドレスをNICのPARレジスタへ書き込み

(6)動作モード(通常 or ブートストラップ)のジャンパSW状態確認。その後、選択されたモードのメインルーティンへ突入。

(7)DHCPサーバからのIPアドレス取得(IP=0.0.0.0)が必要なら、ブロードキャストでDHCPプロトコルのDISCOVERメッセージを送出する。

※上記の処理が終了した後、フレーム受信待ち状態となる。

 

●プログラミングTips

0. どの変数がBANKのどこに存在するか

1. プログラムメモリの空きについて
2. ファームウェア上のプログラムモジュールとメモリ使用領域との対応
3. EEPROMデータ領域の現在の格納値について
4. 「default」値→EEPROMへ書き戻す時の処理について

5. web制御画面のメッセージ作成について
6. "In", "Out", "High", "Low"などの、ショートメッセージの取得
7. DAへのエスケープシーケンス埋め込みと、ワードアライメントについて
8. 値埋め込み指示はワードアライメントの上位から始める

9. 室温センサ値表示(RA5)について
10. 現在の摂氏(℃)温度表示の代りに、華氏(゜F)温度表示にしたい

11. 内部タイマ&割り込みについて
12. リアルタイムクロック(RTC)について

13. CGI ?引数コマンドパースとEEPROMへの書き込みについて
14. DHCPによるIPアドレス取得時の処理について(注意点)
15. 「送信パケット数」表示の仕組み

16. v12.asmの内部タイマ・割り込み関係の処理について
17. ad_inモジュールのADCON0制御レジスタに関する実装誤りについて


6.3.0 どの変数がBANKのどこに存在するか

参考資料リンクに挙げてある、PIC16F877の、Data sheet はキチンと読みましたか?

http://ysserve.wakasato.jp/sugsi/Lecture/picnic/links.html#Microchip

PIC16F87x Complete Mid-Range Reference Manual
http://ww1.microchip.com/downloads/en/DeviceDoc/33023a.pdf

これの、ページ103から、Data memoryバンクについての説明があります。

Section 6. Memory Organization
6.3.3 Banking

-----
v1204org.asmの、153行目から462行目までが、このファームウェアで使用している「変数」の一覧となっています。上記データシートの説明の通り、

000h -- 07Fh : BANK #0
080h -- 0FFh : BANK #1
100h -- 17Fh : BANK #2
180h -- 1FFh : BANK #3

のアドレス領域が、各変数が対応するバンクとなります。

バンク切り替えは、STATUSレジスタの [RP1:RP0]の組みで指定します。
-----

例)変数timer は、350行目に指定されているように、

timer EQU 0B0H

ですので BANK #1 に存在します。


6.3.1 プログラムメモリの空きについて

プログラムメモリ全体で8192バイト。
アドレス空間は0x0000--0x1FFF(13bit)。

v12.asm(ver1.2.0.0)の場合、アセンブル後機械語のメモリ使用状況は以下の通り。

PAGE#0 : 0x0000-0x07FF : 0x0000--0x07EA 使用中
PAGE#1 : 0x0800-0x0FFF : 0x0820--0x0FED 使用中
PAGE#2 : 0x1000-0x17FF : 0x1000--0x1240、0x1300--0x151C、0x1700--0x17FF 使用中
PAGE#3 : 0x1800-0x1FFF : 0x1800--0x190A、0x1A00--0x1B9D、0x1D00--0x1D9F
0x1F00--0x1FC1 使用中

もう殆ど空き領域が無いことがよくわかる。

他の機能を追加する場合、既存の以下のような機能を削除し、それによって空いたプログラムメモリ空間(バンク)へ、追加機能のコードをいれていくしかない。

・1441〜1565行目:UDPプロトコル処理(パラレルポート出力管理)
・1571〜1682行目:DHCP(BOOTP)プロトコル処理(IPアドレス取得関係)
・3352〜3578行目:UDP→パラレルI/O制御、A/D変換機能
・3584〜3560行目:UDPチェックサム計算
・3655〜3873行目:シリアルインターフェース制御

・5297〜5404行目:ブートストラップモード・初期化ルーチン
・5411〜5944行目:シリアル端末操作コマンド関係
・7015〜7054行目:ブートストラップモード・端末表示
・7311〜7374行目:ブートストラップモード・コマンド解析用データ

・6431〜6697行目:DHCPサーバからのIPアドレス取得処理

・6273〜6424行目:シリアルポート→UDP送信処理
・6703〜7009行目:UDP受信→シリアルポート出力、LCDパネル表示


6.3.2 ファームウェア上のプログラムモジュールとメモリ使用領域との対応(v1.2.0.0)

<プログラムメモリ>

PAGE 1
プログラム開始 0x0000--0x07EA

PAGE 2
パラレルI/Fの制御〜 0x0820--0x0FED
(0x0800から0x081Fまでの32バイトが空き)

PAGE 3
シリアル受信データ送信 0x1000--0x1240
ブートストラップモード 0x1300--0x13FB

Webブラウザ用メッセージ#1 0x1400--0x151C
Webブラウザ用メッセージ#2 0x1700--0x190A PAGE3-->4へ跨っている
Webブラウザ用メッセージ#3 0x1A00--0x1B9D
Webブラウザ用メッセージ#4 0x1D00--0x1D9F

※0x0300毎に表示領域を相対指定。db 0,0 (0000h)がブロック終端識別データ列。

default_values_begin:
EEPROMデータ初期値 0x1F00--0x1FC1 IPアドレスなど初期値

<EEPROMメモリ>

EEPROM領域 0x2100--0x21FF (IPアドレスなど初期値)

IPアドレス 0x2100 (192.168.0.200)
ネットマスク 0x2104 (255.255.255.0)
default gateway 0x2108 (0.0.0.0)
Farmware Version 0x210C (1.2.0.1)
HTTPポート番号 0x2110 (80d)
LCDポート番号 0x2112 (10000d)
PARALLELポート番号 0x2114 (10001d)
SERIALポート番号 0x2116 (10002d)

以後、空き領域 0x2118--0x21FF

 


6.3.3 EEPROMデータ領域の現在の格納値について

IP:192.168.0.200, PORT:80の時のEEPROMデータの例:(256バイト 0x0000--0x00FF)

0000:C0 A8 00 C8 FF FF FF 00 00 00 00 00 01 02 00 01 ・・・・・・・・・・・・・・・・
0010:00 50 00 00 27 11 27 12 FF FF FF FF FF FF FF FF ・P・・'・'・・・・・・・・・

以後、0x00FFまで 0xFF。
現在、0x0000--0x0017まで使用中。ユーザは0x0020から使える。

実メモリアドレスとしては、0x2100--0x21FFに対応している。
プログラム中では、ORG 0x2100 以降でこれらのデータをROM書き込み時の初期値としてDEしている(MPASMで対応)。


6.3.4 「default」値→EEPROMへ書き戻す時の処理について

「EEPROMデータを初期へ戻すため」の値が、0x1F00--0x1F17(18hバイト分)入っている(§6.3.3参照)。

これの領域のサイズの取得には、default_values_end - default_values_beginのラベル間の引き算を行い、その領域の分だけ、モジュール:initial_values にて、EEPROM領域へ、書き戻している。

実際のEEPROMへの書き込みシーケンスは、モジュール:write_eeprom にてEEADR, EEDATAなどのレジスタファイルを操作しながら行っている。

<注意点>

上記のことから、(現在のところ)プログラム中の

EEPROM領域 0x2100--0x2117(18hバイト分) と、default_values_begin:
EEPROMデータ初期値 0x1F00--0x1F17(18hバイト分)

の部分の定数値は一致させておく必要がある。現在のところ、以下の4つは重要である。

IPアドレス 0x2100 (192.168.0.200)
ネットマスク 0x2104 (255.255.255.0)
default gateway 0x2108 (0.0.0.0)
Farmware Version 0x210C (1.2.0.1)

一致していない場合、web制御画面から「default」値のリードを行った際に、ROM書き込み値と違う値がロードされてしまう。

(逆手にとって、複数コンテクストによるreconfigurableとするのも一興か?)

なお、HTTP_PORT, LCD_PORT, PARALLEL_PORT, SERIAL_PORTの各定数については、 上部で定数定義が行ってあるので、そこを変更するのみでOK.


6.3.5 web制御画面のメッセージ作成について

PICNIC web制御画面のメッセージ作成に関して、各バンクの内容の送信処理と、値埋め込みのための各分岐指示子について以下にまとめる。

参考URL:
ASCIIコード表:
http://dbkun.cs.shinshu-u.ac.jp/cai/c2/text/e_ascii.html

MPASM Users Guide with MPLINK and MPLIB
page 59--60
microchip/33014g.pdf#page=59

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(1) I/O Ports割り当て

RA0--RA3 アナログ入力(RA0とRA1は端子台CN4)
RA5 室温センサIC入力(Celsius度に換算して表示)
RB0--RB1 デジタル入力(RB0,RB1ともに端子台CN4接点入力)
RB2--RB5 LCD表示器専用(ユーザ使用不可)
RB6--RB7 デジタル出力(RB6,RB7ともに端子台CN3接点出力)

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(2) 応答メッセージ領域

#1から#4まで4パートに分かれている。

PAGE 3
Webブラウザ用メッセージ#1 0x1400--0x151C
Webブラウザ用メッセージ#2 0x1700--0x190A
Webブラウザ用メッセージ#3 0x1A00--0x1B9D
Webブラウザ用メッセージ#4 0x1D00--0x1D9F

0x0300毎に表示領域を相対指定。

各々のバンクの内容は以下の通り。

#1:Content-type: text/html -- I/O RA5 In xx Celsius の</TD></TR>まで
#2:RB0 In -- I/O Ports の [Reload] ボタン表示、</FORM>まで
#3:ネットワーク設定 -- [Save][Default]ボタン表示、</FORM>まで
#4:Status表示 -- 最後の</HTML>まで

DB 0,0 (0000h)がブロック終端識別データ列。


++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(3) 各バンクの内容の送信処理について

job_ESTAB→以下の00から99の各モード間を順次状態遷移する。

  goto ESTAB_00
  goto ESTAB_01
  goto ESTAB_02
  goto ESTAB_03
  goto ESTAB_99

ESTAB_00:
  TCP処理→httpポートでEstablishした場合、
  URL中に?文字が含まれるならば、
  URLの?引数のコマンド解析へ移る。(→parse_cgi_tagへ)

  そうでなければ、
estab1:
  バンクアドレス LW=14h として、バンク#1のメッセージを
  send_mesモジュールを使用して送信する。

  MOVLW 014H ;バンクアドレス=14h fan
  MOVWF wk
  goto send_mes ; メッセージ送信

ESTAB_01:
  バンクアドレス LW=17h fanとして、バンク#2のメッセージを
  send_mesモジュールを使用して送信する。

ESTAB_02:
  バンクアドレス LW=1Ah fan、バンク#3メッセージ送信

ESTAB_03:
  バンクアドレス LW=1Dh fan、バンク#4メッセージ送信

ESTAB_99:
  送信の終了処理:call send_fin+call inc_seq_no


++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(4) メッセージ送信 send_mesモジュール

2017行目付近から開始。

send_mes_head: TCPヘッダ作成
send_mes0: バンク領域からデータを読み込む。
  テスト
  読み込んだデータが 0,0 バンク終端指示ならば、終了(→send_mes9へ)

  あるいは、データが処理分岐指示子の場合、以下のように処理を分岐。

  メタ文字 && 0x7F : 分岐指示子は以下の通り:
    "$"(24h) : ctrl_code2
    "@"(40h) : ctrl_code3
    "%"(25h) : ctrl_code4
    " ̄"(3Fh) : ctrl_code5

  上記以外の通常文字の場合、下位7ビットをRTL8019バッファへ転送
  send_mes0間をループ

send_mes9: データ領域送信脱出ポイント
send_mes_foot: TCPフッタ作成:チェックサム計算
最後に call transmit して送信!


++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(5) 各分岐指示子と処理の対応

ctrl_code5: SOCKET STATUS socketステータスを表示
   ̄",0ahから0ehで、socket#1から#5まで表示する(現在は機能停止)
  →  call put_socket_stat

ctrl_code4: TRIS 入力・出力の方向表示
  "%",00hから04hで、RA0からRA5までの入出力方向を表示する。
  "%",10hから17hで、RB0からRB7までの入出力方向を表示する。

ctrl_code3: PARALLEL 現在の入出力値 High/Lowの表示
  '@",00hから04hで、RA0からRA5までのアナログ入力値を表示する。
  '@",10hから17hで、RB0からRB7までのデジタル入出力値を表示する。

ctrl_code2: META CHARACTER コントロール文字出力ルーチンへ
  → call ctrl_code1

ctrl_code1: ネットワーク設定値の表示 メッセージ中埋め込みの$?を置換する
  "$","0"から"B"で、以下のような設定内容を表示する。

goto put_mac_address ; 0:MACアドレス
goto put_ip_address ; 1:IPアドレス
goto put_netmask ; 2:ネットマスク
goto put_gateway_address ; 3:ゲートウェイ
goto put_http_port ; 4:httpポート
goto put_lcd_port ; 5:lcdポート
goto put_io_port ; 6:parallelポート
goto put_232_port ; 7:232cポート
goto put_version ; 8:バージョン表示

goto put_send_packet ; 9:送信パケット数
goto put_this_ip ; A:自IPの表示
goto put_ptop ; B:PtoP IPアドレス

ctrl_code: ctrl_code1へのタグ?


6.3.6 "In", "Out", "High", "Low"などの、ショートメッセージの取得

"In", "Out", "High", "Low"などの、ショートメッセージの取得について、
メッセージバンクは、0x1F00から

org 1F00h
getadtable:
mes_low DT " Low ",0
mes_high DT " High ",0
mes_in DT "In ",0
mes_out DT "Out",0

メッセージ表示モジュール
get_short_mes:

ctrl_out, ctrl_in, ctrl_high, ctrl_low で、それぞれ処理している。
例」:mes_out & .255 ; 'OUT' (下位8ビットマスク)

実際の書き出しは、put_short_mes10で行っている。

<ヒント>従って、メッセージバンクに

mes_open DT " Open ",0
mes_close DT " Close ",0

という文字列定義を追加して、_low, _high の代りに呼び出せば、

入力:
RB0 : ドアが 開いている状態:Open 閉じている状態:Close
RB1 : 外出中モード  在宅中:Open 外出中:Close

出力:
RB6 : カギが かけられていない状態:Open カギがかかっている状態:Close

などという表示に切り替えられる。(_low <--> _Open, _high <--> _Closeに対応)


6.3.7 DAへのエスケープシーケンス埋め込みと「ワードアライメント」について

参考URL:

ASCIIコード表:
http://dbkun.cs.shinshu-u.ac.jp/cai/c2/text/e_ascii.html

MPASM Users Guide with MPLINK and MPLIB
page 59--60
microchip/33014g.pdf#page=59

エスケープシーケンスコード
page105--
microchip/33014g.pdf#page=105

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

※DB,DAの7ビット区切りストア(Packed-8bit value)について

PIC16F877は1ワード=14bitのデータ幅を持っている。
1ワードの範囲は、0x0000 -- 0x3FFFである。
(2進数で 00.0000.0000.0000b -- 11.1111.1111.1111b)

これは、7ビット幅の「バイト」が、2つ並んでいる(Packed-8bit value)と考えると、後述のDB,DAのストア結果が理解しやすい。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(1)DB(Declare Data of One Byte)について

DB(Declare Data of One Byte)で定数記述したものは、PIC16F877の場合、その14bitのビット幅の上位から、6ビット+8ビットに順次「並べられて」ストアされる。
つまり、DB指示の奇数個目、偶数個目でストアできる値の範囲は、それぞれ 0x00--0x3F、0x00--0xFF ということになる。

例1−1:DB 0xFF, 0xFF などと無理やり指示した場合、

Message[303]: Program word too large. Truncated to core size. (FFFF)

と警告が出る。実際には、0x3FFF が生成されている。


DBの値並びは偶数個である。奇数個である場合、下位の8ビット分は0で埋められる。

例1−2:DB 0x3F などとすると、0x3F00 が生成される。


生成された並び(14ビット分)は、これを7bit+7bitのASCIIコードと見ることができる。これは後述のDAと深く関係している。

例1−3: DB 0x06, 0x8A である場合、0x068Aが生成される。
これを7bit+7bitのビット幅で分割すると、0x06=0000.0110b, 0x8A=1000.1010b
であることから、

0x068A = 00.0110.1000.1010b → [000.1101]:[000.1010]b → [0x0D]:[0x0A]

すなわち、CR+LFというテキストファイルの「改行」にあたるコードが生成されていることとなる。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(2)DA(Store Strings in Program Memory)について

例えば、DA "HTTP" という指示があった場合、"H","T","P"という非メタキャラクタ文字のASCIIコードは下位7ビットの範囲である。
DA指示によって生成された並び(14ビット分)は、これを7bit+7bitのASCIIコードとしてストアされた結果と見ることができる。

一般に、DAの文字並びの奇数個目、偶数個目の値が各々 0xXX, 0xYYである場合、

 0xXX = 0xxx.xxxxb, 0xYY = 0yyy.yyyyb

この[x6...x0], [y6...y0] を並べた xx.xxxx.xyyy.yyyyb が、メモリ上へストアされる1ワードの「値」となる。

例2−1:DA "HTTP" である場合、

 "H" = 0x48 → 0100.1000b
 "T" = 0x54 → 0101.0100b
 "P" = 0x50 → 0101.0000b

であることから、"HT", "TP"を各々1ワードしてPackedした、

 [100.1000]:[101.0100] → 10.0100.0101.0100b = 0x2454
 [101.0100]:[101.0000] → 10.1010.0101.0000b = 0x2A50

が生成される。


DAの文字数は偶数個である。奇数個である場合、下位の7ビット分は0で埋められる。

例2−2:DA "K" というように奇数個の場合、

 "K" = 0x4B → 0100.1011b

であることから、"K"+7bit分0としてPackedした、

 [100.1011]:[000.0000] → 10.0101.1000.0000b = 0x2580

が生成される。


例2−3:DA の値並びは、DBと同様、6+8ビットとしてストアされる。たとえば、下の2つの実装は同等である。

 DB 0x3F, 0xFF
 DA 0x3FFF

いずれも、0x3FFFが生成される。


例2−4:DA の文字列並びには、ASCIIエスケープシーケンス、ならびに¥x??という形式の16進数埋め込みが可能である。

下の2つの実装は同等である。

 DA "test¥r¥n"
 DA "test¥x0D¥x0A"

いずれも、0x39F4 0x068A が生成される。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(3)DAへのエスケープシーケンス埋め込みと、ワードアライメントについて

今回のweb応答メッセージの記述に関しては、

(a) メッセージ送信文字列の最後に、CR+LFもしくはLFという「改行」コードを埋め込みたい。
(b) バンク終端は、DB 0,0 として記述し、かつワードアライメントによる7bit分のzero-fillingの副作用を排除し、正しく動作させたい。
(c) Packed-8bit文字列として、限りあるメモリ領域を無駄なく使用したい。

という3つの条件を満足したい。


目的(c)メモリ使用効率を当座は考慮しないならば、(a)(b)については、ワードアライメントは無視し、DAへのエスケープシーケンス埋め込みで済ませるのが簡単な方法である。


例3−1:DAへのエスケープシーケンス埋め込み

HTTPレスポンスとヘッダ送信の場面について、

----------------------------------------------------------------------
HTTP/1.0 200 OK[CR+LF]
Connection: close[CR+LF]
Content-type: text/html[CR+LF]
[CR+LF]            ←メッセージ本体との間には、空行が必要
<HTML><HEAD><TITLE>PIC Network Interface Card</TITLE></HEAD><BODY>[LF]
----------------------------------------------------------------------

という「文字列」を送信したい、かつ、目的(c)を無視するならば、
DAへのエスケープシーケンス埋め込みを使って、単純に、
[CR]->¥r, [LF]->¥nを埋め込み、

----------------------------------------------------------------------
DA "HTTP/1.0 200 OK¥r¥n"
DA "Connection: close¥r¥n"
DA "Content-type: text/html¥r¥n"
DA "¥r¥n"
DA "<HTML><HEAD><TITLE>PIC Network Interface Card</TITLE></HEAD><BODY>¥n"
----------------------------------------------------------------------

という具合に実装したい。

しかしながら、一部のwebブラウザ(Netscape7.1など)では、¥r¥nの後ろのゴミ(0x00)をキチンと解釈して、メッセージタイプが text/htmlにならず不具合が生じる場合があるので、これは採用しないこととする。(2003/10/12修正)


例3−2:ワードアライメントを考慮したDAへのエスケープシーケンス埋め込み

再度、HTTPレスポンスとヘッダ送信の場面について、DAによる文字列並びで
各DAが偶数個の文字列を持つように、適当に分割すると、例えば以下のようになる。

----------------------------------------------------------------------
HTTP/1.0 200 OK[CR+LF]C  18文字
onnection: close[CR+LF]  18文字
Content-type: text/htm   22文字
l[CR+LF][CR+LF]< 6文字
HTML><HEAD><TITLE>PIC Network Interface Card</TITLE></HEAD><BODY>[LF] 66文字
----------------------------------------------------------------------

これの実装は以下の通り。

----------------------------------------------------------------------
DA "HTTP/1.0 200 OK¥r¥nC"
DA "onnection: close¥r¥n"
DA "Content-type: text/htm"
DA "l¥r¥n¥r¥n<"
DA "HTML><HEAD><TITLE>PIC Network Interface Card</TITLE></HEAD><BODY>¥n"
----------------------------------------------------------------------

この場合、生成されるワード列は以下の通り。
0x1441アドレスから0x1482-1アドレスまでの0x40ワードを生成した。
ワードアライメントを考慮しない場合にくらべて、2ワードを節約した!


1441 2454 2A50 17B1 DA "HTTP/1.0 200 OK¥r¥nC"
1730 1032 1830
104F 258D 0543
144A 37EE 3765 31F4 DA "onnection: close¥r¥n"
34EF 373A 1063
366F 39E5 068A
1453 21EF 3774 32EE DA "Content-type: text/htm"
3A2D 3A79 3865
1D20 3A65 3C74
17E8 3A6D
145E 360D 050D 053C DA "l¥r¥n¥r¥n<"
1461 2454 26CC 1F3C DA "HTML><HEAD><TITLE>PIC Network Interface Card</TITLE>....."
2445 20C4 1F3C
2A49 2A4C 22BE
2849 21A0 2765
3A77 37F2 35A0
24EE 3A65 3966
30E3 32A0 21E1
3964 1E2F 2A49
2A4C 22BE 1E2F
2445 20C4 1F3C
214F 2259 1F0A
1482 ....


例3−3:可読性を考慮しなくても良い場合

上記の例では、結局全ての文字列を並べた結果、偶数個であるのだから、
可読性を考えなければ、DAで全てを""に1行に並べてしまえばよい。

----------------------------------------------------------------------
DA "HTTP/1.0 200 OK¥r¥nConnection: close¥r¥nContent-type: text/html¥r¥n¥r¥n<HTML><HEAD><TITLE>PIC Network Interface Card</TITLE></HEAD><BODY>¥n"
----------------------------------------------------------------------

これでも、例3−2と同じコード生成がなされる。

<問題>
一行の最大文字数の制限はあるのだろうか?
リスティングファイル .lst 上では、長すぎるコード生成の表示は途中で省略された。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(4)結局、DBとDAのコンビネーションは面倒臭いだけ?

結局、オリジナルのアセンブリソースファイル(v12.asm)にあるような、DBとDAのコンビネーション、例えば、

----------------------------------------------------------------
DA "HTTP/1.0 200 O"
DB 25,8D ;'K',13
DB 05,43 ;10,C
DA "onnection: close"
DB 06,8A ;, .13, .10,
DA "Content-type: text/htm"
DB 36,0D,05,0D,05,3C ;'l', .13, .10, .13, .10, '<'
----------------------------------------------------------------

などという実装は、可読性が悪いだけで、何のメリットもない、ということになりますね ;-<


6.3.8 値埋め込み指示はワードアライメントの上位から始める

値埋め込み ($8 など)の指示は、ワードアライメントの上位から始めなければならない。

アライメントの下位から$*が始まった場合、文字としての"$"+"8"として解釈され、"$8"として「表示」される。

例:(誤)の場合、<空白>$8の$の位置がアライメントの下位となってしまうため、"$8"と表示される。いちいち文字列をカウントするのは面倒なので(正)のように2つのDAに分割してしまうのが簡単。

(正)
  DA "<H1>PIC Network Interface Card Version "
  DA "$8</H1>"

(誤)
  DA "<H1>PIC Network Interface Card Version $8</H1>"


6.3.9 室温センサ値表示(RA5)について

RA5には温度センサIC(NS社製 LM35DZ)が接続されていて、PIC内部のA/D変換器を使って10bitのバイナリ値として扱っている。

LM35データシート:
http://www.national.com/JPN/ds/LM/LM35.pdf

このデータシート上にも記載されているとおり、このICは、摂氏℃リニアな校正済み出力(温度係数 +10.0mV/℃)を直接得ることができる(とても便利)。

(1)web制御画面メッセージへの、RA5のバイナリ値の「℃度」変換後の値埋め込み

さて、RA5の値埋め込みは、org 1400h から始まるバンク1の最後に、

;
;++++++++++++++ 室温センサ入力 RA5(℃換算) ++++++++++++++++++
;
DA "<TR><TD>RA5 "
DB 12,84 ;%",0000_0100b,(入力方向)
DA " </TD>"
DA "<TD ALIGN=¥"right¥">"
DB 20,04 ;@",00000100b,(℃換算値表示)
DA " Celsius</TD></TR>"

と指定されている。 つまり、制御キャラクタ "@"+0x04として埋め込みが行われている。

send_mes0モジュールによるデータ出力時、"@"(40h):ctrl_code3モジュールへ分岐し、アナログ入力ピンの場合、ad_in:モジュールによるA/D変換処理が開始される。

PIC16F877は、0〜+5V入力について10bitの分解能を持つA/D変換器を持っており、 この温度センサICの出力は、変換結果として変数[val1:val]へ格納されている。

RA5の場合、上記の温度係数、R14(10KΩ)によるVs=GNDへの接続、A/D変換範囲(0〜5V)ならびに分解能(10進数で 0 から 1023(≒1000))を勘案し、入力値として0〜約1000が得られたら、リニアに摂氏0℃〜50℃と表示できるように計算(5倍)を行っている。

詳しくは、v12.asmアセンブルソースファイルの2325行目付近から始まっているad_in:モジュールを参照のこと。

(2)"DB 12,84"の意味するところは?

先の6.3.7 DAへのエスケープシーケンス埋め込みと「ワードアライメント」についてでも説明したように、これはHTMLメッセージ中の<入力方向>"In" or "Out"という文字列埋め込みのための指示子である。

ワードアライメント上位から"%"が見つかった場合、メッセージ出力の処理が分岐され、次の下位で指定されたパラメータによって、対応する内容を実行する。この実装では、"%"+0x04というメッセージが生成されている。

<解説>

今回のMPASMアセンブリ言語でDBディレクティブは、Declare Data of One Byteということで、

 DB 0x12, 0x84

という実装は、1ワード14bitコアであるPIC16F877の場合、

 0x1284

という機械語コードが生成される。

これを7bit+7bitのビット幅で分割すると、0x12=0001.0010b, 0x84=1000.0100b であることから、

 0x1284 = 00.010.0101:000.0100b → [010.0101]:[000.0100]b → [0x25]:[0x04]

すなわち、"%"+0x04というメッセージが生成されていることとなる。

詳しくは、§6.3.7を参考にすること。

(3) "%"メタ文字の処理について

※メッセージ送信send_mesモジュールは、2017行目付近から開始。

++++++++++++++++++++++++++++++++++++++++++++++++++++
send_mes_head: TCPヘッダ作成
send_mes0: バンク領域からデータを読み込む。
  読み込んだデータが 0,0 バンク終端指示ならば、終了(→send_mes9へ)

  あるいは、データがメタ文字の場合、コントロール文字と見なすか、
  以下のように処理を分岐。

  メタ文字 && 0x7F : 分岐指示子は以下の通り:
  "$"(24h) : ctrl_code2
  "@"(40h) : ctrl_code3
  "%"(25h) : ctrl_code4
  " ̄"(3Fh) : ctrl_code5
++++++++++++++++++++++++++++++++++++++++++++++++++++

今回は、"%"であるので、ctrl_code4:モジュールへ飛び、

++++++++++++++++++++++++++++++++++++++++++++++++++++
ctrl_code4: TRIS 入力・出力の方向表示
  "%"+00hから04hで、RA0からRA5までの入出力方向を表示する。
  "%"+10hから17hで、RB0からRB7までの入出力方向を表示する。
++++++++++++++++++++++++++++++++++++++++++++++++++++

という処理の分岐を行う。ここでは"%"+0x04という指定がなされているので、RA5の入出力方向の「表示」、つまり
"In" or "Out"という文字列出力を行うためのモジュールへ飛ぶ(この後はメッセージバンクの表引き処理となる。§6.3.6を参照のこと)

RA5は、先の説明の通り温度センサからの「入力」であるから、PICのレジスタファイルの入出力方向ビットを検査し、結果"In "という表示が行われることとなる。

<ヒント>

例:次の実装は同等である。 DB 12,84 ⇔ DA "%¥x04" 詳しくは、§6.3.7を参考にすること。


6.3.10 現在の摂氏(℃)温度表示の代りに、華氏(゜F)温度表示にしたい

2つの方法がある。

(1)現在の摂氏(℃)センサの代りに、華氏(゜F)温度センサを取り付ける

現在実装されている摂氏直読NS LM35(DZ)の代わりに、これの華氏直読バージョンであるLM34(DZ)を取り付ければ、それで完了。

LM34データシート:
http://www.national.com/JPN/ds/LM/LM34.pdf

温度係数も、LM35が+10.0mV/℃、LM34が+10.0mV/F であるので、A/D変換結果から温度計算をおこなう部分も同じ。

混乱を防ぐために、IC交換後、web表示の "Celsius" を "Fahrenheit" などと変更すると良い。

(2)摂氏(℃)→華氏(゜F)変換式を追加する

摂氏(℃)センサをそのまま使いながら、華氏温度表示を行いたい場合、以下の摂氏(℃)→華氏(゜F)変換式を、先に説明したad_in:モジュールの表示向けメモリへ値ストアする前に、追加(もしくは現在の”5倍”計算と置き換え)
する必要がある。

++++++++++++++++++++++++++++++++++++++++
※摂氏→華氏の換算公式
  (華氏温度) = 1.8 × (摂氏温度) + 32
++++++++++++++++++++++++++++++++++++++++

摂氏温度は非負であるから、計算結果の華氏温度も非負となり、マイナス数の取り扱いは不要。

<ヒント>
PIC16F877は整数演算しか用意されていないので、x 1.8 は直接計算できない。
実装上のヒントとしては、上記公式の両辺を適当に何倍かすることで、何回かのbit左シフト演算に置き換える、ということである。16bitバイナリ向けの割り算モジュールは既にファームウェア上に実装されている(divide16モジュール)

※いい問題なので、受講生の皆様への練習のために残しておきますね :-)


6.3.11 内部タイマ&割り込みについて

<参考URL>

PIC16F877データシート(和文)
microchip/30292a-j.pdf

PICmicro PIC16F87x MID-RANGE Complete Reference Manual:
microchip/33023a.pdf

Microchip社セミナー資料:
http://www.microchip.co.jp/1999seminar058-094.pdf?page=25
http://www.microchip.co.jp/1999seminar150-198.pdf?page=8

(1)内部タイマについて

PIC16F877は、TMR0からTMR2までの3つの内部タイマを搭載している。

PIC16F877データシート DS30292A-J page47--
microchip/30292a-j.pdf#page=47

8bitタイマ/カウンタ プリスケーラ付き
TMR0 --> オーバーフロー時に、T0IF割り込みフラグセット

16bitタイマ/カウンタ プリスケーラ付き
TMR1 --> [TMR1H]:[TMR1L]オーバーフロー時に、T1IF割り込みフラグセット

8bitタイマ/カウンタ プリスケーラ・ポストスケーラ付き
TMR2 --> オーバーフロー時に、T2IF割り込みフラグセット

※PICNICの現在の Fosc=20MHz(50nsec) 故に、Fosc/4 = 5MHz(200nsec)

++++++++++++++++++++++++++++++++++++++++++++++++++++++

内部タイマ0 TMR0(8bit)

ブロック図
PIC16F877データシート DS30292A-J page47 図5-1
microchip/30292a-j.pdf#page=47

関連するレジスタ
PIC16F877データシート DS30292A-J page48 表5-1
microchip/30292a-j.pdf#page=48

TMR0レジスタ:レジスタBANK#1, 101h

PSout := !TOCS && PSA && (Fosc/4)

PSAビットクリア --> プリスケーラがタイマ0に割り当てられる。
プリスケール値:PSAビット==L --> 1:2 〜 1:256

TMR0レジスタ:0xFF --> 0x00への遷移時(オーバーフロー時)に、
TMR0割り込みが発生する。

割り込みフラグはT0IF(INTCON:2)(予めビットを0にしておく必要あり)
割り込みマスクはT0IE(INTCON:5)

++++++++++++++++++++++++++++++++++++++++++++++++++++++

内部タイマ1 TMR1(16bit)

ブロック図
PIC16F877データシート DS30292A-J page50 図6-3
microchip/30292a-j.pdf#page=50

T1CON コントロールレジスタ
PIC16F877データシート DS30292A-J page49 図6-1
microchip/30292a-j.pdf#page=49

関連するレジスタ
PIC16F877データシート DS30292A-J page51 表6-2
microchip/30292a-j.pdf#page=51


TMR1レジスタ [TMR1H]:[TMR1L]:レジスタBANK#0, TMR1L:0Eh, TMR1H0Fh

PSout := !TMR1CS && (Fosc/4) もしくは RC共振オシレータ

プリスケール値:1:1 〜 1:8

TMR1レジスタ:0xFFFF --> 0x0000への遷移時(オーバーフロー時)に、
TMR1割り込みが発生する。

割り込みフラグはTMR1IF(PIR1:0)(予めビットを0にしておく必要あり)
割り込みマスクはTMR1IE(PIE1:0)

++++++++++++++++++++++++++++++++++++++++++++++++++++++

内部タイマ2 TMR2(8bit)

ブロック図
PIC16F877データシート DS30292A-J page54 図7-2
microchip/30292a-j.pdf#page=54

T2CON コントロールレジスタ
PIC16F877データシート DS30292A-J page53 図7-1
microchip/30292a-j.pdf#page=53

関連するレジスタ
PIC16F877データシート DS30292A-J page54 表7-1
microchip/30292a-j.pdf#page=54

TMR2レジスタ:レジスタBANK#0, 11h

PSout := TMR2ON && (Fosc/4)

プリスケール値:1:1 1:4 1:16
ポストスケール値(4ビット):1:1〜1:16

PR2レジスタ:TMR2レジスタと比較器に入っている。リセット時 FFh

TMR2レジスタ:0x00 --> PR2と一致したら --> ポストスケーラで分周 -->
TMR2割り込みが発生する。(TMR2レジスタは 0x00に戻る)

割り込みフラグはTMR2IF(PIR1:1)(予めビットを0にしておく必要あり)
割り込みマスクはTMR2IE(PIE1:1)

++++++++++++++++++++++++++++++++++++++++++++++++++++++

(2)割り込みについて

PICmicro PIC16F87x MID-RANGE Complete Reference Manual page123--
microchip/33023a.pdf#page=123

PIC16F877データシート DS30292A-J page143--
microchip/30292a-j.pdf#page=143

割り込み要因ロジック
PIC16F877データシート DS30292A-J page144 図12-11
microchip/30292a-j.pdf#page=144

割り込み要因が14本。


グローバル割り込みマスク:GIE
割り込みマスク:INTCON
周辺割り込みマスクビット:PEIE

周辺割り込みフラグ:PIR1 PIR2
周辺割り込みイネーブルビット:PIE1 PIE2

割り込みが実行されると、GIE ビットが0 になり、そ
れ以上の割り込みがディセーブルとなり、リターンア
ドレスがスタックにプッシュされて、PC に0004h
(割り込み処理ルーティン)がロードされる。

割り込み処理ルーチンでは、割り込みフラグビットを検査することにより、発生した割り込み要因がわかる。

割り込み処理後、当該割り込みを再イネーブルする前に、割り込みフラグビットをソフトウエア側でクリアする必要がある。(そうしないと、割り込みルーティンからの復帰直後、再度割り込みが発生してしまい、要求の無限ループが発生してしまう)

割り込み中のコンテクスト保存(STATUS,W,PCLATHレジスタ)は必須。
PIC16F877データシート DS30292A-J page145--
microchip/30292a-j.pdf#page=145

※具体的な実装については、上記データシートの記事を参考にせよ。

 


6.3.12 リアルタイムクロックについて

(1)いわゆる「リアルタイムクロック(RTC)」は、PIC16F877は内蔵していない。リアルタイムクロックICを外部ポート(シリアルポートなど)に外部接続して日時を取得するより他ない。

例:リコー製超小型シリアル入出力リアルタイムクロックモジュール
RICOH Rx5C348A/B
http://www.ricoh.co.jp/LSI/spec/rtc/5c348/5c348ab-j.pdf

(2)ミリ秒msec単位、ならびにマイクロ秒μsec単位のwaitモジュールは既にファームウェアの中に実装されている。

(3)今回のPICNICファームウェアでなく、別のプロジェクトでインターネット上のNTPサーバから日時を取得するプログラムを作成した例がある。
http://park11.wakwak.com/ ̄domo/time.html

(4)何らかの方法で現在日時をセット可能で、かつ暦計算のルーチンが組み込めるだけのプログラムメモリの余裕があり、さらに、内部タイマ割り込みを発生させ、秒単位のカウントアップが実現できれば、このPICに「現在時刻」をソフトウェアとして実装することは、リクツの上ではできるかもしれない。

※上記(3)では、実際に、100msecの内部タイマ割り込みを発生させてカウントしている。

しかしながら、添付のv12.asmファームウェアは、現在の機能の実現のために、プログラムメモリを使い切ってしまっているために、何らかの機能削減を行わないと、そのままでは実装困難である。

(5)(4)の実装では、電源が切れてしまうと現在時刻を忘れてしまうために、別途、外部から何らかの方法での日時セットが必要となる。これではイタチごっこなので、結局バッテリバックアップ付きのRTCを外部に持たせる(1)の方法が最も良いソリューションであろう。


6.3.13 CGI ?引数コマンドパースとEEPROMへの書き込みについて

gatewayアドレスの変更などをwebブラウザから行うと、URL中の?引数が渡されてEEPROM中のgatewayアドレス値が書き換わる。

例:http://192.168.1.200:8080/submit.cgi?00b=192.168.1.200&04b=255.255.255.0&08b=0.0.0.0&10w=8080

のなかの、08b=0.0.0.0 がそれ。(byte単位で0,0,0,0を書く)

(1)引数コマンドパースとEEPROMへの書き込みの概要

++++++++++++++++++++++++++++++++++++++++
<処理>
estab0:中において、
; '?'文字を発見?(=GETコマンドの処理)
MOVLW '?'
SUBWF data0,0
BTFSC 3,2
GOTO parse_cgi_tag
++++++++++++++++++++++++++++++++++++++++

としており、URL中に"?"が含まれると、それ以後を引数とみなして解析モジュールparse_cgi_tag へ飛ぶ。こればメモリバンク間ジャンプ用のタグであり、実際の処理はparse_cgi モジュールである。

4374行目付近からのparse_cgiの処理概要は以下の通り。

++++++++++++++++++++++++++++++++++++++++
parse_cgi
 次の文字を読む。
 文字列が終っていたら終了→parse9_tag
 I/O関係かチェック
 word単位かチェック
 byte単位かチェック
 "&"が現れたら次の引数なので最初に戻る

parse_cgi0
 "&"が現れたら次の引数なので最初に戻る
 "."区切りの数値を端から読んでいく
 まだ続くようであれば parse_cgi0へループしながら値取得

parse_cgi2
parse_cgi3
 "."区切りの数値から計算した数値を、EEPROMへ書く
 次の引数チェックのために最初に戻る

parse9_tag
 バンク間ジャンプのタグ。
 goto parse9 でTCP送信処理(estab1と同じアドレス)
++++++++++++++++++++++++++++++++++++++++

ということで、gatewayアドレスの変更が指示されたら、即、EEPROM上の値を書き換えていることがわかる。

(2)IPアドレスとhttpポート番号はメモリ上変数の値を使用

現在のファームウェアのバージョンでは、IPアドレスとhttpポート番号を、リセット後の立ち上げ時に EEPROMから読み出して、メモリ上にthis_ip、http_port としてコピーを保持している。

パケットヘッダ生成時などには、EEPROM上でなく、このメモリ上のコピーされた値を使っている。

したがって、IPアドレスとhttpポート番号の変更をweb画面から行っても、その時点では、EEPROM上の値を書き換えているだけで、リセット後、this_ip、http_port として読み出されるまでは、設定が反映されない。

それ以外の netmask値、gateway値については、EEPROM上の値を直接読んでいるようなので、即時反映される。

<ヒント>

netmask値、gateway値については、ルータ下に接続されたローカルPC側から見ると、通常のHTTP応答時には無関係(同一セグメントだから)であるので、これらの値を変更・反映させても、web制御画面などは「継続して」見える。


6.3.14 DHCPによるIPアドレス取得時の処理について(注意点)

取り扱い説明書には、
 「IPアドレス=0.0.0.0とすることで、リセット時にDHCPによるIPアドレス取得 ができる」
との記述があるが、実際の運用時には注意が必要である。

現在わかっている不具合は、以下の通り。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
DHCPサーバからの応答のうち、IPアドレス値しか利用しておらず、
ネットマスク値、dhcp_router_ipの取得値はセットされない。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

(1)DHCPによるアドレス取得動作

<DHCPプロトコルパケット>

;               DHCPプロトコル
;
dhcp_header SET udp_data
dhcp_ope SET udp_data+0
dhcp_type SET udp_data+1
dhcp_phylen SET udp_data+2
dhcp_hop SET udp_data+3
dhcp_trans SET udp_data+4
dhcp_sec SET udp_data+8
dhcp_dummy SET udp_data + .10
dhcp_client_ip SET udp_data + .12
dhcp_user_ip SET udp_data + .16
dhcp_user_ip1 SET udp_data + .17
dhcp_user_ip2 SET udp_data + .18
dhcp_user_ip3 SET udp_data + .19
dhcp_server_ip SET udp_data + .20
dhcp_server_ip1 SET udp_data + .21
dhcp_server_ip2 SET udp_data + .22
dhcp_server_ip3 SET udp_data + .23
dhcp_router_ip SET udp_data + .24

<DHCPによるIPアドレス取得の処理概要>
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
main:
 ; 自分のIPアドレスが0.0.0.0?
do_dhcp
 dhcp_doneフラグ立てる
 call dhcp

dhcp:
bootp_tx:
 dhcpアドレス取得要求のUDPパケットを送信
bootp_res:
 自分のMACアドレス向けのbootpの応答チェック
 返ってきたら、取得したIPアドレスdhcp_user_ipを this_ipとして格納

→タイマ割り込みルーチン内でチェックし、dhcp_doneフラグ下ろす

※main0ループ内では、dhcp_doneフラグをチェック。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

ということで、DHCPサーバからの応答のうち、dhcp_user_ip〜ip3しか利用しておらず、ネットマスク値dhcp_router_ipの取得値はセットされない。

従って、defaultの
netmask=255.255.255.0
gateway=0.0.0.0
のまま稼動しているので、注意が必要である。

(2)ルータ外部とのアクセスが必要な場合

ネットマスク値とgateway値を

・あらかじめEEPROMの初期値として書き込んでおく あるいは、
・あるいは、別途、ルータ内部に接続されたPCから、URL?引数を使って
値をセットする必要がある。

(3)ゲートウェイアドレス等も自動で取得して動作するようにできる?

ネットマスク値、dhcp_router_ipの取得値をセットするように自分でファームウェアを変更すれば、Windowsパソコンの場合と同じように、ゲートウェイアドレス等も自動で取得して動作するようにできる、というのは、リクツから言えば可能であろう。

this_ip, http_port などと同じように、メモリ上の変数として各々確保し、固定IPで運用しているか、DHCPサーバからの取得か、の各モードに応じて、netmask, gateway値も動的に変化させれば良い。

値のセットは、dhcpサーバが応答してthis_ipなどの値セットをしている場所に機能追加すればよい。

現在のファームウェアに対する改造で難しいところは、netmask, gateway値を参照している箇所全てが、EEPROMからの値リードとなっていて、これを行っている全ての場面でメモリ上変数からのリードに変更しなければならない点である。

※難しいですが、うまく安定動作したら、ver1.2.1.0として配布して喜ばれるかもしれませんね :-)

(4)DHCPのプロトコルの規格について(BOOTP)

DHCPのプロトコル規格の詳細については、インターネットワーキングプロトコルなので、全てRFCから取得せよ。
http://www.faqs.org/rfcs/

DHCPのドラフト標準は、RFC2131。
RFC2131標準:
http://www.faqs.org/rfcs/rfc2131.html


歴史的には、先にBOOTPがあって、それを拡張する形でDHCPが出来た。

RFC 951(BOOTP)
RFC 1084(BOOTP Extensions)
RFC 1542(Clarifications and extensions for BOOTP)

RFC 2131 Dynamic Host Configuration Protocol, Draft Standard Protocol
RFC 2132 DHCP Options and BOOTP Vendor Extensions

<参考URL>

http://www.net.intap.or.jp/oiia/cont1/p0302.html%7B0recid=10342.html
http://www.net.intap.or.jp/oiia/cont1/p0302.html%7B0recid=10024.html


6.3.15 「送信パケット数」の表示の仕組み

送信パケット数の表示について、Sent Packets $9 として値埋め込みを行っている。具体的な処理について以下に示す。

(1) send_mesモジュールによる処理分岐

send_mes0:モジュール内の、
; 上位7ビットがメタ文字なら処理を分岐
MOVLW '$'
SUBWF data0,0
BTFSC 3,2
GOTO ctrl_code2
でマッチした場合、ctrl_code2に飛び、更にctrl_code1へ飛んで$?の値に応じた
(? -"0"(0x30)した)相対ジャンプをさせている。
ctrl_code1
ADDWF 2,1
goto put_mac_address ; 0:MACアドレス
goto put_ip_address ; 1:IPアドレス
goto put_netmask ; 2:ネットマスク
goto put_gateway_address ; 3:ゲートウェイ
goto put_http_port ; 4:httpポート
goto put_lcd_port ; 5:lcdポート
goto put_io_port ; 6:parallelポート
goto put_232_port ; 7:232cポート
goto put_version ; 8:バージョン表示

goto put_send_packet ; 9:送信パケット数
goto put_this_ip ; A:自IPの表示
goto put_ptop ; B:PtoP IPアドレス

※$9の次は、'$'+0x3a(":")である。(ASCIIコード表参照)

$9の場合、put_send_packet:モジュールが呼ばれる。このモジュールの処理は、IPパケットシーケンス番号:identを参照し、

val1 <-- シーケンス番号:identの上位
val <-- シーケンス番号:identの下位
val2,val3 <-- 0

を各変数に格納した後、32bit->10進変換:put_decimal32を呼び出し、 送信パケット数を表示する。

(2)バイナリ32ビットの10進変換ルーチン : put_decimal32モジュール

バイナリ32ビットの10進変換ルーチンput_decimal32()モジュールは、表示するためのバイナリ値を格納した領域:(val3:val2:val1:val)から、指定する4ワード(バイト有効)分を「引数」として読み込み、32bitバイナリ値を10進数(val_m)として表示する。

関係する変数メモリ領域は以下の通り。

※環境設定用変数
ident EQU 0AEH ; IPパケットシーケンス番号
ident1 EQU 0AFH ;
※グローバル変数
val EQU 32H ; 第1バイト
val1 EQU 33H ; 第2バイト
val2 EQU 34H ; 第3バイト
val3 EQU 35H ; 第4バイト
val_m EQU 36H ; for DECIMAL
val_cn EQU 37H ; 除算の余り?
※10進変換用作業エリア(org 120h してないけど大丈夫?)
decimal_top EQU 120H ; バイナリ→10進数変換用

 


6.3.16 v12.asmの内部タイマ・割り込み関係の処理について

<概要>

・DHCP要求完了のタイムアウト処理のために、内部タイマTMR1を使用している。
・ソケットのタイムアウトチェックは、dec_tmで行っている。これにも、内部タイマTMR1を用いている。


(1)スタートアップルーチンstart:モジュール内での、タイマ・割り込み関係初期設定の様子

(a) オプションレジスタ設定
(4662行目付近)


                bsf     STATUS,RP0
MOVLW B'10000110'
DB 00,62 ; オプションレジスタ
......
bcf STATUS,RP0

レジスタバンク#1/3に切り替え
Wreg←B'10000110'(bit7,2,1をイネーブル)
機械語コード 0x0062 => オプションレジスタ(BANK#1, 81h)←Wreg

(参考)
オプションレジスタWregロードの機械語コード(00.0000.0110.0010)
PIC16F87x Complete Reference Manual page554
microchip/33023a.pdf#page=554

※なぜOPTION_REGに86hをロードしているのか、謎。
(PIC16Cとの互換性云々とデータシートには書いてある。)


(b) USART設定&割り込みenable
(4781行目付近)


                bsf     STATUS,RP0
MOVLW B'10100000' ; rcを元に戻す
MOVWF TRISC
MOVLW B'00100110'
MOVWF TXSTA ; ASYNCモジュール送信側を初期化
MOVLW BAUD_RATE
MOVWF SPBRG ; ボーレート設定
BSF PIE1,5 ; rcie; 受信割り込み許可
bcf STATUS,RP0
MOVLW B'10000000'
MOVWF RCSTA ; ASYNCモジュール受信側を初期化
bcf STATUS,RP0
bcf INTCON,6 ;peie ; 周辺装置割り込み許可

シリアルインターフェースUSARTの受信処理のための割り込みを設定する。
割り込み制御レジスタへのセットは、

PEIE(INTCON[bit6])
PIE[bit5] (RCIE: USART Receive Interrupt Enable bit)

である。

(参考)
割り込みコントロールレジスタ INTCON, PIE
PIC16F87x Complete Reference Manual page127-- §8.2.1
microchip/33023a.pdf#page=127


(c) DHCP要求中フラグクリア、タイマTMR1初期化
(4803行目付近)


                bsf     STATUS,RP0
CLRF dhcp_done
bcf STATUS,RP0
CLRF PORTB ; 初期状態 RB=00h
CLRF TMR1L
CLRF TMR1H
MOVLW B'000101'
MOVWF T1CON

DHCP要求中フラグ(dhcp_done)クリア(BANK#1)
PORB 全ビットLow(BANK#0)

タイマTMR1 16bit([TMR1H]:[TMR1L])ゼロへ初期化
TICON ← B'xx000101'
; T1CKPS1:T1CKPS0 := 0:0, !T1SYNC:=1, TMR1ON :=1

TMR1CS := 0 なので、内部クロックを使用する。

!TISYNC : Timer1 External Clock Input Synchronization Select bit
-- 1 = "Do not synchronize external clock input"

TMR1ON: Timer1 On bit
-- 1 = "Enables Timer1"

T1CKPS1:T1CKPS0: Timer1 Input Clock Prescale Select bits
-- 00 = 1:1 Prescale value(Fosc/4でカウントアップ)


以上のことから、TMR1の0x0000-->0xFFFFロールオーバまでの時間を計算すると、
PICNICの現在の Fosc=20MHz(50nsec)であるから、
Fosc/4 = 5MHz(200nsec)。これで 65535カウントするから、ロールオーバして
割り込みがかかるまでにかかる時間は、

200nsec x 65535 = 13,107,000nsec (約13.1msec)となる。

(参考)
TMR1コントロールレジスタ T1CON
PIC16F87x Complete Reference Manual page183
microchip/33023a.pdf#page=183


(d) タイマTMR1割り込み許可、周辺割り込み許可、グローバル割り込み許可
(4811行目付近)


                BSF     STATUS,RP0
BSF PIE1,TMR1IE
BCF STATUS,RP0
BSF INTCON,PEIE
BSF INTCON,GIE
BSF INTCON,GIE ;gie; グローバル割り込み許可
goto main

PIE1[TMR1IE] セット:タイマTMR1割り込み許可
INTCON[PEIE] セット:周辺割り込み許可
INTCON[GIE] セット:グローバル割り込み許可

※なぜGIEイネーブルを2回やっているのかは不明。

 

(2)main, main0ルーティンについて

startの後、mainルーティンへ飛んで、DHCP関係への条件分岐をチェックした後、メインループ: main0 へ突入する。main0ループ内では、

・USART I/Fからの受信チェック(receive232c)
・DHCP要求の完了チェック(do_dhcp)※タイムアウト管理にタイマTMR1を使用
・Ethernet-NIC受信バッファのオーバーフローチェック(していれば空読みoverflow)
・Ethernetからのパケット受信チェック(get_packet)

の処理を順次行う。

各々へgotoした後は、このmain0のラベルへ再びgotoして戻ってくるように工夫されている。

 

(3)割り込み処理ルーティンについて

PIR1,TMR1IFをチェックして、TMR1割り込みである場合、
int_tmr1へジャンプ

DHCP done の処理:int_tmr1

TMR1のロールオーバー間隔 約13.1msec * 80h
= 1676.8msec(約1.68秒)の間隔で、DHCP取得要求を出す
タイマ管理用のメモリ:dhcp_done(BANK#1, 0xB2)

ソケットタイムアウトチェック:int_tmr2
タイマ管理用のメモリ:timer, timer_cn(BANK#1, 0xB3, 0xB4)を使っている
タイムアウト管理モジュール:dec_tmへジャンプ

→dec_tm9へ戻ってくる

 


6.3.17 ad_inモジュールのADCON0制御レジスタに関する実装誤りについて

・ad_inモジュールでA/D変換器の制御のためにADCON0レジスタを変更する場面で、実際のファームウェア(v12.asm)では、これが ADCON1レジスタへの値変更を行っているように見える。
・しかしながら、STATUSレジスタのRP0をHighにしていないので、BANK#0のADCON0レジスタへの操作となっていて、結果として「正しく」動作している状態である。
・この部分は、ADCON0への操作とするコードを書かなければならず、実装上の誤りがあるので留意のこと。

ad_inモジュールの実装
(2320行目付近から)


;		AD変換
;
ad_in
RLF getmes_wk1,1
RLF getmes_wk1,1
RLF getmes_wk1,1
MOVLW B'00111000'
ANDWF getmes_wk1,1
MOVLW B'10000001'
IORWF getmes_wk1,1
MOVF getmes_wk1,0 ; ADCON1に値を設定
MOVWF ADCON1
MOVLW 1
MOVWF wait_cn
MOVLW HIGH (wait_ms)
MOVWF PCLATH
;
call wait_ms ; サンプリング時間20us待機
MOVLW HIGH (ad_in)
MOVWF PCLATH
;
bsf ADCON1,2 ; AD変換開始
btfsc ADCON1,2 ; AD変換待ち
goto $-1

上記の範囲の ADCON1 は全て ADCON0 と書かなければならない。

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

Creative Commons License

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