MZもLANにつなぎたい(完結編)
待てば「回路」の日和があるもので
ネットワークにつながらなきゃコンピュータじゃない、と言われて久しいですが、いまだに8bitパソコンにはLANがありません。もちろんサポートの終わった製品にメーカーがなんとかしてくれるわけはありませんから、ユーザーの手でいろいろと考えて試みたりなどしているのですが、なかなか実を結びません。
悶々とした日々を何年過ごしたか…しかし、WIZnetの製品W3100Aに出会い、一気に様々な障害が吹っ飛びました。ついにMZも本当にネットワークにつながるのです。
これまでの試みなど
本題の前に、これまで見聞きしたり実行してみたことを拾ってみましょう。
まずは「MZもLANにつなぎたい」。とりあえず8bitマシンは横に置いて、16bitマシンに関して考えてみました。まぁ考えただけ。でも今回のは16bitマシンは対象外のつもりなので、生きていると言えば生きています。ただなんとなく、PC/AT互換機のDOSマシンでNetBEUIプロトコルでの接続がWinXP・SP2になってつながらなくなったように思います。手をこまねいてる間に事態は悪い方向に向かっているということなのでしょうか…。
いくら8bit用といってもGP-IBネットワークってのは無理があるでしょう、ということで次に考えたのがデュアルポートメモリを専用通信線でつなぐ「8ビットマシン用ネットワークI/F」。ハード・ソフトのレベルは8bitマシンにふさわしいものだと今でも思いますが、本物のネットワークにつなげるための方法が全く考えられませんでした。まぁAT互換機をゲートウェイとして使うようなことになるのでしょうが、そこはやはり独自I/F、AT互換機のほうに開発の負担がかかってしまいます。
ならばLANを電話線に見立てて、モデムとして振る舞えば当時からある通信ソフトと親和性が高く開発の手間が少しは省けるかと取り組んでみたのが「シリアル-LANアダプタ」です。何も自分で全部作る必要は本来ないはずですが、悲しいかなシリアルラインとLANを変換するもののほとんどがサーバとして動作し、クライアント機能を持つものが(当時は)皆無だったために望むと望まざるに関わらずTCP/IPの勉強をすることに…。得るものもたくさんあり、おこづかいも稼げましたので悪いことばかりではないのですが、頓挫してしまっています。書いてないですが、主原因はKL5C16030内蔵の周辺機能による割り込みに「禁止」がないこと。受け付けないモードはあるんですが、その間に割り込みが発生すると、ただペンディングされて捨ててくれないのですよ。割り込み受け付けを許可してすぐの割り込みが、今起きたものか、過去起きたものか、区別がつかないのはイケてないです…。
別の方法として、Armadillo-JにシリアルとLANの仲立ちをさせればソフト開発は楽かもと調達してはみましたが、通電さえせず。これはこれで別の用途で遊ぼうとは思いますが。その中身のDigi Connect MEやLANTRONIXのXPortなどはソフトが組みにくいなど問題がありますし、またアルファプロジェクトのezTCPシリーズもなかなかいいかと思ったのですが未入手。
本当に8bitパソコンでTCP/IPは無理なのか?というそもそもの疑問があるのですが、世の中にはContikiのように(SLIPですが)TCP/IPを実装しているものや、そこまで立派なOSでなくてもPICNICのように狭いメモリにサブセットくさいですがなんとか実装しているものもありますし、簡単にあきらめてはいけないのかもしれません。
そこで、私ではないですがひとつ挑戦してみたのが同人誌「MZ ENCYCLOPEDIA Vol.8」の「ネットワークのつなぎかた」。初級編としてMZをシリアルコンソールとして使い、中級編としてSLIP通信を実装し、上級編としてLANボードを製作してIP通信しています。残念ながらTCPは未実装(テスト版にはあるようですが)、それでプロトコルスタック込みのアプリのサイズが40〜50KBと、およそ実用とは思えないオブジェクトになっているようです。
そう、8bitパソコンには不可能ではないにせよTCP/IPは重たいのです。プロトコルスタックだけで数十KBのプログラムサイズとなり、アプリの使える余地はほとんど残されていません(場合によっては、スタックなどが不足することもある)。すでにプロトコルスタックがアプリのサイズになってるわけです。
かと言って、拡張ボードにLANとTCP/IPを処理するためのマイコンを搭載するのは、かなり現実的ではあるもののどこか敗北したような感じで釈然としません(外付けの変換ボックスと変わらんだろうという話もありますが)。それにそこのソフトの開発が必要なこともまた事実です。
W3100Aとは
ふとしたきっかけでCTRON実装したい熱に浮かれていた時、ああVMwareが無料で使えるんならこれで実装すると開発が便利かなぁ、どうもこれにはNICとしてAM79C970が使われているらしいなぁ、そういえば最近CQ出版社から出た「Ethernetのしくみとハードウェア設計技法」とかいう本にAM79C97xの解説が出てるみたいだなぁ、やっぱり資料として買っておくかぁとその本を手にしたのですが、Appendix
4としてWIZnetのW3100Aというチップが紹介されていました。どうもこいつは、TCPなどをハードで処理しマイコンとは送受信するデータの交換を行なうのみの簡単に使えるチップらしいことがわかりました。
特徴としては、
となっていて、なかなか使いやすそうです。なんといっても、面倒なTCPやUDPまでもがチップで処理されるため8bitパソコンとしては実質的にアプリを書くことに専念できるというのが魅力的です。マイコンを使っているわけでもないので、もやもやした気分で使うという心配もありません(^^;。
購入チャンネルを調べるうち、PHYまでセットになったモジュールのNM7000Aやパルストランス・LED内蔵のコネクタがつくNM7010A、PPPoEやIGMPにも対応したチップであるW3150Aとそのモジュール品であるNM7010Bなど、いろいろな製品があることがわかりました。W3100Aの端子が0.5mmピッチということもあって工作が大変かなと思っていたのですが、モジュール製品があるならその方が簡単です。
購入は国内代理店は無理みたいですが海外に取り扱っているショップが若干数ありました。そのうちで最も安いところを選んで購入しました。PayPal支払いなのでちょっと面倒ですが、発注から1.5週間以内に届くようです。
なおデータシートは本家WIZnetの他、日本国内最大の代理店であるアイヴィス(トップページのみ、古いFirefoxから見るとブラウザが死ぬかもしれません。面倒がらずにバージョンアップしてね)の製品紹介ページからも豊富に入手できます。データシートだけでなくサンプルプログラムもありますので、参考にさせていただきました。
W5100とは
W3100Aのモジュールを使って試作した後もたもたしていたら、その改良版としてW5100というものが登場しました。レジスタ構成やアクセス方法など若干違うところはあるものの、基本機能としてW3100Aでできることは全て継承した上で、いくつかの追加機能とPHYを内蔵してより使いやすく改良されています。
その後登場した改良版やシリーズ製品を含めて日本国内でも容易に手に入るようになり、またArduino用のEthernetシールドに採用されたことからその存在も広く知られるところとなり、いつの間にかひっそり消えてしまうというような心配も無用そうです。
私も二次試作品からW5100に切り替えました。W3100Aのようにディスコンにはならなさそうなので、これで進めていきます。
一次試作品
MZ用のユニバーサルボードはもう製造されてませんので、サンハヤトのCPU-110A-DOTという大きさがよく似ているボードを流用し、ささっと作ってみました。基板の加工のしかたはtree氏の「How to create MCC-157」を参照してください。
表 | 裏 |
基板上の部品は大きく4つのブロックに分けられます。
CPLDを使用してはいますが、書き込んだ回路の大部分はRAMディスクのためのもので、NM7010A向けにはアドレスデコードとリセットの反転をしているにすぎません。Z80のI/O領域に配置することとし、アドレスエリアの問題からNM7010A(W3100A)はインダイレクトモードでアクセスすることにします。アドレスは&H60〜63の範囲としました。
割り込みについては、TCPのウィンドウ制御でフローコントロールされることから、とりあえず必要ないと考えて何も対策していません。使用するならZ80側ではモード2割り込みになるようにCPLDに回路を作り込むつもりですが、本当にポーリングだけでいけそうな雰囲気です(ソフト編にて説明)。
一.五次試作品
確かにポーリングだけで済ませてそれなりのものは動かせるボードではあるのですが、適用例によっては割り込みもあった方がよかろうということで、追加してみました。追加後の写真は間違い探し並に差異が少ないので、今回は回路図を掲載します。CPLDのVHDLソースは後日公開します。
割り込みについてのI/Oマップは次のとおりとしました。
R/W | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
割り込みベクタ (64h) |
R/W | ||||||||
割り込み制御 (65h) |
W | イネーブル | |||||||
ステータス (65h) |
R | 割り込み 入力 |
割り込み 受付け |
イネーブル 状態 |
割り込みベクタはモード0用に8ビット分あります。モード0使用時には8種類あるRST命令のいずれかをセットします。モード2使用時は上位7ビットに割り込みベクタをセットします。最下位ビットはZ80によって無視されます。
割り込み制御は、1を書き込むことで割り込みを受付けられるようになります。リセット時は0(ディセーブル)です。
ステータスは書き込んだイネーブルビットの読み出しと、割り込み状態を表示します。割り込みが発生しているとビット7に1が入ります。割り込みアクノリッジが発生して割り込みが受付けられたなら、ビット6に1が入ります。単なる参考用で、使い道はあまりないと思います。
一.五次試作品(改良後)
実は一次試作品を作った当時から、ノイズに弱そうな特性が現れていました(下記「製作上の注意点」にも関連した説明を書いています)。最初はパスコンで対処できていたものの、特にMZ-2800では受信したHTMLテキストに文字化けが多発し、パスコンなんかの付け焼き刃ではどうしようもなくなりました。
しかしいろいろ調べて見つけた方法、CPLDの出力バッファのスルーレートを遅くすることと未使用端子のGND固定でかなり改善されたことから、GND強化が効果的かもしれないと考え、日本橋に出たついでにサンハヤトの磁気ガード(銅箔テープ)を購入してGNDの面積を増やしてアースをしっかりとる加工をしてみました。
すでに部品や配線があるのでやや控え目に、裏面だけ貼りました。銅箔の左の切れ欠きは電源関連を避けたもの、右の空き地は二次試作品用コネクタ変換基板の予定地です。
効果はてきめんで、MZ-2800でも全く文字化けが起らなくなりました(まぁ改良前は細いスズメッキ線1本で意味のない1点アースだったのですからものすごい違いがあるわけですが)。最初故障かと思って買い直しそのまま予備としていたNM7010Aがあったのですが、実はその予備の方がノイズ耐性が低く、もっとたくさんの文字化けが発生していました。しかしGND強化後はこちらも全く問題なく使用できるようになりました。やはりGNDは大切ですねぇ。
※スルーレートを遅くする…デジタル信号の変化はよく直角に立ち上がって直角に安定するような表現がされますが、現実はそんなことはなくていくらかの時間をかけて変化しています(ごく微少な時間なんですけど)。この変化にかかる時間がスルーレート(Slew Rate)です。スルーレートが遅くなると、それだけH/Lの認識が切り替わる閾値を通過するのも遅くなり、結果として信号伝播遅延も大きくなります。一般的に配線が長くなったり分配される数が多くなるとスルーレートも遅くなる傾向があります。CPLD/FPGAではその影響を小さくするためスルーレートを速くする設定が存在するものがあります。スルーレートを速くするというのは実際には流す電流の量を増やすことを意味するのですが、その分ノイズを大きくしてしまう危険性もあるわけです。そこで、今回の基板のように分配数の少ない回路ではスルーレートが遅くとも問題にはなりにくいので、ノイズ低減を優先して遅く設定するという選択肢もあります。何事も速ければ優れているというわけではないのです。
製作上の注意点
この程度のものなら特に回路図を書かなくとも作れてしまうような簡単なものですね。本当にこれだけでEthernet基板が作れてしまうのですから、たいしたものです。ただ、ちょっとだけ注意した方が良い箇所がありましたので、説明しておきます。
NM7010Aなどのモジュール製品は、どういうわけか端子ピッチが2.0mmのコネクタを使用しています。そのおかげで写真の印象に比べて実物が小さく見えたりもするのですが、当然ながら普通の2.54mmピッチ穴基板には取り付けられません。今回は別に2.0mmピッチ基板を調達し、メイン基板に穴をあけて貼付けました。といっても接着剤は使わず、リード線の余りを使ってはんだ付けしています。
また、完成して通電試験していると、突然リンクが切れてしまう(ハブのリンクランプが消灯してしまう)現象が発生しました。リンク自体はW3100Aを初期化しなくともPHYチップによって自動的に確立するはずなのですが、切れたり復活したりと不安定なのです。当然初期化してping試験をしていても、突然応答がなくなり、しばらくするとまた応答するようになるという現象を繰り返していました。繰り返しの周期も特に決まっていない様子です。
WIZnetのサイトには一般ユーザーからの質問を受け付けるコーナーがあるのですが、そこには一件だけ同様の症状を訴えるものがありました。しかしキャプチャしたパケットのデータをくれという対応のみで、解決したのかどうかわかりません(そもそもリンクが切れるのだから、パケットの取り込みようもないのだけれど)。
いろいろ調べてようやくわかったのは、PHYチップへのリセットにノイズが入っているようだということで、GNDとの間にバイパスコンデンサを入れることでこのトラブルは解消しました。リセットのノイズ耐性はNM7010Bの方が優れているようで、未対策でもリンクが切れることはありませんでしたが、NM7000Aを使用する予定であったことからNM7010Bに切り替えてしまうことはしませんでした。
一.五次試作品(改良後)でも書いてますが、GNDはしっかり確保する必要があります。やはり100Mbpsの高速信号を間接的とは言え取り扱っているのですから、そこをおろそかにはできないということですね。
二次試作品
二次試作では当初、NM7000Aにモジュールを交換してコネクタをMZ基板に降ろすよう計画していましたが、一.五次試作以降かなり時間を置いてしまったのと、その間にいくつか新しいデバイスが手頃になってきたこともあり、基板製作を視野に入れつつ仕様を変更しました。
表 | 裏 |
概要はこんな感じ。
一.五次試作品を改造して制作しているので、元のボードは残っていません。FPGAは容量がかなり余っているので、基板化の際にはひとつ小さいものに替えるかもしれません。
三次試作品
とりあえずWIZ810MJのアクセスなども成功し、Pingが通るようになったりとかしたんですが、一方でRAMディスク機能のデバッグに苦しみました。その原因はZ80バス信号に含まれるノイズ。「RGBI対応アップスキャンコンバータ」で確認した現象をここでも説明しますが、おそらくこのボードでも同様の現象が発生していると思われます。
二次試作品で、Spartan-IIを採用したのは「5Vトレラント入力」が可能だからでした。Spartan-II自体は3.3Vデバイスですが、5Vレベルの入力にも対応できる回路を備えているため、一種のレベルコンバータになると考えたのです。
これはFPGAに出入りする信号をオシロで観測したもの。上が入力、下が出力で、5Vロジック回路だとまぁこれくらいはあるかなという入力のリンギングが、出力では見事にまともなHレベルに変換されてしまってます。 3.3Vなのでせめて1.65V、LVTTL設定なのでもっと高いところに閾値があると期待していたのですが、現実には1V近辺にあると考えて良さそうです。 |
このノイズのために、例えばEMMでデータを読み出すと、なぜかアドレスのカウンタが余計にカウントアップしているという現象などが発生しました。これを押さえ込むため1クロック打ってずらすとかいろいろ工夫したのですけど、結局決定打といえるようなものはありませんでした。
つまり、5Vトレラント入力というのは5Vが入力されても大丈夫というだけのことで、5Vロジックに従って動作する回路ではなかったのです。
そこで、スキャンコンバータでも試したようにレベルコンバータを使用してみることにしました。スキャンコンバータではHC CMOSデバイスという、特性上レベルコンバータとして使用できるというものを使いましたが、今回は本格的にレベルコンバータICを使用することにします。
ただ、前回と同様にこれまでの基板を改造して手配線にて製作を始めたのですが、さすがに3度目で面倒になってきたのと、ちょうどワークサイズパッケージという価格的にもサイズ的にも手頃な基板製作サービスが期間限定で出てきましたので、これで基板を作ってみました。
表 | 裏 | |
基板パターン図 | 出来上がった基板 |
部品実装後 |
概要はこんな感じ。
しかしワークサイズパッケージが期間限定だったのは残念ですね…。
とりあえず、現時点でほぼ全部の機能を確認できているので、仕様書代わりにCPLDのアドレスマップを書いておきます。
R/W | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
W5100 アクセスモード (60h) |
W | RST | AI | IND | |||||
W5100 アドレス上位 (61h) |
W | ||||||||
W5100 アドレス下位 (62h) |
W | ||||||||
W5100 データ (63h) |
R/W | ||||||||
割り込みベクタ (64h) |
R/W | ||||||||
割り込み制御 (65h) |
W | イネーブル | |||||||
ステータス (65h) |
R | 割り込み 入力 |
割り込み 受付け |
イネーブル 状態 |
|||||
メモ (66h) |
R/W | ||||||||
UFM アドレス (68h) |
W | ||||||||
UFM ステータス (68h) |
R | ライト プロテクト |
データ 有効 |
UFM ビジー |
|||||
UFM データ (69h) |
R/W | ||||||||
アンロック (6Fh) |
W |
W5100レジスタはソフト編を、割り込みレジスタは一.五次試作品での説明を参照してください。
メモレジスタは、値が保管されるだけで何の機能もありませんが、例えばLANの初期化状態をアプリケーション間で伝達することを想定しています。例えばあるアプリケーションでDHCPにてアドレスを取得しているのに、そのまま別のアプリが起動してまたDHCPでアドレスをもらうのはムダですし、必要以上にアドレスを消費してしまいます。もしここを読んでアドレス設定済みだとわかれば、すぐアプリの動作に移れます。このレジスタはリセット後クリアされますが、システムのリセットだけでなくW5100チップのリセットも監視していて、W5100と共にリセットされるようになっています。
UFMアドレスレジスタはこれからUFMにアクセスするアドレスをセットします。00h〜FFhまで指定できます。同じレジスタを読み出すと、ステータスとしてUFMアクセス状況とライトプロテクト状態('0'で書込み禁止)が判別できます。UFMに読み書きするデータはUFMデータレジスタを介します。
アンロックレジスタは、ボード内の機能に対する制御を行うためのもので、まずD1hを書き込んでから、各機能のキーワードを書き込みます。キーワードと対応する機能の一覧を下に示します。
キーワード | 機能 | リセット時 |
12h | スタティックRAMの容量を64KBに倍増 | 32KB |
37h | MZ-2500用EMMのスイッチがOFFの時に、EMMを有効にする (ROMをONにしている時に他のアプリケーションがEMMを上書きしてしまうのを防止する) |
EMM無効 (EMMスイッチOFF時のみ) |
05h | MZ-700用の拡張ROMとFD ROMのエリアのライトプロテクトを解除する | ライトプロテクト |
57h | UFMエリアを消去する(ライトプロテクト時でも消去します) | ─ |
0Fh | UFMエリアへのライトプロテクトを反転する | ライトプロテクト |
あと、プログラムROMを搭載するという関係上、バンク切り替えレジスタもあります。
量産先行試作品(RAMファイルボード互換)
三次試作品は些細なミスがや若干のトラブルはあったものの良好に動作し、次はいよいよモジュールを流用しないちゃんとしたボードとしての設計(でないとスロットにきちんとは収まらないので)だ…となるはずなのですが、なまじ動作上は目標通りのものが出来上がるとテンションが下がってしまい、某所などでのデモでも三次試作品で済ませてしまう有様でした。
それでもこんなツールを作ってみたり、こういうボードを入手したり部品を購入するなど徐々に準備を進め、他の趣味に紆余曲折しつつ、ある時ふと上がったテンションを足がかりにRAMファイルボード互換として作成してみました。RAMファイルボードだと基本的にはMZ-800/1500に内蔵する形で使うので適用機種も少なくなってしまうのですが、これを選んだのはひとえにブラケットが不要だからです…まだブラケットをどうやって作るかわからなかったんですよね…。
表 | ||
裏 | ||
基板パターン図 | 出来上がった基板 |
部品実装後 |
概要はこんな感じ。
三次試作品ではワークサイズパッケージの制約により端子の金メッキが指定できなかったためカードエッジコネクタもハンダパターンになっていたのですが、制約のない今回も同様なのは試作ゆえ費用を節約したためです。ちょっとはんだ付けのコツを忘れてしまって苦労しましたが、これで動作することを確認しました。ただLANコネクタがこの向きだとスペースに余裕がないことが発覚し、LANチップごと90度回転させることを検討しています。
上記の通りCPLDはRAMファイル非対応機種とも共通ですが、デバッグ等に便利かと思いボード上のDIPスイッチの状態を読めるようにレジスタを追加しました。OFF=1、ON=0となります。
R/W | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
DIP SW (67h) |
R | EMM (2500) |
ROM (2500) |
RAM FILE |
EMM (700) |
EMM (80B/2000) |
S-RAM | EX. ROM |
FD ROM |
今回とある方にお願いしていろいろいじっていただいたのですが、バッファに書き込む方法がかなり面倒(リングバッファの管理をプログラムでやらないといけない)ということで、その改良と別の拡張機能を考えるとCPLDの改良をやりたくなってきました…さてどうしようか。
レジスタマップみたいな情報などいろいろ端折ってますので、データシートを横に置いてご覧ください。
基本的なアクセス方法
W3100A |
今回の回路ではインダイレクトモードを使用することにしましたので、W3100Aのアクセス回路にアクセスするというのが基本であり全てです。そのアクセス回路のレジスタについて。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
IDM_OR (0Ch) |
IND_EN | L/B | AUTO_INC |
IDM_ORレジスタはインダイレクトモードの動作を制御するレジスタです。
IND_ENに1を書き込むことで、インダイレクトモードに入ります。通常はここを切り替えて使用することはないと思います。
L/Bはアドレスレジスタに入れるアドレスの並びをどうするかの設定です。つまりリトルエンディアンかビッグエンディアンか、ということです。0でビッグエンディアン、1でリトルエンディアンとなります。
AUTO_INCはアドレスの自動加算を有効にするか否かの設定です。1にすると自動的に加算します。読み書き両方ともに有効です。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
IDM_AR0 (0Dh) |
||||||||
IDM_AR1 (0Eh) |
IDM_AR0とIDM_AR1レジスタはこれからアクセスするレジスタのアドレスを格納するレジスタです。IDM_ORレジスタのL/Bビットの設定により、アドレスの上位と下位がどちらに入るかが変わります。例えば4000hを指定する場合、
L/B=0では
IDM_AR0 ← 40h
IDM_AR1 ← 00h
L/B=1では
IDM_AR0 ← 00h
IDM_AR1 ← 40h
となります。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
IDM_DR (0Fh) |
IDM_DRレジスタは、読み書きしたいデータの窓口です。データを書き込めばIDM_ARxレジスタにセットされたアドレスにそのデータが書き込まれ、読み出せばそのアドレスからデータが読み出されます。その後IDM_ORレジスタのAUTO_INCビットの設定により、アドレスレジスタの内容が+1されるか否かに分かれます。
W5100 |
W3150Aからアクセスに関わる重要なレジスタ、つまりはモードレジスタが先頭に配置されるなど整理されています。インダイレクトモードもここで指定します。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
MR (00h) |
RST | Ping Block Mode |
PPPoE Mode | AI | IND |
RSTレジスタはソフトリセットのためのレジスタで、1を書き込めばリセットします。リセット後は自動的に0に戻ります。全部初期化してしまいますので、MRレジスタにある他のビットも一緒に消えます。ですので、初期化とこの後のモード設定は別々に行ってください。
Ping Block ModeはPingに自動的に応答するかを設定するモードで、1を書き込んでおくとPing応答しなくなります。
PPPoE ModeはADSLに直結するためのモードレジスタで、1を書き込むとADSLサーバに接続するようになります。情報家電用にルータレスで使えるようにするよう設けられた、W3150A以降の目玉機能でもあります。今回の制作には必要ありませんが…。
AIレジスタはインダイレクトモード時にアクセスごとのオートインクリメントをするかどうかの設定で、1を書き込めばオートインクリメントするようになります。
INDレジスタが、インダイレクトモードとするかどうかの設定で、1でインダイレクトモードになります。以上のレジスタの初期値はすべて0です。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
IDM_AR0 (01h) |
||||||||
IDM_AR1 (02h) |
インダイレクトモード用のアドレスを指定するレジスタです。エンディアンのモード設定がなくなりましたので、ここではビッグエンディアンのみ指定できます。上記の説明を借りれば、4000hを指定する場合
IDM_AR0 ← 40h
IDM_AR1 ← 00h
と書き込むことになります。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
IDM_DR (03h) |
データ読み書き用レジスタで、アクセス時点でのIDM_AR0/1レジスタで指定されるアドレスのレジスタに対して読み書きできます。MRレジスタにてAI=1の場合はIDM_AR0/1レジスタがオートインクリメントされるので、次いでアクセスすれば隣のレジスタを読み書きできます。
初期化
W3100A |
いろいろ複雑な処理をするチップのわりには、初期化は簡単です。自MACアドレス(SHAR,88〜8Dh)、自IPアドレス(SIPR,8E〜91h)、サブネットマスク(SMR,84〜87h)、必要ならゲートウェイアドレス(GAR,80〜83h)を各レジスタにセットし、初期化コマンドを送るだけです。コマンドを送るといっても、所定のレジスタの所定のビットに1をセットするということです。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_CR (00h) |
S/W Reset |
Recv | Send | Close | Listen | Connect | Sock_Init | Sys_Init |
C0_CRレジスタはチャンネル0の制御レジスタですが、全体の初期化のためのコマンドビットはこのレジスタのSys_Initビットに配置されています。ここに1を書き込めば、初期化が実行されます。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_ISR (04h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK | Init_OK |
初期化がうまくいったかどうかは、C0_ISRレジスタに結果として表示されます。Init_OKビットが1になれば、初期化終了です。この時点で、外部からのPing(ICMPエコー要求)に応答できるようになっています。TCPやUDPモードで使っている限りは、Pingが来たかどうかなどはシステムに通知されませんので、気にする必要もありません(RAWモードだとICMPも処理しなくなるはずなので、パケット到着を通知されるはず)。
本当はもうちょっと初期化として設定するものがあります。しなくてもいいものもありますが、おそらくしないといけなさそうなのが送受信バッファの大きさを設定するRMSRとTMSRです。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
RMSR (95h) |
CH3 | CH2 | CH1 | CH0 | ||||
S1 | S0 | S1 | S0 | S1 | S0 | S1 | S0 |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
TMSR (96h) |
CH3 | CH2 | CH1 | CH0 | ||||
S1 | S0 | S1 | S0 | S1 | S0 | S1 | S0 |
S1 | S0 | メモリサイズ |
0 | 0 | 1KB |
0 | 1 | 2KB |
1 | 0 | 4KB |
1 | 1 | 8KB |
RMSRは受信バッファの、TMSRは送信バッファのサイズを設定します。それぞれ8KBありますが、チャンネルは4つありますので、配分してやる必要があります。合計がオーバーした時にどうなるのかマニュアルからは読み取りにくいですが、CH0から順に確保されるものと思われます。例えば、ビット1と0にそれぞれ1を入れれば、バッファ全体をCH0が使用するようになり、他のチャンネルには割り当てられません。また当然ですが、RMSRで設定した領域の大きさはTCPのウィンドウサイズの最大値として送信されます。
私の作った上記のボードでは電源投入後最初の動作が変で、そのままプログラムを再始動させると正しい動作になりました。そこでプログラムに初期化を2回やるように変更すると一発目からちゃんと動作するようになりました。W3100Aの問題というよりは、ボードがデータシートにあるリセットシーケンスを守れていない(W3100AとPHYチップとはリセットに時間差をつける)せいではないかと思っています。
W5100 |
さらに初期化が簡単になりました。自MACアドレス(SHAR,0009〜000Eh)、自IPアドレス(SIPR,000F〜0012h)、サブネットマスク(SUBR,0005〜0008h)、必要ならゲートウェイアドレス(GWR,0001〜0004h)を各レジスタにセットするだけで、もうPing応答できます。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
IMR (0016h) |
IM_IR7 (CONFLICT) |
IM_IR6 (UNREACH) |
IM_IR5 (PPPoE) |
IM_IR3 (S3_INT) |
IM_IR2 (S2_INT) |
IM_IR1 (S1_INT) |
IM_IR0 (S0_INT) |
割り込みを使用する場合、ここでマスクレジスタ(IMR)を設定しておきます。割り込みレジスタ(IR,0015h)とビットが対応しており、MSBからIPアドレス衝突、宛先不明パケット受信、PPPoEクローズ、各ソケット固有の割り込みについてマスクをかけられます。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
RMSR (001Ah) |
Socket3 | Socket2 | Socket1 | Socket0 | ||||
S1 | S0 | S1 | S0 | S1 | S0 | S1 | S0 |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
TMSR (001Bh) |
Socket3 | Socket2 | Socket1 | Socket0 | ||||
S1 | S0 | S1 | S0 | S1 | S0 | S1 | S0 |
S1 | S0 | メモリサイズ |
0 | 0 | 1KB |
0 | 1 | 2KB |
1 | 0 | 4KB |
1 | 1 | 8KB |
次に受信バッファサイズ(RMSR)、送信バッファサイズ(TMSR)を設定します。ここはW3100Aと変わっていませんが、呼び方がチャンネルではなくソケットになりました。
他にもリトライタイムアウト設定(RTR,0017〜0018h)やリトライ回数(RCR,0019h)、PPPoE関連設定レジスタなどがあって、この初期化の段階で設定しておきます。
ソケット生成
W3100A |
ソケットと言っても、自動生成してくれるわけはないので、自分で使いたいチャンネルを選んで、プロトコルと自分のポートを設定したものがソケットに相当します。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
SOPR (A1h/B9h/D1h/E9h) |
Broadcast/ERR |
NDTimeout/B | NDAck | SWS/P | Protocol |
設定値 | Protocol |
000 | 切断 |
001 | SOCK_STREAM(TCP) |
010 | SOCK_DGRAM(UDP) |
011 | SOCK_IPL_RAW(IP Layer RAW Mode) |
100 | SOCK_MACL_RAW(MAC Layer RAW Mode) |
SOPRは設定するチャンネルが、どの動作モードで動くのかを設定します。動作というのはプロトコルのことですね。設定できるのはTCPとUDP、そしてIP層での任意データとMAC層での任意データ。Pingに応答できてもこちらから送出することはできないので、やりたい場合はSOCK_IPL_RAWモードでICMPパケットを作ることになります。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_CR (00h) |
S/W Reset |
Recv | Send | Close | Listen | Connect | Sock_Init | Sys_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_CR (01h) |
Memory Test |
Recv | Send | Close | Listen | Connect | Sock_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C2_CR (02h) C3_CR (03h) |
Recv | Send | Close | Listen | Connect | Sock_Init |
自分のポート(C0_SPR,AE〜AFh/C1_SPR,C6〜C7h/C2_SPR,DE〜DFh/C3_SPR,F6〜F7h)を設定して、コマンドレジスタのSock_Initに1を書き込めばソケットの初期化が実行できます。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_ISR (04h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK | Init_OK |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_ISR (05h) C2_ISR (06h) C3_ISR (07h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK |
そしてステータスレジスタのSInit_OKビットが1になれば初期化終了。これで設定したチャンネルはそのプロトコルで動作するようになります。
ソケット初期化のついでに、送信バッファのポインタ値も初期化します。書き込みポインタのCx_TW_PR、UDP用送信済ポインタのCx_TR_PR、TCP用送信済ポインタのCx_TA_PRについて、全部同じ値を書き込んでおきます。値はできれば乱数で決めるようにします。バッファが最大でも8KBしかないのにポインタレジスタが32bitなのはあとで説明するので今は気にしないように。
W5100 |
自分の使用したいソケットに対して必要な設定をします。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
S0_MR (0400h) |
MULTI |
ND/MC | Protocol | |||||
S1_MR (0500h) |
||||||||
S2_MR (0600h) |
||||||||
S3_MR (0700h) |
設定値 | Protocol |
0000 | 切断 |
0001 | TCP |
0010 | UDP |
0011 | IPRAW |
0100 | MACRAW |
0101 | PPPoE |
PPPoEが増えているので、プロトコルの指定が4bitになっています。他はW3100Aの時と同様。今回の目的にはTCPとUDPしか使いませんね。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
S0_CR (0401h) |
||||||||
S1_CR (0501h) |
||||||||
S2_CR (0601h) |
||||||||
S3_CR (0701h) |
値 | コマンド | 説明 |
01h | OPEN | ソケットの初期化 |
02h | LISTEN | 接続待ち(TCPのみ) |
04h | CONNECT | 接続(TCPのみ) |
08h | DISCON | 切断(TCPのみ) |
10h | CLOSE | ソケットの終了 |
20h | SEND | データ送信 |
21h | SEND_MAC | ARPを経ずに、直接指定したMACアドレスへ送信(UDPのみ) |
22h | SEND_KEEP | 送信ごとに接続が継続されているかを確認し、切れていれば割り込みを発生させる(TCPのみ) |
40h | RECV | 受信データを引き取り、次の受信のためにポインタを更新する |
自分のポート(S0_PORT,0404〜0405h/S1_PORT,0504〜0505h/S2_PORT,0604〜0605h/S3_PORT,0704〜0705h)を設定して、コマンドレジスタにOPENコマンドを書き込めばソケットの初期化が実行できます。コマンドの種類が増えたためか、制御ビットの操作ではなくコマンドコードの書込みに変わりました。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
S0_SR (0403h) |
||||||||
S1_SR (0503h) |
||||||||
S2_SR (0603h) |
||||||||
S3_SR (0703h) |
値 | ステータス | 説明 |
00h | SOCK_CLOSED | CLOSEコマンドが実行されるなどしてソケットが閉じられている |
11h | SOCK_ARP | TCPにおけるSYN送出のためのARP |
13h | SOCK_INIT | TCPにおけるソケット初期化完了 |
14h | SOCK_LISEN | LISTENコマンドが受け付けられ、接続待ちになっている |
15h | SOCK_SYNSENT | 接続のためSYNを送出する |
16h | SOCK_SYNRECV | 接続待ちにてSYNを待っている状態 |
17h | SOCK_ESTABLISHED | 接続完了 |
18h | SOCK_FIN_WAIT | ソケットが閉じられる過程にある |
1Ah | SOCK_CLOSING | ソケットが閉じられる過程にある |
1Bh | SOCK_TIME_WAIT | ソケットが閉じられる過程にある |
1Ch | SOCK_CLOSE_WAIT | 接続相手から切断要求を受信(まだ切れてはいない) |
1Dh | SOCK_LAST_ACK | ソケットが閉じられる過程にある |
21h | SOCK_ARP | UDPのためのARP |
22h | SOCK_UDP | UDPにおけるソケット初期化完了 |
31h | SOCK_ARP | ICMPのためのARP |
32h | SOCK_IPRAW | IPRAWにおけるソケット初期化完了 |
42h | SOCK_MACRAW | MACRAWにおけるソケット初期化完了 |
5Fh | SOCK_PPPOE | PPPoEにおけるソケット初期化完了 |
ソケットの初期化が完了したかどうかは、ステータスレジスタの値で判断します。コマンドに対する応答が単純ではなくなったので、例えばTCPで初期化したのとUDPとでは別の値になるなど注意が必要です。
なお、W5100ではバッファのポインタレジスタの初期化は必要なくなりました。
接続
W3100A |
TCPではデータ送受信に先立ち接続を確立しておきます。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_CR (00h) |
S/W Reset |
Recv | Send | Close | Listen | Connect | Sock_Init | Sys_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_CR (01h) |
Memory Test |
Recv | Send | Close | Listen | Connect | Sock_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C2_CR (02h) C3_CR (03h) |
Recv | Send | Close | Listen | Connect | Sock_Init |
接続先IPアドレス(C0_DIR,A8〜ABh/C1_DIR,C0〜C3h/C2_DIR,D8〜DBh/C3_DIR,F0〜F3h)、接続先ポート(C0_DPR,AC〜ADh/C1_DPR,C4〜C5h/C2_DPR,DC〜DDh/C3_DPR,F4〜F5h)を設定して、コマンドレジスタのConnectに1を書き込めばTCPの接続が実行されます(UDPではいきなりデータを送りつけますので、そちらをご覧ください)。
値 | ステータス | 内容 |
0x00 | SOCK_COLSED | ソケットが閉じられた(開いていない) |
0x01 | SOCK_ARP | ARP送出後の応答待ち状態 |
0x02 | SOCK_LISTEN | 受動モードでクライアントからの接続待ち |
0x03 | SOCK_SYNSENT | 接続時にSYNパケットを送って相手からのSYN・ACKパケット待ち |
0x04 | SOCK_SYNSENT_ACK | 接続時にSYN・ACKパケットを受信しそれに応答するACKパケットを送り終えた状態 |
0x05 | SOCK_SYNRECV | 受動モードでクライアントからのSYNパケットを受信しSYN・ACKパケットを送ろうとしているところ |
0x06 | SOCK_ESTABLISHED | TCPの接続が完了した状態(能動・受動両モード共) |
0x07 | SOCK_CLOSE_WAIT | 切断過程の状態 |
0x08 | SOCK_LAST_ACK | 切断過程の状態 |
0x09 | SOCK_FIN_WAIT1 | 切断過程の状態 |
0x0A | SOCK_FIN_WAIT2 | 切断過程の状態 |
0x0B | SOCK_CLOSING | 切断過程の状態 |
0x0C | SOCK_TIME_WAIT | 切断過程の状態 |
0x0D | SOCK_RESET | リセットパケットを相手から受信し接続を切断しようとしているところ |
0x0E | SOCK_INIT | ソケットの初期化 |
0x0F | SOCK_UDP | UDPモードでのソケットの初期化 |
0x10 | SOCK_RAW | IP RAWモードでのソケット初期化 |
0x11 | SOCK_UDP_ARP | UDPモードでのARP送出後の応答待ち状態 |
0x12 | SOCK_UDP_DATA | UDPモードかRAWモードでのデータ転送中状態 |
0x13 | SOCK_RAW_INIT | MAC RAWモードでのソケット初期化 |
ソケットの接続状況はソケットステータスレジスタ(C0_SSR,A0h/C1_SSR,B8h/C2_SSR,D0h/C3_SSR,E8h)に値として表示されます。いろいろありますが、W3100A内部のプロトコルエンジンの遷移状態がそのまま出力されていると考えていいでしょう。もちろんCx_ISRレジスタでステータスを見てもいいですし、割り込みが使えるならそれが素直かもしれません。Cx_ISRレジスタは読み出すと内容が消えますので、CxSSRの方が都合がいい場合もあると思います。
サーバとしての動作の場合は、接続待ちの設定を行ないます。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_CR (00h) |
S/W Reset |
Recv | Send | Close | Listen | Connect | Sock_Init | Sys_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_CR (01h) |
Memory Test |
Recv | Send | Close | Listen | Connect | Sock_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C2_CR (02h) C3_CR (03h) |
Recv | Send | Close | Listen | Connect | Sock_Init |
コマンドレジスタのListenビットに1を書き込めば、接続待ちとなります。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_ISR (04h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK | Init_OK |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_ISR (05h) C2_ISR (06h) C3_ISR (07h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK |
Cx_ISRのEstablishedビットを観測または割り込みで検知するか、Cx_SSRのステータス表示にて接続を確認します。
(このモードでの使用をまだ試してないので説明が淡白なのはカンベンしてください…)
W5100 |
まず、TCPのCONNECTの場合です。
接続先IPアドレス(S0_DIPR,040C〜040Fh/S1_DIPR,050C〜050Fh/S2_DIPR,060C〜060Fh/S3_DIPR,070C〜070Fh)、接続先ポート(S0_DPORT,0410〜0411h/S1_DPORT,0510〜0511h/S2_DPORT,0610〜0611h/S3_DPORT,0710〜0711h)を設定して、コマンドレジスタにCONNECTコマンドを書き込めばTCPの接続が実行されます。
TCPのLISTENの場合は、コマンドレジスタにLISTENコマンドを書き込めば待ち受け状態になります。
ステータスレジスタでSOCK_ESTABLISHEDが読み出されれば接続完了です。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
S0_IR (0402h) |
SEND_OK | TIMEOUT | RECV | DISCON | CON | |||
S1_IR (0502h) |
||||||||
S2_IR (0602h) |
||||||||
S3_IR (0702h) |
接続/切断/送受信などは割り込みの要因にもなりますので、その状態は割り込みレジスタでも確認可能です。CONビットが1になると接続完了です。該当するビットに1を書き込むことで消去できます。
データ送信(TCPの場合)
W3100A |
接続が確立できたら、あとはバッファ書き込み→送信ビットに1書き込み、でデータ送信ができます。その手順です。
シャドウレジスタ | アドレス | 対応するポインタレジスタ |
C0_SRW_PR | 0x1E0 | C0_RW_PR |
C0_SRR_PR | 0x1E1 | C0_RR_PR |
C0_STA_PR | 0x1E2 | C0_TA_PR |
C1_SRW_PR | 0x1E3 | C1_RW_PR |
C1_SRR_PR | 0x1E4 | C1_RR_PR |
C1_STA_PR | 0x1E5 | C1_TA_PR |
C2_SRW_PR | 0x1E6 | C2_RW_PR |
C2_SRR_PR | 0x1E7 | C2_RR_PR |
C2_STA_PR | 0x1E8 | C2_TA_PR |
C3_SRW_PR | 0x1E9 | C3_RW_PR |
C3_SRR_PR | 0x1EA | C3_RR_PR |
C3_STA_PR | 0x1EB | C3_TA_PR |
C0_STW_PR | 0x1F0 | C0_TW_PR |
C0_STR_PR | 0x1F1 | C0_TR_PR |
C1_STW_PR | 0x1F3 | C1_TW_PR |
C1_STR_PR | 0x1F4 | C1_TR_PR |
C2_STW_PR | 0x1F6 | C2_TW_PR |
C2_STR_PR | 0x1F7 | C2_TR_PR |
C3_STW_PR | 0x1F9 | C3_TW_PR |
C3_STR_PR | 0x1FA | C3_TR_PR |
バッファのポインタはプログラムで管理せず、できるだけその都度W3100Aから読み出して使用します。読み出しもいきなりそのレジスタを読むのではなく、一度シャドウレジスタと呼ばれるものをアクセスしてから読み出します。プロトコルエンジンは制御プログラムとは独立して動いていますので、レジスタ読み出し中に繰り上がりなどで狂ってしまうのを防ぐ目的があるようです。
TCPデータ送信の場合は、バッファへの書き込みポインタがCx_TW_PR、バッファから取り出されて送信された位置を示すポインタがCx_TA_PRとなっていますので、それぞれCx_STW_PRやCx_STA_PRをダミーで読み出してから、本当のレジスタを読み出します。
ポインタレジスタはそれぞれ4バイトのレジスタとなっています。バッファは最大でも8KB、13ビットまでしか必要ないはずですが、必ず4バイト単位で取り扱ってください。特にCx_TA_PRはTCPのシーケンス番号として使われています。
レジスタの最新値を取得したら、差分をとってバッファに空きがあることを確認します。なければ待つしかありませんが、ケーブルが外れているなどの問題でバッファが埋まっているかもしれませんので、タイムアウトができるようになっているのが好ましいと思います。
空きがあれば送信データをバッファに書き込みます。バッファそのものは単純なメモリでリングバッファとして扱われていますが、FIFO構造ではありませんのでどのアドレスに書き込むかは自分で計算する必要があります。メモリマップは次のようになっています。
|
|
チャンネル0の場合なら4000h番地からのオフセットとして、C0_TW_PRの下位13〜10ビット(ビット数は初期化時に設定したバッファの大きさから決まりますよね)の値を足したものがこれからデータを書き込めるアドレスとなります。空き容量を超えないように注意して、必要数を書き込んだらCx_TW_PRに新しい値を書き込みます。書き込みの際はシャドウレジスタのアクセスは不要です。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_CR (00h) |
S/W Reset |
Recv | Send | Close | Listen | Connect | Sock_Init | Sys_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_CR (01h) |
Memory Test |
Recv | Send | Close | Listen | Connect | Sock_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C2_CR (02h) C3_CR (03h) |
Recv | Send | Close | Listen | Connect | Sock_Init |
ポインタを更新したらコマンドレジスタのSendビットに1を書き込めば送信されます。もちろん自動的にウィンドウ制御されながら送られますので、バッファに書き込むデータ量は相手側ウィンドウ値とは無関係に考えることができます。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_ISR (04h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK | Init_OK |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_ISR (05h) C2_ISR (06h) C3_ISR (07h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK |
送信が終われば、ステータスレジスタのSend_OKビットに1が立ち割り込みが入ります。バッファより大きなデータを効率よく送りたい場合には利用するとよいでしょう。
W5100 |
手順そのものは、W3100A同様バッファ書込み→送信コマンドで送信できます。ただし、ポインタレジスタの初期化が不要になったことから予想できるように、ポインタの扱いが変わっています。
S0_TX_FSR (0420〜0421h) S1_TX_FSR (0520〜0521h) S2_TX_FSR (0620〜0621h) S3_TX_FSR (0720〜0721h) |
送信バッファの残り空き容量 |
S0_TX_RD (0422〜0423h) S1_TX_RD (0522〜0523h) S2_TX_RD (0622〜0623h) S3_TX_RD (0722〜0723h) |
送信バッファの読み出しポインタ |
S0_TX_WR (0424〜0425h) S1_TX_WR (0524〜0525h) S2_TX_WR (0624〜0625h) S3_TX_WR (0724〜0725h) |
送信バッファの書込みポインタ |
ここで送信に必要なレジスタ(ポインタ)はSn_TX_FSRとSn_TX_WRです。
Sn_TX_FSRはそれぞれのソケットに割り当てられたバッファの残り容量を示しており、W3100A時代にはポインタから計算で求めていたものがダイレクトで得られるようになりました。これがゼロだと送信データを書込みできないので、空きができるまで待つことになります。
Sn_TX_WRは各ソケット単位での書込みポインタです。データ書込みには物理アドレスを計算する必要があるのはW3100Aと同じです。Sn_TX_RDも含め、正しい値を得るためには若いアドレスから順番にアクセスする必要があります。シャドウレジスタはなくなりましたが、値の保証のためアクセス順が決められているのです。
|
|
メモリマップも以前からほぼ変わりません。4000〜5FFFhが送信に、6000〜7FFFhが受信に使われ、これを先に説明したTMSR/RMSRレジスタ設定にて分割します。
ソケット0の送信バッファは4000hから始まりますが、2KB設定だとすると4000〜47FFhがそのエリアとなります。しかしSn_TX_WRはアドレスとは関係なく0000〜FFFFhの範囲で変化するとされています(論理的なオフセットなのでしょう)。なのでレジスタの値に対してあらかじめ変化の範囲でマスク(2KBなら07FFhと論理積)しておく必要があります。というわけで真にアクセスすべきアドレスは
先頭アドレス+(Sn_TX_WR and マスク値)
となります。これは他のポインタレジスタについても同じです。
データを書き込んだら、元のSn_TX_WRレジスタの値に対して書き込んだバイト数を加えた数をSn_TX_WRレジスタに書き込みます。この後コマンドレジスタにSENDコマンドを書き込むとデータ送信されます。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
S0_IR (0402h) |
SEND_OK | TIMEOUT | RECV | DISCON | CON | |||
S1_IR (0502h) |
||||||||
S2_IR (0602h) |
||||||||
S3_IR (0702h) |
送信結果は割り込みレジスタのSEND_OKビットが1になっていることで確認できます。
データ送信(UDPの場合)
W3100A |
UDPはコネクションレスですから、いきなり送信することになります。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_CR (00h) |
S/W Reset |
Recv | Send | Close | Listen | Connect | Sock_Init | Sys_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_CR (01h) |
Memory Test |
Recv | Send | Close | Listen | Connect | Sock_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C2_CR (02h) C3_CR (03h) |
Recv | Send | Close | Listen | Connect | Sock_Init |
接続先IPアドレス(C0_DIR,A8〜ABh/C1_DIR,C0〜C3h/C2_DIR,D8〜DBh/C3_DIR,F0〜F3h)、接続先ポート(C0_DPR,AC〜ADh/C1_DPR,C4〜C5h/C2_DPR,DC〜DDh/C3_DPR,F4〜F5h)を設定して、バッファにデータを書き込み、コマンドレジスタのSendビットに1を書き込めば送信されます。
バッファを管理するポインタは、書き込みポインタがCx_TW_PR、バッファから取り出されて送信された位置を示すポインタがCx_TR_PRとなっていてTCPの場合と少し異なっています。ポインタはシーケンス番号など他の用途に使われることはありませんが、やはり4バイトで扱うようにしてください。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_ISR (04h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK | Init_OK |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_ISR (05h) C2_ISR (06h) C3_ISR (07h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK |
送信が終われば、ステータスレジスタのSend_OKビットに1が立ち割り込みが入ります。バッファより大きなデータを効率よく送りたい場合には利用するとよいでしょう。
W5100 |
送信データの設定方法は、TCPと同じになりました。TCPと違うのはコネクションレスなので送信のたびに送信先設定が必要なこと。接続先IPアドレス(S0_DIPR,040C〜040Fh/S1_DIPR,050C〜050Fh/S2_DIPR,060C〜060Fh/S3_DIPR,070C〜070Fh)、接続先ポート(S0_DPORT,0410〜0411h/S1_DPORT,0510〜0511h/S2_DPORT,0610〜0611h/S3_DPORT,0710〜0711h)を設定して、コマンドレジスタにSENDコマンドを書き込めば送信されます。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
S0_IR (0402h) |
SEND_OK | TIMEOUT | RECV | DISCON | CON | |||
S1_IR (0502h) |
||||||||
S2_IR (0602h) |
||||||||
S3_IR (0702h) |
送信結果は割り込みレジスタのSEND_OKビットが1になっていることで確認できます。
データ受信(TCPの場合)
W3100A |
バッファに空きがあるのを確認して(制御されるので必須ではないですが)、受信コマンドを送ればソケットを生成すればコネクションが確立すればチップとしての受信態勢が整います。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_CR (00h) |
S/W Reset |
Recv | Send | Close | Listen | Connect | Sock_Init | Sys_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_CR (01h) |
Memory Test |
Recv | Send | Close | Listen | Connect | Sock_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C2_CR (02h) C3_CR (03h) |
Recv | Send | Close | Listen | Connect | Sock_Init |
コマンドレジスタのRecvビットに1を書き込めば受信待ちとなります。送信時にSendビットと同時にセットしてもかまいません。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_ISR (04h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK | Init_OK |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_ISR (05h) C2_ISR (06h) C3_ISR (07h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK |
データを受信すると、受信割り込みが発生し受信ポインタがその数だけ進みます。受信済ポインタがCx_RW_PR、バッファの読み出しポインタがCx_RR_PRとなります。どれだけ受信したかは一度シャドウレジスタにアクセスしてから各ポインタを読み出して、差分を見ればわかります。あらかじめコマンドレジスタのRecvビットに1を書き込んでおけばCx_ISRのRecv_OKビットに1が立ち割り込みが発生します。
受信データを取り込んだら、Cx_RR_PRをその分更新しコマンドレジスタのRecvビットに1を書き込みます。レジスタを更新するだけではバッファに空きを作ったことにはならず、受信コマンドを送ることで初めて空きが発生します。
受信バッファの空き容量は、そのままウィンドウ幅として接続相手に通知されます。バッファに空きが長い間作られないと、制御プログラム側が止められたり暴走したりしていると考えてか、W3100A側から接続を切断するようにもなっています。受信したらできるだけ早くデータを取り出したほうがよいでしょう。
最後のデータを受信すると相手から切断されることがありますが(HTTPなど)、それから一定時間が経過すると、Cx_RW_PRがゼロになってしまうという現象があります。切断されてすぐというわけではないので普通のシステムでは問題ないとは思いますが、BASICで実験した私の場合には受信バッファの残りがわからなくなってしまう不都合がありました。この場合受信すべき内容は全てW3100Aに取り込まれているので情報欠落の心配はないのですが、ポインタから残りバイト数を求めることができなくなるため、あらかじめ全受信バイト数を調べておくなどの工夫が必要です。
W5100 |
コネクションが確立すれば、受信態勢も整います。特にあらかじめコマンドレジスタの操作は必要ないようです。
S0_RX_RSR (0426〜0427h) S1_RX_RSR (0526〜0527h) S2_RX_RSR (0626〜0627h) S3_RX_RSR (0726〜0727h) |
受信済データ量 |
S0_RX_RD (0428〜0429h) S1_RX_RD (0528〜0529h) S2_RX_RD (0628〜0629h) S3_RX_RD (0728〜0729h) |
受信バッファの読み出しポインタ |
Sn_RX_RSRはその時点で受信されているデータの量で、ゼロならすでにデータを取り込んだか何もデータが来ていないことを示します。Sn_RX_RDも含めて、送信ポインタレジスタ同様、若いアドレスから順に読み出すことで値を確定させます。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
S0_IR (0402h) |
SEND_OK | TIMEOUT | RECV | DISCON | CON | |||
S1_IR (0502h) |
||||||||
S2_IR (0602h) |
||||||||
S3_IR (0702h) |
受信されたかどうかは割り込みレジスタでも確認できます。RECVビットが1になっているのは受信データがバッファにある時なので、例えば300バイト受信して200バイト引き取りまだ100バイト残っているなどの場合は、1を書き込んでクリアしても1が残っています。
受信バッファ上の受信データは、Sn_RX_RDをオフセットとするアドレスに書き込まれています。このアドレスの計算方法も送信の場合と同じで、必要分だけマスクしてバッファ先頭アドレスに加えることで真のアドレスを求めることができます。
データを引き取ったら、元のSn_RX_RDにその分のバイト数を加えた値をSn_RX_RDに書込み、コマンドレジスタにRECVコマンドを書き込んで、新たなデータ受信に備えます。RECVコマンドはSn_RX_RDレジスタを更新するためのコマンドと考えて良さそうです。
なお、データ処理の都合上1バイトずつデータを引き取るようなことをしても、シリーウィンドウシンドロームを避け適切にシーケンス番号を送信するようです。
データ受信(UDPの場合)
W3100A |
バッファに空きがあるのを確認して、受信コマンドを送ればチップとしての受信態勢が整います。 ソケットが生成されれば、即受信態勢となります。ただそのままでは受信したことをステータスレジスタで検知することはできません。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_CR (00h) |
S/W Reset |
Recv | Send | Close | Listen | Connect | Sock_Init | Sys_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_CR (01h) |
Memory Test |
Recv | Send | Close | Listen | Connect | Sock_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C2_CR (02h) C3_CR (03h) |
Recv | Send | Close | Listen | Connect | Sock_Init |
コマンドレジスタのRecvビットに1を書き込めば受信待ちとなります。 コマンドレジスタのRecvビットに1を書き込むことで、ステータスレジスタに受信状態を反映させることができるようになります。送信時にSendビットと同時にセットしてもかまいません。当然フロー制御はありませんから、受信バッファに十分な空きを確保してから受信コマンドを送るべきです。使用するアプリケーションの内容によって十分なバッファ量に設定しておくべきです(マニュアルからはバッファの空きを超える量のデータが到着した時の挙動は読み取れていません。コネクションレスということで、捨てられてもしかたないという考え方なのでしょうか)。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_ISR (04h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK | Init_OK |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_ISR (05h) C2_ISR (06h) C3_ISR (07h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK |
データを受信すると、受信割り込みが発生し受信ポインタがその数だけ進みます。TCPの場合と同様に、受信済ポインタがCx_RW_PR、バッファの読み出しポインタがCx_RR_PRとなります。
ステータスレジスタはコマンドレジスタのRecvビットに1を書き込んである時だけRecv_OKに反映されます。受信済みの場合はRecvビットに1を書き込むと即座にRecv_OKに1が立ちます。
接続のステップがないので受信してもどこから送られてきたかをあらかじめ知っておくことは不可能です。そこで、受信データに送信元の情報が入っているのがUDP受信の特徴です。
全データ長 | 送信元IPアドレス | 送信元ポート | 受信UDPデータ |
2バイト | 4バイト | 2バイト |
W5100 |
コネクションレスなのでソケットが生成されれば即受信可能となります。あらかじめ受信コマンドを書き込む必要もありません。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
S0_IR (0402h) |
SEND_OK | TIMEOUT | RECV | DISCON | CON | |||
S1_IR (0502h) |
||||||||
S2_IR (0602h) |
||||||||
S3_IR (0702h) |
データの受信に伴う動作はTCPと同じで、Sn_RX_RSRレジスタがゼロでなくなるか割り込みレジスタのRECVビットが1になることでそれが確認できます。ポインタ操作やデータ引き取り後のRECVコマンドまで、TCPと同じ操作で処理します。
送信元IPアドレス | 送信元ポート | データ長 | 受信UDPデータ |
4バイト | 2バイト | 2バイト |
W3100A同様受信データの送信元情報は受信データに含まれる形で得られるのですが、その並びが変わっています。データ長はヘッダ分を含まなくなりました。
切断
W3100A |
TCPでの切断は明示的に行なわれる必要がありますが、相手から切られる場合と自分から切る場合とがあります。UDPでの切断は自身に持つソケットの終了という意味以上のものはありません。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_CR (00h) |
S/W Reset |
Recv | Send | Close | Listen | Connect | Sock_Init | Sys_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_CR (01h) |
Memory Test |
Recv | Send | Close | Listen | Connect | Sock_Init |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C2_CR (02h) C3_CR (03h) |
Recv | Send | Close | Listen | Connect | Sock_Init |
TCPモードで接続状態の時、コマンドレジスタのCloseビットに1を書き込むと、相手にFINパケットを送信して接続の終了シーケンスに入ります。接続同様、ここも自動的に処理されます。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C0_ISR (04h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK | Init_OK |
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
C1_ISR (05h) C2_ISR (06h) C3_ISR (07h) |
Recv_OK | Send_OK | Timeout | Closed | Established | SInit_OK |
切断の状況はCx_ISRやCx_SSRで確認できます。切断されたら、再度の通信にはまたソケット生成からやり直す必要があります。
W5100 |
W3100A同様、コマンドレジスタにCLOSEコマンドを書き込むとTCPの場合は切断シーケンスに移行し、UDPの場合はソケットの終了処理を行います。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
S0_IR (0402h) |
SEND_OK | TIMEOUT | RECV | DISCON | CON | |||
S1_IR (0502h) |
||||||||
S2_IR (0602h) |
||||||||
S3_IR (0702h) |
CLOSEコマンドの結果はステータスレジスタの他、割り込みレジスタのDISCONビットでも確認できます。
TCPの場合は接続先からの切断要求という場合もありますが、その場合でも同様に確認できます。
製作上の注意点
W3100A |
バグなのか何かトラブルがあるのかわからないのですが、その割には安定して出てくる症状なので、ソフト製作上の注意点としていくつか挙げておきます。
プロトコルを設定するSOPRレジスタを読み出すと、CxISRのフラグがクリアされるようです。SOPRは設定レジスタなのだから読み出す必要はないように思いますが、読み出せば設定し現在動作しているプロトコルがわかりますので、プロトコルに応じたソフトがそのまま動いていいかどうか判別するのに便利と言えます。が、クリアされるのでは例えば割り込みなどが発生しなくなるなどの不都合もありそうです。設定値をワークエリアに保管してそれを参照するようにした方が良さそうです。
受信バッファは、アドレス設定直後の1回だけ、不定な値が読み出されるようです。私は回路構成上インダイレクトモードしか試していませんので、もしかしたらダイレクトモードとかI2Cモードではそんな症状は出ないかもしれません。
私の場合は一時的にインクリメントモードをやめて、先頭データをダミーで読み出してから、インクリメントモードに戻すようにしています。この一連のダミー読み出しの後は元々の想定どおりにアクセスして問題ありません。
W5100 |
Webで検索するといくつかバグのようなものは報告されているようですが、上記W3100Aの時のような「データシートの通りに操作してもうまく動かない」という現象には遭遇していません。
UFMマップ
8bitマシンとしてのMZなどは当然OSベースのシステムではないため、共通のシステム設定管理というものはありません。でもアプリにいちいちMACアドレスを書くとか、アプリごとにIPアドレスが変わるとか、全てアプリ任せではえらいことになってしまいそうです。MZを複数台並べている私の環境では、同じプログラムのハズなのにMACアドレスとIPアドレスの違うものが台数分必要ということになります。そんなの管理できない…。
そこで、市販のLANボードでは普通のことですが、ボード上の小容量フラッシュメモリにMACアドレスなどを記録しておくことにしました。さらにはIPアドレスなど今時のPCならOSがレジストリに記録するような情報も入れておき、アプリから参照することで機体固有情報をプログラム内に持たなくて良いようにしました。
「シリアル-LANアダプタ(その2)」の時は本当にシリアルEEPROMを使おうと準備しましたが、今回はCPLD内にUFMと呼ばれる自由に使えるフラッシュメモリがありますので、そこにいろいろ記録することにします。
以下にこれまで決まっているデータの配置を示します。
オフセット | 内容 |
0 | データ有効(bit7)、DHCP(bit0) |
1 | ゲートウェイアドレス |
2 | |
3 | |
4 | |
5 | サブネットマスク |
6 | |
7 | |
8 | |
9 | MACアドレス |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | IPアドレス |
16 | |
17 | |
18 | |
19 | DNSアドレス |
20 | |
21 | |
22 |
オフセット0は動作設定で、DHCPによるアドレス設定(bit0=1)か固定アドレス(bit0=0)を表します。もちろんW5100などにDHCPでのアドレス設定機能はありませんので、これはアドレスを設定するプログラムがどのように振る舞えばいいのかを示すフラグに過ぎません。なのでフラグと異なる設定を行ってもボードとして異常動作をするわけではありません。
またbit7はUFMデータの有効状態を示します。イレースされると全てFFHとなることから、このビットが1のままだと意味のあるデータが書かれていないことを示すことにしました。
オフセット | フラグ状態 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0 | 0 | 有効 | 固定アドレス | ||||||
1 | 無効 | DHCP |
これらのデータに基づき、各アプリケーションはW5100に必要な設定を行うことになります。
また、DHCPでアドレスを獲得するにしても、複数のアプリが別々にDHCP要求するようでは資源の無駄というか、効率の良い話ではありません。そこで、アドレス66Hのメモレジスタをアプリ間で共有するフラグとして使用することで、各アプリは問題のない限りさっきまで動いていた別のアプリの設定を引き継いで良いかどうかを確認することができるようになります。リセットでゼロクリアされるので、1を立ててその項目が設定済みであることを示すことにします。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
メモレジスタ (66H) |
IP等 アドレス |
MAC アドレス |
ソケットライブラリ
W3100A時代は内輪向けデモに間に合わせるため取り急ぎ…ということでBASICでアプリを記述していましたが、いつまでもそれではさすがにネタにしかなりませんし、それもBASIC-M25だからなんとかなるのであって他の処理系では苦労の度合いは想像を絶しそう…というわけで、それなりにまともなソフトを作るためにもマシン語でなんとかできるようにしておきたいところ。
そこで、ソケットライブラリを作ることにしました。
アプリケーション
LANとしては100BASE-TXという性能がありますが、TCPで通信する分にはフロー制御があるのでなんとかなりそう、ということでBASICでアプリケーションを書いてみました。マシンはMZ-2500、添付のBASIC-M25にてWebテキストブラウザモドキのようなもの、とWebDAVクライアントを作ったわけです。エラーチェック不足・変数は適当・やたら遅いといいとこなしですが、まぁ技術的可能性の確認ということでカンベンしてください。デバッグは04WebServerというソフトで行なってますが、ブラウザモドキにはDNSクライアントも実装したので他のサーバでも試しています。
なおここのプログラムは一次試作品時代に作成したもので、W3100Aに依存した記述になっています。
Webテキストブラウザモドキ | もう少しまともなブラウザにしたいと思ったものの、時間不足でHTMLファイル中のテキスト部分を表示するだけのものになりました。ほとんどのタグをスキップして表示しています。表示するだけなのでリンクやCGIは実装していません。URLはDNS実装済なのでIPアドレスでなくドメイン名が使用できますが、逆にHTMLファイル名は省略できません(index.htmlとか)。 1行目にMACアドレスを記述する箇所がありますので、適切なものに書き換えてください。 |
WebDAVクライアント | 上記テキストブラウザモドキを改造する形で作りました。 'DIR フォルダ名' でサーバのディレクトリを表示、 'PUT MZ側ファイル名 サーバ側フォルダ名+ファイル名' でMZ側カレントディスクのファイルを転送、 'GET サーバ側フォルダ名+ファイル名 MZ側ファイル名' でサーバにあるファイルをMZに転送、 というコマンドに対応しています。 チェンジディレクトリというのはできませんが、フルパスで指定すればサブディレクトリのファイルもアクセスできます。 これらのテキストファイルは、MZ-2500にてアスキーセーブしたものをこのソフトで転送したものです。 1行目にMACアドレスを記述する箇所がありますので、適切なものに書き換えてください。 |