; COPYRIGHT 2002 BY DAVID EK. PERMISSION IS GRANTED TO FREELY USE
; THIS SOFTWARE FOR NONCOMMERCIAL PURPOSES.
;
; NOTE: USE AT YOUR OWN RISK. THE AUTHOR ASSUMES NO LIABILITY FOR DAMAGE
; INCURRED FROM THE USE OF THIS SOFTWARE.
;
; This is the PIC code for the circuit shown in part 1 of the NK0E
; weather station project, appearing in the Digital QRP Homebrewing
; column in QRP Quarterly, July 2002. This is a simple test circuit
; that uses a switch connected to pin RA0 (pin 17) to control an LED
; connected to pin RB0 (pin 6). The switch is normally open, holding
; RA0 at +5V and causing the LED to flash slowly. Depressing the switch
; causes RA0 to be grounded, causing the LED to flash rapidly.
;
; There are lots of different ways I could have written this code. I 
; tried to keep it simple and straightforward, but I could have used
; interrupts to handle timing and detecting the state of the input on
; RA0. If you see other ways to do the same things, try them out. 
;
; Written using Microchip MPLAB v5.40
;
; Please direct any questions, comments, or suggestions to the
; author: David Ek
;         nk0e@earthlink.net
;
; History:
;
; Rev 1 (30 June 2002):
;    Creation.
;
;----------------------------------------------------------------
;
; Compiler directives:
;
; These are instructions to tell the compiler how to create
; the hex code from my source code file.
;
; The list command tells the compiler that my code is 
; written for the PIC16F84.

	list	p=16f84

; The radix command is used to tell the compiler how to
; interpret numbers if they aren't specifically formatted
; as hexadecimal, decimal, etc. In this case I'm using
; decimal as my default. Numbers beginning with "0x" are
; in hexadecimal format.

	radix	dec

; The __config command is used to tell the compiler to set
; the chip's configuration bits in a certain way. In this
; case, I'm disabling the code protection feature, the 
; watchdog timer, and I'm setting the oscillator type to be
; an external crystal oscillator.

	__config _CP_OFF & _WDT_OFF & _XT_OSC

; The "include" directive tells the compiler to insert the
; contents of the specified file at that point in my
; source code. The p16f84.inc file is included with MPLAB
; and is used out of convenience. What it does is
; predefines symbols so I don't have to worry about
; addresses for common memory locations. For example, the
; STATUS register is located at address 0x03, but it's
; much easier to use "STATUS" instead of "0x03" in the
; source code because it's easier to remember and also
; easier to read. The p16f84.inc file defines the value of
; STATUS to be 0x03 for the compiler (and it also defines
; many other symbols).

	include	"p16f84.inc"

; We need to define a few memory locations to use as variables.
; I'll name all my variables starting with "_", for consistency.
; The addresses we can use begin at 0x0C. We'll use the cblock
; command to define our variables, starting at address 0x0C:

	cblock 0x0C

	_MSDelay		;address 0x0C
	_FlashDelay		;address 0x0D

; "endc" ends our block of variable definitions:

	endc

; Now begins our program. The first thing we need to do is set
; the correct data lines (RA0-RA4 and RB0-RB7) to be either
; inputs or outputs. We do that by setting bits in the TRISA
; and TRISB registers (TRIS stands for "tri-state"). TRISA is
; used to set RA0-RA4, and TRISB is used to set RB0-RB7.

; There are two banks of file registers, known as bank 0 and
; bank 1. Some of these file registers serve special purposes,
; like the TRISA and TRISB registers. There are also registers
; for handling interrupts, indicating processor status, and
; accessing the data lines. The remaining file registers can
; be used by your program as variables.

; Only one bank of file registers is accessible at any given
; time. Bank 0 is the accessible register when your program
; starts. In order to access registers in bank 1, you need
; to tell the processor to switch the banks. The STATUS
; register exists in both banks and is used to switch from
; bank 0 to bank 1 and vice versa. To access bank 1, we
; change the value of the RP0 bit in the STATUS register
; from 0 to 1, and changing it from 1 to 0 switches us back
; to bank 0. Since the TRISA and TRISB registers are in bank
; 1, we need to switch to that bank in order to access them.
; Here's how:

	bsf	STATUS,RP0	;set (make 1) the RP0 bit of
				;the STATUS register to
				;access bank 1

; Now we need to load TRISA and TRISB with values that make
; RB0 an output and RA0 and input. Each bit in TRISA and TRISB
; corresponds to one of the data lines, and making that bit a
; 1 makes that line an input. Making that bit a zero makes that
; line an output. We'll make every line an input except for
; RB0. We use the W register to move the number into the 
; desired file register. The W register is a special register
; used frequently, mainly for loading and moving values 
; between file registers, and also for holding the results of
; bit manipulations, additions, subtractions, etc. Many of the
; programming instructions use the W register.

; First we'll set TRISA:

	movlw	b'11111111'	;put 255 into the W register.
				;this is a binary representation
				;of the number, to make it easier
				;to see which bits are on or off.
				;I could have used "0xFF" or "255"
				;instead.

	movwf	TRISA		;now move the number from the W
				;register to TRISA. This is the
				;only way to put a literal value
				;into a file register.

; Now TRISB, in the same way:

	movlw	0xFE		;now load W with 254 (b'11111110').
				;note that I used hex instead of
				;binary expression this time. I
				;can choose either, and the
				;compiler will know what I mean.

	movwf	TRISB		;now move the value from the W
				;register to TRISB.

; Now we'll switch back to bank 0. To do so, we write a zero in
; the RP0 bit of the STATUS register.

	bcf	STATUS,RP0

; Here begins the main program. We'll start by assuming that RA0
; is held high. If not, we'll figure it out soon enough and
; switch. We'll set up a loop to flash the LED on RB0. Each time
; through the loop we'll see if RA0 has been changed to low, and
; if so we'll jump to the part of the program that handles that.

; "RA0Set" is a label for the address of the instruction below it.
; We can use these labels (that begin in column 1 of the text)
; to refer to places in the program to which we want to jump or
; call subroutines.

RA0Set
	movlw	250		;250 will give us about 1 sec on
				;and 1 sec off for the led
	movwf	_FlashDelay
	call	ToggleRB0	;ToggleRB0 is a subroutine that
				;flips the state of RB0 (the LED)

; LoopRA0Set defines the start of a loop that kills time while
; waiting to toggle the LED. There is also a check to see if the
; user has flipped the switch to take RA0 low.

LoopRA0Set
	call	WaitMS		;we want to wait a bit so we'll
	call	WaitMS		;call the WaitMS subroutine.
	call	WaitMS		;each call wastes a millisecond.
	call	WaitMS
	btfss	PORTA,0		;is RA0 still high (set)? the btfss
				;instruction checks to see if the 
				;specified bit on the specified
				;memory location is set. If it is,
				;the next instruction ("goto
				;RA0Cleared") is skipped. If not,
				;the next instruction is executed.

	goto	RA0Cleared	;if not set, go to RA0Cleared

	decfsz	_FlashDelay,F	;otherwise, decrement the loop
				;counter and check to see if
				;it's zero (that's what decfsz
				;does). If it's zero, the next
				;instruction "goto LoopRA0Set"
				;will be skipped. Otherwise,
				;that instruction will be 
				;executed. The ",F" at the end
				;of the instruction means that
				;the result of the decrement should
				;be written back to _FlashDelay.
				;If it ended with ",W", the result
				;would have been written to the W
				;register, and _FlashDelay would
				;not have changed.
	goto	LoopRA0Set

	goto	RA0Set		;if we reached the end of the
				;loop, go back and start
				;another. The LED state will
				;be toggled to make it flash.

; The treatment for when RA0 is low is exactly the same, except
; we set _FlashDelay to a value that makes the LED flash more
; rapidly by reducing the number of times we go through the
; loop to waste time.

RA0Cleared
	movlw	50
	movwf	_FlashDelay
	call	ToggleRB0

LoopRA0Cleared
	call	WaitMS
	call	WaitMS
	call	WaitMS
	call	WaitMS

; here we check to see if RA0 is still low, instead of high as before.
; btfsc works like btfss, but checks to see if the bit is cleared
; instead of set.

	btfsc	PORTA,0		;is RA0 still low (cleared)?
	goto	RA0Set		;if not, go to RA0Set
	decfsz	_FlashDelay,F
	goto	LoopRA0Cleared
	goto	RA0Cleared

; That's the end of the main program. What follows now are two 
; subroutines. Subroutines are reached using the "call" instruction,
; and will return to the instruction after the "call" when the
; "return" instruction is executed.

;------Subroutine ToggleRB0------------------------------------------
;
; ToggleRB0 changes the state of output line RB0.
;

ToggleRB0
	btfss	PORTB,0		;is RB0 high now?
	goto	SetRB0High	;no--go set it high
	bcf	PORTB,0		;yes--set it low (bcf clears the
				;specified bit of the specified
				;memory location)
	goto	EndToggleRB0

SetRB0High
	bsf	PORTB,0		;bsf sets (to 1) the specified bit of
				;the specified memory location.

EndToggleRB0
	return

;------end ToggleRB0-------------------------------------------------

;------Subroutine WaitMS---------------------------------------------
;
; WaitMS is an approximate millisecond delay. It assumes a 4 MHz
; oscillator, meaning instructions are executed at a rate of 1 MHz.
; I got the timing info (number of cycles per instruction) from the
; Microchip PIC16F84 data sheet.

; the call to this subroutine takes 2 cycles to execute.

WaitMS
	movlw	248		;1 cycle
	movwf	_MSDelay	;1 cycle
	nop			;1 cycle--these nops are added to 
	nop			;1 cycle  make the total number of
	nop			;1 cycle  instructions executed in
				;         the routine to be 1000.
				;the nop instruction simply does 
				;nothing except take time to execute.

; The loop below takes four cycles for every time through except the
; last, when it takes five (including the time needed to execute the
; return). So, the total number of instructions executed in getting
; to and returning from this subroutine is:
;
;        2 to get here
;    +   2 to set the MSDelay value
;    +   3 for the nops
;   + 247*4 for the first 247 times through the loop
;    +   5 for the last time through the loop and to return
;   --------
;    = 1000

RepeatWaitMS
	nop			;1 cycle
	decfsz	_MSDelay,F	;1 cycle if not zero, 2 if zero
	goto	RepeatWaitMS	;2 cycles
	return			;2 cycles

;------end WaitMS----------------------------------------------------

	end			;"end" indicates the end of the code.

