Suspended plotter for the skaperfest

Dependencies:   mbed HTTPServer EthernetNetIf FatFileSystemCpp

Files at this revision

API Documentation at this revision

Comitter:
rengro01
Date:
Mon Aug 22 10:24:23 2022 +0000
Commit message:
skaperfest

Changed in this revision

EthernetNetIf.lib Show annotated file Show diff for this revision Revisions of this file
FatFileSystem.lib Show annotated file Show diff for this revision Revisions of this file
HTTPServer.lib Show annotated file Show diff for this revision Revisions of this file
HomePageHandler.cpp Show annotated file Show diff for this revision Revisions of this file
HomePageHandler.hpp Show annotated file Show diff for this revision Revisions of this file
MSCFileSystem.cpp Show annotated file Show diff for this revision Revisions of this file
MSCFileSystem.h Show annotated file Show diff for this revision Revisions of this file
SuspendedPlotter.cpp Show annotated file Show diff for this revision Revisions of this file
SuspendedPlotter.h Show annotated file Show diff for this revision Revisions of this file
USBHostLite/usbhost_cpu.h Show annotated file Show diff for this revision Revisions of this file
USBHostLite/usbhost_err.h Show annotated file Show diff for this revision Revisions of this file
USBHostLite/usbhost_inc.h Show annotated file Show diff for this revision Revisions of this file
USBHostLite/usbhost_lpc17xx.cpp Show annotated file Show diff for this revision Revisions of this file
USBHostLite/usbhost_lpc17xx.h Show annotated file Show diff for this revision Revisions of this file
USBHostLite/usbhost_ms.cpp Show annotated file Show diff for this revision Revisions of this file
USBHostLite/usbhost_ms.h Show annotated file Show diff for this revision Revisions of this file
UploadHandler.cpp Show annotated file Show diff for this revision Revisions of this file
UploadHandler.hpp Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
nanosvg.h Show annotated file Show diff for this revision Revisions of this file
diff -r 000000000000 -r 602ff2b2d41c EthernetNetIf.lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/EthernetNetIf.lib	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/donatien/code/EthernetNetIf/#bc7df6da7589
diff -r 000000000000 -r 602ff2b2d41c FatFileSystem.lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/FatFileSystem.lib	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/igorsk/code/FatFileSystemCpp/#88f22c32a456
diff -r 000000000000 -r 602ff2b2d41c HTTPServer.lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HTTPServer.lib	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/donatien/code/HTTPServer/#d753966e4d97
diff -r 000000000000 -r 602ff2b2d41c HomePageHandler.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HomePageHandler.cpp	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,227 @@
+#include "HomePageHandler.hpp"
+#include "mbed.h"
+
+#define CHUNK_SIZE 128
+
+HomePageHandler::HomePageHandler(const char* rootPath, const char* path, TCPSocket* pTcpSocket)
+: HTTPRequestHandler(rootPath, path, pTcpSocket)
+{
+}
+
+HomePageHandler::~HomePageHandler()
+{
+}
+
+void HomePageHandler::doGet()
+{
+    const char* opening = 
+    "<html>\n"
+    "  <head>\n"
+    "    <title>ARM MBED Plotter</title>\n"
+    "    <style>\n"
+    "    table {\n"
+    "        text-align: center;\n"
+    "        width:352px;\n"
+    "    }\n"
+    "    #btn {\n"
+    "        border: 1px solid black;\n"
+    "        background-color: powderblue;\n"
+    "    }\n"
+    "    td, tr {\n"
+    "        width: 32px;\n"
+    "        height: 32px;\n"
+    "    }\n"
+    "    </style>\n"
+    "    <script>\n"
+    "      var client;\n"
+    "      function upload()\n"
+    "      {\n"
+    "        var filetag = document.getElementById('uploadfile');\n"
+    "        var file = filetag.files[0];\n"
+    "        if (!file) {\n"
+    "          return;\n"
+    "        }\n"
+    "        client = new XMLHttpRequest();\n"
+    "        client.open('POST', 'upload');\n"
+    "        client.setRequestHeader('Content-Type', 'text/plain');\n"
+    "        client.setRequestHeader('Request', 'upload');\n"
+    "        client.setRequestHeader('X-File-Name', file.name);\n"
+    "        client.setRequestHeader('X-File-Size', file.size);\n"
+    "        client.send(file);\n"
+    "      }\n"
+    "      function plot()\n"
+    "      {\n"
+    "        var filetag = document.getElementById('to_plot');\n"
+    "        var file = filetag.value;\n"
+    "        var formData = new FormData();\n"
+    "        formData.append('plot', file);\n"
+    "        client = new XMLHttpRequest();\n"
+    "        client.open('POST', 'upload');\n"
+    "        client.setRequestHeader('Request', 'plot');\n"
+    "        client.setRequestHeader('X-File-Name', file);\n"
+    "        client.setRequestHeader('Content-Type', 'multipart/form-data');\n"
+    "        client.send(formData);\n"
+    "      }\n"
+    "      function move(value)\n"
+    "      {\n"
+    "        var formData = new FormData();\n"
+    "        formData.append('value', value);\n"
+    "        client = new XMLHttpRequest();\n"
+    "        client.open('POST', 'upload');\n"
+    "        client.setRequestHeader('Request', 'move');\n"
+    "        client.setRequestHeader('Direction', value);\n"
+    "        client.setRequestHeader('Content-Type', 'multipart/form-data');\n"
+    "        client.send(formData);\n"
+    "      }\n"
+    "    </script>\n"
+    "  </head>\n"
+    "  <body>\n"
+    "    <h1>ARM MBED Plotter</h1>\n"
+    "    <br/>\n"
+    "    <br/>\n"
+    "    <input name='FileSubmit' type='submit' value='Upload' onclick='upload()' />\n"
+    "    <input type='file' id='uploadfile' name='uploadfile' />\n"
+    "    <br/>\n"
+    "    <br/>\n"
+    "    <input name='FilePlot' type='submit' value='Plot' onclick='plot()' />\n"
+    "    <select id='to_plot' name='to_plot'>\n"
+    "";
+
+    const char* closing = 
+    "    </select>\n"
+    "    <br/>\n"
+    "    <br/>\n"
+    "    <table>\n"
+    "      <tr>\n"
+    "        <td></td>\n"
+    "        <td id='btn' onclick='move(0)' >&#8657;</td>\n"
+    "        <td></td>\n"
+    "        <td></td>\n"
+    "        <td id='btn' onclick='move(4)' >&#8624;</td>\n"
+    "        <td></td>\n"
+    "        <td id='btn' onclick='move(6)' >&#8662;</td>\n"
+    "        <td></td>\n"
+    "        <td id='btn' onclick='move(10)'>   0   </td>\n"
+    "        <td></td>\n"
+    "        <td id='btn' onclick='move(9)' >&#8663;</td>\n"
+    "      </tr>\n"
+    "      <tr>\n"
+    "        <td id='btn' onclick='move(1)' >&#8656;</td>\n"
+    "        <td id='btn' onclick='move(2)' >&#8659;</td>\n"
+    "        <td id='btn' onclick='move(3)' >&#8658;</td>\n"
+    "        <td></td>\n"
+    "        <td id='btn' onclick='move(5)' >&#8627;</td>\n"
+    "        <td></td>\n"
+    "        <td></td>\n"
+    "        <td id='btn' onclick='move(7)' >&#8664;</td>\n"
+    "        <td></td>\n"
+    "        <td id='btn' onclick='move(8)' >&#8665;</td>\n"
+    "        <td></td>\n"
+    "      </tr>\n"
+    "    </table>\n"
+    "  </body>\n"
+    "</html>\n";
+
+    m_fp = fopen("/usb/tmpidx", "w");
+
+    m_idx_size = 0;
+    for(int i=0; i<strlen(opening); i++)
+    {
+        m_idx_size += fwrite(&opening[i], 1, 1, m_fp);
+    }
+    fclose(m_fp);
+    m_fp = fopen("/usb/tmpidx", "a");
+    
+    DIR *d = opendir("/usb");    
+    if ( d != NULL )
+    {
+        struct dirent *p;
+        while ( (p = readdir(d)) != NULL )
+        {
+            if(strncmp(p->d_name, "tmpidx", sizeof("tmpidx"))==0) continue;
+            char filename[256];
+            strncpy(filename, "      <option value='", 256);
+            strncat(filename, p->d_name, 256);
+            strncat(filename, "'>", 256);
+            strncat(filename, p->d_name, 256);
+            strncat(filename, "</option>\n", 256);
+            m_idx_size += fwrite(filename, strlen(filename), 1, m_fp);
+        }
+        closedir(d);
+    }
+    for(int i=0; i<strlen(closing); i++)
+    {
+        m_idx_size += fwrite(&closing[i], 1, 1, m_fp);
+    }
+    // workaroud: add 2KB of new lines to allow the tcp socket to flush...
+    for(int i=0; i<2048; i++)
+    {
+        const char* nl = "\n";
+        m_idx_size += fwrite(nl, 1, 1, m_fp);
+    }
+    fclose(m_fp); 
+
+    m_fp = fopen("/usb/tmpidx", "r");
+
+    setContentLen(m_idx_size);
+    respHeaders()["Connection"] = "close";
+    onWriteable();
+}
+
+void HomePageHandler::doPost()
+{
+}
+
+void HomePageHandler::doHead()
+{
+}
+
+void HomePageHandler::onReadable()
+{
+}
+
+void HomePageHandler::onWriteable()
+{
+  static char rBuf[CHUNK_SIZE];
+  while(true)
+  {
+    int len = fread(rBuf, 1, CHUNK_SIZE, m_fp);
+    if(len>0)
+    {
+      int writtenLen = writeData(rBuf, len);
+      if(writtenLen < 0) //Socket error
+      {
+        if(writtenLen == TCPSOCKET_MEM)
+        {
+          fseek(m_fp, -len, SEEK_CUR);
+          return; //Wait for the queued TCP segments to be transmitted
+        }
+        else
+        {
+          //This is a critical error
+          close();
+          return; 
+        }
+      }
+      else if(writtenLen < len) //Short write, socket's buffer is full
+      {
+        fseek(m_fp, writtenLen - len, SEEK_CUR);
+        return;
+      }
+    }
+    else
+    {
+      close(); //Data written, we can close the connection
+      return;
+    }
+  }
+}
+
+void HomePageHandler::onClose()
+{
+    if(m_fp)
+    {
+        fclose(m_fp);
+        m_fp = NULL;
+    }
+}
diff -r 000000000000 -r 602ff2b2d41c HomePageHandler.hpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HomePageHandler.hpp	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,30 @@
+#ifndef HOME_PAGE_HANDLER_H
+#define HOME_PAGE_HANDLER_H
+ 
+#include "HTTPRequestHandler.h"
+
+class HomePageHandler : public HTTPRequestHandler
+{
+public:
+  HomePageHandler(const char* rootPath, const char* path, TCPSocket* pTcpSocket);
+  virtual ~HomePageHandler();
+ 
+  static inline HTTPRequestHandler* inst(const char* rootPath, const char* path, TCPSocket* pTcpSocket) 
+  {
+      return new HomePageHandler(rootPath, path, pTcpSocket);
+  } 
+ 
+protected:
+  virtual void doGet();
+  virtual void doPost();
+  virtual void doHead();
+  
+  virtual void onReadable(); //Data has been read
+  virtual void onWriteable(); //Data has been written & buf is free
+  virtual void onClose(); //Connection is closing
+
+private:
+    FILE* m_fp;
+    int   m_idx_size;
+};
+#endif
\ No newline at end of file
diff -r 000000000000 -r 602ff2b2d41c MSCFileSystem.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MSCFileSystem.cpp	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,113 @@
+/* USB Mass Storage device file system
+ * Copyrigh (c) 2010, Igor Skochinsky
+ * based on SDFileStorage
+ * Copyright (c) 2008-2009, sford
+ */
+ 
+/* Introduction
+ * ------------
+ * TODO: write one
+ * we're basically using NXP's USBHotLite sample code, just plugging in our own FAT library
+ */
+ 
+#include "MSCFileSystem.h"
+#include "usbhost_inc.h"
+
+MSCFileSystem::MSCFileSystem(const char* name) :
+  FATFileSystem(name)
+{
+}
+
+void print_inquiry(USB_INT08U *inqReply)
+{
+    // see USB Mass Storage Class – UFI Command Specification,
+    // 4.2 INQUIRY Command
+    printf("Inquiry reply:\n");
+    uint8_t tmp = inqReply[0]&0x1F;
+    printf("Peripheral device type: %02Xh\n", tmp);
+    if ( tmp == 0 )
+        printf("\t- Direct access (floppy)\n");
+    else if ( tmp == 0x1F )
+        printf("\t- none (no FDD connected)\n");
+    else
+        printf("\t- unknown type\n");
+    tmp = inqReply[1] >> 7;
+    printf("Removable Media Bit: %d\n", tmp);
+    tmp = inqReply[2] & 3;
+    printf("ANSI Version: %02Xh\n", tmp);
+    if ( tmp != 0 )
+        printf("\t- warning! must be 0\n");
+    tmp = (inqReply[2]>>3) & 3;
+    printf("ECMA Version: %02Xh\n", tmp);
+    if ( tmp != 0 )
+        printf("\t- warning! should be 0\n");
+    tmp = inqReply[2]>>6;
+    printf("ISO Version: %02Xh\n", tmp);
+    if ( tmp != 0 )
+        printf("\t- warning! should be 0\n");
+    tmp = inqReply[3] & 0xF;
+    printf("Response Data Format: %02Xh\n", tmp);
+    if ( tmp != 1 )
+        printf("\t- warning! should be 1\n");
+    tmp = inqReply[4];
+    printf("Additional length: %02Xh\n", tmp);
+    if ( tmp != 0x1F )
+        printf("\t- warning! should be 1Fh\n");
+    printf("Vendor Information: '%.8s'\n", &inqReply[8]);
+    printf("Product Identification: '%.16s'\n", &inqReply[16]);
+    printf("Product Revision: '%.4s'\n", &inqReply[32]);        
+}
+
+int MSCFileSystem::initialise_msc()
+{
+    USB_INT32S  rc;
+    USB_INT08U  inquiryResult[INQUIRY_LENGTH];
+    
+    //print_clock();
+    Host_Init();               /* Initialize the  host controller                                    */
+    rc = Host_EnumDev();       /* Enumerate the device connected                                            */
+    if (rc != OK)
+    {
+        fprintf(stderr, "Could not enumerate device: %d\n", rc);
+        return rc;
+    }
+        
+    
+    /* Initialize the mass storage and scsi interfaces */
+    rc = MS_Init( &_blkSize, &_numBlks, inquiryResult );
+    if (rc != OK)
+    {
+        fprintf(stderr, "Could not initialize mass storage interface: %d\n", rc);
+        return rc;
+    }
+    printf("Successfully initialized mass storage interface; %d blocks of size %d\n", _numBlks, _blkSize);
+    print_inquiry(inquiryResult);
+    // FATFileSystem supports only 512-byte blocks
+    return _blkSize == 512 ? OK : 1;
+}
+
+int MSCFileSystem::disk_initialize()
+{
+    if ( initialise_msc() != OK )
+        return 1;
+        
+    return 0;
+}
+
+int MSCFileSystem::disk_write(const char *buffer, int block_number)
+{
+    if ( OK == MS_BulkSend(block_number, 1, (USB_INT08U *)buffer) )
+        return 0;
+    return 1;
+}
+
+int MSCFileSystem::disk_read(char *buffer, int block_number)
+{
+    if ( OK == MS_BulkRecv(block_number, 1, (USB_INT08U *)buffer) )
+        return 0;
+    return 1;
+}
+
+int MSCFileSystem::disk_status() { return 0; }
+int MSCFileSystem::disk_sync() { return 0; }
+int MSCFileSystem::disk_sectors() { return _numBlks; }
diff -r 000000000000 -r 602ff2b2d41c MSCFileSystem.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MSCFileSystem.h	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,49 @@
+/* USB Mass Storage device file system
+ * Copyrigh (c) 2010, Igor Skochinsky
+ * based on SDFileStorage
+ * Copyright (c) 2008-2009, sford
+ */
+ 
+#ifndef MSCFILESYSTEM_H
+#define MSCFILESYSTEM_H
+
+#include "mbed.h"
+#include "FATFileSystem.h"
+
+/* Class: MSCFileSystem
+ *  Access the filesystem on an attached USB mass storage device (e.g. a memory stick)
+ *
+ * Example:
+ * > MSCFileSystem msc("msc");
+ * > 
+ * > int main() {
+ * >     FILE *fp = fopen("/msc/myfile.txt", "w");
+ * >     fprintf(fp, "Hello World!\n");
+ * >     fclose(fp);
+ * > }
+ */
+class MSCFileSystem : public FATFileSystem {
+public:
+
+    /* Constructor: MSCFileSystem
+     *  Create the File System for accessing a USB mass storage device
+     *
+     * Parameters:
+     *  name - The name used to access the filesystem
+     */
+    MSCFileSystem(const char* name);
+    virtual int disk_initialize();
+    virtual int disk_write(const char *buffer, int block_number);
+    virtual int disk_read(char *buffer, int block_number);    
+    virtual int disk_status();
+    virtual int disk_sync();
+    virtual int disk_sectors();
+
+protected:
+
+    int initialise_msc();
+    uint32_t _numBlks;
+    uint32_t _blkSize;
+};
+
+#endif
diff -r 000000000000 -r 602ff2b2d41c SuspendedPlotter.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SuspendedPlotter.cpp	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,185 @@
+#include "SuspendedPlotter.h"
+
+// Keep speeds low to increase torque
+#define SERVO_SPEED 8
+#define STEPPER_SPEED 4
+
+// 1000, 1500, 2000
+
+SuspendedPlotter::Servo::Servo(PinName pn)
+    : m_pwm(pn)
+    , m_pulse_width(0.0015)
+{
+    m_pwm.period(0.020);
+    m_pwm.pulsewidth(m_pulse_width);
+}
+
+void SuspendedPlotter::Servo::open()
+{
+    while(m_pulse_width > 0.0015)
+    {
+        m_pwm.pulsewidth(m_pulse_width);
+        wait_ms(SERVO_SPEED);
+        m_pulse_width -= 0.000005;
+    }
+}
+
+void SuspendedPlotter::Servo::close()
+{
+    while(m_pulse_width < 0.002)
+    {
+        m_pwm.pulsewidth(m_pulse_width);
+        wait_ms(SERVO_SPEED);
+        m_pulse_width += 0.000005;
+    }
+}
+
+SuspendedPlotter::Stepper::Stepper(PinName pn1, PinName pn2, PinName pn3, PinName pn4)
+    : m_step(0)
+    , m_in1(pn1)
+    , m_in2(pn2)
+    , m_in3(pn3)
+    , m_in4(pn4)
+{
+    for (int i = 0; i < 8; i++)
+        stepTo(-1);
+    for (int i = 0; i < 8; i++)
+        stepTo(1);
+}
+
+void SuspendedPlotter::Stepper::stepTo(int dir)
+{
+    static const int steps[8][4] = {
+        {1, 0, 0, 0},
+        {1, 1, 0, 0},
+        {0, 1, 0, 0},
+        {0, 1, 1, 0},
+        {0, 0, 1, 0},
+        {0, 0, 1, 1},
+        {0, 0, 0, 1},
+        {1, 0, 0, 1}
+    };
+    m_step = (m_step+dir+8) % 8;
+
+    m_in1 = steps[m_step][0];
+    m_in2 = steps[m_step][1];
+    m_in3 = steps[m_step][2];
+    m_in4 = steps[m_step][3];
+}
+
+SuspendedPlotter::SuspendedPlotter()
+    : m_left(p17, p18, p19, p20)
+    , m_right(p13, p14, p15, p16)
+    , m_servo(p21)
+    , m_x(0.5)
+    , m_y(0.5)
+{
+}
+
+void SuspendedPlotter::reset()
+{
+    m_x = 0.5;
+    m_y = 0.5;
+}
+
+void SuspendedPlotter::startDraw()
+{
+    m_servo.close();
+}
+
+void SuspendedPlotter::stopDraw()
+{
+    m_servo.open();
+}
+
+void SuspendedPlotter::moveTo(float x, float y)
+{
+    static const float MIN_Y = 0.2;
+    static const float MAX_Y = 2.0;
+    if (x < 0.0)
+        x = 0.0;
+    if (x > 1.0)
+        x = 1.0;
+    if (y < MIN_Y)
+        y = MIN_Y;
+    if (y > MAX_Y)
+        y = MAX_Y;
+
+    /* Adaptation of Bresenham's line algorithm */
+    /* 280 mm per 10000 steps */
+    /* Base is 815 mm or 29107 steps */
+    /* Diagonals at 0.5 are 576mm */ /*280 left , 280 right */
+    static const float BASE_STEPS_L = 29107;
+    static const float BASE_STEPS_R = 29107;
+    int left_0 = hypot(m_x * BASE_STEPS_L, m_y * BASE_STEPS_L);
+    int right_0 = hypot(BASE_STEPS_R - m_x * BASE_STEPS_R, m_y * BASE_STEPS_R);
+    int left_1 = hypot(x * BASE_STEPS_L, y * BASE_STEPS_L);
+    int right_1 = hypot(BASE_STEPS_R - x * BASE_STEPS_R, y * BASE_STEPS_R);
+
+    int d_right = abs(left_1 - left_0);
+    int s_right = left_0 < left_1 ? 1 : -1;
+    int d_left = abs(right_1 - right_0);
+    int s_left = right_0 < right_1 ? 1 : -1;
+    int err = (d_right > d_left ? d_right : -d_left) / 2, e2;
+
+    while ((left_0 != left_1) || (right_0 != right_1))
+    {
+        if (left_0 == left_1 && right_0 == right_1)
+            break;
+        e2 = err;
+        if (e2 > -d_right)
+        {
+            err -= d_left;
+            left_0 += s_right;
+            m_left.stepTo(-s_right); /* Should be +, but the left motor is reversed */
+            //printf("LEFT: current=%d, left_0=%d, left_1=%d, s_right=%d\n\r", m_left.getPosition(), left_0, left_1, s_right);
+        }
+        if (e2 < d_left)
+        {
+            err += d_right;
+            right_0 += s_left;
+            m_right.stepTo(-s_left);
+            //printf("RIGHT: current=%d, right_0=%d, right1=%d, s_left=%d\n\r", m_right.getPosition(), right_0, right_1, s_left);
+        }
+        wait_ms(STEPPER_SPEED);
+    }
+
+    m_x = x;
+    m_y = y;
+}
+
+void SuspendedPlotter::unrollLeft(int steps)
+{
+    for (int i = 0; i < steps; i++)
+    {
+        m_left.stepTo(-1); /* Should be +, but the left motor is reversed */
+        wait_ms(STEPPER_SPEED);
+    }
+}
+
+void SuspendedPlotter::unrollRight(int steps)
+{
+    for (int i = 0; i < steps; i++)
+    {
+        m_right.stepTo(-1);
+        wait_ms(STEPPER_SPEED);
+    }
+}
+
+void SuspendedPlotter::rollLeft(int steps)
+{
+    for (int i = 0; i < steps; i++)
+    {
+        m_left.stepTo(1); /* Should be -, but the left motor is reversed */
+        wait_ms(STEPPER_SPEED);
+    }
+}
+
+void SuspendedPlotter::rollRight(int steps)
+{
+    for (int i = 0; i < steps; i++)
+    {
+        m_right.stepTo(1);
+        wait_ms(STEPPER_SPEED);
+    }
+}
diff -r 000000000000 -r 602ff2b2d41c SuspendedPlotter.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SuspendedPlotter.h	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,51 @@
+#include "mbed.h"
+#include <PwmOut.h>
+
+class SuspendedPlotter
+{
+public:
+    SuspendedPlotter();
+    void startDraw();
+    void stopDraw();
+    void moveTo(float x, float y);
+    void unrollLeft(int steps);
+    void unrollRight(int steps);
+    void rollLeft(int steps);
+    void rollRight(int steps);
+    void reset();
+
+private:
+    class Stepper
+    {
+    public:
+        Stepper(PinName pn1, PinName pn2, PinName pn3, PinName pn4);
+        void stepTo(int dir);
+
+    private:
+        int m_step;
+        DigitalOut m_in1;
+        DigitalOut m_in2;
+        DigitalOut m_in3;
+        DigitalOut m_in4;
+    };
+
+    class Servo
+    {
+    public:
+        Servo(PinName pn1);
+        void open();
+        void close();
+        void reset();
+
+    private:
+        PwmOut m_pwm;
+        float m_pulse_width;
+    };
+
+private:
+    Stepper m_left;
+    Stepper m_right;
+    Servo m_servo;
+    float m_x;
+    float m_y;
+};
diff -r 000000000000 -r 602ff2b2d41c USBHostLite/usbhost_cpu.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/USBHostLite/usbhost_cpu.h	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,35 @@
+/*
+**************************************************************************************************************
+*                                                 NXP USB Host Stack
+*
+*                                     (c) Copyright 2008, NXP SemiConductors
+*                                     (c) Copyright 2008, OnChip  Technologies LLC
+*                                                 All Rights Reserved
+*
+*                                                  www.nxp.com
+*                                               www.onchiptech.com
+*
+* File           : usbhost_cpu.h
+* Programmer(s)  : Ravikanth.P
+* Version        :
+*
+**************************************************************************************************************
+*/
+
+#ifndef  USBHOST_CPU_H
+#define  USBHOST_CPU_H
+
+/*
+**************************************************************************************************************
+*                                           TYPE DEFINITIONS OF DATA TYPES
+**************************************************************************************************************
+*/
+
+typedef  unsigned int    USB_INT32U;
+typedef  signed   int    USB_INT32S;
+typedef  unsigned short  USB_INT16U;
+typedef  signed   short  USB_INT16S;
+typedef  unsigned char   USB_INT08U;
+typedef  signed   char   USB_INT08S;
+
+#endif
diff -r 000000000000 -r 602ff2b2d41c USBHostLite/usbhost_err.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/USBHostLite/usbhost_err.h	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,63 @@
+/*
+**************************************************************************************************************
+*                                                 NXP USB Host Stack
+*
+*                                     (c) Copyright 2008, NXP SemiConductors
+*                                     (c) Copyright 2008, OnChip  Technologies LLC
+*                                                 All Rights Reserved
+*
+*                                                  www.nxp.com
+*                                               www.onchiptech.com
+*
+* File           : usbhost_err.h
+* Programmer(s)  : Ravikanth.P
+* Version        :
+*
+**************************************************************************************************************
+*/
+
+#ifndef  USBHOST_ERR_H
+#define  USBHOST_ERR_H
+
+
+/*
+**************************************************************************************************************
+*                                        GENERAL DEFINITIONS
+**************************************************************************************************************
+*/
+
+#define  OK                        0
+#define  MATCH_FOUND               0
+
+/*
+**************************************************************************************************************
+*                                HOST CONTROLLER SPECIFIC ERROR CODES
+**************************************************************************************************************
+*/
+
+#define  ERR_TD_FAIL              -1
+
+/*
+**************************************************************************************************************
+*                                  MASS STORAGE SPECIFIC ERROR CODES
+**************************************************************************************************************
+*/
+
+#define  ERR_MS_CMD_FAILED       -10
+#define  ERR_BAD_CONFIGURATION   -11
+#define  ERR_NO_MS_INTERFACE     -12
+
+/*
+**************************************************************************************************************
+*                                      FAT SPECIFIC ERROR CODES
+**************************************************************************************************************
+*/
+
+#define  MATCH_NOT_FOUND         -20
+#define  ERR_FAT_NOT_SUPPORTED   -21
+#define  ERR_OPEN_LIMIT_REACHED  -22
+#define  ERR_INVALID_BOOT_SIG    -23
+#define  ERR_INVALID_BOOT_SEC    -24
+#define  ERR_ROOT_DIR_FULL       -25
+
+#endif
diff -r 000000000000 -r 602ff2b2d41c USBHostLite/usbhost_inc.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/USBHostLite/usbhost_inc.h	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,39 @@
+/*
+**************************************************************************************************************
+*                                                 NXP USB Host Stack
+*
+*                                     (c) Copyright 2008, NXP SemiConductors
+*                                     (c) Copyright 2008, OnChip  Technologies LLC
+*                                                 All Rights Reserved
+*
+*                                                  www.nxp.com
+*                                               www.onchiptech.com
+*
+* File           : usbhost_inc.h
+* Programmer(s)  : Ravikanth.P
+* Version        :
+*
+**************************************************************************************************************
+*/
+
+#ifndef  USBHOST_INC_H
+#define  USBHOST_INC_H
+
+/*
+**************************************************************************************************************
+*                                       INCLUDE HEADER FILES
+**************************************************************************************************************
+*/
+
+#include  "usbhost_cpu.h"
+#include  "usbhost_err.h"
+#include  "usbhost_lpc17xx.h"
+#include  "usbhost_ms.h"
+#include  "mbed.h"
+
+
+#ifdef TARGET_LPC2368
+#error "There is no USB host on the LPC2368!"
+#endif
+
+#endif
diff -r 000000000000 -r 602ff2b2d41c USBHostLite/usbhost_lpc17xx.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/USBHostLite/usbhost_lpc17xx.cpp	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,820 @@
+/*
+**************************************************************************************************************
+*                                                 NXP USB Host Stack
+*
+*                                     (c) Copyright 2008, NXP SemiConductors
+*                                     (c) Copyright 2008, OnChip  Technologies LLC
+*                                                 All Rights Reserved
+*
+*                                                  www.nxp.com
+*                                               www.onchiptech.com
+*
+* File           : usbhost_lpc17xx.c
+* Programmer(s)  : Ravikanth.P
+* Version        :
+*
+**************************************************************************************************************
+*/
+ 
+/*
+**************************************************************************************************************
+*                                            INCLUDE HEADER FILES
+**************************************************************************************************************
+*/
+
+#include  "usbhost_lpc17xx.h"
+
+/*
+**************************************************************************************************************
+*                                              GLOBAL VARIABLES
+**************************************************************************************************************
+*/
+int gUSBConnected;
+
+volatile  USB_INT32U   HOST_RhscIntr = 0;         /* Root Hub Status Change interrupt                       */
+volatile  USB_INT32U   HOST_WdhIntr  = 0;         /* Semaphore to wait until the TD is submitted            */
+volatile  USB_INT08U   HOST_TDControlStatus = 0;
+volatile  HCED        *EDCtrl;                    /* Control endpoint descriptor structure                  */
+volatile  HCED        *EDBulkIn;                  /* BulkIn endpoint descriptor  structure                  */
+volatile  HCED        *EDBulkOut;                 /* BulkOut endpoint descriptor structure                  */
+volatile  HCTD        *TDHead;                    /* Head transfer descriptor structure                     */
+volatile  HCTD        *TDTail;                    /* Tail transfer descriptor structure                     */
+volatile  HCCA        *Hcca;                      /* Host Controller Communications Area structure          */ 
+          USB_INT16U  *TDBufNonVol;               /* Identical to TDBuffer just to reduce compiler warnings */
+volatile  USB_INT08U  *TDBuffer;                  /* Current Buffer Pointer of transfer descriptor          */
+
+// USB host structures
+// AHB SRAM block 1
+#define HOSTBASEADDR 0x2007C000
+// reserve memory for the linker
+static USB_INT08U HostBuf[0x200] __attribute__((at(HOSTBASEADDR)));
+/*
+**************************************************************************************************************
+*                                         DELAY IN MILLI SECONDS
+*
+* Description: This function provides a delay in milli seconds
+*
+* Arguments  : delay    The delay required
+*
+* Returns    : None
+*
+**************************************************************************************************************
+*/
+
+void  Host_DelayMS (USB_INT32U  delay)
+{
+    volatile  USB_INT32U  i;
+
+
+    for (i = 0; i < delay; i++) {
+        Host_DelayUS(1000);
+    }
+}
+
+/*
+**************************************************************************************************************
+*                                         DELAY IN MICRO SECONDS
+*
+* Description: This function provides a delay in micro seconds
+*
+* Arguments  : delay    The delay required
+*
+* Returns    : None
+*
+**************************************************************************************************************
+*/
+
+void  Host_DelayUS (USB_INT32U  delay)
+{
+    volatile  USB_INT32U  i;
+
+
+    for (i = 0; i < (4 * delay); i++) {    /* This logic was tested. It gives app. 1 micro sec delay        */
+        ;
+    }
+}
+
+// bits of the USB/OTG clock control register
+#define HOST_CLK_EN     (1<<0)
+#define DEV_CLK_EN      (1<<1)
+#define PORTSEL_CLK_EN  (1<<3)
+#define AHB_CLK_EN      (1<<4)
+
+// bits of the USB/OTG clock status register
+#define HOST_CLK_ON     (1<<0)
+#define DEV_CLK_ON      (1<<1)
+#define PORTSEL_CLK_ON  (1<<3)
+#define AHB_CLK_ON      (1<<4)
+
+// we need host clock, OTG/portsel clock and AHB clock
+#define CLOCK_MASK (HOST_CLK_EN | PORTSEL_CLK_EN | AHB_CLK_EN)
+
+/*
+**************************************************************************************************************
+*                                         INITIALIZE THE HOST CONTROLLER
+*
+* Description: This function initializes lpc17xx host controller
+*
+* Arguments  : None
+*
+* Returns    : 
+*
+**************************************************************************************************************
+*/
+void  Host_Init (void)
+{
+    PRINT_Log("In Host_Init\n");
+    NVIC_DisableIRQ(USB_IRQn);                           /* Disable the USB interrupt source           */
+    
+    // turn on power for USB
+    LPC_SC->PCONP       |= (1UL<<31);
+    // Enable USB host clock, port selection and AHB clock
+    LPC_USB->USBClkCtrl |= CLOCK_MASK;
+    // Wait for clocks to become available
+    while ((LPC_USB->USBClkSt & CLOCK_MASK) != CLOCK_MASK)
+        ;
+    
+    // it seems the bits[0:1] mean the following
+    // 0: U1=device, U2=host
+    // 1: U1=host, U2=host
+    // 2: reserved
+    // 3: U1=host, U2=device
+    // NB: this register is only available if OTG clock (aka "port select") is enabled!!
+    // since we don't care about port 2, set just bit 0 to 1 (U1=host)
+    LPC_USB->OTGStCtrl |= 1;
+    
+    // now that we've configured the ports, we can turn off the portsel clock
+    LPC_USB->USBClkCtrl &= ~PORTSEL_CLK_EN;
+    
+    // power pins are not connected on mbed, so we can skip them
+    /* P1[18] = USB_UP_LED, 01 */
+    /* P1[19] = /USB_PPWR,     10 */
+    /* P1[22] = USB_PWRD, 10 */
+    /* P1[27] = /USB_OVRCR, 10 */
+    /*LPC_PINCON->PINSEL3 &= ~((3<<4) | (3<<6) | (3<<12) | (3<<22));  
+    LPC_PINCON->PINSEL3 |=  ((1<<4)|(2<<6) | (2<<12) | (2<<22));   // 0x00802080
+    */
+
+    // configure USB D+/D- pins
+    /* P0[29] = USB_D+, 01 */
+    /* P0[30] = USB_D-, 01 */
+    LPC_PINCON->PINSEL1 &= ~((3<<26) | (3<<28));  
+    LPC_PINCON->PINSEL1 |=  ((1<<26)|(1<<28));     // 0x14000000
+        
+    PRINT_Log("Initializing Host Stack\n");
+
+    Hcca       = (volatile  HCCA       *)(HostBuf+0x000);
+    TDHead     = (volatile  HCTD       *)(HostBuf+0x100);
+    TDTail     = (volatile  HCTD       *)(HostBuf+0x110);
+    EDCtrl     = (volatile  HCED       *)(HostBuf+0x120); 
+    EDBulkIn   = (volatile  HCED       *)(HostBuf+0x130);
+    EDBulkOut  = (volatile  HCED       *)(HostBuf+0x140);
+    TDBuffer   = (volatile  USB_INT08U *)(HostBuf+0x150);
+    
+    /* Initialize all the TDs, EDs and HCCA to 0  */
+    Host_EDInit(EDCtrl);
+    Host_EDInit(EDBulkIn);
+    Host_EDInit(EDBulkOut);
+    Host_TDInit(TDHead);
+    Host_TDInit(TDTail);
+    Host_HCCAInit(Hcca);
+    
+    Host_DelayMS(50);                                   /* Wait 50 ms before apply reset              */
+    LPC_USB->HcControl       = 0;                       /* HARDWARE RESET                             */
+    LPC_USB->HcControlHeadED = 0;                       /* Initialize Control list head to Zero       */
+    LPC_USB->HcBulkHeadED    = 0;                       /* Initialize Bulk list head to Zero          */
+    
+                                                        /* SOFTWARE RESET                             */
+    LPC_USB->HcCommandStatus = OR_CMD_STATUS_HCR;
+    LPC_USB->HcFmInterval    = DEFAULT_FMINTERVAL;      /* Write Fm Interval and Largest Data Packet Counter */
+
+                                                        /* Put HC in operational state                */
+    LPC_USB->HcControl  = (LPC_USB->HcControl & (~OR_CONTROL_HCFS)) | OR_CONTROL_HC_OPER;
+    LPC_USB->HcRhStatus = OR_RH_STATUS_LPSC;            /* Set Global Power                           */
+    
+    LPC_USB->HcHCCA = (USB_INT32U)Hcca;
+    LPC_USB->HcInterruptStatus |= LPC_USB->HcInterruptStatus;                   /* Clear Interrrupt Status                    */
+
+
+    LPC_USB->HcInterruptEnable  = OR_INTR_ENABLE_MIE |
+                         OR_INTR_ENABLE_WDH |
+                         OR_INTR_ENABLE_RHSC;
+
+    NVIC_SetPriority(USB_IRQn, 0);       /* highest priority */
+    /* Enable the USB Interrupt */
+    NVIC_EnableIRQ(USB_IRQn);
+    PRINT_Log("Host Initialized\n");
+}
+
+/*
+**************************************************************************************************************
+*                                         INTERRUPT SERVICE ROUTINE
+*
+* Description: This function services the interrupt caused by host controller
+*
+* Arguments  : None
+*
+* Returns    : None
+*
+**************************************************************************************************************
+*/
+
+void USB_IRQHandler (void) __irq
+{
+    USB_INT32U   int_status;
+    USB_INT32U   ie_status;
+
+    int_status    = LPC_USB->HcInterruptStatus;                          /* Read Interrupt Status                */
+    ie_status     = LPC_USB->HcInterruptEnable;                          /* Read Interrupt enable status         */
+ 
+    if (!(int_status & ie_status)) {
+        return;
+    } else {
+
+        int_status = int_status & ie_status;
+        if (int_status & OR_INTR_STATUS_RHSC) {                 /* Root hub status change interrupt     */
+            if (LPC_USB->HcRhPortStatus1 & OR_RH_PORT_CSC) {
+                if (LPC_USB->HcRhStatus & OR_RH_STATUS_DRWE) {
+                    /*
+                     * When DRWE is on, Connect Status Change
+                     * means a remote wakeup event.
+                    */
+                    HOST_RhscIntr = 1;// JUST SOMETHING FOR A BREAKPOINT
+                }
+                else {
+                    /*
+                     * When DRWE is off, Connect Status Change
+                     * is NOT a remote wakeup event
+                    */
+                    if (LPC_USB->HcRhPortStatus1 & OR_RH_PORT_CCS) {
+                        if (!gUSBConnected) {
+                            HOST_TDControlStatus = 0;
+                            HOST_WdhIntr = 0;
+                            HOST_RhscIntr = 1;
+                            gUSBConnected = 1;
+                        }
+                        else
+                            PRINT_Log("Spurious status change (connected)?\n");
+                    } else {
+                        if (gUSBConnected) {
+                            LPC_USB->HcInterruptEnable = 0; // why do we get multiple disc. rupts???
+                            HOST_RhscIntr = 0;
+                            gUSBConnected = 0;
+                        }
+                        else
+                            PRINT_Log("Spurious status change (disconnected)?\n");
+                    }
+                }
+                LPC_USB->HcRhPortStatus1 = OR_RH_PORT_CSC;
+            }
+            if (LPC_USB->HcRhPortStatus1 & OR_RH_PORT_PRSC) {
+                LPC_USB->HcRhPortStatus1 = OR_RH_PORT_PRSC;
+            }
+        }
+        if (int_status & OR_INTR_STATUS_WDH) {                  /* Writeback Done Head interrupt        */
+            HOST_WdhIntr = 1;
+            HOST_TDControlStatus = (TDHead->Control >> 28) & 0xf;
+        }            
+        LPC_USB->HcInterruptStatus = int_status;                         /* Clear interrupt status register      */
+    }
+    return;
+}
+
+/*
+**************************************************************************************************************
+*                                     PROCESS TRANSFER DESCRIPTOR
+*
+* Description: This function processes the transfer descriptor
+*
+* Arguments  : ed            Endpoint descriptor that contains this transfer descriptor
+*              token         SETUP, IN, OUT
+*              buffer        Current Buffer Pointer of the transfer descriptor
+*              buffer_len    Length of the buffer
+*
+* Returns    : OK       if TD submission is successful
+*              ERROR    if TD submission fails
+*
+**************************************************************************************************************
+*/
+
+USB_INT32S  Host_ProcessTD (volatile  HCED       *ed,
+                            volatile  USB_INT32U  token,
+                            volatile  USB_INT08U *buffer,
+                                      USB_INT32U  buffer_len)
+{
+    volatile  USB_INT32U   td_toggle;
+
+
+    if (ed == EDCtrl) {
+        if (token == TD_SETUP) {
+            td_toggle = TD_TOGGLE_0;
+        } else {
+            td_toggle = TD_TOGGLE_1;
+        }
+    } else {
+        td_toggle = 0;
+    }
+    TDHead->Control = (TD_ROUNDING    |
+                      token           |
+                      TD_DELAY_INT(0) |                           
+                      td_toggle       |
+                      TD_CC);
+    TDTail->Control = 0;
+    TDHead->CurrBufPtr   = (USB_INT32U) buffer;
+    TDTail->CurrBufPtr   = 0;
+    TDHead->Next         = (USB_INT32U) TDTail;
+    TDTail->Next         = 0;
+    TDHead->BufEnd       = (USB_INT32U)(buffer + (buffer_len - 1));
+    TDTail->BufEnd       = 0;
+
+    ed->HeadTd  = (USB_INT32U)TDHead | ((ed->HeadTd) & 0x00000002);
+    ed->TailTd  = (USB_INT32U)TDTail;
+    ed->Next    = 0;
+
+    if (ed == EDCtrl) {
+        LPC_USB->HcControlHeadED = (USB_INT32U)ed;
+        LPC_USB->HcCommandStatus = LPC_USB->HcCommandStatus | OR_CMD_STATUS_CLF;
+        LPC_USB->HcControl       = LPC_USB->HcControl       | OR_CONTROL_CLE;
+    } else {
+        LPC_USB->HcBulkHeadED    = (USB_INT32U)ed;
+        LPC_USB->HcCommandStatus = LPC_USB->HcCommandStatus | OR_CMD_STATUS_BLF;
+        LPC_USB->HcControl       = LPC_USB->HcControl       | OR_CONTROL_BLE;
+    }    
+
+    Host_WDHWait();
+
+//    if (!(TDHead->Control & 0xF0000000)) {
+    if (!HOST_TDControlStatus) {
+        return (OK);
+    } else {      
+        return (ERR_TD_FAIL);
+    }
+}
+
+/*
+**************************************************************************************************************
+*                                       ENUMERATE THE DEVICE
+*
+* Description: This function is used to enumerate the device connected
+*
+* Arguments  : None
+*
+* Returns    : None
+*
+**************************************************************************************************************
+*/
+
+USB_INT32S  Host_EnumDev (void)
+{
+    USB_INT32S  rc;
+
+    PRINT_Log("Connect a Mass Storage device\n");
+    while (!HOST_RhscIntr)
+        __WFI();
+    Host_DelayMS(100);                             /* USB 2.0 spec says atleast 50ms delay beore port reset */
+    LPC_USB->HcRhPortStatus1 = OR_RH_PORT_PRS; // Initiate port reset
+    while (LPC_USB->HcRhPortStatus1 & OR_RH_PORT_PRS)
+        __WFI(); // Wait for port reset to complete...
+    LPC_USB->HcRhPortStatus1 = OR_RH_PORT_PRSC; // ...and clear port reset signal
+    Host_DelayMS(200);                                                 /* Wait for 100 MS after port reset  */
+
+    EDCtrl->Control = 8 << 16;                                         /* Put max pkt size = 8              */
+                                                                       /* Read first 8 bytes of device desc */
+    rc = HOST_GET_DESCRIPTOR(USB_DESCRIPTOR_TYPE_DEVICE, 0, TDBuffer, 8);
+    if (rc != OK) {
+        PRINT_Err(rc);
+        return (rc);
+    }
+    EDCtrl->Control = TDBuffer[7] << 16;                               /* Get max pkt size of endpoint 0    */
+    rc = HOST_SET_ADDRESS(1);                                          /* Set the device address to 1       */
+    if (rc != OK) {
+        PRINT_Err(rc);
+        return (rc);
+    }
+    Host_DelayMS(2);
+    EDCtrl->Control = (EDCtrl->Control) | 1;                          /* Modify control pipe with address 1 */
+                                                                      /* Get the configuration descriptor   */
+    rc = HOST_GET_DESCRIPTOR(USB_DESCRIPTOR_TYPE_CONFIGURATION, 0, TDBuffer, 9);
+    if (rc != OK) {
+        PRINT_Err(rc);
+        return (rc);
+    }
+                                                                       /* Get the first configuration data  */
+    rc = HOST_GET_DESCRIPTOR(USB_DESCRIPTOR_TYPE_CONFIGURATION, 0, TDBuffer, ReadLE16U(&TDBuffer[2]));
+    if (rc != OK) {
+        PRINT_Err(rc);
+        return (rc);
+    }
+    rc = MS_ParseConfiguration();                                      /* Parse the configuration           */
+    if (rc != OK) {
+        PRINT_Err(rc);
+        return (rc);
+    }
+    rc = USBH_SET_CONFIGURATION(1);                                    /* Select device configuration 1     */
+    if (rc != OK) {
+        PRINT_Err(rc);
+    }
+    Host_DelayMS(100);                                               /* Some devices may require this delay */
+    return (rc);
+}
+
+/*
+**************************************************************************************************************
+*                                        RECEIVE THE CONTROL INFORMATION
+*
+* Description: This function is used to receive the control information
+*
+* Arguments  : bm_request_type
+*              b_request
+*              w_value
+*              w_index
+*              w_length
+*              buffer
+*
+* Returns    : OK       if Success
+*              ERROR    if Failed
+*
+**************************************************************************************************************
+*/
+   
+USB_INT32S  Host_CtrlRecv (         USB_INT08U   bm_request_type,
+                                    USB_INT08U   b_request,
+                                    USB_INT16U   w_value,
+                                    USB_INT16U   w_index,
+                                    USB_INT16U   w_length,
+                          volatile  USB_INT08U  *buffer)
+{
+    USB_INT32S  rc;
+
+
+    Host_FillSetup(bm_request_type, b_request, w_value, w_index, w_length);
+    rc = Host_ProcessTD(EDCtrl, TD_SETUP, TDBuffer, 8);
+    if (rc == OK) {
+        if (w_length) {
+            rc = Host_ProcessTD(EDCtrl, TD_IN, TDBuffer, w_length);
+        }
+        if (rc == OK) {
+            rc = Host_ProcessTD(EDCtrl, TD_OUT, NULL, 0);
+        }
+    }
+    return (rc);
+}
+
+/*
+**************************************************************************************************************
+*                                         SEND THE CONTROL INFORMATION
+*
+* Description: This function is used to send the control information
+*
+* Arguments  : None
+*
+* Returns    : OK                      if Success
+*              ERR_INVALID_BOOTSIG    if Failed
+*
+**************************************************************************************************************
+*/
+
+USB_INT32S  Host_CtrlSend (          USB_INT08U   bm_request_type,
+                                     USB_INT08U   b_request,
+                                     USB_INT16U   w_value,
+                                     USB_INT16U   w_index,
+                                     USB_INT16U   w_length,
+                           volatile  USB_INT08U  *buffer)
+{
+    USB_INT32S  rc;
+
+
+    Host_FillSetup(bm_request_type, b_request, w_value, w_index, w_length);
+
+    rc = Host_ProcessTD(EDCtrl, TD_SETUP, TDBuffer, 8);
+    if (rc == OK) {
+        if (w_length) {
+            rc = Host_ProcessTD(EDCtrl, TD_OUT, TDBuffer, w_length);
+        }
+        if (rc == OK) {
+            rc = Host_ProcessTD(EDCtrl, TD_IN, NULL, 0);
+        }
+    }
+    return (rc);
+}
+
+/*
+**************************************************************************************************************
+*                                          FILL SETUP PACKET
+*
+* Description: This function is used to fill the setup packet
+*
+* Arguments  : None
+*
+* Returns    : OK                      if Success
+*              ERR_INVALID_BOOTSIG    if Failed
+*
+**************************************************************************************************************
+*/
+
+void  Host_FillSetup (USB_INT08U   bm_request_type,
+                      USB_INT08U   b_request,
+                      USB_INT16U   w_value,
+                      USB_INT16U   w_index,
+                      USB_INT16U   w_length)
+{
+    int i;
+    for (i=0;i<w_length;i++)
+        TDBuffer[i] = 0;
+    
+    TDBuffer[0] = bm_request_type;
+    TDBuffer[1] = b_request;
+    WriteLE16U(&TDBuffer[2], w_value);
+    WriteLE16U(&TDBuffer[4], w_index);
+    WriteLE16U(&TDBuffer[6], w_length);
+}
+
+
+
+/*
+**************************************************************************************************************
+*                                         INITIALIZE THE TRANSFER DESCRIPTOR
+*
+* Description: This function initializes transfer descriptor
+*
+* Arguments  : Pointer to TD structure
+*
+* Returns    : None
+*
+**************************************************************************************************************
+*/
+
+void  Host_TDInit (volatile  HCTD *td)
+{
+
+    td->Control    = 0;
+    td->CurrBufPtr = 0;
+    td->Next       = 0;
+    td->BufEnd     = 0;
+}
+
+/*
+**************************************************************************************************************
+*                                         INITIALIZE THE ENDPOINT DESCRIPTOR
+*
+* Description: This function initializes endpoint descriptor
+*
+* Arguments  : Pointer to ED strcuture
+*
+* Returns    : None
+*
+**************************************************************************************************************
+*/
+
+void  Host_EDInit (volatile  HCED *ed)
+{
+
+    ed->Control = 0;
+    ed->TailTd  = 0;
+    ed->HeadTd  = 0;
+    ed->Next    = 0;
+}
+
+/*
+**************************************************************************************************************
+*                                 INITIALIZE HOST CONTROLLER COMMUNICATIONS AREA
+*
+* Description: This function initializes host controller communications area
+*
+* Arguments  : Pointer to HCCA
+*
+* Returns    : 
+*
+**************************************************************************************************************
+*/
+
+void  Host_HCCAInit (volatile  HCCA  *hcca)
+{
+    USB_INT32U  i;
+
+
+    for (i = 0; i < 32; i++) {
+
+        hcca->IntTable[i] = 0;
+        hcca->FrameNumber = 0;
+        hcca->DoneHead    = 0;
+    }
+
+}
+
+/*
+**************************************************************************************************************
+*                                         WAIT FOR WDH INTERRUPT
+*
+* Description: This function is infinite loop which breaks when ever a WDH interrupt rises
+*
+* Arguments  : None
+*
+* Returns    : None
+*
+**************************************************************************************************************
+*/
+
+void  Host_WDHWait (void)
+{
+  while (!HOST_WdhIntr)
+      __WFI();
+
+  HOST_WdhIntr = 0;
+}
+
+/*
+**************************************************************************************************************
+*                                         READ LE 32U
+*
+* Description: This function is used to read an unsigned integer from a character buffer in the platform
+*              containing little endian processor
+*
+* Arguments  : pmem    Pointer to the character buffer
+*
+* Returns    : val     Unsigned integer
+*
+**************************************************************************************************************
+*/
+
+USB_INT32U  ReadLE32U (volatile  USB_INT08U  *pmem)
+{
+    USB_INT32U val = *(USB_INT32U*)pmem;
+#ifdef __BIG_ENDIAN
+    return __REV(val);
+#else
+    return val;
+#endif    
+}
+
+/*
+**************************************************************************************************************
+*                                        WRITE LE 32U
+*
+* Description: This function is used to write an unsigned integer into a charecter buffer in the platform 
+*              containing little endian processor.
+*
+* Arguments  : pmem    Pointer to the charecter buffer
+*              val     Integer value to be placed in the charecter buffer
+*
+* Returns    : None
+*
+**************************************************************************************************************
+*/
+
+void  WriteLE32U (volatile  USB_INT08U  *pmem,
+                            USB_INT32U   val)
+{
+#ifdef __BIG_ENDIAN
+    *(USB_INT32U*)pmem = __REV(val);
+#else
+    *(USB_INT32U*)pmem = val;
+#endif
+}
+
+/*
+**************************************************************************************************************
+*                                          READ LE 16U
+*
+* Description: This function is used to read an unsigned short integer from a charecter buffer in the platform
+*              containing little endian processor
+*
+* Arguments  : pmem    Pointer to the charecter buffer
+*
+* Returns    : val     Unsigned short integer
+*
+**************************************************************************************************************
+*/
+
+USB_INT16U  ReadLE16U (volatile  USB_INT08U  *pmem)
+{
+    USB_INT16U val = *(USB_INT16U*)pmem;
+#ifdef __BIG_ENDIAN
+    return __REV16(val);
+#else
+    return val;
+#endif    
+}
+
+/*
+**************************************************************************************************************
+*                                         WRITE LE 16U
+*
+* Description: This function is used to write an unsigned short integer into a charecter buffer in the
+*              platform containing little endian processor
+*
+* Arguments  : pmem    Pointer to the charecter buffer
+*              val     Value to be placed in the charecter buffer
+*
+* Returns    : None
+*
+**************************************************************************************************************
+*/
+
+void  WriteLE16U (volatile  USB_INT08U  *pmem,
+                            USB_INT16U   val)
+{
+#ifdef __BIG_ENDIAN
+    *(USB_INT16U*)pmem = (__REV16(val) & 0xFFFF);
+#else
+    *(USB_INT16U*)pmem = val;
+#endif
+}
+
+/*
+**************************************************************************************************************
+*                                         READ BE 32U
+*
+* Description: This function is used to read an unsigned integer from a charecter buffer in the platform
+*              containing big endian processor
+*
+* Arguments  : pmem    Pointer to the charecter buffer
+*
+* Returns    : val     Unsigned integer
+*
+**************************************************************************************************************
+*/
+
+USB_INT32U  ReadBE32U (volatile  USB_INT08U  *pmem)
+{
+    USB_INT32U val = *(USB_INT32U*)pmem;
+#ifdef __BIG_ENDIAN
+    return val;
+#else
+    return __REV(val);
+#endif
+}
+
+/*
+**************************************************************************************************************
+*                                         WRITE BE 32U
+*
+* Description: This function is used to write an unsigned integer into a charecter buffer in the platform
+*              containing big endian processor
+*
+* Arguments  : pmem    Pointer to the charecter buffer
+*              val     Value to be placed in the charecter buffer
+*
+* Returns    : None
+*
+**************************************************************************************************************
+*/
+
+void  WriteBE32U (volatile  USB_INT08U  *pmem,
+                            USB_INT32U   val)
+{
+#ifdef __BIG_ENDIAN
+    *(USB_INT32U*)pmem = val;
+#else
+    *(USB_INT32U*)pmem = __REV(val);
+#endif
+}
+
+/*
+**************************************************************************************************************
+*                                         READ BE 16U
+*
+* Description: This function is used to read an unsigned short integer from a charecter buffer in the platform
+*              containing big endian processor
+*
+* Arguments  : pmem    Pointer to the charecter buffer
+*
+* Returns    : val     Unsigned short integer
+*
+**************************************************************************************************************
+*/
+
+USB_INT16U  ReadBE16U (volatile  USB_INT08U  *pmem)
+{
+    USB_INT16U val = *(USB_INT16U*)pmem;
+#ifdef __BIG_ENDIAN
+    return val;
+#else
+    return __REV16(val);
+#endif    
+}
+
+/*
+**************************************************************************************************************
+*                                         WRITE BE 16U
+*
+* Description: This function is used to write an unsigned short integer into the charecter buffer in the
+*              platform containing big endian processor
+*
+* Arguments  : pmem    Pointer to the charecter buffer
+*              val     Value to be placed in the charecter buffer
+*
+* Returns    : None
+*
+**************************************************************************************************************
+*/
+
+void  WriteBE16U (volatile  USB_INT08U  *pmem,
+                            USB_INT16U   val)
+{
+#ifdef __BIG_ENDIAN
+    *(USB_INT16U*)pmem = val;
+#else
+    *(USB_INT16U*)pmem = (__REV16(val) & 0xFFFF);
+#endif
+}
diff -r 000000000000 -r 602ff2b2d41c USBHostLite/usbhost_lpc17xx.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/USBHostLite/usbhost_lpc17xx.h	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,254 @@
+/*
+**************************************************************************************************************
+*                                                 NXP USB Host Stack
+*
+*                                     (c) Copyright 2008, NXP SemiConductors
+*                                     (c) Copyright 2008, OnChip  Technologies LLC
+*                                                 All Rights Reserved
+*
+*                                                  www.nxp.com
+*                                               www.onchiptech.com
+*
+* File           : usbhost_lpc17xx.h
+* Programmer(s)  : Ravikanth.P
+* Version        :
+*
+**************************************************************************************************************
+*/
+
+#ifndef USBHOST_LPC17xx_H
+#define USBHOST_LPC17xx_H
+
+/*
+**************************************************************************************************************
+*                                       INCLUDE HEADER FILES
+**************************************************************************************************************
+*/
+
+#include    "usbhost_inc.h"
+
+/*
+**************************************************************************************************************
+*                                        PRINT CONFIGURATION
+**************************************************************************************************************
+*/
+
+#define  PRINT_ENABLE         1
+
+#if PRINT_ENABLE
+#define  PRINT_Log(...)       printf(__VA_ARGS__)
+#define  PRINT_Err(rc)        printf("ERROR: In %s at Line %u - rc = %d\n", __FUNCTION__, __LINE__, rc)
+
+#else 
+#define  PRINT_Log(...)       do {} while(0)
+#define  PRINT_Err(rc)        do {} while(0)
+
+#endif
+
+/*
+**************************************************************************************************************
+*                                        GENERAL DEFINITIONS
+**************************************************************************************************************
+*/
+
+#define  DESC_LENGTH(x)  x[0]
+#define  DESC_TYPE(x)    x[1]
+
+
+#define  HOST_GET_DESCRIPTOR(descType, descIndex, data, length)                      \
+         Host_CtrlRecv(USB_DEVICE_TO_HOST | USB_RECIPIENT_DEVICE, GET_DESCRIPTOR,    \
+         (descType << 8)|(descIndex), 0, length, data)
+
+#define  HOST_SET_ADDRESS(new_addr)                                                  \
+         Host_CtrlSend(USB_HOST_TO_DEVICE | USB_RECIPIENT_DEVICE, SET_ADDRESS,       \
+         new_addr, 0, 0, NULL)
+
+#define  USBH_SET_CONFIGURATION(configNum)                                           \
+         Host_CtrlSend(USB_HOST_TO_DEVICE | USB_RECIPIENT_DEVICE, SET_CONFIGURATION, \
+         configNum, 0, 0, NULL)
+
+#define  USBH_SET_INTERFACE(ifNum, altNum)                                           \
+         Host_CtrlSend(USB_HOST_TO_DEVICE | USB_RECIPIENT_INTERFACE, SET_INTERFACE,  \
+         altNum, ifNum, 0, NULL)
+
+/*
+**************************************************************************************************************
+*                                  OHCI OPERATIONAL REGISTER FIELD DEFINITIONS
+**************************************************************************************************************
+*/
+
+                                            /* ------------------ HcControl Register ---------------------  */
+#define  OR_CONTROL_CLE                 0x00000010
+#define  OR_CONTROL_BLE                 0x00000020
+#define  OR_CONTROL_HCFS                0x000000C0
+#define  OR_CONTROL_HC_OPER             0x00000080
+                                            /* ----------------- HcCommandStatus Register ----------------- */
+#define  OR_CMD_STATUS_HCR              0x00000001
+#define  OR_CMD_STATUS_CLF              0x00000002
+#define  OR_CMD_STATUS_BLF              0x00000004
+                                            /* --------------- HcInterruptStatus Register ----------------- */
+#define  OR_INTR_STATUS_WDH             0x00000002
+#define  OR_INTR_STATUS_RHSC            0x00000040
+                                            /* --------------- HcInterruptEnable Register ----------------- */
+#define  OR_INTR_ENABLE_WDH             0x00000002
+#define  OR_INTR_ENABLE_RHSC            0x00000040
+#define  OR_INTR_ENABLE_MIE             0x80000000
+                                            /* ---------------- HcRhDescriptorA Register ------------------ */
+#define  OR_RH_STATUS_LPSC              0x00010000
+#define  OR_RH_STATUS_DRWE              0x00008000
+                                            /* -------------- HcRhPortStatus[1:NDP] Register -------------- */
+#define  OR_RH_PORT_CCS                 0x00000001
+#define  OR_RH_PORT_PRS                 0x00000010
+#define  OR_RH_PORT_CSC                 0x00010000
+#define  OR_RH_PORT_PRSC                0x00100000
+
+
+/*
+**************************************************************************************************************
+*                                               FRAME INTERVAL
+**************************************************************************************************************
+*/
+
+#define  FI                     0x2EDF           /* 12000 bits per frame (-1)                               */
+#define  DEFAULT_FMINTERVAL     ((((6 * (FI - 210)) / 7) << 16) | FI)
+
+/*
+**************************************************************************************************************
+*                                       TRANSFER DESCRIPTOR CONTROL FIELDS
+**************************************************************************************************************
+*/
+
+#define  TD_ROUNDING        (USB_INT32U) (0x00040000)        /* Buffer Rounding                             */
+#define  TD_SETUP           (USB_INT32U)(0)                  /* Direction of Setup Packet                   */
+#define  TD_IN              (USB_INT32U)(0x00100000)         /* Direction In                                */
+#define  TD_OUT             (USB_INT32U)(0x00080000)         /* Direction Out                               */
+#define  TD_DELAY_INT(x)    (USB_INT32U)((x) << 21)          /* Delay Interrupt                             */
+#define  TD_TOGGLE_0        (USB_INT32U)(0x02000000)         /* Toggle 0                                    */
+#define  TD_TOGGLE_1        (USB_INT32U)(0x03000000)         /* Toggle 1                                    */
+#define  TD_CC              (USB_INT32U)(0xF0000000)         /* Completion Code                             */
+
+/*
+**************************************************************************************************************
+*                                       USB STANDARD REQUEST DEFINITIONS
+**************************************************************************************************************
+*/
+
+#define  USB_DESCRIPTOR_TYPE_DEVICE                     1
+#define  USB_DESCRIPTOR_TYPE_CONFIGURATION              2
+#define  USB_DESCRIPTOR_TYPE_INTERFACE                  4
+#define  USB_DESCRIPTOR_TYPE_ENDPOINT                   5
+                                                    /*  ----------- Control RequestType Fields  ----------- */
+#define  USB_DEVICE_TO_HOST         0x80
+#define  USB_HOST_TO_DEVICE         0x00
+#define  USB_REQUEST_TYPE_CLASS     0x20
+#define  USB_RECIPIENT_DEVICE       0x00
+#define  USB_RECIPIENT_INTERFACE    0x01
+                                                    /* -------------- USB Standard Requests  -------------- */
+#define  SET_ADDRESS                 5
+#define  GET_DESCRIPTOR              6
+#define  SET_CONFIGURATION           9
+#define  SET_INTERFACE              11
+
+/*
+**************************************************************************************************************
+*                                       TYPE DEFINITIONS
+**************************************************************************************************************
+*/
+
+typedef struct hcEd {                       /* ----------- HostController EndPoint Descriptor ------------- */
+    volatile  USB_INT32U  Control;              /* Endpoint descriptor control                              */
+    volatile  USB_INT32U  TailTd;               /* Physical address of tail in Transfer descriptor list     */
+    volatile  USB_INT32U  HeadTd;               /* Physcial address of head in Transfer descriptor list     */
+    volatile  USB_INT32U  Next;                 /* Physical address of next Endpoint descriptor             */
+} HCED;
+
+typedef struct hcTd {                       /* ------------ HostController Transfer Descriptor ------------ */
+    volatile  USB_INT32U  Control;              /* Transfer descriptor control                              */
+    volatile  USB_INT32U  CurrBufPtr;           /* Physical address of current buffer pointer               */
+    volatile  USB_INT32U  Next;                 /* Physical pointer to next Transfer Descriptor             */
+    volatile  USB_INT32U  BufEnd;               /* Physical address of end of buffer                        */
+} HCTD;
+
+typedef struct hcca {                       /* ----------- Host Controller Communication Area ------------  */
+    volatile  USB_INT32U  IntTable[32];         /* Interrupt Table                                          */
+    volatile  USB_INT32U  FrameNumber;          /* Frame Number                                             */
+    volatile  USB_INT32U  DoneHead;             /* Done Head                                                */
+    volatile  USB_INT08U  Reserved[116];        /* Reserved for future use                                  */
+    volatile  USB_INT08U  Unknown[4];           /* Unused                                                   */
+} HCCA;
+
+/*
+**************************************************************************************************************
+*                                     EXTERN DECLARATIONS
+**************************************************************************************************************
+*/
+
+extern  volatile  HCED        *EDBulkIn;        /* BulkIn endpoint descriptor  structure                    */
+extern  volatile  HCED        *EDBulkOut;       /* BulkOut endpoint descriptor structure                    */
+extern  volatile  HCTD        *TDHead;          /* Head transfer descriptor structure                       */
+extern  volatile  HCTD        *TDTail;          /* Tail transfer descriptor structure                       */
+extern  volatile  USB_INT08U  *TDBuffer;        /* Current Buffer Pointer of transfer descriptor            */
+
+/*
+**************************************************************************************************************
+*                                       FUNCTION PROTOTYPES
+**************************************************************************************************************
+*/
+
+void        Host_Init     (void);
+
+extern "C" void USB_IRQHandler(void)  __irq;
+
+USB_INT32S  Host_EnumDev  (void);
+
+USB_INT32S  Host_ProcessTD(volatile  HCED       *ed,
+                           volatile  USB_INT32U  token,
+                           volatile  USB_INT08U *buffer,
+                                     USB_INT32U  buffer_len);
+
+void        Host_DelayUS  (          USB_INT32U    delay);
+void        Host_DelayMS  (          USB_INT32U    delay);
+
+
+void        Host_TDInit   (volatile  HCTD *td);
+void        Host_EDInit   (volatile  HCED *ed);
+void        Host_HCCAInit (volatile  HCCA  *hcca);
+
+USB_INT32S  Host_CtrlRecv (          USB_INT08U   bm_request_type,
+                                     USB_INT08U   b_request,
+                                     USB_INT16U   w_value,
+                                     USB_INT16U   w_index,
+                                     USB_INT16U   w_length,
+                           volatile  USB_INT08U  *buffer);
+
+USB_INT32S  Host_CtrlSend (          USB_INT08U   bm_request_type,
+                                     USB_INT08U   b_request,
+                                     USB_INT16U   w_value,
+                                     USB_INT16U   w_index,
+                                     USB_INT16U   w_length,
+                           volatile  USB_INT08U  *buffer);
+
+void        Host_FillSetup(          USB_INT08U   bm_request_type,
+                                     USB_INT08U   b_request,
+                                     USB_INT16U   w_value,
+                                     USB_INT16U   w_index,
+                                     USB_INT16U   w_length);
+
+
+void        Host_WDHWait  (void);
+
+
+USB_INT32U  ReadLE32U     (volatile  USB_INT08U  *pmem);
+void        WriteLE32U    (volatile  USB_INT08U  *pmem,
+                                     USB_INT32U   val);
+USB_INT16U  ReadLE16U     (volatile  USB_INT08U  *pmem);
+void        WriteLE16U    (volatile  USB_INT08U  *pmem,
+                                     USB_INT16U   val);
+USB_INT32U  ReadBE32U     (volatile  USB_INT08U  *pmem);
+void        WriteBE32U    (volatile  USB_INT08U  *pmem,
+                                     USB_INT32U   val);
+USB_INT16U  ReadBE16U     (volatile  USB_INT08U  *pmem);
+void        WriteBE16U    (volatile  USB_INT08U  *pmem,
+                                     USB_INT16U   val);
+
+#endif
diff -r 000000000000 -r 602ff2b2d41c USBHostLite/usbhost_ms.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/USBHostLite/usbhost_ms.cpp	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,455 @@
+/*
+**************************************************************************************************************
+*                                                 NXP USB Host Stack
+*
+*                                     (c) Copyright 2008, NXP SemiConductors
+*                                     (c) Copyright 2008, OnChip  Technologies LLC
+*                                                 All Rights Reserved
+*
+*                                                  www.nxp.com
+*                                               www.onchiptech.com
+*
+* File           : usbhost_ms.c
+* Programmer(s)  : Ravikanth.P
+* Version        :
+*
+**************************************************************************************************************
+*/
+
+/*
+**************************************************************************************************************
+*                                       INCLUDE HEADER FILES
+**************************************************************************************************************
+*/
+
+#include  "usbhost_ms.h"
+
+/*
+**************************************************************************************************************
+*                                         GLOBAL VARIABLES
+**************************************************************************************************************
+*/
+
+USB_INT32U  MS_BlkSize;
+
+/*
+**************************************************************************************************************
+*                                      INITIALIZE MASS STORAGE INTERFACE
+*
+* Description: This function initializes the mass storage interface
+*
+* Arguments  : None
+*
+* Returns    : OK                      if Success
+*              ERR_INVALID_BOOTSIG    if Failed
+*
+**************************************************************************************************************
+*/
+
+USB_INT32S MS_Init (USB_INT32U *blkSize, USB_INT32U *numBlks, USB_INT08U *inquiryResult)
+{
+    USB_INT08U  retry;
+    USB_INT32S  rc;
+
+    MS_GetMaxLUN();                                                    /* Get maximum logical unit number   */
+    retry  = 80;
+    while(retry) {
+        rc = MS_TestUnitReady();                                       /* Test whether the unit is ready    */
+        if (rc == OK) {
+            break;
+        }
+        MS_GetSenseInfo();                                             /* Get sense information             */
+        retry--;
+    }
+    if (rc != OK) {
+        PRINT_Err(rc);
+        return (rc);
+    }
+    rc = MS_ReadCapacity(numBlks, blkSize);                         /* Read capacity of the disk         */
+    MS_BlkSize = *blkSize;                        // Set global
+    rc = MS_Inquire (inquiryResult);
+    return (rc);
+}
+/*
+**************************************************************************************************************
+*                                         PARSE THE CONFIGURATION
+*
+* Description: This function is used to parse the configuration
+*
+* Arguments  : None
+*
+* Returns    : OK                      if Success
+*              ERR_INVALID_BOOTSIG    if Failed
+*
+**************************************************************************************************************
+*/
+
+USB_INT32S  MS_ParseConfiguration (void)
+{
+    volatile  USB_INT08U  *desc_ptr;
+              USB_INT08U   ms_int_found;
+
+
+    desc_ptr     = TDBuffer;
+    ms_int_found = 0;
+
+    if (desc_ptr[1] != USB_DESCRIPTOR_TYPE_CONFIGURATION) {    
+        return (ERR_BAD_CONFIGURATION);
+    }
+    desc_ptr += desc_ptr[0];
+
+    while (desc_ptr != TDBuffer + ReadLE16U(&TDBuffer[2])) {
+
+        switch (desc_ptr[1]) {
+
+            case USB_DESCRIPTOR_TYPE_INTERFACE:                       /* If it is an interface descriptor   */
+                 if (desc_ptr[5] == MASS_STORAGE_CLASS &&             /* check if the class is mass storage */
+                     desc_ptr[6] == MASS_STORAGE_SUBCLASS_SCSI &&     /* check if the subclass is SCSI      */
+                     desc_ptr[7] == MASS_STORAGE_PROTOCOL_BO) {       /* check if the protocol is Bulk only */
+                     ms_int_found = 1;
+                     desc_ptr    += desc_ptr[0];                      /* Move to next descriptor start      */
+                 }
+                 break;
+
+            case USB_DESCRIPTOR_TYPE_ENDPOINT:                        /* If it is an endpoint descriptor    */
+                 if ((desc_ptr[3] & 0x03) == 0x02) {                  /* If it is Bulk endpoint             */
+                     if (desc_ptr[2] & 0x80) {                        /* If it is In endpoint               */
+                         EDBulkIn->Control =  1                             |      /* USB address           */
+                                              ((desc_ptr[2] & 0x7F) << 7)   |      /* Endpoint address      */
+                                              (2 << 11)                     |      /* direction             */
+                                              (ReadLE16U(&desc_ptr[4]) << 16);     /* MaxPkt Size           */
+                         desc_ptr += desc_ptr[0];                     /* Move to next descriptor start      */
+                     } else {                                         /* If it is Out endpoint              */
+                         EDBulkOut->Control = 1                             |      /* USB address           */
+                                              ((desc_ptr[2] & 0x7F) << 7)   |      /* Endpoint address      */
+                                              (1 << 11)                     |      /* direction             */
+                                              (ReadLE16U(&desc_ptr[4]) << 16);     /* MaxPkt Size           */
+                         desc_ptr += desc_ptr[0];                     /* Move to next descriptor start      */
+                     }
+                 } else {                                             /* If it is not bulk end point        */
+                     desc_ptr += desc_ptr[0];                         /* Move to next descriptor start      */
+                 }
+                 break;
+
+            default:                                 /* If the descriptor is neither interface nor endpoint */
+                 desc_ptr += desc_ptr[0];                             /* Move to next descriptor start      */
+                 break;
+        }
+    }
+    if (ms_int_found) {
+        PRINT_Log("Mass Storage device connected\n");
+        return (OK);
+    } else {
+        PRINT_Log("Not a Mass Storage device\n");
+        return (ERR_NO_MS_INTERFACE);
+    }
+}
+
+/*
+**************************************************************************************************************
+*                                         GET MAXIMUM LOGICAL UNIT
+*
+* Description: This function returns the maximum logical unit from the device
+*
+* Arguments  : None
+*
+* Returns    : OK                      if Success
+*              ERR_INVALID_BOOTSIG    if Failed
+*
+**************************************************************************************************************
+*/
+
+USB_INT32S  MS_GetMaxLUN (void)
+{
+    USB_INT32S  rc;
+
+
+    rc = Host_CtrlRecv(USB_DEVICE_TO_HOST | USB_REQUEST_TYPE_CLASS | USB_RECIPIENT_INTERFACE,
+                       MS_GET_MAX_LUN_REQ,
+                       0,
+                       0,
+                       1,
+                       TDBuffer);
+    return (rc); 
+}
+
+/*
+**************************************************************************************************************
+*                                          GET SENSE INFORMATION
+*
+* Description: This function is used to get sense information from the device
+*
+* Arguments  : None
+*
+* Returns    : OK       if Success
+*              ERROR    if Failed
+*
+**************************************************************************************************************
+*/
+
+USB_INT32S  MS_GetSenseInfo (void)
+{
+    USB_INT32S  rc;
+
+
+    Fill_MSCommand(0, 0, 0, MS_DATA_DIR_IN, SCSI_CMD_REQUEST_SENSE, 6);
+    rc = Host_ProcessTD(EDBulkOut, TD_OUT, TDBuffer, CBW_SIZE);
+    if (rc == OK) {
+        rc = Host_ProcessTD(EDBulkIn, TD_IN, TDBuffer, 18);
+        if (rc == OK) {
+            rc = Host_ProcessTD(EDBulkIn, TD_IN, TDBuffer, CSW_SIZE);
+            if (rc == OK) {
+                if (TDBuffer[12] != 0) {
+                    rc = ERR_MS_CMD_FAILED;
+                }
+            }
+        }
+    }
+    return (rc);
+}
+
+/*
+**************************************************************************************************************
+*                                           TEST UNIT READY
+*
+* Description: This function is used to test whether the unit is ready or not
+*
+* Arguments  : None
+*
+* Returns    : OK       if Success
+*              ERROR    if Failed
+*
+**************************************************************************************************************
+*/
+
+USB_INT32S  MS_TestUnitReady (void)
+{
+    USB_INT32S  rc;
+
+
+    Fill_MSCommand(0, 0, 0, MS_DATA_DIR_NONE, SCSI_CMD_TEST_UNIT_READY, 6);
+    rc = Host_ProcessTD(EDBulkOut, TD_OUT, TDBuffer, CBW_SIZE);
+    if (rc == OK) {
+        rc = Host_ProcessTD(EDBulkIn, TD_IN, TDBuffer, CSW_SIZE);
+        if (rc == OK) {        
+            if (TDBuffer[12] != 0) {
+                rc = ERR_MS_CMD_FAILED;
+            }
+        }
+    }
+    return (rc);
+}
+
+/*
+**************************************************************************************************************
+*                                            READ CAPACITY
+*
+* Description: This function is used to read the capacity of the mass storage device
+*
+* Arguments  : None
+*
+* Returns    : OK       if Success
+*              ERROR    if Failed
+*
+**************************************************************************************************************
+*/
+
+USB_INT32S MS_ReadCapacity (USB_INT32U *numBlks, USB_INT32U *blkSize)
+{
+    USB_INT32S  rc;
+
+
+    Fill_MSCommand(0, 0, 0, MS_DATA_DIR_IN, SCSI_CMD_READ_CAPACITY, 10);
+    rc = Host_ProcessTD(EDBulkOut, TD_OUT, TDBuffer, CBW_SIZE);
+    if (rc == OK) {
+        rc = Host_ProcessTD(EDBulkIn, TD_IN, TDBuffer, 8);
+        if (rc == OK) {
+            if (numBlks)
+                *numBlks = ReadBE32U(&TDBuffer[0]);
+            if (blkSize)
+                *blkSize = ReadBE32U(&TDBuffer[4]);
+            rc = Host_ProcessTD(EDBulkIn, TD_IN, TDBuffer, CSW_SIZE);
+            if (rc == OK) {
+                if (TDBuffer[12] != 0) {
+                    rc = ERR_MS_CMD_FAILED;
+                }
+            }
+        }
+    }
+    return (rc);
+}
+
+
+
+USB_INT32S MS_Inquire (USB_INT08U *response)
+{
+    USB_INT32S rc;
+    USB_INT32U i;
+
+    Fill_MSCommand(0, 0, 0, MS_DATA_DIR_IN, SCSI_CMD_INQUIRY, 6);
+    rc = Host_ProcessTD(EDBulkOut, TD_OUT, TDBuffer, CBW_SIZE);
+    if (rc == OK) {
+        rc = Host_ProcessTD(EDBulkIn, TD_IN, TDBuffer, INQUIRY_LENGTH);
+        if (rc == OK) {
+            if (response) {
+                for ( i = 0; i < INQUIRY_LENGTH; i++ )
+                    *response++ = *TDBuffer++;
+#if 0
+                MemCpy (response, TDBuffer, INQUIRY_LENGTH);
+                StrNullTrailingSpace (response->vendorID, SCSI_INQUIRY_VENDORCHARS);
+                StrNullTrailingSpace (response->productID, SCSI_INQUIRY_PRODUCTCHARS);
+                StrNullTrailingSpace (response->productRev, SCSI_INQUIRY_REVCHARS);
+#endif
+            }
+            rc = Host_ProcessTD(EDBulkIn, TD_IN, TDBuffer, CSW_SIZE);
+            if (rc == OK) {
+                if (TDBuffer[12] != 0) {    // bCSWStatus byte
+                    rc = ERR_MS_CMD_FAILED;
+                }
+            }
+        }
+    }
+    return (rc);
+}
+
+/*
+**************************************************************************************************************
+*                                         RECEIVE THE BULK DATA
+*
+* Description: This function is used to receive the bulk data
+*
+* Arguments  : None
+*
+* Returns    : OK                      if Success
+*              ERR_INVALID_BOOTSIG    if Failed
+*
+**************************************************************************************************************
+*/
+    
+USB_INT32S  MS_BulkRecv (          USB_INT32U   block_number,
+                                   USB_INT16U   num_blocks,
+                         volatile  USB_INT08U  *user_buffer)
+{
+    USB_INT32S  rc;
+    int i;
+    volatile USB_INT08U *c = user_buffer;
+    for (i=0;i<MS_BlkSize*num_blocks;i++)
+        *c++ = 0;
+
+
+    Fill_MSCommand(block_number, MS_BlkSize, num_blocks, MS_DATA_DIR_IN, SCSI_CMD_READ_10, 10);
+
+    rc = Host_ProcessTD(EDBulkOut, TD_OUT, TDBuffer, CBW_SIZE);
+    if (rc == OK) {
+        rc = Host_ProcessTD(EDBulkIn, TD_IN, user_buffer, MS_BlkSize * num_blocks);
+        if (rc == OK) {
+            rc = Host_ProcessTD(EDBulkIn, TD_IN, TDBuffer, CSW_SIZE);
+            if (rc == OK) {
+                if (TDBuffer[12] != 0) {
+                    rc = ERR_MS_CMD_FAILED;
+                }
+            }
+        }
+    }
+    return (rc);
+}
+
+/*
+**************************************************************************************************************
+*                                         SEND BULK DATA
+*
+* Description: This function is used to send the bulk data
+*
+* Arguments  : None
+*
+* Returns    : OK                      if Success
+*              ERR_INVALID_BOOTSIG    if Failed
+*
+**************************************************************************************************************
+*/
+
+USB_INT32S  MS_BulkSend (          USB_INT32U   block_number,
+                                   USB_INT16U   num_blocks,
+                         volatile  USB_INT08U  *user_buffer)
+{
+    USB_INT32S  rc;
+
+
+    Fill_MSCommand(block_number, MS_BlkSize, num_blocks, MS_DATA_DIR_OUT, SCSI_CMD_WRITE_10, 10);
+
+    rc = Host_ProcessTD(EDBulkOut, TD_OUT, TDBuffer, CBW_SIZE);
+    if (rc == OK) {
+        rc = Host_ProcessTD(EDBulkOut, TD_OUT, user_buffer, MS_BlkSize * num_blocks);
+        if (rc == OK) {
+            rc = Host_ProcessTD(EDBulkIn, TD_IN, TDBuffer, CSW_SIZE);
+            if (rc == OK) {
+                if (TDBuffer[12] != 0) {
+                    rc = ERR_MS_CMD_FAILED;
+                }
+            }
+        }
+    }
+    return (rc);
+}
+
+/*
+**************************************************************************************************************
+*                                         FILL MASS STORAGE COMMAND
+*
+* Description: This function is used to fill the mass storage command
+*
+* Arguments  : None
+*
+* Returns    : OK                      if Success
+*              ERR_INVALID_BOOTSIG    if Failed
+*
+**************************************************************************************************************
+*/
+
+void  Fill_MSCommand (USB_INT32U   block_number,
+                      USB_INT32U   block_size,
+                      USB_INT16U   num_blocks,
+                      MS_DATA_DIR  direction,
+                      USB_INT08U   scsi_cmd,
+                      USB_INT08U   scsi_cmd_len)
+{
+            USB_INT32U  data_len;
+    static  USB_INT32U  tag_cnt = 0;
+            USB_INT32U  cnt;
+
+
+    for (cnt = 0; cnt < CBW_SIZE; cnt++) {
+         TDBuffer[cnt] = 0;
+    }
+    switch(scsi_cmd) {
+
+        case SCSI_CMD_TEST_UNIT_READY:
+             data_len = 0;
+             break;
+        case SCSI_CMD_READ_CAPACITY:
+             data_len = 8;
+             break;
+        case SCSI_CMD_REQUEST_SENSE:
+             data_len = 18;
+             break;
+        case SCSI_CMD_INQUIRY:
+             data_len = 36;
+             break;
+        default:
+             data_len = block_size * num_blocks;
+             break;
+    }
+    WriteLE32U(TDBuffer, CBW_SIGNATURE);
+    WriteLE32U(&TDBuffer[4], tag_cnt);
+    WriteLE32U(&TDBuffer[8], data_len);
+    TDBuffer[12]     = (direction == MS_DATA_DIR_NONE) ? 0 : direction;
+    TDBuffer[14]     = scsi_cmd_len;                                   /* Length of the CBW                 */
+    TDBuffer[15]     = scsi_cmd;
+    if ((scsi_cmd     == SCSI_CMD_REQUEST_SENSE)
+     || (scsi_cmd     == SCSI_CMD_INQUIRY)) {
+        TDBuffer[19] = (USB_INT08U)data_len;
+    } else {
+        WriteBE32U(&TDBuffer[17], block_number);
+    }
+    WriteBE16U(&TDBuffer[22], num_blocks);
+}
diff -r 000000000000 -r 602ff2b2d41c USBHostLite/usbhost_ms.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/USBHostLite/usbhost_ms.h	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,101 @@
+/*
+**************************************************************************************************************
+*                                                 NXP USB Host Stack
+*
+*                                     (c) Copyright 2008, NXP SemiConductors
+*                                     (c) Copyright 2008, OnChip  Technologies LLC
+*                                                 All Rights Reserved
+*
+*                                                  www.nxp.com
+*                                               www.onchiptech.com
+*
+* File           : usbhost_ms.h
+* Programmer(s)  : Ravikanth.P
+* Version        :
+*
+**************************************************************************************************************
+*/
+
+#ifndef  USBHOST_MS_H
+#define  USBHOST_MS_H
+
+/*
+**************************************************************************************************************
+*                                       INCLUDE HEADER FILES
+**************************************************************************************************************
+*/
+
+#include  "usbhost_inc.h"
+
+/*
+**************************************************************************************************************
+*                               MASS STORAGE SPECIFIC DEFINITIONS
+**************************************************************************************************************
+*/
+
+#define    MS_GET_MAX_LUN_REQ            0xFE
+#define    MASS_STORAGE_CLASS            0x08
+#define    MASS_STORAGE_SUBCLASS_SCSI    0x06
+#define    MASS_STORAGE_PROTOCOL_BO      0x50
+
+#define    INQUIRY_LENGTH                36
+/*
+**************************************************************************************************************
+*                                  SCSI SPECIFIC DEFINITIONS
+**************************************************************************************************************
+*/
+
+#define  CBW_SIGNATURE               0x43425355
+#define  CSW_SIGNATURE               0x53425355
+#define  CBW_SIZE                      31
+#define  CSW_SIZE                      13
+#define  CSW_CMD_PASSED              0x00
+#define  SCSI_CMD_REQUEST_SENSE      0x03
+#define  SCSI_CMD_TEST_UNIT_READY    0x00
+#define  SCSI_CMD_INQUIRY            0x12
+#define  SCSI_CMD_READ_10            0x28
+#define  SCSI_CMD_READ_CAPACITY      0x25
+#define  SCSI_CMD_WRITE_10           0x2A
+
+/*
+**************************************************************************************************************
+*                                       TYPE DEFINITIONS
+**************************************************************************************************************
+*/
+
+typedef enum  ms_data_dir {
+
+    MS_DATA_DIR_IN     = 0x80,
+    MS_DATA_DIR_OUT    = 0x00,
+    MS_DATA_DIR_NONE   = 0x01
+
+} MS_DATA_DIR;
+
+/*
+**************************************************************************************************************
+*                                     FUNCTION PROTOTYPES
+**************************************************************************************************************
+*/
+
+USB_INT32S  MS_BulkRecv          (          USB_INT32U    block_number,
+                                            USB_INT16U    num_blocks,
+                                  volatile  USB_INT08U   *user_buffer);
+
+USB_INT32S  MS_BulkSend          (          USB_INT32U    block_number,
+                                            USB_INT16U    num_blocks,
+                                  volatile  USB_INT08U   *user_buffer);
+USB_INT32S  MS_ParseConfiguration(void);
+USB_INT32S  MS_TestUnitReady     (void);
+USB_INT32S  MS_ReadCapacity (USB_INT32U *numBlks, USB_INT32U *blkSize);
+USB_INT32S  MS_GetMaxLUN         (void);
+USB_INT32S  MS_GetSenseInfo      (void);
+USB_INT32S  MS_Init (USB_INT32U *blkSize, USB_INT32U *numBlks, USB_INT08U *inquiryResult);
+USB_INT32S  MS_Inquire (USB_INT08U *response);
+
+void        Fill_MSCommand       (          USB_INT32U    block_number,
+                                            USB_INT32U    block_size,
+                                            USB_INT16U    num_blocks,
+                                            MS_DATA_DIR   direction,
+                                            USB_INT08U    scsi_cmd,
+                                            USB_INT08U    scsi_cmd_len);
+#endif
diff -r 000000000000 -r 602ff2b2d41c UploadHandler.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UploadHandler.cpp	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,198 @@
+#include "UploadHandler.hpp"
+#include "mbed.h"
+#include "nanosvg.h"
+
+// The static plotter instance
+static SuspendedPlotter plotter;
+
+static void moveTo(float x, float y)
+{
+    plotter.moveTo(x, y);
+    printf("Moving to %f, %f\n\r", x, y);
+}
+
+static void beginPath()
+{
+    plotter.startDraw();
+    printf("Begin path\n\r");
+}
+
+static void endPath()
+{
+    plotter.stopDraw();
+    printf("End path\n\r");
+}
+
+UploadHandler::UploadHandler(const char* rootPath, const char* path, TCPSocket* pTcpSocket)
+: HTTPRequestHandler(rootPath, path, pTcpSocket)
+{
+    m_x = orig_x;
+    m_y = orig_y;
+}
+
+UploadHandler::~UploadHandler()
+{
+}
+
+void UploadHandler::doGet()
+{
+    const char* opening = "<html><head><title>Upload Page</title></head><body><p>Upload Page</p>";
+    const char* closing = "</body></html>";
+    char filename[256];
+    int len = 0;
+    len += writeData(opening, strlen(opening));
+    
+    DIR *d = opendir("/usb");    
+    if ( d != NULL )
+    {
+        struct dirent *p;
+        while ( (p = readdir(d)) != NULL )
+        {
+            strncpy(filename, "<p>", strlen("<p>")+1);
+            strncat(filename, p->d_name, 128);
+            strncat(filename, "</p>", strlen("</p>")+1);
+            len += writeData(filename, strlen(filename));
+        }
+        closedir(d);
+    }
+    len += writeData(closing, strlen(closing));
+
+    setContentLen(len);
+    respHeaders()["Connection"] = "close";
+}
+
+void UploadHandler::doPost()
+{
+    m_post_size = dataLen();
+    m_total_read = 0;
+    const char* request_type = reqHeaders()["Request"].c_str();
+
+    if(strncmp(request_type, "upload", sizeof("upload"))==0)
+    {
+        const char* file_name = reqHeaders()["X-File-Name"].c_str();
+        int file_size = atoi(reqHeaders()["X-File-Size"].c_str());
+    
+        char full_file_name[512];
+        strncpy(full_file_name, "/usb/", sizeof("/usb/"));
+        strncat(full_file_name, file_name, 510 - sizeof("/usb/"));
+    
+        m_fp = fopen( full_file_name, "w");
+    
+        onReadable();
+    }
+    else if(strncmp(request_type, "plot", sizeof("plot"))==0)
+    {
+        const char* file_name = reqHeaders()["X-File-Name"].c_str();
+        char full_file_name[512];
+        strncpy(full_file_name, "/usb/", sizeof("/usb/"));
+        strncat(full_file_name, file_name, 510 - sizeof("/usb/"));
+
+        const char* answer =    "<html><head><title>Upload Page</title></head>"
+                                "<body><p>Plot Handled</p></body></html>";
+        writeData(answer, strlen(answer));
+        setContentLen(strlen(answer));
+        respHeaders()["Connection"] = "close";
+        close();
+
+        m_x = orig_x;
+        m_y = orig_y;
+        plotter.reset();
+        nsvgDrawFromFile(full_file_name, moveTo, beginPath, endPath, 0.3, 0.35, 0.4);
+        plotter.moveTo(orig_x, orig_y);
+    }
+    else if(strncmp(request_type, "move", sizeof("plot"))==0)
+    {
+        int dir = atoi(reqHeaders()["Direction"].c_str());
+
+        const char* answer =    "<html><head><title>Upload Page</title></head>"
+                                "<body><p>Move Handled</p></body></html>";
+        writeData(answer, strlen(answer));
+        setContentLen(strlen(answer));
+        respHeaders()["Connection"] = "close";
+        close();
+
+        switch (dir)
+        {
+        case 0:
+            m_y -= 0.01;
+            plotter.moveTo(m_x, m_y);
+            break;
+        case 1:
+            m_x -= 0.01;
+            plotter.moveTo(m_x, m_y);
+            break;
+        case 2:
+            m_y += 0.01;
+            plotter.moveTo(m_x, m_y);
+            break;
+        case 3:
+            m_x += 0.01;
+            plotter.moveTo(m_x, m_y);
+            break;
+        case 4:
+            plotter.startDraw();
+            break;
+        case 5:
+            plotter.stopDraw();
+            break;
+        case 6:
+            plotter.rollLeft(100);
+            break;
+        case 7:
+            plotter.unrollLeft(100);
+            break;
+        case 8:
+            plotter.unrollRight(100);
+            break;
+        case 9:
+            plotter.rollRight(100);
+            break;
+       case 10:
+            m_x = orig_x;
+            m_y = orig_y;
+            plotter.moveTo(m_x, m_y);
+            break;
+         }
+    }
+}
+
+void UploadHandler::doHead()
+{
+}
+
+void UploadHandler::onReadable()
+{
+    int rlen = 0;
+    do
+    {
+        char buf[4096];
+        memset(buf, 0, 4096);
+        rlen = readData(buf, 4096);
+        if(rlen>0)
+        {
+            fwrite(buf, rlen, 1, m_fp);
+        }
+        m_total_read += rlen>0?rlen:-rlen; // negative rlen is an error
+    } while (rlen>0);
+
+    // If we got an error or if we reached the end of the socket
+    if(rlen<0 || m_total_read >= m_post_size)
+    {
+        fclose(m_fp); 
+        const char* answer =    "<html><head><title>Upload Page</title></head>"
+                                "<body><p>Post Handled</p></body></html>";
+        writeData(answer, strlen(answer));
+        setContentLen(strlen(answer));
+        respHeaders()["Connection"] = "close";
+        close();
+    } // else wait for next call to onReadable()
+}
+
+void UploadHandler::onWriteable()
+{
+        close();
+}
+
+void UploadHandler::onClose()
+{
+}
diff -r 000000000000 -r 602ff2b2d41c UploadHandler.hpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UploadHandler.hpp	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,36 @@
+#ifndef UPLOAD_HANDLER_H
+#define UPLOAD_HANDLER_H
+ 
+#include "HTTPRequestHandler.h"
+#include "SuspendedPlotter.h"
+
+class UploadHandler : public HTTPRequestHandler
+{
+public:
+  UploadHandler(const char* rootPath, const char* path, TCPSocket* pTcpSocket);
+  virtual ~UploadHandler();
+ 
+  static inline HTTPRequestHandler* inst(const char* rootPath, const char* path, TCPSocket* pTcpSocket) 
+  {
+      return new UploadHandler(rootPath, path, pTcpSocket);
+  } 
+
+protected:
+  virtual void doGet();
+  virtual void doPost();
+  virtual void doHead();
+  
+  virtual void onReadable(); //Data has been read
+  virtual void onWriteable(); //Data has been written & buf is free
+  virtual void onClose(); //Connection is closing
+
+private:
+    FILE* m_fp;
+    int m_total_read;
+    int m_post_size;
+    static const float orig_x = 0.5;
+    static const float orig_y = 0.5;
+    float m_x;
+    float m_y;
+};
+#endif
\ No newline at end of file
diff -r 000000000000 -r 602ff2b2d41c main.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,59 @@
+#include "mbed.h"
+#include "MSCFileSystem.h"
+#include "EthernetNetIf.h"
+#include "HTTPServer.h"
+#include "UploadHandler.hpp"
+#include "HomePageHandler.hpp"
+
+MSCFileSystem msc("usb");
+Serial pc(USBTX, USBRX);
+DigitalOut myled(LED1);
+
+#if 0
+IpAddr ip(10,0,0,85);
+IpAddr netmask(255,255,255,0);
+IpAddr nullip(0,0,0,0);
+EthernetNetIf eth(ip, netmask, nullip, nullip);
+#else
+EthernetNetIf eth;
+#endif
+
+HTTPServer svr;
+
+int main()
+{
+    pc.baud(115200);
+    printf("Suspended Plotter\n\r");
+
+    printf("Setting up...\n\r");
+    EthernetErr ethErr = eth.setup();
+    if(ethErr)
+    {
+        printf("Error %d in setup.\n\r", ethErr);
+        return -1;
+    }
+    printf("Setup OK\n\r");
+
+    svr.addHandler<HomePageHandler>("/");
+    svr.addHandler<UploadHandler>("/upload");
+    svr.bind(80);
+    
+    printf("Listening...\n\r");
+    
+    Timer tm;
+    tm.start();
+
+    while (true)
+    {
+        IpAddr ip = eth.getIp();
+        printf("Connected as: %d.%d.%d.%d\n\r", ip[0], ip[1], ip[2], ip[3]);
+        Net::poll();
+        if(tm.read()>.5)
+        {
+            //Show that we are alive
+            myled=!myled;
+            tm.start();
+        }
+
+    }
+}
diff -r 000000000000 -r 602ff2b2d41c mbed.bld
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/9114680c05da
diff -r 000000000000 -r 602ff2b2d41c nanosvg.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nanosvg.h	Mon Aug 22 10:24:23 2022 +0000
@@ -0,0 +1,1674 @@
+/*
+ * Copyright (c) 2013-14 Mikko Mononen memon@inside.org
+ * Copyright (c) 2016 Renato Grottesi renato.grottesi@gmail.com
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example
+ * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/)
+ *
+ * Arc calculation code based on canvg (https://code.google.com/p/canvg/)
+ *
+ * Limitations: the transform attribute must come before the other attributes.
+ */
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Lenght proportional to radius of a cubic bezier handle for 90deg arcs.
+#define NSVG_KAPPA90 (0.5522847493f)
+#define NSVG_PI (3.14159265358979323846264338327f)
+#define NSVG_EPSILON (1e-12)
+#define NSVG_MAX_XFORMS 64
+
+typedef void (*nsvgMoveToCb)(float, float);
+typedef void (*nsvgBeginPathCb)();
+typedef void (*nsvgEndPathCb)();
+
+typedef struct NSVGxform
+{
+    float xform[6];
+} NSVGxform;
+
+typedef struct NSVGCommonAttrs
+{
+    float cx;
+    float cy;
+    float height;
+    float r;
+    float rx;
+    float ry;
+    float width;
+    float x;
+    float x1;
+    float x2;
+    float y;
+    float y1;
+    float y2;
+} NSVGCommonAttrs;
+
+typedef struct NSVGparser
+{
+    NSVGxform xforms[NSVG_MAX_XFORMS];
+    int xformCurrent;
+    float *pts;
+    int npts;
+    float sx;
+    float sy;
+    int cpts;
+    char pathFlag;
+    nsvgMoveToCb moveToCb;
+    float mtx; // move to last entries
+    float mty;
+    nsvgBeginPathCb beginPathCb;
+    nsvgEndPathCb endPathCb;
+    float width;
+    float height;
+    float scale;
+    float x0;
+    float y0;
+    NSVGCommonAttrs attrs;
+} NSVGparser;
+
+static int nsvg__isspace(char c)
+{
+    return strchr(" \t\n\v\f\r", c) != 0;
+}
+
+static int nsvg__isdigit(char c)
+{
+    return strchr("0123456789", c) != 0;
+}
+
+static int nsvg__isnum(char c)
+{
+    return strchr("0123456789+-.eE", c) != 0;
+}
+
+static void nsvg__xformIdentity(float *t)
+{
+    t[0] = 1.0f;
+    t[1] = 0.0f;
+    t[2] = 0.0f;
+    t[3] = 1.0f;
+    t[4] = 0.0f;
+    t[5] = 0.0f;
+}
+
+static void nsvg__xformSetTranslation(float *t, float tx, float ty)
+{
+    t[0] = 1.0f;
+    t[1] = 0.0f;
+    t[2] = 0.0f;
+    t[3] = 1.0f;
+    t[4] = tx;
+    t[5] = ty;
+}
+
+static void nsvg__xformSetScale(float *t, float sx, float sy)
+{
+    t[0] = sx;
+    t[1] = 0.0f;
+    t[2] = 0.0f;
+    t[3] = sy;
+    t[4] = 0.0f;
+    t[5] = 0.0f;
+}
+
+static void nsvg__xformSetSkewX(float *t, float a)
+{
+    t[0] = 1.0f;
+    t[1] = 0.0f;
+    t[2] = tanf(a);
+    t[3] = 1.0f;
+    t[4] = 0.0f;
+    t[5] = 0.0f;
+}
+
+static void nsvg__xformSetSkewY(float *t, float a)
+{
+    t[0] = 1.0f;
+    t[1] = tanf(a);
+    t[2] = 0.0f;
+    t[3] = 1.0f;
+    t[4] = 0.0f;
+    t[5] = 0.0f;
+}
+
+static void nsvg__xformSetRotation(float *t, float a)
+{
+    float cs = cosf(a), sn = sinf(a);
+    t[0] = cs;
+    t[1] = sn;
+    t[2] = -sn;
+    t[3] = cs;
+    t[4] = 0.0f;
+    t[5] = 0.0f;
+}
+
+static void nsvg__xformMultiply(float *t, float *s)
+{
+    float t0 = t[0] * s[0] + t[1] * s[2];
+    float t2 = t[2] * s[0] + t[3] * s[2];
+    float t4 = t[4] * s[0] + t[5] * s[2] + s[4];
+    t[1] = t[0] * s[1] + t[1] * s[3];
+    t[3] = t[2] * s[1] + t[3] * s[3];
+    t[5] = t[4] * s[1] + t[5] * s[3] + s[5];
+    t[0] = t0;
+    t[2] = t2;
+    t[4] = t4;
+}
+
+static void nsvg__xformPremultiply(float *t, float *s)
+{
+    float s2[6];
+    memcpy(s2, s, sizeof(float) * 6);
+    nsvg__xformMultiply(s2, t);
+    memcpy(t, s2, sizeof(float) * 6);
+}
+
+static void nsvg__xformPoint(float *dx, float *dy, float x, float y, float *t)
+{
+    *dx = x * t[0] + y * t[2] + t[4];
+    *dy = x * t[1] + y * t[3] + t[5];
+}
+
+static void nsvg__xformVec(float *dx, float *dy, float x, float y, float *t)
+{
+    *dx = x * t[0] + y * t[2];
+    *dy = x * t[1] + y * t[3];
+}
+
+static void nsvg__resetPath(NSVGparser *p)
+{
+    p->npts = 0;
+}
+
+static void nsvg__addPoint(NSVGparser *p, float x, float y)
+{
+    if (p->npts + 1 > p->cpts)
+    {
+        p->cpts = p->cpts ? p->cpts * 2 : 8;
+        p->pts = (float *)realloc(p->pts, p->cpts * 2 * sizeof(float));
+        if (!p->pts)
+            return;
+    }
+    p->pts[p->npts * 2 + 0] = x;
+    p->pts[p->npts * 2 + 1] = y;
+    p->npts++;
+}
+
+static void nsvg__moveTo(NSVGparser *p, float x, float y)
+{
+    if (p->npts > 0)
+    {
+        p->pts[(p->npts - 1) * 2 + 0] = x;
+        p->pts[(p->npts - 1) * 2 + 1] = y;
+    }
+    else
+    {
+        nsvg__addPoint(p, x, y);
+        p->sx = x;
+        p->sy = y;
+    }
+}
+
+static void nsvg__lineTo(NSVGparser *p, float x, float y)
+{
+    float px, py, dx, dy;
+    if (p->npts > 0)
+    {
+        px = p->pts[(p->npts - 1) * 2 + 0];
+        py = p->pts[(p->npts - 1) * 2 + 1];
+        dx = x - px;
+        dy = y - py;
+        nsvg__addPoint(p, px + dx / 3.0f, py + dy / 3.0f);
+        nsvg__addPoint(p, x - dx / 3.0f, y - dy / 3.0f);
+        nsvg__addPoint(p, x, y);
+    }
+}
+
+static void nsvg__cubicBezTo(NSVGparser *p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y)
+{
+    nsvg__addPoint(p, cpx1, cpy1);
+    nsvg__addPoint(p, cpx2, cpy2);
+    nsvg__addPoint(p, x, y);
+}
+
+static NSVGxform *nsvg__getCurrentXForm(NSVGparser *p)
+{
+    return &p->xforms[p->xformCurrent];
+}
+
+static void nsvg__pushAttr(NSVGparser *p)
+{
+    if (p->xformCurrent < NSVG_MAX_XFORMS - 1)
+    {
+        p->xformCurrent++;
+        memcpy(&p->xforms[p->xformCurrent], &p->xforms[p->xformCurrent - 1], sizeof(NSVGxform));
+    }
+}
+
+static void nsvg__popAttr(NSVGparser *p)
+{
+    if (p->xformCurrent > 0)
+        p->xformCurrent--;
+}
+
+static float nsvg__distPtSeg(float x, float y, float px, float py, float qx, float qy)
+{
+    float pqx, pqy, dx, dy, d, t;
+    pqx = qx - px;
+    pqy = qy - py;
+    dx = x - px;
+    dy = y - py;
+    d = pqx * pqx + pqy * pqy;
+    t = pqx * dx + pqy * dy;
+    if (d > 0)
+        t /= d;
+    if (t < 0)
+        t = 0;
+    else if (t > 1)
+        t = 1;
+    dx = px + t * pqx - x;
+    dy = py + t * pqy - y;
+    return dx * dx + dy * dy;
+}
+
+static void nsvg__cubicBez(NSVGparser *p, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, float tol,
+                           int level)
+{
+    float x12, y12, x23, y23, x34, y34, x123, y123, x234, y234, x1234, y1234;
+    float d;
+
+    if (level > 12)
+        return;
+
+    x12 = (x1 + x2) * 0.5f;
+    y12 = (y1 + y2) * 0.5f;
+    x23 = (x2 + x3) * 0.5f;
+    y23 = (y2 + y3) * 0.5f;
+    x34 = (x3 + x4) * 0.5f;
+    y34 = (y3 + y4) * 0.5f;
+    x123 = (x12 + x23) * 0.5f;
+    y123 = (y12 + y23) * 0.5f;
+    x234 = (x23 + x34) * 0.5f;
+    y234 = (y23 + y34) * 0.5f;
+    x1234 = (x123 + x234) * 0.5f;
+    y1234 = (y123 + y234) * 0.5f;
+
+    d = nsvg__distPtSeg(x1234, y1234, x1, y1, x4, y4);
+    if (d > tol * tol)
+    {
+        nsvg__cubicBez(p, x1, y1, x12, y12, x123, y123, x1234, y1234, tol, level + 1);
+        nsvg__cubicBez(p, x1234, y1234, x234, y234, x34, y34, x4, y4, tol, level + 1);
+    }
+    else
+    {
+        if (x4 > 0.0 && x4 < 1.0 && y4 > 0.0 && y4 < 4.0)
+            p->moveToCb(x4, y4);
+        p->mtx = x4;
+        p->mty = y4;
+    }
+}
+
+static void nsvg__drawPath(NSVGparser *p, char closed)
+{
+    float pts[8]; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ...
+
+    NSVGxform *xform = nsvg__getCurrentXForm(p);
+    int i;
+
+    if (p->npts < 4)
+        return;
+
+    if (closed)
+        nsvg__lineTo(p, p->sx, p->sy);
+
+    float orig_x = 0.0;
+    float orig_y = 0.0;
+
+    int idx = 0;
+    float norm_f = p->width > p->height ? p->width : p->height;
+    // Transform path.
+    for (i = 0; i < p->npts; ++i)
+    {
+        nsvg__xformPoint(&pts[(i * 2) % 8], &pts[(i * 2 + 1) % 8], p->pts[i * 2], p->pts[i * 2 + 1], xform->xform);
+        pts[(i * 2 + 0) % 8] = ((pts[(i * 2 + 0) % 8] / norm_f) * p->scale) + p->x0;
+        pts[(i * 2 + 1) % 8] = ((pts[(i * 2 + 1) % 8] / norm_f) * p->scale) + p->y0;
+        /* First point starts the drawing */
+        if (i == 0)
+        {
+            orig_x = pts[0];
+            orig_y = pts[1];
+            if (p->mtx != orig_x || p->mty != orig_y)
+            {
+                p->endPathCb();
+
+                if (orig_x > 0.0 && orig_x < 1.0 && orig_y > 0.0 && orig_y < 4.0)
+                    p->moveToCb(orig_x, orig_y);
+                p->mtx = orig_x;
+                p->mty = orig_y;
+            }
+            p->beginPathCb();
+        }
+
+        /* If we have at least 4 points */
+        if (i >= 3)
+        {
+            /* A bezier every 3 points, linking with one from before */
+            if ((i % 3) == 0)
+            {
+                nsvg__cubicBez(p, pts[(0 + idx) % 8], pts[(1 + idx) % 8], pts[(2 + idx) % 8], pts[(3 + idx) % 8], pts[(4 + idx) % 8],
+                               pts[(5 + idx) % 8], pts[(6 + idx) % 8], pts[(7 + idx) % 8], 0.00015 * p->scale, 0);
+                idx = (idx + 8 - 2) % 8;
+            }
+        }
+    }
+
+    if (closed)
+    {
+        p->mtx = orig_x;
+        p->mty = orig_y;
+    }
+
+    return;
+}
+
+static int nsvg__parseNumber(FILE *fp, char quote, int begin, char *it, const int size)
+{
+    const int last = size - 1;
+    int i = 0;
+    int s = begin;
+
+    // sign
+    if (s == '-' || s == '+')
+    {
+        if (i < last)
+            it[i++] = s;
+        s = fgetc(fp);
+    }
+    // integer part
+    while (s != EOF && s != quote && nsvg__isdigit(s))
+    {
+        if (i < last)
+            it[i++] = s;
+        s = fgetc(fp);
+    }
+    if (s == '.')
+    {
+        // decimal point
+        if (i < last)
+            it[i++] = s;
+        s = fgetc(fp);
+        // fraction part
+        while (s != EOF && s != quote && nsvg__isdigit(s))
+        {
+            if (i < last)
+                it[i++] = s;
+            s = fgetc(fp);
+        }
+    }
+    // exponent
+    if (s == 'e' || s == 'E')
+    {
+        if (i < last)
+            it[i++] = s;
+        s = fgetc(fp);
+        if (s == '-' || s == '+')
+        {
+            if (i < last)
+                it[i++] = s;
+            s = fgetc(fp);
+        }
+        while (s != EOF && s != quote && nsvg__isdigit(s))
+        {
+            if (i < last)
+                it[i++] = s;
+            s = fgetc(fp);
+        }
+    }
+    it[i] = '\0';
+    return s;
+}
+
+static float nsvg__parseCoordinate(FILE *fp, char quote)
+{
+    static char attr_value[32];
+    int s = fgetc(fp);
+    // Store value and find the end of it.
+    int attr_value_len = 0;
+    while (s != EOF && s != '>' && s != quote)
+    {
+        // Only save up to the buffer, but skip to the next quote
+        if (attr_value_len < (32 - 2))
+        {
+            attr_value[attr_value_len++] = s;
+        }
+        s = fgetc(fp);
+    }
+    attr_value[attr_value_len++] = '\0';
+
+    float value = 0.0f;
+    char units[32] = "";
+    sscanf(attr_value, "%f%s", &value, units);
+    return value;
+}
+
+static int nsvg__parseTransformArgs(FILE *fp, char quote, float *args, int maxNa, int *na)
+{
+    char it[64];
+    int s = fgetc(fp);
+
+    *na = 0;
+    while (s != EOF && s != quote && s != '(')
+        s = fgetc(fp);
+    if (s == EOF || s == quote)
+        return s;
+
+    while (s != EOF && s != quote && s != ')')
+    {
+        if (s == '-' || s == '+' || s == '.' || nsvg__isdigit(s))
+        {
+            if (*na >= maxNa)
+                return s;
+            s = nsvg__parseNumber(fp, quote, s, it, 64);
+            args[(*na)++] = (float)atof(it);
+        }
+        else
+        {
+            s = fgetc(fp);
+        }
+    }
+    return s;
+}
+
+static int nsvg__parseMatrix(float *xform, FILE *fp, char quote)
+{
+    float t[6];
+    int na = 0;
+    int s = nsvg__parseTransformArgs(fp, quote, t, 6, &na);
+    if (na != 6)
+        return s;
+    memcpy(xform, t, sizeof(float) * 6);
+    return s;
+}
+
+static int nsvg__parseTranslate(float *xform, FILE *fp, char quote)
+{
+    float args[2];
+    float t[6];
+    int na = 0;
+    int s = nsvg__parseTransformArgs(fp, quote, args, 2, &na);
+    if (na == 1)
+        args[1] = 0.0;
+
+    nsvg__xformSetTranslation(t, args[0], args[1]);
+    memcpy(xform, t, sizeof(float) * 6);
+    return s;
+}
+
+static int nsvg__parseScale(float *xform, FILE *fp, char quote)
+{
+    float args[2];
+    int na = 0;
+    float t[6];
+    int s = nsvg__parseTransformArgs(fp, quote, args, 2, &na);
+    if (na == 1)
+        args[1] = args[0];
+    nsvg__xformSetScale(t, args[0], args[1]);
+    memcpy(xform, t, sizeof(float) * 6);
+    return s;
+}
+
+static int nsvg__parseSkewX(float *xform, FILE *fp, char quote)
+{
+    float args[1];
+    int na = 0;
+    float t[6];
+    int s = nsvg__parseTransformArgs(fp, quote, args, 1, &na);
+    nsvg__xformSetSkewX(t, args[0] / 180.0f * NSVG_PI);
+    memcpy(xform, t, sizeof(float) * 6);
+    return s;
+}
+
+static int nsvg__parseSkewY(float *xform, FILE *fp, char quote)
+{
+    float args[1];
+    int na = 0;
+    float t[6];
+    int s = nsvg__parseTransformArgs(fp, quote, args, 1, &na);
+    nsvg__xformSetSkewY(t, args[0] / 180.0f * NSVG_PI);
+    memcpy(xform, t, sizeof(float) * 6);
+    return s;
+}
+
+static int nsvg__parseRotate(float *xform, FILE *fp, char quote)
+{
+    float args[3];
+    int na = 0;
+    float m[6];
+    float t[6];
+    int s = nsvg__parseTransformArgs(fp, quote, args, 3, &na);
+    if (na == 1)
+        args[1] = args[2] = 0.0f;
+    nsvg__xformIdentity(m);
+
+    if (na > 1)
+    {
+        nsvg__xformSetTranslation(t, -args[1], -args[2]);
+        nsvg__xformMultiply(m, t);
+    }
+
+    nsvg__xformSetRotation(t, args[0] / 180.0f * NSVG_PI);
+    nsvg__xformMultiply(m, t);
+
+    if (na > 1)
+    {
+        nsvg__xformSetTranslation(t, args[1], args[2]);
+        nsvg__xformMultiply(m, t);
+    }
+
+    memcpy(xform, m, sizeof(float) * 6);
+    return s;
+}
+
+static int nsvg__parseTransform(float *xform, FILE *fp, char quote)
+{
+    int s;
+    float t[6];
+
+    nsvg__xformIdentity(xform);
+    while (1)
+    {
+        s = fgetc(fp);
+
+        if (s == EOF || s == '>' || s == quote)
+            break;
+
+        if (s == 'm')
+        {
+            if (fgetc(fp) != 'a')
+                continue;
+            if (fgetc(fp) != 't')
+                continue;
+            if (fgetc(fp) != 'r')
+                continue;
+            if (fgetc(fp) != 'i')
+                continue;
+            if (fgetc(fp) != 'x')
+                continue;
+            s = nsvg__parseMatrix(t, fp, quote);
+            nsvg__xformPremultiply(xform, t);
+        }
+        else if (s == 'r')
+        {
+            if (fgetc(fp) != 'o')
+                continue;
+            if (fgetc(fp) != 't')
+                continue;
+            if (fgetc(fp) != 'a')
+                continue;
+            if (fgetc(fp) != 't')
+                continue;
+            if (fgetc(fp) != 'e')
+                continue;
+            s = nsvg__parseRotate(t, fp, quote);
+            nsvg__xformPremultiply(xform, t);
+        }
+        else if (s == 't')
+        {
+            if (fgetc(fp) != 'r')
+                continue;
+            if (fgetc(fp) != 'a')
+                continue;
+            if (fgetc(fp) != 'n')
+                continue;
+            if (fgetc(fp) != 's')
+                continue;
+            if (fgetc(fp) != 'l')
+                continue;
+            if (fgetc(fp) != 'a')
+                continue;
+            if (fgetc(fp) != 't')
+                continue;
+            if (fgetc(fp) != 'e')
+                continue;
+            s = nsvg__parseTranslate(t, fp, quote);
+            nsvg__xformPremultiply(xform, t);
+        }
+        else if (s == 's')
+        {
+            if (fgetc(fp) == 'c')
+            {
+                if (fgetc(fp) != 'a')
+                    continue;
+                if (fgetc(fp) != 'l')
+                    continue;
+                if (fgetc(fp) != 'e')
+                    continue;
+                s = nsvg__parseScale(t, fp, quote);
+                nsvg__xformPremultiply(xform, t);
+            }
+            if (fgetc(fp) == 'k')
+            {
+                if (fgetc(fp) != 'e')
+                    continue;
+                if (fgetc(fp) != 'w')
+                    continue;
+
+                if (fgetc(fp) == 'X')
+                {
+                    s = nsvg__parseSkewX(t, fp, quote);
+                    nsvg__xformPremultiply(xform, t);
+                }
+                else if (fgetc(fp) == 'Y')
+                {
+                    s = nsvg__parseSkewY(t, fp, quote);
+                    nsvg__xformPremultiply(xform, t);
+                }
+                else
+                    continue;
+            }
+            else
+                continue;
+        }
+    }
+
+    return s;
+}
+
+static int nsvg__getArgsPerElement(char cmd)
+{
+    switch (cmd)
+    {
+    case 'v':
+    case 'V':
+    case 'h':
+    case 'H':
+        return 1;
+    case 'm':
+    case 'M':
+    case 'l':
+    case 'L':
+    case 't':
+    case 'T':
+        return 2;
+    case 'q':
+    case 'Q':
+    case 's':
+    case 'S':
+        return 4;
+    case 'c':
+    case 'C':
+        return 6;
+    case 'a':
+    case 'A':
+        return 7;
+    }
+    return 0;
+}
+
+static void nsvg__pathMoveTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+    if (rel)
+    {
+        *cpx += args[0];
+        *cpy += args[1];
+    }
+    else
+    {
+        *cpx = args[0];
+        *cpy = args[1];
+    }
+    nsvg__moveTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+    if (rel)
+    {
+        *cpx += args[0];
+        *cpy += args[1];
+    }
+    else
+    {
+        *cpx = args[0];
+        *cpy = args[1];
+    }
+    nsvg__lineTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathHLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+    if (rel)
+        *cpx += args[0];
+    else
+        *cpx = args[0];
+    nsvg__lineTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathVLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+    if (rel)
+        *cpy += args[0];
+    else
+        *cpy = args[0];
+    nsvg__lineTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathCubicBezTo(NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+    float x2, y2, cx1, cy1, cx2, cy2;
+
+    if (rel)
+    {
+        cx1 = *cpx + args[0];
+        cy1 = *cpy + args[1];
+        cx2 = *cpx + args[2];
+        cy2 = *cpy + args[3];
+        x2 = *cpx + args[4];
+        y2 = *cpy + args[5];
+    }
+    else
+    {
+        cx1 = args[0];
+        cy1 = args[1];
+        cx2 = args[2];
+        cy2 = args[3];
+        x2 = args[4];
+        y2 = args[5];
+    }
+
+    nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+    *cpx2 = cx2;
+    *cpy2 = cy2;
+    *cpx = x2;
+    *cpy = y2;
+}
+
+static void nsvg__pathCubicBezShortTo(NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+    float x1, y1, x2, y2, cx1, cy1, cx2, cy2;
+
+    x1 = *cpx;
+    y1 = *cpy;
+    if (rel)
+    {
+        cx2 = *cpx + args[0];
+        cy2 = *cpy + args[1];
+        x2 = *cpx + args[2];
+        y2 = *cpy + args[3];
+    }
+    else
+    {
+        cx2 = args[0];
+        cy2 = args[1];
+        x2 = args[2];
+        y2 = args[3];
+    }
+
+    cx1 = 2 * x1 - *cpx2;
+    cy1 = 2 * y1 - *cpy2;
+
+    nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+    *cpx2 = cx2;
+    *cpy2 = cy2;
+    *cpx = x2;
+    *cpy = y2;
+}
+
+static void nsvg__pathQuadBezTo(NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+    float x1, y1, x2, y2, cx, cy;
+    float cx1, cy1, cx2, cy2;
+
+    x1 = *cpx;
+    y1 = *cpy;
+    if (rel)
+    {
+        cx = *cpx + args[0];
+        cy = *cpy + args[1];
+        x2 = *cpx + args[2];
+        y2 = *cpy + args[3];
+    }
+    else
+    {
+        cx = args[0];
+        cy = args[1];
+        x2 = args[2];
+        y2 = args[3];
+    }
+
+    // Convert to cubic bezier
+    cx1 = x1 + 2.0f / 3.0f * (cx - x1);
+    cy1 = y1 + 2.0f / 3.0f * (cy - y1);
+    cx2 = x2 + 2.0f / 3.0f * (cx - x2);
+    cy2 = y2 + 2.0f / 3.0f * (cy - y2);
+
+    nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+    *cpx2 = cx;
+    *cpy2 = cy;
+    *cpx = x2;
+    *cpy = y2;
+}
+
+static void nsvg__pathQuadBezShortTo(NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+    float x1, y1, x2, y2, cx, cy;
+    float cx1, cy1, cx2, cy2;
+
+    x1 = *cpx;
+    y1 = *cpy;
+    if (rel)
+    {
+        x2 = *cpx + args[0];
+        y2 = *cpy + args[1];
+    }
+    else
+    {
+        x2 = args[0];
+        y2 = args[1];
+    }
+
+    cx = 2 * x1 - *cpx2;
+    cy = 2 * y1 - *cpy2;
+
+    // Convert to cubix bezier
+    cx1 = x1 + 2.0f / 3.0f * (cx - x1);
+    cy1 = y1 + 2.0f / 3.0f * (cy - y1);
+    cx2 = x2 + 2.0f / 3.0f * (cx - x2);
+    cy2 = y2 + 2.0f / 3.0f * (cy - y2);
+
+    nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+    *cpx2 = cx;
+    *cpy2 = cy;
+    *cpx = x2;
+    *cpy = y2;
+}
+
+static float nsvg__sqr(float x)
+{
+    return x * x;
+}
+static float nsvg__vmag(float x, float y)
+{
+    return sqrtf(x * x + y * y);
+}
+
+static float nsvg__vecrat(float ux, float uy, float vx, float vy)
+{
+    return (ux * vx + uy * vy) / (nsvg__vmag(ux, uy) * nsvg__vmag(vx, vy));
+}
+
+static float nsvg__vecang(float ux, float uy, float vx, float vy)
+{
+    float r = nsvg__vecrat(ux, uy, vx, vy);
+    if (r < -1.0f)
+        r = -1.0f;
+    if (r > 1.0f)
+        r = 1.0f;
+    return ((ux * vy < uy * vx) ? -1.0f : 1.0f) * acosf(r);
+}
+
+static void nsvg__pathArcTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+    // Ported from canvg (https://code.google.com/p/canvg/)
+    float rx, ry, rotx;
+    float x1, y1, x2, y2, cx, cy, dx, dy, d;
+    float x1p, y1p, cxp, cyp, s, sa, sb;
+    float ux, uy, vx, vy, a1, da;
+    float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6];
+    float sinrx, cosrx;
+    int fa, fs;
+    int i, ndivs;
+    float hda, kappa;
+
+    rx = fabsf(args[0]);                // y radius
+    ry = fabsf(args[1]);                // x radius
+    rotx = args[2] / 180.0f * NSVG_PI;  // x rotation engle
+    fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc
+    fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction
+    x1 = *cpx;                          // start point
+    y1 = *cpy;
+    if (rel)
+    { // end point
+        x2 = *cpx + args[5];
+        y2 = *cpy + args[6];
+    }
+    else
+    {
+        x2 = args[5];
+        y2 = args[6];
+    }
+
+    dx = x1 - x2;
+    dy = y1 - y2;
+    d = sqrtf(dx * dx + dy * dy);
+    if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f)
+    {
+        // The arc degenerates to a line
+        nsvg__lineTo(p, x2, y2);
+        *cpx = x2;
+        *cpy = y2;
+        return;
+    }
+
+    sinrx = sinf(rotx);
+    cosrx = cosf(rotx);
+
+    // Convert to center point parameterization.
+    // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+    // 1) Compute x1', y1'
+    x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f;
+    y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f;
+    d = nsvg__sqr(x1p) / nsvg__sqr(rx) + nsvg__sqr(y1p) / nsvg__sqr(ry);
+    if (d > 1)
+    {
+        d = sqrtf(d);
+        rx *= d;
+        ry *= d;
+    }
+    // 2) Compute cx', cy'
+    s = 0.0f;
+    sa = nsvg__sqr(rx) * nsvg__sqr(ry) - nsvg__sqr(rx) * nsvg__sqr(y1p) - nsvg__sqr(ry) * nsvg__sqr(x1p);
+    sb = nsvg__sqr(rx) * nsvg__sqr(y1p) + nsvg__sqr(ry) * nsvg__sqr(x1p);
+    if (sa < 0.0f)
+        sa = 0.0f;
+    if (sb > 0.0f)
+        s = sqrtf(sa / sb);
+    if (fa == fs)
+        s = -s;
+    cxp = s * rx * y1p / ry;
+    cyp = s * -ry * x1p / rx;
+
+    // 3) Compute cx,cy from cx',cy'
+    cx = (x1 + x2) / 2.0f + cosrx * cxp - sinrx * cyp;
+    cy = (y1 + y2) / 2.0f + sinrx * cxp + cosrx * cyp;
+
+    // 4) Calculate theta1, and delta theta.
+    ux = (x1p - cxp) / rx;
+    uy = (y1p - cyp) / ry;
+    vx = (-x1p - cxp) / rx;
+    vy = (-y1p - cyp) / ry;
+    a1 = nsvg__vecang(1.0f, 0.0f, ux, uy); // Initial angle
+    da = nsvg__vecang(ux, uy, vx, vy);     // Delta angle
+
+    //  if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI;
+    //  if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0;
+
+    if (fa)
+    {
+        // Choose large arc
+        if (da > 0.0f)
+            da = da - 2 * NSVG_PI;
+        else
+            da = 2 * NSVG_PI + da;
+    }
+
+    // Approximate the arc using cubic spline segments.
+    t[0] = cosrx;
+    t[1] = sinrx;
+    t[2] = -sinrx;
+    t[3] = cosrx;
+    t[4] = cx;
+    t[5] = cy;
+
+    // Split arc into max 90 degree segments.
+    // The loop assumes an iteration per end point (including start and end), this
+    // +1.
+    ndivs = (int)(fabsf(da) / (NSVG_PI * 0.5f) + 1.0f);
+    hda = (da / (float)ndivs) / 2.0f;
+    kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda));
+    if (da < 0.0f)
+        kappa = -kappa;
+
+    for (i = 0; i <= ndivs; i++)
+    {
+        a = a1 + da * (i / (float)ndivs);
+        dx = cosf(a);
+        dy = sinf(a);
+        nsvg__xformPoint(&x, &y, dx * rx, dy * ry, t);                      // position
+        nsvg__xformVec(&tanx, &tany, -dy * rx * kappa, dx * ry * kappa, t); // tangent
+        if (i > 0)
+            nsvg__cubicBezTo(p, px + ptanx, py + ptany, x - tanx, y - tany, x, y);
+        px = x;
+        py = y;
+        ptanx = tanx;
+        ptany = tany;
+    }
+
+    *cpx = x2;
+    *cpy = y2;
+}
+
+static void nsvg__forceLowMemory(NSVGparser *p, char closedFlag)
+{
+    if (p->npts > 0)
+    {
+        nsvg__drawPath(p, closedFlag);
+        p->pts[0] = p->pts[p->npts * 2 - 2];
+        p->pts[1] = p->pts[p->npts * 2 - 1];
+        p->npts = 1;
+    }
+}
+
+static void nsvg__parsePathDescriptors(NSVGparser *p, FILE *fp, char quote)
+{
+    char cmd = '\0';
+    float args[10];
+    int nargs;
+    int rargs = 0;
+    float cpx, cpy, cpx2, cpy2;
+    char closedFlag;
+    char item[64];
+
+    nsvg__resetPath(p);
+    cpx = 0;
+    cpy = 0;
+    cpx2 = 0;
+    cpy2 = 0;
+    closedFlag = 0;
+    nargs = 0;
+
+    int s = fgetc(fp);
+
+    while (s != EOF && s != '>' && s != quote)
+    {
+        item[0] = '\0';
+        // Skip white spaces and commas
+        while (s != EOF && s != '>' && s != quote && (nsvg__isspace(s) || s == ','))
+            s = fgetc(fp);
+        if (s == EOF || s == '>' || s == quote)
+            break;
+        if (s == '-' || s == '+' || s == '.' || nsvg__isdigit(s))
+        {
+            s = nsvg__parseNumber(fp, quote, s, item, 64);
+        }
+        else
+        {
+            // Parse command
+            item[0] = s;
+            item[1] = '\0';
+            s = fgetc(fp);
+        }
+        if (!*item)
+            break;
+        if (nsvg__isnum(item[0]))
+        {
+            if (nargs < 10)
+                args[nargs++] = (float)atof(item);
+            if (nargs >= rargs)
+            {
+                switch (cmd)
+                {
+                case 'm':
+                case 'M':
+                    nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0);
+                    // Moveto can be followed by multiple coordinate pairs,
+                    // which should be treated as linetos.
+                    cmd = (cmd == 'm') ? 'l' : 'L';
+                    rargs = nsvg__getArgsPerElement(cmd);
+                    cpx2 = cpx;
+                    cpy2 = cpy;
+                    break;
+                case 'l':
+                case 'L':
+                    nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0);
+                    cpx2 = cpx;
+                    cpy2 = cpy;
+                    break;
+                case 'H':
+                case 'h':
+                    nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0);
+                    cpx2 = cpx;
+                    cpy2 = cpy;
+                    break;
+                case 'V':
+                case 'v':
+                    nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0);
+                    cpx2 = cpx;
+                    cpy2 = cpy;
+                    break;
+                case 'C':
+                case 'c':
+                    nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0);
+                    break;
+                case 'S':
+                case 's':
+                    nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0);
+                    break;
+                case 'Q':
+                case 'q':
+                    nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0);
+                    break;
+                case 'T':
+                case 't':
+                    nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0);
+                    break;
+                case 'A':
+                case 'a':
+                    nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0);
+                    cpx2 = cpx;
+                    cpy2 = cpy;
+                    break;
+                default:
+                    if (nargs >= 2)
+                    {
+                        cpx = args[nargs - 2];
+                        cpy = args[nargs - 1];
+                        cpx2 = cpx;
+                        cpy2 = cpy;
+                    }
+                    break;
+                }
+                nargs = 0;
+            }
+        }
+        else
+        {
+            cmd = item[0];
+            rargs = nsvg__getArgsPerElement(cmd);
+            if (cmd == 'M' || cmd == 'm')
+            {
+                // Commit path.
+                if (p->npts > 0)
+                    nsvg__drawPath(p, closedFlag);
+                // Start new subpath.
+                nsvg__resetPath(p);
+                closedFlag = 0;
+                nargs = 0;
+            }
+            else if (cmd == 'Z' || cmd == 'z')
+            {
+                closedFlag = 1;
+                // Commit path.
+                if (p->npts > 0)
+                {
+                    // Move current point to first point
+                    cpx = p->sx;
+                    cpy = p->sy;
+                    cpx2 = cpx;
+                    cpy2 = cpy;
+                    nsvg__drawPath(p, closedFlag);
+                }
+                // Start new subpath.
+                nsvg__resetPath(p);
+                nsvg__moveTo(p, cpx, cpy);
+                closedFlag = 0;
+                nargs = 0;
+            }
+            else
+            {
+                nsvg__forceLowMemory(p, closedFlag);
+            }
+        }
+    }
+    // Commit path.
+    if (p->npts)
+        nsvg__drawPath(p, closedFlag);
+}
+
+static void nsvg__parsePolyPoints(NSVGparser *p, FILE *fp, char quote)
+{
+    float args[2];
+    int nargs, npts = 0;
+    char item[64];
+    int s = fgetc(fp);
+
+    nargs = 0;
+    while (s != EOF && s != '>' && s != quote)
+    {
+        item[0] = '\0';
+        // Skip white spaces and commas
+        while (s != EOF && s != '>' && s != quote && (nsvg__isspace(s) || s == ','))
+            s = fgetc(fp);
+        if (s == EOF || s == '>' || s == quote)
+            break;
+        if (s == '-' || s == '+' || s == '.' || nsvg__isdigit(s))
+        {
+            s = nsvg__parseNumber(fp, quote, s, item, 64);
+        }
+        else
+        {
+            // Parse command
+            item[0] = s;
+            item[1] = '\0';
+            s = fgetc(fp);
+        }
+        args[nargs++] = (float)atof(item);
+        if (nargs >= 2)
+        {
+            if (npts == 0)
+                nsvg__moveTo(p, args[0], args[1]);
+            else
+                nsvg__lineTo(p, args[0], args[1]);
+            nargs = 0;
+            npts++;
+        }
+    }
+}
+
+static void nsvg__parseAttribs(NSVGparser *p, FILE *fp, int *end = NULL)
+{
+    memset(&p->attrs, 0, sizeof(NSVGCommonAttrs));
+    /* Marks not set */
+    p->attrs.rx = -1.0;
+    p->attrs.rx = -1.0;
+    static char attr_name[32];
+
+    int s = fgetc(fp);
+
+    // Get attribs
+    while (!(end && *end) && s != '>' && s != EOF)
+    {
+        // Skip white space before the attrib name
+        while (s != '>' && nsvg__isspace(s))
+            s = fgetc(fp);
+        if (s == '>' || s == EOF)
+            return;
+        if (s == '/')
+        {
+            if (end)
+                *end = 1;
+            return;
+        }
+
+        int attr_name_len = 0;
+        // Find end of the attrib name.
+        while (s != EOF && s != '>' && !nsvg__isspace(s) && s != '=' && attr_name_len < (32 - 2))
+        {
+            attr_name[attr_name_len++] = s;
+            s = fgetc(fp);
+        }
+        attr_name[attr_name_len++] = '\0';
+
+        // Skip until the beginning of the value.
+        while (s != EOF && s != '>' && s != '\"' && s != '\'')
+            s = fgetc(fp);
+        if (s == '>')
+            return;
+
+        if (attr_name_len)
+        {
+            if (strcmp(attr_name, "d") == 0)
+            {
+                nsvg__parsePathDescriptors(p, fp, s);
+            }
+            else if (strcmp(attr_name, "points") == 0)
+            {
+                nsvg__parsePolyPoints(p, fp, s);
+            }
+            else if (strcmp(attr_name, "transform") == 0)
+            {
+                NSVGxform *xform = nsvg__getCurrentXForm(p);
+                if (xform)
+                {
+                    float xform_floats[6];
+                    nsvg__parseTransform(xform_floats, fp, s);
+                    nsvg__xformPremultiply(xform->xform, xform_floats);
+                }
+            }
+            else
+            {
+                if (strcmp(attr_name, "r") == 0)
+                    p->attrs.r = nsvg__parseCoordinate(fp, s);
+                else if (strcmp(attr_name, "width") == 0)
+                    p->attrs.width = nsvg__parseCoordinate(fp, s);
+                else if (strcmp(attr_name, "height") == 0)
+                    p->attrs.height = nsvg__parseCoordinate(fp, s);
+                else if (strcmp(attr_name, "x") == 0)
+                    p->attrs.x = nsvg__parseCoordinate(fp, s);
+                else if (strcmp(attr_name, "y") == 0)
+                    p->attrs.y = nsvg__parseCoordinate(fp, s);
+                else if (strcmp(attr_name, "rx") == 0)
+                    p->attrs.rx = fabsf(nsvg__parseCoordinate(fp, s));
+                else if (strcmp(attr_name, "ry") == 0)
+                    p->attrs.ry = fabsf(nsvg__parseCoordinate(fp, s));
+                else if (strcmp(attr_name, "cx") == 0)
+                    p->attrs.cx = fabsf(nsvg__parseCoordinate(fp, s));
+                else if (strcmp(attr_name, "cy") == 0)
+                    p->attrs.cy = fabsf(nsvg__parseCoordinate(fp, s));
+                else if (strcmp(attr_name, "x1") == 0)
+                    p->attrs.x1 = nsvg__parseCoordinate(fp, s);
+                else if (strcmp(attr_name, "y1") == 0)
+                    p->attrs.y1 = nsvg__parseCoordinate(fp, s);
+                else if (strcmp(attr_name, "x2") == 0)
+                    p->attrs.x2 = nsvg__parseCoordinate(fp, s);
+                else if (strcmp(attr_name, "y2") == 0)
+                    p->attrs.y2 = nsvg__parseCoordinate(fp, s);
+                else
+                {
+                    // Consume the attribute without saving it
+                    char quote = s;
+                    s = fgetc(fp);
+                    while (s != EOF && s != '>' && s != quote)
+                    {
+                        s = fgetc(fp);
+                    }
+                }
+            }
+        }
+        s = fgetc(fp);
+    }
+}
+
+static void nsvg__parsePath(NSVGparser *p, FILE *input, int *is_end_tag)
+{
+    nsvg__parseAttribs(p, input, is_end_tag);
+}
+
+static void nsvg__parseRect(NSVGparser *p, FILE *input)
+{
+    nsvg__parseAttribs(p, input);
+
+    float x = p->attrs.x;
+    float y = p->attrs.y;
+    float w = p->attrs.width;
+    float h = p->attrs.height;
+    float rx = p->attrs.rx;
+    float ry = p->attrs.ry;
+
+    if (rx < 0.0f && ry > 0.0f)
+        rx = ry;
+    if (ry < 0.0f && rx > 0.0f)
+        ry = rx;
+    if (rx < 0.0f)
+        rx = 0.0f;
+    if (ry < 0.0f)
+        ry = 0.0f;
+    if (rx > w / 2.0f)
+        rx = w / 2.0f;
+    if (ry > h / 2.0f)
+        ry = h / 2.0f;
+
+    if (w != 0.0f && h != 0.0f)
+    {
+        nsvg__resetPath(p);
+
+        if (rx < 0.00001f || ry < 0.0001f)
+        {
+            nsvg__moveTo(p, x, y);
+            nsvg__lineTo(p, x + w, y);
+            nsvg__lineTo(p, x + w, y + h);
+            nsvg__lineTo(p, x, y + h);
+        }
+        else
+        {
+            // Rounded rectangle
+            nsvg__moveTo(p, x + rx, y);
+            nsvg__lineTo(p, x + w - rx, y);
+            nsvg__cubicBezTo(p, x + w - rx * (1 - NSVG_KAPPA90), y, x + w, y + ry * (1 - NSVG_KAPPA90), x + w, y + ry);
+            nsvg__lineTo(p, x + w, y + h - ry);
+            nsvg__cubicBezTo(p, x + w, y + h - ry * (1 - NSVG_KAPPA90), x + w - rx * (1 - NSVG_KAPPA90), y + h, x + w - rx, y + h);
+            nsvg__lineTo(p, x + rx, y + h);
+            nsvg__cubicBezTo(p, x + rx * (1 - NSVG_KAPPA90), y + h, x, y + h - ry * (1 - NSVG_KAPPA90), x, y + h - ry);
+            nsvg__lineTo(p, x, y + ry);
+            nsvg__cubicBezTo(p, x, y + ry * (1 - NSVG_KAPPA90), x + rx * (1 - NSVG_KAPPA90), y, x + rx, y);
+        }
+
+        nsvg__drawPath(p, 1);
+    }
+}
+
+static void nsvg__parseCircle(NSVGparser *p, FILE *input)
+{
+    nsvg__parseAttribs(p, input);
+
+    float cx = p->attrs.cx;
+    float cy = p->attrs.cy;
+    float r = p->attrs.r;
+
+    if (r > 0.0f)
+    {
+        nsvg__resetPath(p);
+
+        nsvg__moveTo(p, cx + r, cy);
+        nsvg__cubicBezTo(p, cx + r, cy + r * NSVG_KAPPA90, cx + r * NSVG_KAPPA90, cy + r, cx, cy + r);
+        nsvg__cubicBezTo(p, cx - r * NSVG_KAPPA90, cy + r, cx - r, cy + r * NSVG_KAPPA90, cx - r, cy);
+        nsvg__cubicBezTo(p, cx - r, cy - r * NSVG_KAPPA90, cx - r * NSVG_KAPPA90, cy - r, cx, cy - r);
+        nsvg__cubicBezTo(p, cx + r * NSVG_KAPPA90, cy - r, cx + r, cy - r * NSVG_KAPPA90, cx + r, cy);
+
+        nsvg__drawPath(p, 1);
+    }
+}
+
+static void nsvg__parseEllipse(NSVGparser *p, FILE *input)
+{
+    nsvg__parseAttribs(p, input);
+
+    float cx = p->attrs.cx;
+    float cy = p->attrs.cy;
+    float rx = p->attrs.rx;
+    float ry = p->attrs.ry;
+
+    if (rx > 0.0f && ry > 0.0f)
+    {
+
+        nsvg__resetPath(p);
+
+        nsvg__moveTo(p, cx + rx, cy);
+        nsvg__cubicBezTo(p, cx + rx, cy + ry * NSVG_KAPPA90, cx + rx * NSVG_KAPPA90, cy + ry, cx, cy + ry);
+        nsvg__cubicBezTo(p, cx - rx * NSVG_KAPPA90, cy + ry, cx - rx, cy + ry * NSVG_KAPPA90, cx - rx, cy);
+        nsvg__cubicBezTo(p, cx - rx, cy - ry * NSVG_KAPPA90, cx - rx * NSVG_KAPPA90, cy - ry, cx, cy - ry);
+        nsvg__cubicBezTo(p, cx + rx * NSVG_KAPPA90, cy - ry, cx + rx, cy - ry * NSVG_KAPPA90, cx + rx, cy);
+
+        nsvg__drawPath(p, 1);
+    }
+}
+
+static void nsvg__parseLine(NSVGparser *p, FILE *input)
+{
+    nsvg__parseAttribs(p, input);
+
+    float x1 = p->attrs.x1;
+    float y1 = p->attrs.y1;
+    float x2 = p->attrs.x2;
+    float y2 = p->attrs.y2;
+
+    nsvg__resetPath(p);
+
+    nsvg__moveTo(p, x1, y1);
+    nsvg__lineTo(p, x2, y2);
+
+    nsvg__drawPath(p, 0);
+}
+
+static void nsvg__parsePoly(NSVGparser *p, FILE *input, int closeFlag)
+{
+    nsvg__resetPath(p);
+    nsvg__parseAttribs(p, input);
+    nsvg__drawPath(p, (char)closeFlag);
+}
+
+static void nsvg__parseSVG(NSVGparser *p, FILE *input)
+{
+    nsvg__parseAttribs(p, input);
+    p->width = p->attrs.width;
+    p->height = p->attrs.height;
+}
+
+static void nsvg__parseGroup(NSVGparser *p, FILE *input, int *is_end_tag)
+{
+    nsvg__parseAttribs(p, input, is_end_tag);
+}
+
+static void nsvg__parseXML(FILE *fp, NSVGparser *p)
+{
+    int s = fgetc(fp);
+    while (EOF != s)
+    {
+        if (s == '<')
+        {
+            s = fgetc(fp);
+
+            // Skip white space after the '<'
+            while (s != EOF && s != '<' && nsvg__isspace(s))
+                s = fgetc(fp);
+
+            // Start of a content or new tag.
+            int start = 0;
+            int end = 0;
+
+            // Check if the tag is end tag
+            if (s == '/')
+            {
+                s = fgetc(fp);
+                end = 1;
+            }
+            else
+            {
+                start = 1;
+            }
+
+            // Skip comments, data and preprocessor stuff.
+            if (s == EOF || s == '?' || s == '!')
+                continue;
+
+            // Get tag name
+            static const int max_tag_name_len = 16;
+            char tag_name[max_tag_name_len];
+            int tag_name_len = 0;
+            while (s != EOF && s != '<' && !nsvg__isspace(s) && tag_name_len < (max_tag_name_len - 2))
+            {
+                tag_name[tag_name_len++] = s;
+                s = fgetc(fp);
+            }
+            tag_name[tag_name_len++] = '\0';
+
+            if (start)
+            {
+                const char *known_tags[9] = { "g", "path", "rect", "circle", "ellipse", "line", "polyline", "polygon", "svg" };
+
+                int found = 0;
+                for (int i = 0; i < 9; i++)
+                {
+                    found += strcmp(tag_name, known_tags[i]) == 0;
+                    if (found)
+                        break;
+                }
+                if (!found)
+                    continue;
+
+                if (strcmp(tag_name, "g") == 0)
+                {
+                    nsvg__pushAttr(p);
+                    nsvg__parseGroup(p, fp, &end);
+                }
+                else if (strcmp(tag_name, "path") == 0)
+                {
+                    if (p->pathFlag) // Do not allow nested paths.
+                        continue;
+                    nsvg__pushAttr(p);
+                    nsvg__parsePath(p, fp, &end);
+                    nsvg__popAttr(p);
+                }
+                else if (strcmp(tag_name, "rect") == 0)
+                {
+                    nsvg__pushAttr(p);
+                    nsvg__parseRect(p, fp);
+                    nsvg__popAttr(p);
+                }
+                else if (strcmp(tag_name, "circle") == 0)
+                {
+                    nsvg__pushAttr(p);
+                    nsvg__parseCircle(p, fp);
+                    nsvg__popAttr(p);
+                }
+                else if (strcmp(tag_name, "ellipse") == 0)
+                {
+                    nsvg__pushAttr(p);
+                    nsvg__parseEllipse(p, fp);
+                    nsvg__popAttr(p);
+                }
+                else if (strcmp(tag_name, "line") == 0)
+                {
+                    nsvg__pushAttr(p);
+                    nsvg__parseLine(p, fp);
+                    nsvg__popAttr(p);
+                }
+                else if (strcmp(tag_name, "polyline") == 0)
+                {
+                    nsvg__pushAttr(p);
+                    nsvg__parsePoly(p, fp, 0);
+                    nsvg__popAttr(p);
+                }
+                else if (strcmp(tag_name, "polygon") == 0)
+                {
+                    nsvg__pushAttr(p);
+                    nsvg__parsePoly(p, fp, 1);
+                    nsvg__popAttr(p);
+                }
+                else if (strcmp(tag_name, "svg") == 0)
+                {
+                    nsvg__parseSVG(p, fp);
+                }
+            }
+
+            if (end)
+            {
+                if (strcmp(tag_name, "g") == 0)
+                {
+                    nsvg__popAttr(p);
+                }
+                else if (strcmp(tag_name, "path") == 0)
+                {
+                    p->pathFlag = 0;
+                }
+            }
+        }
+        s = fgetc(fp);
+
+        while (s != EOF && s != '<')
+            s = fgetc(fp);
+    }
+}
+
+// Draws SVG from a file
+void nsvgDrawFromFile(const char *filename, nsvgMoveToCb moveToCb, nsvgBeginPathCb beginPathCb, nsvgEndPathCb endPathCb, float scale,
+                      float x0, float y0)
+{
+    FILE *fp = NULL;
+
+    fp = fopen(filename, "rb");
+    if (!fp)
+        return;
+
+    NSVGparser p = {};
+
+    // Init style
+    nsvg__xformIdentity(p.xforms[0].xform);
+
+    p.moveToCb = moveToCb;
+    p.beginPathCb = beginPathCb;
+    p.endPathCb = endPathCb;
+    p.scale = scale;
+    p.x0 = x0;
+    p.y0 = y0;
+
+    nsvg__parseXML(fp, &p);
+
+    p.endPathCb();
+
+    free(p.pts);
+    fclose(fp);
+}