Sie sind auf Seite 1von 21

; 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
; 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
; 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.
;
; 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.
;
;----------------------------------------------------------------

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:

_SER_IN equ 0 ;Serial input line (PORTB)


_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)

; 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

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

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
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
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.

; 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 'v' ;compare with 'v' character
btfsc STATUS,Z
goto ReportVersion ;if v, report the version number

goto MainLoop

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

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

Das könnte Ihnen auch gefallen