udpdata.pwp


// PicoWeb Project File

#define BANNER "\r\nPicoWeb UDP data acquisition tester.\r\n"
#define EEPROM_IP                /* use file "ip" for IP address */
#define ENABLE_WATCHDOG
#undef DEBUGGER                 /* no debugger */
#define SERIAL_BAUD_DIVISOR 51  /* 9600 @ 8Mhz */

//#define SHOW_UDP_REQUESTS

#define UDP_CHECKSUM_DONE

#define UDP_CMD_PORT 99

// HTML and images
udpdata.htm             // base page

// CGI routines
sertest.cgi             // iu00

// Assembly language

//------------------ included AVR assembly language follows ---------------
#avr_reset

    clr r17
    sts data_pkt_count,r17          ; reset packet counter

#avr_slow

#avr_fast
    rcall udp_loop_processing

#avr_asm

;--------------------------------------------------------------
; Project Notes:
; 
; This sample is not really set up to run.  It was used it to
; test UDP throughput to a PC using a special version of Perl
; which was hacked to do UDP transfers.  For various reasons,
; we cannot distribute it.
; 
; The sample project takes a "trigger" UDP packet from the PC
; which tells the PicoWeb how much "dummy" data to send back
; using one or more UDP packets.  The response UDP packets
; are limited to 1024 bytes of "payload" data.  Therefore,
; if the PC sends a "trigger" packet requesting 16K bytes of
; data, then a total of 16 1024-byte UDP packets will be sent
; by the PicoWeb in quick succession.  Using this setup, we
; determined the maximum sustained rate at which we could move
; UDP data from the PicoWeb to a laptop PC (running Windows NT)
; with a PCMCIA Ethernet card was 239 Kbytes/sec.
; 
; As for a description of the various routines in this project:
; 
; The routine "import_udp" gets called each time a UDP packet
; is received by the PicoWeb.  This routine looks at the first
; two bytes of the incoming UDP packet and then saves this
; 16-bit number, the total amount of dummy UDP data to return.
; This routine then zeros the counter which keeps track of how
; much UDP data has been returned.  Also, this routine calls
; "set_client" to remember the IP address and port of the
; incoming UDP packet.  This information will be used to send
; back one or more response UDP data packets.
; 
; The routine "udp_loop_processing" is called each time through
; the PicoWeb "fast idle" loop.  It checks whether any UDP
; "response" data needs to be sent.  If so, it uses a routine
; called "movedata" (see pcode instruction "pf2x") to setup for
; UDP data transfer and then build up the UDP packet's data.
; The routine "udp_loop_processing" then calls the routine
; "mem_udp_send_client" to complete the checksum the UDP packet
; and to send the newly formed packet out on the network.
; 
; The routine "set_client" records the IP address and port
; number from an incoming UDP packet for later use.
; 
; The routine "movedata" is called to setup for creating the
; data for the UDP packet.  UDP data is supplied two bytes
; at a time by a co-routine whose address is stored in the
; Z-register.  This co-routine is named "movedata_next" in this
; sample project.  Each time this "co-routine" is called it is
; expected to supply 16-bits of new UDP data in the Y-register
; (after first calling "f2x_checksum" to compute a running
; UDP checksum).
; 
; Note that this project also can send data to the serial port
; using "parameters" supplied as part of a GET request's URL,
; then return any data the serial port responds with as part of
; a Web page.  (See CGI routine "sertest".)  This has nothing
; to do with the "UDP part" of the project.
;--------------------------------------------------------------

#include "tcpip.inc"
#include "mempkt.asm"

.dseg

client_ip:          .byte 4
client_port:        .byte 2
udp_byte_count:     .byte 2
udp_pkt_seq:        .byte 2         ; packet sequence # 0...N
packet_header:      .byte UDP_DATA  ; ether/ip/udp headers
data_pkt_count:     .byte 2         ; incremented by 1 for every packet sent

.cseg

#define UDP_PKT_LEN     1024

#define MEM_ETH_DEST    packet_header+0
#define MEM_ETH_SRC     MEM_ETH_DEST+6
#define MEM_ETH_PT      MEM_ETH_SRC+6

#define MEM_IPH         MEM_ETH_PT+2
#define MEM_IP_HVTOS    MEM_IPH+0
#define MEM_IP_LEN      MEM_IPH+2
#define MEM_IP_ID       MEM_IPH+4
#define MEM_IP_FFO      MEM_IPH+6
#define MEM_IP_TTLP     MEM_IPH+8
#define MEM_IP_CHKSUM   MEM_IPH+10
#define MEM_IP_SRCADDR  MEM_IPH+12
#define MEM_IP_DSTADDR  MEM_IPH+16
#define MEM_IPD         MEM_IPH+20

#define MEM_UDP_HEADER  MEM_IPD+0
#define MEM_UDP_SP      MEM_UDP_HEADER+0
#define MEM_UDP_DP      MEM_UDP_HEADER+2
#define MEM_UDP_LEN     MEM_UDP_HEADER+4
#define MEM_UDP_CHKSUM  MEM_UDP_HEADER+6
#define MEM_UDP_DATA    MEM_UDP_HEADER+8
    
;+
; **-udp-udp receive handler.
;
; return with ret.
;-


#define UDP_FUNC    UDP_DATA

import_udp:
    rcall pcode
    pr2s buf,UDP_DP,2           ; get destination port
    pcmpwi buf,BSWAP(UDP_CMD_PORT)          ; check if it matches
    pjumpne udp_bail

    pr2s udp_byte_count,UDP_DATA,2      ; byte count is first two bytes of data
    pmovwi udp_pkt_seq,0        ; zero sequence number
    pincw data_pkt_count
#if defined(SHOW_UDP_REQUESTS)
    ppushn putcok,1
    ppushn putc_b,1
    pmovbi putcok,1
    pmovbi putc_b,0
    pprintr "UDP src",IP_SRCADDR,4
    pprintr ":",UDP_DP,2
    pprintv " len ",[udp_byte_count]
    pcrlf
    ppopn putc_b,1
    ppopn putcok,1
#endif
    pcall set_client            ; remember who to send data to!
udp_bail:
    pend
    ret
;+
; **-udp_loop_processing-fast loop hook.
;
; this routine is called every time through the "fast loop".  it generates the
; next packet if there is more data to send.
;
; note:
;   this routine is called via "rcall", and must return with "ret".
;-

udp_loop_processing:
    rcall pcode
    pandwi udp_byte_count,0xffff            ; check remaining byte count
    pjumpeq udp_bail

#if defined(SHOW_UDP_REQUESTS)
    pprintv "pkt=",[udp_byte_count]
#endif
    pmovwi MEM_UDP_LEN,UDP_PKT_LEN+2        ; set length (+2 for sequence #)
    pf2x UDP_DATA,movedata,UDP_PKT_LEN+2    ; move the data
    pcall mem_udp_send_client               ; send it

    psubwi udp_byte_count,UDP_PKT_LEN       ; minus bytes just moved (excluding seq #)
    pjumplo udp_stop
    pjumpeq udp_stop
    pjump udp_bail                          ; more to send
udp_stop:
    pmovwi udp_byte_count,0                 ; zero out count
    pjump udp_bail
;+
; **-set_client-remember requestor as client for future autonomous sends.
;
; inputs:
;   current receive packet contains client info
;
; outputs:
;   appropriate info copied to memory packet buffer.
;
; caveats:
;   called via pcall/pret
;-

set_client:
    pr2s client_ip,IP_SRCADDR,4                 ; remember client's IP
    pr2s client_port,UDP_SP,2                   ;  and port number
    pr2s MEM_ETH_DEST,ETH_SRC,6                 ; and client ether


    pmemcpy MEM_IP_SRCADDR,my_ip,4              ; source ip == me

    pmovwi MEM_UDP_SP,BSWAP(UDP_CMD_PORT)       ;  and port
    pmemcpy MEM_ETH_SRC,my_ether,6              ;   and ether
    
    pmovwi MEM_ETH_PT,0x0008                    ; set PT=IP

    pmovwi MEM_IP_HVTOS,0x0045                  ; 4500
    pmovwi MEM_IP_TTLP,0x1101           ; UDP proto and TTL=1
    pmovwi MEM_IP_FFO,0                 ; zero fragment stuff
    pret
;+
; **-movedata-move data to packet.
;
; inputs:
;   none
;
; outputs:
;   data moved
;
; notes:
;   demonstrates how to use pf2x with initial setup processing vs.
;   "during the loop" processing.
;-

movedata:
    movwi z,movedata_next
    clrw x                                  ; reset the checksum
    ldsw y,udp_pkt_seq                      ; get packet sequence #
    rcall moveword                          ; "move" it
    incw y                                  ; advance sequence #
    stsw udp_pkt_seq,y                      ; store it back
    lds yl,data_pkt_count                   ; get start of sequence
    mov yh,yl
    inc yh
    
    ret
movedata_next:
    rcall moveword
    inc yl
    inc yl
    inc yh
    inc yh
    ret
moveword:
    mov i0,yl
    mov i1,yh						; i0/i1 = next word of data

    f2x_checksum y,r10				; accumulate checksum - r10 destroyed

    ret
;+
; **-mem_udp_send-send a UDP datagram from ram.
;
; inputs:
;   transmit buffer:
;       MEM_UDP_DATA    = user data to be sent
;       MEM_UDP_LEN     = native byte order length of data to send
;
; checksum algorithm:
;
;   ip_chksum = 0 ;
;   ip_chksum = ~checksum(IPH,IPHLEN)
;   udp_chksum = 0
;   ph.src = ip.src
;   ph.dst = ip.dst
;   ph.mbzprot = 0|(ip.prot<<8)
;   ph.len = udp_len (UDP data only, not header)
;   ph_chksum = checksum(UDP_HDR,UDP_LEN+UDP_HEADER_LEN)
;   udp_chksum = ~checksum(PH_HDR,PH_HEADER_LEN)
;-

#define PH_HEADER  rcv_hdr+en_rbuf_nhdr
#define PH_SRCADDR PH_HEADER+0
#define PH_DSTADDR PH_HEADER+4
#define PH_MBZPROT PH_HEADER+8
#define PH_LEN     PH_HEADER+10
#define PH_CHKSUM  PH_HEADER+12
#define PH_HEADER_LEN      14

#define MOVW(x,y) pmovwi x,[y]

#define UDP_CHECKSUM_DONE

mem_udp_send_client:
    pmemcpy MEM_IP_DSTADDR,client_ip,4          ; destination = client
    pmovwi MEM_UDP_DP,[client_port]             ;  and client port

mem_udp_send:
#if defined(UDP_MEM_SEND_DEBUG)
    pprintv " MVCHK=",[chkacc]
    pclrw chkacc
    pmem_chksum MEM_UDP_DATA,[MEM_UDP_LEN]
    pprintv " UDPCHK=",[chkacc]
#endif

;
; compute UDP length and store in UDP_LEN field and PH_LEN field.
;
    MOVW(buf,MEM_UDP_LEN)              ; get length (native order)
    paddwi buf,UDP_HEADER_LEN           ; total length
    MOVW(MEM_UDP_LEN,swap buf)         ; set UDP length
;
; checksum UDP data, and store checksum (uncomplemented) in pseudoheader.
;
    pclrw MEM_UDP_CHKSUM                ; zero UDP checksum
#if defined(UDP_CHECKSUM_DONE)
    pmem_chksum MEM_UDP_HEADER,UDP_HEADER_LEN
#else
    pclrw chkacc                        ; reset accumulator
    pmem_chksum MEM_UDP_HEADER,[buf]    ; compute UDP checksum
#endif
    MOVW(PH_CHKSUM,chkacc)             ; store checksum in pseudoheader

    paddwi buf,IP_HEADER_LEN
    MOVW(MEM_IP_LEN,swap buf)          ; set IP length
;
; the IP header should be ready, zero its checksum, checksum it, and
; store complemented checksum back in header.
;
    pclrw chkacc
    pclrw MEM_IP_CHKSUM                 ; zero IP header checksum
    pmem_chksum MEM_IPH,IP_HEADER_LEN   ; checksum IP header
    pxorwi chkacc,-1                    ; complement the ip checksum
#if defined(LOG_MEM_CHKSUM)
    pprintv "IPCK ",[chkacc]
#endif
    MOVW(MEM_IP_CHKSUM,chkacc)          ; store in IP header
;
; finally, checksum the pseudoheader and store complemented checksum
; in UDP header.
;
    pmemcpy PH_SRCADDR,MEM_IP_SRCADDR,8 ; source/dest IP addrs
    pmovwi PH_MBZPROT,0x1100            ; UDP proto in PH
    MOVW(PH_LEN,MEM_UDP_LEN)            ; store net order length in ph

    pclrw chkacc
    pmem_chksum PH_HEADER,PH_HEADER_LEN ; checksum pseudoheader
    pxorwi chkacc,-1                    ; complement
    MOVW(MEM_UDP_CHKSUM,chkacc)         ; store in UDP header checksum
#if defined(LOG_MEM_CHKSUM)
    pprintv "PHCK ",[chkacc]
#endif
;
; compute total packet size and send.
;
    MOVW(buf,swap MEM_IP_LEN)           ; ip length total
    paddwi buf,6+6+2                    ; compute length with ether header

#if defined(UDP_CHECKSUM_DONE)
;
; if the checksum has already been computed, it also means the packet
; data is in the frame buffer, so just load the headers.
;
    ps2x 0,MEM_ETH_DEST,UDP_DATA        ; ether, ip, udp headers
#else
;
; load everything.
;
    ps2x 0,MEM_ETH_DEST,[buf]           ; copy packet to board
#endif

    xmit_frame [buf]                    ; out she goes

#if defined(LOG_MEM_CHKSUM)
    pprintv "PKLEN ",[buf]
#endif

    pret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;   serial device I/O ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

.eseg

#define putchar_serial pmovbi putc_b,0
#define putchar_net    pmovbi putc_b,1
#define serial_binary  pser_mode 1
#define serial_normal  pser_mode 0
#define SER_TMO         1500

;+
; **-sertest-serial I/O testor.
;-
#define SD_ADDR     buf
#define SD_LEN      buf+2
#define SD_CHAR     buf+4
#define SD_TEMP     buf+6
#define SD_LAST     buf+8
#define SD_TMO      SD_CHAR

sertest:
    ppushn putcok,1                     ; save putchar enable state
    pmovbi putcok,0xff                  ; putchar is now OK
    pprint "HTTP/1.0 200\nContent-type: text/html\n\n"
    putchar_serial                      ; switch putchar to serial port
    serial_binary                       ; put serial port in binary mode
    pmovwi SD_ADDR,[data_addr]          ; get data address
    paddwi SD_ADDR,10                   ; GET /iuNN?xxxxxxxxxxx
                                        ; 01234567890
    pmovwi SD_LEN,[data_len]
    psubwi SD_LEN,10                    ; bias length for GET /...? part
;
; note there is not a lot of checking on the format of the URL.  the first
; occurrence of '=' is interpreted as the start of the stuff to transmit
; to the serial port, and that continues until either '&' or ' ' is
; encountered.
;
    pmovwi SD_LAST,0               ; clear last character (both bytes!)
find_equal_next:
    pr2s SD_CHAR,[SD_ADDR],1       ; scan for '=' in Ethernet packet
    pincw SD_ADDR
    pdecw SD_LEN
    pcmpbi SD_CHAR,'='
    pjumpeq serdev_next
    pcmpwi SD_LEN,0
    pjumplo serial_done
    pjumpne find_equal_next
    pjump serial_done
;
; send stuff after "=" to serial port with %XX expansion until ' '.
;
serdev_next:
    pwdr                                ; reset hardware watchdog timer
    pr2s SD_CHAR,[SD_ADDR],1            ; get next byte
    pcmpbi SD_CHAR,' '                  ; the end?
    pjumpeq serial_done                 ; yes...we found a ' '
    pcmpbi SD_CHAR,'+'                  ; map '+' -> ' '
    pjumpne not_plus
    pmovbi SD_CHAR,' '
    pjump output_next_char
not_plus:
    pcmpbi SD_CHAR,'%'                  ; check for HTTP quoting char
    pjumpne output_next_char
;
; get next two chars from request.
;
    pincw SD_ADDR                       ; skip '%'
    pr2s SD_CHAR,[SD_ADDR],2            ; get XX of %XX
    paddwi SD_ADDR,1                    ; skip past it
    psubwi SD_LEN,2                     ; discount in length
;
; hex conversion of two chars in SD_CHAR, SD_CHAR+1.
;
    pmovb SD_TEMP,SD_CHAR               ; get first char
    pandwi SD_TEMP,0xff
    psubwi SD_TEMP,'0'                  ; convert to binary (maybe)
    pcmpwi SD_TEMP,10                   ; see if valid hex digit
    pjumplo do_upper_digit              ; yep - do upper one
    pandwi SD_TEMP,0xf                  ; 1-6
    paddwi SD_TEMP,9                    ; turn 1-6 into 10-15

do_upper_digit:
    pmovb SD_CHAR,SD_CHAR+1             ; get second digit
    pandwi SD_CHAR,0xff                 ; isolate it
    psubwi SD_CHAR,'0'                  ; convert to binary (maybe)
    pcmpwi SD_CHAR,10                   ; see if valid hex digit
    pjumplo merge_digits                ; yep - merge the two
    pandwi SD_CHAR,0xf
    paddwi SD_CHAR,9

merge_digits:
    pshnw SD_TEMP,4                     ; shift top bits into place
    paddwi SD_CHAR,[SD_TEMP]            ; SD_CHAR now has char

output_next_char:
    pandwi SD_CHAR,0xff                 ; isolate it
    pcmpwi SD_CHAR,0x5c                 ; back-slash?
    pjumpeq skip_output                 ; yes...don't output

    pcmpwi SD_LAST,0x5c                 ; last character a back-slash?
    pjumpne output_char_now             ; no...proceed as normal
    pcmpwi SD_CHAR,'r'                  ; got a \r?
    pjumpne not_CR                      ; no...skip ahead
    pmovbi SD_CHAR,0x0d                 ; yes...change it to CR
not_CR:
    pcmpwi SD_CHAR,'n'                  ; got \n?
    pjumpne not_LF                      ; no...
    pmovbi SD_CHAR,0x0a                 ; yes...change to LF
not_LF:

output_char_now:
    pputcb SD_CHAR
skip_output:
    pmovbi SD_LAST,[SD_CHAR]            ; save last character
    pincw SD_ADDR                       ; adjust pointer
    pdecw SD_LEN                        ; see if we're done
    pjumplo serial_done
    pjumpne serdev_next
serial_done:
    pmovwi SD_TMO,SER_TMO               ; select the timeout period
    putchar_net                         ; switch putchar back to 'net
    pcall serdev_copynet                ; copy serial port input to net
    ppopn putcok,1                      ; restore putchar enable state
    pret
;
; loop reading chars from the serial buffer and writing to 'net until
; LF encountered (or timeout)
;
; SD_TMO (word) = timeout loop count.
;
.cseg
serdev_nextchar:
    pser_getc SD_CHAR                   ; get a char into buf
    pjumpeq serdev_empty                ; no more chars!
    pputcb SD_CHAR                      ; put char to the net
    pcmpbi SD_CHAR,0x0a                 ; check for LF
    pjumpeq serdev_copydone
serdev_copynet:
    pmovwi SD_TEMP,[SD_TMO]             ; reset timeout
    pwdr                                ; reset hardware watchdog timer too
    pjump serdev_nextchar               ; go get another one
serdev_empty:
    psubwi SD_TEMP,1                    ; decrement timeout
    pjumpne serdev_nextchar             ; not yet timed out
serdev_copydone:
    pret

Back