; WRITTEN BY 		G.C.Hartnup
; Copyright Reserved	G.C.Hartnup
; DATE	June 9 2020
;*********************** 
;****CURRENT VERSION**** issue 1.1  27/12/20
;***********************
;		 	
; FILE SAVED AS 	dcc4a.asm
; FOR 		PIC 12f629
	list	p=12f629
	errorlevel -302	;omit memory bank warning messages
; 4.00 MHz Internal Clock 
; INSTRUCTION CLOCK 	1.00 MHz,  Tick = 1uS 
;
; FUNCTION	DCC Accessory decoder
;		with 4 servo output channels
;		service mode address setting	
;-----------
;---Ports---
;-----------
#define	_in	GPIO,GP3		;Pin 4, input from optocoupler
#define	_servo1	GPIO,GP0		;Pin 7, to servo 1
#define	_servo2	GPIO,GP1		;Pin 6, to servo 2
#define	_servo3	GPIO,GP2		;Pin 5, to servo 3
#define	_servo4	GPIO,GP5		;Pin 2, to servo 4
#define	_led	GPIO,GP4		;Pin 3,	LED
;
;so	ports 0, 1, 2, 4, 5 are o\p,   port 3 is i\p
_tris	equ	0x08		;i/o port direction setting
;
;------------------------
;---servo control data---
;------------------------	
sev	equ	0x10		;"not servo" bits
s1	equ	0x01		;servo i/o bit values
s2	equ	0x02
s3	equ	0x04
s4	equ	0x20
;
;-------------------------
;---Memory bank equates---
;-------------------------
#define	page0	bcf	STATUS,RP0	;sets memory bank 0
#define	page1	bsf	STATUS,RP0	;sets memory bank 1
;
;---------------
;---Variables---
;---------------
address	equ	0x30		;received address
byte	equ	0x31		;value returned by getbyte
cnt	equ	0x32		;bit counter
cnt2	equ	0x33		;counter
_data	equ	0x34		;received data
errb	equ	0x35		;received error byte
instr	equ	0x36		;received instruction
_temp	equ	0x38		;temporary holding variable
oldch1	equ	0x3a		;old states
oldch2	equ	0x3b
oldch3	equ	0x3c
oldch4	equ	0x3d
uhi	equ	0x3e		;pulse length hi byte
ulo	equ	0x3f		;pulse length lo byte
CC	equ	0x40		;arith routines data
DD	equ	0x41
EE	equ	0x42
FF	equ	0x43
minlo1	equ	0x44		;negated servo pulse lengths
minhi1	equ	0x45		;servo 1
maxlo1	equ	0x46
maxhi1	equ	0x47
;
minlo2	equ	0x48		;negated servo pulse lengths
minhi2	equ	0x49		;servo 2
maxlo2	equ	0x4a
maxhi2	equ	0x4b
;
minlo3	equ	0x4c		;negated servo pulse lengths
minhi3	equ	0x4d		;servo 1
maxlo3	equ	0x4e
maxhi3	equ	0x4f
;
minlo4	equ	0x50		;negated servo pulse lengths
minhi4	equ	0x51		;servo 2
maxlo4	equ	0x52
maxhi4	equ	0x53
v	equ	0x54		;received state
mask	equ	0x55		;servo GPIO bit value
myaddr	equ	0x56		;decoder page address
command	equ	0x57
;
;----------
;---data---
;----------
blo	equ	0xdf		;frame pulse length
bhi	equ	0xb1
;
; include file with Chip Details and Mnemonic Equates
        include "p12F629.inc"	
	list 	mm = OFF, st=OFF	;some warning messages suppressed
;
;------------------------
;--- 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_OSC_NOCLKOUT  & _WDT_ON & _MCLRE_OFF
;
;-------------------
;---Program Start---
;-------------------
	org 	0
start	page0      	; set up Memory Bank 0
	goto	init	;set configuration
	org	4	;interrupt entry point
	retfie		;return, in case spurious interrupts occur
;
;------------------
;---Subroutines---
;------------------
;
;----------------------
;---Read data memory---
;----------------------
; on entry w holds relative address to read
; on exit w holds data
RRAM	page1     		; set up Memory Bank 1
	bcf	EECON1,WREN
	movwf	EEADR           	; set address
	bsf	EECON1,RD       	; do a read
	movf	EEDATA,w       	; data to w
	page0		  	; set up Memory Bank 0
	return
;-----------------------			
;---Write data memory---
;-----------------------
; on entry 	CC contains relative address
;		DD contains data to write
WRAM	movf	CC,w
	page1		     	;set up memory Bank 1
	movwf	EEADR	   	;write address
	page0		     	;set up Memory Bank 0
	movf	DD,w	   	;value to be stored
	page1		     	; set up memory Bank 1 
	movwf	EEDATA
	bsf	EECON1,WREN
	movlw   	0x55             	;do write sequence
	movwf   	EECON2          	;       /
	movlw   	0xaa            	; (mandatory sequence) 
	movwf   	EECON2          	;     /  
	bsf     	EECON1,WR       	;initiate write 
	btfsc	EECON1,WR		;loop until write completed
	goto	$-1
	page0			;set up memory bank 0
	return
;----------------------
;---get pulse length---
;----------------------
;measure from low edge to next low edge
;and test for "1" or "0" and return 1 or 0 in w register
;if timer1 overflows, dcc controller has probably tripped
; _in is the input port
getbit	clrwdt			;get in synch
	btfsc	_in		;is it hi?
	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
	bnz	d1	;far too long
	movlw	0x4d	;less than 77 microseconds
	subwf	CC,w
	bc	d1	;it is a nought
	movlw	1	;report one
	return
d1	clrw		;report nought
	return
;-------------------------------
;---get byte from 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---
;--------------------
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 pulse---
;----------------------
;on entry EE, FF hold pulse length
;bit turned on and off by caller
pulse	bcf	T1CON,0	;stop timer1
	bcf	PIR1,0	;clear timer1 flag
	movf 	EE,w
	movwf	TMR1L
	movf	FF,w
	movwf	TMR1H
	bsf	T1CON,0	;start timer 1
	nop
	btfss	PIR1,0	;check timer1 flag for end
	goto 	$-2
	return
;--------------------------
;---generate pulse train---
;--------------------------
;on entry ulo, uhi contains pulse length
;mask contains GPIO bit value for channel
fcp	movlw	0x14	;twenty pulses only
	movwf	cnt2	
fcp1	movf	mask,w	
	iorwf	GPIO,f	;set u/p channel
	movf	ulo,w	;pulse length
	movwf	EE
	movf	uhi,w
	movwf	FF
	call	pulse	
	movlw	sev	;channel reset mask
	andwf	GPIO,f	;reset all servo o/ps
	call	frame
	decfsz	cnt2,f	;check no of pulses
	goto	fcp1
	clrwdt
	return
;--------------------------------
;---- RESET & INITIALISATION ----
;--------------------------------
;this section configures the PIC
init	page0	 		; set up Memory Bank 0
	btfss	STATUS,4		;was it wdt restart?
	goto	bp		;yes, don't change anything, stop servos resetting
	clrf	INTCON		;inhibit interrupts
	movlw	0x07
	movwf	CMCON		;turn off comparator
	clrwdt
	page1		   	; set up memory Bank 1
	call 	3ffh		;calibrate internal clock
	movwf	OSCCAL
	movlw	0x8f
	movwf	OPTION_REG	;set watchdog and pullups
	movlw	_tris		;set port directions
	movwf	TRISIO
	page0		   	; set up Memory Bank 0
	movlw	0x01
	movwf	T1CON		;setup timer 1
	bsf	_led		;led off
;-----------------------------------
;---Get Settings from Data Memory---
;-----------------------------------
;data is retrieved and negated as timer counts upwards to zero
;servo 1
	movlw	0		;EERAM relative address
	call	RRAM		;read EERAM
	movwf	minlo1
	comf	minlo1,f		;(rough negate, error of 1 microsecond)
	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
;decoder address
;bit 7 is set to match incoming address format
	movlw	0x10	;memory relative address 16
	call	RRAM	;read data memory
	movwf	myaddr	;and save my address
	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)
;mask is GPIO bit value for channel
	movf	minlo1,w		;servo 1
	movwf	ulo
	movf	minhi1,w
	movwf	uhi
	movlw	s1
	movwf	mask		;set servo 1
	call	fcp		;generate pulse train	
	movf	minlo2,w		;servo2
	movwf	ulo
	movf	minhi2,w
	movwf	uhi
	movlw	s2
	movwf	mask		;set servo 2
	call	fcp		;generate pulse train
	movf	minlo3,w		;servo 3
	movwf	ulo
	movf	minhi3,w
	movwf	uhi
	movlw	s3
	movwf	mask		;set servo 3
	call	fcp		;generate pulse train	
	movf	minlo4,w		;servo 4
	movwf	ulo
	movf	minhi4,w
	movwf	uhi
	movlw	s4
	movwf	mask		;set servo 4
	call	fcp		;generate pulse train
;now start up
;-----------------------
;---look for Preamble---
;-----------------------
;re-enter here after wdt timeout
;look for preamble, >10 successive "1"s
bp	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	;less than 10, so not valid
;find end of preamble
pb16	call	getbit
	incf	cnt,f	;increment counter
	iorlw	0	;look for end
	bnz	pb16
	movlw	.20
	subwf	cnt,w
	bc	Service	;service mode
;-----------------------
;--- 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	ch1
	decf	_data,f
	bz	ch2
	decf	_data,f
	bz	ch3
;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 true or false
	btfsc	v,0
	goto	ch4b	;v is false
	movf	minlo4,w
	movwf	ulo
	movf	minhi4,w
	goto	ch4c
ch4b	movf	maxlo4,w
	movwf	ulo
	movf	maxhi4,w
ch4c	movwf	uhi
	movlw	s4
	movwf	mask	;set servo 4
	call	fcp	;generate pulse chain
	goto	bp
;process channel 3		
ch3
;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	ch3b	;v is false
	movf	minlo3,w
	movwf	ulo
	movf	minhi3,w
	goto	ch3c
ch3b	movf	maxlo3,w
	movwf	ulo
	movf	maxhi3,w
ch3c	movwf	uhi
	movlw	s3
	movwf	mask	;set servo 3
	call	fcp	;generate pulse chain
	goto	bp
;process channel 2		
ch2
;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	ch2b	;v is false
	movf	minlo2,w
	movwf	ulo
	movf	minhi2,w
	goto	ch2c
ch2b	movf	maxlo2,w
	movwf	ulo
	movf	maxhi2,w
ch2c	movwf	uhi
	movlw	s2
	movwf	mask	;set servo 2
	call	fcp	;generate pulse train
	goto	bp
;process channel 1		
ch1
;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	ch1b	;v is false
	movf	minlo1,w
	movwf	ulo
	movf	minhi1,w
	goto	ch1c
ch1b	movf	maxlo1,w
	movwf	ulo
	movf	maxhi1,w
ch1c	movwf	uhi
	movlw	s1
	movwf	mask	;set servo 1
	call	fcp	;generate pulse chain
	goto	bp
;------------------
;---Service Mode---
;------------------	
Service	call	getbyte
;now look for a zero in next startbit
	call	getbit
	iorlw	0
	bnz	bp	;failed start again
	movf	byte,w
	movwf	command
;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 address byte
	call 	getbyte	;result returned in byte
;look for a one in startbit	(end of packet)
	call	getbit	;returned in w
	xorlw	0
	bnz	bp	;failed, don't bother
	movf	byte,w
	movwf	address
;error byte check in here
	movlw	0xfc
	andwf	command,w
	xorlw	0x7c
	bnz	bp
	movlw	1
	xorwf	_data,w
	bnz	bp
	movf	address,w
	movwf	DD
	movlw	0x10
	movwf	CC
	call	WRAM	;write new address to CV1
	bcf	_led	;led on
ere	nop
	goto	ere	;and loop until turned off
;-----------------------
;---Reserve Copyright---
;-----------------------	
	de	"Copyright Reserved, G.C.Hartnup: June 2020"
	de	"Issue 1.0  9/6/20"
;-----------------
;---Dara Memory---
;-----------------
		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
;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
;address 0x10    (CV1)
;decoder page address (not port address)
;port number = (page number-1) x 4 + channel number
;eg page 3 means ports 9, 10, 11, 12
	de	.3	;default page 3
	end
