;---------------------------------------------------------------------------
; Simple cooperative multitasking scheduler for the SX18AC
; Aaron Logue, Apr 2002
;
; The idea here is that we may want to have several different processes
; running, each one independently waiting for some amount of time to elapse
; before twiddling an output, polling an input pin, sending a message to
; another process, or changing states.  This scheduler provides a simple
; non-interrupt driven mechanism to allow processes to specify a time
; interval to sleep.
;
; mpasm /x- /l+ /e+ /w0 /p16C58A /aINHX8M /c- sleeper.asm
;---------------------------------------------------------------------------
	radix		dec
	include		mydefs.inc

DEVICE	EQU	TURBO | SYNC | IRC | RC4MHZ	; Internal RC clock
;DEVICE	EQU	TURBO | SYNC | FOSCHI | FOSC2	; External oscillator

MHZ	EQU	4		; Clock rate in Mhz (4, 16, 20, or 50 is best)
JIF	EQU	8		; How many microseconds in a jiffy
PSCALE	EQU	8		; Prescaler ratio (1 if unused)

JIFTIKS	EQU	(MHZ * JIF / PSCALE)	; How many RTCC counts per jiffy
 IF (JIFTIKS == 0)
 ERROR Prescaler may be too high for jiffy granularity.
 ENDIF

JBITS	EQU	(104 / JIF)	; How many jiffies per 9600 baud bit
JMILLI	EQU	(1000 / JIF)	; How many jiffies per millisecond

 IF (PSCALE == 1)		; Pick a value for OPTION register
OPTREG	EQU	0xC8
 ENDIF
 IF (PSCALE == 2)
OPTREG	EQU	0xC0
 ENDIF
 IF (PSCALE == 4)
OPTREG	EQU	0xC1
 ENDIF
 IF (PSCALE == 8)
OPTREG	EQU	0xC2
 ENDIF
 IF (PSCALE == 16)
OPTREG	EQU	0xC3
 ENDIF

; Global variables
ELAPSEDJIF 	EQU	8	; Global, elapsed number of jiffies
LASTRTCC 	EQU	9	; Global, last value of RTCC
ELAPSEDRTCC	EQU	10	; Global, elapsed number of RTCC ticks
TEMP		EQU	11	; Global, general purpose
EXAMPLEJIFS	EQU	12	; Process 1's jiffy countdown sleep timer

	org	0		; Generate code here
	movlw	OPTREG		; Initialize OPTION register
	option

	mode	0xf
  	movlw	0x00		; Move 0x00 to W to set direction
        tris	ra		; W --> ra direction bits
        tris	rb		; W --> rb direction bits
	clrf	ra
	clrf	rb

	clrf	LASTRTCC
	clrf	ELAPSEDRTCC
	clrf	EXAMPLEJIFS

main_loop
	; -------------------------------------------------------------------
	; Compute elapsed RTCC ticks.  This needs to be called often
	; enough to avoid losing time by wrapping.  1 jiffy costs 20 ticks
	; of detection overhead here.  The prescaler should be set high
	; enough that there's no chance of ever missing a jiffy.
	;
	movf	rtcc, w		; Load current RTCC counter
	movwf	TEMP		; Save an unchanging copy of it
	movf	LASTRTCC, w	; Load previous RTCC counter value
	subwf	TEMP, w		; Compute difference
	addwf	ELAPSEDRTCC, f	; Add difference to elapsed RTCC ticks
	movf	TEMP, w		; Save new previous RTCC counter value
	movwf	LASTRTCC	;  for next elapsed computation
	;
	; Compute elapsed jiffies.  Our clock speed should be high enough
	; and our jiffy period long enough that we don't have very many
	; elapsed jiffies each time through here.  That means that repeated
	; subtraction of the number of RTCC ticks per jiffy (JIFTIKS) from
	; elapsed RTCC ticks (ELAPSEDRTCC) should be more efficient than
	; dividing ELAPSEDRTCC by JIFTIKS.  The more instructions per jiffy,
	; the better, as long as jiffies are still granular enough to be
	; useful.  4 or 8 microseconds per jiffy at 50 Mhz is probably good.
	;
	clrf	ELAPSEDJIF	; Assume no jiffies have elapsed
	movlw	JIFTIKS		; Load number of ELAPSEDRTCC ticks per jiffy
ej_10
	subwf	ELAPSEDRTCC, f	; Subtract JIFTIKS from ELAPSEDRTCC
				; C is 1 if result is positive or zero
	btfss	STATUS, C	; Did we have enough ELAPSEDRTCC for a jiffy?
	goto	ej_20		;   sadly, no
	incf	ELAPSEDJIF, f	;   joyously, yes - we have a jiffy!
	goto	ej_10		; Go see if there's enough for another one
ej_20
	addwf	ELAPSEDRTCC, f	; Restore incomplete-jiffy elapsed RTCC count
	;
	; ELAPSEDJIF is now the number of elapsed jiffies since processes
	; were last called.
	; -------------------------------------------------------------------

	call	example_process
	goto	main_loop


	; -------------------------------------------------------------------
	; This code goes into each process to handle its state machine delays.
	; If process is sleeping (waiting for some number of jiffies to pass),
	; count down its remaining sleep time and wake it up if we reach zero.
	; ELAPSEDJIFS is a global register.  EXAMPLEJIFS is per-machine.
	;  compute number of jiffies remaining to wait for before waking
	;  if zero, then we're done - go run process
	;  compute number of jiffies elapsed since last called
	;  subtract jiffies elapsed from jiffies remaining
	;  did result set C or Z?
	;  if so, we're done with the delay; go run process
	;  otherwise, decrement jiffies remaining and return to main loop
example_process
	movf	EXAMPLEJIFS, w	; sleep time remaining -> W
	btfsc	STATUS, Z	; skip if we're asleep
	goto	example_running	; process is awake and running

	movf	ELAPSEDJIF, w	; elapsed jiffies -> W
	btfsc	STATUS, Z	; skip if jiffies have elapsed
	retlw	0		; no jiffies elapsed - keep sleeping

	subwf	EXAMPLEJIFS, f	; Subtract elapsed from remaining sleep time
				; C is 1 if result is positive or zero
	btfss	STATUS, C	; Was result negative?
	goto	example_running	;   yes, time to wake up.  We overslept, so
				;   leave negative value in EXAMPLEJIFS for
				;   reduced sleep later.
	btfsc	STATUS, Z	; Was result zero?
	goto	example_running	;   yes, time to wake up

	retlw	0		; keep sleeping

example_running
	;
	; Do stuff
	;
	comf	rb, f		; Toggle all bits so we see something happening
	;
	; We could return and keep running, or go back to sleep by setting
	; EXAMPLEJIFS to the number of jiffies to sleep for. If we just return,
	; we need to clear EXAMPLEJIFS in case it contains a negative value.
	;
	movlw	JMILLI		; Sleep for 1ms
	addwf	EXAMPLEJIFS, f	; We can use addwf instead of movwf to correct
				; for oversleeping.

	retlw	0		; All done, return to main loop
	; -------------------------------------------------------------------

	end
