Ethernet easy plug&play

Plug&play Ethernet client for Industruino

Tom

This post aims to get you started with the Industruino Ethernet module with a plug&play example sketch. It uses a common HTTP GET request that can be easily modified for any API, or any specific TCP communication. The sketch requires 3 libraries:

  • Ethernet2 this needs to be the Industruino version, as the SPI speed needs to be set to 4MHz
  • UC1701 for the Industruino LCD
  • Adafruit_SleepyDog for watchdog reset functionality 
The sketch does the following:
  1. Enable the watchdog timer with an interval of 20 seconds: if the code hangs for some reason so the watchdog does not get reset within 20 seconds, the unit reboots
  2. MAC address: the Industruino D21G has a unique MAC address stored in the EEPROM of the RTC, this 8-byte MAC is retrieved and formatted to 6 bytes for IPv4
  3. Check ETH module: the Ethernet module has 8KB non-volatile FRAM memory; we use this to check whether the module is connected by writing/reading a flag at address 0 of the FRAM
  4. Start Ethernet: we can use a fixed IP address, or request via DHCP, using the parameter USE_DHCP at the top of the sketch
  5. Connect to server: the sketch loop makes a connection to the TCP_SERVER at port TCP_SERVER_PORT
  6. HTTP GET request: if a connection is established, the sketch retrieves www.arduino.cc/latest.txt and looks for the '200 OK' reply
  7. If the connection to the server fails, Ethernet is restarted
  8. At the start of the loop, the watchdog timer is reset 
In case the ETH module is not connected, the sketch stops, which triggers a watchdog reset after 20 seconds.
In case USE_DHCP is set to 1 but no IP address is assigned by a DHCP server, the sketch also stops, and the watchdog will trigger a reset.
Open the Serial Monitor to see diagnostics, and most info is also displayed on the LCD.
Below is the code, also on github.
/*
   Industruino Ethernet example sketch for:
   > Industruino D21G PROTO or IND.I/O
   > Industruino Ethernet module

   Libraries needed:
   > Ethernet2: Industruino version at https://github.com/Industruino/Ethernet2
   > UC1701: https://github.com/Industruino/UC1701
   > Adafruit_SleepyDog: https://github.com/adafruit/Adafruit_SleepyDog

   The D21G topboard has a built-in RTC (MCP79402) with a EUI-64 number that can be used as MAC address
   The Ethernet module has an SD card and 8KB non-volatile FRAM memory

   This sketch:
   -reads 6-byte (48-bit) MAC address from RTC EEPROM over I2C
   -checks if Ethernet module is connected (by writing/reading a flag in FRAM)
   -starts Ethernet with MAC address and static IP address or DHCP
   -sends an HTTP GET request to a server and shows the reply status
   -if the code hangs anywhere for more than 20 seconds, the watchdog timer resets the unit

   Tom Tobback June 2019 for Industruino
*/


////////////////// RTC MAC ////////////////////////////////////////
#include <Wire.h>                               // for RTC MAC
byte mac[6];                                    // read from RTC

////////////////// ETHERNET //////////////////////////////////////
#include <SPI.h>                                // for Ethernet
#include <SD.h>                                 // for FRAM to work
#include <Ethernet2.h>                          // use Industruino version
EthernetClient client;

////////////////// USER CONFIGURATION /////////////////////////////
#define USE_DHCP 0
IPAddress industruino_ip (192, 168, 1, 100);    // example
#define TCP_SERVER "www.arduino.cc"
#define TCP_SERVER_PORT 80                     // for http

//////////////////////// FRAM ///////////////////////////////////
const byte FRAM_CMD_WREN = 0x06; //0000 0110 Set Write Enable Latch
const byte FRAM_CMD_WRDI = 0x04; //0000 0100 Write Disable
const byte FRAM_CMD_RDSR = 0x05; //0000 0101 Read Status Register
const byte FRAM_CMD_WRSR = 0x01; //0000 0001 Write Status Register
const byte FRAM_CMD_READ = 0x03; //0000 0011 Read Memory Data
const byte FRAM_CMD_WRITE = 0x02; //0000 0010 Write Memory Data
const int FRAM_CS1 = 6; //chip select 1

//////////////////// INDUSTRUINO LCD //////////////////////////////
#include <UC1701.h>
static UC1701 lcd;
#define LCD_PIN 26

/////////////////////// WATCHDOG TIMER ////////////////////////////
#include <Adafruit_SleepyDog.h>
const unsigned int watchdog_interval = 20000;     // 20 seconds: if no wdt reset within this interval, wdt will reset the unit

///////////////////////////////////////////////////////////////////
/// FRAM functions need to be included before setup()
///////////////////////////////////////////////////////////////////
/**
   Write to FRAM (assuming 2 FM25C160 are used)
   addr: starting address
   buf: pointer to data
   count: data length.
          If this parameter is omitted, it is defaulted to one byte.
   returns: 0 operation is successful
            1 address out of range
*/
int FRAMWrite(int addr, byte *buf, int count = 1) {
  if (addr > 0x7ff) return -1;
  byte addrMSB = (addr >> 8) & 0xff;
  byte addrLSB = addr & 0xff;
  digitalWrite(FRAM_CS1, LOW);
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  SPI.transfer(FRAM_CMD_WREN);  //write enable
  digitalWrite(FRAM_CS1, HIGH);
  digitalWrite(FRAM_CS1, LOW);
  SPI.transfer(FRAM_CMD_WRITE); //write command
  SPI.transfer(addrMSB);
  SPI.transfer(addrLSB);
  for (int i = 0; i < count; i++) SPI.transfer(buf[i]);
  digitalWrite(FRAM_CS1, HIGH);
  return 0;
}

///////////////////////////////////////////////////////////////////
/**
   Read from FRAM (assuming 2 FM25C160 are used)
   addr: starting address
   buf: pointer to data
   count: data length.
          If this parameter is omitted, it is defaulted to one byte.
   returns: 0 operation is successful
            1 address out of range
*/
int FRAMRead(int addr, byte *buf, int count = 1) {
  if (addr > 0x7ff) return -1;
  byte addrMSB = (addr >> 8) & 0xff;
  byte addrLSB = addr & 0xff;
  digitalWrite(FRAM_CS1, LOW);
  SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));
  SPI.transfer(FRAM_CMD_READ);
  SPI.transfer(addrMSB);
  SPI.transfer(addrLSB);
  for (int i = 0; i < count; i++) buf[i] = SPI.transfer(0x00);
  digitalWrite(FRAM_CS1, HIGH);
  return 0;
}

////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////

void setup() {

  // setup LCD
  pinMode(LCD_PIN, OUTPUT);                     // LCD backlight
  digitalWrite(LCD_PIN, HIGH);                  // LCD backlight ON
  lcd.begin();
  lcd.print("INDUSTRUINO ETHERNET");

  // start SerialUSB for Serial Monitor
  SerialUSB.begin(115200);
  //while (!SerialUSB);                           // wait for Serial Monitor to connect, for testing only
  SerialUSB.println("===================================");
  SerialUSB.println("== INDUSTRUINO ETHERNET EXAMPLE ===");
  SerialUSB.println("===================================");
  printTime(); SerialUSB.println("SETUP");

  // start watchdog timer
  Watchdog.enable(watchdog_interval);
  SerialUSB.println("Watchdog timer started");

  // get MAC address from RTC
  printTime();
  readMACfromRTC();             // MAC stored in RTC eeprom
  lcd.setCursor(0, 1);
  lcd.print("MAC ");
  for (int u = 0; u < 6; u++) {
    lcd.print(mac[u], HEX);
    if (u < 5) lcd.print(":");
  }

  // setup Ethernet module: SPI chip select pins
  pinMode(FRAM_CS1, OUTPUT);    // for FRAM
  digitalWrite(FRAM_CS1, HIGH);
  pinMode(4, OUTPUT);           // for SD
  digitalWrite(4, HIGH);
  pinMode(10, OUTPUT);          // for Ethernet
  digitalWrite(10, HIGH);

  // check if Ethernet module is connected (by write/read FRAM flag)
  lcd.setCursor(0, 2);
  printTime();
  if (ethernetModule()) {
    SerialUSB.println("Ethernet module found");
    lcd.print("ETH module found");
  } else {
    SerialUSB.println("Ethernet module not found, stop here");
    lcd.print("ETH module NOT found");
    lcd.setCursor(0, 3);
    lcd.print("STOP");
    while (1);  // stay here forever, this will trigger the watchdog timer to reset the unit
  }

  // start Ethernet
  if (USE_DHCP) {
    SerialUSB.print("requesting IP address from DHCP... ");
    if (!Ethernet.begin(mac)) {
      SerialUSB.println("could not get IP address over DHCP, stop here");
      lcd.setCursor(0, 3);
      lcd.print("DHCP failed, STOP");
      while (1);  // stay here forever, this will trigger the watchdog timer to reset the unit
    }
    SerialUSB.println("OK");
  } else {        // static IP
    Ethernet.begin(mac, industruino_ip);
  }
  printTime();
  SerialUSB.print("Ethernet started with above MAC and IP: ");
  SerialUSB.println(Ethernet.localIP());
  lcd.setCursor(0, 3);
  lcd.print("IP ");
  lcd.print(Ethernet.localIP());
  if (USE_DHCP) lcd.print(" DHCP");

  printTime();
  SerialUSB.println("END OF SETUP");
  SerialUSB.println();

}

////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////

void loop() {

  Watchdog.reset();                          // needed to avoid watchdog reset

  printTime();
  SerialUSB.print("connecting to server ");
  SerialUSB.print(TCP_SERVER);
  SerialUSB.print(" on port ");
  SerialUSB.print(TCP_SERVER_PORT);
  SerialUSB.print("... ");
  lcd.setCursor(0, 4);
  lcd.print("connecting to:");
  lcd.setCursor(0, 5);
  lcd.print(TCP_SERVER);

  if (client.connect(TCP_SERVER, TCP_SERVER_PORT)) {
    SerialUSB.println("connected");
    lcd.print(" OK");
    // example for HTTP GET REQUEST
    client.println("GET /latest.txt HTTP/1.1");
    client.print("Host: ");
    client.println(TCP_SERVER);
    client.println("User-Agent: arduino-ethernet");
    client.println("Connection: close");
    client.println();

    /*
      // this block outputs the reply
      unsigned long timestamp = millis();
      while (client.available() || (millis() - timestamp < 1000)) {
      if (client.available()) {
        SerialUSB.write(client.read());
      }
      }
    */

    // this block just looks for '200 OK' in the reply
    lcd.setCursor(0, 6);
    printTime();
    if (client.find("200 OK")) {
      SerialUSB.println("server replies OK");
      lcd.print("server replies OK ");
    } else {
      SerialUSB.println("no OK received from server");
      lcd.print("no OK received    ");
    }

    client.stop();

  } else {
    printTime();
    SerialUSB.println("failed to connect, restarting Ethernet");
    lcd.setCursor(0, 6);
    lcd.print("failed to connect    ");
    lcd.setCursor(0, 7);
    lcd.print("restart Ethernet...  ");

    if (USE_DHCP) {
      SerialUSB.print("requesting IP address from DHCP... ");
      if (!Ethernet.begin(mac)) {
        SerialUSB.println("could not get IP address over DHCP, stop here");
        lcd.setCursor(0, 3);
        lcd.print("DHCP failed, STOP");
        while (1);  // stay here forever, this will trigger the watchdog timer to reset the unit
      }
      SerialUSB.println("OK");
    } else {   // static IP
      Ethernet.begin(mac, industruino_ip);
    }
    // refresh IP on LCD
    lcd.setCursor(0, 3);
    lcd.print("IP ");
    lcd.print(Ethernet.localIP());
    if (USE_DHCP) lcd.print(" DHCP");

  }
  SerialUSB.println();

  delay(5000);
  // clear LCD lines about connection
  lcd.setCursor(0,5);
  lcd.print("                    ");
  lcd.setCursor(0,6);
  lcd.print("                    ");
  lcd.setCursor(0,7);
  lcd.print("                    ");
  
}

////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

//////////////////////////// MAC ADDRESS ////////////////////////////////////////////
// the RTC has a MAC address stored in EEPROM - 8 bytes 0xf0 to 0xf7
void readMACfromRTC() {
  Wire.begin();                                 // I2C for RTC MAC
  SerialUSB.print("Reading MAC from RTC EEPROM: ");
  int mac_index = 0;
  for (int i = 0; i < 8; i++) {   // read 8 bytes of 64-bit MAC address, 3 bytes valid OUI, 5 bytes unique EI
    byte m = readByte(0x57, 0xf0 + i);
    SerialUSB.print(m, HEX);
    if (i < 7) SerialUSB.print(":");
    if (i != 3 && i != 4) {       // for 6-bytes MAC, skip first 2 bytes of EI
      mac[mac_index] = m;
      mac_index++;
    }
  }
  SerialUSB.println();
  SerialUSB.print("Extracted 6-byte MAC address: ");
  for (int u = 0; u < 6; u++) {
    SerialUSB.print(mac[u], HEX);
    if (u < 5) SerialUSB.print(":");
  }
  SerialUSB.println();
}

//////////////////////////// MAC ADDRESS /////////////////////////////////////////////
// the RTC has a MAC address stored in EEPROM
uint8_t readByte(uint8_t i2cAddr, uint8_t dataAddr) {
  Wire.beginTransmission(i2cAddr);
  Wire.write(dataAddr);
  Wire.endTransmission(false); // don't send stop
  Wire.requestFrom(i2cAddr, 1);
  return Wire.read();
}

///////////////////////// CHECK MODULE //////////////////////////////////////////////
boolean ethernetModule() {
  SD.begin(4);  // FRAM seems to work only after SD is initialised
  byte set_flag[] = {1};
  FRAMWrite(0, set_flag, 1);
  byte read_flag[1];
  FRAMRead(0, read_flag, 1);
  if (read_flag[0] == 1) return true;
  else return false;
}

///////////////////////// PRINT TIME ////////////////////////////////////////////////
void printTime() {
  SerialUSB.print("[" + String(millis() / 1000.0) + "] ");
}