A Place Where No Dreams Come True...

Current Topic: The W7500P IOT microcontroller from WizNet is a device based on an ARM CORTEX-M0 microcontroller with several standard peripherals - Timers, PWM, SPI, IIC, UART, WatchDog and an internal hardwired eight port TCP/IP stack with MAC and PHY layers targeted at Internet of Things designs. Let's see how well it does.

The First Step To Any Design Is A Proper Schematic.

Recently I have been using KiCad as my EDA suite. It comes with a very good schematic capture program with a quality design-rule-checker and good part library manager. It includes a proper multilayer Printed Circuit Board design program also with good Design Rule Checker and part library manager. The PCB program includes 3D modeling, Gerber file generator and Excellon drill file mapper. There is also a fair, but slow, Gerber file viewer to assist in verification. All in all. It works very good. It's free. It's well supported, still in active development and runs great on Linux.

W7500P KiCad Schematic page 1 W7500P KiCad Schematic page 2

Design And Layout Of The Printed Circuit Board.

I have designed many circuit cards in my career but it has been several years since that last time. Learning KiCad was not very difficult because of my earlier experience. I found the User Interface to be reasonable and the design rule enforcement very usable. The library editor was very reasonable and in no time I was off and running. It did take a few tries to get the design rules set up which required modifications to the schematic and multiple importing of the net-list.

I found though that the KiCad suite worked very well together and I had no trouble revising the schematic and PCB until I was happy with the results.

I ended up creating, for this design, a four layer circuit card. Top and bottom signal layers with two inner planes for ground and power with all components mounted on the top side. The card also uses mostly surface mount parts with only a few through hole connectors.

W7500P PCB top layer W7500P PCB bottom layer W7500P PCB inner ground plane

One of the extra features of KiCad is the ability to render a 3D model of the circuit card design. It is not only a cool feature but it is very useful in helping verify that any custom component footprints that may have been created will actually fit the component. As an example. The footprint I created for the USB connector did not fit the 3D model I received from the manufacturer. I was in fact off by small amount on the Non Plated Through (NPT) alignment holes. The model pointed this out. When I rechecked my design I realized that I had a small division (/ 2) error and the spacing (between them) was incorrect. Fortunately I caught this before I had the boards fabricated.

Here's a note to component manufacturers. At the last minute I substituted a few components specifically because the manufacturer did NOT provide 3D models of their components. Some manufacturers also wanted me to create an online account to download their models. Manufacturers need to take special care to provide these models "Free and Unencumbered" as tools for designers. Suppliers need to make these easily obtainable to encourage designers to use their parts. Having many parts to chose from many designers will shy away from parts where the models are hard to obtain.

W7500P KiCad PCB 3D Rendering W7500P KiCad PCB 3D Rendering W7500P KiCad PCB 3D Rendering

Fabricating And Assembling The Printed Circuit Cards.

I had these cards fabricated in China at a company called EasyEDA. They do great work at a very low price with quick turnaround and reasonable shipping time. These boards (4 layer) took about four days to fab with two week standard (cheap) shipping. The boards cost me only $11.62 each for five pieces and they actually sent me six boards. It's the third time I have made boards with them but it was the first multilayer one. The quality is fantastic. The boards traces are crisp, the Solder Mask is consistant and the Silk Screen is accurate.

So I bought a big pink bag of parts from Mouser, a rather large soldering station from Fry's and began assembling. I am an expert level solderer. I have had Military/Aerospace training in my past and have been at it for maybe 45 years. Mostly it went relatively easy however the W7500P itself was a real challenge. Just aligning the part was a miserable chor at a 0.40mm pitch it packs a modest 64 pins around a 1/4 inch body. Pretty much at the limit of my soldering skills. Maybe anyones skill.

W7500P PCB bare boards W7500P PCB pink bag of parts W7500P PCB component parts

With the soldering iron heated up, some very fine gauge solder, a pair of tweezers and a magniging glass I started out assembling. First thing was to complete the regulator and UART to USB interface. I needed the USB interface because it supplies the main power to the 3.3V LDO regulator. Also I needed to place the UART to USB chip first because once the connectors and other support parts are in place there is no room to solder the interface chip. I plugged in the board and the regulator was working and the USB interface chip (FTDI). Linux immediately recognized the USB interface and the 3.3V regulator was supplying the correct voltage. Nice! I then had to solder in W7500P. Again the order of hand soldering the parts is important because it is possible that the wrong part installed first will block access making it difficult to solder other surrounding components.

W7500P KiCad PCB assembly tools

The biggest problem I had assembling really were my eyes. Old eyes. I have only a pair of reading glasses (non prescription) and a small magnifying glass (actually plastic). The iron is rather bulky for this kind of work and the tip is not very good. I place the parts using the magnifier and the tweezers and tack the part down. Then I solder the remainder of the part including the part that was originally only tacked on. I then use solder wick to suck up the solder and carefully resolder the part again. This way I am assured that the part is soldered correctly with a minimum amount of solder. All in all it's a very good method for such tiny components.

The above picture, by the way, represents the only tools I have available to not only build the prototype but to debug it as well. No DVM... No Oscilloscope... No fancy probes or signal tracers of any kind. Just a lot of experience and good common sense.

Testing The Printed Circuit Cards.

Here's where the fun really begins. In order to test the boards you need to be able to write and execute code in the target processor. This requires a compiler and a programming (flash) utilities. Since I'm a Linux user I of course chose the GNU arm-none-eabi-gcc ARM bare-metal compiler toolchain consisting of a C/C++ compiler, assembler, linker and various utilities to handle the creation debugging of the actual binaries. However, there was no tool for uploading (flashing) the target processor. Hmm! Unfortunately the manufacturer, WizNet, supports only WinDoze. Bad Idea. Another note to manufacturers. Windows is for amateurs and hobbiests. Serious developers use Linux (or Unix). An Opperating System that really works and is designed for engineering development. If you want to attract developers to your product then make sure you support their choice of development environments.

To make matters worse. The manufacturer provides only a third party script written in Python available from Git. Now I use Arch Linux. It is a bare very progressive distribution. It is usually several months ahead of distro's like Ubuntu which means that often things are released earlier before all the bugs are worked out. I have never found this to be a problem with Linux itself but still silly things tend to break. The Python script was a prime example of this problem. It seems that it was caught up between revisions (Python2 vs: Python3) and did not run the script on my computer. Since I'm not much of a script person I decided not to modify the script but instead to rewrite it as a C program using no frills and only basic Linux dependencies.

W7500P assembled PCB W7500P peripherals attached W7500P peripheral wiring

Using the script as a model I reverse engineered the programming protocol and wrote a console program (no UI) which takes the compiler binary and feeds it to the flash programer resident in the target processor. Wow! That was surprisingly simple. In only about two days I was reliably programming the target. I actually had more trouble with the Linux stdio interface then I did with the target programming.

Here is what I ended up with...

//----------------------------------------------------------------------------
// file:flash.c
//
//  Utility to flash (program) Smegware W7500P SmegNet eval board.
//
// Copyright (c) 2017 - Dysfunctional Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//
//----------------------------------------------------------------------------

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
#include <termios.h>

#include <report.h>

#include "flash.h"
#include "xmodem.h" // Includes serial.h.

//----------------------------------------------------------------------------

#define MAX_RESPONSE  32
#define MAX_TRIES     32

//----------------------------------------------------------------------------
// FLASH programming bank lookup table 64-Banks 512 X 4 Bytes = 128KB.
// Maybe should just calculate this instead?
/*
static const uint32_t flash_bank[] = {
  0X00000000,0X00000800,0X00001000,0X00001800,
  0X00002000,0X00002800,0X00003000,0X00003800,
  0X00004000,0X00004800,0X00005000,0X00005800,
  0X00006000,0X00006800,0X00007000,0X00007800,
  0X00008000,0X00008800,0X00009000,0X00009800,
  0X0000A000,0X0000A800,0X0000B000,0X0000B800,
  0X0000C000,0X0000C800,0X0000D000,0X0000D800,
  0X0000E000,0X0000E800,0X0000F000,0X0000F800,
  0X00010000,0X00010800,0X00011000,0X00011800,
  0X00012000,0X00012800,0X00013000,0X00013800,
  0X00014000,0X00014800,0X00015000,0X00015800,
  0X00016000,0X00016800,0X00017000,0X00017800,
  0X00018000,0X00018800,0X00019000,0X00019800,
  0X0001A000,0X0001A800,0X0001B000,0X0001B800,
  0X0001C000,0X0001C800,0X0001D000,0X0001D800,
  0X0001E000,0X0001E800,0X0001F000,0X0001F800
};
*/
//----------------------------------------------------------------------------

struct _console {
  pthread_t     me;
  int           file;
  pcomm_channel comm;
  int           result;
  int           running;
  int           stop;
  int           fdata;
  int           block;
  int           count;
  uint8_t       chunk[BLOCK_SIZE];
};

//----------------------------------------------------------------------------

static uint8_t get_response(struct _console *console)
{
  uint8_t rtn = 0;
  uint8_t i;
  int c;

  for(i = 0; i < 30; i++)
  {
    usleep(1000);
    c = serial_peek_char(console->comm);
    if(c != -1)
    {
      rtn = c;
      break;
    }
  }
  if(rtn)
  {
    for(i = 0; i < 100; i++)
    {
      usleep(1000);
      c = serial_peek_char(console->comm);
      if(c == -1)
      {
        break;
      }
    }
  }
  return rtn;
}

//----------------------------------------------------------------------------

#define MAX_MESSAGE 80
#define ANSI_ESCAPE 0X1B

static int isp_negotiate(struct _console *console)
{
  int i;
  int t;
  int s;
  int rtn;
  char response[MAX_RESPONSE + 2];
  char msg[MAX_MESSAGE + 4];


  report(dbglevl_none, "\n");
  sprintf(msg, "%cMNegotiating Interface Speed \n", ANSI_ESCAPE);
  report(dbglevl_none, msg);
  fflush(stdout);
  for(t = 0; t < MAX_TRIES; t++)
  {
    for(s = 0; s < MAX_MESSAGE; s++)
    {
      if(msg[s] == '\n')
      {
        msg[s] = '.';
        msg[s + 1] = '\n';
        msg[s + 2] = '\0';
        break;
      }
    }
    report(dbglevl_none, msg);
    serial_put_line(console->comm, SYNC_CMD);
    sleep(1);
    response[0] = 0;
    for(i = 0; i < MAX_RESPONSE; i++)
    {
      rtn = serial_peek_char(console->comm);
      if(rtn == -1)
      {
        response[i] = 0;
        break;
      }
      response[i] = (char)rtn;
    }
    if(strncmp(response, SYNC_RESP, strlen(SYNC_RESP)) == 0)
    {
      report(dbglevl_none, "Found W7500 ISP ver %s\n", response);
      rtn = 0;
      break;
    }
    if(console->stop)
    {
      rtn = -1;
      break;
    }
  }
  get_response(console);
  return rtn;
}

//----------------------------------------------------------------------------

static int prepare_block(struct _console *console)
{
  uint32_t i;

  report(dbglevl_debug, "Clearing block buffer to 0XFF.\n");
  for(i = 0; i < BLOCK_SIZE; i++)
  {
    console->chunk[i] = 0XFF;
  }
  return RET_SUCCESS;
}

//----------------------------------------------------------------------------

static int write_block(struct _console *console)
{
  int rtn = RET_FILE_EOF;
  uint32_t i;
  char command[48];

  if((rtn = prepare_block(console)) == RET_SUCCESS)
  {
    report(dbglevl_debug, "Writing file chunk to buffer.\n");
    console->count = read(console->file, console->chunk, BLOCK_SIZE);
    if(console->count > 0)
    {
      sprintf(command, CMD_WRITE, DOWN_ADDR0, DOWN_SIZE);
      serial_put_line(console->comm, command);
      for(i = 0; i < DOWN_SIZE; i++)
      {
        serial_put_char(console->comm, (char)console->chunk[i]);
      }
      rtn = get_response(console);
      if(rtn == RET_SUCCESS)
      {
        sprintf(command, CMD_WRITE, DOWN_ADDR1, DOWN_SIZE);
        serial_put_line(console->comm, command);
        for(i = DOWN_SIZE; i < BLOCK_SIZE; i++)
        {
          serial_put_char(console->comm, (char)console->chunk[i]);
        }
        rtn = get_response(console);
      }
    }
  }
  if(rtn == RET_SUCCESS)
  {
    if(console->count < DOWN_SIZE)
    {
      rtn = RET_FILE_EOF;
    }
  }
  report(dbglevl_debug, "Write Block returned %02X.\n", rtn);
  return rtn;
}

//----------------------------------------------------------------------------

static int flash_block(struct _console *console)
{
  uint8_t rtn;

  report(dbglevl_msg, "Programming 512 byte block %i.\n", console->block);
  serial_put_line(console->comm, CMD_PROGBANK0);
  rtn = get_response(console);
  if(rtn == RET_SUCCESS)
  {
    serial_put_line(console->comm, CMD_PROGBANK1);
    rtn = get_response(console);
  }
  console->block++;
  report(dbglevl_debug, "Flash Block returned %02X.\n", rtn);
  return rtn;
}

//----------------------------------------------------------------------------

static int erase_chip(struct _console *console)
{
  int rtn;

  serial_put_line(console->comm, CMD_ERASECHIP);
  report(dbglevl_msg, "Erasing entire chip.\n");
  rtn = get_response(console);
  report(dbglevl_debug, "Erase Chip returned %02X.\n", rtn);
  return rtn;
}

//----------------------------------------------------------------------------

static int erase_mass(struct _console *console)
{
  int rtn;

  serial_put_line(console->comm, CMD_ERASEMASS);
  report(dbglevl_msg, "Erasing data block and entire chip.\n");
  rtn = get_response(console);
  report(dbglevl_debug, "Mass Erase returned %02X.\n", rtn);
  return rtn;
}

//----------------------------------------------------------------------------

static int remap_flash(struct _console *console)
{
  int rtn;

  serial_put_line(console->comm, CMD_REMAP);
  report(dbglevl_msg, "Remapping target FLASH.\n");
  rtn = get_response(console);
  report(dbglevl_debug, "Remap Flash returned %02X.\n", rtn);
  return rtn;
}

//----------------------------------------------------------------------------

static int reset_target(struct _console *console)
{
  int rtn;

  serial_put_line(console->comm, CMD_RESET);
  report(dbglevl_msg, "Resetting target.\n");
  rtn = get_response(console);
  report(dbglevl_debug, "Reset Target returned %02X.\n", rtn);
  return rtn;
}

//----------------------------------------------------------------------------

static int program_flash(struct _console *console)
{
  int rtn;
  struct stat st;
  unsigned char *filebuf;
  char command[48];

  if((rtn = fstat(console->file, &st)) != -1)
  {
    filebuf = malloc(st.st_size);
    if(filebuf)
    {
      if((rtn = read(console->file, filebuf, st.st_size)) != -1)
      {
        report(dbglevl_msg, "Programming Program FLASH %i bytes.\n", st.st_size);
        sprintf(command, CMD_PROGFLASH, 0, (unsigned int)st.st_size);
        serial_put_line(console->comm, command);
        rtn = xmodemTransmit(console->comm, filebuf, st.st_size);
        if(rtn < 0)
        {
          report_error("error : Programming FLASH - XMODEM Transfer failed %s - ",
                       xmodem_get_error(rtn));
        }
        else
        {
          rtn = get_response(console);
        }
      }
      else
      {
        report_error("error : Programming FLASH - Unable to read file into buffer - ");
      }
      free(filebuf);
    }
    else
    {
      report_error("error : Programming FLASH - Unable to allocate buffer %i bytes - ",
                   st.st_size);
    }
  }
  else
  {
    report_error("error : Programming FLASH - Unable to determine file size - ");
  }
  return rtn;
}

//----------------------------------------------------------------------------

static int verify_flash(struct _console *console)
{
//  const char *response;

//  serial_put_line(console->comm, CMD_RESET);
//  response = serial_get_line(console->comm);
//  return *response;
  return RET_SUCCESS;
}

//----------------------------------------------------------------------------

static void isp_error(struct _console *console, int status)
{
  const char *msg = "";

  switch(status)
  {
    case RET_SUCCESS:
      break;

    case RET_IVLD_SIZE:
      msg = "error : Invalid operand Size.\n";
      break;

    case RET_IVLD_ADDR:
      msg = "error : Invalid address.\n";
      break;

    case RET_IVLD_CMD:
      msg = "error : Invalid command request.\n";
      break;

    case RET_NO_PRIV:
      msg = "error : Privilege violation.\n";
      break;

    case RET_IVLD_PARAM:
      msg = "error : Invalid parameter specified.\n";
      break;

    case RET_READ_LOCK:
      msg = "error : Read Lock detected.\n";
      break;

    case RET_WRITE_LOCK:
      msg = " error : Write Lock detected.\n";
      break;

    case RET_RESET:
      msg = "Done: Target Reset.\n";
      break;

    case RET_FILE_EOF:
      break;

    default:
      msg = "error : Unknown 0X%02X.\n";
      break;
  }
  report(dbglevl_none, msg, status);
}

//----------------------------------------------------------------------------

static void *process_console(void *ptr)
{
  int status;
  struct _console *console = ptr;

  console->running = 1;
  report(dbglevl_msg, "Starting ISP programmer.\n");
  if(isp_negotiate(console) == 0)
  {
    if(console->fdata)
    {
      if(!console->stop && ((status = erase_mass(console)) == RET_SUCCESS))
      {
        report(dbglevl_msg, "Programming data FLASH.\n");
        while(!console->stop)
        {
          status = write_block(console);
          if(status == RET_SUCCESS)
          {
            status = flash_block(console);
          }
          if(status == RET_FILE_EOF)
          {
            status = flash_block(console);
            break;
          }
          isp_error(console, status);
          if((status == RET_FILE_EOF) || (status != RET_SUCCESS))
          {
            break;
          }
        }
      }
    }
    else
    {
      if(!console->stop && ((status = erase_chip(console)) == RET_SUCCESS))
      {
        status = program_flash(console);
      }
    }
    if(status == RET_SUCCESS)
    {
      report(dbglevl_msg, "FLASH Programming successful.\n");
      if(!console->stop && ((status = verify_flash(console)) == RET_SUCCESS))
      {
        if(!console->stop && ((status = remap_flash(console)) == RET_SUCCESS))
        {
          if((!console->stop && (status = reset_target(console)) == RET_RESET))
          {
            report(dbglevl_debug, "ISP programming complete.\n");
          }
        }
      }
    }
    isp_error(console, status);
  }
  else
  {
    report(dbglevl_none, "error : Unable to establish connection to ISP Boot Loader.\n");
  }
  console->running = 0;
  return NULL;
}

//----------------------------------------------------------------------------

int flash_target(int fbin, char* dev, int baud, int format)
{
  struct termios tty;
  int vmin;
  int vtime;
  int lflag;

  tcgetattr(STDIN_FILENO, &tty);
  // These need to be restored when done or stdio will be screwed up.
  lflag = tty.c_lflag;
  vmin  = tty.c_cc[VMIN];
  vtime = tty.c_cc[VTIME];
  // Disable blocking on stdin.
  tty.c_lflag    &= ~(ICANON);  // Set to NON Canonical to disable blocking.
  tty.c_cc[VMIN]  = 0;          // Read doesn't block (0 character wait).
  tty.c_cc[VTIME] = 0;          // 0 second timeout.
  tcsetattr(STDIN_FILENO, TCSANOW, &tty);

  struct _console *console = malloc(sizeof(struct _console));
  if(console)
  {
    report(dbglevl_debug, "Created console context.\n", dev);
    memset(console, 0, sizeof(struct _console));
    console->file = fbin;
    console->comm = serial_open(dev, baud, format);
    if(console->comm)
    {
      serial_set_blocking(console->comm, 0);
      report(dbglevl_debug, "Open console on %s : success.\n", dev);
      console->result = pthread_create(&console->me, NULL, process_console, console);
      report(dbglevl_debug, "Console thread created.\n");
      sleep(1);
      while(console->running)
      {
        if(getchar() == 0X1B)
        {
          console->stop = 1;
          serial_abort(console->comm);
        }
        sleep(0);
      }
      report(dbglevl_debug, "Console thread exited.\n");
      report(dbglevl_debug, "Closing console on %s.\n", dev);
      serial_close(console->comm);
    }
    free(console);
    report(dbglevl_debug, "Released console context.\n", dev);
  }
  else
  {
    report_error("Unable to create console context - ");
  }
  // Restore original stdio settings.
  tcgetattr(STDIN_FILENO, &tty);
  tty.c_lflag     = lflag;
  tty.c_cc[VMIN]  = vmin;
  tty.c_cc[VTIME] = vtime;
  tcsetattr(STDIN_FILENO, TCSANOW, &tty);
  return 0;
}

//----------------------------------------------------------------------------
// end:flash.c
//

And the companion Header file...

//----------------------------------------------------------------------------
// file:flash.h
//
//  Utility to flash (program) Smegware W7500P SmegNet eval board.
//
// Copyright (c) 2017 - Dysfunctional Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//
//----------------------------------------------------------------------------

#define SYNC_CMD        "U"
#define SYNC_RESP       "U1.2\n"

#define RET_FILE_EOF     1
#define RET_SUCCESS     '0'
#define RET_IVLD_SIZE   '1'
#define RET_IVLD_ADDR   '2'
#define RET_IVLD_CMD    '3'
#define RET_NO_PRIV     '4'
#define RET_IVLD_PARAM  '5'
#define RET_READ_LOCK   '6'
#define RET_WRITE_LOCK  '7' 
#define RET_RESET       '8'

#define CMD_EOL         0X0D

#define DOWN_ADDR0      0X20000000
#define DOWN_ADDR1      0X20000100
#define DOWN_SIZE       0X00000100
#define BLOCK_SIZE      (DOWN_SIZE + DOWN_SIZE)

#define CMD_READ        "DUMP %08X %08X\r"
#define CMD_WRITE       "DOWN %08X %08X\r"
#define CMD_PROGBANK0   "PROG DAT0 20000000\r"
#define CMD_PROGBANK1   "PROG DAT1 20000100\r"
#define CMD_PROGFLASH   "XPRG %08X %08X\r"

#define CMD_LOCKREAD    "LOCK READ\r"
#define CMD_LOCK        "LOCK PROG %08X %08X\r"
#define CMD_ERASEMASS   "ERAS MASS\r"
#define CMD_ERASECHIP   "ERAS CHIP\r"
#define CMD_REMAP       "REMP FLSH\r"
#define CMD_RESET       "REST\r"

//----------------------------------------------------------------------------

extern int flash_target(int, char*, int, int);

//----------------------------------------------------------------------------
// end:flash.h
//

There was also a serial communication interface...

//----------------------------------------------------------------------------
// file:serial.c
//
//  Serial USBtty interface utilities.
//
// Copyright (c) 2017 - Dysfunctional Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//
//----------------------------------------------------------------------------

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <memory.h>
#include <errno.h>

#include <report.h>

//----------------------------------------------------------------------------

#define MAXRXBUF 128
#define MAXTXBUF 128
#define MAXLINEBUF 127

struct _comm_channel {
  int comm;
  int flags;
  int rxmax;
  int rxopnt;
  int rxipnt;
  int abort;
  struct termios tty;
  char rxbuf[MAXRXBUF + 1];
  char txbuf[MAXTXBUF + 1];
  char linebuf[MAXLINEBUF + 1];
};

struct _baudrate {
  int rate;
  int code;
};

static struct _baudrate serialrate[] = {
  {   1200, B1200 },
  {   2400, B2400 },
  {   4800, B4800 },
  {   9600, B9600 },
  {  19200, B19200 },
  {  38400, B38400 },
  {  57600, B57600 },
  { 115200, B115200 },
  { 230400, B230400 }
};

static const unsigned int size_serialrate = sizeof(serialrate) / sizeof(struct _baudrate);

//----------------------------------------------------------------------------

static int serial_get_baud(int rate)
{
  int i;
  int rtn = 0;

  for(i = 0; i < size_serialrate; i++)
  {
    if(serialrate[i].rate == rate)
    {
      rtn = serialrate[i].code;
    }
  }
  return rtn;
}

//----------------------------------------------------------------------------

static int serial_get_tty(int comm, struct termios *tty)
{
  int rtn;

  memset(tty, 0, sizeof(struct termios));
  if((rtn = tcgetattr(comm, tty)) == -1)
  {
    report_error("serial_get_tty() failed - ");
  }
  return rtn;
}

//----------------------------------------------------------------------------

static int serial_set_tty_default(int comm, int rate, int fmt)
{
  int rtn;
  struct termios tty;

  if((rtn = serial_get_tty(comm, &tty)) != -1)
  {
    cfsetospeed(&tty, rate);
    cfsetispeed(&tty, rate);

    tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars
    tty.c_iflag &= ~IGNBRK;                     // disable break processing
    tty.c_lflag = 0;                            // no signaling chars, no echo,
                                                // no canonical processing
    tty.c_oflag = 0;                            // no remapping, no delays
    tty.c_cc[VMIN]  = 0;                        // read doesn't block
    tty.c_cc[VTIME] = 5;                        // 0.5 seconds read timeout
    tty.c_iflag &= ~(IXON | IXOFF | IXANY | IGNPAR); // shut off xon/xoff ctrl
    tty.c_iflag |= ICRNL;                       // Translate CR->NL on input.
    tty.c_cflag |= (CLOCAL | CREAD);            // ignore modem, enable reading,
    tty.c_cflag &= ~(PARENB | PARODD);          // shut off parity
    tty.c_cflag |= fmt;
    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CRTSCTS;
    if((rtn = tcsetattr(comm, TCSANOW, &tty)) == -1)
    {
      report_error("serial_set_tty_default() failed - ");
    }
  }
  return rtn;
}

//----------------------------------------------------------------------------

void serial_set_blocking(struct _comm_channel *comm, int blocking)
{
  struct termios tty;
  if(serial_get_tty(comm->comm, &tty) != -1)
  {
    tty.c_cc[VMIN]  = blocking ? 1 : 0;
    tty.c_cc[VTIME] = 0; // 0.5 seconds read timeout

    if(tcsetattr(comm->comm, TCSANOW, &tty) == -1)
    {
      report_error("serial_set_blocking(%i) failed - ", blocking);
    }
  }
  else
  {
    report(dbglevl_debug, "serial_set_blocking():serial_get_tty(%i) failed.\n", comm->comm);
  }
}

//----------------------------------------------------------------------------

void serial_abort(struct _comm_channel *comm)
{
  comm->abort = 1;
}

//----------------------------------------------------------------------------

int serial_peek_char(struct _comm_channel *comm)
{
  char c = 0;
  int rtn = read(comm->comm, &c, 1);
  if(rtn == 1)
  {
      rtn = c & 0X7F;
  }
  else
  {
    rtn = -1;
  }
  return rtn;
}

//----------------------------------------------------------------------------
// Support for XMODEM transfer.

int _inbyte(struct _comm_channel *comm, unsigned short timeout)
{
  int rtn = -1;
  unsigned short i;
  unsigned char c;

  for(i = 0; i < timeout; i++)
  {
    rtn = read(comm->comm, &c, 1);
    if(rtn == 1)
    {
      rtn = c;
      break;
    }
    else
    {
      rtn = -1;
    }
    usleep(100);
  }
  return rtn;
}

//----------------------------------------------------------------------------

int serial_get_char(struct _comm_channel *comm)
{
  unsigned char c = 0;
  int s;

  while((s = read(comm->comm, &c, 1)) == -1)
  {
    //if(s == -1)
    {
      if((errno != EAGAIN) || (comm->abort))
      {
        report(dbglevl_debug, "serial_get_char():read(1) failed - abort=%i errno=%i.\n", comm->abort, errno);
        break;
      }
      sleep(0);
    }
  }
  s = c;
  return s;
}

//----------------------------------------------------------------------------

int serial_put_char(struct _comm_channel *comm, char c)
{
  int s;

  while((s = write(comm->comm, &c, 1)) == -1)
  {
    if((errno != EAGAIN) || (comm->abort))
    {
      report(dbglevl_debug, "serial_get_char():read(1) failed - abort=%i errno=%i.\n", comm->abort, errno);
      break;
    }
    sleep(0);
  }
  return s;
}

//----------------------------------------------------------------------------
// Support for XMODEM transfer.

int _outbyte(struct _comm_channel *comm, int c)
{
  return serial_put_char(comm, c);
}

//----------------------------------------------------------------------------

const char *serial_get_line(struct _comm_channel *comm)
{
  int s;

  memset(comm->linebuf, 0, MAXLINEBUF);
  s = read(comm->comm, &comm->linebuf[0], 1);
  for(comm->rxipnt = 0; comm->rxipnt < MAXLINEBUF; s = read(comm->comm, &comm->linebuf[comm->rxipnt], 1))
  {
    if(s == -1)
    {
      if((errno != EAGAIN) || (comm->abort))
      {
        report(dbglevl_debug, "serial_get_line():read(1) failed - abort=%i errno=%i.\n", comm->abort, errno);
        break;
      }
      sleep(0);
    }    
    else if(comm->abort)
    {
      break;
    }
    else if(s == 1)
    {
      if((comm->linebuf[comm->rxipnt] == '\n'))
      {
        comm->linebuf[comm->rxipnt] = '\0';
        break;
      }
      else if(comm->linebuf[comm->rxipnt] != '\r')
      {
        comm->rxipnt++;
      }
    }
    else
    {
      sleep(0);
    }
  }
  return comm->linebuf;
}

//----------------------------------------------------------------------------

int serial_put_line(struct _comm_channel *comm, const char *data)
{
  int rtn = write(comm->comm, data, strlen(data));
  if(rtn == -1)
  {
    report_error("error : serial_put_line():write(%s) failed - ");
  }
  return rtn;
}

//----------------------------------------------------------------------------

struct _comm_channel *serial_open(const char *dev, int rate, int fmt)
{
  struct _comm_channel *comm = malloc(sizeof(struct _comm_channel));
  if(comm)
  {
    memset(comm, 0, sizeof(struct _comm_channel));
    comm->comm = open(dev, O_RDWR);
    if(comm->comm != -1)
    {
      if(serial_set_tty_default(comm->comm, serial_get_baud(rate), fmt) != -1)
      {
        usleep(200000); // Kernel bug? - improper flush without delay.
        if(tcflush(comm->comm, TCIFLUSH) == -1)
        {
          report_error("serial_open(%s):tcflush() failed - ", dev);
        }
      }
    }
    else
    {
      free(comm);
      comm = 0;
      report_error("serial_open(%s) failed - ", dev ? dev : "NULL");
    }
  }
  return comm;
}

//----------------------------------------------------------------------------

void serial_close(struct _comm_channel *comm)
{
  if(comm)
  {
    if(comm->comm != -1)
    {
      tcflush(comm->comm, TCOFLUSH);
      close(comm->comm);
    }
    free(comm);
  }
}

//----------------------------------------------------------------------------
// end:serial.c
//

And the Header file...

//-----------------------------------------------------------------------------
// file:serial.h
//
//  Serial USBtty interface utilities.
//
// Copyright (c) 2017 - Dysfunctional Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//-----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//
//-----------------------------------------------------------------------------

typedef struct _comm_channel comm_channel, *pcomm_channel;

extern pcomm_channel serial_open(const char*, int, int);
extern void serial_set_blocking(pcomm_channel, int);
extern void serial_abort(pcomm_channel);
extern int serial_peek_char(pcomm_channel);
extern int serial_get_char(pcomm_channel);
extern int serial_put_char(pcomm_channel, char);
extern const char *serial_get_line(pcomm_channel);
extern int serial_put_line(pcomm_channel, const char*);
extern void serial_close(pcomm_channel);

// XMODEM support.

extern int _inbyte(pcomm_channel, unsigned short);
extern int _outbyte(pcomm_channel, int);

//-----------------------------------------------------------------------------
// end:serial.h
//

There was additionally two other files that I did not write that provided the actual XMODEM protocol interface for the programming data transfer. I am not going to post the actual code here however here are two links to the files I used. They are Copyright Georges Menie (www.menie.org) and the code worked great. Thanks Georges.
source code for xmodem.c
source code for crc16.c

And to tie the whole program together...

//----------------------------------------------------------------------------
// file:main.c
//
//  Utility to flash (program) Smegware W7500P SmegNet eval board.
//
// Copyright (c) 2017 - Dysfunctional Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//
//----------------------------------------------------------------------------

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#include <report.h>

#include "flash.h"

//----------------------------------------------------------------------------

#define MAXDEVICE   128
#define MAXLOGFILE  128
#define MAXINFILE   256

static int baudrate = 9600;
static int format = 0;
static char serialdev[MAXDEVICE + 1] = "/dev/ttyUSB0";
static char logfile[MAXLOGFILE + 1] = "";
static char infile[MAXINFILE + 1] = "";

//----------------------------------------------------------------------------

static void show_usage(void)
{
  report(dbglevl_none, "usage: ispw7500 -i tty -dmwvV [logfile] -r baud -f format filename.\n");
  report(dbglevl_none, "-i ttydevice     : tty device name (/dev/tty---).\n");
  report(dbglevl_none, "-d [logfilename] : debug level reporting.\n");
  report(dbglevl_none, "-m [logfilename] : message level reporting.\n");
  report(dbglevl_none, "-v [logfilename] : verbose level reporting.\n");
  report(dbglevl_none, "-V [logfilename] : extra verbose level reporting.\n");
  report(dbglevl_none, "-w [logfilename] : warning level reporting.\n");
  report(dbglevl_none, "-r rate          : tty baud rate.\n");
  report(dbglevl_none, "-f format        : serial bit/stop/parity settings.\n");
}

//----------------------------------------------------------------------------

static void show_options(void)
{
  enum report_level hmm = report_get_level();
  switch(hmm)
  {
    case dbglevl_msg:
      report(dbglevl_msg, "Debug level   : 'Message'.\n");
      break;

    case dbglevl_warn:
      report(dbglevl_msg, "Debug level   : 'Warn'.\n");
      break;

    case dbglevl_debug:
      report(dbglevl_msg, "Debug level   : 'Debug'.\n");
      break;

    case dbglevl_verbose:
      report(dbglevl_msg, "Debug level   : 'Verbose'.\n");
      break;

    case dbglevl_VERBOSE:
      report(dbglevl_msg, "Debug level   : 'Extra Verbose'.\n");
      break;

    case dbglevl_ERROR:
      report(dbglevl_msg, "Debug level   : 'Error'.\n");
      break;

    default:
      break;
  }
  report(dbglevl_msg, "serial device : %s\n", serialdev);
  report(dbglevl_msg, "log file      : %s\n", strlen(logfile) ? logfile : "stdout");
  report(dbglevl_msg, "baudrate      : %i\n", baudrate);
  report(dbglevl_msg, "format        : %i\n", format);
  report(dbglevl_msg, "input File    : %s\n", infile);
}

//----------------------------------------------------------------------------

int main(int argc, char **argv)
{
  int rtn = 0;
  int arg;
  FILE *log = 0;
  int fbin;

  report_init(stdout);
  for(arg = 1; arg < argc; arg++)
  {
    if(rtn)
    {
      // Command line parsing failure.
      break;
    }
    if(argv[arg][0] == '-')
    {
      switch(argv[arg][1])
      {
        case 'm':
	  // Messages.
	  report_set_level(dbglevl_msg);
	  if(((arg + 1) < argc) && (argv[arg + 1][0] != '-'))
	  {
	    strncpy(logfile, argv[arg + 1], MAXLOGFILE - 1);
	    logfile[MAXLOGFILE - 1] = '\0';
            if(!strlen(logfile))
            {
              rtn = 1;
            }
            arg++;
	  }
	  break;

        case 'D':
        case 'd':
	  // Debug messages.
	  report_set_level(dbglevl_debug);
	  if(((arg + 1) < argc) && (argv[arg + 1][0] != '-'))
	  {
	    strncpy(logfile, argv[arg + 1], MAXLOGFILE - 1);
	    logfile[MAXLOGFILE - 1] = '\0';
            if(!strlen(logfile))
            {
              rtn = 1;
            }
            arg++;
	  }
	  break;

        case 'w':
	  // Debug messages.
	  report_set_level(dbglevl_warn);
	  if(((arg + 1) < argc) && (argv[arg + 1][0] != '-'))
	  {
	    strncpy(logfile, argv[arg + 1], MAXLOGFILE - 1);
	    logfile[MAXLOGFILE - 1] = '\0';
            if(!strlen(logfile))
            {
              rtn = 1;
            }
            arg++;
	  }
	  break;

        case 'v':
	  // Verbose debug reporting.
	  report_set_level(dbglevl_verbose);
	  if(((arg + 1) < argc) && (argv[arg + 1][0] != '-'))
	  {
	    strncpy(logfile, argv[arg + 1], MAXLOGFILE - 1);
	    logfile[MAXLOGFILE - 1] = '\0';
            if(!strlen(logfile))
            {
              rtn = 1;
            }
            arg++;
	  }
	  break;

        case 'i':
	  // Set interface device.
	  if(((arg + 1) < argc) && (argv[arg + 1][0] != '-'))
	  {
	    strncpy(serialdev, argv[arg + 1], MAXDEVICE - 1);
	    serialdev[MAXDEVICE - 1] = '\0';
            if(!strlen(serialdev))
            {
              rtn = 1;
            }
            arg++;
	  }
	  break;

        case 'f':
	  // Set bits/stop/parity format.
          if(((arg + 1) < argc) && (argv[arg + 1][0] != '-'))
          {
	    if(!sscanf(argv[arg + 1], "%i", &format))
            {
              // Invalid argument.
              rtn = 1;
            }
          }
          else
          {
            // Mandatory operand omitted.
            rtn = 1;
          }
	  break;

        case 'r':
	  // Set baudrate.
          if(((arg + 1) < argc) && (argv[arg + 1][0] != '-'))
          {
	    if(!sscanf(argv[arg + 1], "%i", &baudrate))
            {
              // Invalid argument.
              rtn = 1;
            }
          }
          else
          {
            // Mandatory operand omitted.
            rtn = 1;
          }
	  break;

        default:
          // Parse error.
          rtn = 1;
	  break;
      }
    }
    else
    {
      // Input File
      strncpy(infile, argv[arg], MAXINFILE - 1);
      serialdev[MAXINFILE] = '\0';
      if(!strlen(infile))
      {
        rtn = 1;
      }
    }
  }
  if(rtn)
  {
    show_options();
    show_usage();
  }
  else
  {
    if(strlen(logfile) != 0)
    {
      log = fopen(logfile, "w");
      if(log > 0)
      {
        // FIXME: report_init() resets the debug level.
        arg = report_get_level();
        report_init(log);
        report_set_level(arg);
      }
    }
    show_options();
    fbin = open(infile, O_RDONLY);
    if(fbin != -1)
    {
      report(dbglevl_debug, "Opened file %s.\n", infile);
      rtn = flash_target(fbin, serialdev, baudrate, format);
      close(fbin);
      report(dbglevl_debug, "Closed file %s.\n", infile);
    }
    else
    {
      report_error("error : open(%s) failed - ", infile);
    }
  }
  return rtn;
}

//----------------------------------------------------------------------------
// end:main.c
//

The beauty of the flash program without a UI, for me anyway, is that I can call it from the make system which making it more suitable for my development system.

Getting The GNU Compiler Toolchain To Work.

This was one of the more difficult aspects of the development. It has been many years since I have used a development system outside of an integrated environment. Yeah! I have done lot's of Arduino development but I used the standard Arduino system. Which I never Liked. But worked well enough for the simple projects I used it for in spite of it's shortcomings. The first problem I had was getting the linker to work the way I wanted. It has been a long time since I had to write a linker script and there were a few GNU specific requirements needed to be discovered in order to get the C-std libs to behave. Especially the stdio which includes all the string manipulation functions and malloc as well. Code I'm heavilly dependent on.

This is finally what I came up with...

/*----------------------------------------------------------------------------
 * file: w7500p.ld
 *
 * W7500P Cortex-M0 Demonstration Linker Script.
 *
 * Copyright (c) 2017 - Dysfunctional Farms A.K.A. www.smegware.com
 *
 *  All Smegware software is free you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This software is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *----------------------------------------------------------------------------
 *
 * History...
 *
 *   $Source$
 *   $Author$
 * $Revision$
 *
 * $Log$
 *----------------------------------------------------------------------------
 */

ENTRY(__reset_handler);
MEMORY
{
    FLASH(rx) : ORIGIN = 0x0, LENGTH = 0x20000        /* 128K */
    RAM(rw)   : ORIGIN = 0x20000000, LENGTH = 0x4000  /* 16K */
}

SECTIONS
{
    . = ORIGIN(FLASH);
    .text :
    {
        KEEP(*(.isr_vectors));
        . = ALIGN(4);
        __vec_end__ = .;
        *(.text);
        . = ALIGN(4);
        __end_text__ = .;
    } > FLASH

    .rodata :
    {
        . = ALIGN(4);
        __rodata_start__ = .;
        *(.rodata);
        *(.rodata.*);
        . = ALIGN(4);
        __rodata_end__ = .;
    } > FLASH

    .ARM.extab : 
    {
        *(.ARM.extab* .gnu.linkonce.armextab.*)
    } > FLASH

    __exidx_start = .;
    .ARM.exidx :
    {
         *(.ARM.exidx* .gnu.linkonce.armexidx.*)
    } > FLASH
	__exidx_end = .;

	__etext = .;
	__init_data__ = __etext;
		
    .data :
    {
        __data_start__ = .;
        . = ALIGN(4);
        *(.data);
        *(.data.*);
        . = ALIGN(4);
    } > RAM AT > FLASH

    .bss :
    {
        __data_end__ = .;
        __bss_start__ = .;
        . = ALIGN(4);
        *(.bss);
        . = ALIGN(4);
        __bss_end__ = .;
    } > RAM

    __data_size__ = (__data_end__ - __data_start__);

    .heap :
    {
        . = ALIGN(4);
        __heap_start__ = .;
        . = (ORIGIN(RAM) + LENGTH(RAM)) - 0X100;
        __heap_end__ = .;
    } > RAM

    __heap_size__ = SIZEOF(.heap);
    __ram_length__ = LENGTH(RAM);

    .stack :
    {
        __stack_end__ = .;
        __stack_start__ = (ORIGIN(RAM) + LENGTH(RAM));
    } > RAM

}

__end__ = __stack_start__;
PROVIDE(_end = .);

/*----------------------------------------------------------------------------
 * end: w7500p.ld
 */

Starting To Write Code.

With the hardware assembled and the toolchain figured out I was finally able to begin code development. I chose Not to use any of the pre-canned startup code so I began to write my own. This included the vector table, processor initialization, default interrupt handlers and runtime environment setup. Not to bad really but I always write low-level code as assembly code. This required getting the assembler to work and a little bit of research on how the ARM processor works. There's a weird thing about the Cortex-M0 and it's instruction set and the way the processor switches states, sometimes, when calling subroutines causing me a little grief until I figured out how to specify 'thumb functions' correctly at the assembler level.

With the Compiler and Assembler and Programmer working I was good to go. Not to difficult really but a little challenging to say the least.

I created a vector table and default interrupt vectors which are defined using the '.weak' attribute so they can be overridden by official handlers in actual application code. The use of the default vectors is primarily to disable an unexpected interrupt that might happen during reset if say the WatchDog timer causes an interrupt and thus a reset during runtime. Finally fault handlers end up at an endless loop that pushes a character, slowly, to the default UART interface allowing a debugger to interrupt and poke around trying to find the cause of the fault.

@-----------------------------------------------------------------------------
@ file: vectors.s
@
@ W7500P Vector map and default interrupt handlers.
@
@ Copyright (c) 2017 - Dysfunctional Farms A.K.A. www.smegware.com
@
@  All Smegware software is free you can redistribute it and/or modify
@  it under the terms of the GNU General Public License as published by
@  the Free Software Foundation either version 2 of the License, or
@  (at your option) any later version.
@
@  This software is distributed in the hope that it will be useful,
@  but WITHOUT ANY WARRANTY without even the implied warranty of
@  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
@  GNU General Public License for more details.
@
@-----------------------------------------------------------------------------
@
@ History...
@
@   $Source$
@   $Author$
@ $Revision$
@
@ $Log$
@------------------------------------------------------------------------------
@ Clock Reset Generator register definitions.
.equ _OSCPD,    0X00000000  @ Oscillator Normal Operation.
.equ _PLLPD,    0X00000001  @ PLL Normal Operation.
.equ _M_N_OD,   0X000C0200  @ 8MHz crystal * 12 / 2 = 48Mhz?
.equ _PLLOEN,   0X00000000  @ Disable PLL Clock Output.
.equ _PLLBP,    0X00000000  @ PLL Normal Operation.
.equ _PLLIS,    0x00000001  @ PLL Uses External Oscillator.
.equ _FCLKSRC,  0X00000000  @ FCLK Uses PLL Output.
.equ _FCLKPRE,  0X00000000  @ FCLK Prescale 1/1.
.equ _SSPCLK,   0X00000001  @ SSPCLK Uses PLL Clock Output.
.equ _SSPCP,    0X00000000  @ SSPCLK Prescale 1/1.
.equ _ADCCLK,   0X00000001  @ ADC Uses PLL Clock Output.
.equ _ADCCP,    0X00000000  @ ADC Prescale 1/1.
.equ _TIM0CLK,  0X00000001  @ TIMER0 Uses PLL Clock Output.
.equ _TIM0CP,   0X00000000  @ TIMER0 Prescale 1/1.
.equ _TIM1CLK,  0X00000001  @ TIMER1 Uses PLL Clock Output.
.equ _TIM1CP,   0X00000000  @ TIMER1 Prescale 1/1.
.equ _PWM0CLK,  0X00000001  @ PWM0 Uses PLL Clock Output.
.equ _PWM0CP,   0X00000000  @ PWM0 Uses Prescale 1/1.
.equ _PWM1CLK,  0X00000001  @ PWM1 Uses PLL Clock Output.
.equ _PWM1CP,   0X00000000  @ PWM1 Uses Prescale 1/1.
.equ _PWM2CLK,  0X00000001  @ PWM2 Uses PLL Clock Output.
.equ _PWM2CP,   0X00000000  @ PWM2 Uses Prescale 1/1.
.equ _PWM3CLK,  0X00000001  @ PWM3 Uses PLL Clock Output.
.equ _PWM3CP,   0X00000000  @ PWM3 Uses Prescale 1/1.
.equ _PWM4CLK,  0X00000001  @ PWM4 Uses PLL Clock Output.
.equ _PWM4CP,   0X00000000  @ PWM4 Uses Prescale 1/1.
.equ _PWM5CLK,  0X00000001  @ PWM5 Uses PLL Clock Output.
.equ _PWM5CP,   0X00000000  @ PWM5 Uses Prescale 1/1.
.equ _PWM6CLK,  0X00000001  @ PWM6 Uses PLL Clock Output.
.equ _PWM6CP,   0X00000000  @ PWM6 Uses Prescale 1/1.
.equ _PWM7CLK,  0X00000001  @ PWM7 Uses PLL Clock Output.
.equ _PWM7CP,   0X00000000  @ PWM7 Uses Prescale 1/1.
.equ _WDCLK,    0X00000001  @ WatchDog Uses PLL Clock Output.
.equ _WDCP,     0X00000000  @ WatchDog Uses Prescale 1/1.

.equ _WDHS,     0X00000001  @ WDOGCLK Uses PLL Clock Output.
.equ _WDPRE,    0X00000000  @ WDOGCLK Prescale 1/1.
.equ _UCSS,     0x00000001  @ UARTCLK Uses PLL Clock Output.
.equ _UCP,      0X00000000  @ UARTCLK Prescale 1/1.

.equ _CRG,      0X41001000  @ Clock Reset Generator register group base address.
.equ _OSCPDRV,  0X0000      @ Oscillator Power Down Register offset.
.equ _PLLPDRV,  0X0010      @ PLL Power Down Register offset.
.equ _PLLFCRV,  0X0014      @ PLL Frequency Calculating Register offset.
.equ _PLLOERV,  0X0018      @ PLL Output Enable Register offset..
.equ _PLLBPRV,  0X001C      @ PLL Bypass Clock Register offset.
.equ _PLLIFSRV, 0X0020      @ PLL Input Clock Source Select Register offset.
.equ _FCLKSSRV, 0X0030      @ FCLK Source Select Register offset.
.equ _FCLKPVSRV,0X0034      @ FCLK Prescale Value Select Register offset.
.equ _SSPCSSRV, 0X0040      @ SSP CLK Source Select Register offset.
.equ _SSPCPVSRV,0X0044      @ SSP CLK Prescale Value Select Register offset.
.equ _ADCCSSRV, 0X0060      @ ADC CLK Source Select Register offset.
.equ _ADCCPVSRV,0X0064      @ ADC CLK Prescale Value Select Register offset.
.equ _TC0CSSRV, 0X0070      @ TIMER0 CLK Source Select Register offset.
.equ _TC0CPVSRV,0X0074      @ TIMER0 CLK Prescale Value Select Register offset.
.equ _TC1CSSRV, 0X0080      @ TIMER1 CLK Source Select Register offset.
.equ _TC1CPVSRV,0X0084      @ TIMER1 CLK Prescale Value Select Register offset.
.equ _PWM0SSRV, 0X00B0      @ PWM0 CLK Source Select Register offset.
.equ _PWM0PVSRV,0X00B4      @ PWM0 CLK Prescale Value Select Register offset.
.equ _PWM1SSRV, 0X00C0      @ PWM1 CLK Source Select Register offset.
.equ _PWM1PVSRV,0X00C4      @ PWM1 CLK Prescale Value Select Register offset.
.equ _PWM2SSRV, 0X00D0      @ PWM2 CLK Source Select Register offset.
.equ _PWM2PVSRV,0X00D4      @ PWM2 CLK Prescale Value Select Register offset.
.equ _PWM3SSRV, 0X00E0      @ PWM3 CLK Source Select Register offset.
.equ _PWM3PVSRV,0X00E4      @ PWM3 CLK Prescale Value Select Register offset.
.equ _PWM4SSRV, 0X00F0      @ PWM4 CLK Source Select Register offset.
.equ _PWM4PVSRV,0X00F4      @ PWM4 CLK Prescale Value Select Register offset.
.equ _PWM5SSRV, 0X0100      @ PWM5 CLK Source Select Register offset.
.equ _PWM5PVSRV,0X0104      @ PWM5 CLK Prescale Value Select Register offset.
.equ _PWM6SSRV, 0X0110      @ PWM6 CLK Source Select Register offset.
.equ _PWM6PVSRV,0X0114      @ PWM6 CLK Prescale Value Select Register offset.
.equ _PWM7SSRV, 0X0120      @ PWM7 CLK Source Select Register offset.
.equ _PWM7PVSRV,0X0124      @ PWM7 CLK Prescale Value Select Register offset.
.equ _WDCSSRV,  0X0140      @ WatchDog CLK Source Select Register offset.
.equ _WDCPVSRV, 0X0144      @ WatchDog CLK Prescale Value Select Register offset.
.equ _UARTSSRV, 0X0150      @ UART CLK Source Select Register offset.
.equ _UARTPVSRV,0X0154      @ UART CLK Prescale Value Select Register offset.

@-----------------------------------------------------------------------------
@ WatchDog register definitions.
.equ _WDOG,     0X40000000  @ WatchDog register group base address.
.equ _WDLVRV,   0X0000      @ WatchDog Load Register offset.
.equ _WDCVRV,   0X0004      @ WatchDog Value Register offset.
.equ _WDCRV,    0X0008      @ WatchDog Control Register offset.
.equ _WDICRV,   0X000C      @ WatchDog Interrupt Clear Register offset.
.equ _WDRISRV,  0X0010      @ WatchDog Raw Interrupt Status Register offset.
.equ _WDMISRV,  0X0014      @ WatchDog Masked Interrupt Status Register offset.
.equ _WDLOCKV,  0X0C00      @ WatchDog Lock Register offset.

.equ _WDIEN,    0B00000001  @ WatchDog Interrupt Enable mask.
.equ _WDREN,    0B00000010  @ WatchDog Reset Enable mask.
.equ _WDWIC,    0B00000001  @ WatchDog Interrupt Clear mask.
.equ _WDRIS,    0B00000001  @ WatchDog Raw Interrupt Status flag.
.equ _WDMIS,    0B00000001  @ WatchDog Masked Interrupt Status flag.
.equ _WDWES,    0B00000001  @ WatchDog Write Enable Status flag.

.equ _WDDIS,    0X0000      @ WatchDog disable value.
.equ _WDTIMEOUT,0XFFFFFFFF  @ WatchDog timeout value.

@-----------------------------------------------------------------------------
@ SysTick register definitions.
.equ _SYSTICK,  0XE000E000  @ SysTick register group base address.
.equ _STCSRV,   0X0010      @ Systick Control snd Status Register offset.
.equ _STRVRV,   0X0014      @ SysTick Reload Value Register offset.
.equ _STCVRV,   0X0018      @ SysTick Current Value Register offset.

.equ _TICKEN,   0B00000001  @ SysTick CSR enable bit.
.equ _TICKINT,  0B00000010  @ SysTick CSR interrupt bit.
.equ _CNTFLAG,  0X00010000  @ SysTick CSR count flag (overflow).
.equ _TICKCLK,  0B00000100  @ SysTick CSR clock source bit.
.equ _TICK1MS,  (SYSTEMCLK / 1000)  @ SysTick 1ms value.
.equ _TICKINIT, (_TICKCLK | _TICKINT | _TICKEN) @ SysTick CSR init clock | interrupt | enable.

@-----------------------------------------------------------------------------
@ USART register definitions.
.equ _UART0,    0X4000C000  @ UART0 Register group base address.
.equ _UART1,    0X4000D000  @ UART1 Register group base address.
.equ _UARTCRV,  0X0030      @ UART Control Register offset.
.equ _UARTICRV, 0X0044      @ UART Interrupt Clear Register offset.

.equ _UARTDIS,  0X0000      @ UART Disable value.

@-----------------------------------------------------------------------------
@ UART2 register definitions.
.equ _UART2,    0X40006000  @ UART2 register group base address.
.equ _UART2DRV, 0X0000      @ UART2 Data Register offset.
.equ _UART2CRV, 0X0008      @ UART2 Control Register offset.
.equ _UART2ICRV,0X000C      @ UART2 Interrupt Status/Clear Register offset.
.equ _UART2BDRV,0X0010      @ UART2 BAUD Rate Divisor Register offset.

.equ _RXE,      0B00000010  @ Receive Enable mask.
.equ _TXE,      0B00000001  @ Transmit Enable mask.
.equ _UART2DIS, 0X0000      @ UART2 disable value.
.equ _UART2EN,  (_RXE | _TXE) @ Receive and Transmit enable mask.
.equ _BAUD115200, 0X000001A0  @ 115200 BAUD at 4800000MHz system clock.

@-----------------------------------------------------------------------------
@ I2C register definitions.
.equ _I2C0,     0X40008000  @ I2C0 register group base address.
.equ _I2C1,     0X40009000  @ I2C1 register group base address.
.equ _I2CCRV,   0X0004      @ I2C Control Register Offset.
.equ _I2CICRV,  0X0024      @ I2C Interrupt Clear Register offset.

.equ _I2CDIS,   0X0000      @ I2C disable value.

@-----------------------------------------------------------------------------
@ SSP register definitions.
.equ _SSP0,     0X4000A000  @ SSP0 register group base address.
.equ _SSP1,     0X4000B000  @ SSP1 register group base address.
.equ _SSPCR0V,  0X0000      @ SSP Control Register 0 offset.
.equ _SSPCR1V,  0X0004      @ SSP Control Register 1 offset.
.equ _SSPIMSCV, 0X0014      @ SSP Interrupt Mask or Clear Register offset.
.equ _SSPICRV,  0X0020      @ SSP Interrupt Control Register offset.
.equ _SSPDMACV, 0X0024      @ SSP DMA Control Register offset.

.equ _SSPDIS,   0X0000      @ SSP disable.

@-----------------------------------------------------------------------------
@ PWM register definitions.
.equ _PWM0,     0X40005000  @ PWM0 register group base address.
.equ _PWM1,     0X40005100  @ PWM1 register group base address.
.equ _PWM2,     0X40005200  @ PWM2 register group base address.
.equ _PWM3,     0X40005300  @ PWM3 register group base address.
.equ _PWM4,     0X40005400  @ PWM4 register group base address.
.equ _PWM5,     0X40005500  @ PWM5 register group base address.
.equ _PWM6,     0X40005600  @ PWM6 register group base address.
.equ _PWM7,     0X40005700  @ PWM7 register group base address.
.equ _PWMIERV,  0X0004      @ PWM Interrupt Enable Register offset.
.equ _PWMICRV,  0X0008      @ PWR Interrupt Clear Register offset.

.equ _PWMDIS,   0X0000      @ PWM disable.

@-----------------------------------------------------------------------------
@ GPIO register definitions.
.equ _GPIOA,    0X42000000  @ GPIOA register group base address.
.equ _GPIOB,    0X43000000  @ GPIOB register group base address.
.equ _GPIOC,    0X44000000  @ GPIOC register group base address.
.equ _GPIOOCRV, 0X0014      @ GPIO Output Enable Clear Register offset.
.equ _GPIOIDRV, 0X0024      @ GPIO Interrupt Disable Register offset.
.equ _GPIOICRV, 0X0038      @ GPIO Interrupt Clear Register offset.

.equ _GPIODIS,  0XFF7F      @ disable value.

@-----------------------------------------------------------------------------
@ DMA register definitions.
.equ _DMA,      0X41004000  @ DMA Controller register group base address.
.equ _DMACRV,   0X0004      @ DMA Configuration Register offset.
.equ _DMACDRV,  0X002C      @ DMA Channel Enable Clear Register offset.

.equ _DMACHCLR, 0B00111111  @ DMA Channel Enable Clear mask.
.equ _DMABECLR, 0B00000001  @ DMA Buss Error Clear mask.
.equ _DMADIS,   0X0000      @ DMA Disable value.

@-----------------------------------------------------------------------------
@ ADC register definitions.
.equ _ADC,      0X41000000  @ ADC register group base address.
.equ _ADCIERV,  0X0010      @ ADC Interrupt Enable Register offset.
.equ _ADCICRV,  0X001C      @ ADC Interrupt Clear Register offset.

.equ _ADCDIS,   0X0000      @ ADC disable.

@-----------------------------------------------------------------------------
@ TCP/IP Offload Engine register definitions.
.equ _TOEICR,   0X46002108  @ TOE Interrupt Clear Register address.

.equ _TOEDIS,   0X00F0      @ TCP/IP Offlead Engine interrupt disable value.

@-----------------------------------------------------------------------------
@-----------------------------------------------------------------------------
@ TCP/IP Offload Engine register definitions.
.equ _EXTINT,   0X41002000  @ External Interrupt register group base address.

.equ _EXTFIRST, 0X0200      @ First PAD register offset.
.equ _EXTLAST,  0X02D0      @ Last PAD register offset.
.equ _EXTDIS,   0X0000      @ EXTINT disable value.

@-----------------------------------------------------------------------------
.align
.thumb
.section .isr_vectors
__ISR_VECTORS:
    .long   __stack_start__         @ 0x00 : Initial stack pointer.
    .long   __reset_handler         @ 0x04 : Reset.
    .long   __watchdog_handler      @ 0x08 : NMI.
    .long   __hard_fault_handler    @ 0X0C : Hard Fault.
    .long   __undefined_handler     @ 0X10 : Undefined for W7500P.
    .long   __undefined_handler     @ 0X14 : Undefined for W7500P.
    .long   __undefined_handler     @ 0X18 : Undefined for W7500P.
    .long   __undefined_handler     @ 0X1C : Undefined for W7500P.
    .long   __undefined_handler     @ 0X20 : Undefined for W7500P.
    .long   __undefined_handler     @ 0X24 : Undefined for W7500P.
    .long   __undefined_handler     @ 0X28 : Undefined for W7500P.
    .long   __SV_call_handler       @ 0X2C : Undefined for W7500P.
    .long   __undefined_handler     @ 0X30 : Undefined for W7500P.
    .long   __undefined_handler     @ 0X34 : Undefined for W7500P.
    .long   __pend_SV_handler       @ 0X38 : Undefined for W7500P.
    .long   __sys_tick_handler      @ 0X3C : Undefined for W7500P.
    .long   __SSPO_handler          @ 0X40 : IRQ0  : SSP0.
    .long   __SSP1_handler          @ 0x44 : IRQ1  : SSP1.
    .long   __UART0_handler         @ 0X48 : IRQ2  : UART0.
    .long   __UART1_handler         @ 0X4C : IRQ3  : UART1.
    .long   __UART2_handler         @ 0X50 : IRQ4  : UART2.
    .long   __I2C0_handler          @ 0X54 : IRQ5  : I2C0.
    .long   __I2C1_handler          @ 0X58 : IRQ6  : I2C1.
    .long   __GPIO0_handler         @ 0X5C : IRQ7  : GPIO0.
    .long   __GPIO1_handler         @ 0X60 : IRQ8  : GPIO1.
    .long   __GPIO2_handler         @ 0X64 : IRQ9  : GPIO2.
    .long   __GPIO3_handler         @ 0X68 : IRQ10 : GPIO3.
    .long   __DMA_handler           @ 0X6C : IRQ11 : DMA.
    .long   __dualtimer0_handler    @ 0X70 : IRQ12 : Dual Timer 0.
    .long   __dualtimer1_handler    @ 0X74 : IRQ13 : Dual Timer 1.
    .long   __PWM0_handler          @ 0X78 : IRQ14 : PWM0.
    .long   __PWM1_handler          @ 0X7C : IRQ15 : PWM1.
    .long   __PWM2_handler          @ 0X80 : IRQ16 : PWM2.
    .long   __PWM3_handler          @ 0X84 : IRQ17 : PWM3.
    .long   __PWM4_handler          @ 0X88 : IRQ18 : PWM4.
    .long   __PWM5_handler          @ 0X8C : IRQ19 : PWM5.
    .long   __PWM6_handler          @ 0X90 : IRQ20 : PWM6.
    .long   __PWM7_handler          @ 0X94 : IRQ21 : PWM7.
    .long   __undefined_handler     @ 0X98 : Undefined for W7500P.
    .long   __ADC_handler           @ 0X9C : IRQ23 : ADC.
    .long   __TCPIP_handler         @ 0XA0 : IRQ24 : TCPIP.
    .long   __EXT_INT_handler       @ 0XA4 : IRQ25 : EXT_INT.
    .long   __undefined_handler     @ 0XA8 : Undefined for W7500P.
    .long   __undefined_handler     @ 0XAC : Undefined for W7500P.
    .long   __undefined_handler     @ 0XB0 : Undefined for W7500P.
    .long   __undefined_handler     @ 0XB4 : Undefined for W7500P.
    .long   __undefined_handler     @ 0XB8 : Undefined for W7500P.
    .long   __undefined_handler     @ 0XBC : Undefined for W7500P.

@------------------------------------------------------------------------------
@ Default Interrupt Service Routines (handlers).
.section .text
@-----------------------------------------------------------------------------
@ Reset Interrupt Handler.
.align
.thumb_func
.func   __reset_handler
.global __reset_handler             @ Reset.
__reset_handler:
__reset_default_handler:
@ Initialize clock sources.
    CPSID   i                       @ Disable Interrupts.
    LDR     R0,=(__stack_start__)   @ User mode Stack Pointer.
    MOV     SP,R0                   @ Initialize the stack.
    LDR     R2,=(_CRG)              @ Clock Reset Generator register group base.
    MOV     R1,#_OSCPD              @ Oscillator Reset Value.
    STR     R1,[R2,#_OSCPDRV]       @ Enable Oscillator.
    MOV     R1,#_PLLPD              @ PLL Reset value.
    STR     R1,[R2,#_PLLPDRV]       @ Enable PLL.
    LDR     R1,=(_M_N_OD)           @ Clock Frequency Control.
    STR     R1,[R2,#_PLLFCRV]       @ Set system clock operating frequency.
    MOV     R1,#_PLLOEN             @ PLL Output Enable.
    STR     R1,[R2,#_PLLOERV]       @ Disable Clock output.
    MOV     R1,#_PLLBP              @ PLL Bypass.
    STR     R1,[R2,#_PLLBPRV]       @ PLL as system clock source.
    MOV     R1,#_PLLIS              @ PLL input select.
    STR     R1,[R2,#_PLLIFSRV]      @ PLL use external oscillator.
    MOV     R1,#_FCLKSRC            @ FCLK source.
    STR     R1,[R2,#_FCLKSSRV]      @ FCLK uses output of PLL.
    MOV     R1,#_FCLKPRE            @ FCLK prescale value.
    STR     R1,[R2,#_FCLKPVSRV]     @ FCLK bypass prescale.
    MOV     R1,#_SSPCLK             @ SSP clock source.
    STR     R1,[R2,#_SSPCSSRV]      @ SSP uses PLL output.
    MOV     R1,#_SSPCP              @ SSP prescale value.
    STR     R1,[R2,#_SSPCPVSRV]     @ SSP bypass prescale.
    MOV     R1,#_ADCCLK             @ ADC clock source.
    STR     R1,[R2,#_ADCCSSRV]      @ ADC uses PLL output
    MOV     R1,#_ADCCP              @ ADC prescale value.
    STR     R1,[R2,#_ADCCPVSRV]     @ ADC bypass prescale.
    BL      __startup               @ Runtime setup.
    LDR     R0,=(_TICK1MS)          @ SysTick timer value.
    BL      systick_init            @ Initialize the system tick timer.
    CPSIE   i                       @ Enable interrupts : clear PRIMASK.
    BL      main                    @ Jump to application startup.
@ If main returns continue transmitting a character as a debug flag.
__main_fault:
    CPSID   i                       @ Disable Interrupts.
    LDR     R1,=(_UART2)            @ Base address for UART2 register set.
    MOV     R0,#_UART2DIS           @ Disable configuration.
    STR     R0,[R1,#_UART2CRV]      @ Disable UART2.
    STR     R0,[R1,#_UART2ICRV]     @ Clear any pending interrupts.
    LDR     R0,=(_BAUD115200)       @ 115200 BAUD at 48MHz.
    STR     R0,[R1,#_UART2BDRV]     @ Initialize BAUD rate.
@ Enable Receiver and Transmitter.
    MOV     R0,#_UART2EN            @ Enable Receiver/Transmitter No interrupts.
    STR     R0,[R1,#_UART2CRV]      @ Configure UART2 for operation.
.waithere:
    MOV     R0,#0X30
    STR     R0,[R1,#_UART2DRV]      @ Push data to transmitter.
    LDR     R0,=(SYSTEMCLK / 10)    @ Short delay.
.delay:
    SUB     R0,#1                   @ Countdown.
    BNE     .delay                  @ Delay.
    B       .waithere               @ Loop indefinitely.
.endfunc
@-----------------------------------------------------------------------------
@ Default WatchDog handler.
.align
.thumb_func
.func   __watchdog_handler
.global __watchdog_handler          @ NMI.
.weak   __watchdog_handler
__watchdog_handler:
__watchdog_default_handler:
    LDR     R1,=(_WDOG)             @ WatchDog register group base address.
    LDR     R0,=(_WDTIMEOUT)        @ Timeout value.
    STR     R0,[R1,#_WDLVRV]        @ Reset WatchDog timer.
    MOV     R0,#_WDDIS              @ Disable value.
    STR     R0,[R1,#_WDCRV]         @ Disable WatchDog.
    STR     R0,[R1,#_WDICRV]        @ Clear any pending interrupt.
    B       __reset_handler         @ Not really much to do here except restart.
.endfunc
@-----------------------------------------------------------------------------
@ Default Hard Fault handler.
.align
.thumb_func
.func   __hard_fault_handler
.global __hard_fault_handler        @ HardFault.
.weak   __hard_fault_handler
__hard_fault_handler:
__hard_fault_default_handler:
    B       __main_fault
.endfunc
@-----------------------------------------------------------------------------
@ Default Supervisor Call handler.
.align
.thumb_func
.func   __SV_call_handler
.global __SV_call_handler           @ Supervisor SVC.
.weak   __SV_call_handler
__SV_call_handler:
__SV_call_default_handler:
    B       __main_fault
.endfunc
@-----------------------------------------------------------------------------
@ Default Pendable Service handler.
.align
.thumb_func
.func   __pend_SV_handler
.global __pend_SV_handler           @ Pendable Service.
.weak   __pend_SV_handler
__pend_SV_handler:
__pend_SV_default_handler:
    B       __main_fault
.endfunc
@-----------------------------------------------------------------------------
@ Default: Disable SysTick interrupt.
.align
.thumb_func
.func   __sys_tick_handler
.global __sys_tick_handler          @ SysTick
.weak   __sys_tick_handler
__sys_tick_handler:
__sys_tick_default_handler:
    PUSH    {R0-R2,LR}              @ Save working registers.
    LDR     R2,=(_SYSTICK)          @ SysTick CSR register pointer.
    LDR     R1,[R2]                 @ SysTick current configuration.
    LDR     R0,=(~_TICKINT)         @ SysTick Interrupt Flag Mask.
    AND     R0,R1                   @ Negate interrupt enable bit.
    STR     R0,[R2]                 @ Disable SysTick interrupt.
    POP     {R0-R2,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __undefined_handler
.global __undefined_handler         @ Undefined Interrupt.
.weak   __undefined_handler
__undefined_handler:
__undefined_default_handler:
    B       __main_fault
.endfunc
@-----------------------------------------------------------------------------
@ Default: Disable SSP0 interrupt.
.align
.thumb_func
.func   __SSPO_handler
.global __SSPO_handler              @ IRQ0  : SSP0.
.weak   __SSP0_handler
__SSPO_handler:
__SSP0_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_SSP0)             @ SSP0 register group base address.
    MOV     R0,#_SSPDIS             @ SSP disable value.
    STR     R0,[R1,#_SSPICRV]       @ Disable SSP interrupts.
    STR     R0,[R1,#_SSPDMACV]      @ Disable SSP DMA.
    STR     R0,[R1,#_SSPIMSCV]      @ Mask all SSP interrupt sources.
    STR     R0,[R1,#_SSPCR0V]       @ Disble/Reset SSP.
    STR     R0,[R1,#_SSPCR1V]       @ Dsiable/Reset SSP.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __SSP1_handler
.global __SSP1_handler              @ IRQ1  : SSP1.
.weak   __SSP1_handler
__SSP1_handler:
__SSP1_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_SSP1)             @ SSP1 register group base address.
    MOV     R0,#_SSPDIS             @ SSP disable value.
    STR     R0,[R1,#_SSPICRV]       @ Disable SSP interrupts.
    STR     R0,[R1,#_SSPDMACV]      @ Disable SSP DMA.
    STR     R0,[R1,#_SSPIMSCV]      @ Mask all SSP interrupt sources.
    STR     R0,[R1,#_SSPCR0V]       @ Disble/Reset SSP.
    STR     R0,[R1,#_SSPCR1V]       @ Dsiable/Reset SSP.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __UART0_handler
.global __UART0_handler             @ IRQ2  : UART0.
.weak   __UART0_handler
__UART0_handler:
__UART0_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_UART0)            @ UART0 register group base address.
    MOV     R0,#_UARTDIS            @ Disable value.
    STR     R0,[R1,#_UARTCRV]       @ Disable UART0.
    STR     R0,[R1,#_UARTICRV]      @ Disable interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __UART1_handler
.global __UART1_handler             @ IRQ3  : UART1.
.weak   __UART1_handler
__UART1_handler:
__UART1_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_UART1)            @ UART1 register group base address.
    MOV     R0,#_UARTDIS            @ Disable value.
    STR     R0,[R1,#_UARTCRV]       @ Disable UART1.
    STR     R0,[R1,#_UARTICRV]      @ Disable interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
@ Default disable UART interrupts.
.align
.thumb_func
.func   __UART2_handler
.global __UART2_handler             @ IRQ4  : UART2.
.weak   __UART2_handler
__UART2_handler:
__UART2_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_UART2)            @ Point to UART2 base register.
    MOV     R0,#_UART2DIS           @ Disable value.
    STR     R0,[R1,#_UART2CRV]      @ Disable UART2.
    STR     R0,[R1,#_UART2ICRV]     @ Clear any pending interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __I2C0_handler
.global __I2C0_handler              @ IRQ5  : I2C0.
.weak   __ISC0_handler
__I2C0_handler:
__I2C0_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_I2C0)             @ I2C0 register group base address.
    MOV     R0,#_I2CDIS             @ Disable value.
    STR     R0,[R1,#_I2CCRV]        @ Disable I2C0.
    STR     R0,[R1,#_I2CICRV]       @ Clear any pending interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __I2C1_handler
.global __I2C1_handler              @ IRQ6  : I2C1.
.weak   __I2C1_handler
__I2C1_handler:
__I2C1_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_I2C1)             @ I2C1 register group base address.
    MOV     R0,#_I2CDIS             @ Disable value.
    STR     R0,[R1,#_I2CCRV]        @ Disable I2C0.
    STR     R0,[R1,#_I2CICRV]       @ Clear any pending interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __GPIO0_handler
.global __GPIO0_handler             @ IRQ7  : GPIO0.
.weak   __GPIO0_handler
__GPIO0_handler:
__GPIO0_default_handler:
    PUSH    {R0-R1,LR}              @ Save working regiters.
    LDR     R1,=(_GPIOA)            @ GPIOA register group base address.
    LDR     R0,=(_GPIODIS)          @ Disable value.
    STR     R0,[R1,#_GPIOOCRV]      @ Reset outputs to inputs.
    STR     R0,[R1,#_GPIOIDRV]      @ Disable interrupts.
    STR     R0,[R1,#_GPIOICRV]      @ Clear any pending interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __GPIO1_handler
.global __GPIO1_handler             @ IRQ8  : GPIO1.
.weak   __GPIO1_handler
__GPIO1_handler:
__GPIO1_default_handler:
    PUSH    {R0-R1,LR}              @ Save working regiters.
    LDR     R1,=(_GPIOB)            @ GPIOC register group base address.
    LDR     R0,=(_GPIODIS)          @ Disable value.
    STR     R0,[R1,#_GPIOOCRV]      @ Reset outputs to inputs.
    STR     R0,[R1,#_GPIOIDRV]      @ Disable interrupts.
    STR     R0,[R1,#_GPIOICRV]      @ Clear any pending interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __GPIO2_handler
.global __GPIO2_handler             @ IRQ9  : GPIO2.
.weak   __GPIO2_handler
__GPIO2_handler:
__GPIO2_default_handler:
    PUSH    {R0-R1,LR}              @ Save working regiters.
    LDR     R1,=(_GPIOC)            @ GPIOC register group base address.
    LDR     R0,=(_GPIODIS)          @ Disable value.
    STR     R0,[R1,#_GPIOOCRV]      @ Reset outputs to inputs.
    STR     R0,[R1,#_GPIOIDRV]      @ Disable interrupts.
    STR     R0,[R1,#_GPIOICRV]      @ Clear any pending interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __GPIO3_handler
.global __GPIO3_handler             @ IRQ10 : GPIO3.
.weak   __GPIO3_handler
__GPIO3_handler:
__GPIO3_default_handler:
    B       __main_fault
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __DMA_handler
.global __DMA_handler               @ IRQ11 : DMA.
.weak   __DMA_handler
__DMA_handler:
__DMA_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_DMA)              @ DMA Controller register group base address.
    MOV     R0,#_DMADIS             @ Disable value.
    STR     R0,[R1,#_DMACRV]        @ Disable DMA Controller.
    MOV     R0,#_DMACHCLR           @ Channel clear value.
    STR     R0,[R1,#_DMACDRV]       @ Disable all DMA channels.
    POP     {R0-R1,PC}              @ Restore registers and return.
    B       __reset_handler
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __dualtimer0_handler
.global __dualtimer0_handler        @ IRQ12 : Dual Timer 0.
.weak   __dualtimer0_handler
__dualtimer0_handler:
__dualtimer0_default_handler:
    B       __reset_handler
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __dualtimer1_handler
.global __dualtimer1_handler        @ IRQ13 : Dual Timer 1.
.weak   __dualtimer1_handler
__dualtimer1_handler:
__dualtimer1_default_handler:
    B       __reset_handler
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __PWM0_handler
.global __PWM0_handler              @ IRQ14 : PWM0.
.weak   __PWM0_handler
__PWM0_handler:
__PWM0_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_PWM0)             @ Point to PWM0 base register.
    MOV     R0,#_PWMDIS             @ PWM  disable value.
    STR     R0,[R1,#_PWMIERV]       @ Disable PWM interrupts.
    STR     R0,[R1,#_PWMICRV]       @ Clear pending PWM interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __PWM1_handler
.global __PWM1_handler              @ IRQ15 : PWM1.
.weak   __PWM1_handler
__PWM1_handler:
__PWM1_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_PWM1)             @ Point to PWM1 base register.
    MOV     R0,#_PWMDIS             @ PWM  disable value.
    STR     R0,[R1,#_PWMIERV]       @ Disable PWM interrupts.
    STR     R0,[R1,#_PWMICRV]       @ Clear pending PWM interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __PWM2_handler
.global __PWM2_handler              @ IRQ16 : PWM2.
.weak   __PWM2_handler
__PWM2_handler:
__PWM2_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_PWM2)             @ Point to PWM2 base register.
    MOV     R0,#_PWMDIS             @ PWM  disable value.
    STR     R0,[R1,#_PWMIERV]       @ Disable PWM interrupts.
    STR     R0,[R1,#_PWMICRV]       @ Clear pending PWM interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __PWM3_handler
.global __PWM3_handler              @ IRQ17 : PWM3.
.weak   __PWM3_handler
__PWM3_handler:
__PWM3_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_PWM3)             @ Point to PWM3 base register.
    MOV     R0,#_PWMDIS             @ PWM  disable value.
    STR     R0,[R1,#_PWMIERV]       @ Disable PWM interrupts.
    STR     R0,[R1,#_PWMICRV]       @ Clear pending PWM interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __PWM4_handler
.global __PWM4_handler              @ IRQ18 : PWM4.
.weak   __PWM4_handler
__PWM4_handler:
__PWM4_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_PWM4)             @ Point to PWM4 base register.
    MOV     R0,#_PWMDIS             @ PWM  disable value.
    STR     R0,[R1,#_PWMIERV]       @ Disable PWM interrupts.
    STR     R0,[R1,#_PWMICRV]       @ Clear pending PWM interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __PWM5_handler
.global __PWM5_handler              @ IRQ19 : PWM5.
.weak   __PWM5_handler
__PWM5_handler:
__PWM5_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_PWM5)             @ Point to PWM5 base register.
    MOV     R0,#_PWMDIS             @ PWM  disable value.
    STR     R0,[R1,#_PWMIERV]       @ Disable PWM interrupts.
    STR     R0,[R1,#_PWMICRV]       @ Clear pending PWM interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __PWM6_handler
.global __PWM6_handler              @ IRQ20 : PWM6.
.weak   __PWM6_handler
__PWM6_handler:
__PWM6_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_PWM6)             @ Point to PWM6 base register.
    MOV     R0,#_PWMDIS             @ PWM  disable value.
    STR     R0,[R1,#_PWMIERV]       @ Disable PWM interrupts.
    STR     R0,[R1,#_PWMICRV]       @ Clear pending PWM interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __PWM7_handler
.global __PWM7_handler              @ IRQ21 : PWM7.
.weak   __PWM7_handler
__PWM7_handler:
__PWM7_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_PWM7)             @ Point to PWM7 base register.
    MOV     R0,#_PWMDIS             @ PWM  disable value.
    STR     R0,[R1,#_PWMIERV]       @ Disable PWM interrupts.
    STR     R0,[R1,#_PWMICRV]       @ Clear pending PWM interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __ADC_handler
.global __ADC_handler               @ IRQ23 : ADC.
.weak   __ADC_handler
__ADC_handler:
__ADC_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_ADC)              @ Point to ADC base register.
    MOV     R0,#_ADCDIS             @ ADC disable value.
    STR     R0,[R1,#_ADCIERV]       @ Disable ADC interrupts.
    STR     R0,[R1,#_ADCICRV]       @ Clear pending ADC interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __TCPIP_handler
.global __TCPIP_handler             @ IRQ24 : TCPIP.
.weak   __TCPIP_handler
__TCPIP_handler:
__TCPIP_default_handler:
    PUSH    {R0-R1,LR}              @ Save working registers.
    LDR     R1,=(_TOEICR)           @ TCPIP Interrupt Clear Register address.
    MOV     R0,#_TOEDIS             @ Interrupt disable value.
    STR     R0,[R1]                 @ Disable TCPIP interrupts.
    POP     {R0-R1,PC}              @ Restore registers and return.
.endfunc
@-----------------------------------------------------------------------------
.align
.thumb_func
.func   __EXT_INT_handler
.global __EXT_INT_handler           @ IRQ25 : EXT_INT.
.weak   __EXT_INT_handler
__EXT_INT_handler:
__EXT_INT_default_handler:
    PUSH    {R0-R3,LR}              @ Save working registers.
    LDR     R3,=(_EXTINT)           @ EXTINT register group base address.
    LDR     R2,=(_EXTLAST)          @ EXTINT Last register offset.
    LDR     R1,=(_EXTFIRST)         @ EXTINT First register offset.
    MOV     R0,#_EXTDIS             @ EXTINT disable value.
.clr_ext:
    STR     R0,[R4,R1]              @ Disable interrupt.
    ADD     R1,#4                   @ Point to next register.
    CMP     R1,R2                   @ Check finished yet.
    BLE     .clr_ext                @ Loop.
    POP     {R0-R3,PC}              @ Restore registers and return.
.endfunc
@------------------------------------------------------------------------------
.align
.immediate_data:
.end
@------------------------------------------------------------------------------
@ end: vectors.s

It is important to note here the use of the assembler directive '.thumb_func' and it's cooresponding '.endfunc' directive. These tell the assembler to use odd addresses when referencing calling these functions in order to make sure the processor will execute thumb instructions. This is a wierd quirk of the CORTEX-M0 processor which uses the armv6 instruction model.

And the startup code that I came up with...

@-----------------------------------------------------------------------------
@ file: startup.s
@
@ W7500P Startup and initialization implementation.
@
@ Copyright (c) 2017 - Dysfunctional Farms A.K.A. www.smegware.com
@
@  All Smegware software is free you can redistribute it and/or modify
@  it under the terms of the GNU General Public License as published by
@  the Free Software Foundation either version 2 of the License, or
@  (at your option) any later version.
@
@  This software is distributed in the hope that it will be useful,
@  but WITHOUT ANY WARRANTY without even the implied warranty of
@  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
@  GNU General Public License for more details.
@
@-----------------------------------------------------------------------------
@
@ History...
@
@   $Source$
@   $Author$
@ $Revision$
@
@ $Log$
@------------------------------------------------------------------------------
.section .text
@-----------------------------------------------------------------------------
@ System startup and initialization.
.align
.thumb_func
.func   __startup
.global __startup                   @ Initialize memory and runtime.
__startup:
@ Set CRT initialized variables from FLASH.
@     LDR     R4,=(__rodata_start__)  @ Initialization data base address.
    LDR     R4,=(__init_data__)     @ Initialization data base address.
    LDR     R3,=(__data_start__)    @ Data section base address.
    LDR     R2,=(__data_size__)     @ Size of initialization chunk in bytes.
    MOV     R1,#0                   @ Index pointer.
    CMP     R1,R2                   @ Is there any data to initialize.
    BEQ     .init_bss               @ No: Continue and reset RAM.
.init_data:
    LDR     R0,[R4,R1]              @ Get variable init data from FLASH.
    STR     R0,[R3,R1]              @ Initialize variable in RAM.
    ADD     R1,#4                   @ Update index : 32-bit operation.
    CMP     R1,R2                   @ Check if init complete.
    BNE     .init_data              @ Loop.
@ Clear uninitialized RAM memory.
.init_bss:
    LDR     R2,=(__ram_length__)    @ Length of uninitialized data section.
    MOV     R0,#0                   @ Uninitialize to 0.
.clear_bss:
    STR     R0,[R3,R1]              @ Clear uninitialized data.
    ADD     R1,#4                   @ Update index : 32-bit operation.
    CMP     R1,R2                   @ Check if init complete.
    BLT     .clear_bss              @ Loop.
    BX      LR                      @ return to init.
.endfunc
@------------------------------------------------------------------------------
.align
.immediate_data:
.end
@------------------------------------------------------------------------------
@ end: startup.s

Finally... There is an odd thing about the W7500P and that is that the MAC address is not hard programmed into the chip so a MAC address has to be entered at runtime. This presents a small programming problem if you need to have multiple devices on the same subnet. To account for this I wrote an additional tool that is called by the make system which increments a counter, in a file, that is compiled into the executable by the linker. Using this method I can create a unique MAC address for every chip I program thus allowing for multiple devices on my network. Kind of a clever solution I thought. And... Since it compiles a constant into flash memory it defines a proper serial number for the device.

//-----------------------------------------------------------------------------
// serno.c
//
//  Modifies a serno in a file which is added to a MAC address making it
//   unique for each compile or flash programming operation.
//
// Copyright (c) 2017 - Dyefunctional Farms A.K.A. www.smegware.com
//
//  All Smegware software is free; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This software is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//-----------------------------------------------------------------------------
//
// History...
//
//   $Source$
//   $Author$
// $Revision$
//
// $Log$
//
//-----------------------------------------------------------------------------

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#include "report.h"

//-----------------------------------------------------------------------------

#define MAXFILE 256
#define SIZEBUFFER 130

static char serno[MAXFILE + 2] = "serno.c";

//-----------------------------------------------------------------------------

int main(int argc, char **argv)
{
  unsigned serial;
  char fbuf[SIZEBUFFER];

  report_init(stdout);
  report_set_level(dbglevl_none);
  if(argc > 1)
  {
    memset(serno, 0, MAXFILE);
    strncpy(serno, argv[1], MAXFILE - 1);
  }
  report(dbglevl_none, "Using serno file %s.\n", serno);
  int file = open(serno, O_RDWR);
  if(file)
  {
    memset(fbuf, 0, SIZEBUFFER);
    if(read(file, fbuf, SIZEBUFFER - 2) != -1)
    {
      serial = 0;
      sscanf(fbuf, "%*[^0-9]%u", &serial);
      serial += 1;
      report(dbglevl_none, "new serno = %u.\n", serial);
      close(file);
      file = open(serno, O_RDWR | O_TRUNC);
      if(file != -1)
      {
        sprintf(fbuf, "const unsigned __serno__ = %u;\n", serial);
        if(write(file, fbuf, strlen(fbuf)) == -1)
        {
          report_error("error : Unable to write new serno - ");
        }
        close(file);
      }
      else
      {
        report_error("error : Unable to reset serno file - ");
      }
    }
    else
    {
      report_error("error : read() failed - ");
    }
  }
  else
  {
    report_error("error : Unable to open file %s - ", serno);
  }
  return 0;
}

//-----------------------------------------------------------------------------
// end: serno.c
//

Conclusions.

I'd like to say that using the Wiznet W7500P was relatively easy but I can't. Aside from being a little buggy there is very little information available for it. No application notes and the documentation from WizNet is horrible. I have never seen such poorly written, incomplete, incorrect and sloppily translated documentation anywhere. Ever. The application support is terrible using only a Forum as a way of communicating with the manufacturer and most of the questions seem to be from Numpties with responses in broken English with an emphesis it seems on misunderstanding. Sadly the only persons using the Forum clearly have no idea what real support is and are not complaining much about it. I can only say that this product is going nowhere with this kind of support.

What really bothers me though is that there are known bugs with the internal PHY chip (IP101) which prevents it, sometimes, from linking at 100MBS. As Advertized. This is apparently not a new problem. It has been known for some time. Well... I started this project recently and was not notified by my supplier, Mouser, that the chip is buggy and may be on End Of Life status. Why? Because WizNet has not mentioned that fact. It may just be me but I find that sort of deceit very irresponsible. Really Wiznet? I may have just wasted a huge amount of time and blown my product plans because a freshman, fabless, wannabe, chip manufacturer with apparently a lousy engineering team has no idea what supporting a product is all about.

641