; WRITTEN BY 		G.C.Hartnup
; Copyright Reserved	G.C.Hartnup
; DATE	July 5 2020
;*********************** 
;****CURRENT VERSION**** issue 1.0  5/7/20
;***********************
;		 	
; FILE SAVED AS 	NCAD.ASM
; FOR 		PIC 16F818
	list	p=16F818
	errorlevel -302	;omit memory bank warning messages
; 8.00 MHz Internal Clock 
; INSTRUCTION CLOCK 	2.00 MHz,  Tick = 0.5uS 
;
; FUNCTION	DCC Accessory decoder
;		4 servo channels
;		Page address set on Hex Switch
;		Servo Limits set by Potentiometer
;	Library Files
	include	"P16F818.inc"
;------------------------
;--- Set Configuration---
;------------------------
; Code protection off
; Watchdog timer on
; PWRTE Power Restart enable
; Internal Oscillator
; Master Clear OFF (Makes Pin 3 available, MCLR tied to Vdd)
	__CONFIG   _CP_OFF & _PWRTE_ON & _INTRC_IO  & _WDT_ON & _MCLRE_ON
;
;-----------
;---Ports---
;-----------
;
;logic level dcc input
#define	_in	PORTA,RA4	;dcc in
;calibration mode
#define	_ANIN	PORTA,AN0	;analogue in from Pot
#define	_save	PORTB,RB4	;save data
#define	_hilo	PORTB,RB6	;hi/lo value
#define	_kal	PORTB,RB5	;set calibration mode
;servo outputs
#define	_servo1	PORTA,RA2	;servo 1 output
#define	_servo2	PORTA,RA1	;servo 2 output
#define	_servo3	PORTA,RA7	;servo 3 output
#define	_servo4	PORTA,RA6	;servo 4 output
;hex dil switch
#define	_ad1	PORTB,RB0	;page address bit 0 from hex switch
#define	_ad2	PORTB,RB2	;page address bit 2
#define	_ad4	PORTB,RB3	;page address bit 4
#define	_ad8	PORTB,RB1	;page address bit 8
;acknowledge led
#define	_led	PORTB,RB7	;signal led
;----------
;---data---
;----------
_trisA	equ	0x11		;RA0 and RA4 as i/p
_trisB	equ	0x7f		; port RB7 as o/p. rest as i/p
_adcon1	equ	0x8e		;A/D settings
_adcon0	equ	0x41
s1	equ	2		;servo o/p bits
s2	equ	1
s3	equ	7
s4	equ	6
;
;
;---------------
;---Variables---
;---------------
CC	equ	0x2c		;micellaneous use
DD	equ	0x2d
EE	equ	0x2e
FF	equ	0x2f
cnt	equ	0x30		;bit counter
cnt2	equ	0x31		;pulse counter
address	equ	0x32		;received address
_data	equ	0x33		;received data
byte	equ	0x34		;value returned by getbyte
instr	equ	0x35		;received instruction
errb	equ	0x36		;received error byte
_temp	equ	0x37		;temporary storage
oldch1	equ	0x38		;old servo states
oldch2	equ	0x39
oldch3	equ	0x3a
oldch4	equ	0x3b
uhi	equ	0x3c		;pulse length hi byte
ulo	equ	0x3d		;pulse length lo byte
;
minlo1	equ	0x3e		;negated servo pulse lengths
minhi1	equ	0x3f		;servo 1
maxlo1	equ	0x40
maxhi1	equ	0x41
;
minlo2	equ	0x42		;negated servo pulse lengths
minhi2	equ	0x43		;servo 2
maxlo2	equ	0x44
maxhi2	equ	0x45
;
minlo3	equ	0x46		;negated servo pulse lengths
minhi3	equ	0x47		;servo 3
maxlo3	equ	0x48
maxhi3	equ	0x49
;
minlo4	equ	0x4a		;negated servo pulse lengths
minhi4	equ	0x4b		;servo 4
maxlo4	equ	0x4c
maxhi4	equ	0x4d
;
v	equ	0x4e		;received state
myaddr	equ	0x4f		;decoder page address
mask	equ	0x50		;mask for servo drive
;----------
;---data---
;----------
blo	equ	0xdf		;frame pulse length
bhi	equ	0xb1	
;
bg	org	0	;start or reset vector
	BANKSEL	PORTA
	goto 	init
st	org	4	;interrupt vector
	retfie		;return, in case one happens
;-----------------
;---subroutines---
;-----------------
;EERAM
;-----------------
;---Write EERAM---
;-----------------
; on entry CC contains EEram address, DD contains data
WRAM	movf	CC,w
	BANKSEL	EECON1
	btfsc	EECON1, WR	; Wait for write
	goto	$-1		; to complete
	BANKSEL	EEADR	; Select Bank of EEADR
	movf		CC, W
	movwf	EEADR
	movf	DD, W
	movwf	EEDATA
	BANKSEL	EECON1
	bcf	EECON1, EEPGD ; Point to DATA
	bsf	EECON1, WREN
	bcf	INTCON, GIE
	movlw	0x55
	movwf	EECON2	
	movlw	0xAA
	movwf	EECON2
	bsf	EECON1, WR
	bcf	EECON1, WREN
	btfsc	EECON1,WR		;loop until write completed
	goto	$-1
	BANKSEL	TMR1
	return
;----------------
;---Read EERAM---
;----------------
;on entry w contains address, on exit w contains data			
; on entry w holds eeram address
; on exit w holds data
RRAM
	BANKSEL	EEADR
	movwf	EEADR		; set address
	BANKSEL	EECON1
	bcf	EECON1,EEPGD       ; do a read
	bsf	EECON1,RD
	BANKSEL	EEDATA
	movf	EEDATA,w		; data to w
	BANKSEL	PORTA
	return
;---------------------
;---16 bit addition---
;---------------------
;CC,DD +EE,FF = CC.DD
addit	movf 	EE,w	;2 byte addition routine
 	addwf 	CC,f
	btfsc 	STATUS,0
	incf	DD,f
	movf	FF,w
 	addwf 	DD,f
 	skpnc
 	retlw 	-1
 	retlw 	0
	return
;--------------------------------------------
;---read in device address from hex switch---
;--------------------------------------------
getmyaddr	clrf	myaddr
	btfss	_ad1
	bsf	myaddr,0
	btfss	_ad2
	bsf	myaddr,1
	btfss	_ad4
	bsf	myaddr,2
	btfss	_ad8
	bsf	myaddr,3
	return
;----------------------
;---get pulse length---
;----------------------
;measure from low edge to next low edge
;returns 1 or 0 in w
;if timer1 overflows, dcc controller has probably tripped
; _in is the input port
getbit	clrwdt
	btfsc	_in
	goto	getbit
pb1	clrwdt
	btfss	_in		;is it lo?
	goto	pb1
	clrf	TMR1L		;zero value of timer       
	clrf	TMR1H		;clear msb
	bsf     T1CON,TMR1ON    ;start timer 1
pb2	clrwdt
	btfsc	_in
	goto	pb2
	bcf	T1CON,TMR1ON    ;stop Timer 1
	movf	TMR1L,w		;get lo byte
	movwf	CC
	decf	CC,f		;less 1
	movf	TMR1H,w		;get hi byte
	movwf	DD
;determine if pulse is a one or a zero
;returns 1 or 0 in w
	bnz	d1	;far too long
	movlw	0x4d	;less than 77 microseconds
	subwf	CC,w
	bc	d1	;it is a nought
	movlw	1	;it is a one
	return
d1	clrw	;report nought
	return
;get 8 bits from the data stream, returned in byte 
getbyte	clrwdt
	clrf	byte
	bcf	PIR1,0	;clear overflow flag
	movlw	0xf8
	movwf	cnt	;to count bits
gb1	call	getbit	;get bit from dcc stream
	movwf	FF
	rrf	FF,f	;bit into C
	rlf	byte,f	;hence into byte
	incfsz	cnt,f	;test for 8 bits
	goto 	gb1
	return
;----------------------------------
;---packet spacing for servo o/p---
;----------------------------------
frame	bcf	T1CON,0	;stop timer1
	movlw 	blo	;load frame length
	movwf	TMR1L
	movlw	bhi
	movwf	TMR1H
	bcf	PIR1,0	;clear tmr1 flag
	bsf	T1CON,0	;start timer 1
	nop
	btfss	PIR1,0	;check tmr1 flag for end
	goto 	$-2
	bcf	PIR1,0	;clear tmr1 flag
	clrwdt		;after each frame spacer
	return
;----------------------------
;---generate a servo pulse---
;----------------------------
;on entry CC, DD hold pulse length
;bit turned on and off by caller
pulse	bcf	T1CON,0	;stop timer1
	bcf	PIR1,0	;clear timer1 flag
	movf 	CC,w
	movwf	TMR1L
	movf	DD,w
	movwf	TMR1H
	bsf	T1CON,0	;start timer 1
	nop
	btfss	PIR1,0	;check timer1 flag for end
	goto 	$-2
	return
;-----------------------
;---servo pulse train---
;-----------------------
;on entry ulo, uhi contains pulse length
;on entry mask contains PORTA bit for servo
fcp	movlw	0x14	;twenty pulses only
	movwf	cnt2	
	movf	ulo,w
	movwf	CC
	movf	uhi,w
	movwf	DD
fcp1	movf	mask,w
	iorwf	PORTA,f		;selected bit high
	call	pulse
	movlw	0x39
	andwf	PORTA,f		;all servo bits low
	call	frame
	decfsz	cnt2,f
	goto	fcp1
	clrwdt
	return
;--------------------------------
;---- RESET & INITIALISATION ----
;--------------------------------
;this section configures the PIC
init
	BANKSEL	PORTA	; set up Memory Bank 0
	btfss	STATUS,4		;was it wdt restart?
	goto	bp		;yes, don't change anything, stop servos resetting
	clrf	PORTA
	BANKSEL	OSCCON
	movlw	0x60
	movwf	OSCCON	;select 4MHz clock
;set watchdog and pullups
	movlw	0x0e
	movwf	OPTION_REG
;set port i/o
	movlw	_trisA
	movwf	TRISA	;PortA I/O
	movlw	_trisB
	movwf	TRISB	;PortB I/O
;inhibit interrupts
	clrf	INTCON
;turn off comparator
	BANKSEL	CCP1CON
	clrf	CCP1CON
;turn off A/D
	clrf	ADCON0
;configure timer 1
	BANKSEL	T1CON
	movlw	0x80
	movwf	T1CON
;
;-----------------------------------
;---Get Servo Settings from EERAM---
;-----------------------------------
;servo 1
	movlw	0		;EERAM relative address
	call	RRAM		;read EERAM
	movwf	minlo1
	comf	minlo1,f		;(rough negate)
	movlw	1
	call	RRAM
	movwf	minhi1
	comf	minhi1,f
	movlw	2
	call	RRAM
	movwf	maxlo1
	comf	maxlo1,f
	movlw	3
	call	RRAM
	movwf	maxhi1
	comf	maxhi1,f
;servo 2
	movlw	4
	call	RRAM
	movwf	minlo2
	comf	minlo2,f
	movlw	5
	call	RRAM
	movwf	minhi2
	comf	minhi2,f
	movlw	6
	call	RRAM
	movwf	maxlo2
	comf	maxlo2,f
	movlw	7
	call	RRAM
	movwf	maxhi2
	comf	maxhi2,f
;servo 3
	movlw	8
	call	RRAM
	movwf	minlo3
	comf	minlo3,f
	movlw	9
	call	RRAM
	movwf	minhi3
	comf	minhi3,f
	movlw	0x0a
	call	RRAM
	movwf	maxlo3
	comf	maxlo3,f
	movlw	0x0b
	call	RRAM
	movwf	maxhi3
	comf	maxhi3,f
;servo 4
	movlw	0x0c
	call	RRAM
	movwf	minlo4
	comf	minlo4,f
	movlw	0x0d
	call	RRAM
	movwf	minhi4
	comf	minhi4,f
	movlw	0x0e
	call	RRAM
	movwf	maxlo4
	comf	maxlo4,f
	movlw	0x0f
	call	RRAM
	movwf	maxhi4
	comf	maxhi4,f
;--------------------------------------------
;---read in device address from hex switch---
;--------------------------------------------
	call	getmyaddr
	bsf	myaddr,7	;set bit 7
	clrwdt
;-----------------------
;---Initialise Servos---
;-----------------------
	clrf	oldch1		;set last state to false
	clrf	oldch2
	clrf	oldch3
	clrf	oldch4
;move servos to start position  (min)
	movf	minlo1,w		;servo 1
	movwf	ulo		;set pulse length
	movf	minhi1,w
	movwf	uhi
	clrf	mask
	bsf	mask,s1
	call	fcp		;generate pulse train	
	movf	minlo2,w		;servo2
	movwf	ulo
	movf	minhi2,w
	movwf	uhi
	clrf	mask
	bsf	mask,s2
	call	fcp		;generate pulse train	
	movf	minlo3,w		;servo 3
	movwf	ulo
	movf	minhi3,w
	movwf	uhi
	clrf	mask
	bsf	mask,s3
	call	fcp		;generate pulse train		
	movf	minlo4,w		;servo 4
	movwf	ulo
	movf	minhi4,w
	movwf	uhi
	clrf	mask
	bsf	mask,s4
	call	fcp		;generate pulse train	
;now start up
;
;test for calibrate mode
bp	bcf	_led	;light out
	btfss	_kal
	goto	kal	;switch is "on" so go to calibrate mode
;-----------------------
;---look for Preamble---
;-----------------------
;look for preamble, >10 successive "1"s
;don't bother about service mode (long preamble)
;as it is not used
	clrf 	cnt
p1	clrwdt
	bcf	PIR1,0	;clear overflow flag
	call 	getbit	;result returned in w
	iorlw	0	;test for "1"
	bz	bp	;it wasn't a "1",start again
	incf	cnt,f	;increment counter
	movlw	0x0a	;ten
	subwf	cnt,w
	bnz	p1
;check length of preamble
pb16	call	getbit
	incf	cnt,f	;increment counter
	iorlw	0	;look for end
	bnz	pb16	
;-----------------------
;--- Get Address Byte---
;-----------------------
;we have already received start bit so get a data byte
p2	call	getbyte
;now look for a zero in next startbit
	call	getbit
	iorlw	0
	bnz	bp	;failed start again
	movf	byte,w
	movwf	address
;get data
	call 	getbyte	;result returned in byte
;look for a zero in next startbit
	call	getbit	;returned in w
	iorlw	0
	bnz	bp	;failed start again
	movf	byte,w
	movwf	_data
;get error byte and test
	call 	getbyte	;result returned in byte
;look for a one in startbit	(end of packet)
	call	getbit	;returned in w
	xorlw	1
;	bnz	bp	;failed, don't bother
	movf	byte,w
	movwf	errb
;check page address
	movf	myaddr,w
	subwf	address,w	;compare with device page number
	bnz	bp	;fail, so round again
;process instruction
;we have valid data, so check it out
;we are looking for 
	movlw	7
	andwf	_data,f	;mask off unwanted bits
;on or off
	movlw	0x01
	btfsc	_data,0
	clrw
	movwf	v	;LSB contains servo state, "1" or "0"
;now find which channel
;and set ulo, uhi as pulse length
	rrf	_data,f	;lsb into carry
	movlw	0x03	;mask 2 lowest bits
	andwf	_data,f	;sub channel address now in _data
	bz	sig1
	decf	_data,f
	bz	sig2
	decf	_data,f
	bz	sig3
;process channel 4		
;check if needed
	movf	oldch4,w
	subwf	v,w
	bz	bp	;not needed
	movf	v,w	;change last setting
	movwf	oldch4
;set pulse length on whether v is "1" or "0"
	btfsc	v,0
	goto	sig4b	;v is false
	movf	minlo4,w
	movwf	ulo
	movf	minhi4,w
	movwf	uhi
	clrf	mask
	bsf	mask,s4
	call	fcp	
	goto	bp
sig4b	movf	maxlo4,w
	movwf	ulo
	movf	maxhi4,w
	movwf	uhi
	clrf	mask
	bsf	mask,s4
	call	fcp		;generate pulse chain
	goto	bp
;process channel 3		
sig3
;check if needed
	movf	oldch3,w
	subwf	v,w
	bz	bp	;not needed
	movf	v,w	;change last setting
	movwf	oldch3
;set pulse length on whether v is true of false
	btfsc	v,0
	goto	sig3b	;v is false
	movf	minlo3,w
	movwf	ulo
	movf	minhi3,w
	movwf	uhi
	clrf	mask
	bsf	mask,s3
	call	fcp	
	goto	bp
sig3b	movf	maxlo3,w
	movwf	ulo
	movf	maxhi3,w
	movwf	uhi
	clrf	mask
	bsf	mask,s3
	call	fcp		;generate pulse chain
	goto	bp
;process channel 2		
sig2
;check if needed
	movf	oldch2,w
	subwf	v,w
	bz	bp	;not needed
	movf	v,w	;change last setting
	movwf	oldch2
;set pulse length on whether v is true of false
	btfsc	v,0
	goto	sig2b	;v is false
	movf	minlo2,w
	movwf	ulo
	movf	minhi2,w
	movwf	uhi
	clrf	mask
	bsf	mask,s2
	call	fcp	;generate pulse chain
	goto	bp
sig2b	movf	maxlo2,w
	movwf	ulo
	movf	maxhi2,w
	movwf	uhi
	clrf	mask
	bsf	mask,s2
	call	fcp		;generate pulse chain
	goto	bp
;process channel 1		
sig1
;check if needed
	movf	oldch1,w
	subwf	v,w
	bz	bp	;not needed
	movf	v,w	;change last setting
	movwf	oldch1
;set pulse length on whether v is true of false
	btfsc	v,0
	goto	sig1b	;v is false
	movf	minlo1,w
	movwf	ulo
	movf	minhi1,w
	movwf	uhi
	clrf	mask
	bsf	mask,s1
	call	fcp		;generate pulse chain
	goto	bp
sig1b	movf	maxlo1,w
	movwf	ulo
	movf	maxhi1,w
	movwf	uhi
	clrf	mask
	bsf	mask,s1
	call	fcp		;generate pulse chain
	goto	bp
;---------------------------
;---Calibrate Servo Throw---
;---------------------------
kal
	BANKSEL	ADCON1	;set up a/d
	movlw	_adcon1
	movwf	ADCON1
	BANKSEL	ADCON0
	movlw	_adcon0
	movwf	ADCON0	;turn on A/D
	call	frame	;to give a pause to settle A/D
kal2	clrwdt
	bsf	_led	;light on to indicate calibration mode
	call	getmyaddr	;renew address in case of change
	decf	myaddr,f	;reduce range to 0 -3
	movlw	3	;mask off all but last 2 bits, value in range 0-3
	andwf	myaddr,f	;gives servo address
	bsf	ADCON0,2
	btfsc	ADCON0,2
	goto	$-1
	movf	ADRESH,w	;gives value between 0 and 3ffh (1023)
	movwf	DD
	BANKSEL	ADRESL
	movf	ADRESL,w
	BANKSEL	PORTA
	movwf	CC
	movlw	3	;add 977
	movwf	FF
	movlw	0xD1
	movwf	EE
	call	addit	;result in DD, CC is rquired pulse length in microseconds
	comf	CC,f	;rough negate, 1 not added to solve carry problem.
	comf	DD,f	;timer 2 increments, so negative value needed
;set o/p bit
;myaddr has a value 0 - 3
	movf	myaddr,w
	bnz	n1
	bsf	_servo1
	goto	n4
n1	rrf	myaddr,f
	bnc	n2
	bsf	_servo2
	goto	n4
n2	bnz	n3
	bsf	_servo3
	goto	n4
n3	bsf	_servo4	;set o/p
n4	call	pulse
	movlw	0x39
	andwf	PORTA,f	;clear o/p, all servos
	call	frame
;test _save
	btfsc	_save
	goto	kal2	;save not requested
;save data
	movf	myaddr,w
	movwf	_temp	;decrement
	clrc
	rlf	_temp,f	;times 4
	rlf	_temp,f
	clrw
;test hi/lo
	btfss	_hilo
	movlw	2
	addwf	_temp,f
;save to EERAM,	address is in tmp, data in CC,DD
	comf	CC,w	;un-negate so times can be read in microseconds
	movwf	EE
	comf	DD,w
	movwf	FF
	movf	_temp,w
	movwf	CC	;address
	movf	EE,w
	movwf	DD	;data
	call	WRAM	;LSByte
	incf	CC,f	;address
	movf	FF,w
	movwf	DD	;data
	call	WRAM
	bcf	_led	;light out
;wait for _save to be released
	clrwdt
	btfss	_save
	goto	$-2
	goto	kal2	;round again
;
;have to change _kal and restart to exit
;
;-----------------------
;---Reserve Copyright---
;-----------------------
;new page
ex	org	200h
	da	"Copyright Reserved, G.C.Hartnup: July 2002"
;-----------
;---EERAM---
;-----------
me	org	2100H
;servo pulse lengths (lo and hi bytes) in microseconds
;Set the limit values required to get the positions you want for each servo
;The lower and higher values of 1000 and 2000 microseconds give 90 degrees travel
;When setting data, Low and High means upper and lower bytes of the hexadecimal value.
;The full stop indicates a decimal value
;These are default values to give 90 degree throw
;Calibration mode will overwrite them
;channel 1
	de	Low .1000
	de	High .1000	
	de	Low .2000
	de	High .2000
;channel 2
	de	Low .1000
	de	High .1000	
	de	Low .2000
	de	High .2000
;channel 3
	de	Low .1000
	de	High .1000 	
	de	Low .2000
	de	High .2000
;channel 4
	de	Low .1000
	de	High .1000 	
	de	Low .2000
	de	High .2000
	end



