; 
;
; Development code for servo control with RS232 comm to host PC
; Version 5 -- initial development
;              * Changing how mask is built
;              * Removing servo_ctr var
;              * Hacking fix for RB0 to be moved to RA3
;              * Implement interrupts on RB0 for serial port input
;              * Fix timing for pulse widths
;              * Now attempting add query ('A') command (it works)
;              * Adding protection from bad servo number and overwriting memory
;              * Added fix for 0 value (off) for servos 1-7
;
; PINOUTS:
; 
; A0 - Status LED
; A1 - NC
; A2 - Output - RS232 output (transmit)
; A3 - Servo control output
; A4 - NC
; B0 - Input  - RS232 input (receive)
; B1-B7 - Servo control outputs
;
;
;

	processor 16F84

;;;;;;;;;;;;;;;;;;;;;;
; Configuration word : bits 13-4 - 1 (code protection off)
;;;;;;;;;;;;;;;;;;;;;; bit 3     - 1 (power up timer disabled)
;                      bit 2     - 0 (watchdog timer disabled)
;                      bit 1,0   - 10 (HS oscillator)
;          
	__config B'11111111111010'
;
;

	include "p16f84.inc"
	title "Variables *****************************************"
RS232_delay_ctr					equ 0x0c
RS232_counter					equ 0x0d
RS232_datachar					equ 0x0e
temp							equ 0x0f
temp2							equ 0x10
temp3							equ 0x11
w_temp							equ 0x12
;
; servo positions
;
pos0							equ 0x13
pos1							equ 0x14
pos2							equ 0x15
pos3							equ 0x16
pos4							equ 0x17
pos5							equ 0x18
pos6							equ 0x19
pos7							equ 0x1A

curr_pos						equ 0x1B
servo_mask						equ 0x1C
ctr								equ 0x1D

; Used in ASCII conversion
ASCIIO							EQU	0x1F
ASCIIT							EQU 0x20
ASCIIH							EQU 0x21

status_temp						equ 0x22
intmp							equ 0x23
cmd0							equ 0x24
cmd1							equ 0x25
cmd2							equ 0x26
cmd3							equ 0x27
incbytes						equ 0x28
fsr_temp						equ 0x29

;------------------------------------------------------------------
; Assembler defines
CR								equ 0x0D
LF								equ 0x0A

;------------------------------------------------------------------
; Reset vector

	ORG 0
	goto main_code

;------------------------------------------------------------------
; Interrupt vector

	ORG 4
_interrupt_vector

	movwf		w_temp				; Save w register value
	swapf		STATUS, w			; swap status and w
	movwf		status_temp			; Save status register
	movf		FSR, W				; Get the FSR register into W
	movwf		fsr_temp			; Save it into temp variable

	call		RS232_getchar		; Read incoming character

savebyte

	btfsc		incbytes, 0
	goto		savebyte2

	bsf			incbytes, 0			; Set flag to indicate first char received
	movwf		cmd0
	goto		icleanup

savebyte2

	btfsc		incbytes, 1
	goto		savebyte3

	bsf			incbytes, 1
	movwf		cmd1
	goto		icleanup

savebyte3

	btfsc		incbytes, 2
	goto		gotfullcmd

	bsf			incbytes, 2
	movwf		cmd2
	goto		icleanup

gotfullcmd

	movwf		cmd3
	clrf		incbytes			; Clear out flags

	; Now see if we got one of the special commands:
	movf		cmd0, W
	sublw		'A'
	btfss		STATUS, Z			; See if we match 'A'
	goto		fullcmd1
	; If here, we got the 'A'sk command
	call		handleask
	goto		icleanup

fullcmd1

	; If we made it here, assume that we have a position setting command...
	movlw		'0'
;	subwf		cmd0, 1				; Convert single ascii value to binary
	subwf		cmd0, W				; Convert single ascii value to binary
	andlw		B'00000111'			; Mask off upper bits to protect from overwriting deeper memory!
	movwf		cmd0

	call		tobinary			; Convert ascii representation to binary (result in cmd3)

	movlw		0x13				; Point to start of servo positions in memory
	addwf		cmd0, W				; Add the cmd0 offset
	movwf		FSR					; Then set up our pointer address

	movf		cmd3, W				; W <- cmd3
	movwf		INDF				; Store new value at pointer reference!

icleanup

	; Clean up before returning from interrupt
	bcf			INTCON, INTF		; Clear RB0 interrupt flag bit
	
	movf		FSR, W
	movwf		FSR					; Restore FSR register
	swapf		status_temp, w
	movwf		STATUS				; Restore status register
	swapf		w_temp, F
	swapf		w_temp, w

end_interrupt
	retfie							; return from interrupt

;--------------------------------------------------------------------
; Main program start

main_code
	call		main_init

	; Beep LED 3 times
	movlw		D'3'
	movwf		temp3				; temp3 = 3
init_led
	call		beepled
	decfsz		temp3, 1			; temp3 = temp3 - 1
	goto		init_led

	bsf			PORTA, 0			; Turn LED on

	;
	; This is where all the fun begins...
	;
term_loop
	
	movlw		B'10000000'			; Start mask on last (bit 7) servo
	movwf		servo_mask
	
	movlw		0x1A				; This is where the servo positions end in memory.
	movwf		FSR					; Store in indirect register

each_servo

	call		do_single_servo		; Handle this single output

	decf		FSR, 1				; Decrement pointer
	bcf			STATUS, C			; Clear carry flag

	rrf			servo_mask, 1		; Rotate mask to right...
	btfss		STATUS, C			; See if we're in carry yet
	
	goto		each_servo			; If not, keep going

	;
	; Delay for post processing after 8 servos (14ms)
	;
;	movlw		D'14'
	movlw		D'6'
	movwf		temp
	call		delay_ms

	goto term_loop					; Repeat main loop for each servo

_main__end

;------------------------------------------------------------------
; handleask - Handles an 'A'sk command from the serial port
handleask

	call		tobinary			; Convert ascii representation to binary (result in cmd3)

	movlw		0x13				; Point to start of servo positions in memory
	addwf		cmd3, W				; Add the cmd3 (servo number) offset
	movwf		FSR					; Then set up our pointer address

	movf		INDF, W				; W <- servo[num]
	call		printbyte			; Print the byte in ASCII on the seial port
									; That's all there is...
	return

;------------------------------------------------------------------
; Does the processing for a single servo output
do_single_servo

	btfss		servo_mask, 0		; Hacking fix for RB0
	goto		do_ss_cont1
	; If here, then we have to use RA3 instead of RB0

	call		pinfix				; Do the pin fix for RB0 -> RA3
	return			

do_ss_cont1

	movf		INDF, W				; W <- servo[num]
	btfsc		STATUS, Z			; Hop around if servo pos == 0
	goto		do_ss_cont2			; so we don't turn on servo pulse at all...

	movf		servo_mask, W		; W <- servo_mask
	iorwf		PORTB, 1			; OR mask to PORTB to turn on current serv

do_ss_cont2
	
	movlw		D'1'
	movwf		temp				; Delay 1ms which is 0 base time
	call		delay_ms

	movf		INDF, W				; W <- servo[num]
	movwf		ctr					; Store in counter

	; Not entirely sure about this next line (currently untested)
	btfss		STATUS, Z			; Skip call to stepfor if Z bit set (set on movf above)
	
	call		stepfor

	comf		servo_mask, 0		; W <- ~servo_mask
	andwf		PORTB, 1			; PORTB = ~mask * PORTB (turn off current servo)

	movf		INDF, W				; W <- servo[num]
	movwf		ctr
	comf		ctr, 1				; Compliment...so (ctr = 255 - servo[num])
	btfss		STATUS, Z			; If ctr == 0, don't stepfor (it would cause problems!)

	call		stepfor

	return

;------------------------------------------------------------------
; pinfix - handles hack for using RA3 instead of RB0
pinfix

	movf		INDF, W				; W <- servo[num]
	btfsc		STATUS, Z			; Hop around if servo pos == 0
	goto		pinfix1

	bsf			PORTA, 3

pinfix1
	
	movlw		D'1'
	movwf		temp
	call		delay_ms

	movf		INDF, W				; W <- servo[num]
	movwf		ctr					; Store in counter

	; Not entirely sure about this next line (currently untested)
	btfss		STATUS, Z			; Skip call to stepfor if Z bit set (set on movf above)
	
	call		stepfor

	bcf			PORTA, 3

	movf		INDF, W				; W <- servo[num]
	movwf		ctr
	comf		ctr, 1				; Compliment...so (ctr = 255 - servo[num])
	btfss		STATUS, Z			; If ctr == 0, don't stepfor (it would cause problems!)

	call		stepfor

	return

;------------------------------------------------------------------
; Implements a "for" loop with ctr.  Uses/modifies ctr.
; Total instructions used per iteration: 5
stepfor
	nop
	nop

	; New extra nops to double
	nop
	nop
	nop
	nop
	nop

	decfsz		ctr, 1
	goto		stepfor
	return

;------------------------------------------------------------------
; Initialization for main program entry
main_init

	;
	; basic register setup and pin i/o steup
	; kind of stuff every prog requires
	; 

	clrwdt							; Clear watchdog timer
	bcf			INTCON, GIE			; Clear interrupts
	bsf			STATUS, RP0			; Select bank 1

	movlw		0x01				
	movwf		TRISB				; Set TRISB to 0x01 (all output except RB0 input)
	movlw		D'2'
	movwf		TRISA				; Set TRISA to 0x02 (Pins 1 is input)
	bcf			OPTION_REG, INTEDG	; Configure interrupt to happen on FALLING edge
	bcf			STATUS, RP0			; Select bank0 back

	bcf			PORTA, 0			; Turn LED off
	bsf			PORTA, 2			; Set RS232 output to HIGH idle state
	clrf		incbytes			; No incoming serial bytes...

	;
	; set up initial servo positions
	;

	movlw		D'127'				; Initial servo position (should be 0 for production)
	movwf		pos0
	movwf		pos1
	movwf		pos2
	movwf		pos3
	movwf		pos4
	movwf		pos5
	movwf		pos6
	movwf		pos7

	; Turn all servo outputs off
	clrf		PORTB
	bcf			PORTA, 3

	; Now lets configure our interrupt on RB0
	clrf		INTCON				; Disable all interrupts
	bsf			INTCON, INTE		; Enable RB0 external interrupt
	bsf			INTCON, GIE			; Enable all unmasked interrupts

	return

;------------------------------------------------------------------
; beepled - Flashes LED on pin A0 on then off with short delays
beepled
	bsf			PORTA, 0
	movlw		D'250'
	movwf		temp
	call		delay_ms
	movlw		D'250'
	movwf		temp
	call		delay_ms
	bcf			PORTA, 0
	movlw		D'250'
	movwf		temp
	call		delay_ms
	movlw		D'250'
	movwf		temp
	call		delay_ms
	return

;------------------------------------------------------------------
; delay_ms - Delays for number of milliseconds in temp
; Uses/modifies temp2
; Assumes 10MHz clock
; 29 * 0.0000001 * 4 * 86 = 0.0009976 sec

delay_ms
delay_ms_1
	movlw		D'86'
	movwf		temp2
	nop
	nop
delay_ms_2
	nop			; 1
	nop			; 2
	nop			; 3
	nop			; 4
	nop			; 5
	nop			; 6
	nop			; 7
	nop			; 8
	nop			; 9
	nop			; 10
	nop			; 11
	nop			; 12
	nop			; 13
	nop			; 14
	nop			; 15
	nop			; 16
	nop			; 17
	nop			; 18
	nop			; 19
	nop			; 20
	nop			; 21
	nop			; 22
	nop			; 23
	nop			; 24
	nop			; 25
	nop			; 26
	decfsz		temp2, 1		; +1
	goto		delay_ms_2		; +2	--> Total = 29
	nop
	decfsz		temp, 1
	goto		delay_ms_1
	nop
	return

;------------------------------------------------------------------
; RS232_putchar - Sends a char via serial port on pin RA2
; Uses true RS232 polarity
; Char is passed in reg W

RS232_putchar
	movwf		RS232_datachar			; Put w into storage byte
	movlw		D'9'					; Put 9 into W
	movwf		RS232_counter			; Put W into counter byte (10 bits = 1 start + 8 data + 1 stop)
	bcf			PORTA, 2				; Set pin RA2 low (start bit)
	nop
	nop
RS232_1
	call		RS232_bitdelay			; Delay for the bit
	decfsz		RS232_counter, 1		; See if all bits sent (decrement counter)
	goto		RS232_2					; if not, jump ahead
	bsf			PORTA, 2				; Otherwise, set pin high for stop bit
	call		RS232_bitdelay			; wait for bit delay
	return								; and return
RS232_2
	rrf			RS232_datachar, 1		; Rotate byte right through carry, store result in RS232_datachar
	btfss		STATUS, C				; Bit test C, skip if set
	goto		RS232_3					; C not set (bit is a 0)
	bsf			PORTA, 2				; Set pin output to high
	goto		RS232_1					; Loop for next bit
RS232_3
	bcf			PORTA, 2				; Set output pin low C is set (bit is a 1)
	goto		RS232_1					; Loop for next bit
	return

;------------------------------------------------------------------
; RS232_getchar - Receives a char via serial port on pin RB0
; Uses true RS232 polarity
; Char is returned in W

RS232_getchar

	movlw D'8'
	movwf RS232_counter					; _counter__c2c_getchar
	clrf RS232_datachar					; _ret__c2c_getchar

RS232_getch1
	btfsc PORTB, 0
	goto RS232_getch1
	call RS232_bitdelay2				; __c2c_rs232_delay2
	btfsc PORTB, 0
	goto RS232_getch1

RS232_getch2
	call RS232_bitdelay					;__c2c_rs232_delay
	rrf RS232_datachar, F				; _ret__c2c_getchar, F
	bcf RS232_datachar, 7				; _ret__c2c_getchar, 7
	btfsc PORTB, 0						; Input on PORTB
	bsf RS232_datachar, 7				; _ret__c2c_getchar, 7
	decfsz RS232_counter, 1				; _counter__c2c_getchar, 1
	goto RS232_getch2
	call RS232_bitdelay					; __c2c_rs232_delay
	movf RS232_datachar, W				; _ret__c2c_getchar, W
	return

;------------------------------------------------------------------
; RS232_bitdelay - Delays for a bit being sent over serial port
; Assumes 10MHz clock speed

RS232_bitdelay
	movlw		D'82'
	movwf		RS232_delay_ctr
RS232_bitdelay_1
	decfsz		RS232_delay_ctr, 1
	goto		RS232_bitdelay_1
	return

;------------------------------------------------------------------
; RS232_bitdelay2 - Delay used in receiving chars from serial port

RS232_bitdelay2
	movlw D'41'
	movwf RS232_delay_ctr
RS232_bitdelay2_1
	decfsz RS232_delay_ctr, 1
	goto RS232_bitdelay2_1
	return
;------------------------------------------------------------------
; printcrlf - prints a CR+LF pair on the serial port
; Modifies: W, temp
printcrlf
	movlw		'\r'
	call		RS232_putchar
	movlw		'\n'
	call		RS232_putchar
	return

;------------------------------------------------------------------
; printbyte - Prints an 8-bit binary value in ASCII over the serial
;             port.  
; Input: W
; Modifes: W, ASCIIH, ASCIIT, ASCIIO

printbyte
	movwf	ASCIIO
	call	toascii
	movf	ASCIIH, 0
	call	RS232_putchar
	movf	ASCIIT, 0
	call	RS232_putchar
	movf	ASCIIO, 0
	call	RS232_putchar
	return

;------------------------------------------------------------------
; tobinary - Converts a 3-digit ascii representation of a number
;            into binary.  
; Input: cmd1, cmd2, cmd3
; Output: cmd3

tobinary
	movlw		'0'
	subwf		cmd1, 1				; Convert these guys to binary first
	subwf		cmd2, 1
	subwf		cmd3, 1

	movlw		D'100'
tobin2
	movf		cmd1, 1				; Test the file for zero
	
	btfsc		STATUS, Z			; If zero, move on...
	goto		tobin3

	addwf		cmd3, 1				; Add 100 to cmd3
	decf		cmd1, 1
	goto		tobin2

tobin3
	movlw		D'10'
tobin4
	movf		cmd2, 1

	btfsc		STATUS, Z
	goto		tobin5
	
	addwf		cmd3, 1				; Add 10 to cmd3
	decf		cmd2, 1
	goto		tobin4

tobin5
	
	return

;------------------------------------------------------------------
; toascii - Converts an 8-bit binary value in ASCIIO to an
;           ASCII 3-digit representation.
; Output: ASCIIH - hundredths, ASCIIT - tenths, ASCIIO - ones

toascii	
	MOVLW   '0'
	MOVWF   ASCIIH
	MOVWF   ASCIIT

DO100S
	MOVLW   D'100'
	SUBWF   ASCIIO,W
	BNC     DO10S

	MOVWF   ASCIIO
	INCF    ASCIIH, 1
	GOTO    DO100S

DO10S
	MOVLW   D'10'
	SUBWF   ASCIIO,W
	BNC     ADJUST

	MOVWF   ASCIIO
	INCF    ASCIIT, 1
	GOTO    DO10S

ADJUST
	MOVLW   '0'
	ADDWF   ASCIIO, 1

	return


	END
