;*********************************************************************************
;
; Virtual Sector Generator (VSG)
;
;    The VSG sits between a 5.25" hard-sectored floppy controller and the drives
;    to allow use of soft or hard sectored media. The board is jumper selectable
;    to work with 10 or 16 hard-sector controllers. The board is powered by a
;    standard 9 volt battery and automatically powers itself on and off with the
;    host computer. A battery should last a year or more of typical use.
;
; Operational Overview
;    The VSG passes all signals  between the controller and the drive except
;    for index/sector pulses. Pulses from the drives are received by the
;    VSG and the VSG generates the index/sector pulses that are seen by
;    the controller. This arrangement is required to handle specific timing
;    requirements expected by the controllers and software during the first
;    revolution after drive selection. See the document in this folder titled
;    "Startup and Select Issues" for more information.
;
;    For each of the three standard drive selects, the VSG software maintains
;    a running average of the last four revolution times so that the timing of
;    virtual sector pulses matches each drive's rotational rate.
;
;    Input capture and output compare hardware is used to time received
;    index/sector pulses and to generate outgoing pulses. Math works out that
;    the finest resolution possible in the hardware is a 4us lsb. This could
;    result in accumulation of timing errors as virtual sector pulses are
;    computed. To reduce the amount of error, a software version of the output
;    compare register is maintained with an LSB of 1us for computing sector
;    times. 
;
;    Author: Mike Douglas
;
;  Revision: 1.0 (9/26/15),  Initial release 
;	 Checksum: 0x0f62
;
;*********************************************************************************
;
;  Processor is a 16F1824, 8 MHz Internal Oscillator w/o PLL
;
	list p=16f1824,b=12,r=dec
	include <p16f1824.inc>

_BORV_HI	EQU	H'FBFF'	;Brown-out Reset Voltage (Vbor), high trip point selected.

	__config _CONFIG1, _FCMEN_OFF & _IESO_OFF & _CLKOUTEN_OFF & _BOREN_ON & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDTE_ON & _FOSC_INTOSC
	__config _CONFIG2, _LVP_OFF & _BORV_HI & _STVREN_OFF & _PLLEN_OFF & _WRT_ALL
	
;  Misc Equates 


;  Port A bit definitions

SEC16_A0	equ	0	;jumper in = 16 sector drives, otherwise 10
SPARE_JP_A3	equ	3	;spare jumper position
PWR_HOLD_A4	equ	4	;holds power on when high
IDX_OUT_A2	equ	2	;index out to controller (inverts) on CCP3
IDX_IN_A5	equ	5	;index in from drives on CCP2

;  Port C bit definitions

DRIVE1_C0	equ	0	;drive 1 select
DRIVE2_C1	equ	1	;drive 2 select
DRIVE3_C2	equ	2	;drive 3 select
MOTOR_ON_C3	equ	3	;motor on/off

; Disk timing equates. 

DT_FULL_REV	equ	200000		;nominal full rev in us
DT_SEC_10	equ	DT_FULL_REV/10  	;nominal sector time for 10 sector disk in us
DT_SEC_16	equ	DT_FULL_REV/16  	;nominal sector time for 16 sector disk in us	
DT_HARD_SEC	equ	DT_FULL_REV/4/4 	;time < this considered hard sector, 4us lsb
DT_IDX_PULSE equ	2000/4		;index/sector pulses are 2ms long, 4us lsb

DT_TBL_INIT0 equ	DT_FULL_REV & 0xff		;lsb of 4 revs summed (4us lsb)
DT_TBL_INIT1 equ	(DT_FULL_REV >> 8) & 0xff
DT_TBL_INIT2 equ	(DT_FULL_REV >> 16) & 0xff 	;msb of 4 revs summed (4us lsb)

; Output Compare 3 is used to generate index/sector pulses to the controller
;    These equates are CCPCON register control values.

OC_OFF	 equ	0x00	;output compare off
OC_DO_PULSE	 equ	0x09	;asserts IDX when written, removes at OC time
OC_NO_PULSE	 equ	0x0A	;OC with no automatic output on IDX
OC_PULSE_END equ	0	;bit zero is a 1 if ending a pulse

; Power off counter. 16 bit timer increments at 30.5hz to time out the power off
;    interval of no activity. Activity is determined by the presence of index
;    pulses from the drive (i.e., input captures).

PWR_OFF_BIT	equ	6	;power off when this bit goes high in MSB of 
			;30.5hz counter. 6 is 16384, giving 9 minutes

;  Page 0 data

	cblock	0x20	;start of bank 0 RAM
pwrOffCntL			;16 bit power off counter at 30.5hz
pwrOffCntH
prevSelect			;previous state of drive select and motor-on
icTimeL,icTimeH		;time between new and prev index pulses
average:3			;average of 4 rotation times for current drive
ccpr3_1us:3			;CCPR3 with 1us lsb instead of 4us

; variables used for 24 bit by 8 bit divide

dividend:3			;24 bit dividend for divisions
divisor			;8 bit divisor
remainder			;8 bit remainder
divideCnt			;counts 24 shift cycles

; The drive table contains the 24 bit sum of the last 4 rotation times for each drive.
;    The drive 0 entry is not actually used. It is pointed to when no drive is selected.
;    Values are stored little endian (LSB first)

drv0Sum:3			;24 bit sum of 4 rotation times
drv1Sum:3
drv2Sum:3
drv3Sum:3
	endc

sectors	equ	divisor	;divisor is the number of sectors
sectorTime	equ	dividend	;sectorTime overlays dividend as it is where the
;			;divide routine store its result

; Data common to all pages
	
	cblock	0x70	;common RAM all pages
flags			;byte of flags (see below)
secNum			;current sector number
icPrevL,icPrevH		;previous input capture value
icNewL,icNewH		;new input capture value
temp24:3			;24 bit temp variable
	endc

;  Flags variable equates

fPREV_VALID	   equ	0	;1=value in icPrev is valid
fSEND_PULSES   equ	1	;1=send virtual pulses, 0=inhibit
fHARD_SECTORED equ	2	;1=current disk is hard sectored
fIDX1_SENT	   equ	3	;1=1st pulse of index sync sent
fIDX2_SENT	   equ	4	;1=2nd pulse of index sync sent


;************************************************************************
;
;  Program Space
;
;************************************************************************

;  Reset and interrupt vectors

	org	0		;page zero for jump sbrs
	goto	devReset		;reset vector location zero
	goto	$		;loop here and die
	goto	$
	goto	$
	goto	$		;interrupt vector location 0x04

;  devReset - device reset entry point

devReset	equ	$
	banksel	OSCCON
	movlw	b'01110000'		;8 Mhz internal clock
	movwf	OSCCON
	banksel	OPTION_REG
	movlw	b'01010111'		;weak pullups enabled, /256 on TMR0
	movwf	OPTION_REG		;TMR0 wraps at 30.52hz
	
; Initialize PORT A

	banksel	PORTA
	movlw	b'00010000'		;power hold on, index output off
	movwf	PORTA
	banksel	LATA
	movwf	LATA		;init Port A latch = Port A
	banksel	ANSELA
	clrf	ANSELA		;all digital - no analog
	banksel	TRISA
	movlw	b'00101011'		;index from drives and jumpers are inputs
	movwf	TRISA
	banksel	WPUA
	movlw	b'00001011'		;pullups on jumper inputs
	movwf	WPUA

; Initialize PORT C

	banksel	PORTC
	movlw	b'00000000'		;no outputs actually used
	movwf	PORTC
	banksel	LATC
	movwf	LATC		;init Port C latch = Port C
	banksel	ANSELC
	clrf	ANSELC		;all digital - no analog
	banksel	TRISC
	movlw	b'00001111'		;DS1-DS3 and motor on are inputs 
	movwf	TRISC
	banksel	WPUC
	movlw	b'00000000'		;no pullups used
	movwf	WPUC

; Set TMR1 to run at 250khz (4us per tic)

	banksel	T1CON
	movlw	b'00110101'		;FOSC/4, /8, Timer on
	movwf	T1CON

; Init input capture 2 to come in on RA5 and detect falling edge index/sector pulses
;    from the drives.

	banksel	APFCON1		
	movlw	b'00000001'		;alternate pin function, CCP2 on RA5
	movwf	APFCON1
	banksel	CCP2CON
	movlw	b'00000100'		;IC on falling edge
	movwf	CCP2CON

; Initialize variables

	banksel	PORTA		;default to page 0
	clrf	FSR0H		;high byte of FSRs point to page 0
	clrf	FSR1H
	movfw	PORTC		;get drive and motor select lines
	movwf	prevSelect		;save initial state
	clrf	flags		;all flags false
	clrf	secNum		;sector number = 0
	clrf	pwrOffCntH		;zero msb of the power off timer

; Init each drive table sum with 4 * nominal rotation time

	movlw	4		;init four drive table entries
	movwf	divideCnt		;use divideCnt as a counter
	movlw	drv0Sum		;point to first byte of table
	movwf	FSR0L		;FSR0->drive table

initLoop	movlw	DT_TBL_INIT0	;init lsb
	movwi	0[FSR0]
	movlw	DT_TBL_INIT1	;init middle byte
	movwi	1[FSR0]
	movlw	DT_TBL_INIT2	;init msb
	movwi	2[FSR0]
	addfsr	0,(drv1Sum-drv0Sum)	;increment pointer by entry length
	decfsz	divideCnt,F
	goto	initLoop

; initialize for 10 or 16 sectors based on the sector jumper

	btfss	PORTA,SEC16_A0	;10 or 16 sectors?
	goto	init16		;jumper in, 16 sectors

; init for 10 sector disk

	movlw	10		;init for 10 sectors
	movwf	sectors		;this is the variable "divisor" as well

	movlw	LOW DT_SEC_10	;init nominal sector time
	movwf	sectorTime
	movlw	HIGH DT_SEC_10
	movwf	sectorTime+1
	goto	main		;enter main loop

;init for 16 sector disk

init16	movlw	16		;init for 16 sectors
	movwf	sectors		;this is the variable "divisor" as well

	movlw	LOW DT_SEC_16	;init nominal sector time
	movwf	sectorTime
	movlw	HIGH DT_SEC_16
	movwf	sectorTime+1
				;fall into main loop

;----------------------------------------------------------------------------
;  Main Loop - wait for and process events:
;     1) Change in drive select or motor-on lines
;     2) Input capture (index/sector pulse from drive)
;     3) Output compare (generates index/sector pulses to controller)
;     4) TRM0 overflow is a 30.5hz tic for power off timeout
;----------------------------------------------------------------------------
main	clrwdt
	movfw	PORTC		;read drive and motor lines
	xorwf	prevSelect,W	;any change?
	btfss	STATUS,Z
	call	selectChg		;yes, process select line change

	btfsc	PIR2,CCP2IF		;input capture occur?
	call	haveIc		;yes

	btfsc	PIR3,CCP3IF		;output compare occur?
	call	haveOc		;yes

	btfsc	INTCON,TMR0IF	;30.5hz tic occur?
	call	haveTic		;yes, process 30.5hz tic

	goto	main

;----------------------------------------------------------------------------
; selectChg - drive select or motor line has changed state. Force the
;    processing state to start over by clearing all flags. Point FSR0
;    to the proper drive table entry if a drive is selected.
;----------------------------------------------------------------------------
selectChg	xorwf	prevSelect,F	;save new state
	bcf	PIR2,CCP2IF		;clear possbile "dirty" capture

	banksel	CCP3CON
	clrf	CCP3CON		;turn off output compare	
	banksel	PORTA		;back to default page 0
	bcf	PIR3,CCP3IF		;clear possible "dirty" compare

	clrf	pwrOffCntH		;restart power off counter
	clrf	flags		;return to starting state
	clrf	secNum		;force sector number to zero

; Point FSR0 to the sum in the drive table for the selected drive. Point to the 
;   drive 0 spot if no drive is selected. Lower drive numbers take priority over
;   higher numbers since an unterminated drive select may look selected all the time.

	movlw	drv0Sum		;assume drive 0 entry (no drive)
	btfss	prevSelect,DRIVE3_C2	;drive three selected?
	movlw	drv3Sum		;yes
	btfss	prevSelect,DRIVE2_C1	;drive two selected?
	movlw	drv2Sum		;yes
	btfss	prevSelect,DRIVE1_C0	;drive one selected?
	movlw	drv1Sum		;yes

	movwf	FSR0L		;FSR0->sum for drive
	return		

;----------------------------------------------------------------------------
; haveIc - have input capture on the index line from the drives. This
;    occurs on the leading (falling) edge of index and sector pulses
;    from the drives. Time between captures is used to determine if the
;    disk is hard-sectored or soft-sectored, and to compute the rotation
;    time of soft-sectored disks. The captured time of a soft-sectored
;    index pulse is also the base point for generating virtual sector
;    pulses within each revolution.
;----------------------------------------------------------------------------
haveIc	bcf	PIR2,CCP2IF		;clear capture flag
	clrf	pwrOffCntH		;have activity, reset pwr off 
	banksel	CCPR2H
	movfw	CCPR2H		;w = msb of capture value
	btfss	flags,fPREV_VALID	;have a valid previous capture?
	goto	firstIc		;no, this is a 1st capture

	movwf	icNewH		;save capture value in icNew
	movfw	CCPR2L
	movwf	icNewL
	banksel	PORTA		;back to default page 0

; Compute time between new and old capture events: icTime = icNew - icPrev.
;    Then move the new capture time to the previous capture spot.

	movfw	icPrevL		;w = lsb of previous capture
	subwf	icNewL,W		;compute icNewL - icPrevL
	movwf	icTimeL		;save as icTimeL

	movfw	icPrevH		;w = msb of previous capture
	subwfb	icNewH,W		;compute icNewH - icPrevH
	movwf	icTimeH		;save as icTimeH
	
	movfw	icNewL		;move icNew to icPrev
	movwf	icPrevL
	movfw	icNewH
	movwf	icPrevH

; See if the time between pulses is less than the hard sectored threshold. If so,
;    assume a hard sector disk in inserted and generate an index/sector pulse 
;    to the controller for each pulse received from the drive.

	movlw	high DT_HARD_SEC	;w = hard sectored threshold
	subwf	icTimeH,W		;compute icTime - DT_HARD_SEC
	bc	notHardSec		;icTime > DT_HARD_SEC, not hard sectored

	bsf	flags,fHARD_SECTORED	;set hard sectored flag
	goto	genIdxPulse		;generate index pulse and return

; notHardSec - not a hard sector disk, so this should be the soft sectored disk's
;    index pulse. The pulse is expected between sectors 9 and 0 (10 sector disk)
;    or between sectors 15 and 0 (16 sector disk). 

notHardSec	bcf	flags,fHARD_SECTORED	;not hard sectored
	movfw	sectors		;between last and 1st sector?
	subwf	secNum,W
	bnz	setSec0		;no, resync to index pulse

; time to send an index pulse, but if this is the first index pulse for a
;    16 sector disk, don't send it, just use it as sync for computing the
;    upcoming sector zero pulse.

	btfsc	PORTA,SEC16_A0	;16 sector disk?
	goto	genIdxPulse		;no, go send the index pulse

	btfsc	flags,fSEND_PULSES	;sending pulses yet?
	goto	genIdxPulse		;yes, go send the index pulse

	bsf	flags,fSEND_PULSES	;ok to send pulses now
	goto	setSec0		;resync to this index pulse

; firstIc - first input capture. Save the IC value as icPrev, then start timing
;    for virtual sector generation in case this ends up being soft-sectored.

firstIc	movwf	icPrevH		;save capture high byte
	movfw	CCPR2L
	movwf	icPrevL		;save capture low byte

	bsf	flags,fPREV_VALID	;now have first input capture value
	banksel	PORTA		;restore default page 0
	goto	setSec0		;set sector 0 wakeup and return	

;----------------------------------------------------------------------------
; haveOc - have output compare. This either marks the time that a new
;    virtual sector pulse should start, or it marks then end of generated 
;    index/sector pulse.
;----------------------------------------------------------------------------
haveOc	bcf	PIR3,CCP3IF		;clear compare flag
	btfss	flags,fHARD_SECTORED	;hard sectored disk?
	goto	ocSoft		;no, soft sectored disk

; Hard sectored disk. This OC marked the end of an index/sector pulse sent
;    to the controller. Disable further compares and exit.

	banksel	CCP3CON
	clrf	CCP3CON		;disable further OC
	banksel	PORTA		;restore page 0
	return

; ocSoft - output compare for a soft sectored disk. Split into two separate
;    routines for 10 sector or 16 sector as they differ substantially.

ocSoft	btfss	PORTA,SEC16_A0	;10 or 16 sector?
	goto	ocSoft16		;jumper in = 16 sector

;----------------------------------------------------------------------------
; ocSoft10 - 10 sector processing. This compare either marks the time to
;    start a virtual sector pulse, or it marks the end of a pulse just sent
;    to the controller.

ocSoft10	equ	$
	banksel	CCP3CON
	btfsc	CCP3CON,OC_PULSE_END	;start or end of pulse?
	goto	nextSec10		;end of pulse, set up for next sector

; The OC marked the time to start a virtual sector pulse. Send the pulse now unless
;    fSEND_PULSES is still false (false until sector 8 is reached for the 1st time).

	btfsc	flags,fSEND_PULSES	;sending pulses yet?
	goto	genSecPulse		;yes, go send a sector pulse and exit

; nextSec10 - setup for the next virtual sector. If the sector number is -1, then
;    an index pulse just completed and it's time to update the average rotation time
;    and compute a new sector zero time. Otherwise, compute the next virtual sector
;    wakeup from the current sector time. If sector 8 is ever reached, then the
;    fSEND_PULSES flag allows pulses to be sent to the controller from then on. If
;    sector 16 is ever reached, we've gone too long without an index pulse.

nextSec10	equ	$
	banksel	PORTA		;restore default page 0
	incf	secNum,F		;increment sector number
	bz	updateAvg		;-1 to 0 = valid index pulse just finished
	
	btfsc	secNum,3		;reached sector 8 yet?
	bsf	flags,fSEND_PULSES	;yes, allow sectors to be sent

	btfss	secNum,4		;reached sector 16 yet (missed index)?
	goto	setNextSec		;no, still good, set next sector wakeup

;  Have incremented past sector 9 all the way to sector 16 without an index pulse.
;     Start the state machine over by clearing all flags.

	banksel	CCP3CON
	clrf	CCP3CON		;disable further OC
	banksel	PORTA		;back to default page 0
	clrf	flags
	clrf	secNum
	return

;----------------------------------------------------------------------------
; ocSoft16 - 16 sector processing. This compare either marks the time to
;    start a virtual sector pulse, or it marks the end of a pulse just sent
;    to the controller.

ocSoft16	equ	$
	banksel	CCP3CON
	btfss	CCP3CON,OC_PULSE_END	;start or end of pulse?
	goto	pulseStart		;start

; OC marks the end of a pulse. Handle the special case of the two pulses sent
;    to establish initial index sync. Otherwise, just jump to nextSec16
;    to compute the next sector wakeup.

	btfsc	flags,fIDX1_SENT	;end of 1st sync pulse?
	goto	chkIndex2		;no, go see if end of 2nd pulse

; 1st of two pulses to generate index sync just ended. Compute OC wakeup to
;    generate 2nd pulse, 1/2 sector time after 1st pulse.

	bsf	flags,fIDX1_SENT	;indicate we've finished pulse 1
	clrf	CCP3CON		;disable output compare

	movlw	LOW (DT_SEC_16/4/2-DT_IDX_PULSE)	;1/2 sector - pulse width
	addwf	CCPR3L,F		;compute next wakeup
	movlw	HIGH (DT_SEC_16/4/2-DT_IDX_PULSE)
	addwfc	CCPR3H,F

	movlw	OC_NO_PULSE		;no pulse, just wakeup
	movwf	CCP3CON
	banksel	PORTA		;back to page 0
	bcf	PIR3,CCP3IF		;make sure OC flag is clear
	return

; chkIndex2 - Set the fIDX2_SENT flag and jump to nextSec16 to resume normal wakeups

chkIndex2	bsf	flags,fIDX2_SENT	;indicate we've finished pulse 2
	goto	nextSec16		;set wakeup for next normal sector

; pulseStart - The OC marked the time to start a virtual sector pulse. If 
;    fSEND_PULSES is true, then send the virtual pulse. Otherwise, we're probably
;    still in the 1st revolution and a two pulse index sync sequence needs to be
;    sent when we reach the time for sector 2 (just a delay period, nothing specific
;    to do with sector 2).

pulseStart	btfsc	flags,fSEND_PULSES	;sending pulses yet?
	goto	genSecPulse		;yes, go send a sector pulse and exit

	btfss	secNum,1		;reached sector 2 yet?
	goto	nextSec16		;no, go compute next normal wakeup

	btfss	flags,fIDX1_SENT	;1st pulse of index sync sent yet?
	goto	genSecPulse		;no, go send it

	btfss	flags,fIDX2_SENT	;2nd pulse of index sync sent yet
	goto	genSecPulse		;no, go send it

; nextSec16 - setup for the next virtual sector. If the sector number is -1, then
;    an index pulse just completed and it's time to update the average rotation time
;    and compute a new sector zero time. If sector 32 is ever reached, we've gone
;    too long without an index pulse.

nextSec16	equ	$
	banksel	PORTA		;restore default page 0
	incf	secNum,F		;increment sector number
	bz	updateAvg		;-1 to 0 = valid index pulse just finished

	btfss	secNum,5		;reached sector 32 yet (missed index)?
	goto	setNextSec		;no, still good, set next sector wakeup

;  Have incremented past sector 15 all the way to sector 32 without an index pulse.
;     Start the state machine over by clearing all flags.

	banksel	CCP3CON
	clrf	CCP3CON		;disable further OC
	banksel	PORTA		;back to default page 0
	clrf	flags
	clrf	secNum
	return

;----------------------------------------------------------------------------
; haveTic - have 30.5hz tic. Increment the power off counter. If the
;    counter increments to 16,384 (about 9 minutes), then release the
;    powerHold output to see if power from the host computer is gone.
;    If so, the regulator shuts off. Otherwise, execution continues
;    and this routine exits.
;----------------------------------------------------------------------------
haveTic	bcf	INTCON,TMR0IF	;clear the timer 0 wrap flag

; increment lsb of power timeout

	incf	pwrOffCntL,F	;do 16 bit increment
	btfss	STATUS,Z		;wrap to zero?
	return			;no, just exit

; increment msb of power timeout

	incf	pwrOffCntH,F	;yes, increment MSB as well
	btfss	pwrOffCntH,PWR_OFF_BIT	;reached power off time in MSB yet?
	return			;no, not yet

; Attempt to power off by setting the power hold output low for 32.8ms. If the host is
;   off, the regulator will turn off. Otherwise, the code here continues to execute
;   and then exits.

	clrf	pwrOffCntH		;restart power off counter
	bcf	PORTA,PWR_HOLD_A4	;turn off power hold
	
pwrOffLoop	btfss	INTCON,TMR0IF	;wait for 1 tic (32.8ms)
	goto	pwrOffLoop		

	bcf	INTCON,TMR0IF	;clear the timer 0 wrap flag
	bsf	PORTA,PWR_HOLD_A4	;re-enable power hold
	return

;----------------------------------------------------------------------------
; genIdxPulse - generate 2ms index pulse from the most recent input
;    capture time.
;----------------------------------------------------------------------------
genIdxPulse	movlw	-1		;force sector to -1 (flags an index pulse)
	movwf	secNum
	banksel	CCP3CON
	clrf	CCP3CON		;disable output compare

	movlw	LOW DT_IDX_PULSE	;compute OC = icPrev + 2ms
	addwf	icPrevL,W
	movwf	CCPR3L
	movlw	HIGH DT_IDX_PULSE
	addwfc	icPrevH,W
	movwf	CCPR3H

	movlw	OC_DO_PULSE		;assert IDX out when written
	movwf	CCP3CON		;IDX removed when OC occurs
	banksel	PORTA		;back to page 0
	bcf	PIR3,CCP3IF		;make sure OC flag is clear
	return

;----------------------------------------------------------------------------
; genSecPulse - generate 2ms sector pulse from time in output compare 3.
;----------------------------------------------------------------------------
genSecPulse	equ	$
	banksel	CCP3CON
	clrf	CCP3CON		;disable output compare

	movlw	LOW DT_IDX_PULSE	;compute OC = CCPR3 + 2ms
	addwf	CCPR3L,F
	movlw	HIGH DT_IDX_PULSE
	addwfc	CCPR3H,F

	movlw	OC_DO_PULSE		;assert IDX when written
	movwf	CCP3CON		;IDX removed when OC occurs
	banksel	PORTA		;back to page 0
	bcf	PIR3,CCP3IF		;make sure OC flag is clear
	return

;----------------------------------------------------------------------------
; updateAvg	- update the average rotation sum with the most recent rotation,
;   then compute the new sector time with a 1us lsb. Finally, fall into
;   setSec0 to compute and start a new sector 0 wakeup in OC3.
;----------------------------------------------------------------------------
;   First, compute the average of the current data (divide by 4) so we can
;   can subtract it out. FSR0->sum for current drive.

updateAvg	moviw	0[FSR0]		;copy current sum to average
	movwf	average
	moviw	1[FSR0]
	movwf	average+1
	moviw	2[FSR0]
	movwf	average+2

	lsrf	average+2,F		;divide by 4 by shifting twice
	rrf	average+1,F
	rrf	average,F
	lsrf	average+2,F
	rrf	average+1,F
	xorlw	1		;toggle bit we're about to shift out
	rrf	average,W		;w contains LSB of average, C=not old lsb

; Subtract current average from the current sum. The first subtract is done with
;   borrow to implement rounding. The complement of the first fractional bit is
;   in the carry bit. If the fractional bit is 1, then carry is zero and borrow
;   will occur. This effectively rounds the average up. If the fractional bit is
;   zero, then carry is one and borrow does not occur.

	subwfb	INDF0,F		;compute LSB of sum-average-1st fraction bit

	incf	FSR0,F		;FSR0->middle byte of sum
	movfw	average+1		;w=middle byte of average
	subwfb	INDF0,F		;compute middle byte of sum-average

	incf	FSR0,F		;FSR0->msb of sum
	movfw	average+2		;w=msb of average (should be zero)
	subwfb	INDF0,F		;compute msb of sum-average
	addfsr	0,-2		;restore FSR0->lsb of sum (default)

; Add the new rotation time into the current sum

	movfw	icTimeL		;w=LSB of most recent rotation
	addwf	INDF0,F		;compute lsb of sum

	incf	FSR0,F		;FSR0->middle byte of sum
	movfw	icTimeH		;MSB of most recent rotation
	addwfc	INDF0,F		;compute middle byte of sum

	incf	FSR0,F		;FSR1->MSB of sum
	clrw			;add in possible carry
	addwfc	INDF0,F		;compute msb of sum
	addfsr	0,-2		;restore FSR0->lsb of sum (default)	

; Compute sector time to 1us lsb. Since the sum just computed is 4*rotation time
;   to 4us lsb, the sum is the average rotation time to 1us lsb. Dividing by
;   the number of sectors gives the sector time to 1us. The division result
;   is saved in dividend which is equated to the sectorTime symbol. The result
;   is rounded up if the remainder >= 1/2 the divisor.

	moviw	0[FSR0]		;copy sum to dividend
	movwf	dividend
	moviw	1[FSR0]
	movwf	dividend+1
	moviw	2[FSR0]
	movwf	dividend+2

	call	div24x8		;divide rotation time by 10 or 16

; round up result if remainder > 1/2 the divisor (number of sectors)

	lsrf	sectors,W		;w=1/2number of sectors
	subwf	remainder,W		;carry set if remainder >= 5 or 8
	clrw			;do possible round-up
	addwfc	dividend,F
	addwfc	dividend+1,F	;sector time is 16 bits
				;fall through to setSec0

;----------------------------------------------------------------------------
; setSec0 - compute and set sector zero wakeup time in OC3. The time of
;     the index pulse time is in icPrev. The real CCPR3 for OC3 has a 4us
;     lsb. A version with a 1us lsb (ccpr3_1us) is kept to improve
;     computational accuracy of subsequent virtual sector wakeups.
;----------------------------------------------------------------------------
setSec0	clrf	secNum		;we're at sector 0

	lslf	icPrevL,W		;align 4us lsb to 1us lsb
	movwf	ccpr3_1us		;lsb
	rlf	icPrevH,W
	movwf	ccpr3_1us+1		;middle byte
	clrf	ccpr3_1us+2		;msb
	rlf	ccpr3_1us+2,F

	lslf	ccpr3_1us,F		;2nd shift
	rlf	ccpr3_1us+1,F	
	rlf	ccpr3_1us+2,F

;  ccpr3_1us presently has the index time aligned to 1us lsb. Now compute 1/2
;     the sector time (index to sector 0) and add it to the index time.

	lsrf	sectorTime+1,W	;divide sector time (16 bits) by 2
	movwf	temp24+1
	rrf	sectorTime,W	;w=lsb of 1/2 sector time

	addwf	ccpr3_1us,F		;add 1/2 sector time to ccpr3_1us
	movfw	temp24+1
	addwfc	ccpr3_1us+1,F
	clrw
	addwfc	ccpr3_1us+2,F	
	goto	setOC3		;set OC3 wakeup from ccpr3_1us

;----------------------------------------------------------------------------
; setNextSec - compute and set next sector wakeup time in OC3. The real 
;     CCPR3 for OC3 has a 4us lsb. A version with a 1us lsb (ccpr3_1us)
;     is kept to improve computational accuracy of subsequent sector wakeups.
;----------------------------------------------------------------------------
setNextSec	movfw	sectorTime		;add sector time to last wakeup
	addwf	ccpr3_1us,F	
	movfw	sectorTime+1
	addwfc	ccpr3_1us+1,F
	clrw
	addwfc	ccpr3_1us+2,F	

; setOC3 - shift ccpr3_1us two bits to the right to form the real CCPR3 (4us lsb). 
;    The result is rounded to give +2/-2us timing instead of +0/-4us. Then enable
;    an OC3 compare at that time.
	
setOC3	lsrf	ccpr3_1us+2,W	;shift to right, msb is first
	movwf	temp24+2
	rrf	ccpr3_1us+1,W
	movwf	temp24+1
	rrf	ccpr3_1us,W
	movwf	temp24

	lsrf	temp24+2,F		;2nd shift
	rrf	temp24+1,F
	rrf	temp24,F
	
	clrw			;round up if 1st fraction bit (C) is 1
	addwfc	temp24,F
	addwfc	temp24+1,F		;only 16 bits used
	
; set up OC3 compare to occur at the computed time

	banksel	CCP3CON
	clrf	CCP3CON		;disable output compare

	movfw	temp24		;store OC3 compare value in CCPR3
	movwf	CCPR3L
	movfw	temp24+1
	movwf	CCPR3H
			
	movlw	OC_NO_PULSE		;enable just an OC wakeup
	movwf	CCP3CON
	banksel	PORTA		;return to default page 0
	bcf	PIR3,CCP3IF		;make sure OC flag is clear
	return

;----------------------------------------------------------------------------
;Unsigned 24 bit by 8 bit divide routine
;
; Inputs:
;   Dividend  - dividend, dividend+1, dividend+2 (msb)
;   Divisor   - divisor
; Temporary:
;   Counter   - divideCnt
; Output:
;   Quotient  - dividend, dividend+1, dividend+2 (msb)
;   Remainder - remainder
;
; Size: 17
; Timing: 342 cycles (including call and return)
;
; This is basically Nikolai Golovchenko's 24 by 16 bit 
; divide routine, with some instructions removed to 
; optimize it for an 8 bit divide. 
;
; James Hillman, 2 December 2005 
;----------------------------------------------------------------------------
div24x8	clrf	remainder		;clear the remainder
	movlw	24
	movwf	divideCnt		;doing 24 bits

divLoop	rlf	dividend,W		;shift dividend left...
	rlf	dividend+1,F
	rlf	dividend+2,F
	rlf	remainder,F		;shift next dividend bit into remainder
	rlf	dividend,F		;finish shift and save carry in dividend bit 0
				;since remainder can be 9 bits long. This
				;position also serves as the next result bit

	movfw	divisor		;subtract divisor from 8 bit remainder
         	subwf	remainder,F	       

;here we also need to take into account the 9th bit of remainder, which
;is in dividend bit 0. If we don't have a borrow after subtracting from 
;8 bits of remainder, then there is no borrow regardless of 9th bit 
;value. But, if we have the borrow, then that will depend on 9th bit 
;value. If it is 1, then no final borrow will occur. If it is 0, borrow
;will occur. These values match the borrow flag polarity.

	btfsc	STATUS,C		;if no borrow after 8 bit subtraction
	bsf	dividend,0		;then there is no borrow in result. Overwrite
                      			;dividend bit 0 with 1 to indicate no borrow.
                      			;if borrow did occur, dividend bit 0 already
                      			;holds the final borrow value (0-borrow, 
                      			;1-no borrow)
	btfss	dividend,0		;if no borrow after 9-bit subtration
	addwf	remainder,F		;restore remainder (w contains the value
				;subtracted from it previously
	decfsz	divideCnt,F
	goto	divLoop

	return

	end