
;********************************************************************
;
;	Program: inoutbox.asm
;
;	Author:  Herbert Dingfelder, DL5NEG
;
;	Function: 	serves as a universal serial interface-box 
;			between a PC and the outside world
;			reads simple control commands from the serial
;			port of the PC and sets/reads the pin on the
;			in-out-box accordingly
;			during active communcation a LED is switched on
;
;	Hardware:	AT90LS4433 Atmel 8Bit AVR Microprocessor
;			crystal frequency is 3.6864 MHz
;
;	History:	15.02.2001	Start of development
;					RS232 communication works
;					Online editing with terminal works
;					Switch-on Message works
;			16.02.2001	Implementation of Commands started
;			26.02.2001	UART synchonsisation debugged
;			27.02.2001	ADconversion routine works
;			09.03.2001	Set Bit Routine included
;			12.03.2001	Clear Bit Routine included
;			13.03.2001	Read Bit Routine included
;					bin2bit introduced for cleaner code
;			-> Version 1.0 released
;			12.06.2001	bugfix: C5 for LED was not set as output
;			-> Version 1.01 released
;			03.11.2001	Code cleanup for internet publishing
;			-> Version 1.1  released
;			15.08.2002	bugfix: two ",0" as termination were
;					missing in the string definitions at
;					the end of the code
;					(no effect on 4433 but problem if code
;					is ported for 8535)
;			-> Version 1.2	released
;
;********************************************************************


;*** includes ***

.include "4433def.inc"


;*** definitions, equations, constants ***

.def char 	   = r0
.def ad_high 	   = r1
.def ad_low 	   = r2
.def rb_value 	   = r3
.def power    	   = r4
.def bbres    	   = r5
.def temp 	   = r16
.def wait 	   = r17
.def wait1 	   = r18
.def wait2 	   = r19
.def data 	   = r20
.def data_received = r21
.def str_len 	   = r22
.def command 	   = r23
.def sendbyte 	   = r24
.def hex_ascii 	   = r25
.def temp_sub  	   = r26

.def str_ptr_l 	   = r30
.def str_ptr_h 	   = r31

.equ val_bit7 = 128
.equ string = $0060	; string start in memory
			; (SRAM starts at $0060, not at $0000 !!!)

.equ stringmaxlen = 40  ; max. number of char in the input string


;*** interrupt table ***
.CSEG
.ORG 0x00
	rjmp	reset		;jump to reset handler
	reti			;irq0 handler
	reti			;irq1 handler
	reti			;timer1 capture handler
	reti			;timer1 compare handler
	reti			;timer1 overflow handler
	reti			;timer0 overflow handler
	reti			;spi transfer complete handler
	rjmp	uart_rx		;uart rx complete handler
	reti			;udr empty handler
	reti			;uart tx complete handler
	reti			;adc conversion complete interrupt handler
	reti			;eeprom ready handler
	reti			;analog comparator handler



;++++++++++++++++++++++++++ main +++++++++++++++++++++++++++++++++



main:

	ldi str_ptr_l, string	; load the string pointer with the string
	clr str_ptr_h		; start address
	
	clr str_len		; reset the number of char in the string

	rcall sendstartmsg	; send the startup-message via the UART

			
loop:	
	sbi PORTC, 5		; switch of the LED (active low)
	
	sbrs data_received,0	; skip the jump if bit0 is set
				; i.e. if a databyte was received from UART
	
	rjmp loop		; wait for a received databyte in this loop


	;a data byte was received from UART, lets handle it:
	
	clr data_received	; reset my own received flag
	
	cbi PORTC, 5		; switch on the LED (active low)
				
	cpi data, 13		; was the databyte a CR (ASCII 13) ?
	brne not_cr
	rcall cr_received
	rjmp loop
not_cr:

	cpi data, 8		; was the databyte a Backspace (ASCII 8) ?
	brne not_bs
	rcall backspace
	rjmp loop
not_bs:

	cpi data, 10		; was the databyte a LF (ASCII 10) ?
	breq loop		; if yes -> do nothing and ignore it
		
	cpi str_len, stringmaxlen ; ignore input if max. number of char in the
	breq loop		; input string is already reached


	; the databyte is a normal character, add it to the input string
	
	inc str_len		; increments the number of char. in the string
	
	st z+, data		; stores the databyte in the string and inc. 
				; the pointer to the next addr.

	mov sendbyte, data	; echo the received character
	rcall send_ser
			
	rjmp loop
	
	
	
;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++






;---------------------- subroutine reset -------------------------
reset:

init_stack:
	ldi temp, low(RAMEND)	; init stack pointer to last byte of ram
	out SPL, temp

init_portd:			; configure Port D as input
	clr temp		;(Bit 2-7 will be used as general input)
	out DDRD, temp		;(see init_uart for bits 0 and 1)
	ldi temp, 0b11111100	;switch on the internal pull-up resistors
	out PORTD, temp		;  to prevent floating of the inputs

init_uart:
	sbi DDRD,1		; UART TX-pin (PortD, Bit1) as output
	cbi DDRD,0		; UART RX-pin (PortD, Bit0) as input
	
	ldi temp, 0b10011000	; enable TX, enable RX, enable RX-interrupt
	out UCSRB,temp
	
	ldi temp,23		; 9600 Baud with fq=3.6864 MHz
	out UBRR,temp
	clr temp
	out UBRRH,temp
		
init_portc:
	ldi temp, 0b00100000	; configure Port C as input 
	out DDRC, temp		;   except C5 =output for LED

init_portb:
	ser temp		; configure Port B as output
	out DDRB, temp
	
enable_irq:
	ldi temp, val_bit7	; set bit 7 in Statusregister to 
	out SREG, temp		; enable the interupts
	
	rcall pause		; for phasing in the UART:
	ldi temp, 32		; pause - send one character - pause
	out UDR, temp
	rcall pause
		
	rjmp main		; everything initialized, now lets go...


;----------------- interrupt routine uart_rx ---------------------

uart_rx:

	in data, UDR		; read received data from UART
	
	inc data_received	; increment number of received datas

	reti			; end of interrupt routine


;--------------------- subroutine pause --------------------------
;(pause for about 10ms)
pause:
	ldi wait,35
	
wl:	
	ser wait1
wl1:	dec wait1
	brne wl1
	
	dec wait
	brne wl

	ret

;----------------- subroutine send_ser----------------
; sends one byte via UART and waits long enough for the
; byte to be send 
send_ser:
	
	out UDR, sendbyte	; send the character via UART
	
serl:
	sbis	UCSRA, UDRE	; wait till TX is ready
	rjmp	serl

	ret

;------------------------subroutine rec_cmd -----------------------
; This subroutine tries to recognize the command in the inputstring
; Returns the number of the command (0 if no command could be recog.)
rec_cmd:


;*** check if at least 2 char are in the string, otherwise old
;characters may be recognized as commands ********

	clr command		; preset reg command with 0 (no command rec.)
	
	cpi str_len, 2		; check if input is at least 2 char. long
	brlo nocommand		; if no -> go to end of routine (returns 0)
		
	
	ldi str_ptr_l, string	; set the string pointer to the first char
	clr str_ptr_h		; in the string
	
	ld temp, z+
	cpi temp, 's'
	brne noset

	ld temp, z+
	cpi temp, 'b'
	brne noset
	
	ldi command, 1		; the command was recognized as SB set bit
noset:

	ldi str_ptr_l, string	; set the string pointer to the first char
	clr str_ptr_h		; in the string

	ld temp, z+
	cpi temp, 'c'
	brne noclr

	ld temp, z+
	cpi temp, 'b'
	brne noclr
	
	ldi command, 2		; the command was recognized as CB clear bit
noclr:

	ldi str_ptr_l, string	; set the string pointer to the first char
	clr str_ptr_h		; in the string
	ld temp, z+
	cpi temp, 'r'
	brne norb

	ld temp, z+
	cpi temp, 'b'
	brne norb
	
	ldi command, 4		; the command was recognized as RB read bit
norb:

	ldi str_ptr_l, string	; set the string pointer to the first char
	clr str_ptr_h		; in the string
	ld temp, z+
	cpi temp, 'r'
	brne nora

	ld temp, z+
	cpi temp, 'a'
	brne nora
	
	ldi command, 3		; the command was recognized as RA read ADC
nora:

	ldi str_ptr_l, string	; set the string pointer to the first char
	clr str_ptr_h		; in the string
	ld temp, z+
	cpi temp, 'e'
	brne noei

	ld temp, z+
	cpi temp, 'i'
	brne noei
	
	ldi command, 5		; the command was recognized as ei echo input
noei:


nocommand:

	ret
	
	
;-------------------------output string----------------------------
; reads characters from the flash meomory and puts them out via the
; the uart, stops when a '\0' is read from the memory. Starts to read
; at the location Z points to
outputstring:

	lpm 			;read one byte from flash (acc. to Z-pointer)

	tst char		;check if the char is zero (marks end of string)
	breq endos		;if end-of-string -> jump to end of routine

	mov sendbyte,char	;transver the character to sendbyte
	rcall send_ser		;call send_ser to transmit the char. via UART

	adiw ZL,1		;increment Z for next char. (add 1 to 16bit reg.)
	
	rjmp outputstring	;jump up for next character

endos:
	ret
	

;---------------------------setbit---------------------------------
; sets a bit on Output Port B according to inputstring
setbit:
	ldi str_ptr_l, low(2*sb_ok)
	ldi str_ptr_h, high(2*sb_ok)
	rcall outputstring


	ldi str_ptr_l, string	; set the string pointer to the first char
	clr str_ptr_h		; in the string
	
	ld temp, z+		; overread the first 2 characters (sb...)
	ld temp, z+
	
	ld temp, z+		; read the 3rd char.
	
	cpi temp, ' '		; if the 3rd char is no space -> error
	brne sbparametererr

	cpi str_len, 4		; is the input exactly 4 character long?
	brne sbparametererr	; if not -> error
	
	ld temp, z+		; read the 4th char. (number of ADC)

	subi temp, 48		; sub 48 to convert ASCII -> BIN
	
	cpi temp, 6		; check if Bit-Nr. is >= 6
	brsh sbparametererr	; only 1-4 allowed, if >=6 -> error

	
	; all errors excluded, lets go on with bit setting

	ldi sendbyte, ' '	; output space
	rcall send_ser	
	
	mov sendbyte, temp	; output bit number
	subi sendbyte, -48	; (convert to ASCII before output)
	rcall send_ser	
	
	ldi str_ptr_l, low(2*sb_hs)   ; output "has been set"
	ldi str_ptr_h, high(2*sb_hs)
	rcall outputstring

	mov power, temp		; convert the bitnumber to a set bit
	rcall bin2bit		; (calculates bbres=2^power)
	
	in temp, PORTB		; read current value of PORTB
	or temp, bbres		; set the chosen bit
	out PORTB, temp		; write the value back to PORTB
	
	rjmp sbend		; all done, go to end of subroutine
	
sbparametererr:
	ldi str_ptr_l, low(2*parerr)  ;write to terminal: 
	ldi str_ptr_h, high(2*parerr) ;Parameter error
	rcall outputstring

sbend:
	ret

;---------------------------clearbit---------------------------------
; clears a bit on Output Port B according to input
clearbit:
	ldi str_ptr_l, low(2*cb_ok)
	ldi str_ptr_h, high(2*cb_ok)
	rcall outputstring


	ldi str_ptr_l, string	; set the string pointer to the first char
	clr str_ptr_h		; in the string
	
	ld temp, z+		; overread the first 2 characters (sb...)
	ld temp, z+
	
	ld temp, z+		; read the 3rd char.
	
	cpi temp, ' '		; if the 3rd char is no space -> error
	brne cbparametererr

	cpi str_len, 4		; is the input exactly 4 character long?
	brne cbparametererr	; if not -> error
	
	ld temp, z+		; read the 4th char. (number of ADC)

	subi temp, 48		; sub 48 to convert ASCII -> BIN
	
	cpi temp, 6		; check if Bit-Nr. is >= 6
	brsh cbparametererr	; only 1-4 allowed, if >=6 -> error

	
	; all errors excluded, lets go on with bit clearing

	ldi sendbyte, ' '	; output space
	rcall send_ser	
	
	mov sendbyte, temp	; output bit number
	subi sendbyte, -48	; (convert to ASCII before output)
	rcall send_ser	
	
	ldi str_ptr_l, low(2*cb_hc)   ; output "has been cleared"
	ldi str_ptr_h, high(2*cb_hc)
	rcall outputstring

	
	mov power, temp		; convert the bitnumber to a set bit
	rcall bin2bit		; (calculates bbres=2^power)
	
	in temp, PORTB		; read current value of PORTB
	com bbres		; invert all bits in bbres
	and temp, bbres		; clear the chosen bit
	out PORTB, temp		; write the value back to PORTB

	
	rjmp cbend		; all done, go to end of subroutine
	
cbparametererr:
	ldi str_ptr_l, low(2*parerr)  ;write to terminal: 
	ldi str_ptr_h, high(2*parerr) ;Parameter error
	rcall outputstring

cbend:
	ret

	
;---------------------------read adc-------------------------------
readadc:
	ldi str_ptr_l, low(2*ra_ok)  ;write to terminal: READ ADC -
	ldi str_ptr_h, high(2*ra_ok)
	rcall outputstring
	

	ldi str_ptr_l, string	; set the string pointer to the first char
	clr str_ptr_h		; in the string
	
	ld temp, z+		; overread the first 2 characters (ra...)
	ld temp, z+
	
	ld temp, z+		; read the 3rd char.
	
	cpi temp, ' '		; if the 3rd char is no space -> error
	brne parametererr

	cpi str_len, 4		; is the input exactly 4 character long?
	brne parametererr	; if not -> error
	
	ld temp, z+		; read the 4th char. (number of ADC)
	
	cpi temp, '0'		; ADC 0 is not allowed
	breq parametererr	; if 0 -> error
	
	subi temp, 48		; sub 48 to convert ASCII -> BIN
	
	cpi temp, 5		; check if ADC-Nr. is >= 5
	brsh parametererr	; only 1-4 allowed, if >=5 -> error

	
	; all errors excluded, lets go on with the AD conversion
	
	out ADMUX, temp		; switch the MUX to the selected ADC

	
	subi temp, -48		; output the ADC number to the terminal
	mov sendbyte, temp
	rcall send_ser
	
	ldi sendbyte, '='	; output =
	rcall send_ser	
	
	ldi sendbyte, '$'	; output $ to make clear the output is hex
	rcall send_ser	

		
	ldi temp, 0b11100110	; status for ADC: enable adc, start
	out ADCSR, temp		; 1st conversion, free run,
				; clock prescaled with factor 64
				; ->57kHz clock with fq=3.686MHz

	rcall pause		; wait for ADconversion to be performed

	in ad_low, ADCL		; read adc-value (low byte must be
	in ad_high, ADCH	; read first!!!)


	mov temp, ad_high	; get the high-byte of the ADC value,
	cbr temp, $F0		; mask out higher nibble (lower remains)
	
	mov hex_ascii, temp	; convert the hex-number into ASCII
	rcall hex2ascii
	mov sendbyte, hex_ascii ; send the ASCII character to the terminal 
	rcall send_ser

	mov temp, ad_low	; get the low-byte of the ADC value,
	cbr temp, $0F		; mask out lower nibble (higher remains)
	swap temp		; swap nibbles to shift the higher nibble down
	
	mov hex_ascii, temp	; convert the hex number into ASCII
	rcall hex2ascii
	mov sendbyte, hex_ascii	; send the ASCII character to the terminal
	rcall send_ser
	
	mov temp, ad_low	; get the low-byte of the ADC value,
	cbr temp, $F0		; mask out higher nibble (lower remains)
	
	mov hex_ascii, temp	; convert the hex-number into ASCII
	rcall hex2ascii
	mov sendbyte, hex_ascii	; send the ASCII character to the terminal
	rcall send_ser
	
	ldi sendbyte, 32	; send one space to the terminal
	rcall send_ser
		
	rjmp endadc

parametererr:
	
	ldi str_ptr_l, low(2*parerr)  ;write to terminal: 
	ldi str_ptr_h, high(2*parerr) ;Parameter error
	rcall outputstring
	
endadc:
	ret


;-------------------------hex2ascii------------------------------
;converts a byte with value 0-15 to a ascii-code in hex notation
;parameter: hex_ascii (contains the hex value before this routine
;is called and contains the ascii value afterwards)
hex2ascii:

	subi hex_ascii, -48	; add 48 for bin->ascii conversion
				; (there is no addi!-> sub -48)
	
	cpi hex_ascii, 58	; 10 is 58 after the add above
	brlo h2a_end		; if<58 ok, if not make 10->A, etc. 

	subi hex_ascii, -7	; if 10-15 -> add 6 (58->65=A etc.)

h2a_end:
	ret


;--------------------------read bit------------------------------
readbit:
	ldi str_ptr_l, low(2*rb_ok)
	ldi str_ptr_h, high(2*rb_ok)
	rcall outputstring

	ldi str_ptr_l, string	; set the string pointer to the first char
	clr str_ptr_h		; in the string
	
	ld temp, z+		; overread the first 2 characters (ra...)
	ld temp, z+
	
	ld temp, z+		; read the 3rd char.
	
	cpi temp, ' '		; if the 3rd char is no space -> error
	brne rbparametererr

	cpi str_len, 4		; is the input exactly 4 character long?
	brne rbparametererr	; if not -> error
	
	ld temp, z+		; read the 4th char. (number of ADC)
	
	subi temp, 48		; sub 48 to convert ASCII -> BIN
	
	cpi temp, 8		; check if Bit-Nr. is >= 8
	brsh rbparametererr	; only 2-7 allowed, if >=8 -> error

	cpi temp, 2		; check if Bit-Nr. is < 2
	brlo rbparametererr	; only 2-7 allowed, if <2 -> error

	
	; all errors excluded, lets go on with the bit reading
	
	mov power, temp		; convert the bitnumber to a set bit
	rcall bin2bit		; (calculates bbres=2^power)
	
	in rb_value, PIND	; read the physical status of the PortD pins
	and rb_value, bbres	; mask out the chosen bit
	; now r_value is zero when the bit was cleared or
	; r_value is not equal to zero when the bit was set
	
	; now we can go ahead with the output to the terminal
	subi temp, -48		; output the bit number to the terminal
	mov sendbyte, temp
	rcall send_ser

	ldi sendbyte, '='	; output =
	rcall send_ser	
		
	tst rb_value		; test if r_value is zero or not
	breq bitzero		; if zero go to bitzero
	
	ldi sendbyte, '1'	; output char. '1' to the terminal
	rcall send_ser
	rjmp endrb		; all done, got to end of subr.
	
bitzero:
	ldi sendbyte, '0'	; output char. '0' to the terminal
	rcall send_ser
	rjmp endrb		; all done, got to end of subr.

rbparametererr:
	
	ldi str_ptr_l, low(2*parerr)  ;write to terminal: 
	ldi str_ptr_h, high(2*parerr) ;Parameter error
	rcall outputstring
	
endrb:
	ret


;---------------------------bin2bit--------------------------------
; converts a number (0-7) into a set bit in a byte
; e.g. the bit number 3 into 00001000, i.e. mathemtically
; it simply calulates n to the power of 2
; parameter is reg. exp, result is stored in bbres
bin2bit:

	ldi temp_sub, 0b00000001 ; preset bbres with 1
	mov bbres, temp_sub

bbloop:
	tst power		; test if power is already zero
	breq bbdone		; if yes go to end of subroutine
	
	lsl bbres		; logic shift left (bbres=2*bbres)
	
	dec power		; decrement power

	rjmp bbloop		; go up for next round

bbdone:
	ret

;--------------------------echo input------------------------------
echoinput:
	ldi str_ptr_l, low(2*ei_ok)
	ldi str_ptr_h, high(2*ei_ok)
	rcall outputstring

	ldi str_ptr_l, string	; set the string pointer to the first char
	clr str_ptr_h		; in the string

nextchar:	
	ld temp, z+		; load one char and inc. the point to the next one
	
	mov sendbyte, temp	; send the char out via the UART
	rcall send_ser

	dec str_len		; decrement the stringlenght
			
	brne nextchar		; stringlength=0 reached? if not, next character

	ret

;---------------------- subroutine cr_received --------------------

; whenever a CR (Return = ASCII 13) is received via the UART, this
; subroutine is called

cr_received:

	ldi sendbyte,13		; send CR, LF to start a new line
	rcall send_ser
	ldi sendbyte,10	
	rcall send_ser

	rcall rec_cmd		; try to recognize the command
	
	tst command		; if command=0 -> no command could be
	brne iscommand		; recognized, send SYNTAX ERROR
	ldi str_ptr_l, low(2*synerr)
	ldi str_ptr_h, high(2*synerr)
	rcall outputstring
iscommand:
	
	cpi command,1		; if command=1 -> go to setbit
	brne cr_nosetbit
	rcall setbit
cr_nosetbit:

	cpi command,2		; if command=2 -> go to clearbit
	brne cr_noclearbit
	rcall clearbit
cr_noclearbit:

	cpi command,3		; if command=3 -> go to read adc
	brne cr_noreadadc
	rcall readadc
cr_noreadadc:

	cpi command,4		; if command=4 -> go to read bit
	brne cr_noreadbit
	rcall readbit
cr_noreadbit:

	cpi command,5		; if command=5 -> echo input
	brne cr_noechoinput
	rcall echoinput
cr_noechoinput:


	clr str_len		; empty the inputstring and
	ldi str_ptr_l, string	; reset the
	clr str_ptr_h		; stringpointer for the next string

	ldi sendbyte,13		; send CR, LF to start a new line
	rcall send_ser
	ldi sendbyte,10	
	rcall send_ser

	ldi sendbyte,13		; send CR, LF to start a new line
	rcall send_ser
	ldi sendbyte,10	
	rcall send_ser


	ret
	

;----------------------- sendstartmsg -----------------------------
sendstartmsg:

.eseg
on_msg:
	.db 13,10,13,10
	.db "Universial serial PC interface, Herbert Dingfelder DL5NEG",13,10
	.db "For users guide check out www.t-online.de/home/dl5neg",13,10
	.db "For futher questions send a mail to dl5neg",13,10
	.db "Firmware Version 1.1, built 03.11.2001",13,10,13,10,0
.cseg

	ldi temp,low(on_msg) 	;load the EEPROM pointer with the address where
	out EEAR,temp		;the message (see above) is stored

EERead:
	sbic	EECR,EEWE	;if EEWE not clear
	rjmp	EERead		;wait more


	sbi	EECR,EERE	;set EEPROM Read strobe
				;This instruction takes 4 clock cycles since
				;it halts the CPU for two clock cycles
	in	temp,EEDR	;get data

	tst temp		;end of message is marked by an zerobyte
	breq ssm_end		;check if 0, if yes jump to end of routine

	mov sendbyte, temp	;transver the character to register sendbyte
	rcall send_ser		;call send_ser to transmit the char. via UART
	
	in temp, EEAR		;increment EEAR (pointer to current address
	inc temp		;in the EEPROM) for next character
	out EEAR, temp
	
	rjmp EERead		;jump up for next character

ssm_end:
	ret
	
	
;---------------------- subroutine backspace ----------------------------

; whenever a Backspace (Return = ASCII 8) is received via the UART, this
; subroutine is called

backspace:

	tst str_len		; is the stringlength=0 ? If yes ignore Backspace
	breq end_bs

	ld temp, -z		; set stringpointer one character left
				; (temp is not needed, the command is only used
				; to decrement Z)
	
	dec str_len		; decrease the number of char in the string
	
	ldi sendbyte, 8		; send Backspace, Space, Backspace because
	rcall send_ser		; the BS alone only moves the cursor one step
				; left but does not delete the char from the	
	ldi sendbyte, 32	; screen
	rcall send_ser	
		
	ldi sendbyte, 8		
	rcall send_ser		
		
end_bs:	
	ret			; return from subroutine


;*************** output messages *****************************************

synerr:	.db "SYNTAX ERROR - available commands: sb, cb, rb, ra, ei",0	
sb_ok:  .db "SET BIT - Bit",0
sb_hs:  .db " has been set",0
cb_ok:  .db "CLEAR BIT - Bit",0
cb_hc:  .db " has been cleared",0
rb_ok:  .db "READ BIT - Bit ",0
ra_ok:  .db "READ ADC - ADC",0
ei_ok:  .db "ECHO INPUT - ",0
ok_out: .db "OK",0
parerr: .db " parameter not allowed or parameter syntax incorrect!",0
