; 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 4 OF THE PIC WX ARTICLES IN
; THE DIGITAL QRP HOMEBREWING COLUMN OF QRP QUARTERLY.
;
; 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
; RA2: clock out to SHT11 temp/humid sensor
; RA3: data in/out for SHT11 temp/humid sensor
; RA4: LED CW output for stand-alone calibration
; RB0: serial in from PC (generates interrupt)
; RB1: anemometer input
;
; This software implements the following command set:
;
; Received  Reply        Description
;-----------------------------------
; 't'       12345 CR LF  Get the temperature raw data. Five digits
;           or e1 CR LF  are always returned, with leading zeroes
;           or e2 CR LF  if necessary, followed by a carriage return
;                        and line feed. See the SHT11 data sheet
;                        for instructions on computing the temperature.
;                        A return of e1 indicates that the SHT11 didn't
;                        acknowledge the request. A return of e2
;                        indicates that no data was returned by the SHT11
;                        within the wait period (255 ms).
; 'h'       12345 CR LF  Get the humidity raw data. Five digits
;           or e1 CR LF  are always returned, with leading zeroes
;           or e2 CR LF  if necessary, followed by a carriage return
;                        and line feed. See the SHT11 data sheet for
;                        instructions on computing the humidity.
;                        A return of e1 indicates that the SHT11 didn't
;                        acknowledge the request. A return of e2
;                        indicates that no data was returned by the SHT11
;                        within the wait period (255 ms).
; 'v'       version info Gets the version number of the firmware.
;                        returns an ASCII string followed by a 
;                        carriage return and line feed.
;
; References:
; - the method for representing the CW characters in memory was adapted from
;   KEYER1.ASM by W. Buescher (DL4YHF). See www.qsl.net/dl4yhf/pic_key.html
; - the logic for handling iambic paddle keying was adapted from K1EL's K8
;   keyer source code. See http://www.k1el.com/.
;
; The following characters are supported for CW:
; 
;     0 through 9
;

; 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.
; Rev 2 (26 Sep 2002):
;    Added code for the Sensirion SHT11 temperature/humidity sensor
;    for installment 3 of the PIC WX articles in QQ.
; Rev 3 (7 Mar 2003):
;    Added code for the easter-egg anemometer for installment 4 of
;    the QQ articles. Included a pushbutton and LED so that the
;    anemometer could be calibrated while mobile without a PC. The
;    pushbutton triggers a read of wind speed and outputs the five
;    digit raw result to the LED via CW.
;
;----------------------------------------------------------------

	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

; defines for I/0 pins:

; PORTA:

_INVCW		equ	0			;inverted CW out is PORTA,0
_SER_OUT	equ	1			;Serial output line (PORTA)
_SHT11_SCK	equ	2			;clock line for SHT11 sensor
						;(PORTA)
_SHT11_DAT	equ	3			;data line for SHT11 sensor
						;(PORTA)
;PORTB:

_SER_IN		equ	0			;Serial input line (PORTB)
_WIND_IN	equ	1			;anemometer input line (PORTB)
_WIND_IN_MASK	equ	b'00000010'		;for masking the _WIND_IN bit
						;on PORTB
_PBUTTON	equ	2			;pushbutton line--normally high,
						;pushing the button grounds it.
						;(PORTB)

; NOTE: This method of representing the CW characters in memory was adapted from
; KEYER1.ASM (revision 3/2000), written by Wolfgang Buescher (DL4YHF).

_Char_0		equ	b'00111111'	; zero
_Char_1		equ	b'00101111'	; one
_Char_2		equ	b'00100111'	; two
_Char_3		equ	b'00100011'	; three
_Char_4		equ	b'00100001'	; four
_Char_5		equ	b'00100000'	; five
_Char_6		equ	b'00110000'	; six
_Char_7		equ	b'00111000'	; seven
_Char_8		equ	b'00111100'	; eight
_Char_9		equ	b'00111110'	; nine

; 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

	SHT11Byte	;a byte of data sent to or returned by the SHT11

	counter		;generic counter register
	counter2	;another generic counter register

	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. Also used as
			;input to SendErrorCode

	MSDelay		;register for WaitMS timing

	RegSave		;copy of PORTA or PORTB register

; memory locations used by CW sending and timing:

	DitLen		;milliseconds per dit.

	DitWordLen	;milliseconds per dit for intercharacter/word spacing

	DitLoopIndex	;loop index for WaitDit.

	CharToSend	;CW character being sent

	CharBitCount	;counter for bits in the CW character to send
	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,_SER_OUT	;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,_SER_IN
	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,_SER_IN	;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,_SER_OUT	;if carry is set, send a one.
	goto	EndDoBit	;PORTA,1 is set on the 24th cycle

SendZero
	bcf	PORTA,_SER_OUT	;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,_SER_OUT	;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,_SER_OUT

; 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-------------------------------------------------

;------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----------------------------------------------------


;------begin WaitDit-------------------------------------------------
;
; WaitDit is a delay loop which times dits.

WaitDit
	movf	DitLen,W
	movwf	DitLoopIndex

RepeatWaitDit
	call	WaitMS
	decfsz	DitLoopIndex,F
	goto	RepeatWaitDit

	return

;------end WaitDit---------------------------------------------------

;------begin WaitDah-------------------------------------------------
;
; WaitDah is a delay loop which times dahs. Dahs are three times the
; length of dits.

WaitDah
	call WaitDit
	call WaitDit
	call WaitDit
	return

;------end WaitDah---------------------------------------------------

;------begin WaitWordDit---------------------------------------------
;
; WaitWordDit is a delay loop which times dits based on intercharacter
; spacing.

WaitWordDit
	movf	DitWordLen,W
	movwf	DitLoopIndex

RepeatWaitWordDit
	call WaitMS
	decfsz	DitLoopIndex,F
	goto	RepeatWaitWordDit
	return

;------end WaitWordDit-----------------------------------------------

;------begin WaitWordDah---------------------------------------------
;
; WaitWordDah is a delay loop which times dahs based on intercharacter
; spacing. WordDahs are three times the length of Worddits.

WaitWordDah
	call WaitWordDit
	call WaitWordDit
	call WaitWordDit
	return

;------end WaitWordDah-----------------------------------------------

;------begin SendCWDit-----------------------------------------------
;
; actually sends the dit over the LED

SendCWDit
	bcf	PORTA,_INVCW	; turn on LED
	call	WaitDit		; wait the appropriate amount of time
	bsf	PORTA,_INVCW	; turn off LED
	call	WaitDit		; wait for space between dits and dahs
	return

;------end SendCWDit-------------------------------------------------

;------begin SendCWDah-----------------------------------------------
;
; actually sends the dah over the LED

SendCWDah
	bcf	PORTA,_INVCW	; turn on LED
	call	WaitDah		; wait the appropriate amount of time
	bsf	PORTA,_INVCW	; turn off LED
	call	WaitDit		; wait for space between dits and dahs
	return

;------end SendCWDah-------------------------------------------------

;------begin SendCWDigit---------------------------------------------
;
; load CharToSend with the ASCII value of the character to be sent.

SendCWDigit
	movlw	0x30		;we'll subtract 48 from the ASCII value
				;since we're only interested in characters
				;starting with ASCII 48 (zero)
	subwf	CharToSend,F	;subtract 48 from the next character ASCII value

	movlw	HIGH Table	;preset the PCLATH for the computed goto
	movwf	PCLATH

	movf	CharToSend,W	;now put the value in W for table lookup
	call	Table		;return value will be the bit string to send
	movwf	CharToSend	;store the bit string in CharToSend

; find the Start bit. Start looking at bit 6 and continue down until a 1 is encountered.
; the rest of the bits to send will follow.

	rlf	CharToSend,F	; get rid of bit 7.
	movlw	0x08		; load the bit counter so we know when we're out of bits.
	movwf	CharBitCount
	bcf	STATUS,C

FindCWStartBit
	decf	CharBitCount,F	; count the bits we check.
	rlf	CharToSend,F	; look at the next bit. is it a 1? if so, that's our start bit.
	btfss	STATUS,C
	goto	FindCWStartBit

SendCWBit
	decfsz	CharBitCount,F	; check to see if we're out of bits to send.
	goto	SendNextCWBit
	goto	DoneSendingCWBits

SendNextCWBit
	rlf	CharToSend,F	; get the next bit. 1 is a dah, 0 is a dit.
	btfss	STATUS,C
	goto	SendADit
	goto	SendADah

SendADit
	call	SendCWDit	; send the dit
	goto	SendCWBit	; do the next dit or dah

SendADah
	call	SendCWDah	; send the dah
	goto	SendCWBit	; do the next dit or dah

DoneSendingCWBits

; wait a dah length for the next character.

	call	WaitWordDah	; use the intercharacter spacing

	return

;------end SendCWChar------------------------------------------------

;------begin SendCWAsciiNum--------------------------------------------
;
; load lo, hi with 16 bit unsigned num to send. Sends the resulting
; ASCII sequence of digits in CW via the LED.

SendCWAsciiNum
	dodigit	10000
	movf	digit,W
	movwf	CharToSend
	call	SendCWDigit
	dodigit	1000
	movf	digit,W
	movwf	CharToSend
	call	SendCWDigit
	dodigit	100
	movf	digit,W
	movwf	CharToSend
	call	SendCWDigit
	dodigit	10
	movf	digit,W
	movwf	CharToSend
	call	SendCWDigit
	movf	lo,w		; ls byte is already correct
	addlw	'0'		; convert to ascii
	movwf	CharToSend
	call	SendCWDigit
	return			; done

;------end SendCWAsciiNum----------------------------------------------

;------begin SHT11TXRX-----------------------------------------------
;
; Sends a byte command to the SHT11 temp/humidity sensor and retrieves
; a two-byte response. Sends the response back to the PC as an ASCII
; string representation of the number.
;
; Put the byte to send in SHT11Byte before calling this routine. 

SHT11TXRX

	;make _SHT11_DAT an output

	bsf	STATUS,RP0	;switch to bank 1
	bcf	TRISA,_SHT11_DAT	;make Port A data line an output
	bcf	STATUS,RP0	;switch back to bank 0

	;send the Transmission Start sequence:

	bsf	PORTA,_SHT11_DAT	;set the data line high
	bsf	PORTA,_SHT11_SCK	;take the clock line high
	bcf	PORTA,_SHT11_DAT	;take the data line low
	bcf	PORTA,_SHT11_SCK	;take the clock line low
	bsf	PORTA,_SHT11_SCK	;take the clock line high again
	bsf	PORTA,_SHT11_DAT	;set the data line high again
	
	;load up the counter to loop through the eight bits to send:

	movlw	8
	movwf	counter

SHT11SendBitLoop
	bcf	PORTA,_SHT11_SCK	;take the clock line low

	btfss	SHT11Byte,7	;is the next bit to send a one?
	goto	SHT11SendZero	;nope. Go send a zero.
	bsf	PORTA,_SHT11_DAT	;if it's a one, send it.
	goto	SHT11SendBit

SHT11SendZero
	bcf	PORTA,_SHT11_DAT	;set the data line to zero

SHT11SendBit
	bsf	PORTA,_SHT11_SCK	;take the clock line high to send

	rlf	SHT11Byte,F	;move the next bit into MSB

	decfsz	counter,F	;dec the counter and check for zero.
	goto	SHT11SendBitLoop	;if not zero, more bits to send

	bcf	PORTA,_SHT11_SCK	;take the clock line low

	;no more bits to send. Set the data line to be an input and
	;wait for the ack from the SHT11:

	bsf	STATUS,RP0	;switch to bank 1
	bsf	TRISA,_SHT11_DAT	;make Port A data line an input
	bcf	STATUS,RP0	;switch back to bank 0

	; now look for an ack (SHT11 pulls data line low--should
	; happen on the next rise of the SCK line). If it doesn't
	; happen, return an 'e' and quit.

	bsf	PORTA,_SHT11_SCK

SHT11WaitAck
	btfss	PORTA,_SHT11_DAT
	goto	SHT11GotAck

	;if we don't get an ack, quit, send an 'e1' for error and return.

	movlw	'1'
	movwf	digit
	call	SendErrorCode
	goto	SHT11TXRXDone

	; we got an Ack. Get ready for the data to be returned. take
	; the clock line low, and then wait for the data line to be
	; pulled low again.

SHT11GotAck
	bcf	PORTA,_SHT11_SCK

	; now wait for the data. It takes approximately 210 ms for
	; the temperature measurement, or 55 ms for the humidity
	; measurement, so we'll wait up to 255 ms before giving up.

	movlw	255
	movwf	counter

SHT11WaitData
	btfss	PORTA,_SHT11_DAT
	goto	SHT11DataReady
	call	WaitMS
	decfsz	counter,F
	goto	SHT11WaitData

	;if we don't get the data, quit, send an 'e2' error and return.

	movlw	'2'
	movwf	digit
	call	SendErrorCode
	goto	SHT11TXRXDone

SHT11DataReady

	; get the most sig byte:

	call	SHT11GetByte
	movf	SHT11Byte,W
	movwf	hi
	call	SendAck		;acknowledge the byte

	; get the least sig byte:

	call	SHT11GetByte
	movf	SHT11Byte,W
	movwf	lo
	call	SendAck		;acknowledge the byte

	call	SHT11GetByte	; gets the checksum (not needed)

	; Send the data as ASCII:
	call	SendAsciiNum

	; Send the terminating CR and LF:
	call	SendCRLF

SHT11TXRXDone
	return

;------end SHT11TXRX--------------------------------------------------

;------begin SHT11GetByte---------------------------------------------
;
; Gets a byte of data from the SHT11. Assumes that the data
; is ready to be sent by the SHT11. Also assumes that _SHT11_DAT has
; been set to input. Also assumes that _SHT11_SCK has been set to low.
; Returns the byte in SHT11Byte.

SHT11GetByte
	; clear SHT11Byte:

	clrf	SHT11Byte

	; set counter to get eight bits

	movlw	8
	movwf	counter

SHT11GetByteLoop
	bsf	PORTA,_SHT11_SCK	;set the clock high to get the next bit
	btfss	PORTA,_SHT11_DAT	;is the next bit a one?
	goto	SHT11GetZeroBit	;no--it's a zero
	bsf	SHT11Byte,0	;if it's a one, set the LSB in SHT11Byte
	goto	SHT11GotBit

SHT11GetZeroBit
	bcf	SHT11Byte,0	;set the LSB to zero in SHT11Byte

SHT11GotBit
	bcf	PORTA,_SHT11_SCK	;set the clock line low again.
	decfsz	counter,F
	goto	SHT11GetNextBit
	goto	SHT11GetByteDone

SHT11GetNextBit
	rlf	SHT11Byte,F	;move the bits over to get the next bit
	goto	SHT11GetByteLoop

SHT11GetByteDone

	return

;------end SHT11GetByte-----------------------------------------------

;------begin SendAck-------------------------------------------------
;
; send the ack. Set the data line as an output:

SendAck
	bsf	STATUS,RP0	;switch to bank 1
	bcf	TRISA,_SHT11_DAT	;make Port A data line an output
	bcf	STATUS,RP0	;switch back to bank 0

	; now send the ack. Take the data line low.

	bcf	PORTA,_SHT11_DAT
	bsf	PORTA,_SHT11_SCK
	bcf	PORTA,_SHT11_SCK

	; now make the data line an input again.

	bsf	STATUS,RP0	;switch to bank 1
	bsf	TRISA,_SHT11_DAT	;make Port A data line an input
	bcf	STATUS,RP0	;switch back to bank 0

	return

;------end SendAck---------------------------------------------------

;------begin SendErrorCode-------------------------------------------
;
; send error code back to PC. Error code is 'e' plus a digit. Load
; ASCII value of digit into 'digit' register before calling.

SendErrorCode
	movlw	'e'
	movwf	TXChar
	call	SendAChar
	movf	digit,W
	movwf	TXChar
	call	SendAChar
	call	SendCRLF
	return
	
;------end SendErrorCode---------------------------------------------

;------begin TellTemperature-----------------------------------------

;This subroutine is called when the 't' command is received. Calls
;SHT11TXRX.

TellTemperature

	movlw	3
	movwf	SHT11Byte
	call	SHT11TXRX

	goto	MainLoop

;------end TellTemperature-------------------------------------------

;------begin TellHumidity--------------------------------------------

;This subroutine is called when the 'h' command is received. Calls
;SHT11TXRX.

TellHumidity

	movlw	5
	movwf	SHT11Byte
	call	SHT11TXRX

	goto	MainLoop

;------end TellHumidity----------------------------------------------

;------begin SendCRLF------------------------------------------------
;
; Send the terminating CR and LF:

SendCRLF
	movlw	13
	movwf	TXChar
	call	SendAChar
	movlw	10
	movwf	TXChar
	call	SendAChar
	return

;------end SendCRLF--------------------------------------------------

;------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 GetWindSpeed-------------------------------------------
;
; Count the number of milliseconds between transitions and return.

GetWindSpeed

; First, zero out the counters:

	movlw	0
	movwf	lo
	movwf	hi

; Next, save the value of RB1 so we can detect when a transition
; occurs:

	movf	PORTB,W
	andlw	_WIND_IN_MASK
	movwf	RegSave

; Here is where we count pulses for a specified period of time (3 sec).
; Use nested loops again.

StartWindCount

	movlw	200
	movwf	counter

WindCountLoop
	movlw	15
	movwf	counter2

WindCountInnerLoop
	call	WaitMS
	call	CountWind
	decfsz	counter2,F
	goto	WindCountInnerLoop

	decfsz	counter,F
	goto	WindCountLoop

	return

;------end GetWindSpeed----------------------------------------------

;------begin CountWind-----------------------------------------------
;
; CountWind checks for a transition on the _WIND_IN line and 
; increments the 16-bit lo/hi counter for each transition. After a
; transition it saves the new state of the _WIND_IN line in RegSave
; for the next test.

CountWind

	;did a transition occur?

	movf	PORTB,W
	andlw	_WIND_IN_MASK
	subwf	RegSave,W
	btfsc	STATUS,Z
	return

	;yes. increment the counter

	incfsz	lo,F
	goto	SaveWindReg
	incf	hi,F

SaveWindReg

	;save the new state of the line

	movf	PORTB,W
	andlw	_WIND_IN_MASK
	movwf	RegSave

	return

;------end CountWind-------------------------------------------------

;------begin TellWindSpeed-------------------------------------------
;
; call GetWindSpeed and then send the result back to the PC.

TellWindSpeed
	call	GetWindSpeed
	call	SendAsciiNum
	call	SendCRLF
	goto	MainLoop

;------end TellWindSpeed---------------------------------------------

;------begin TellWindSpeedCW-----------------------------------------
;
; call GetWindSpeed and then send the result in CW via the LED.

TellWindSpeedCW

	; wait until button is released:

	btfss	PORTB,_PBUTTON
	goto	TellWindSpeedCW

	;get the wind speed and send it in CW via the LED.

	call	GetWindSpeed
	call	SendCWAsciiNum
	goto	MainLoop

;------end TellWindSpeedCW-------------------------------------------

;------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, Idle checks to see if the button has been pressed.
; if so, it calls TellWindSpeedCW to get the wind speed and send it
; in CW via the LED.

Idle
	; is the pushbutton line low?
	btfss	PORTB,_PBUTTON
	goto	TellWindSpeedCW	;yes--tell the wind speed via CW to LED

	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

; set the _INVCW line high to turn off the LED.

	bsf	PORTA,_INVCW

; set default for the CW speed:

	movlw	0x50
	movwf	DitLen
	movlw	0x50
	movwf	DitWordLen

	call	SerSetup	;set up serial comm routines & int.

; 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 't' character
	btfsc	STATUS,Z
	goto	TellTemperature	;if t, report the temperature

	movf	RXBuff,W	;move the rx char into W
	sublw	'h'		;compare with 'h' character
	btfsc	STATUS,Z
	goto	TellHumidity	;if h, report the humidity

	movf	RXBuff,W	;move the rx char into W
	sublw	'w'		;compare with 'w' character
	btfsc	STATUS,Z
	goto	TellWindSpeed	;if w, report the temperature

	movf	RXBuff,W	;move the rx char into W
	sublw	'v'		;compare with 'v' character
	btfsc	STATUS,Z
	goto	ReportVersion	;if v, report the version number

	goto	MainLoop

;------begin Table---------------------------------------------------
;
; This is a lookup table for use in getting the right bit pattern
; to send CW based on the ASCII value of the character to be sent.

	org	0x380		;begin the table on a page boundary
Table
	addwf	PCL,F		;compute the GOTO
	retlw	_Char_0
	retlw	_Char_1
	retlw	_Char_2
	retlw	_Char_3
	retlw	_Char_4
	retlw	_Char_5
	retlw	_Char_6
	retlw	_Char_7
	retlw	_Char_8
	retlw	_Char_9
	
;------end Table-----------------------------------------------------

;------Version EEPROM------------------------------------------------

	org	0x2100
	de	"WxPIC v0.5g (c) 2003 by NK0E",0x0D,0x0A,0x00	; Version 0.5g
	end
	

