Using the Arduino Ethernet shield with AMICUS18 (Joint forum project?)


Closed Thread
Results 1 to 40 of 63

Hybrid View

  1. #1
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,557

    Default Re: Using the Arduino Ethernet shield with AMICUS18 (Joint forum project?)

    Hello,
    Now that we know how to setup the W5100 to listen for TCP trafic on the correct port, how to determine if there's data to be read in the sockets RX memory buffer and where in the buffer that data is it's time to look at how we can parse the received data and act upon it. After all, simply "resending" the received data out the USART may be cool (for 5 minutes) but it doesn't really do anything for us, though it does serve as a nice debugging tool.

    So today I'll try to cover how we can load the recevied data from the W5100 into a local buffer, do some (for now very) simple parsing of that data in order to determine what the client wants and then build and send a response. The end result will be a simple web-page displayed in the browser window.

    Trying to parse the data directly in the W5100 proved to be a bit tricky so I decided to move it to RAM in the PIC and do the parsing there. To do this we first need somewhere to put the data, an array called Buffer of some, for now arbitrary, length:
    Code:
    BufferSize  CON     128
    Buffer      VAR     BYTE[BufferSize]' Local buffer for loading and unloading data from the W5100
    Then I modified the GetData routine posted earlier to not only "print" the received bytes on the serial terminal but to also stuff them into our previously declared array. The GetData routine now looks like this:
    Code:
    '*****************************************************************************************************
    GetData:
        HSEROUT [REP "-"\20, " Data read from Rx buffer follows ", REP "-"\20, 13]
     
        Counter = 0                                 ' Start loading the local array buffer at the beginning.
    
        ' Do we have a buffer wrap around condition?
        If WizEnd < WizStart THEN
     
        ' When that happends we need to start reading at the first byte in the received
        ' package and go on until the end of the sockets RX memory....
            For WizAdress = WizStart to W5100_S0_RX_END
                Gosub Read_W5100
                HSEROUT[WizData]
    
                ' Make sure we don't write outside of the allocated buffer.
                If Counter <= BufferSize THEN
                    Buffer[Counter] = WizData
                ENDIF
     
                Counter = Counter + 1                ' Keep count
    
            NEXT
     
            ' ...then we need to start over at the beginning of the buffer and keep reading
            ' until we've got all the bytes in the buffer.
     
            For WizAdress = W5100_S0_RX_START to WizEnd
                Gosub Read_W5100
                HSEROUT[WizData]
    
                ' Make sure we don't write outside of the allocated buffer.            
                If Counter <= BufferSize THEN
                    Buffer[Counter] = WizData
                ENDIF
     
                Counter = Counter + 1                ' Keep count
            NEXT
     
        ELSE                                         ' There's no buffer wrap-around.
     
            ' WizStart now contains the physical adress of the first byte to read.
            For WizAdress = WizStart to WizEnd
                Gosub Read_W5100
                HSEROUT[WizData]
     
                ' Make sure we don't write outside of the allocated buffer.
                If Counter <= BufferSize THEN
                    Buffer[Counter] = WizData
                ENDIF
    
                Counter = Counter + 1                ' Keep count
    
            NEXT
     
        ENDIF
     
        HSEROUT [REP "-"\23, " End of data from Rx buffer ", REP "-"\23, 13]
        HSEROUT ["Total number of bytes read:", #Counter,13]
        HSEROUT [REP "-"\76, 13, 13]
     
        ' The variable RxPtr contains the "raw value" read from the sockets Rx Read Pointer Register.
        ' Now we need to be update that so it points one "step" ahead of the last location we just read.
        ' This is done to let the W5100 how "far" in the buffer we've read and that it can use any space
        ' "before" this location for new data. Note that this is not the ACTUAL adress of the data,
        ' just the unmasked index.
     
        WizPtr = WizPtr + WizSize
        WizAdress = W5100_S0_RX_RD0 : WizData = WizPtr.HighByte : GOSUB Write_W5100
        WizAdress = W5100_S0_RX_RD1 : WizData = WizPtr.LowByte  : GOSUB Write_W5100
    
        ' Then we send the RECV command to the sockets command register to let it know that
        ' we've handled all the data we currently are prepared to handle. This updates the
        ' pointers in the sockets memory so that it knows where to put new data. It is not
        ' until after issuing the RECV command that the sockets RX pointer is actually updated
        ' even though we write to it above.
    
        WizAdress = W5100_S0_CR : WizData = W5100_Sn_RECV : GOSUB Write_W5100
     
    RETURN
    '*****************************************************************************************************
    As you can see we are no longer disconnecting and closing the connection at the end of the GetData routine, the reason is obviously because we intend to send a response to the client so we need to keep the connection open until we've done that.

    Now that we have the data in our local array Buffer (or at least the first 128 bytes of it) we are ready to start analysing it in order to determine what the client wants. Looking back at the screenshot of the received data being printed to the serial terminal we can see that simply browsing to the W5100's IP adress results in the first couple of bytes in the buffer looking like this:
    Code:
    GET / HTTP/1.1
    What we're interested in here is the GET part, that is what the client wants to do - it wants to GET something from our server. The client could have specified a specific resource to get after the first forward slash, like
    Code:
    GET /thatpage.html HTTP/1.1
    But for now we won't look at serving more than a single resource so as soon as we see the word GET we know we should respond with the webpage we've got in the PIC. Here's a very simple routine to parse the buffer, starting at the beginning:
    Code:
    '******************************************************************************************
    ' Very simple routine to parse the data in the local buffer.
    ' If the first word is GET we set a semaphore signaling the main program that we've got
    ' a GET request.
    '******************************************************************************************
    GetRequest VAR BIT
    
    ParseData:
    
    If Buffer[0] = "G" THEN
        IF Buffer[1] = "E" THEN
            IF Buffer[2] = "T" THEN
                IF Buffer[3] = " " THEN
                    GetRequest = 1
                    HSEROUT["Found GET request",13]
                 ENDIF
            ENDIF
        ENDIF
    ENDIF
    
    RETURN
    Once we've found the word GET at the very beginning of the received data we're going to respond with a simple web-page. To do that the first order of business is to determine the amount of free space in the socket transmit buffer and where it wants us to start loading the data. This is done in a similar way to checking the RX-buffer, in fact we're reusing the same variables (WizPtr, WisStart and WizSize) for this purpose.
    Code:
    '*****************************************************************************************************
    ' This routine checks the amount of free space in the sockets transmit register.
    ' It also gets the pointer of where to start loading data and calculates the
    ' physical adress for us. It returns the amount of free space in WizSize and
    ' the adress of the first byte in WizStart. WizPtr is the "raw" pointer value
    ' and is used to update the socket transmit pointer after writing to the buffer.
    '*****************************************************************************************************
    GetFreeTXSize:
     
        ' Get the total amount of free memory in the sockets transmit buffer.
        WizAdress = W5100_S0_TX_FSR0 : GOSUB Read_W5100 : WizSize.HighByte = WizData
        WizAdress = W5100_S0_TX_FSR1 : GOSUB Read_W5100 : WizSize.LowByte = WizData
     
        ' Get the pointer adress of where to put new data.
        WizAdress = W5100_S0_TX_WR0 : GOSUB Read_W5100 : WizPtr.HighByte = WizData
        WizAdress = W5100_S0_TX_WR1 : GOSUB Read_W5100 : WizPtr.LowByte = WizData
     
        'Calculate physical adress of first byte
        WizStart = W5100_S0_TX_START + (WizPtr & W5100_S0_TX_MASK)
     
    RETURN
    When we return from this subroutine the amount of free space is available to us in WizSize, the raw pointer value (working exactly as the pointer into the RX memory buffer) is avaliable to us in WizPtr and the actual, physical, memory adress of the first free location is available to us inWizStart.

    In order to somewhat effeciently load HTML data into the buffer I came up with the following routine. What it does is loading null-terminated strings from the local array Buffer to the sockets TX memory, it handles the circular buffer and it keeps track of the total number of bytes loaded to the buffer so we can tell the W5100 how "far" into the buffer it should send.
    Code:
    '******************************************************************************************
    ' Subroutine to load a string from the local array Buffer to the W5100 socket 0's
    ' TX memory buffer. The string can be any length but must end with NULL.
    '******************************************************************************************
    PutStringInTXBuffer:
     
        Counter = 0                                 ' Counter for indexing the local array we're about to load into the TX buffer.
    
        While Buffer[Counter] <> 0                  ' Last byte in each part is null.
    
        ' Check if we are about to write outside the sockets memory area.
        ' If so we wrap around to the start of the buffer.
    
            If WizStart + TotalCount > W5100_S0_TX_END then 
                WizStart = W5100_S0_TX_Start - TotalCount
            ENDIF
    
        ' Write byte to TX memory buffer.
            WizAdress = WizStart + TotalCount : WizData = Buffer[Counter] : GOSUB Write_W5100
            Counter = Counter + 1             ' Point at the next byte in the array.
            TotalCount = TotalCount + 1             ' Count total number of bytes for the response.
        WEND
    
    RETURN
    We can then "feed" the PutStringInTxBuffer routine with data like in the first half of the HandleGetRequest routine:
    Code:
    '******************************************************************************************
    ' Subroutine used to build a simple web-page in response to a GET request.
    '******************************************************************************************
    HandleGetRequest:
    
        RequestCount = RequestCount + 1
    
        GOSUB GetFreeTXSize
    
        ' Reset the total byte count. This will be incremented in the PutStringInTXBuffer routine
        ' and used to keep track of the total number of bytes loaded to the TX memory buffer.
        TotalCount = 0
    
        HSEROUT ["Loading buffer...",13]
    
        ArrayWrite Buffer,["HTTP/1.0 200 OK",13,10,0]
        GOSUB PutStringInTXBuffer
    
        ArrayWrite Buffer,["Content-Type: text/html",13,10,13,10,0]
        GOSUB PutStringInTXBuffer
    
        ArrayWrite Buffer,["<html><body><title>AMICUS18</title>",13,10,0]
        Gosub PutStringInTXBuffer
    
        ArrayWrite Buffer,["<h1>Embedded Web Server</h1>",13,10,0]
        Gosub PutStringInTXBuffer
    
        ArrayWrite Buffer,["<h3>PIC18F25K20 / W5100 Ethernet chip</h3>",13,10,0]
        GOSUB PutStringInTXBuffer
    
        ArrayWrite Buffer,["<p>Henrik Olsson 2011</p>",13,10,0]
        GOSUB PutStringInTXBuffer
    
        ArrayWrite Buffer,["<a href=",34,"http://www.picbasic.co.uk/forum",34,">Visit the PBP Forum</a>",13,10,0]
        Gosub PutStringInTXBuffer
    
        ArrayWrite Buffer,["<p>This page has been requested ", DEC RequestCount, " times since powerup/reset</p>",13,10,0]
        GOSUB PutStringInTXBuffer
    
        ArrayWrite Buffer,["</body></html>",13,10,13,10,0]
        GOSUB PutStringInTXBuffer
    
        HSEROUT[13, "done...",DEC TotalCount, " bytes.."]
    The above is the actual HTML response that we're sending as a result of the GET request. What's important here is that we must reset the TotalCount variable before starting to load data and then not touch it until we're done. Each "part" of the response must end with NULL as can be seen in the ArrayWrite operations. There's no real requirement to split the response up in a certain way, it's more for the purpose of readabillity.

    Using ArrayWrite like this is a simple aproach but it seems very (VERY) inefficient because each and every character/byte of adds 6 bytes(!) to the footprint of the code. This isn't really a problem for this type of simple web-page but for more complicated stuff it'll add up quickly. We'll have to look into getting the SD card working as a storage "container" for the HMTL pages but that's another topic.

    Once we've got all the data loaded to the TX buffer we're ready to send it and this is handled by the second half of the HandleGetRequest subroutine:
    Code:
        ' Now recalculate the transmit pointer so the W5100 knows where to stop sending. WizPtr currently contains
        ' the adress of the first free byte and TotalCount is the number of bytes of byte we've loaded to the buffer.
        WizPtr = WizPtr + TotalCount
    
        ' Write the updated pointer value to the W5100
        WizAdress = W5100_S0_TX_WR0  : WizData = WizPtr.HighByte : Gosub Write_W5100
        WizAdress = W5100_S0_TX_WR1  : WizData = WizPtr.LowByte : Gosub Write_W5100
    
        ' Issue a SEND command.
        WizAdress = W5100_S0_CR : WizData = W5100_Sn_SEND : GOSUB Write_W5100
    
    WaitForCompletion:
        GOSUB Read_W5100
        If WizData <> 0 then Goto WaitForCompletion
    
        HSEROUT[".sent.",13]
    
        ' Then disconnect from the client.
        WizAdress = W5100_S0_CR : WizData = W5100_Sn_DISCON : GOSUB Write_W5100
     
        ' And close the socket. We'll reopen the socket again in the main routine.
        WizAdress = W5100_S0_CR : WizData = W5100_Sn_CLOSE : GOSUB Write_W5100
        GetRequest = 0
     
    RETURN
    Note that I don't actually verify if the data I'm about to load will fit in the amount of free space provided in the sockets TX memory. Not doing that should be considered bad practice but for now I'll do it anyway since I know that the size of the buffer is more than 6 times the size of the data I'm writing to it.

    Now all we need to do in our main routine is to:
    1) Check the received size register of the socket, if it's >0 we'll handle the received data using the above routines.
    2) Since we're closing the socket after sedning the data it's the main routines job to re-open it and set it to listen mode.
    Code:
    HSEROUT ["Program start",13]
    
    GOSUB Init_W5100   ' Reset W5100 and set MAC, IP, NetMask and Gateway adress, 
    
    Main:
        ' The socket 0 status register automatically switches to SOCK_ESTABLISHED 
        ' when a valid request to connect comes in from a client.
        WizAdress = W5100_S0_SR : GOSUB Read_W5100
     
        If WizData = W5100_SOCK_ESTABLISHED then
            Gosub CheckBufferForData
            If WizSize > 0 then
                GOSUB GetData
                GOSUB ParseData
                If GetRequest = 1 then GOSUB HandleGetRequest
            ENDIF
        ENDIF
     
        ' If the socket is closed (we're closing it in the GetData routine) then
        ' we must re-open it so we are prepared for the next client request.    
        WizAdress = W5100_S0_SR : GOSUB Read_W5100
        If WizData = Socket_CLOSED THEN
            GOSUB Init_Socket_0
        ENDIF
     
    Pause 250
    Goto Main
    To this post I'm attaching the source file (remove the .txt extension and you'll need the W5100_defs.pbp file as well) I used to arrive at the following results:
    Name:  HTTP_2.jpg
Views: 5629
Size:  49.4 KB

    It compiles to 5275 bytes out of which the ArrayWrite statements (with a total of ~340 bytes of HTML) occupies 2038 bytes - yikes. So, without the HTML we're looking at less than 3.5k for the actual server code - not bad.

    /Henrik.
    Attached Files Attached Files

Members who have read this thread : 0

You do not have permission to view the list of names.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts