// 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
#define DEBUGGER                 /* no debugger */
#define SERIAL_BAUD_DIVISOR 51  /* 9600 @ 8Mhz */
#define I2C_PORT1_PRESENT
#define DS1621_BASE_ADDR 0x9E    /* ext. DS1621 digital thermometer */

#define USER_int0_isr int0_isr  /* plug INT0 interrupt */
#define USER_int1_isr int0_isr  /* plug INT1 interrupt */

#undef DUMMY_DATA               /* define for dummy tringle wave data (no H/W) */

//#define SHOW_UDP_REQUESTS

#define UDP_CHECKSUM_DONE

#define UDP_CMD_PORT 99

#define Data_PORT PORTB      /* D0-7, input, PB0-7 */
#define Data_PINS PINB
#define Data_DDR  DDRB

#define RdClk_BIT  4         /* RdClk, output, DB-25 pin 12, PD4 (same as LED2) */
#define RdClk_PORT PORTD
#define RdClk_PINS PIND
#define RdClk_DDR  DDRD

#define Start_BIT  2         /* Start, input, DB-25 pin 7, PD2 */
#define Start_PORT PORTD
#define Start_PINS PIND
#define Start_DDR  DDRD

#define Done_BIT   3         /* Done, output, DB-25 pin 9, PD3 */
#define Done_PORT PORTD
#define Done_PINS PIND
#define Done_DDR  DDRD

// 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
    sts udp_byte_count,r17	    ; clear UDP counter		
    sts udp_byte_count+1,r17	    ; 

    clr r17
    out Data_DDR,r17                ; make PB0-7 input
    out Data_PORT,r17               ; ...and turn off pull-ups

    sbi RdClk_DDR,RdClk_BIT         ; make output
    cbi Start_DDR,Start_BIT         ; make input
#if 1
    cbi Start_PORT,Start_BIT	    ; ...and turn off pull-up
    in r17,Start_PORT
    mov r0,r17
    hexb
#else
    cbi Start_PORT,Start_BIT	    ; ...and turn off pull-up
#endif
    sbi Done_DDR,Done_BIT           ; make output

    cbi RdClk_PORT,RdClk_BIT        ; "RdClk" => low
    cbi Done_PORT,Done_BIT          ; "Done" => low

#avr_slow

#avr_fast
    rcall udp_loop_processing

    in r17,MCUCR        ; edge sensitive interrupts
    ori r17,0x0f
    out MCUCR,r17

    clr r17             ; disable INT0 and INT1
    out GIMSK,r17
#avr_asm

/*--------------------------------------------------------------------------

   Data Acquisition Interface

   The data acquisition interface to the PicoWeb UDP Server uses
   17 pins on the unit's 25-pin male D-subminiature  (DB-25P)
   connector.  When data is ready to be transferred into the
   PicoWeb UDP server for retransmission to a remote host
   computer over the Ethernet, the attached data acquisition
   device activates the "Start" signal. The PicoWeb server
   first waits until it receives a "start data acquisition"
   UDP packet from the controlling host computer.  If both
   conditions are met, the PicoWeb UDP server begins taking
   data, one 8-bit byte at a time over the data bus (D0-D7).
   These data bytes are transferred to the PicoWeb's outgoing UDP
   packet buffer.  The signal "RdClk" is used to clock read data
   from the external device.  It is assumed that the external
   device can supply data faster than the PicoWeb firmware can
   strobe the read data clock (i.e., some sort of hardware FIFO
   buffer in the external device's data path is assumed).

   The amount of data clocked from the external device for
   each "start data acquisition" UDP packet received is
   user-programmable, set by a data word in the "start data
   acquisition" UDP packet.  Acquired data automatically will
   be broken up into a maximum of 1024-byte data packets and
   sent in sequence to the remote host computer.  Besides the
   acquired data, the UDP data packet sent to the remote host also
   contains header information with a data packet sequence number.
   Lost UDP packets are not automatically retransmitted by the
   PicoWeb server.

   Once the requested number of bytes of data has been clocked
   from the external device, any remaining buffered UDP packet
   data is sent to the remote host, and the PicoWeb server
   signals completion to the external device by activating the
   "Done" signal.  The external device acknowledges this signal
   my de-asserting the original "Start" signal.  The PicoWeb UDP
   server then waits for the assertion of a new "Start" signal and
   the arrival of a new "start data acquisition" UDP packet before
   beginning the data acquisition/UDP transfer sequence again.

--------------------------------------------------------------------------*/

#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:
#ifndef DUMMY_DATA
    in r17,Start_PINS                       ; "Start" signal active?
    bst r17,Start_BIT                       ; move "Start" bit to T-reg
    brts started                            ; check T-reg
    ret                                     ; not set...return
started:                                    ; set...send (more) UDP data
#endif
    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
#ifndef DUMMY_DATA
    psignal_done                            ; signal that we are done
#endif
    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

#ifdef DUMMY_DATA
;+
; **-movedata-move dummy 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:                      ; called to supply next 2 bytes of UDP data
    rcall moveword                  ; set dummy data
    inc yl                          ; bump dummy data counters in yh,yl
    inc yl                          ; (this makes a triangle wave)
    inc yh
    inc yh
    ret

moveword:
    mov i0,yl                       ; supply next two bytes of dummy data
    mov i1,yh                       ; i0/i1 = next word of data
    f2x_checksum y,r10              ; accumulate checksum - r10 destroyed
    ret

#else /* !DUMMY_DATA */

;+
; **-movedata-move UT data from FIFO to packet.
;
; inputs:
;   none
;
; outputs:
;   data moved from FIFO to outgoing UDP packet
;-

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:                      ; called to supply next 2 bytes of UDP data

    in yl,Data_PINS                 ; read LSB from FIFO
    rcall strobe_RdClk

    in yh,Data_PINS                 ; read MSB from FIFO
    rcall strobe_RdClk

    mov i1,yh                       ; i0/i1 = next word of data
    mov i0,yl
    f2x_checksum y,r10              ; accumulate checksum - r10 destroyed
    ret

strobe_RdClk:
    sbi RdClk_PORT,RdClk_BIT        ; "RdClk" => high
    nop                             ; delay (2 nops + sbi => 4 8MHz clocks)
    nop
    cbi RdClk_PORT,RdClk_BIT        ; "RdClk" => low
    ret

moveword:
    mov i0,yl                       ; supply next two bytes of dummy data
    mov i1,yh                       ; i0/i1 = next word of data
    f2x_checksum y,r10              ; accumulate checksum - r10 destroyed
    ret

#endif /* !DUMMY_DATA */

;+
; **-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        2000   ; 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

#ifndef DUMMY_DATA
;
;  Assert "Done", wait for "Start" to go low, then release "Done"
;
psignal_done:   pcode_routine   0
    sbi Done_PORT,Done_BIT              ; "Done" => high
psignal_done_loop:
    in r17,Start_PINS                   ; "Start" signal active?
    bst r17,Start_BIT                   ; move "Start" bit to T-reg
    brts psignal_done_loop              ; loop back if still set
    cbi Done_PORT,Done_BIT              ; not set..."Done" => low
    ret
#endif

;
; INT0/1 interrupt service routine
;
.cseg
int0_isr:
    push r17                            ; save working register
    in r17,SREG                         ; save flags
    push r17                            ;
    clr r17                             ; disable INT0 and INT1
    out GIMSK,r17                       ;
    in r17,MCUCR                        ; make edge sensitive interrupts
    ori r17,0x0f                        ;
    out MCUCR,r17                       ;
    pop r17                             ; restore flags
    out SREG,r17                        ;
    pop r17                             ; restore working register
    reti                                ; return from interrupt

