Wednesday, March 9, 2011

My Evalbot First Steps: Hosting a webpage From a USB stick

So for starters this is a contiunation of a previous post my evalbot first steps Part 1. In this part though we will be mounting a usb flash drive to host our web page.  Since the example provided does not work at all on the arm stellaris evalbot, we will be doing this from scratch except for the fat32 implementation.  So load up the LWIP project and lets get started.





Changing the Interupts
 So first thing we need to do is open our start up file it should be called something like startup_rvmdk.S.  Now look for your interrupt vector.  Look for the like DCD   lwIPEthernetIntHandler  ; Ethernet we will change that to DCD   IpInterupt   ; Ethernet Now look for the line that ends with ; USB0 it should be 2 lines lower.  We will replace IntDefaultHandler with USB0HostIntHandler.  What we are actually doing, is replacing the function that will get called when the interupt occurs to our own.  USB0HostIntHandler All ready exist within the USB drivers in the Stellaris SDK.  We are also replacing the Ethernet interrupt from the LWIP to our own.  The reason we are doing that is because right now, when ever we receive data from the Ethernet we call the interrupt.  Then we load the web page and send it back and we return to the normal execution.  The problem with that, is the the USB driver uses interrupt as well.  If we want to load data from the USB we can't be with in an interrupt routine because the USB will not interrupt the interrupt.  Now we also need to tell our startup code that the functions actually do exists some where in the code.  So scroll up just above the vector table and you should see an external deceleration section.  Simply add these 2 lines of code:
        EXTERN  USB0HostIntHandler
        EXTERN  IpInterupt


Getting the USB working

Lets start by getting the #includes out of the way.  You will need to add:

#include "usblib/usblib.h"
#include "usblib/usbmsc.h"
#include "usblib/host/usbhost.h"
#include "usblib/host/usbhmsc.h"
#include "driverlib/udma.h"
#include "fatfs/src/ff.h"


Next, you will need to add the USB lib to your project.  simply double click on the libraries folder. It should open a file browse window.  Navigate to the directory of the stellaris SDK/usblib/rvmdk. you will need to set the Files of type (at the bottom) to all files and add usblib.lib.

Now we need to add a bunch of stuff in our main().  You can put it in a function call initUSB() or something and call that from the main it might be cleaner but that's up to you.  So I added my USB init after the SysTick setup (ROM_SysTickIntEnable();).  The USB host for mast storage requires UDMA so first thing is to enable that.  3 simple lines should do it

    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UDMA);
    ROM_uDMAEnable();
    ROM_uDMAControlBaseSet(g_sDMAControlTable);

You will also need to define a global variable:

tDMAControlTable g_sDMAControlTable[6] __attribute__ ((aligned(1024)));

Now we need to enable the USB hardware.  The actually USB device is on GPIO portA.  If you look at the schematic, we also need GPIO portB.  This is used to indicate if we are Host mode or device mode.  So lets enable the GPIO we will also enable the USB0 device.

    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_USB0);


Now lets set the chip to USB host mode.

    ROM_GPIOPinTypeGPIOOutput(GPIO_PORTB_BASE, GPIO_PIN_0);
    ROM_GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_0, 0);

Finally lets configure the USB data pin and let the Driver know what pin to use.

    GPIOPinConfigure(GPIO_PA6_USB0EPEN);
    ROM_GPIOPinTypeUSBDigital(GPIO_PORTA_BASE, GPIO_PIN_6)
;

Now we need to tell the SDK drive that we want to use the Mass storage driver.  Keep note of where you are in your main.  When we come back to it we will be adding code there.  For now we will need to set a few things up.  First we need to define a char buffer for the Host controller.  128 should be big enough.  We also need a variable to store what USB is connected host or device.  You can use this one if you ever switch between USB host or device.  This will get changed in a call back.  Nest we will define an enum to hold the different states the USB can be in.  This way we know if its ready ect.  Also, we need to declare the variable that will hold the instance of the driver.  It MUST KEEP the same name as the fat implementation declares it as extern: g_ulMSCInstance .  We also will declare an array of USB host drivers and associate our call back with the driver.  Since we only wan the Mass storage driver, that is the only one we will put. Finally I will declare 3 functions used for call back.  I will go more in details as we define them.  Here are what my decelerations look like.

unsigned long g_ulMSCInstance = 0;
unsigned char hostControllerPool[128];
tUSBMode usbMode;
enum {
    STATE_NO_DEVICE,
    STATE_MSC_INIT,
    STATE_MSC_CONNECTED,
    STATE_UNKNOWN_DEVICE,
    STATE_POWER_FAULT
} usb_state;

void  USBEventCallback(void *eventData);
void ModeCallback(unsigned long ulIndex, tUSBMode eMode);
void MSCCallback(unsigned long ulInstance, unsigned long ulEvent, void *pvData);
//associate the call back 
DECLARE_EVENT_DRIVER(USBEventHandler, 0, 0, USBEventCallback);
static tUSBHostClassDriver const * const UsbClassDrivers[] =
{
    &g_USBHostMSCClassDriver,
    &USBEventHandler
};
#define NUM_CLASS_DRIVERS       (sizeof(UsbClassDrivers) / sizeof(*UsbClassDrivers))


Now we will define our call back functions. Let start with void  USBEventCallback(void *eventData);  This function is called at the lowest layer.  It gets called every type a USB device is plugged in removed or has power fault.  which ever the case we want to know what happen so our program know where we are.

void  USBEventCallback(void *eventData)
{
    tEventInfo *eventInfo;
    eventInfo = (tEventInfo *)eventData;
    switch(eventInfo->ulEvent) {
        case USB_EVENT_CONNECTED:
            usb_state = STATE_UNKNOWN_DEVICE;
            break;
        case USB_EVENT_DISCONNECTED:
            usb_state = STATE_NO_DEVICE;
            break;
        case USB_EVENT_POWER_FAULT:
            usb_state = STATE_POWER_FAULT;
            break;
        default:
            break;
    }
}

Next we got void ModeCallback(unsigned long ulIndex, tUSBMode eMode).  this specifies if a USB Devices is connected or Host.  Ill leave the switch but we don't really need it.  It just there in case you want to add to it.

void ModeCallback(unsigned long ulIndex, tUSBMode eMode)
{
    usbMode = eMode;
    switch(eMode) {
        case USB_MODE_HOST:
            break;
        case USB_MODE_DEVICE:
            break;
        case USB_MODE_NONE:
            break;
        default:
            break;
    }
}

Finally this last call back are event specific to the usb mass storage devices.  This only gets called if we have a USB mass storage devices plunged in or unplugged.  So if we detect a USB Mass storage, we will mount the file system.  (we will only mount sda0 assuming there is only 1 partition.).  The actual mounting will be done later  for now lets just call our fs init code.

void MSCCallback(unsigned long ulInstance, unsigned long ulEvent, void *pvData)
{
    switch(ulEvent)
    {
        case MSC_EVENT_OPEN:
        {

            usb_state = STATE_MSC_CONNECTED;

            //Ensure we are still connects
            USBHCDMain();

           //wait for the device to be ready
            while(USBHMSCDriveReady(g_ulMSCInstance))
            {

                 //pause to not stress out the device
                SysCtlDelay(SysCtlClockGet()/30);
            }
              fs_init();
            break;

        case MSC_EVENT_CLOSE:
            //should probably unnount the partition. 
            usb_state = STATE_NO_DEVICE;
            break;
        default:
            break;
    }
}


Now lets go back to the main. We will need to add the code to start up the USB host driver.  Initialise the USB to not connected and tell the driver that we will be using USB host only.

usb_state = STATE_NO_DEVICE;
USBStackModeSet(0, USB_MODE_HOST, ModeCallback);

Next we need to the the USB host driver, what kind of Host device can we connect.  This is where we pass our array of drivers.

USBHCDRegisterDrivers(0, UsbClassDrivers, NUM_CLASS_DRIVERS);

Finally, lest start a instance of the USB mass storage driver, give the USB power and Start the USB Host controller.

     //start a MSC USB HOST
    g_ulMSCInstance = USBHMSCDriveOpen(0, MSCCallback);
    //give the usb some power
    USBHCDPowerConfigInit(0, USBHCD_VBUS_AUTO_HIGH | USBHCD_VBUS_FILTER);
    //USBOTGModeInit(0, 2000, g_pHCDPool, HCD_MEMORY_SIZE);
    USBHCDInit(0, g_pHCDPool2, HCD_MEMORY_SIZE);    //dcm


At the end of you main there should be a infinite loop.  insert USBHCDMain(); in that loop.
Now the USB should be up and running.  Now we need to read from the USB when we load a web page.

Breaking Out Of The Interrupt Request
 Let's start with handelling the Ethernet in our main rather than in a interrupt request.  Lest start by declaring 2 global variables.  IPInterupted is a flag that we will use to indicate we received a Interrupt and InitDone is a flag to tell us that we have finished the init and we and now in our endless loop in our main.

int IPInterupted = 0;
int InitDone = 0;


Now we will add our interrupt function.  If we are still initializing stuff, then lets simply handle the interrupt in our interrupt request.  IF we are done the init, then set the flag so we can handle it in our main execution and free up the interrupts.  we will also disable the Ethernet interrupt until we have dealt with this one.

void IpInterupt(void)
{
    if(InitDone)
    {
             IPInterupted = 1;
            EthernetIntDisable(ETH_BASE, ETH_INT_RX | ETH_INT_TX);
    }
    else
    {
        lwIPEthernetIntHandler();   
    }
}


Now back to the main.  Right before we enter the infinite loop, we will add InitDone = 1;  This way our interrupt know not to handle it but flag to let us know it happened and let us deal with the interrupt.  Now in the infinite loops we need to add code to handle the interrupt is if occurs and re-enable the Ethernet interrupt once we have dealt with it.  Here is what my infinite loop looks like.

    InitDone = 1;
    while(1)
    {
        if(IPInterupted)
        {
            lwIPEthernetIntHandler();
            EthernetIntEnable(ETH_BASE, ETH_INT_RX | ETH_INT_TX);
        }
        USBHCDMain();
    }


Loading A Page From USB
We will need to add 2 files to our 3rd Party folder. ff.c and fat_usbmsc.c.  So double click the 3rparty folder.  Navigate to the folder the stellaris SDK/third_party/fatfs/port  and add the file "fat_usbmsc.c".  Now go back one folder go in src, and add the file ff.c.

Now that the files are loaded we are ready to read out htm files from the USB.  So lets open the lmi_fs.c file.  This files contains the function that httd will call to open the web pages.  we will simple add the calls to read the USB here.  Before we start modifying code, we need to add a couble of global variables:

FATFS g_sFatFs;
extern enum {
    STATE_NO_DEVICE,
    STATE_MSC_INIT,
    STATE_MSC_CONNECTED,
    STATE_UNKNOWN_DEVICE,
    STATE_POWER_FAULT
} usb_state;


and include

#include "fatfs/src/ff.h".

Let start with fs_init().  If you remember we call that when we detect a USB key plugged in.  In this code, we will need to mount the file system.  We only want to mount it though if we have a USB connected.
void fs_init(void)
{
    FIL sFileObject;
    if(usb_state == STATE_MSC_CONNECTED)
    {
        f_mount(0, &g_sFatFs);
    }
}
Now we want to be able to open the USB files.  Lets head over to fs_open(char *name).  Right after we allocate the memory for ptFile and check if its not null,  we will add our code.  we want to return ptFile with and open fat file in the struct and only if we have a USB connected.  I left the rest of the code there.  That way if the USB is not connected, we will open the default web page. I don't do error handling yet that is left as an exercise for you.  Here is a snippet of my code:
    if(usb_state == STATE_MSC_CONNECTED)
    {
         if(f_open(&ptFile->sFileObject, name, FA_READ) != FR_OK)
        {
        }
        return ptFile;   
    }
In Order to have this working though we will need to  add to the struct.  So lets open fs.h.  If you Are in the project explorer, and you click the + sign next to the lmi_fs, you should see a list of included files. double click on FS.h  you should add the include #include "fatfs/src/ff.h" and add FIL sFileObject to the struct.  Here is what my struct looks like:
#ifndef __FS_H__
#define __FS_H__
#include "fatfs/src/ff.h"
struct fs_file {
  char *data;
  int len;
  int index;
  void *pextension;
  FIL sFileObject
};
Now back to lmi_fs.c.  We need to implement the read function.  This function would return -1 if no more data is left if not it returns the number of bytes read.  Again I left the rest of the code there so I can still serve web pages if no usb is connects.  here is what my read code looks like:
    if(usb_state == STATE_MSC_CONNECTED)
    {
        f_read(&file->sFileObject, buffer,count,&usBytesRead);
        if(!usBytesRead)
        {
            //no data left
            f_close(&file->sFileObject);
            return -1;
        }
        return usBytesRead;
    }

And there you go.  you should be able to serve web pages from your arm stellaris evalbot by loading the pages from USB.  If its not working, I might have forgotten a step.  Please let me know and Ill fix it.  Also if you see any improvements thanks.

5 comments:

  1. Great blog! Exactly what I was looking for!

    I assembled my evalbot a month ago and left it at that (lack of free time it seems). I also have a few discovery boards as well as arduinos.

    Your posts will enable me to start things up a bit faster and I will (hopefulle) take the time to update my blog to show progress!

    Way to go!

    ReplyDelete
  2. Be sure to send me the link if you do write about it.

    Feel free to email me any time if you have problems. Good luck and thanks for commenthing

    ReplyDelete
  3. Hi, may i ask if there is any other ways to program the evalbot to move?

    ReplyDelete
  4. If i remember correctly, you basically need to send pwm to the motor controller. I have the link to the data sheet some where. check my about me page for my email. I can try to help you a little bit more.

    ReplyDelete
  5. i've emailed already :)

    ReplyDelete