Date post: | 26-Mar-2015 |
Category: |
Documents |
Upload: | son-nguyen |
View: | 100 times |
Download: | 1 times |
; 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; [email protected];; 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=16f84radix 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 arg1movlw high(arg1)movwf himovlw low(arg1)movwf loendm
dodigit macro arg1movlw high(arg1)movwf shimovlw low(arg1)movwf slocall dosubendm
; memory locations:
cblock 0x0C
BitCount ;number of bits left to send or;receive, not including start & stop;bits
RXChar ;received character while being receivedRXBuff ;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 asciilo ;lo byte for number to be converted to asciishi ;hi byte for subtractor for conversion to asciislo ;lo byte for subtractor for conversion to asciidigit ;ascii digit converted from binary. Also used as
;input to SendErrorCode
MSDelay ;register for WaitMS timing
endc
org 0x00goto 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:
Intmovwf WSave
swapf STATUS,W ;use swapf to prevent anymovwf 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,T0IEgoto 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 itgoto 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,Wmovwf STATUSswapf WSave,Fswapf WSave,W
retfie
;------end Main Interrupt Routine------------------------------------
;------Subroutine SerSetup-------------------------------------------
SerSetup
; set up the option register for internal counting, WDT disabled,; no prescaler.
clrf TMR0bsf STATUS,RP0clrwdt ;set bits in OPTION_REG tomovlw 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 enablemovwf 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 _StartRxDelaymovwf 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_INgoto 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 _BitRxDelaymovwf TMR0 ;4 cycles from read of PORTBmovlw 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 8movwf 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,2goto Sending
; check to see if we're receiving the stop bit:
movf BitCount,Fbtfsc STATUS,Zgoto 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 _BitRxDelaymovwf TMR0 ;21 cycles from start of interruptgoto Restore
; if we get to here it's because we need to check for the stop bit.
GetStopBitbtfss PORTB,_SER_IN ;is the RX line low? If so, it's notgoto Done ;the stop bit. Otherwise, set themovlw b'00000001' ;SerialReg to show a character hasmovwf SerialReg ;been receivedmovf RXChar,W ;copy the received character to RXBuff
movwf RXBuffgoto Done
; We got here because we're sending.; check to see if we're finished sending the stop bit:
Sendingbtfsc SerialReg,3goto Done
; check to see if we need to send the stop bit:
movf BitCount,Fbtfsc STATUS,Z ;18th cyclegoto 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 thebtfss STATUS,C ;least significant bit in thegoto SendZero ;carry flag.nopbsf PORTA,_SER_OUT ;if carry is set, send a one.goto EndDoBit ;PORTA,1 is set on the 24th cycle
SendZerobcf PORTA,_SER_OUT ;otherwise, send a zero. (24th cycle)nop ;nop's are for taking the same timenop ;to get to reloading TMR0 as for when
;a one is sent.
; Decrement the bit counter.
EndDoBitdecf BitCount,F
; reload TMR0 for the next interrupt, and; go to the end of the interrupt routine.
movlw _BitTxDelaymovwf TMR0 ;29th cyclegoto 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.
SendStopBitnopnopnopbsf 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 _StopTxDelaymovwf TMR0 ;27th cyclegoto Restore
; we're completely done sending or receiving. Clean up.
Done movlw b'00010000' ;set bits in INTCON to enablemovwf INTCON ;external interruptmovlw b'00000001'andwf SerialReg,F ;clear the busy bits in SerialReggoto 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 timingmovlw _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 8movwf BitCount
return
;------end SendChar--------------------------------------------------
;------begin GetAChar------------------------------------------------
GetACharcall Idlebtfss SerialReg,0 ;wait for a character to be receivedgoto GetACharbcf SerialReg,0return
;------end GetAChar--------------------------------------------------
;------begin SendAChar-----------------------------------------------
SendACharcall SendChar
WaitToFinishcall Idlebtfsc SerialReg,1 ;wait for the character to be sentgoto WaitToFinishreturn
;------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.
WaitMSmovlw 248 ;1 cyclemovwf MSDelay ;1 cyclenop ;1 cycle--these nops are added to nop ;1 cycle make the total number ofnop ;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
RepeatWaitMSnop ;1 cycledecfsz MSDelay,F ;1 cycle if not zero, 2 if zerogoto RepeatWaitMS ;2 cyclesreturn ;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 1bcf TRISA,_SHT11_DAT ;make Port A data line an outputbcf STATUS,RP0 ;switch back to bank 0
;send the Transmission Start sequence:
bsf PORTA,_SHT11_DAT ;set the data line highbsf PORTA,_SHT11_SCK ;take the clock line highbcf PORTA,_SHT11_DAT ;take the data line lowbcf PORTA,_SHT11_SCK ;take the clock line lowbsf PORTA,_SHT11_SCK ;take the clock line high againbsf PORTA,_SHT11_DAT ;set the data line high again
;load up the counter to loop through the eight bits to send:
movlw 8movwf counter
SHT11SendBitLoopbcf 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
SHT11SendZerobcf PORTA,_SHT11_DAT ;set the data line to zero
SHT11SendBitbsf 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 1bsf TRISA,_SHT11_DAT ;make Port A data line an inputbcf 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
SHT11WaitAckbtfss PORTA,_SHT11_DATgoto SHT11GotAck
;if we don't get an ack, quit, send an 'e1' for error and return.
movlw '1'movwf digitcall SendErrorCodegoto 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.
SHT11GotAckbcf 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 255movwf counter
SHT11WaitDatabtfss PORTA,_SHT11_DATgoto SHT11DataReadycall WaitMSdecfsz counter,Fgoto SHT11WaitData
;if we don't get the data, quit, send an 'e2' error and return.
movlw '2'movwf digitcall SendErrorCodegoto SHT11TXRXDone
SHT11DataReady
; get the most sig byte:
call SHT11GetByte
movf SHT11Byte,Wmovwf hicall SendAck ;acknowledge the byte
; get the least sig byte:
call SHT11GetBytemovf SHT11Byte,Wmovwf locall 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
SHT11TXRXDonereturn
;------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 8movwf counter
SHT11GetByteLoopbsf PORTA,_SHT11_SCK ;set the clock high to get the next
bitbtfss PORTA,_SHT11_DAT ;is the next bit a one?goto SHT11GetZeroBit ;no--it's a zerobsf SHT11Byte,0 ;if it's a one, set the LSB in
SHT11Byte
goto SHT11GotBit
SHT11GetZeroBitbcf SHT11Byte,0 ;set the LSB to zero in SHT11Byte
SHT11GotBitbcf PORTA,_SHT11_SCK ;set the clock line low again.decfsz counter,Fgoto SHT11GetNextBitgoto SHT11GetByteDone
SHT11GetNextBitrlf SHT11Byte,F ;move the bits over to get the next
bitgoto SHT11GetByteLoop
SHT11GetByteDone
return
;------end SHT11GetByte-----------------------------------------------
;------begin SendAck-------------------------------------------------;; send the ack. Set the data line as an output:
SendAckbsf STATUS,RP0 ;switch to bank 1bcf TRISA,_SHT11_DAT ;make Port A data line an outputbcf STATUS,RP0 ;switch back to bank 0
; now send the ack. Take the data line low.
bcf PORTA,_SHT11_DATbsf PORTA,_SHT11_SCKbcf PORTA,_SHT11_SCK
; now make the data line an input again.
bsf STATUS,RP0 ;switch to bank 1bsf TRISA,_SHT11_DAT ;make Port A data line an inputbcf 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.
SendErrorCodemovlw 'e'movwf TXCharcall SendACharmovf digit,Wmovwf TXCharcall SendACharcall SendCRLFreturn
;------end SendErrorCode---------------------------------------------
;------begin TellTemperature-----------------------------------------
;This subroutine is called when the 't' command is received. Calls;SHT11TXRX.
TellTemperature
movlw 3movwf SHT11Bytecall SHT11TXRX
goto MainLoop
;------end TellTemperature-------------------------------------------
;------begin TellHumidity--------------------------------------------
;This subroutine is called when the 'h' command is received. Calls;SHT11TXRX.
TellHumidity
movlw 5movwf SHT11Bytecall SHT11TXRX
goto MainLoop
;------end TellHumidity----------------------------------------------
;------begin SendCRLF------------------------------------------------;; Send the terminating CR and LF:
SendCRLFmovlw 13movwf TXCharcall SendACharmovlw 10movwf TXCharcall SendACharreturn
;------end SendCRLF--------------------------------------------------
;------begin SendAsciiNum--------------------------------------------;; load lo, hi with 16 bit unsigned num to send
SendAsciiNumdodigit 10000movf digit,Wmovwf TXCharcall SendAChardodigit 1000movf digit,Wmovwf TXCharcall SendAChardodigit 100movf digit,Wmovwf TXCharcall SendAChardodigit 10movf digit,Wmovwf TXCharcall SendACharmovf lo,w ; ls byte is already correctaddlw '0' ; convert to asciimovwf TXCharcall SendACharreturn ; 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'-1movwf digit
moresub incf digit,F ; increment ASCII charactermovf slo,w ; subtract current power of 10subwf lo,fmovf shi,wbtfss STATUS,Caddlw 1subwf hi,fbtfsc STATUS,C ; any carry?goto moresub ; no, keep subtracting
movf slo,w ; reverse the last subtractionaddwf lo,fmovf shi,wbtfsc STATUS,Caddlw 1addwf hi,freturn
;------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.
ReportVersionbcf STATUS,RP0clrf EEADR ;the string we want starts at the
;beginning of EEPROM memory.
GetNextVersionCharbsf STATUS,RP0bsf EECON1,RDbcf STATUS,RP0movf EEDATA,Wbtfsc STATUS,Z ;if the character in W is null, don't
;send any more.goto MainLoop
movwf TXCharcall SendACharincf 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.
Idlereturn
;------end Idle------------------------------------------------------
;------Main Program--------------------------------------------------
Main
; set up the ports as inputs and outputs as needed.
bsf STATUS,RP0 ;switch to bank 1movlw 0xFFmovwf TRISB ;make Port B inputmovlw 0x00movwf TRISA ;make Port A outputbcf 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.
MainLoopcall GetAChar ;wait for a character
movf RXBuff,W ;move the rx char into Wsublw 't' ;compare with 't' characterbtfsc STATUS,Zgoto TellTemperature ;if t, report the temperature
movf RXBuff,W ;move the rx char into Wsublw 'h' ;compare with 'h' character
btfsc STATUS,Zgoto TellHumidity ;if h, report the humidity
movf RXBuff,W ;move the rx char into Wsublw 'v' ;compare with 'v' characterbtfsc STATUS,Zgoto ReportVersion ;if v, report the version number
goto MainLoop
;------Version EEPROM------------------------------------------------
org 0x2100de "WxPIC v0.3b (c) 2002 by NK0E",0x0D,0x0A,0x00 ;
Version 0.3bend