📄 thermostat.asm
字号:
;
; thermostat.asm -- a simple automatic thermostat
;
;
; source code for gpasm by Byron Jeff - Oct 12th, 2001
; Cleaned for posting - Nov 22nd, 2001
;
; This controller drives set of relays based on the temperature. The
; temp is obtained via a Dallas Semiconductor 1620 digital thermometer.
; The Fahrenheit temp (converted from Celsuis read from the DS1620) is
; displayed on a 2 digit LED display. This software is set to drive the
; Heater from 68 to 73 degrees F, and the AC from 76 to 71 degrees F
; LICENSE: This software can be used, modified, and redistributed to personal
; non-commercial use only. Please contact the author at byron@cc.gatech.edu
; for any commercial usage of this software.
;
; define PIC device type
;
; device pic16F877
;
; define config fuses
;
; __config CP=off,WDT=off,PWRT=off,OSC=hs
radix dec ; Default numbers in decimal. I use 0x for hex.
;
; include PIC register definitions, and some macros
;
include "picreg.h"
; Define identifiers for strings
cblock 0
HELLO
CRLF
endc
; Bit numbers for the DS1620 thermometer. All on port C
cblock 0
DSDATA
DSCLK
DSRESET
endc
; Define some variables - Note that some are leftovers from another project.
cblock 0x2c
tempw
dtempw
bcdhi
bcdlo
nine
vportb
vportd
txout
c1
c2
tempw1
tempw2
fahrtemp
admapoffset
admapcnt
admapval
adtarget
pscnt
psoff
column
dispcnt
nexttmr1
lastknob ; Has the last value of the knob.
lasttemp ; Has the last value of the temp sensor.
ftemphi ; Faharenheit temp
ftemplo
fshifthi ; Shift register for conversion
fshiftlo ; Shift register for conversion
display ; NOTE: This must be the last variable defined!
endc
;
; code start
;
org 0
goto init
org 4 ; Interrupt vector
bcf INTCON,T0IF ; Clear the interrupt
bsf TMR1H,7
retfie ; Leave.
getchar andlw 15 ; Only the low nybble
addwf PCL,F ; Jump table.
dt 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07
dt 0x7f,0x67,0x77,0x7c,0x39,0x5e,0x79,0x71
highbcd andlw 0x0f ; Mask off low part of nybble (already swapped)
addwf PCL,F ; Jump table
dt 0x00,0x16,0x32,0x48,0x64,0x80,0x96
hex2bcd movwf tempw ; Save it in temp register
swapf tempw,W
call highbcd ; Get the BCD value of the high nybble
movwf bcdlo ; Save for later
andlw 0xf0 ; Mask off the low nybble for later add.
movwf bcdhi
movlw 0x0f ; Mask off the high nybble of bcdlo
andwf bcdlo,F
movlw 9 ; Put a nine to subtract in the register
movwf nine
; Add the low nybbles together. Compensate if the sum is greater than 9
; Then add the result to the high nybble of the BCD of the high nybble.
; The low digit add plus the compensation may have pushed the low digit back
; into the hex range. Need to check and recompensate. For example 1F maps to
; (16 + 0F). The low digit sum is 15 hex and with the compensation is bumped
; to 1B. It needs to be compensated again by adding 6 more to get to 21 BCD.
movf tempw,W ; Get the original number
andlw 0x0f ; Mask off high part of the nybble
addwf bcdlo,W ; Add the low nybble of the high digit value.
subwf nine,F ; See if bigger than nine
btfss STATUS,C ; Less than 9
addlw 6 ; Add 6 to compensate for hex digits from add.
; This is where recompensate the low digit sum if necessary. Note that
; This one only checks the low digit of the sum since presumably there will
; be some high digit carry when adding 8+9+6 for example.
movwf bcdlo
movlw 9 ; Put a nine to subtract in the register
movwf nine
movlw 0xf ; Mask for low nybble of low digit sum
andwf bcdlo,W
subwf nine,F ; See if bigger than nine
clrw
btfss STATUS,C ; Less than 9
movlw 6 ; Add 6 to compensate for hex digits from add.
addwf bcdlo,W ; Add the low digit sum with any more comp...
addwf bcdhi,W ; Add the high part of BCD for final result.
return
init
bcf STATUS,RP0 ;register page 0
movlw 0 ; init PORTE0 to input
movwf PORTE ;initialize port E so serial is out and E0
movlw 0x0
movwf PORTD ;initialize port D so that LEDs are on
movlw 0x1a ; Make DSCLK high be default. Both LEDs on.
movwf PORTC ; Clear out PORTC.
movlw 0
movwf PORTB ; All indicator LEDs and relays off.
movlw 5
movwf T2CON
clrf column ; start display in column 0
movlw 100 ; Update display every 100 big loops
movwf dispcnt
MOVLW 0x81 ; F/32 Clock, A/D is on, Channel 1 is selected
MOVWF ADCON0 ;
BCF PIR1, ADIF ; Clear A/D interrupt flag bit
BCF INTCON, PEIE ; Disable peripheral interrupts FOR NOW!
BCF INTCON, GIE ; disable all interrupts FOR NOW!
clrf lastknob
clrf lasttemp
bsf STATUS,RP0 ;register page 1
clrf ADCON1 ; Configure A/D inputs
BCF PIE1,ADIE ; DISABLE A/D interrupts
movlw 0x80 ; PORTB all outputs except for the switch.
movwf PORTB ; register TRISB
movlw 0xe0 ; For now the low 5 bits of PORTC is output
movwf PORTC ; Set it up.
movlw 0x00 ; Whole port as an output.
movwf PORTD ; register TRISD
movwf PORTE ; Port TRISE all outputs
movlw 65
movwf T2CON ; PR2 set to 65 ticks for 52 uS delay
bcf STATUS,RP0 ;register page 0
clrf PCLATH
movlw HELLO
call printstr
movlw 10 ; This is the mapping for temp range
movwf admapoffset ; Save in the offset
; Setup the DS1620 by setting the config byte
bsf PORTC,DSRESET ; Ready to send command
movlw 0xac ; Get config command
call ds1620out ; Send it.
call ds1620in ; Get it
bcf PORTC,DSRESET ; Reset
; Now start the conversions
bsf PORTC,DSRESET ; Ready to send command
movlw 0xee ; Get config command
call ds1620out ; Send it.
bcf PORTC,DSRESET ; Reset
temploop
bsf PORTC,DSRESET ; Ready to send command
movlw 0xaa ; Get config command
call ds1620out ; Send it.
call ds1620in ; Get it
bcf PORTC,DSRESET ; Reset
; We note that the newly imported temp is also in tempw1. See if it's changed
xorwf lasttemp,W ; Are they the same
btfsc STATUS,Z ; They are not. so update
goto main ; Done for now
movf tempw1,W
movwf lasttemp ; This is now the new lasttemp
; Convert the value to Fahrenheit temp, the to decimal and print
movf tempw1,W
call ds1620toF
movwf fahrtemp ; Save the fahrenheit temp
call hex2bcd
movwf tempw1 ; Save it
call getchar ; convert to LED char
xorlw 255 ; And complement for display
movwf display+1 ; Stick in display
swapf tempw1,W ; Do the high nybble
call getchar
xorlw 255 ; And complement for display
movwf display ; Stick in display
; Now see if we need to alter the current state.
btfsc PORTB,0 ; See if A/C is on
goto acstate ; It is. Go here.
btfsc PORTB,2 ; See if heater is on
goto heatstate ; It is. Go here
; Everything is off. Test for extreme temps and turn on heater or AC to
; compensate if necessary.
movlw 77 ; Turn on AC at 77 degrees
subwf fahrtemp,W ; See if the temp matches
btfsc STATUS,C ; Continue if less than 77 degrees
goto ac_on ; Turn AC on.
movlw 69 ; Turn on heat at 68 degrees
subwf fahrtemp,W ; See if the temp matches
btfsc STATUS,C ; Done unless less than 69 degrees
goto main ;
heat_on
bsf PORTB,2 ; Turn on the indicator
NOP
bsf PORTB,3 ; And the relay
goto main
ac_on
bsf PORTB,0 ; Turn on the indicator
NOP
bsf PORTB,1 ; And the relay
goto main
acstate ; AC is on. See if it needs to go off
movlw 71 ; Turn off AC at 71 degrees so it'll run longer
xorwf fahrtemp,W ; See if the temp matches
btfss STATUS,Z ; Done if no match
goto main ;
bcf PORTB,0 ; Turn off the indicator
NOP
bcf PORTB,1 ; And the relay
goto main
heatstate ; Heat is on. See if it needs to go off.
movlw 73 ; Turn off heat at 73F so it'll run longer
xorwf fahrtemp,W ; See if the temp matches
btfss STATUS,Z ; Done if no match
goto main ;
bcf PORTB,2 ; Turn off the indicator
NOP
bcf PORTB,3 ; And the relay
main
BSF ADCON0, GO ; Start A/D Conversion
adloop
btfsc ADCON0, GO ; Wait for the conversion to finish
goto adloop
call idelay ; Wait 52uS for A/D to settle.
movf ADRESH,W ; Check the result. Only the high 7 bits.
call admap ; Map to the range
xorwf lastknob,W ; See if the values are the same
btfsc STATUS,Z ; Continue if different
goto dodisplay
movf ADRESH,W ; Get the result. Only the high 7 bits.
call admap ; Map to the range
movwf lastknob ; and save it.
addlw 65 ; 65 is the bottom temp
call hex2bcd ; Convert to decimal
call printbyte ; And print it
movlw CRLF ; And go to next line
call printstr
dodisplay
decfsz dispcnt,F ; See if it's time to update the display
goto temploop ; Nope. Top of main loop
movlw 100 ; Reset the display count
movwf dispcnt
movlw 1 ; Switch display
xorwf column,F
movf column,W ; Get the digit to display. Remember it's in
addlw display ; The high nybble.
movwf FSR
movf INDF,W
bcf PORTC,3 ; Clear both displays
NOP
bcf PORTC,4
movwf PORTD
btfsc column,W ; Turn on the current display
goto col1on
bsf PORTC,3
goto temploop
col1on bsf PORTC,4
goto temploop
dispbyte
call hex2bcd
movwf dtempw
swapf dtempw,W
call getchar
movwf INDF
incf FSR,F
movf dtempw,W
call getchar
movwf INDF
incf FSR,F
return
; Output to the DS1620. Basically a serial out routine
; above except that it's clocked.
ds1620out
movwf tempw1 ; Save the char to send
movlw 8 ; Number of bits to send
movwf c2
bsf STATUS,RP0 ;register page 1
bcf PORTC,DSDATA ; Set data for output.
bcf STATUS,RP0 ;register page 0
dsoutloop1
bcf PORTC,DSCLK ; Make the clock low
NOP ; Paranoia setting for ports.
bcf PORTC,DSDATA ; Output a 0.
btfsc tempw1,0 ; Test the next bit to send
bsf PORTC,DSDATA ; Output a 1 instead. Shorter code
rrf tempw1,F ; rotate the next bit in
bsf PORTC,DSCLK ; Now clock the bit in.
decfsz c2,F ; Output 8 bits
goto dsoutloop1
return ; All done.
; Clock in 8 bits from the DS1620
ds1620in
movlw 8 ; Number of bits to receive
movwf c2
bsf STATUS,RP0 ;register page 1
bsf PORTC,DSDATA ; Set data for input.
bcf STATUS,RP0 ;register page 0
dsinloop1
bcf PORTC,DSCLK ; Make the clock low
bcf STATUS,C ; Clear for rotate
btfsc PORTC,DSDATA ; Transfer DSDATA to the carry flag
bsf STATUS,C ; Set carry if DSDATA is a 1
rrf tempw1,F ; rotate the next bit into the top of txout
bsf PORTC,DSCLK ; Setup clock for the next bit
decfsz c2,F ; Output 8 bits
goto dsinloop1
movf tempw1,W ; Save the char received
return ; All done.
; Check for a particular config bit of the DS1620. The interesting bit
; Is set in W before calling. We continouously read the config register
; until the bit changes to 0.
ds1620check
movwf tempw2 ; tempw2 is an unused temp register
dchkloop
bsf PORTC,DSRESET ; Ready to do command
movlw 0xac ; Do a read command
call ds1620out ; Send it out
call ds1620in ; Read it in
bcf PORTC,DSRESET ; Done with command
andwf tempw2,W ; Mask only the interesting bit
btfss STATUS,Z ; Done if 0
goto dchkloop ; Loop until done
return ; All done.
; Convert a ds1620 temp in W to Fahrenheit. The idea is to use a modified
; version of F=C*1.8+32. The DS1620 temp is in 0.5C increments. To include
; The 1/2 degree C we modify the formula to F=DS*0.9+32. To do the constant
; multiply by 0.9 we use a 16 bit register and the formla of x*0.9 = x - x/8 +
; x/64 + x/128. To get the last two terms, we divide by 256 (essentially trans
; ferring the upper byte of x to the lower byte) then shift left a couple of
; times. We can then shift left 3 more times from the x/64 term to get the x/8
ds1620toF
movwf ftemphi ; Init registers
movwf fshiftlo ; Does the DS/256
clrf ftemplo
clrf fshifthi
bcf STATUS,C
; We shift 1 bit left to make DS/128. Then we add it to the temp.
rlf fshiftlo,F
rlf fshifthi,F
call ds1620add
; Shift another bit to get DS/64. Add it again.
rlf fshiftlo,F
rlf fshifthi,F
call ds1620add
; Shift 3 bits up to get DS/8. Then negate and add. Don't worry about the 1
; on the end since only the 1st bit after the binary point is checked for
; rounding.
rlf fshiftlo,F
rlf fshifthi,F
rlf fshiftlo,F
rlf fshifthi,F
rlf fshiftlo,F
rlf fshifthi,F
comf fshifthi,F
comf fshiftlo,F
call ds1620add
; Round up if the 1st bit after the binary point is 1
btfsc ftemplo,7
incf fshifthi,F
; Finally add the 32 to the whole part
movlw 32
addwf ftemphi,F
; Put the result in W and return
movf ftemphi,W
return
; Routine to add the current shift value to the temp registers.
ds1620add
movf fshiftlo,W
addwf ftemplo,F
btfsc STATUS,C ; Check for overflow
incf ftemphi,F ; Add one to hi byte if overflow
movf fshifthi,W
addwf ftemphi,F
bcf STATUS,C ; Make sure carry is clear upon leaving.
return
; Delay routines for Timer2
; idelay resets timer 2 while delay uses current settings.
idelay bcf PIR1,TMR2IF ; Clear the timer 2 flag bit
clrf TMR2 ; Clear timer2
delay btfss PIR1,TMR2IF ; Wait until timer2 flag comes up
goto delay
bcf PIR1,TMR2IF ; Clear it.
return
admap ; Maps the A/D value into smaller range
; Input: Value to map in W. offset in admapoffset
; Output: mapping in W
movwf adtarget ; Save the target
movf admapoffset,W ; Get the offset
clrf admapcnt ; Zero the count
; This is a divide by subtraction. We subtract the offset until the target
; goes negative
admloop subwf adtarget,F ; Subtract the offset
btfss STATUS,C ; Positive result. More to do
goto admcont ; Finished if negative
incf admapcnt,F ;
goto admloop
admcont movf admapcnt,W
return
end
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -