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.

Thursday, March 3, 2011

My Evalbot First Steps: Ethernet Controlled robot

Intro
So I finally received my arm stellaris Evalbot. So I put down the ARM stm32 for a bit and took out my evalbot.  The I got the eval bot with the 125$ discount i saw on http://hackaday.com/ turns out they sold more than they thought and they were put on back order.  I got mine now.

So since I all ready had Keil up and running (still waiting for the non commercial one. If i don't get it soon, I think I will go with a GCC tool chain) I decided to test it with the evalbot. first thing is first I downloaded their stellaris SDK and examples here. Next I downloaded the drivers from TI here. Finally, I stole 4 files from this arm evalbot project.  They are for motor control and the oled screen (oled.h/c motor.h/c).  They work well so why reinvent the wheel.  He also has a neat I2C lib in there.  I don't think I plan on ever writing my own code for that since its nicely done and would be a waste of time.  Now I am ready do create my project.  You can follow the same steps as the ARM STM32 only this time use the device LM3S9B92 by Luminary Micro and the "Stellaris ICDI" instead of "ST-Link" as the debug tool and the utility to flash the program.
 so the first thing I did was load the enet_lwip Example.  That is how I got the robot working online.  This I think is the only example that worked out of the box.  I will base this project all off this example project.  It should also come with a Keil uproj file and you can launch.  Because this code is copyright by TI and not open source, My next step is to build create my own project under the same license and LWIP.  For now we are just exploring so this copyright code is good enough.

Adding to the project
If you look at the main, there is not much.  I would recommend you look around the code and get familiar with it before reading on as I will just jump right into it.

First thing I did since I never got the UART to work, was Display the ip address once I received it.  We will need to add the oled.c file to our project.  I like to have a folder for every function in my project folder.  I created a folder called oled in the project folder and copied oled.h and oled.c in that folder.  Next, you need double click on the 3rdparty folder. I like to add both the .c and the .h file so its easy to open the .h file if I need to view the function names but you just need the .c file.  So click on the newly copied oled.c (and oled.h if you want) file and click add then click close.  Now we need to include the oled.h file.  so scroll to the top and add #include "oled/oled.h". Now that the prep work is done, we will head over to the function called lwIPHostTimerHandler in enet_lwip.c. First we should add our buffer unsigned char screenBuff[17] = {0}; 17 is because the LCD can only display 17 characters and 1 for the \0. There should be an if statement that looks like this:
if(ulIPAddress != g_ulLastIPAddr).  In the if block we will add the display code.  Here is what mine looks like after i striped the uart code:
     if(ulIPAddress != g_ulLastIPAddr)
    {
        g_ulLastIPAddr = ulIPAddress;
        dispClear();
        dispString("Ip obtained:    ",0,0);
        sprintf(screenBuff,"%d.%d.%d.%d",((unsigned char*)&g_ulLastIPAddr)[0],

              ((unsigned char*)&g_ulLastIPAddr)[1],
              ((unsigned char*)&g_ulLastIPAddr)[2],
              ((unsigned char*)&g_ulLastIPAddr)[3]);

        dispString(screenBuff,0,1);
        ulIPAddress = lwIPLocalNetMaskGet();
        ulIPAddress = lwIPLocalGWAddrGet();
    }

If you go a head now and plug a network cable in the evalbot,  wait a little bit for the DHCP to resolve you will get an IP address and a example web page if you http on that address.

Making it move
Now we will add some CGI to the project.  HTTPD that come with the example projects will look for calls to pages that end with the cgi file extension and call the associated function. You can go ahead and repeat the steps we took to add oled to our project but with motor instead (create the motor folder copy over the 2 files and and the .c/h file to the 3rdparty folder). Now we need to include the motor header. #include "motor/motor.h".   We will We will add 2 cgi handler 1 for the LCD and 1 for the motor control.  Lets create a file called cgi.h/.c and add it to our project.  I went a head and created a folder called cgi and saved the 2 files in there.  I also created a group called cgi and added both files to that group.  In our cgi.h file we will need to include #include "httpserver_raw/httpd.h".  we will also need to declare our 2 CGI functions and map them to the page.  Here is what my Header file looks like:
#include "httpserver_raw/httpd.h"
#ifndef CGI_H
#define CGI_H

char* LcdCGIHandler(int iIndex, int iNumParams, char *pcParam[],char *pcValue[]);
char* DriveCGIHandler(int iIndex, int iNumParams, char *pcParam[],char *pcValue[]);

//Map the CGI function to the cgi url
static const tCGI CGI_URI_MAP[] =
{
        { "/LCD.cgi", LcdCGIHandler },{ "/drive.cgi", DriveCGIHandler } /// CGI_INDEX_CONTROL
};
#endif
All we got left to do is define those 2 functions and enable CGI.  Before we do that, I created a .c/.h file called driving.  In there I put 4 function up() down() turnRight() turnLeft().  I will use these function in the example here and I will leave implementation of these functions as an exercise to the reader.  I will give you a head-start by showing you what my turnRight function looks like.  It should be very little work to create the other ones.

int GlobalI;  //this is used to stop the compiler from optimising

void turnRight(void)
{
    GlobalI=0; //set it to 0
    setMotor(MOTOR_L,MOTOR_DIR_F,0x40);  //Rotate the Left Motor toward the front
    setMotor(MOTOR_R,MOTOR_DIR_B,0x40); //Rotate the Right Motor toward the back

    //Wait a little bit taking baby steps
    // 0x44000 was chosen sorta randomly.  
    //I found waiting this long 4 time made it turn about 90 deg
     while(GlobalI<0x44000)
     {
             GlobalI++;
     }
    setMotor(MOTOR_L,MOTOR_DIR_F,0);  // Stop the Left Motor
    setMotor(MOTOR_R,MOTOR_DIR_B,0);  // Stop the Right motor
}

It should be pretty straight forward to add the other 3 missing functions.  No we are going to create our CGI functions.  Lets check out what the parameters do.  we got iIndex, this is the index of the cgi handler in the array. I am not sure where we would use this.  Next there is iNumParams.  This is the number of parameter that were found after the ? in the uri. the we have 2 arrays.  1 for the param name and 1 for their value.
So lest make a cgi that will display on the lcd what ever we pass as an argument in the uri.  This function returns a char* with the URI of the page the user should be redirected to.  We will make a global char* with the uri so that it does not go out or scope after we return.

char* indexName = "/index.htm";
char* LcdCGIHandler(int iIndex, int iNumParams, char *pcParam[],char *pcValue[])
{
    int i=0;
    //clear our LCD
    dispClear();
    //loop threw all the parameters
    for(i=0;i<iNumParams;i++)
    {
        //check if we found the parameter we want
        if(!strcmp(pcParam[i],"name")     )
        {
            //display the value we received
            dispString(pcValue[i],0,0);   
            //exit the loop
            i=iNumParams;
        }
    // return the Index uri to redirect the user
    return indexName;
}
It was pretty straight forward.  Now all we got to do is make the drive handler. I will only show how to make it turn right and it will be up to you to do the rest.  Make sure you include #include "motor/driving.h".

char* DriveCGIHandler(int iIndex, int iNumParams, char *pcParam[],char *pcValue[])
{
    int i=0;

    //loop threw the parameters
    for(i=0;i<iNumParams;i++)
    {

        //did we find the parameter we are looking for?
        if(!strcmp(pcParam[i],"dir")     )
        {

           // lest compare the directions
            if(!strcmp(pcValue[i],"up")     )
            {
                       
            }
            else if(!strcmp(pcValue[i],"dn")     )
            {
                       
            }
            else if(!strcmp(pcValue[i],"rt")     )
            {
                turnRight();       
            }
            else if(!strcmp(pcValue[i],"lt")     )
            {
                      
            }   
        }

    }

    // return the user to /index.htm
    return indexName;
}

All we got left to do in enable our CGI.  By default, the CGI is not compiled so we will need to add a define to tell httpd to compile it so lets add the define INCLUDE_HTTPD_CGI in the options.  Hit Alt+F7 to open the option dialog.  head over to the C/C++ tab and in the define section add INCLUDE_HTTPD_CGI.
Now head back to your main.  Right under the variable decelerations, you can call init_display(); and init_motorPWM(); I actually added it right after the ROM_SysCtlClockSet call.

Now right on top of httpd_init(); we will add our  CGI map like this
http_set_cgi_handlers(CGI_URI_MAP, 2);  //the 2 is the number of CGI handlers we have defined.  Don't forget to #include "cgi/cgi.h".

Now if you go to your browser and navigate to http://evalbotIP/LCD.cgi?name=Hello
it should display hello on the LCD. if you head over to http://evalbotIP/drive.cgi?dir=rt
the evalbot robot should turn right.

My Next post in the next couple days will be hosting the web page from a USB key.
Neat little ARM based robot.