;Digital Up Converter - December 2006
;New version, October 2007
;Ethernet code for AtMega 165 and Silicon Laboratories CP2200
;FPGA code for EP2C8
;ARV 2.0 assembly language by James C. Ahlstrom

; This free software is licensed for use under the GNU General Public
; License (GPL), see http://www.opensource.org.
; Note that there is NO WARRANTY AT ALL.  USE AT YOUR OWN RISK!!

;This code assumes an 8 MHz internal oscillator.  Be sure to set fuse CKDIV8
;to unprogrammed or else the clock is divided by 8!  Be sure to disable the
;JTAG interface so you can use PORTC.  You should check the IP address below.

.INCLUDE "m165def.inc"

.ORG 0x00
rjmp RESET
.ORG OC1Aaddr
rjmp TIMER1

;key up/down delay and debounce time in units of 256 microseconds
#define KEY_DOWN_DELAY		47
#define KEY_UP_DELAY		47
#define KEY_DEBOUNCE		12
#define KEY_TEST			234

;Our local IP address
.EQU MYIP0 =		192
.EQU MYIP1 =		168
.EQU MYIP2 =		1
.EQU MYIP3 =		44

;These ports are also set as *_PORT constants in c and Python programs.
;If you change them, change them in all places.
.EQU MyPortHigh =		0x55	;All UDP communication is on port 0x55xx
.EQU MyTftpPort =		0x3A	;Use port 0x553A for file read/write
.EQU MyAudioPort =		0x3B	;Use port 0x553B to receive audio data
.EQU MyKeyingPort =		0x3C	;Use port 0x553C to send key up or down state

.INCLUDE "../CP2200.h"
.INCLUDE "../delay8.asm"	;Contains delay_us() and delay_ms()

;These constants are used for our quasi-tftp protocol
#define RRQ		1		;read request
#define WRQ		2		;write request
#define DATA	3		;data for read or write
#define ACK		4		;acknowledgement
#define ERR		5		;error

;Variables in RAM
.DSEG
zero_start:							;zero memory starting here
MacAddress:			.BYTE 6			;Our ethernet address
PhaseOffsets:		.BYTE 8			;phase offsets for two frequencies for FPGA
PacketWriteL:		.BYTE 1			;current write position in Packet
PacketWriteH:		.BYTE 1
TftpDataEndL:		.BYTE 1			;Packet address of Tftp data end for a full packet
TftpDataEndH:		.BYTE 1
TftpDataEndValid:	.BYTE 1			;Do we need to record the length in TftpDataEnd?  Can be ACK or ERR.
RequestedProgram:	.BYTE 1			;Requested FPGA program number
KeyPacket:			.BYTE 44		;Packet to send to PC setting the keying state, key up/down
Packet:				.BYTE 850		;Received ethernet frame data 42 + 0x300 bytes maximum (810) except audio
zero_end:							;zero memory ending here
.CSEG

;Exclusive register assignments
.DEF ip_seq_h		= r1				;IP sequence number is r1:r0
.DEF ip_seq_l		= r0
.DEF regRAMRXDATA	= r2				;constant RAMRXDATA
.DEF TftpBlockNumber = r3				;Block number of required block
.DEF regIN			= r4				;constant 0x00
.DEF UnreadCountH	= r9				;The number of unread bytes in the packet is r9:r8
.DEF UnreadCountL	= r8
.DEF regRAMADDRH	= r13				;constant RAMADDRH
.DEF regRAMADDRL	= r14				;constant RAMADDRL
.DEF regOUT			= r21				;constant 0xFF
.DEF State			= r22				;The current state for the state machine

#define EthReadH	ZH					;Current read position in Ethernet read buffer: Z register
#define EthReadL	ZL

; *****************************************************************
; 	PORT MAP
; *****************************************************************

; ********    PORT A
;  0   output	CP2200 ALE
;  1   output	CP2200 RD/
;  2   output	CP2200 WR/
;  3   input	CP2200 INT/ (has pad)
;  5   input	MCU pad at pin 46: Key Input
;  7   output	FPGA pin 139: in_addr[0]
#define ALE			PORTA, 0
#define RDb			PORTA, 1
#define WRb			PORTA, 2
#define INTb		PINA, 3
#define KeyIn		PINA, 5
#define FPGA_addr_0	PORTA, 7
.MACRO INIT_PORTA
	ldi r16, 0b10000111
	out DDRA, r16
	ldi r16, 0b01010110
	out PORTA, r16
.ENDMACRO


; ********    PORT B
;  4   input	Pad at pin 14 - FPGA pin 82 nSTATUS
;  6   in/out	FPGA pin 32: key line to/from FPGA
#define FPGA_nSTATUS		PINB, 4
#define FPGA_keying			PORTB, 6
.MACRO INIT_PORTB
	ldi r16, 0b01000000
	out DDRB, r16
	ldi r16, 0b10101111
	out PORTB, r16			;Enable some pullups
.ENDMACRO

; ********    PORT C = Output to FPGA data bus in_data[7:0]
;  0   FPGA pin 4
;  1   FPGA pin 3
;  2   FPGA pin 2
;  3   FPGA pin 1
;  4   FPGA pin 144
;  5   FPGA pin 143
;  6   FPGA pin 142
;  7   FPGA pin 141
#define FPGA_data		PORTC
.MACRO INIT_PORTC
	ser r16
	out DDRC, r16
	clr r16
	out PORTC, r16
.ENDMACRO

; ********    PORT D
;  0   output FPGA pin 21 in_strobe
;  1   output FPGA pin 20 nCONFIG
;  2   output FPGA pin 15 DCLK
;  3   output FPGA pin 14 DATA0
;  4   input  Pad at pin 29 - To CP2200 RSTb
#define FPGA_strobe		PORTD, 0
#define FPGA_nCONFIG	PORTD, 1
#define FPGA_DCLK		PORTD, 2
#define FPGA_DATA0		PORTD, 3
#define ETH_RSTb		PIND, 4
.MACRO INIT_PORTD
	ldi r16, 0b00001111
	out DDRD, r16
	ldi r16, 0b11100000
	out PORTD, r16			;Enable pullups
.ENDMACRO

; ********    PORT E
;	0	output	MCU pad at pin 2 - key the TR switch
;   6   input	Pad at pin 8 - FPGA pin 83 CONF_DONE
#define TrSwitch			PORTE, 0
#define FPGA_CONF_DONE		PINE, 6
.MACRO INIT_PORTE
	ldi r16, 0b00000001
	out DDRE, r16
	ldi r16, 0b10111110
	out PORTE, r16
.ENDMACRO

; ********    PORT F = CP2200 address/data bus; input or output
;  0   D0
;  1   D1
;  2   D2
;  3   D3
;  4   D4 
;  5   D5 
;  6   D6
;  7   D7
#define ETH_DATA_OUT	PORTF
#define ETH_DATA_IN		PINF
;NOTE: The PORTF direction is always output.  If you change it to input
;      you MUST change it back to output when you are done.
.MACRO ETH_IN
	out DDRF, regIN				;Set data port PORTF for input
.ENDMACRO
.MACRO ETH_OUT
	out DDRF, regOUT			;Set data port PORTF for output
.ENDMACRO

; ********    PORT G
;  3   output FPGA pin 28: in_addr[1]
#define FPGA_addr_1		PORTG, 3
.MACRO INIT_PORTG
	ldi r16, 0b00001000
	out DDRG, r16
	com r16
	out PORTG, r16			;Enable pullups
.ENDMACRO

; ********    FPGA pins with pads
;  40	input:	key line, +3.3/0 for key down/up
;  41
;  74	output:	red LED


; *****************************************************************
;Macros and functions to provide access to the CP2200:

.MACRO ADDRESS_ETH	;set address on Ethernet chip using ALE
;Call: ADDRESS_ETH address
;Registers input:
;Registers destroyed: r16
;Registers output:
	ldi r16, @0
	out ETH_DATA_OUT, r16
	sbi ALE
	cbi ALE
.ENDMACRO

.MACRO READ_ETH		;read the register at "address" in the CP2200 into "reg" NOT r16
;Call: READ_ETH register address
;Registers input:
;Registers destroyed: r16
;Registers output: the specified register
	ldi r16, @1
	out ETH_DATA_OUT, r16
	sbi ALE
	cbi ALE
	ETH_IN
	cbi RDb
	nop
	nop
	in @0, ETH_DATA_IN
	sbi RDb
	ETH_OUT
.ENDMACRO

.MACRO READ_ETH_RAMRXDATA	;read register RAMRXDATA into the register
;Call: READ_ETH_RAMRXDATA register
;Registers input:
;Registers destroyed:
;Registers output: the specified register
	out ETH_DATA_OUT, regRAMRXDATA
	sbi ALE
	cbi ALE
	ETH_IN
	cbi RDb
	nop
	nop
	in @0, ETH_DATA_IN
	sbi RDb
	ETH_OUT
.ENDMACRO

.MACRO READ_ETH_BUS	;read cycle on bus
;Call: READ_ETH_BUS register
;Registers input:
;Registers destroyed:
;Registers output: the specified register (can be r16)
;NOTE: The bus must have been set to input with ETH_IN
	cbi RDb
	nop
	nop
	in @0, ETH_DATA_IN
	sbi RDb
.ENDMACRO

.MACRO WRITE_ETH_BUS	;write cycle on bus
;Call: WRITE_ETH_BUS register
;Registers input: the specified register (can be r16)
;Registers destroyed:
;Registers output:
;NOTE: The bus must have been set to output
	out ETH_DATA_OUT, @0
	cbi WRb
	sbi WRb
.ENDMACRO

.MACRO WRITE_ETH_I	;write immediate data to "address" in the CP2200
;Call: WRITE_ETH_I address data
;Registers input:
;Registers destroyed: r16
;Registers output:
	ldi r16, @0
	out ETH_DATA_OUT, r16
	sbi ALE
	cbi ALE
	ldi r16, @1
	out ETH_DATA_OUT, r16
	cbi WRb
	sbi WRb
.ENDMACRO

.MACRO WRITE_ETH_R	;write register data to "address" in the CP2200
;Call: WRITE_ETH_R address register (NOT r16)
;Registers input:
;Registers destroyed: r16
;Registers output:
	ldi r16, @0
	out ETH_DATA_OUT, r16
	sbi ALE
	cbi ALE
	out ETH_DATA_OUT, @1
	cbi WRb
	sbi WRb
.ENDMACRO

.MACRO WRITE_ETH_RAMADDRH	;write CP2200 high address byte
;Call: WRITE_ETH_RAMADDRH
;Registers input:
;Registers destroyed:
;Registers output:
	out ETH_DATA_OUT, regRAMADDRH
	sbi ALE
	cbi ALE
	out ETH_DATA_OUT, EthReadH
	cbi WRb
	sbi WRb
.ENDMACRO

.MACRO WRITE_ETH_RAMADDRL	;write CP2200 low address byte
;Call: WRITE_ETH_RAMADDRL
;Registers input:
;Registers destroyed:
;Registers output:
	out ETH_DATA_OUT, regRAMADDRL
	sbi ALE
	cbi ALE
	out ETH_DATA_OUT, EthReadL
	cbi WRb
	sbi WRb
.ENDMACRO

; *****************************************************************
;More macros and functions:
.MACRO SEND_FREQ	;send the phase offset data to the FPGA
	;Initialize the transfer.
	sbi FPGA_addr_0				;set FPGA address bus to 1 (command)
	cbi FPGA_addr_1
	ldi r16, 1					;data byte is 1: reset phase address
	out FPGA_data, r16
	sbi FPGA_strobe				;Reset internal FPGA phase address
	cbi FPGA_strobe
	cbi FPGA_addr_0				;set FPGA address bus to 2
	sbi FPGA_addr_1
	;write data to FPGA at bus address 2; FPGA will increment its own address.
	lds r16, PhaseOffsets		;first byte of data
	out FPGA_data, r16
	sbi FPGA_strobe				;FPGA reads data on rising edge of strobe
	cbi FPGA_strobe
	lds r16, PhaseOffsets + 1	;second byte of data
	out FPGA_data, r16
	sbi FPGA_strobe	
	cbi FPGA_strobe
	lds r16, PhaseOffsets + 2	;third byte of data
	out FPGA_data, r16
	sbi FPGA_strobe
	cbi FPGA_strobe
	lds r16, PhaseOffsets + 3	;fourth byte of data
	out FPGA_data, r16
	sbi FPGA_strobe
	cbi FPGA_strobe
	;send second phase increment (for second frequency)
	lds r16, PhaseOffsets + 4		;first byte of data
	out FPGA_data, r16
	sbi FPGA_strobe				;FPGA reads data on rising edge of strobe
	cbi FPGA_strobe
	lds r16, PhaseOffsets + 5	;second byte of data
	out FPGA_data, r16
	sbi FPGA_strobe	
	cbi FPGA_strobe
	lds r16, PhaseOffsets + 6	;third byte of data
	out FPGA_data, r16
	sbi FPGA_strobe
	cbi FPGA_strobe
	lds r16, PhaseOffsets + 7	;fourth byte of data
	out FPGA_data, r16
	sbi FPGA_strobe
	cbi FPGA_strobe
.ENDMACRO


;******************************************************************
;*	Read r16 bytes of the Ethernet packet into "Packet" in RAM
;******************************************************************
read_packet:
;Registers input: r16, EthRead
;Registers destroyed: r16, r17, Y
;Registers output:
; r16 is the number of bytes to read.  If zero, read 256 bytes.
; Y points to the write position in Packet, and
; EthReadH:EthReadL points to the read position in the CP2200.
	lds YH, PacketWriteH
	lds YL, PacketWriteL
RP20:
	WRITE_ETH_RAMADDRL					;write address low byte
	READ_ETH_RAMRXDATA r17				;read byte
	st Y+, r17							;store byte
	inc EthReadL						;increment CP2200 address
	brne RP40
	inc EthReadH
	WRITE_ETH_RAMADDRH					;write address of high byte
RP40:
	dec r16								;check number of bytes to read
	brne RP20
	;we are done
	sts PacketWriteH, YH
	sts PacketWriteL, YL
	ret

;This code reads using the RXAUTORD interface, and FAILS (code is not used)
auto_read_packet:		;FAILS
	ldi r17, RXAUTORD
	out ETH_DATA_OUT, r17
	sbi ALE
	cbi ALE
	lds YH, PacketWriteH
	lds YL, PacketWriteL
	ETH_IN
RP30:
	cbi RDb
	nop
	nop
	nop
	nop
	in r17, ETH_DATA_IN
	sbi RDb
	st Y+, r17							;store byte
	dec r16								;check number of bytes to read
	brne RP30
	;we are done
	sts PacketWriteH, YH
	sts PacketWriteL, YL
	ETH_OUT
	ret

;******************************************************************
;*	Transmit the data in "Packet".  The packet length is in r18:r17.
;*  The length will be a minimum of 64 bytes.
;******************************************************************
transmit_packet:
;Registers input: r18:r17 is the packet length
;Registers destroyed: r16
;Registers output
	push r10
	push r17
	push r19
	push r20
	push r28
	push r29
	ldi r28, low(Packet)		;Initialize Y -> packet header bytes
	ldi r29, high(Packet)
	tst r18						;make length at least 64 bytes
	brne TPwait
	cpi r17, 64
	brsh TPwait
	ldi r17, 64					;make length 64 bytes
TPwait:
	READ_ETH r10, TXBUSY		;test to see if the transmitter is busy
	tst r10
	brne TPwait					;wait for transmitter
	WRITE_ETH_I TXSTARTH, 0		;zero the start address
	WRITE_ETH_I TXSTARTL, 0
	;see if the last packet was transmitted successsfully

	rjmp TPaddr

	READ_ETH r10, TXSTA2
	sbrs r10, 7					;check the TXOK bit
	rjmp TPaddr
	;
	;the last packet was transmitted OK - use the autowrite interface
	ADDRESS_ETH TXAUTOWR		;set address to auto-write port
	clr r19						;use r20:r19 as the byte count
	clr r20
	;now write all bytes
TPauto:
	ld r16, Y+
	WRITE_ETH_BUS r16
	inc r19
	brne TPauto2
	inc r20
TPauto2:
	cp r20, r18
	brne TPauto
	cp r19, r17
	brne TPauto
	;all bytes have been written
	rjmp TPdone
	;
TPaddr:
	;the last packet was aborted - write all addresses
	clr r19						;use r20:r19 as the write address and byte count
	clr r20
	WRITE_ETH_R RAMADDRH, r20
TPram:
	WRITE_ETH_R RAMADDRL, r19
	ld r15, Y+
	WRITE_ETH_R RAMTXDATA, r15
	inc r19
	brne TPram2
	inc r20
	WRITE_ETH_R RAMADDRH, r20
TPram2:
	cp r20, r18
	brne TPram
	cp r19, r17
	brne TPram
	;all bytes have been written
	WRITE_ETH_R TXENDL, r19		;set the address of the last byte
	WRITE_ETH_R TXENDH, r20

TPdone:
	;the packet is loaded - transmit it
	WRITE_ETH_I TXSTARTH, 0		;zero the start address
	WRITE_ETH_I TXSTARTL, 0
	WRITE_ETH_I TXCN, 0x01		;write 1 to the TXGO bit
TPabort:
	pop r29
	pop r28
	pop r20
	pop r19
	pop r17
	pop r10
	ret

;*******************************************************************
;Timer 1 output compare A interrupt.  The timer adds one to the State register
;iff the State register is odd, and then turns off.

TIMER1:
	cli				;disable interrupts
	push r16
	clr r16			;turn off timer
	sts TCCR1B, r16
	sts TIMSK1, r16
	sbrc State, 0	;check for odd-numbered State
	inc State		;add one to State only if odd
	pop r16
	reti

;********************************************************************
;Reset vector and Main loop

RESET:
	;Initialize stack pointer
	ldi r16, high(RAMEND)
	out SPH, r16
	ldi r16, low(RAMEND)
	out SPL, r16
	clr ip_seq_h
	clr ip_seq_l
	clr State
	;initialize some constants
	ldi r16, RAMRXDATA
	mov regRAMRXDATA, r16
	ldi r16, RAMADDRH
	mov regRAMADDRH, r16
	ldi r16, RAMADDRL
	mov regRAMADDRL, r16
	clr regIN
	ser regOUT
	;Initialize memory to zero from zero_start to zero_end
	clr r16
	ldi r26, low(zero_start)		;X -> zero_start
	ldi r27, high(zero_start)
	ldi r18, low(zero_end)			;r19:r18 -> zero_end
	ldi r19, high(zero_end)
Mzero:
	st X+, r16
	cp r26, r18
	brne Mzero
	cp r27, r19
	brne Mzero
	;Initialize port directions
	INIT_PORTA
	INIT_PORTB
	INIT_PORTC
	INIT_PORTD
	INIT_PORTE
	ETH_OUT			;PORTF direction is always output except during reads
	INIT_PORTG
	;Initialize NIC
	rcall init_CP2200
	;the timer is initialized in reset_timer, not here


MainLoop:
	rcall state_machine				;run the state machine
	READ_ETH r17, ETH_INT0			;clear the receive buffer full interrupt
	READ_ETH r17, TLBVALID			;see if there are any valid packets
	tst r17							;r17 is non-zero if there are packets to read
	breq MainLoop
	;there are one or more packets available
	;see if the current packet has been read correctly
	READ_ETH r17, CPINFOH
	sbrs r17, 7						;check the RXVALID bit - must be 1
	rjmp SkipPacket
	READ_ETH r18, CPINFOL
	sbrc r18, 7						;check the RXOK bit - must be 1
	rjmp Main20
FinishPacket:
	;write 1 to RXCLVR to dismiss packet that has been comletely read
SkipPacket:
	;skip a packet that has not been completely read
	ldi r17, 0x02					;write 1 to RXSKIP to dismiss packet
	WRITE_ETH_R RXCN, r17
	rjmp MainLoop

Main20:
	;packet was received correctly - initialize some variables
	ldi r16, low(Packet)	;Initialize packet write position
	sts PacketWriteL, r16
	ldi r16, high(Packet)
	sts PacketWriteH, r16
	READ_ETH EthReadH, RXFIFOHEADH	;get packet frame start address in the CP2200
	READ_ETH EthReadL, RXFIFOHEADL
	WRITE_ETH_RAMADDRH				;write address of high byte
	READ_ETH UnreadCountH, CPLENH	;packet length
	READ_ETH UnreadCountL, CPLENL
	tst UnreadCountH				;check for short packet
	brne Main25
	mov r16, UnreadCountL
	cpi r16, 42
	brsh Main25
	rjmp SkipPacket					;packet was too short
Main25:
	;read some of the packet; read the number of bytes in r16
	ldi r16, 42
	clr r17
	sub UnreadCountL, r16
	sbc UnreadCountH, r17
	rcall read_packet				;read some of the packet into "Packet" in RAM
	;frame type 0x0806 is ARP, 0x0800 is IP, others are rejected.
	lds r16, Packet + 12			;check frame type first byte
	cpi r16, 0x08
	breq Main30
	rjmp FinishPacket
Main30:				;skip packet if not 0x08 (not ARP or IP)
	lds r16, Packet + 13			;check frame type second byte
	cpi r16, 0x06
	brne TestIP
	;packet is an ARP packet
	tst UnreadCountH				;check if too many bytes
	breq Main40
	rjmp FinishPacket
Main40:
	tst UnreadCountL				;check for remaining bytes to read
	breq MainArp
	mov r16, UnreadCountL			;read remaining bytes
	rcall read_packet
	clr UnreadCountL
MainArp:
	rcall respond_to_arp			;perhaps send response
	rjmp FinishPacket				;finshed with ARP
TestIP:
	;see if the packet is an IP packet
	cpi r16, 0x00					;test for IP packet 0x8000
	breq TestIP20
	rjmp FinishPacket
TestIP20:
	;we have an IP packet
	;fast check for audio data
	lds r16, Packet + 23			;Check IP protocol at byte 23 for UDP==0x11
	cpi r16, 0x11
	brne TestIP60					;check for UDP
	;we have a UDP packet
	lds r16, Packet + 36			;Check the UDP destination port MSB at byte 36
	cpi r16, MyPortHigh
	breq TestIP40					;for UDP, the MSB must be equal to MyPortHigh
	rjmp FinishPacket
TestIP40:
	lds r16, Packet + 37			;Check the UDP destination port LSB at byte 37
	cpi r16, MyAudioPort			;check for audio data
	brne TestIP60
	;we have audio data
	rcall write_audio_data
	rjmp FinishPacket
TestIP60:
	;we have an IP packet that is not audio data
	;check for our IP address
	lds r16, Packet + 30
	cpi r16, MYIP0
	brne BadIP
	lds r16, Packet + 31
	cpi r16, MYIP1
	brne BadIP
	lds r16, Packet + 32
	cpi r16, MYIP2
	brne BadIP
	lds r16, Packet + 33
	cpi r16, MYIP3
	brne BadIP
	;the IP address is good
	;Check to make sure the header is 20 bytes long (no IP options)
	lds r16, Packet + 14
	cpi r16, 0x45
	brne BadIP
	;Check the IP checksum r19:r18
	rcall ip_checksum
	or r18, r19
	brne BadIP
	rjmp GoodIP
BadIP:
	rjmp FinishPacket
GoodIP:
	;IP packet is good
	mov r17, UnreadCountH	;check if too many bytes
	cpi r17, 0x3
	brsh BadIP
	;read in the remaining bytes
	tst UnreadCountH
	breq Main60
	clr r16					;read 256 bytes
	rcall read_packet
	dec UnreadCountH
	breq Main60
	clr r16					;read 256 bytes
	rcall read_packet
	dec UnreadCountH
Main60:
	tst UnreadCountL		;check for remaining bytes to read
	breq TryICMP
	mov r16, UnreadCountL	;read remaining bytes
	rcall read_packet
	clr UnreadCountL
TryICMP:
	;check the protocol
	lds r16, Packet + 23
	cpi r16, 0x01			;Protocol 0x01 is ICMP
	brne TryUDP
	;Protocol is ICMP
	lds r16, Packet + 34	;Check the ICMP type
	cpi r16, 0x08
	brne ICMPdone
	rcall respond_to_ping	;ICMP type is echo request
ICMPdone:
	rjmp FinishPacket		;End of ICMP
TryUDP:
	cpi r16, 0x11			;Protocol 0x11 is UDP
	brne UDPdone
	;We have a UDP packet
	;Check the UDP checksum r19:r18
	;A zero checksum means checksum was not sent - reject it anyway.
	rcall udp_checksum
	mov r16, r18
	or r16, r19
	breq UDPgood
	;Checksum seems to have failed, but sum 0xFFFF may mean 0x0000
	ser r16
	cp r16, r19
	brne UDPfail
	cp r16, r18
	brne UDPfail
	;The sent checksum was 0xFFFF
	;Assume it was originally 0x0000
	clr r16
	sts Packet + 40, r16			;Set checksum to 0x0000
	sts Packet + 41, r16
	rcall udp_checksum				;Check checksum again
	or r18, r19
	breq UDPgood
UDPfail:
	;Checksum failed
	rjmp FinishPacket
UDPgood:
	;We have a good UDP packet - check the destination port
	lds r16, Packet + 37			;Check low byte
	cpi r16, MyTftpPort
	brne UDPgood20
	;We have a file transfer packet (similar to tftp)
	rcall respond_to_tftp
	rjmp FinishPacket
UDPgood20:
	cpi r16, MyKeyingPort			;Check for a key state request
	brne UDPgood40
	;we have a request to send key up/down state
	rcall record_keying_address	;create response, record address in KeyPacket
	rcall send_udp				;send response
	rjmp FinishPacket
UDPgood40:
	;check for more UDP port numbers here
	;rjmp UDPdone			;End of UDP
UDPdone:
	rjmp FinishPacket



;******************************************************************
;*	Initialize the CP2200
;******************************************************************
init_CP2200:
;Registers input:
;Registers destroyed: r15, r16, r17
;Registers output:

	;wait for RESET pin to rise
eth_init_pwr:
	sbis ETH_RSTb
	rjmp eth_init_pwr
	;wait for oscillator initialization
eth_init_osc:
	sbic INTb
	rjmp eth_init_osc
	;wait for self initialization
eth_init_self:
	READ_ETH r17, INT0RD
	sbrs r17, 5
	rjmp eth_init_self

	;disable unwanted interrupts
	WRITE_ETH_I INT0EN, 0b00000001
	WRITE_ETH_I INT1EN, 0b00000000
	;read our MAC address from the flash memory and save
	WRITE_ETH_I FLASHADDRH, 0x1F		;set the address in flash memory
	WRITE_ETH_I FLASHADDRL, 0xFA
	READ_ETH r17, FLASHDATA				;read the first byte
	sts MacAddress    , r17				;save it
	WRITE_ETH_I FLASHADDRL, 0xFB		;read and save the remaining bytes
	READ_ETH r17, FLASHDATA
	sts MacAddress + 1, r17
	WRITE_ETH_I FLASHADDRL, 0xFC
	READ_ETH r17, FLASHDATA
	sts MacAddress + 2, r17
	WRITE_ETH_I FLASHADDRL, 0xFD
	READ_ETH r17, FLASHDATA
	sts MacAddress + 3, r17
	WRITE_ETH_I FLASHADDRL, 0xFE
	READ_ETH r17, FLASHDATA
	sts MacAddress + 4, r17
	WRITE_ETH_I FLASHADDRL, 0xFF
	READ_ETH r17, FLASHDATA
	sts MacAddress + 5, r17
	;initialize the physical layer
	WRITE_ETH_I PHYCN, 0			;disable physical layer
	READ_ETH r17, ETH_INT0			;erase any interrupt flags
	READ_ETH r17, ETH_INT1
	WRITE_ETH_I TXPWR, 0b10000000	;disable TX power save
	WRITE_ETH_I PHYCF, 0b11100010	;options - no negotiation
	WRITE_ETH_I PHYCN, 0b10000000	;enable physical layer
	ldi r16, 5
	rcall delay_ms					;wait for power up
	WRITE_ETH_I PHYCN, 0b11100000	;enable transmitter and receiver
	;check to see if there are pulses from a link partner
	ldi r16, 20
	rcall delay_ms
	READ_ETH r17, ETH_INT1
	sbrs r17, 5						;check WAKEINT bit 5
	rjmp eth_neg40
	;there is a link partner
	ldi r16, 250					;wait 250 msec
	rcall delay_ms
	rjmp eth_neg60
eth_neg40:
	;no partner was detected
	ldi r16, 250					;wait 1.5 seconds
	rcall delay_ms
	ldi r16, 250
	rcall delay_ms
	ldi r16, 250
	rcall delay_ms
	ldi r16, 250
	rcall delay_ms
	ldi r16, 250
	rcall delay_ms
	ldi r16, 250
	rcall delay_ms
eth_neg60:
	;now start autonegotiation
	WRITE_ETH_I PHYCN, 0			;disable physical layer
	WRITE_ETH_I TXPWR, 0b10000000	;disable TX power save
	WRITE_ETH_I PHYCF, 0b11110010	;options - with negotiation
	WRITE_ETH_I PHYCN, 0b10000000	;enable physical layer
	ldi r16, 5
	rcall delay_ms					;wait for power up
	WRITE_ETH_I PHYCN, 0b11100000	;enable transmitter and receiver
eth_neg80:
	READ_ETH r17, ETH_INT1			;check INT1 bit 0: autonegotiation complete
	sbrs r17, 0
	rjmp eth_neg80
	;autonegotiation is complete, but may have failed
	;set power register for LED's
	WRITE_ETH_I IOPWR, 0b00001100	;turn on both led's
	;initialize the MAC
	READ_ETH r17, PHYCN				;read physical state
	sbrs r17, 4						;check full duplex bit
	rjmp eth_duplex_half
	;set full duplex mode
	WRITE_ETH_I MACADDR, MACCF		;write to MACCF register
	WRITE_ETH_I MACDATAH, 0x40
	WRITE_ETH_I MACDATAL, 0xB3
	WRITE_ETH_I MACRW, 0
	WRITE_ETH_I MACADDR, IPGT		;write to IPGT register
	WRITE_ETH_I MACDATAH, 0x00
	WRITE_ETH_I MACDATAL, 0x15
	WRITE_ETH_I MACRW, 0
	rjmp eth_duplex_done
eth_duplex_half:
	;set half duplex mode
	WRITE_ETH_I MACADDR, MACCF		;write to MACCF register
	WRITE_ETH_I MACDATAH, 0x40
	WRITE_ETH_I MACDATAL, 0x12
	WRITE_ETH_I MACRW, 0
	WRITE_ETH_I MACADDR, IPGT		;write to IPGT register
	WRITE_ETH_I MACDATAH, 0x00
	WRITE_ETH_I MACDATAL, 0x12
	WRITE_ETH_I MACRW, 0
eth_duplex_done:
	WRITE_ETH_I MACADDR, IPGR		;write to IPGR register
	WRITE_ETH_I MACDATAH, 0x0C
	WRITE_ETH_I MACDATAL, 0x12
	WRITE_ETH_I MACRW, 0
	WRITE_ETH_I MACADDR, MAXLEN		;write to MAXLEN register
	WRITE_ETH_I MACDATAH, 0x05
	WRITE_ETH_I MACDATAL, 0xEE
	WRITE_ETH_I MACRW, 0
	WRITE_ETH_I MACADDR, MACAD2		;write Ethernet address
	lds r15, MacAddress
	WRITE_ETH_R MACDATAL, r15
	lds r15, MacAddress + 1
	WRITE_ETH_R MACDATAH, r15
	WRITE_ETH_I MACRW, 0
	WRITE_ETH_I MACADDR, MACAD1
	lds r15, MacAddress + 2
	WRITE_ETH_R MACDATAL, r15
	lds r15, MacAddress + 3
	WRITE_ETH_R MACDATAH, r15
	WRITE_ETH_I MACRW, 0
	WRITE_ETH_I MACADDR, MACAD0
	lds r15, MacAddress + 4
	WRITE_ETH_R MACDATAL, r15
	lds r15, MacAddress + 5
	WRITE_ETH_R MACDATAH, r15
	WRITE_ETH_I MACRW, 0
	WRITE_ETH_I MACADDR, MACCN		;write to MACCN register
	WRITE_ETH_I MACDATAH, 0x00
	WRITE_ETH_I MACDATAL, 0x01
	WRITE_ETH_I MACRW, 0
	ret





;******************************************************************
;*	Perform ARP Response
;*   This routine supplies a requesting computer with the
;*   Ethernet modules's MAC (hardware) address.
;*
;*  ARP packets have the following byte offsets and fields:
;*		0 - 5	Ethernet destination
;*		6 - 11	Ethernet source
;*		12 - 13	Ethernet length/type (ARP is 0x0806); the remainder is ARP data:
;*		14 - 15	Hardware address type, 0x0001
;*		16 - 17	Protocol address type, 0x0800
;*		18		Hardware size, 6
;*		19		Protocol size, 4
;*		20 - 21	Operation; 0x0001==request, 0x0002==reply
;*		22 - 27	MAC source address
;*		28 - 31	IP source address
;*		32 - 37	MAC destination address
;*		38 - 41	IP destination address
;*		42 - 59	Zeros to fill out to minimum length
;******************************************************************
respond_to_arp:
;Registers input:
;Registers destroyed: r16
;Registers output: r16 is the packet length
	push r17
	push r28
	push r29

	;first see if this arp packet targets our IP address
	lds r16, Packet + 38
	cpi r16, MYIP0
	brne ARPnoreply
	lds r16, Packet + 39
	cpi r16, MYIP1
	brne ARPnoreply
	lds r16, Packet + 40
	cpi r16, MYIP2
	brne ARPnoreply
	lds r16, Packet + 41
	cpi r16, MYIP3
	brne ARPnoreply
	;our IP agrees - check remaining fields
	lds r16, Packet + 15
	cpi r16, 0x01
	brne ARPnoreply
	lds r16, Packet + 16
	cpi r16, 0x08
	brne ARPnoreply
	lds r16, Packet + 17
	cpi r16, 0x00
	brne ARPnoreply
	lds r16, Packet + 18
	cpi r16, 0x06
	brne ARPnoreply
	lds r16, Packet + 19
	cpi r16, 0x04
	brne ARPnoreply
	lds r16, Packet + 21
	cpi r16, 0x01
	brne ARPnoreply
	rjmp ARPreply
ARPnoreply:
	rjmp ARPdone
ARPreply:
	;this ARP carries our IP address, respond to it
	;write destination MAC address == current source address
	;write to both Ethernet header at offset 0, and ARP data at offset 32
	lds r16, Packet + 6
	sts Packet     , r16
	sts Packet + 32, r16
	lds r16, Packet + 7
	sts Packet +  1, r16
	sts Packet + 33, r16
	lds r16, Packet + 8
	sts Packet +  2, r16
	sts Packet + 34, r16
	lds r16, Packet + 9
	sts Packet +  3, r16
	sts Packet + 35, r16
	lds r16, Packet + 10
	sts Packet +  4, r16
	sts Packet + 36, r16
	lds r16, Packet + 11
	sts Packet +  5, r16
	sts Packet + 37, r16
	;write our source MAC address
	;write to both Ethernet header at offset 6 and ARP data at offset 22
	lds r16, MacAddress
	sts Packet +  6, r16
	sts Packet + 22, r16
	lds r16, MacAddress + 1
	sts Packet +  7, r16
	sts Packet + 23, r16
	lds r16, MacAddress + 2
	sts Packet +  8, r16
	sts Packet + 24, r16
	lds r16, MacAddress + 3
	sts Packet +  9, r16
	sts Packet + 25, r16
	lds r16, MacAddress + 4
	sts Packet + 10, r16
	sts Packet + 26, r16
	lds r16, MacAddress + 5
	sts Packet + 11, r16
	sts Packet + 27, r16
	;write 2 (ARP reply) to operation code second byte
	ldi r16, 2
	sts Packet + 21, r16
	;copy the ARP IP source address to the ARP destination address
	lds r16, Packet + 28
	sts Packet + 38, r16
	lds r16, Packet + 29
	sts Packet + 39, r16
	lds r16, Packet + 30
	sts Packet + 40, r16
	lds r16, Packet + 31
	sts Packet + 41, r16
	;write our source IP address
	ldi r16, MYIP0
	sts Packet + 28, r16
	ldi r16, MYIP1
	sts Packet + 29, r16
	ldi r16, MYIP2
	sts Packet + 30, r16
	ldi r16, MYIP3
	sts Packet + 31, r16
	;the arp response is complete - transmit the packet
	ldi r17, 42				;packet length
	clr r18
	rcall transmit_packet	;transmitter will add to length
ARPdone:
	pop r29
	pop r28
	pop r17
	ret



;******************************************************************
;*	Perform ICMP Echo Reply Function
;*  This routine responds to a ping.
;*
;*  The Ethernet header (14 bytes) is:
;*		0 - 5	Ethernet destination
;*		6 - 11	Ethernet source
;*		12 - 13	Ethernet length/type (IP is 0x0800); the remainder is IP data:
;*
;*	The IP header (20 bytes) is:
;*		14		Version == 0x45
;*		15		Type of service
;*		16 - 17	Length of IP header and data
;*		18 - 19	Identification
;*		20 - 21	Flags and fragment offset
;*		22		Time to Live
;*		23		Protocol: 0x01==ICMP, 0x11==UDP
;*		24 - 25	Header checksum
;*		26 - 29	Source IP address
;*		30 - 33	Destination address
;*		34      Start of IP data
;*
;*	The ICMP ping request (8 bytes plus data) is:
;*		34		ICMP type: 0x08==echo request, 0x00==echo response
;*		35		ICMP code
;*		36 - 37	ICMP checksum
;*		38 - 39	ICMP identifier
;*		40 - 41	ICMP sequence number
;*		42		start of ICMP data
;******************************************************************
respond_to_ping:
;Registers input:
;Registers destroyed: r16, X, Y
;Registers output:
	push r18
	push r19
	ldi r28, low(Packet)	;Initialize Y -> Packet
	ldi r29, high(Packet)
	;set echo reply
	clr r16
	sts Packet + 34, r16				;ICMP type
	sts Packet + 35, r16				;ICMP code
	sts Packet + 36, r16				;ICMP check sum 1
	sts Packet + 37, r16				;ICMP check sum 2
	;Point X to the end of the IP data (end of ICMP data)
	ldi r26, low(Packet + 14)			;X -> Start of IP packet
	ldi r27, high(Packet + 14)
	lds r19, Packet + 16				;length of IP packet and data is r19:r18
	lds r18, Packet + 17
	add r26, r18						;add IP length: X-> End of IP data
	adc r27, r19
	ldi r28, low(Packet + 34)	;Initialize Y to start of ICMP
	ldi r29, high(Packet + 34)
	;Now the ICMP packet is Y to X
	;calculate the ICMP checksum of Y:X in r19:r18
	clr r18
	clr r19
	rcall cksum
	com r18
	com r19
	sts Packet + 36, r19
	sts Packet + 37, r18
	;send the ICMP reply packet back to the sender
	rcall set_sender_address
	rcall send_ip
	pop r19
	pop r18
	ret


;******************************************************************
;*	Perform quasi-TFTP Response - read and write files
;*   This routine reads and writes files using a protocol similar to
;*   TFTP as described in RFC 1350.  All fields are 1 byte (not 2),
;*   and all strings are 1 byte with no ending null.  The port used
;*   is fixed and is defined in the top of this file (not 69).
;*
;*   The frames are:
;*     Read request		RRQ, filename
;*     Write request	WRQ, filename
;*     Data block		DATA, block number, data...
;*     Acknowledgement	ACK, block number
;*     Error condition	ERR, error code
;*   Blocks of data are of fixed length, and a short block ends the session.
;*     The first block determines the length.
;*
;*  The Ethernet header (14 bytes) is:
;*		0 - 5	Ethernet destination
;*		6 - 11	Ethernet source
;*		12 - 13	Ethernet length/type (IP is 0x0800); the remainder is IP data:
;*
;*	The IP header (20 bytes) is:
;*		14		Version == 0x45
;*		15		Type of service
;*		16 - 17	Length of IP header and data
;*		18 - 19	Identification
;*		20 - 21	Flags and fragment offset
;*		22		Time to Live
;*		23		Protocol: 0x11==UDP
;*		24 - 25	Header checksum
;*		26 - 29	Source IP address
;*		30 - 33	Destination address
;*		34      Start of IP data
;*
;*	The UDP header is:
;*		34 - 35	Source port
;*		36 - 37	Destination port
;*		38 - 39	UDP length
;*		40 - 41	UDP checksum
;*		42		Start of UDP data: quasi-tftp frames
;*		42		opcode: RRQ etc.
;*		43		depends on opcode
;******************************************************************
;* The protocol is:
;* RRQ 's'			Read status.  Reply with DATA, 1, data.
;* WRQ 'p' m		Write program number 'm' to FPGA; reply with ACK 0; expect DATA.
;* WRQ 'k' i		Turn on (i==1) or off (i==0) a rapid key up/down test.  No reply.
;* WRQ 'f' pppp		Write frequency pppp (actually phase increment) to FPGA.  No reply.
;* DATA n bytes		Program bytes for block n; reply with ACK n; or ERR on last block.
;******************************************************************
respond_to_tftp:
;Registers input:
;Registers destroyed: many
;Registers output:
	lds r17, Packet + 42		;check the opcode
	cpi r17, WRQ
	breq Tftp10
	rjmp Tftp60
Tftp10:
	;opcode is WRQ, write request *****************************************
	lds r16, Packet + 43
	cpi r16, 'p'				;check for file name 'p'; program the FPGA
	brne Tftp20
	;Request to write program to FPGA
	clr TftpBlockNumber
	inc TftpBlockNumber				;looking for block number one
	clr r16
	sts TftpDataEndValid, r16		;get the length from the first block
	lds r16, Packet + 44			;record the FPGA program byte
	sts RequestedProgram, r16
	clr State						;set idle state zero
	cbi FPGA_keying					;un-keyed state
	;reset the FPGA and zero its clock
	cbi FPGA_DCLK					;reset FPGA clock
	cbi FPGA_nCONFIG				;reset the FPGA
	ldi r16, 100
	call delay_us
	sbi FPGA_nCONFIG
Tftp15:
	sbis FPGA_nSTATUS			;wait for FPGA nStatus to go high
	rjmp Tftp15
	cbi TrSwitch				;set TR switch to receive
	;send response for WRQ 'p' program message
	clr r16
	sts Packet + 38, r16		;write UDP length high byte
	ldi r16, 8 + 2				;send two byte response
	sts Packet + 39, r16		;UDP length
	ldi r16, ACK
	sts Packet + 42, r16
	clr r16
	sts Packet + 43, r16		;ACK for block number zero
	lds r16, Packet + 34		;reverse source and destination UDP ports
	sts Packet + 36, r16
	lds r16, Packet + 35
	sts Packet + 37, r16
	ldi r16, MyPortHigh
	sts Packet + 34, r16
	ldi r16, MyTftpPort
	sts Packet + 35, r16
	rcall set_sender_address	;reverse source and destination addresses
	rcall send_udp				;send response
	rjmp TftpDone

Tftp20:
	cpi r16, 'k'				;check for tests
	brne Tftp30
	lds r16, Packet + 44		;0==test off  1==CW test  2==SSB test
	tst r16
	breq Tftp25
	cpi r16, 1
	breq Tftp22
	;Turn SSB test on
	ldi State, 4
	rjmp TftpDone
Tftp22:	;Turn CW test on
	ldi State, 32
	rjmp TftpDone
Tftp25:
	;Turn tests off
	ldi State, 22				;set State to key-went-up
	rjmp TftpDone

Tftp30:
	cpi r16, 'f'				;check for file name 'f'; set frequency
	breq Tftp31
	rjmp Tftp39
Tftp31:
	;Request to set FPGA frequency; first 4 bytes of data is the main
	;phase increment, the next four is the second (if any)
	;Save the phase offsets in PhaseOffsets
	lds r16, Packet + 44		;first byte of data
	sts PhaseOffsets, r16
	lds r16, Packet + 45		;second byte of data
	sts PhaseOffsets + 1, r16
	lds r16, Packet + 46		;third byte of data
	sts PhaseOffsets + 2, r16
	lds r16, Packet + 47		;fourth byte of data
	sts PhaseOffsets + 3, r16
	;send second phase increment (for second frequency)
	lds r16, Packet + 48		;first byte of data
	sts PhaseOffsets + 4, r16
	lds r16, Packet + 49		;second byte of data
	sts PhaseOffsets + 5, r16
	lds r16, Packet + 50		;third byte of data
	sts PhaseOffsets + 6, r16
	lds r16, Packet + 51		;fourth byte of data
	sts PhaseOffsets + 7, r16
	;Now send the saved phase offsets
	SEND_FREQ
	;No response to frequency change
Tftp39:
	rjmp TftpDone

Tftp60:
	cpi r17, RRQ
	breq Tftp62
	rjmp Tftp70
Tftp62:
	;opcode is RRQ, read request *****************************************
	lds r16, Packet + 43
	cpi r16, 's'				;check for correct file name 's'
	breq Tftp64
	rjmp TftpDone
Tftp64:
	;Request to read status - send data response
	ldi r16, 8 + 2 + 16			;set the UDP length 8 + 2 + data length
	sts Packet + 39, r16		;UDP length low byte
	ldi r16, DATA				;send DATA resonse
	sts Packet + 42, r16
	ldi r16, 1
	sts Packet + 43, r16		;block number one
	;SEND DATA for RRQ 's' - do not forget to adjust length above
	lds r16, MacAddress			;6 BYTES: send Ethernet address
	sts Packet + 44, r16
	lds r16, MacAddress + 1
	sts Packet + 45, r16
	lds r16, MacAddress + 2
	sts Packet + 46, r16
	lds r16, MacAddress + 3
	sts Packet + 47, r16
	lds r16, MacAddress + 4
	sts Packet + 48, r16
	lds r16, MacAddress + 5
	sts Packet + 49, r16
	lds r16, RequestedProgram	;1 BYTE: send FPGA program number
	sts Packet + 50, r16
	clr r16						;1 BYTE: send status byte
	sbic FPGA_nSTATUS			;send FPGA_nSTATUS in bit 0
	ori r16, 0x01
	sbic FPGA_CONF_DONE			;send FPGA_CONF_DONE in bit 1
	ori r16, 0x02
	sts Packet + 51, r16
	lds r16, PhaseOffsets		;8 BYTES:  phase offsets
	sts Packet + 52, r16
	lds r16, PhaseOffsets + 1
	sts Packet + 53, r16
	lds r16, PhaseOffsets + 2
	sts Packet + 54, r16
	lds r16, PhaseOffsets + 3
	sts Packet + 55, r16
	lds r16, PhaseOffsets + 4
	sts Packet + 56, r16
	lds r16, PhaseOffsets + 5
	sts Packet + 57, r16
	lds r16, PhaseOffsets + 6
	sts Packet + 58, r16
	lds r16, PhaseOffsets + 7
	sts Packet + 59, r16
	rjmp TftpReply

Tftp70:
	cpi r17, DATA
	breq Tftp71
	rjmp TftpDone
Tftp71:
	;opcode is DATA, received data *****************************************
	lds r16, Packet + 43		;data block number
	cp r16, TftpBlockNumber		;check for correct block number
	breq Tftp72
	;we got the wrong block number; re-send the last ACK or ERR
	ldi r16, 8 + 2				;set up two byte response
	sts Packet + 39, r16
	lds r16, TftpDataEndValid	;fails for block zero
	sts Packet + 42, r16
	mov r16, TftpBlockNumber	;send prior block number
	dec r16
	sts Packet + 43, r16
	rjmp TftpReply
Tftp72:
	lds r18, Packet + 38		;UDP length -> r18:r17
	lds r17, Packet + 39
	subi r17, 8 + 2				;subtract UDP length and 2-byte tftp length
	sbci r18, 0					;r18:r17 is now the data length
	ldi r28, low(Packet + 44)	;Initialize Y -> data bytes
	ldi r29, high(Packet + 44)
	;START of byte write loop
TftpReadByte:
	tst r18						;test for zero data length
	brne Tftp75					;When r18:r17 is zero, we are done
	tst r17
	brne Tftp75
	rjmp Tftp90
Tftp75:
	ld r16, Y+					;read a data byte
	;send 8 bits to the FPGA, least significant bit first
	ldi r19, 8					;set counter for 8 bits
	;START of bit write loop
Tftp80:
	sbrc r16, 0					;look at bit to send
	rjmp Tftp82
	cbi FPGA_DATA0				;clear data bit
	rjmp Tftp85
Tftp82:
	sbi FPGA_DATA0				;set data bit
Tftp85:
	sbi FPGA_DCLK				;toggle clock
	cbi FPGA_DCLK
	dec r19						;decrement counter
	breq Tftp88					;see if we are done
	lsr r16						;shift next bit to bit zero
	rjmp Tftp80
	;END of bit write loop
Tftp88:
	;done writing 8 bits
	subi r17, 1					;Subtract 1 from r18:r17, bytes to read
	sbci r18, 0
	rjmp TftpReadByte			;Read another byte
	;END of byte write loop
Tftp90:
	;we are done reading data bytes
	ldi r16, 8 + 2				;set up two byte ACK response
	sts Packet + 39, r16
	ldi r16, ACK
	sts Packet + 42, r16
	sts Packet + 43, TftpBlockNumber
	inc TftpBlockNumber
	;if this is the first block, record the full block data size
	lds r16, TftpDataEndValid
	tst r16
	brne Tftp92
	sts TftpDataEndH, r29				;record the address of the buffer pointer
	sts TftpDataEndL, r28				;this is a full size packet
	ldi r16, ACK						;send ACK of last block for bad block number
	sts TftpDataEndValid, r16
	rjmp TftpReply
Tftp92:
	;not the first block; check for end of file
	lds r16, TftpDataEndL
	cp r16, r28							;if data length is short, we have EOF
	brne Tftp95
	lds r16, TftpDataEndH
	cp r16, r29
	brne Tftp95
	rjmp TftpReply
Tftp95:
	;we have EOF, all bytes have been received
	;check FPGA - respond with ACK or ERR
	sbis FPGA_nSTATUS			;check for bad (low) status
	rjmp Tftp97
	ldi r16, 5					;wait for conf done
	call delay_ms
	sbis FPGA_CONF_DONE			;check configuration done
	rjmp Tftp97
	;programming was successful
	ldi State, 10				;set State to starting state 10
	cbi FPGA_keying
	sbi FPGA_addr_0				;set the FPGA address bus to 1 (command)
	cbi FPGA_addr_1
	clr r16						;data byte 0: reset FPGA
	out FPGA_data, r16
	sbi FPGA_strobe				;write resets the FPGA
	ldi r16, 10
	call delay_ms				;delay
	cbi FPGA_strobe
	;send the saved phase offsets that set the output frequency
	SEND_FREQ
	rjmp TftpReply
Tftp97:							;Error branch
	ldi r16, ERR
	sts Packet + 42, r16		;change ACK to ERR
	sts TftpDataEndValid, r16
	rjmp TftpReply

TftpReply:
	clr r16
	sts Packet + 38, r16		;write UDP length high byte
	lds r16, Packet + 34		;reverse source and destination UDP ports
	sts Packet + 36, r16
	lds r16, Packet + 35
	sts Packet + 37, r16
	ldi r16, MyPortHigh
	sts Packet + 34, r16
	ldi r16, MyTftpPort
	sts Packet + 35, r16
	rcall set_sender_address	;reverse source and destination addresses
	rcall send_udp				;send response

TftpDone:
	ret


;******************************************************************
;*	Write audio data to the FPGA
;******************************************************************
write_audio_data:
;Registers input:
;Registers destroyed: r16, r17, r18, r19
;Registers output:
;On input, Packet has valid data up to byte 41, and the
;current read position in the CP2200 is the start of UDP data.

	clr UnreadCountH			;we will read the whole packet
	clr UnreadCountL
	;get the UDP data length r18:r17
	lds r18, Packet + 38		;get UDP length r18:r17
	lds r17, Packet + 39
	subi r17, 8					;subtract UDP header length 8 from UDP length
	sbci r18, 0					;data length is now r18:r17
	;the current read position in the CP2200 is the start of UDP data

	sbi FPGA_addr_0				;set the FPGA address bus to 1 (command)
	cbi FPGA_addr_1
	ldi r16, 2					;data byte 2: reset FPGA write address
	out FPGA_data, r16
	sbi FPGA_strobe				;write to FPGA
	cbi FPGA_strobe

	sbi FPGA_addr_1				;set the FPGA address bus to 3, audio data

WAD20:
	tst r18
	breq WAD50
	ldi r16, 0					;read and write 256 bytes of audio data
WAD30:
	WRITE_ETH_RAMADDRL			;write address low byte
	READ_ETH_RAMRXDATA r19		;read byte
	out FPGA_data, r19			;Write the data byte to the FPGA
	sbi FPGA_strobe				;FPGA reads data on rising edge of strobe
	cbi FPGA_strobe
	inc EthReadL				;increment CP2200 address
	brne WAD40
	inc EthReadH
	WRITE_ETH_RAMADDRH			;write address of high byte
WAD40:
	dec r16						;check number of bytes to read
	brne WAD30
	dec r18
	rjmp WAD20
WAD50:
	;no more 256 byte blocks
	tst r17
	breq WAD90
	;write a last partial block
WAD60:
	WRITE_ETH_RAMADDRL			;write address low byte
	READ_ETH_RAMRXDATA r19		;read byte
	out FPGA_data, r19			;Write the data byte to the FPGA
	sbi FPGA_strobe				;FPGA reads data on rising edge of strobe
	cbi FPGA_strobe
	inc EthReadL				;increment CP2200 address
	brne WAD70
	inc EthReadH
	WRITE_ETH_RAMADDRH			;write address of high byte
WAD70:
	dec r17						;check number of bytes to read
	brne WAD60
WAD90:			;we are done
	ret


;******************************************************************
;*	CHECKSUM CALCULATION ROUTINE
;******************************************************************
cksum:
;Registers input: Y:X the data, r19:r18 the checksum
;Registers destroyed: r16, Y, maybe X
;Registers output: r19:r18 the checksum
;Note: If the length of Y:X is not even, an ending zero is written: *X++ = 0
	push r5
	push r17
	push r20
	clr r5				;Zero
	clr r20				;Third byte of the checksum: r20:r19:r18
	mov r16, r26		;check that X - Y is an even length
	sub r16, r28
	sbrc r16, 0			;Look at bit 0 of the difference
	st X+, r5			;Length was odd; write zero at X and increment X
CKSUM20:
	ld r17, Y+			;Next 16-bit number is r17:r16
	ld r16, Y+
	add r18, r16		;Add to the checksum r20:r19:r18
	adc r19, r17
	adc r20, r5
	brpl CKSUM30		;Check for at least 128 carries in r20
	mov r16, r20		;Add the carries in r20 to checksum
	clr r20
	add r18, r16
	adc r19, r5
	adc r20, r5
CKSUM30:
	cp r26, r28			;Compare X to Y to see if we are done
	brne CKSUM20
	cp r27, r29
	brne CKSUM20
	;No more characters; add the carries in r20 to checksum.
CKSUM40:
	mov r16, r20
	clr r20
	add r18, r16
	adc r19, r5
	adc r20, r5
	breq CKSUM75
	rjmp CKSUM40
CKSUM75:
	pop r20
	pop r17
	pop r5
	ret



;******************************************************************
;*	IpChecksum: Calculate the IP checksum
;*  The existing IP checksum is included.
;******************************************************************
ip_checksum:
;Registers input:
;Registers destroyed:
;Registers output: r19:r18 the checksum
	push r26
	push r27
	push r28
	push r29

	ldi r28, low(Packet + 14)	;Initialize Y -> IP header bytes
	ldi r29, high(Packet + 14)
	;Initialize X -> End of IP header bytes
	ldi r26, low(Packet + 14)
	ldi r27, high(Packet + 14)
	adiw r26, 20			; add 20 byte header length; X -> End of IP header
	;Checksum is r19:r18
	clr r18
	clr r19
	rcall cksum
	com r18
	com r19

	pop r29
	pop r28
	pop r27
	pop r26
	ret



;******************************************************************
;*	UdpChecksum: Calculate the UDP checksum.
;*  The existing UDP checksum is included.
;******************************************************************
udp_checksum:
;Registers input:
;Registers destroyed:
;Registers output: r19:r18 the checksum
	push r6
	push r7
	push r16
	push r17
	push r26
	push r27
	push r28
	push r29

	;calculate the UDP checksum in r19:r18
	;Add UDP length to checksum
	lds r7, Packet + 38	;UDP length is r7:r6
	lds r6, Packet + 39
	mov r18, r6
	mov r19, r7
	;Add IP protocol
	ldi r16, 0x11		;UDP protocol is 0x11
	clr r17
	add r18, r16
	adc r19, r17
	;Add src address, dest address
	ldi r28, low(Packet + 26)
	ldi r29, high(Packet + 26)
	ldi r26, low(Packet + 34)
	ldi r27, high(Packet + 34)
	rcall cksum		
	;Add UDP header and data to checksum
	ldi r28, low(Packet + 34)		;Y -> start of UDP header
	ldi r29, high(Packet + 34)
	ldi r26, low(Packet + 34)
	ldi r27, high(Packet + 34)
	add r26, r6			;Add length; X -> end of UDP data
	adc r27, r7
	rcall cksum			;Add udp header and data in Y:X
	com r18
	com r19

	pop r29
	pop r28
	pop r27
	pop r26
	pop r17
	pop r16
	pop r7
	pop r6
	ret



;******************************************************************
;* set_sender_address
;*   This function reverses the IP and MAC addresses.  It
;*   does not reverse the UDP ports.
;******************************************************************
set_sender_address:
;Registers input:
;Registers destroyed: r16
;Registers output:
	;move IP source address to destination address
	lds r16, Packet + 26
	sts Packet + 30, r16
	lds r16, Packet + 27
	sts Packet + 31, r16
	lds r16, Packet + 28
	sts Packet + 32, r16
	lds r16, Packet + 29
	sts Packet + 33, r16
	;make ethernet module IP address source address
	ldi r16, MYIP0
	sts Packet + 26, r16
	ldi r16, MYIP1
	sts Packet + 27, r16
	ldi r16, MYIP2
	sts Packet + 28, r16
	ldi r16, MYIP3
	sts Packet + 29, r16
	;move hardware source address to destination address
	lds r16, Packet +  6
	sts Packet     , r16
	lds r16, Packet +  7
	sts Packet +  1, r16
	lds r16, Packet +  8
	sts Packet +  2, r16
	lds r16, Packet +  9
	sts Packet +  3, r16
	lds r16, Packet + 10
	sts Packet +  4, r16
	lds r16, Packet + 11
	sts Packet +  5, r16
	;make ethernet module mac address the source address
	lds r16, MacAddress + 0
	sts Packet +  6, r16
	lds r16, MacAddress + 1
	sts Packet +  7, r16
	lds r16, MacAddress + 2
	sts Packet +  8, r16
	lds r16, MacAddress + 3
	sts Packet +  9, r16
	lds r16, MacAddress + 4
	sts Packet + 10, r16
	lds r16, MacAddress + 5
	sts Packet + 11, r16
	ret



;******************************************************************
;*	Send an IP packet to the remote host.
;*	The IP and MAC addresses must already be set.
;*  The IP protocol and length must already be set.
;*	This fills in everything else.
;******************************************************************
send_ip:
;Registers input:
;Registers destroyed: r16
;Registers output:
	;Write IP header
	ldi r16, 0x45			;IP version and header length
	sts Packet + 14, r16
	ldi r16, 0				;type of service
	sts Packet + 15, r16
	inc r0		;increment IP sequence number
	brne Sip40
	inc r1
Sip40:
	sts Packet + 18, r1
	sts Packet + 19, r0
	clr r16					;fragment offset
	sts Packet + 20, r16
	sts Packet + 21, r16
	ldi r16, 20				;TTL
	sts Packet + 22, r16
	clr r16					;checksum
	sts Packet + 24, r16
	sts Packet + 25, r16
	rcall ip_checksum
	sts Packet + 24, r19	;Set checksum
	sts Packet + 25, r18
	;;Set packet length r18:r17
	lds r18, Packet + 16	;IP length
	lds r17, Packet + 17
	ldi r16, 14				;add Ethernet length
	add r17, r16
	clr r16
	adc r18, r16
	call transmit_packet
	ret

;******************************************************************
;*	Send a UDP packet to the remote host.
;*  All UDP data must be set except for the UDP checksum.
;******************************************************************
send_udp:
;Registers input:
;Registers destroyed: r16
;Registers output:
	push r17
	push r18
	push r19
	;calculate the IP length in r19:r18
	lds r19, Packet + 38		;UDP length
	lds r18, Packet + 39
	ldi r16, 20					;add 20 bytes for IP header
	clr r17
	add r18, r16
	adc r19, r17
	sts Packet + 16, r19		;store IP length
	sts Packet + 17, r18
	;calculate the UDP checksum in r19:r18
	clr r16
	sts Packet + 40, r16		;Zero the checksum
	sts Packet + 41, r16
	rcall udp_checksum
	;If checksum r19:r18 is zero, change to 0xFFFF
	tst r19
	brne Sudp60
	tst r18
	brne Sudp60
	ser r18				;Checksum was zero
	ser r19
Sudp60:
	sts Packet + 40, r19		;Store the checksum
	sts Packet + 41, r18
	rcall send_ip
	pop r19
	pop r18
	pop r17
	ret

;******************************************************************
;*	Set the return address in KeyPacket to send key state back to host.
;******************************************************************
record_keying_address:
;Registers input:
;Registers destroyed: r16
;Registers output:
	push r17
	push Xl
	push Xh
	push Yl
	push Yh

	;prepare a key state response in Packet
	ldi r16, 8 + 2				;send two byte response
	sts Packet + 39, r16		;UDP length low byte
	clr r16
	sts Packet + 38, r16		;write UDP length high byte
	lds r16, KeyPacket + 42		;copy key state from KeyPacket
	sts Packet + 42, r16
	lds r16, KeyPacket + 43
	sts Packet + 43, r16
	lds r16, Packet + 34		;reverse source and destination UDP ports
	sts Packet + 36, r16
	lds r16, Packet + 35
	sts Packet + 37, r16
	ldi r16, MyPortHigh
	sts Packet + 34, r16
	ldi r16, MyKeyingPort
	sts Packet + 35, r16
	rcall set_sender_address	;reverse source and destination addresses
	ldi r17, 42					;copy 42 bytes of 44 byte response
	ldi Xl, low(Packet)			;set pointers for copy
	ldi Xh, high(Packet)
	ldi Yl, low(KeyPacket)
	ldi Yh, high(KeyPacket)
RKA20:
	ld r16, X+		;copy Packet to KeyPacket
	st Y+, r16
	dec r17
	brne RKA20

	pop Yh
	pop Yl
	pop Xh
	pop Xl
	pop r17
	ret

;******************************************************************
;*	Send key state back to host.
;******************************************************************
send_key_state:
;Registers input:
;Registers destroyed: r16
;Registers output:

	lds r16, KeyPacket + 36			;check destination port
	tst r16
	breq SKS90						;destination is zero

	push r17
	push Xl
	push Xh
	push Yl
	push Yh

	ldi r17, 44
	ldi Xl, low(Packet)
	ldi Xh, high(Packet)
	ldi Yl, low(KeyPacket)
	ldi Yh, high(KeyPacket)
SKS20:
	ld r16, Y+		;copy KeyPacket to Packet
	st X+, r16
	dec r17
	brne SKS20
	rcall send_udp

	pop Yh
	pop Yl
	pop Xh
	pop Xl
	pop r17
SKS90:
	ret

;******************************************************************
;*	Set or reset the timer.  Call TIMER1 after a delay.
;******************************************************************
reset_timer:
;Registers input: r16, the timer delay in units of 256 microseconds
;Registers destroyed: r16
;Registers output:
	;initialize the timer - count up to OCR1A and call interrupt TIMER1
	cli						;disable interrupts
	sts OCR1AH, r16			;set time delay
	clr r16
	sts OCR1AL, r16
	sts TCCR1A, r16			;zero control register A
	sts TCNT1H, r16			;zero the counter value
	sts TCNT1L, r16
	ldi r16, 0b00001010		;timer operates at clock / 8
	sts TCCR1B, r16
	ldi r16, 0b00000010		;interrupt for output compare A
	sts TIMSK1, r16
	sei						;enable interrupts
	ret

;******************************************************************
;*	State machine keys the TR switch and sends key state to the PC
;******************************************************************
state_machine:
;Registers input: State
;Registers destroyed: r16
;Registers output: State

	;State zero is a do-nothing state
	cpi State, 0
	brne State4
	rjmp StateDone

	;Start of test 2.  Receive SSB data and transmit it.
State4:
	cpi State, 4
	brne State10
	sbi TrSwitch			;key the TR switch
	ldi r16, 1				;send PC "Keyed"
	sts KeyPacket + 42, r16
	rcall send_key_state
	sbi FPGA_keying			;key the FPGA
	ldi State, 0			;nothing further to do
	rjmp StateDone

	;Start of operating states, CW and SSB.
State10:					;wait for the key to go down
	cpi State, 10
	brne State14
	sbic KeyIn
	rjmp StateDone
	;the key went down
	sbi TrSwitch			;key the TR switch
	ldi r16, KEY_DOWN_DELAY	;initialize the timer delay
	rcall reset_timer		;start timer
	ldi r16, 1				;send PC "Keyed"
	sts KeyPacket + 42, r16
	rcall send_key_state
	ldi State, 13			;timer will change State to 14 after a delay
	rjmp StateDone
State14:					;delay expired; key FPGA
	cpi State, 14
	brne State18
	sbi FPGA_keying			;key the FPGA
	ldi State, 18
	rjmp StateDone
State18:					;wait for the key to go up
	cpi State, 18
	brne State21
	sbis KeyIn
	rjmp StateDone
	;the key went up
	ldi r16, KEY_DEBOUNCE	;initialize the timer delay
	rcall reset_timer		;start timer
	ldi State, 21
	rjmp StateDone
State21:					;debounce the key up transition
	cpi State, 21
	brne State22
	sbic KeyIn				;the timer is running; check the key
	rjmp StateDone
	;the key went down again
	clr r16	
	sts TCNT1H, r16			;restart the timer from zero
	sts TCNT1L, r16
	rjmp StateDone
State22:					;the key went up after a debounce delay
	cpi State, 22
	brne State26
	cbi FPGA_keying			;un-key the FPGA
	ldi r16, KEY_UP_DELAY	;initialize the timer delay
	rcall reset_timer		;start timer
	ldi State, 25			;timer will change State to 26 after the delay
	rjmp StateDone
State26:					;delay before un-keying the TR switch
	cpi State, 26
	brne State32
	cbi TrSwitch			;un-key the TR switch
	ldi r16, 0				;send PC "Unkeyed"
	sts KeyPacket + 42, r16
	rcall send_key_state
	ldi State, 10
	rjmp StateDone

	;Start of test 1.  Generate a fast key up/down pattern.
	;This code should be identical to the CW states.
State32:			;Test: key went down
	cpi State, 32
	brne State34
	sbi TrSwitch			;key the TR switch
	ldi r16, KEY_DOWN_DELAY	;initialize the timer delay
	rcall reset_timer		;start timer
	ldi r16, 1				;send PC "Keyed"
	sts KeyPacket + 42, r16
	rcall send_key_state
	ldi State, 33			;timer will change State to 34 after a delay
	rjmp StateDone
State34:			;Test:  delay expired; key FPGA
	cpi State, 34
	brne State38
	sbi FPGA_keying			;key the FPGA
	ldi r16, KEY_TEST		;set key up delay
	rcall reset_timer		;set timer for key up
	ldi State, 37
	rjmp StateDone
State38:			;Test:  wait for the key to go up
	cpi State, 38
	brne State42
	;the key went up
	ldi r16, KEY_DEBOUNCE	;initialize the timer delay
	rcall reset_timer		;start timer
	ldi State, 41
	rjmp StateDone
State42:			;Test:  the key went up after a debounce delay
	cpi State, 42
	brne State46
	cbi FPGA_keying			;un-key the FPGA
	ldi r16, KEY_UP_DELAY	;initialize the timer delay
	rcall reset_timer		;start timer
	ldi State, 45			;timer will change State to 46 after the delay
	rjmp StateDone
State46:			;Test:  delay before un-keying the TR switch
	cpi State, 46
	brne State50
	cbi TrSwitch			;un-key the TR switch
	ldi r16, 0				;send PC "Unkeyed"
	sts KeyPacket + 42, r16
	rcall send_key_state
	ldi r16, KEY_TEST		;set key down delay
	rcall reset_timer		;set timer for key down
	ldi State, 31
	rjmp StateDone
State50:

StateDone:
	ret
