; 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 CODE FOR INSTALLMENT 2 OF THE PIC WX ARTICLES IN
; THE DIGITAL QRP HOMEBREWING COLUMN OF QRP QUARTERLY, FALL 2002.
;
; PIC16F84 code for the NK0E weather station project. The purpose
; of this project is to provide a hardware interface which measures
; temperature, wind speed, wind direction, humidity, and pressure
; and communicates it to a PC via serial communications when
; requested. Note that the data collected and transmitted must be
; processed by the PC in order to obtain the actual measurement
; values (i.e. no calibration is done in the PIC code).
;
; The hardware circuit makes use of two major components: the 
; PIC16F84 itself and a MAX232 TTL-RS232 level converter.
; The MAX232 converts the TTL signals from the PIC to RS232 levels
; for the PC (and vice versa).
;
; The PIC16F84 pin assignments are as follows:
;
; RA1: serial out to PC
; RB0: serial in from PC (generates interrupt)
;
; This software implements the following command set:
;
; Received  Reply        Description
;-----------------------------------
; 't'       12345 CR LF  Get the temperature raw data. Five digits
;                        are always returned, with leading zeroes
;                        if necessary, followed by a carriage return
;                        and line feed.
; 'v'       version info Gets the version number of the firmware.
;                        returns an ASCII string followed by a 
;                        carriage return and line feed.
;
; Written using Microchip MPLAB v5.40
;
; Please direct any questions, comments, or suggestions to the
; author: David Ek
;         nk0e@earthlink.net
;
; History:
;
; Rev 1 (23 Jul 2002):
;    Creation. Returns dummy temperature data. This version used
;    for installment 2 of the PIC WX articles in QQ.
;
;----------------------------------------------------------------

	list	p=16f84
	radix	dec
	__config _CP_OFF & _WDT_OFF & _XT_OSC

	include	"p16f84.inc"

; defines:

; These are used by the serial comm routines for timing. Note that
; slower speeds may not work well because the delays might be larger
; than 255, requiring the use of the prescalar.

_Clkspd		equ	1000000			;external clock / 4
_BaudRate	equ	9600			;desired baud rate
_Period		equ	(_Clkspd/_BaudRate)	;clock cycles / bit

_StartRxDelay	equ	(_Period/2 - 15)/3	;this is how long to
						;wait to get to the
						;middle of the start
						;bit. This is loops,
						;not clock cycles.

_BitRxDelay	equ	277 - _Period		;this is what to load
						;into TMR0 for correct
						;interval between bits
						;on RX

_BitTxDelay	equ	285 - _Period		;this is what to load
						;into TMR0 for correct
						;interval between bits
						;on TX

_StopTxDelay	equ	272 - _Period		;this is what to load
						;into TMR0 for correct
						;interval betweeen last
						;bit and stop bit on TX

; macros for the bin-to-ascii conversion routines:

loadw	macro	arg1
	movlw	high(arg1)
	movwf	hi
	movlw	low(arg1)
	movwf	lo
	endm
dodigit	macro	arg1
	movlw	high(arg1)
	movwf	shi
	movlw	low(arg1)
	movwf	slo
	call	dosub
	endm

; memory locations:

	cblock 0x0C

	BitCount	;number of bits left to send or
			;receive, not including start & stop
			;bits

	RXChar		;received character while being received
	RXBuff		;most recently received character

	TXChar		;character to transmit

	SerialReg	;status register:
			;bit 0: on if character has been
			;       received
			;bit 1: on if busy with RX/TX
			;bit 2: on if sending, off if receiving
			;bit 3: on if next bit to send is stop bit

	WSave		;copy of W register

	SSave		;copy of the Status register

	TValLo		;the lo byte of the current temperature value
	TValHi		;the hi byte of the current temperature value

	hi		;hi byte for number to be converted to ascii
	lo		;lo byte for number to be converted to ascii
	shi		;hi byte for subtractor for conversion to ascii
	slo		;lo byte for subtractor for conversion to ascii
	digit		;ascii digit converted from binary

	endc

	org	0x00
	goto	Main

	org	0x04

;--------------------------------------------------------------------
;-----Serial Communication Routines----------------------------------
;--------------------------------------------------------------------
;
; The serial comm routines generate 1 start bit, 8 data bits, 1 stop bit, 
; no parity. The baud rate is determined by the delay programmed
; into the onboard timer. The sending and receiving is interrupt driven,
; meaning other tasks can be carried on while the characters are being
; sent and received.
;
;-----Main Interrupt Routine-----------------------------------------
;
; for timing: 4 cycles from interrupt time to get here.

; Save the W and STATUS registers:

Int
	movwf	WSave
	swapf	STATUS,W	;use swapf to prevent any
	movwf	SSave		;status flags from being changed

; cycles so far since interrupt occurred: 7

; Okay, I'm doing something goofy here. Instead of actually checking
; the overflow bits themselves, I'm using the enable bits to determine
; which interrupt occurred. I can do this because there should only
; ever be one type of interrupt active at a time. The problem with
; checking the overflow bits is that the T0IF bit can get set even
; if that interrupt is disabled.

; Check first for a timer overflow interrupt. The overflow bit gets
; set even if the interrupt is disabled.

	btfsc	INTCON,T0IE
	goto	DoBit		;we're in the middle of sending or
				;receiving

; 10 cycles executed on entry to DoBit

; If not a timer overflow interrupt, check for external interrupt:

	btfsc	INTCON,INTE	;RB0 is our receive line and it
	goto	StartRX		;generates an interrupt on a high-
				;to-low transition

; 12 cycles executed on entry to StartRX

; Else, must be something we don't care about:

	;do nothing for now

; Restore the W and STATUS registers:

Restore	swapf	SSave,W
	movwf	STATUS
	swapf	WSave,F
	swapf	WSave,W

	retfie

;------end Main Interrupt Routine------------------------------------


;------Subroutine SerSetup-------------------------------------------

SerSetup

; set up the option register for internal counting, WDT disabled,
; no prescaler.

	clrf	TMR0
	bsf	STATUS,RP0
	clrwdt			;set bits in OPTION_REG to
	movlw	b'10001000'	;enable internal clock counting,
	movwf	OPTION_REG	;disable watchdog timer.
	bcf	STATUS,RP0	;switch to bank 0

; set the output line to idle (high)

	bsf	PORTA,1		;set the output line to idle
				;(high) state

	movlw	b'00000001'
	movwf	PORTB

; enable the external interrupt via RB0

	movlw	b'10010000'	;set bits in INTCON to enable
	movwf	INTCON		;external interrupt

; initialize the SerialReg:

	clrf	SerialReg

	return

;------end SetSetup--------------------------------------------------

;------Subroutine StartRX--------------------------------------------
;
; This subroutine is called by the main interrupt routine when an
; external interrupt on RB0 occurs. This means we're receiving the
; start bit for a character. We want to enable the external TMR0
; interrupt and prepare to receive the character.

StartRX

; wait halfway through the bit to see if it's real:

	bcf	INTCON,INTF	;clear the interrupt
	movlw	_StartRxDelay
	movwf	BitCount	;this is the 15th instruction since
				;the interrupt. Note--we're using
				;BitCount for this loop purely for
				;convenience. Usually it's used to
				;actually count the bits we TX/RX.

RXWait	decfsz	BitCount,F	;this loop takes 3 times the initial
	goto	RXWait		;value of BitCount clock cycles
	
; now we should be at the middle of the start bit. Is the input still
; low? If not, goto Restore and ignore this interrupt.

	btfsc	PORTB,0
	goto	Restore

; if we get to here it must really be the start bit. Load TMR0,
; disable the external interrupt, and enable the TMR0 interrupt. 

; load up the appropriate delay to get us to the middle of the
; first bit:

	movlw	_BitRxDelay
	movwf	TMR0		;4 cycles from read of PORTB
	movlw	b'00100000'
	movwf	INTCON

; set the SerialReg to indicate that the routines are busy getting
; a character:

	movlw	b'00000010'
	movwf	SerialReg

; initialize BitCount:

	movlw	8
	movwf	BitCount

; okay, now we return.

	goto	Restore

;------end StartRX---------------------------------------------------


;------DoBit---------------------------------------------------------
;
; sends or receives the next bit. Bits are sent/received from least
; to most significant bit.

DoBit

; clear the TMR0 overflow interrupt flag:

	bcf	INTCON,T0IF

; Are we receiving?

	btfsc	SerialReg,2
	goto	Sending

; check to see if we're receiving the stop bit:

	movf	BitCount,F
	btfsc	STATUS,Z
	goto	GetStopBit

; if we get to here, we're in the middle of receiving. Get the next
; bit: (16 cycles to get to the next instruction from the start of
; the interrupt).

	rrf	PORTB,W		;rrf PORTB into W. This sets
				;the carry bit if RB0 was high.
	rrf	RXChar,F	;doing a rrf on RXChar brings
				;in the carry bit to the MSB.

; Decrement the bit counter.

	decf	BitCount,F

; reload TMR0 for the next interrupt, and
; go to the end of the interrupt routine.

	movlw	_BitRxDelay	
	movwf	TMR0		;21 cycles from start of interrupt
	goto	Restore

; if we get to here it's because we need to check for the stop bit.

GetStopBit
	btfss	PORTB,0		;is the RX line low? If so, it's not
	goto	Done		;the stop bit. Otherwise, set the
	movlw	b'00000001'	;SerialReg to show a character has
	movwf	SerialReg	;been received
	movf	RXChar,W	;copy the received character to RXBuff
	movwf	RXBuff
	goto	Done
		
; We got here because we're sending.
; check to see if we're finished sending the stop bit:

Sending
	btfsc	SerialReg,3
	goto	Done

; check to see if we need to send the stop bit:

	movf	BitCount,F
	btfsc	STATUS,Z	;18th cycle
	goto	SendStopBit

; if we get to here, we're in the middle of sending. Send the next
; bit: (16 cycles to get to the next instruction from the start of
; the interrupt).

	rrf	TXChar,F	;doing rrf on TXChar puts the
	btfss	STATUS,C	;least significant bit in the
	goto	SendZero	;carry flag.
	nop
	bsf	PORTA,1		;if carry is set, send a one.
	goto	EndDoBit	;PORTA,1 is set on the 24th cycle

SendZero
	bcf	PORTA,1		;otherwise, send a zero. (24th cycle)
	nop			;nop's are for taking the same time
	nop			;to get to reloading TMR0 as for when
				;a one is sent.

; Decrement the bit counter.

EndDoBit
	decf	BitCount,F

; reload TMR0 for the next interrupt, and
; go to the end of the interrupt routine.

	movlw	_BitTxDelay	
	movwf	TMR0		;29th cycle
	goto	Restore


; Here we need to send the stop bit, turn off the TMR0 interrupt,
; turn on the external interrupt, and set the SerStatus register
; flags appropriately.

SendStopBit
	nop
	nop
	nop
	bsf	PORTA,1		;no. Send the stop bit. (24th cycle)
	bsf	SerialReg,3	;set the "sending stop bit" flag

; reload TMR0 for the next interrupt, and
; go to the end of the interrupt routine.

	movlw	_StopTxDelay
	movwf	TMR0		;27th cycle
	goto	Restore

; we're completely done sending or receiving. Clean up.

Done	movlw	b'00010000'	;set bits in INTCON to enable
	movwf	INTCON		;external interrupt
	movlw	b'00000001'
	andwf	SerialReg,F	;clear the busy bits in SerialReg
	goto	Restore

;------end DoBit-----------------------------------------------------

;------Subroutine SendChar-------------------------------------------
;
; This is not called by the interrupt handler. Rather, it activates 
; the interrupts needed to send it. Put the character to be sent in
; the TXChar file register before calling this subroutine.
;

SendChar
	
; send the start bit:

	bcf	PORTA,1

; set the SerStatus to indicate that the routines are busy sending
; a character:

	movlw	b'00000110'
	movwf	SerialReg

; load up TMR0 so it overflows at the right time.

	nop			;for timing
	movlw	_BitTxDelay
	movwf	TMR0		;5th cycle after write to PORTA

; clear the external interrupt flag, disable the external interrupt,
; and enable the TMR0 interrupt.

	movlw	b'10100000'
	movwf	INTCON

; set the BitCount for the eight bits to send:

	movlw	8
	movwf	BitCount

	return

;------end SendChar--------------------------------------------------

;------begin GetAChar------------------------------------------------

GetAChar
	call	Idle
	btfss	SerialReg,0	;wait for a character to be received
	goto	GetAChar
	bcf	SerialReg,0
	return

;------end GetAChar--------------------------------------------------

;------begin SendAChar-----------------------------------------------

SendAChar
	call	SendChar

WaitToFinish
	call	Idle
	btfsc	SerialReg,1	;wait for the character to be sent
	goto	WaitToFinish
	return

;------end SendAChar-------------------------------------------------

;------begin TellTemperature-----------------------------------------

;This subroutine is called when the 't' command is received. Currently
;it only returns dummy data.

TellTemperature

	; change the temp values for test purposes:
	decf	TValLo,F

	movf	TValLo,W
	movwf	lo
	movf	TValHi,W
	movwf	hi

	; Send the data as ASCII:
	call	SendAsciiNum

	; Send the terminating CR and LF:
	movlw	13
	movwf	TXChar
	call	SendAChar
	movlw	10
	movwf	TXChar
	call	SendAChar

	goto	MainLoop

;------end TellTemperature-------------------------------------------


;------begin SendAsciiNum--------------------------------------------
;
; load lo, hi with 16 bit unsigned num to send

SendAsciiNum
	dodigit	10000
	movf	digit,W
	movwf	TXChar
	call	SendAChar
	dodigit	1000
	movf	digit,W
	movwf	TXChar
	call	SendAChar
	dodigit	100
	movf	digit,W
	movwf	TXChar
	call	SendAChar
	dodigit	10
	movf	digit,W
	movwf	TXChar
	call	SendAChar
	movf	lo,w		; ls byte is already correct
	addlw	'0'		; convert to ascii
	movwf	TXChar
	call	SendAChar
	return			; done

; "dosub" is called by the "dodigit" macro defined above.
; Subtract the number in shi/slo from hi/lo until the result
; is negative, incrementing the ascii equivelent each time.

dosub	movlw	'0'-1
	movwf	digit
moresub	incf	digit,F		; increment ASCII character
	movf	slo,w		; subtract current power of 10
	subwf	lo,f
	movf	shi,w
	btfss	STATUS,C
	addlw	1
	subwf	hi,f
	btfsc	STATUS,C	; any carry?
	goto	moresub		; no, keep subtracting

	movf	slo,w		; reverse the last subtraction
	addwf	lo,f
	movf	shi,w
	btfsc	STATUS,C
	addlw	1
	addwf	hi,f
	return

;------end SendAsciiNum----------------------------------------------

;------begin ReportVersion-------------------------------------------

; send a string with the version in it. The string comes from EEPROM
; memory and is null-terminated. The null terminator is not sent. The
; protocol dictates that the string sent is terminated by a CR, which
; is sent. This subroutine is called when the 'v' command is received.

ReportVersion
	bcf	STATUS,RP0
	clrf	EEADR		;the string we want starts at the 
				;beginning of EEPROM memory.

GetNextVersionChar
	bsf	STATUS,RP0
	bsf	EECON1,RD
	bcf	STATUS,RP0
	movf	EEDATA,W
	btfsc	STATUS,Z	;if the character in W is null, don't
                                ;send any more.
	goto	MainLoop

	movwf	TXChar
	call	SendAChar
	incf	EEADR,F
	goto	GetNextVersionChar

;------end ReportVersion---------------------------------------------

;------begin Idle----------------------------------------------------
;
; Idle should be called whenever the chip is waiting for something
; to happen (waiting for a character to be sent or received, for
; example). Here it's not doing anything.

Idle
	return

;------end Idle------------------------------------------------------

;------Main Program--------------------------------------------------

Main

; set up the ports as inputs and outputs as needed.

	bsf	STATUS,RP0	;switch to bank 1
	movlw	0xFF
	movwf	TRISB		;make Port B input
	movlw	0x00
	movwf	TRISA		;make Port A output
	bcf	STATUS,RP0

	clrf	PORTA

	call	SerSetup	;set up serial comm routines & int.

; TEMPORARY: init the TValLo and TValHi fields for testing. Later, we'll
; replace the dummy data with real data.

	movlw	0xFF
	movwf	TValLo
	movwf	TValHi

; this main program simply waits for characters to be received, then
; calls the handler for the command indicated by the received character.

MainLoop
	call	GetAChar	;wait for a character

	movf	RXBuff,W	;move the rx char into W
	sublw	't'		;compare with 'y' character
	btfsc	STATUS,Z
	goto	TellTemperature	;if y, report the temperature

	movf	RXBuff,W	;move the rx char into W
	sublw	'v'		;compare with 'z' character
	btfsc	STATUS,Z
	goto	ReportVersion	;if v, report the version number

	goto	MainLoop

;------Version EEPROM------------------------------------------------

	org	0x2100
	de	"WxPIC v0.1 (c) 2002 by NK0E",0x0D,0x0A,0x00	; Version 0.1
	end
	
