イーサネット機能をPIC32マイコンに追加する

令和 2年 9月

 4.W5500を詳しく検証する

 前回までの検証でW5500を使用したイーサネット接続について、組み込み環境での非常に大雑把な性能を予測しました。
今回はもう少し、詳細な検証を行います。

 4.1 基本的なデータを入手する
 最初にW5500のハードウェアとソフトウェアに関する資料を入手します。
 標準のドライバソフトウェアが動作するためのハードウェア要求仕様ですが、これは文書になったものが見当たりません。
メーカーのHPにリファレンスの回路図として示されているものを参考にすると、マイコン側との接続信号として
 ・ RESET信号
 ・ SPI IF信号(CSを含めて4本)
 ・ 割り込み要求信号
の合計6本です。

 イーサネットネット側の接続は決まりきった標準回路として、指定されたとおりに実装します。
 これらの回路を自作してもいいのですが、Amazonなどで小型の実装基板が販売されています。
今回は写真に示す小型ボードを購入し、自社のPIC32MM基板に接続ポートを追加して接続し、動作の検証を行います。

   図(a) 購入したW5500評価基板と実験用に接続ポートを追加したPIC32MM実験基板

 次にドライバソフトウェアをダウンロードします。こちらもメーカーのHPからGitHuにあるドライバをダウンロードします。
私がダウンロードしたときの最新版はV10.2でした。

  注意点1.
 ダウンロードしたファイルにはiolibrary.chmという名前のヘルプファイルが含まれていますが、この形式のヘルプファイルはWindows10では
正しく扱えないようでヘルプの中身の大半を表示できません。性質が悪いことに中途半端に目次などの表示が部分的に行われるため、
単に出来の悪いヘルプファイルだと勘違いさせられます。同じファイルをWindows7で動作させると正しく中身が表示されます。
ドライバソフトウェアの動作を理解するため参考になる情報が含まれているので一度目を通しておくといいでしょう。

             図(b) Win10によるHelp画面                                 図(c) Win7によるHelp画面


 4.2 注意すべき事項

 上記の資料を基に実験環境を用意していきますが、これらの資料だけでは分かり難い点について書いておきます。
これらは実際に動かしてみて確認できた内容です。

 1) マイコンとW5500を接続する信号線は上記の6本全てが必要か。削除できる信号があれば削除したい。

 まず、削除の可能性があるのは割り込み要求出力をマイコン側に接続するかどうかです。標準のドライバソフトウェアを使用する場合、
割り込みは使用していないので、特に接続する必要はありません。これはV10.2時点での話です。

 逆に説明不足なのはリセット信号でこの信号には少なくともPOR(パワーオンリセット)信号を入力する必要があります。
信号のパルス幅は500usMinとかなり長いパルス幅が必要です。
デバイスのコマンドによるリセットも可能ですが、PORに失敗するとその後のコマンドが正しく動作するかどうかは保障の限りではないので、
サボらずに正しくリセット信号を接続する必要があります。

 2) ドライバソフトウェアの基本情報

 ドライバソフトウェアは直接デバイスを操作するドライバ(フォルダ名はEthernet)とドライバを使用したインターネット上のプロトコール
(フォルダ名はInternet)および、アプリケーションから構成されています。
 まず、ドライバはunixのBSDソケットと呼ばれるIF仕様を模して作られています。BSDソケットについてはネットから多くの情報を得られます。
オリジナルのBSDソケットはunix環境が前提ですのでOSのサポートとしてプリエンプティブ方式のマルチタスク動作が前提になります。
当然、シングルタスクとして動作させることも可能ですが、組み込み装置での使用を前提にした場合には実用的ではありません。
非プリエンプティブ方式のマルチタスクを使用している場合はドライバから書き直しが必要です。
とはいえ、ドライバのプログラムサイズはCソースで1000行ほどと左程大きくはないので、書き直しはそれ程難しくはありません。


 4.3 初期評価の準備

 実験を行うためのハードウェアは用意できたので、ソフトウェアの準備を行います。
まず、ダウンロードしたドライバソフトウェアですが、このドライバはW5500を含む WIZnet社の複数のデバイスで動作するように出来ています。
これをW5500用に設定します。
 ・ Ethernetフォルダ内のwizchip_conf.hファイルの_WIZCHIP_定義をW5500に書き換えます
   #define _WIZCHIP_  W5500

 次に最初の検証に使用するアプリケーションをドライバソフトウェアの中から決めます。初期の動作検証にはアプリケーションのLoopBackを使用します。
通信手段としてTCPのサーバーとクライアントおよびUDPのサーバーが用意されています。
最初に基本的な通信のみを確認するならUDPが向いていますので、UDPのサーバーを使います。

 ただ、これらのLoopBackアプリケーションには、W5500を初期化する関数が定義されておらず、自前で用意する必要があります。
初期化の必要なレジスタはデータシートを読み込んで調べることも出来ますが、ネット上のArdino用ドライバを参考に記述しました。
この関数内からは、ドライバソフトウェアを自作の実験基板用にカスタマイズするための関数呼び出しが含まれています。
 下記に初期関数のサンプルを示します。
インクルードファイルのパスやインサーネットの設定(infoの設定)は、各自の環境に合わせた変更が必要です。
    注意事項
 この時点ではDHCPは使用できないので、W5500に設定するIPアドレスはDHCPが使用しないIPアドレスに設定が必要です。
#include "device/spi/spi_eth_5500/wizchip_conf.h"

//    イーサネットの設定、各自の環境に合わせて変更が必要
static const wiz_NetInfo info = {
    {01, 02, 03, 04, 05, 06},    //mac addr
    {192,168,0, 9},              //IP addr
    {255, 255, 255, 0},          //Subnet mask
    {192,168,0, 1},              //Gateway addr
    {192,168,0, 1},              //DNS addr
    NETINFO_STATIC               // NOt use DHCP
};

//これらの関数はユーザー定義が必要。
extern void cris_en(void);
extern void cris_ex(void);
extern void cs_sel(void);
extern void cs_desel(void);
extern uint8_t spi_read_byte(void);
extern void spi_write_byte(uint8_t data);
extern void spi_read_burst(uint8_t *buf, uint16_t len);
extern void spi_write_burst(uint8_t *buf, uint16_t len);

void initW5500(){
        //IF関数の登録
    reg_wizchip_cris_cbfunc(cris_en, cris_ex);
    reg_wizchip_cs_cbfunc(cs_sel, cs_desel);
    reg_wizchip_spi_cbfunc(spi_read_byte, spi_write_byte);
    reg_wizchip_spiburst_cbfunc(spi_read_burst, spi_write_burst);

        //デバイスReset
    setMR(MR_RST);
    uint8_t status;
    while((status = getMR()) != 0)
        ;
        //デバイスの初期設定
    //TX,RXバッファサイズの設定, defaultで使用する
    ;
        //wiz_NetInfoの設定
    wizchip_setnetinfo((wiz_NetInfo*)&info);
}
 ここで重要なのはreg_wizchip_xxxxという形式の4つの関数で、これらはユーザー定義が必要な関数をドライバに登録する処理です。
個々の関数と登録する関数の意味についてはHelpを参考にします。ここでは簡単に概要のみを説明します。

 ・ reg_wizchip_cris_cbfunc(cris_en, cris_ex);
 ドライバがW5500に接続されたSPIポートへのアクセスを開始する前と終了後に呼び出す関数を登録します。
通常、これらを使用することは少ないと思われます。登録の必要が無い場合は、引数として二つのNULLを与えます。
SPIポートのようなIOはタスク間で処理が競合しないように排他制御を行いますが、「稼働中のタスクのみがSPIポートを占有する」という
排他制御を行っている場合は、SPIポートへのアクセスが始まったら、処理が終了するまではタスクの切り替えを禁止する必要があります。
おそらくは、この用途を想定したものと思われます。

 ・ reg_wizchip_cs_cbfunc(cs_sel, cs_desel);
 SPIポートのCS信号をON/OFFする関数を登録します。
ここでCSをONさせる関数には注意が必要です。W5500を接続しているSPIポートに複数のデバイスを接続している場合は、
W5500のCSをONする前にSPIのボーレートとモードの設定をW5500の設定値に再設定しておく必要があります。

 ・ reg_wizchip_spi_cbfunc(spi_read_byte, spi_write_byte)
 SPIポート経由でW5500内のレジスタへの1バイトのリード/ライトを行う関数を登録します。CS信号の操作は不要です。
実際にはW5500内のレジスタアドレス2バイト, コントロールバイト, データの合計4バイトのデータ転送が必要です。
リード時には3バイトの書き込みと1バイトの読み出しが、ライトの時には4バイトの書き込みが必要になります。
SPI通信ですので書き込みバイト数と同数の無効なリードデータの空読みも必要になります。

 ・ reg_wizchip_spiburst_cbfunc(spi_read_burst, spi_write_burst)
NバイトのデータをW5500に対して読み書きする関数を登録します。注意点は1バイトデータの読み書きと同じになります。

 サンプルではコマンドによるリセット処理を行っていますが、必須ではありません。
次にwizchip_setnetinfo()関数でイーサネット接続に必要なMACアドレスやIPアドレス等の情報をセットします。

 以上が初期化の手順になります。
後はmain()関数を用意してinitW5500()関数を実行後に loopback_udps()を実行します。


 4.4 通信相手を用意する

 通信の動作検証では、通信の相手が必要です。イーサネットで比較的簡単に用意できるのは、やはりPCになります。
W5500はUDPのサーバーとして動作しますので、PC側はUDPのクライアントとして動作させます。
実現方法は幾つもあるのでしょうが、今回はPython言語を使って実現します。
Python言語に関しては、私自身も詳しくはないので、使用したコードを示すに留めます。
なお、HPへのコピーの関係でTabは4つのSpaceに置き換えてあります。
##test.py
from socket import *
import sys

##-----------------------------------------------------------------------
## 下請け関数
def set_addr(self):
    SrcIP = "192.168.0.2"                             # 受信元IP
    SrcPort = 11111                                 # 受信元ポート番号
    self.SrcAddr = (SrcIP, SrcPort)                 # アドレスをtupleに格納
    #送信
    DstIP = "192.168.0.9"                             # 宛先IP
    DstPort = 22222                                 # 宛先ポート番号
    self.DstAddr = (DstIP,DstPort)                  # アドレスをtupleに格納
    

##-----------------------------------------------------------------------
## UDP_クライアントクラス
class UdpClient():
    def __init__(self):
        set_addr(self)
        self.udpClntSock = socket(AF_INET, SOCK_DGRAM)  # ソケット作成
        self.udpClntSock.bind(self.SrcAddr)        # 送信元アドレスでバインド
        self.udpClntSock.setblocking(0)
        #受信
        self.BUFSIZE = 1024                             # バッファサイズ指定

    def recv(self):
        data=''
        address=''        
        try:
            data, addr = self.udpClntSock.recvfrom(self.BUFSIZE)    # 受信
        except error:
            pass
        else:
            print(data.decode(), addr)      # 受信データと送信アドレス表示

    def send(self):
        data = "Hello_AnyOne"
        data = data.encode('utf-8')                     # バイナリに変換
        self.udpClntSock.sendto(data, self.DstAddr)     # 宛先アドレスに送信

def test_udp_client():
    udp = UdpClient()     # クラス呼び出し
    try:
        udp.send()          # 関数実行
        while True:
            udp.recv()          # 関数実行
    except KeyboardInterrupt: 
        #self.udpClntSock.close()
        sys.exit(0)

##-----------------------------------------------------------------------
test_udp_client()

 4.5 実験結果

 UDPによるループバックの実行結果は下記のようになります。
"python test.py"がテストの実行、次の"Hello_AnyOne ('192.168.0.9', 22222)"がPCからUDPによって送受信されたテキスト文字と
宛先のIPアドレスおよびポート番号です。



 以上で、W5500の初期検証は終了です。今回はUDPのみを確認しましたが、TCPについても同様にデータの送受信が出来ることを
確認してあります。