MOSの覚書

趣味人の公開メモ帳

Raspberry Pi Pico で Ethernet 接続|W5500

先日、Amazon の樹海をさまよっていたところ、WIZnet の W5500 チップを搭載した SPI 接続の Ethernet モジュールを見つけました。これを使って RasPico を Ethernet 接続できないかと思い立ち、試しに一個買ってみました

購入元リンクはこちら

https://www.amazon.co.jp/gp/product/B08C7DX4HN/

尚、本稿執筆時点では在庫なしになっています

この記事では、実際にこのモジュールを使用して RasPico を Ethernet 接続に対応させるまでを書き残そうと思います

使用する回路

回路図を起こすのは面倒なので実物の写真でご勘弁ください
MAX7216 搭載の 8 桁 7 セグメント LED モジュールも使えるようにしています
また、部品はすべてピンソケットを経由させています。テスト基板だからね

(※ RasPico が逆になっています)

本モジュールは SPI 通信を使用するため、SPI の信号線が伸びています。接続は以下の通り

W5500 RasPico
MI GP16
MO GP19
CS GP17
SCK GP18

CS はおそらく任意ですが、今回は近場の GP17 を使用しました

回路の確認をするため、サンプルプログラムを書き込んでみます
今回は [ファイル] > [スケッチ例] > [Ethernet] より [UdpNtpClient] を選択しました

環境によって Ethernet.init(); にわたす引数を変えてあげる必要があります。ここに入れる引数はどうも SPI の CS ピンに該当するようなので今回は Ethernet.init(17); を追記しました
また、アクセス先のタイムサーバを ntp.nict.jp に変更しています

時刻を取得することができました。ですが、これだと UTC のままなので JST で表示してくれるようにちょっと手を加えます。また、ついでに実装した 7 セグメント LED に取得した時刻を表示するようにもしてみました

以下使用コード

#include <Ethernet.h>
#include <EthernetUdp.h>
#include <SPI.h>

#define LED_BUILTIN 25

#define MAX7219_DIN 10
#define MAX7219_CS  11
#define MAX7219_CLK 12

#define MAX7219_REGISTER_NOP          0x0  // 未使用:
#define MAX7219_REGISTER_DECODE_MODE  0x9
#define MAX7219_REGISTER_INTENSITY    0xA
#define MAX7219_REGISTER_SCAN_LIMIT   0xB
#define MAX7219_REGISTER_SHUTDOWN     0xC
#define MAX7219_REGISTER_TEST         0xF

// 0 ~ F のインデックスでセグメントの情報を出す配列:
const uint8_t SEGMENT_HEX_DECODER[16] = {0b01111110, 0b00110000, 0b01101101, 0b01111001,   // 0, 1, 2, 3:
                                         0b00110011, 0b01011011, 0b01011111, 0b01110000,   // 4, 5, 6, 7:
                                         0b01111111, 0b01111011, 0b01110111, 0b00011111,   // 8, 9, A, b:
                                         0b00001101, 0b10111101, 0b01001111, 0b01000111};  // c, d, E, F:

byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};  // MAC addr:

unsigned int localPort = 8888;       // local port to listen for UDP packets

const char timeServer[] = "ntp.nict.jp"; // ntp.nict.jp NTP server

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message

int8_t GMT = 9;  // JST に変換するため:

byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

// A UDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

void setup() {
  LED_init();
  Ethernet.init(17);  // CS pin:

  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  while (!Serial);

  // start Ethernet and UDP
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // Check for Ethernet hardware present
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    } else if (Ethernet.linkStatus() == LinkOFF) {
      Serial.println("Ethernet cable is not connected.");
    }
    // no point in carrying on, so do nothing forevermore:
    while (true) {
      delay(1);
    }
  }
  Udp.begin(localPort);
}

void loop() {
  sendNTPpacket(timeServer); // send an NTP packet to a time server

  // wait to see if a reply is available
  delay(25);
  if (Udp.parsePacket()) {
    // We've received a packet, read the data from it
    Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;

    // now convert NTP time into everyday time:
    Serial.print("Unix time = ");
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;
    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears;
    // print Unix time:
    Serial.println(epoch);
    
    epoch = epoch + GMT * 60 * 60;  // JST に変更:

        // print the hour, minute and second:
    Serial.print("The GMT ");
    if(GMT > 0) {
      Serial.print("+");
    }else if(GMT == 0) {
      Serial.print("±");
    }
    Serial.print(GMT);
    Serial.print(" time is ");       // UTC is the time at Greenwich Meridian (GMT)
    Serial.print((epoch  % 86400L) / 3600); // print the hour (86400 equals secs per day)
    Serial.print(':');
    if (((epoch % 3600) / 60) < 10) {
      // In the first 10 minutes of each hour, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.print((epoch  % 3600) / 60); // print the minute (3600 equals secs per minute)
    Serial.print(':');
    if ((epoch % 60) < 10) {
      // In the first 10 seconds of each minute, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.println(epoch % 60); // print the second
    Display_time(epoch % 86400);  // Display to LED:
  }
  Serial.println();
  // // wait ten seconds before asking for the time again
  delay(5000);
  Ethernet.maintain();
}

// send an NTP request to the time server at the given address
void sendNTPpacket(const char * address) {
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); // NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

void LED_init(void) {
  pinMode(MAX7219_CS , OUTPUT);
  pinMode(MAX7219_CLK, OUTPUT);
  pinMode(MAX7219_DIN, OUTPUT);
  
  digitalWrite(MAX7219_CS , HIGH);
  digitalWrite(MAX7219_CLK,  LOW);
  digitalWrite(MAX7219_DIN,  LOW);

  LED_OUT(MAX7219_REGISTER_DECODE_MODE, 0b00000000);    //7セグでデコードするビットに1を立てる:
  LED_OUT(MAX7219_REGISTER_INTENSITY  , 0b00011100);    //輝度を設定, 0-15:
  LED_OUT(MAX7219_REGISTER_SCAN_LIMIT , 0b00000111);    //使用する桁数を指定, 桁数-1:
  LED_OUT(MAX7219_REGISTER_SHUTDOWN   , 0b00000001);    //特にいじる必要なし:
  LED_OUT(MAX7219_REGISTER_TEST       , 0b00000000);    //同上:
}

void LED_OUT(uint8_t addr, uint8_t dat){
  digitalWrite(MAX7219_CS,  LOW);
  shiftOut(MAX7219_DIN, MAX7219_CLK, MSBFIRST, addr);
  shiftOut(MAX7219_DIN, MAX7219_CLK, MSBFIRST, dat);
  digitalWrite(MAX7219_CS, HIGH);
}

void Display_time(uint32_t times) {
  if(times > 86400) {
    times = times % 86400;
  }
  Serial.print("times = ");
  Serial.println(times);
  uint8_t S8 = times / (60 * 60 * 10);
  times = times % (60 * 60 * 10);
  uint8_t S7 = times / (60 * 60);
  times = times % (60 * 60);
  uint8_t S5 = times / (60 * 10);
  times = times % (60 * 10);
  uint8_t S4 = times /  60;
  times = times % (60);
  uint8_t S2 = times /  10;
  times = times % (10);
  uint8_t S1 = times;

  Display_null();
  LED_OUT(8, SEGMENT_HEX_DECODER[S8]);
  LED_OUT(7, SEGMENT_HEX_DECODER[S7]);
  LED_OUT(6, 0b00000001);  // Hyphen:
  LED_OUT(5, SEGMENT_HEX_DECODER[S5]);
  LED_OUT(4, SEGMENT_HEX_DECODER[S4]);
  LED_OUT(3, 0b00000001);  // Hyphen:
  LED_OUT(2, SEGMENT_HEX_DECODER[S2]);
  LED_OUT(1, SEGMENT_HEX_DECODER[S1]);

  Serial.print(S8);
  Serial.print(S7);
  Serial.print(":");
  Serial.print(S5);
  Serial.print(S4);
  Serial.print(":");
  Serial.print(S2);
  Serial.print(S1);
  Serial.print("\n");
}

void Display_null(void){
  LED_OUT(0x9, 0x00);
  LED_OUT(0x1, 0b00000000);
  LED_OUT(0x2, 0b00000000);
  LED_OUT(0x3, 0b00000000);
  LED_OUT(0x4, 0b00000000);
  LED_OUT(0x5, 0b00000000);
  LED_OUT(0x6, 0b00000000);
  LED_OUT(0x7, 0b00000000);
  LED_OUT(0x8, 0b00000000);
}

これを書き込んで実行すると、こうなります

無事 NTP から時刻を取得しディスプレイに表示させることができました。結構簡単でいいですね