;***************************************************************************
;*
;* Title:		SO2R Antenna controller boot table/code loader
;* Version:		1.0
;* Last updated: 13 April 2010	
;* Target:		ATMega328P
;*
;* DESCRIPTION
;*
;* This implements the bootloader for the YCCC Antenna Switch Box
;*
;*
;**************************************************************************

;
; Commands
;
; '$'	Status query
;		Returns version number (1 byte)
;
; 'W'	Write a page of flash ROM
;		Receives 2 byte of address (low first)
;		Receives 64 bytes of data
;		Receives 1 checksum byte (sum of previous 66 bytes)
;		Returns status
;
; 'R'	Read a page of flash ROM
;		Receives 2 bytes of address (low first)
;		Returns status
;		Returns 64 bytes of data
;       Returns a checksum byte (sum of previous 64 bytes)
;
; '0'	Boot
;		Start the program at address 0.
;
; Return Status
;
; 'B'	Bad page write
; 'C'	Page write data checksum
; 'X'	Communications error
; 'Y'	Successful
; '?'   Unknown command


.INCLUDE "m328Pdef.inc" 				; Include Register/Bit Definitions for the mega328P

;***** Register definitions

.def    ML          = r0	; Low order multiplier result
.def    MH          = r1	; High order multiplier result

.def	C			= r16

.def	T1			= r17	; High register temporaries
.def	T2			= r18

.def	COUNT		= r19
.def	ERR			= r20
.def	ACTIVITY	= r21

.equ	RSTR	= PORTB0	; Relay strobe
.equ	ISTR	= PORTB1	; Inhibit strobe
.equ	RSER	= PORTB3	; Relay data
.equ	RCLK	= PORTB5	; Relay clock	

.equ	RS232RX = PORTD0	; RS-232 Receive
.equ	RS232TX = PORTD1	; RS-232 Transmit
.equ	ICLK	= PORTD4	; Inhibit clock
.equ	ISER	= PORTD5	; Inhibit data
.equ	BOOTB   = PORTD6    ; Bootloader button

.equ	UBR = 12 			; Baud rate = 76.8 kbps with fCK = 16 MHz

.equ	RAMPAGE	= 0x100		; Start of page of RAM used as a buffer

.equ PAGESIZEB = PAGESIZE*2 ; PAGESIZEB is page size in BYTES, not words

.equ MY_BOOTSTART = FIRSTBOOTSTART	; 512 bytes of boot loader

;***** Boot loader

.org MY_BOOTSTART					; bootstart address according to .equ above

	; Set the stack to the end of RAM
		ldi		T1, high(RAMEND)
		out		SPH, T1
		ldi		T1, low(RAMEND)
		out		SPL, T1

	; RSTR, ISTR, RSER, RCLK are port B outputs
		ldi		T1, (1<<RSTR) | (1<<ISTR) | (1<<RSER) | (1<<RCLK)
		out		DDRB, T1

	; RS-232 TX, ICLK, ISER are port D outputs.  Set the directions.
		ldi		T1, (1<<RS232TX) | (1<<ICLK) | (1<<ISER)
		out		DDRD, T1

	; The boot button needs a pull-up.  All port D outputs go low.
		ldi		T1, (1<<BOOTB)
		out		PORTD, T1

	; Set all port B outputs low.
		clr		T1
		out		PORTB, T1

	; The relay clock, strobe, and data lines are all low.
	; Run the relay clock through 64 cycles to set all of
	; the relays to zero.  Then raise the enable to set it.
		ldi		T1, 64;
        cbi     PORTB, RSTR
relay_loop:
		sbi		PORTB, RCLK
		rjmp	pc+1				; two cycle no-op
		cbi		PORTB, RCLK
		dec		T1
		brne	relay_loop

	; Raise the strobe to latch the data
		sbi		PORTB, RSTR

	; Clear the inhibit lines and set the activity leds
		clr		ACTIVITY
		rcall	do_leds;
		
	; Wait a little while for things to stabilize
		clr		ZH
		clr		ZL
delay_loop:
		rjmp	pc+1				; two cycle no-op
		adiw	ZH:ZL, 1
		brne	delay_loop

	; Check EEPROM location 0.  If it contains 'B' go into the bootloader
eeprom_wait:
		sbic	EECR, EEPE
		rjmp	eeprom_wait

		clr		T1
		out		EEARH, T1			; EEPROM location 0
		out		EEARL, T1
		sbi		EECR, EERE
		in		T2, EEDR
		cpi		T2, 'B'
		brne	check_switch

	; Bootloader wanted.  Clear the EEPROM locaton so it will
	; execute the program next time through.

		out		EEDR, T1
		sbi		EECR, EEMPE
		sbi		EECR, EEPE
		rjmp	bootloader

	; Check the boot button.  If it is clear (button pressed) go into
	; the bootloader.
check_switch:
		sbic	PIND, BOOTB
		jmp		0

bootloader:
		ldi		T1, UBR 			; Baud rate = 19.2 bps
		sts		UBRR0L, T1

		ldi 	T1, (1<<RXEN0)|(1<<TXEN0)	; Enable receiver & transmitter, 8-bit mode
		sts 	UCSR0B, T1 					

main_loop:	
		rcall 	uart_get 			; repeat (C = uart_get)

		rcall	do_leds

	; Check for '$' -- Status query

		cpi		C, '$'
		brne	check_2

		ldi		C, '2'				; Protocol version
		rcall	uart_send
		rjmp	main_loop

	; Check for 'W' -- Write page

check_2:

		cpi		C, 'W'
		brne	check_3

		rcall	uart_get			; Get the low address byte
		mov		ZL, C
		mov		T2, C

		rcall	uart_get			; Get the high address byte
		mov		ZH, C
		add		T2, C

		ldi		COUNT, PAGESIZEB

		ldi		YL, low(RAMPAGE)	; Area in RAM for page storage
		ldi		YH,	high(RAMPAGE)	; Starts at 256 ($100)

get_page_loop:

		rcall	uart_get			; Get another byte		
		add		T2, C
		st		Y+, C				; Store it
		dec		COUNT
		brne	get_page_loop

		rcall	uart_get			; Get the checksum
		cp		T2, C				; Compare
		breq	check_2_ok

		ldi		C, 'C'				; C = bad checksum
		rcall	uart_send
		rjmp	main_loop

check_2_ok:

	; page erase
		ldi		C, (1<<PGERS) | (1<<SELFPRGEN)
		rcall	do_spm

	; re-enable the RWW section
		ldi		C, (1<<RWWSRE) | (1<<SELFPRGEN)
		call	do_spm

		ldi		YL, low(RAMPAGE)	; Reset to beginning of data
		ldi		COUNT, PAGESIZE

write_page_loop:

		ld		ML, Y+				; Get two bytes in R0 and R1
		ld		MH, Y+
		ldi 	C, (1<<SELFPRGEN)
		rcall	do_spm				; Write them to the flash temp
		subi	ZL, -2				; Add 2 to ZL (go to next word)
		dec		COUNT
		brne	write_page_loop
		
	; execute page write
		subi	ZL, PAGESIZEB 		; Point to page base
		ldi		C, (1<<PGWRT) | (1<<SELFPRGEN)
		rcall	do_spm

	; re-enable the RWW section
		ldi		C, (1<<RWWSRE) | (1<<SELFPRGEN)
		call	do_spm

	; Check for success
		ldi		YL, low(RAMPAGE)	; Area in RAM for page storage

		ldi		COUNT, PAGESIZEB

compare_page_loop:
		lpm		T1, Z+
		ld		T2, Y+
		cp		T1, T2
		brne	compare_page_error
		dec		COUNT
		brne	compare_page_loop

confirm:
		ldi		C, 'Y'				; Good
		rcall	uart_send
		rjmp	main_loop

compare_page_error:
		ldi		C, 'B'				; Bad write
		rcall	uart_send
		rjmp	main_loop

	; Check for 'R' -- Read page

check_3:

		cpi		C, 'R'
		brne	check_4

		rcall	uart_get			; Get the low address byte
		mov		ZL, C

		rcall	uart_get			; Get the high address byte
		mov		ZH, C

		ldi		C, 'Y'				; Success
		rcall	uart_send

		ldi		COUNT, PAGESIZEB
		clr		T2					; Clear checksum counter

	; Send 128 bytes of data
read_page_loop:
		lpm		C, Z+
		add		T2, C
		rcall	uart_send
		dec		COUNT
		brne	read_page_loop

	; Send the checksum
		mov		C, T2
		rcall	uart_send

		rjmp	main_loop

	; Check for '0' -- Boot

check_4:
		cpi		C, '0'
		brne	check_bad

		ldi		C, 'Y'				; Success
		rcall	uart_send
confirm_wait:
		lds		T1, UCSR0A			; Wait for the character to be sent
		sbrs	T1, TXC0
		rjmp	confirm_wait
        clr     ZL
        clr     ZH
reboot_wait:
        rcall   uart_ret            ; Small delay
        adiw    Z, 1
        brne    reboot_wait
		jmp	0

check_bad:
		ldi		C, '?'
		rcall	uart_send
		rjmp	main_loop

;---------------- read one character from UART (returned in C) --------------
uart_get:
		lds		ERR, UCSR0A	 		; wait for incoming data
		sbrs	ERR, RXC0
		rjmp	uart_get
		lds		C, UDR0				; return received data in C

		andi	ERR, (1<<FE0) | (1<<DOR0) | (1<<UPE0)
		breq	uart_ret

		ldi		C, 'X'				; Send the comm error
        rcall   uart_send
        rjmp    uart_get

;---------------- send one character via UART (parameter is C) --------------
uart_send: 							
		lds		ERR, UCSR0A			; wait for empty transmit buffer
		sbrs	ERR, UDRE0
		rjmp	uart_send
		sts		UDR0, C 			; UDR = C, start transmission

uart_ret:
		ret

;-------------------- wait for SPM to be ready for new tasks ------------------
; while( SPMCR_REG & (1<<SPMEN) )

do_spm:

		sbic	EECR, EEPE			; check that no EEPROM write access is present
		rjmp	do_spm

wait_for_spm:
		in		T1, SPMCSR			; read the content of SPMCR into register
		andi	T1, (1<<SELFPRGEN)	; check if bit SPMEN is set
		brne	wait_for_spm		; and do more loops if bit not yet cleared

	; SPM timed sequence
		out		SPMCSR, C
		spm
		ret

;-------------------- Clear inhibits and set activity LEDs --------------------
do_leds:

	; Enable the latch
		cbi		PORTB, ISTR

	; Flip the LEDs
		ldi		T1, 0b11000000
		eor		ACTIVITY, T1

	; Shift the bits into the inhibit/activity register
		mov		T1, ACTIVITY
		ldi		T2, 8
leds_loop:
		cbi		PORTD, ICLK
		tst		T1
		brmi	leds_loop_set
		cbi		PORTD, ISER
		rjmp	leds_loop_out
leds_loop_set:
		sbi		PORTD, ISER
leds_loop_out:
		lsl		T1
		sbi		PORTD, ICLK
		dec		T2
		brne	leds_loop

	; Latch the new value
		sbi		PORTB, ISTR
		ret

