Full Version : Shannon & Pettiss MCU Based Thermostat (ASM)
avr >>HOME & TIME & TEMPERATURE PROJECTS >>Shannon & Pettiss MCU Based Thermostat (ASM)


Admin5- 04-21-2006
An MCU-Controller Based Thermostat by Stephen Shannon & Jason Pettiss
Introduction

The goal of our final project was to design a thermostat using an Atmel AT90S8535 microcontroller. The thermostat was to compute the current temperature once per second and then send an on/off signal to a heating device which would then regulate the temperature to a desired target temperature. In addition, the thermostat was required to keep a time of day clock, as well as record the maximum and minimum temperature values that have occurred.



--------------------------------------------------------------------------------

Setup

We accomplished our goal by using an analog thermistor-based temperature sensitive circuit whose output voltage was inversely proportional to the temperature of the system. In order for the microcontroller to obtain the current temperature, the thermistor voltage was input to the ADC of the 8535 and then converted to a temperature using a simple linear equation involving fixed point mathematics.

In addition, the thermostat also required the use of both an LCD and a keyboard for the purpose of user interaction. Specifically, the LCD performed the following display functions:


display the current temperature
display the target temperature
display the min and max temperatures
display the time of day
displaying keyboard input for verification
The keyboard was used for:

setting the target temperature
setting the time of day
selecting the view min/max mode
clearing the min/max values
As you can see, this project combined many if not all of the concepts taught over the course of the semester. These include timing, use of a keyboard (including debouncing it), use of an LCD, fixed point mathematics, analog circuit design and analog-to-digital conversion.


--------------------------------------------------------------------------------

Thermistor Calibration

The first step in creating a thermistor based temperature controller is the calibration of the thermistor, that is figuring out how the amount of voltage drop across the thermistor corresponds to its temperature. To come up with this data we used the calibration setup circuit shown in Figure 1.




Figure 1. Thermistor Calibration Setup

We then heated the thermistor up from 30 to 90 degrees Celsius (since this setup did not have a safe method for cooling the thermistor to down below ambient temperature) and recorded both the voltage across the thermistor and its temperature. These values are plotted in Figure 2. We accurately knew the temperature of the thermistor because our calibration setup included a thermometer which was thermally coupled to the thermistor. Find our experimental data in Appendex 1.

One complication which arose in this part of the project was that because the thermistor is not a perfect device per se, it requires on the order of ten or so seconds for its resistance to accurately respond to a change in temperature. Because of this effect, there was a thermal lag which existed between the thermistor and the thermometer used to know its temperature. Nonetheless, we found that the way to minimize this source of error was to take calibration data both during a heat up cycle a cool down cycle. This is effective in minimizing this source of error because the thermal lag is effectively canceled out by the fact that during the heat up cycle there is a positive thermal gradient between the thermometer and the thermistor while during the cool down cycle there is a negative thermal gradient.

Therefore we were able to accurately obtain values correlating the voltage drop across the thermistor to the thermistor’s temperature. We then converted these analog voltages (Vread) to 10-bit binary values such as would appear at the output of the ADC using the following equation:

(10-bit ADC value) = (Vread/Aref)*2^10 (1)

For a reference we use Aref = 4.5V. Appendix 1 shows how these 10-bit ADC values correspond to the temperature of the thermistor (in Celsius).


The Code

The code used in implementing a thermostat included many separate routines from timing to analog-to-digital conversion. A brief description of each follows.

Timing: any useful thermostat is programmable and at the very minimum should accept a target temperature. Optionally, it could apply this target temperature for a specified time interval (for example, as in a toaster), or it could have several target temperatures which change during the course of the day (as in a building thermostat). Since this requires accurate, programmable timing, a useful and practical step is to include a clock display on the LCD. We used Timer1 with a clock division of 64, a Compare/MatchA value of 62,500, and the Clear Timer1 Counter flag set, to yield precise timing intervals of 1 second. After implementing our conversion routines, we are able use this to accurately sample temperatures at one second intervals.

Recording: the thermostat was also desired to keep minimum and maximum temperatures. This was a simple comparison routine which looked at the current min and max temperatures in memory and replaced them if the new temperature exceeded either of the current min or max. We also included a button option which allowed us to reset the min and max temperatures at any time.

Control: In order to turn on or turn off a heater, a control segment was coded which applied either a logical 1 or 0 to PortB0 depending on whether the current temperature was above or below the user input target temperature. Because the microcontroller was not capable of driving the power transistor which was necessary for the application of running a heater, we used an LED to indicate the status of PortB0. If given another fifteen minutes we could have used two transistors in a Darlington configuration to increase the current gain of the transistor switch and in this manner control the heater.

Analog-to-Digital Conversion: Using the one second time base described above, we triggered the ADC (in single step mode). The 8535’s straightforward conversion routine converted the voltage at its input to a 10-bit binary value using equation 1 above. The temperature was then calculated using the obtained ADC value to a value in degrees celsius using equation 2 below. This value was then used in the record and control routines.


Temperature© = -0.1143x(10-bit ADC value) + 129 (2)
Display routines: Display routines were also triggered once every second in one of two states: the primary state and the display hi/lo temperature mode. The 16-character LCD display has enough room to output two temperatures and the time, with room to spare as follows:



48->59C P0 16:46 (LCD 1)

Here you see the current temperature heading to the target temperature (in Celsius), an extra indicator (P0) which will be later explained, and then the current time. The colon blinks at a rate of once per second- on for one second, off for the next.

In Hi/Lo Display mode, this LCD is formatted as follows:

22L 48H P0 16 46 (LCD2)

This indicates a low of 22 degrees and a high equal to our current temperature (48 degrees from LCD1 above). The extra indicator ‘P0’ is an optional advanced feature that indicates the current programmed setting which is running.

Two additional states also exist in our basic thermostat design, one each to set the clock and the other to set the target temperature. These routines are straightforward using a two digit entry and echo routine for input. The clock uses this routine twice, once for the hours and once for the minutes (the seconds are reset to zero when the time is set).

The code is designed for an Atmel ATS908535 Microcontroller, an 8-bit 32-register RISC microcontroller with A/D converter.

Conclusion

Our goal of implementing a thermostat using an Atmel AT90S8535 microcontroller was successfully achieved. The implementation included pretty much every aspect of the course described previously as well as including some research outside of the scope of this course. However because we were forced to change projects with only two and a half weeks left until demo (due to the limitations of these microcontrollers- there were not enough ports and memory for our original choice of a project), we were not able to implement all of the options (such as programmable by time of day) that we had hoped to include. Nonetheless the project was a great success and was demoed on 5/3/99 without any hitches


Link: http://instruct1.cit.cornell.edu/courses/e...n/476final.html

CODE

.include "c:\users\jason\8535def.inc"
.device AT90S8535

;==========================================================
; Simple Thermostat
;Takes readings at 1 second intervals using the ADC
;Displays temperature, target, and time of day on
;LCD, uses keypad for the following functions:
; Set Time
; Set Target
; Reset Min/Max Temperatures
; Display Min/Max Temperatures
;
;Port A - ADC   - A0 Voltage Signal
;Port B - Power - B7 LCD V+, B0 Heater On
;Port C - KeyPd - As Per Lab 5
;Port D - LCD   - As Per Lab 5
;
;Intended for AT90S8535
;5/3/99, EE476, Jason Pettiss Steve Shannon
;==========================================================

;=========================================================
; Register definitions
;Low
.def CharCnt = r15         ;lcd
.def LCDStat = r14         ;lcd

.def LastSec = r13

.def Current = r12 ;therm (ADCDone)
.def Target = r11  ;therm (Main)
.def TimeH = r10   ;therm (t1cmpA)
.def TimeM = r9    ;therm (t1cmpA)
.def TimeS = r8    ;therm (t1cmpA)

.def AH = r7           ;mul,add
.def AL = r6           ;mul,add
.def Negative = r5     ;mul
.def CX = r4           ;mul
.def CH = r3           ;mul
.def CL = r2           ;mul

;High
;LCD
.def DelayL  = r18         ;lcd
.def DelayH  = r19         ;lcd

;KeyBd
.def Press      = r20          ;keypd
.def InitPrs    = r21          ;keypd
.def Sure       = r22          ;keypd
.def KeyPd      = r23          ;keypd
.def KeyState   = r24          ;keypd

.def Mode = r25    ;therm (Main)

.def Char    = r16 ;Always Save/Restore If Function Uses
.def Temp  = r16   ;Unless are arguments or returns from function
.def TempL = r16   ;(In which case higher level saves/restores)
.def TL = r16
.def Temp2 = r17
.def TempH = r17
.def TH = r17

.def BL = r26
.def HiTemp = r26;Main
.def BH = r27
.def LoTemp = r27;Main

.def Round = r28

.def Num = r29
.def Temp3 = r29

;==========================================================
; Constants

;Device
.equ lcdrs      = PD6
.equ lcdrw      = PD5
.equ lcde       = PD4
.equ TSTOP      = 0
.equ TCK1       = 1
.equ TCK8       = 2
.equ TCK64      = 3
.equ TCK256     = 4
.equ TCK1024    = 5
.equ TEXF       = 6
.equ TEXR       = 7

;Delay
.equ D1500H     = 22
.equ D1500L     = 210
.equ D500H      = 7
.equ D500L      = 140;?
.equ D15H       = 0
.equ D15L       = 59

;KeyState
.equ KS_IDLE        = 0
.equ KS_PRESS       = 1
.equ KS_DEPRESS     = 2
.equ KS_KEYREADY    = 3

;Key press tolerance
.equ KEYTIME    = 100
.equ KEYTOL     = 30

;Button Equivalents
.equ B_EXIT     = 1
.equ B_HILO     = 2
.equ B_HILORST  = 3
.equ B_SETTIME  = 5
.equ B_SETTGT   = 6
.equ B_LAST     = 16
.equ B_0 = 8
.equ B_1 = 1
.equ B_2 = 5
.equ B_3 = 9
.equ B_4 = 2
.equ B_5 = 6
.equ B_6 = 10
.equ B_7 = 3
.equ B_8 = 7
.equ B_9 = 11

;Mode
.equ S_NORM     = 0
.equ S_HILO     = 1
.equ S_SETTIME  = 2
.equ S_SETTGT   = 3

;ADC
.equ ADC_ENABLE = 0b10001000
.equ ADC_START  = 0b11001000

;Power
.equ PB_HOFF    = 0b11000000
.equ PB_HON = 0b11000001

;Delay 15 ms
.macro xDelay15
   ldi DelayH,D15H
   ldi DelayL,D15L
   rcall Delay
.endmacro

;==========================================================
; Interrupt Vector
.cseg
.org $0000
   rjmp reset
   reti;External Int0
   reti;External Int1
   reti;Timer2 Compare
   reti;Timer2 Overflow
   reti;Timer1 Capture
   rjmp t1cmpA;Timer1 CompA
   reti;Timer1 CompB
   reti;Timer1 Overflow
   rjmp t0int;Timer0 Overflow
   reti;Serial Txfer Complete
   reti;UART Rx
   reti;UART UDRE
   reti;UART Tx
   rjmp ADCdone;ADC Conversion Complete
   reti;EEPROM Rdy
   reti;Analog Comparator

;==========================================================
; Reset
reset:  
   cli        ; Disable Interrupts
   ldi Mode,S_NORM

   ldi Temp, LOW(RAMEND)  ; initialize stack
   out SPL, Temp
   ldi Temp, HIGH(RAMEND)
   out SPH, Temp

   ldi Temp, TSTOP    ; Timer 0 off
   out TCCR0, Temp    ; Stop Timer

   ldi Temp, 0b00000001   ; Enable Timer0 overflow & Timer1 A Match
   out TIMSK, Temp

   ldi Temp, TCK1024;CK/1024
   out TCCR0, Temp

   ldi Temp, 255
   out TCNT0, Temp

   ldi Temp,0b1000000 ;Bit 0 is analog signal
   out ADMUX,Temp
   ldi Temp,ADC_ENABLE;Enable ADC
   out ADCSR,Temp

   sei        ; Enable Interrupts

   ldi KeyState,KS_IDLE
  ; Delay 1.5 seconds
   ldi DelayH, D1500H
   ldi DelayL, D1500L
   rcall Delay

  ; Power up LCD
   ldi Temp, 0xFF  
   out PORTB, Temp

  ; Delay 1.5 seconds
   ldi DelayH, D1500H
   ldi DelayL, D1500L
   rcall Delay

   rcall LCDInit

   rcall LCDClr
   rcall LCDReady
   ldi DelayH, D1500H
   ldi DelayL, D1500L
   rcall Delay

;For 1 sec Timer1:
;TCCR1B = 0b00001011 (CK/64)
;OCR1AH = 244
;OCR1AL = 36    (62500)

   ldi Temp,244
   out OCR1AH,Temp
   ldi Temp,36
   out OCR1AL,Temp

   ldi Temp, 0b00001011   ; CTC1 & CK/64
   out TCCR1B, Temp

   ldi Temp, 0b00010001   ; Enable Timer0 overflow & Timer1 A Match
   out TIMSK, Temp

;==========================================================
; Main
;A
main:
   clr Press
   clr KeyPd
   clr Current
   clr Target

  ;Init time to 1:25:40pm
   ldi Temp,13
   mov TimeH,Temp
   ldi Temp,25
   mov TimeM,Temp
   ldi Temp,40
   mov TimeS,Temp

   clr LastSec

  ;Reset Min/Max temps
   ldi HiTemp,0
   ldi LoTemp,99

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;==========================================================
;MainMenu (S_NORM)
MainMenu:
   ldi Mode,S_NORM
   rcall GetKey
   cpi KeyPd,B_HILO
   breq disphilo
   cpi KeyPd,B_HILORST
   breq hilorst
   cpi KeyPd,B_SETTIME
   breq settime
   cpi KeyPd,B_SETTGT
   breq settgt
rjmp MainMenu
hilorst:
   ldi HiTemp,0
   ldi LoTemp,99
rjmp MainMenu

;==========================================================
;Display Hi/Lo Temperatures (S_HILO)
disphilo:
   ldi Mode,S_HILO
   rcall GetKey
   cpi KeyPd,B_EXIT
   breq MainMenu
rjmp disphilo

;==========================================================
;Set Time (S_SETTIME)
settime:
   ldi Mode,S_SETTIME
   rcall LCDClr
   rcall GetTwoDigits
   mov TimeH,Temp
   ldi Temp,':'
   rcall LCDPut
   rcall GetTwoDigits
   mov TimeM,Temp
   clr TimeS
rjmp MainMenu

;==========================================================
;Set Target (S_SETTGT)
settgt:
   ldi Mode,S_SETTGT
   rcall LCDClr
   rcall GetTwoDigits
   mov Target,Temp
rjmp MainMenu
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

;==========================================================
;Update display if 1 second has elapsed
;==========================================================
DispTest:
   cp LastSec,TimeS
   breq dsptstout
   rcall StdDisplay
   mov LastSec,TimeS
dsptstout:
ret

;=================================================
;StdDisplay Routine
;cc->ttC Pp hh:mm S_NORM
;1234567812345678
;hhH llL    hh:mm S_HILO
;1234567812345678
;=================================================
StdDisplay:
   push Temp
   push Temp2

   rcall LCDClr

   cpi Mode,S_HILO
   breq stddisphilo

stddispnorm:
   mov Temp,Current
   rcall TwoDigit
   
   ldi Temp,'-'
   rcall LCDPut
   ldi Temp,'>'
   rcall LCDPut

   mov Temp,Target
   rcall TwoDigit

   ldi Temp,'C'
   rcall LCDPut
   ldi Temp,' '
   rcall LCDPut
   ldi Temp,'P'
   rcall LCDPut
   ldi Temp,0;Program
   ldi Temp2,'0'
   add Temp,Temp2
   rcall LCDPut
   ldi Temp,' '
   rcall LCDPut
   rjmp stddisptime

stddisphilo:
   mov Temp,HiTemp
   rcall TwoDigit
   ldi Temp,'H'
   rcall LCDPut
   ldi Temp,' '
   rcall LCDPut
   mov Temp,LoTemp
   rcall TwoDigit
   ldi Temp,'L'
   rcall LCDPut
   ldi Temp,' '
   rcall LCDPut
   ldi Temp,' '
   rcall LCDPut
   ldi Temp,' '
   rcall LCDPut
   ldi Temp,' '
   rcall LCDPut

stddisptime:
   mov Temp,TimeH
   rcall TwoDigit

   sbrc TimeS,0
   rjmp stdoddsec
   ldi Temp,':'
   rjmp stdsec
stdoddsec:
   ldi Temp,' '
stdsec:
   rcall LCDPut

   mov Temp,TimeM
   rcall TwoDigit

   pop Temp2
   pop Temp
ret

;=================================================
;Wait for a key press (main 'message loop').
;While waiting, update display once a second.
;=================================================
GetKey:
   cpi Mode,S_NORM
   breq dstst
   cpi Mode,S_HILO
   breq dstst
   rjmp dsnotst
dstst:
   rcall DispTest
dsnotst:
   cpi KeyState,KS_KEYREADY
   brne GetKey
   ldi KeyState,KS_IDLE
ret
   
;=================================================
; Temp -> xx decimal on lcd
;=================================================
TwoDigit:
   push Temp2
   push Temp3
   
   mov Temp2,Temp
   clr Temp
tens:
   cpi Temp2,10
   brlo tensout
   inc Temp
   subi Temp2,10
   rjmp tens
tensout:
   ldi Temp3,'0'
   add Temp,Temp3
   rcall LCDPut
ones:
   mov Temp,Temp2
   add Temp,Temp3
   rcall LCDPut

   pop Temp3
   pop Temp2
ret

;=================================================
; xx decimal from keypad -> Temp (& Echo)
;=================================================
GetTwoDigits:
   push Temp2
   push Temp3

   clr Temp3
getnext:
   rcall GetKey
   cpi KeyPd,B_0
   breq got0
   cpi KeyPd,B_1
   breq got1
   cpi KeyPd,B_2
   breq got2
   cpi KeyPd,B_3
   breq got3
   cpi KeyPd,B_4
   breq got4
   cpi KeyPd,B_5
   breq got5
   cpi KeyPd,B_6
   breq got6
   cpi KeyPd,B_7
   breq got7
   cpi KeyPd,B_8
   breq got8
   cpi KeyPd,B_9
   breq got9
   rjmp getnext
got0:
   ldi Temp2,0
   rjmp gotnumber
got1:
   ldi Temp2,1
   rjmp gotnumber
got2:
   ldi Temp2,2
   rjmp gotnumber
got3:
   ldi Temp2,3
   rjmp gotnumber
got4:
   ldi Temp2,4
   rjmp gotnumber
got5:
   ldi Temp2,5
   rjmp gotnumber
got6:
   ldi Temp2,6
   rjmp gotnumber
got7:
   ldi Temp2,7
   rjmp gotnumber
got8:
   ldi Temp2,8
   rjmp gotnumber
got9:
   ldi Temp2,9
   rjmp gotnumber
gotnumber:
   push Temp2
   ldi Temp,'0'
   add Temp2,Temp
   push Temp
   push Temp2
   push Temp3
   mov Temp,Temp2
   rcall LCDPut
   pop Temp3
   pop Temp2
   pop Temp
   inc Temp3
   cpi Temp3,2
   breq gottwodigits
   rjmp getnext

gottwodigits:  
   ldi Temp3,10
   pop Temp
   pop Temp2
doingtens:
   cpi Temp2,0
   breq donetens
   dec Temp2
   add Temp,Temp3
   rjmp doingtens
donetens:

   pop Temp3
   pop Temp2
ret

;=================================================
; t1cmpA fires at 1 second intervals
;=================================================
t1cmpA:
   push Temp
   in Temp,SREG
   push Temp
   push Temp2
   push Temp3

  ;Start an ADC conversion
   ldi Temp,ADC_START
   out ADCSR,Temp

  ;Time routine: if seconds gets to 60, set it to
  ;zero and increment minutes, and so on.
   mov Temp,TimeS
   inc Temp
   cpi Temp,60
   brne t1cmpAout
   clr Temp
   mov Temp2,TimeM
   inc Temp2
   cpi Temp2,60
   brne t1cmpAminout
   clr Temp2
   mov Temp3,TimeH
   inc Temp3
   cpi Temp3,24
   brne t1cmpAhrout
   clr Temp3
t1cmpAhrout:
   mov TimeH,Temp3
t1cmpAminout:
   mov TimeM,Temp2
t1cmpAout:
   mov TimeS,Temp

   pop Temp3
   pop Temp2
   pop Temp
   out SREG,Temp
   pop Temp
reti

;=================================================
;ADC Conversion:
;=================================================
ADCdone:
   push Temp
   push TempH
   in Temp,SREG
   push Temp
   push AL
   push AH
   push CL
   push CH
   push CX
   push Negative

   push BL
   push BH

  ;RawValue is 10 bit voltage in
  ;range ~200 to 1024.  This is mapped
  ;to a range of 12 to 99 degrees celsius
  ;with a linear function.

   in TempL,ADCL
   in TempH,ADCH;Read
   mov AL,TempL
   mov AH,TempH;A = Raw Value
   ldi BL,0xBE
   ldi BH,0xE2;B = -29.26
   rcall mul
   ldi BL,0
   ldi BH,130;B = 129
   rcall add

  ;Temp = RawValue * -29.26 + 129
  ;Temp = Raw * -0.1143 + 129

   mov TempL,AL
   mov TempH,AH

  ;Round 0.5+ to 1 and 0.49- to 0
   cpi TempL,0x80
   brlo skiproundup
roundup:
   inc TempH
skiproundup:
   mov Current,TempH

   pop BH
   pop BL

  ;If our measured temperature is
  ;bigger than max, or less than min,
  ;change max or min reading.

   cp Current,HiTemp
   brlo adcnomax
   mov HiTemp,Current
adcnomax:
   cp Current,LoTemp
   brge adcnomin
   mov LoTemp,Current
adcnomin:

  ;If we're less than our target, heater
  ;should be on.  Else turn it off.

   cp Current,Target
   brge noheater
   ldi Temp,PB_HON
   rjmp heaterset
noheater:
   ldi Temp,PB_HOFF
heaterset:  
   out PORTB,Temp

   pop Negative
   pop CX
   pop CH
   pop CL
   pop AH
   pop AL
   pop Temp
   out SREG,Temp
   pop TempH
   pop Temp
reti

;Following code reused from Labs 2-6.
;==========================================================
;Prototypes
;Return = Function (Arguments);DestroysReg;Subcalls

;Delay(DelayH, DelayL);;
;t0int ();KeyPd,Press,Sure,InitPrs,KeyState;ReadKeyPd
  ;Saves/Restores: Temp,Temp2,ZL,ZH,r0,SREG

;LCDClr ();CharCnt,Temp;lcdcmd
;LCDInit ();Temp,Temp2 ;lcdcmd
;LCDWait ();Temp,lcdstat
;LCDPut (Temp, &CharCnt);;lcdwait,lcdcmd
;LCDMsgPut (ZL, ZH);ZL,ZH,r0;lcdput
;LCDCmd ();Temp;lcdwait
;LCDReady ();Temp,CharCnt,ZL,ZH;lcdmsgput

;Press = ReadKeyPd ();Temp,Temp2,ZL,ZH,r0

;A = add ( A+B );;
;A = sub ( A-B );;
;A = mul ( A*B );C,CX,Negative;

;==========================================================
;Delay
;
Delay:
   cpi DelayH,0
   brne Delay
   cpi DelayL,0
   brne Delay
   ret

;==========================================================
; Timer 0 Overflow ISR
;
t0int:
   push Temp
   push Temp2
   in Temp, SREG
   push Temp
   push ZL
   push ZH
   push r0

;Keyboard Routine
;To get key, wait for KeyState==KS_KEYREADY
;Key is KeyPd
;Set KeyState <- KS_IDLE when done

   cpi KeyState,KS_IDLE
   breq t0idle
   cpi KeyState,KS_PRESS
   breq t0press
   cpi KeyState,KS_DEPRESS
   breq t0depress
   rjmp t0keyout

t0idle:
   rcall ReadKeyPd
   cpi Press,0
   breq t0keyout
   mov KeyPd,Press
   ldi InitPrs,KEYTIME
   clr Sure
   ldi KeyState,KS_PRESS
   rjmp t0keyout

t0press:
   rcall ReadKeyPd
   cp Press,KeyPd
   brne t0cont
   inc Sure
t0cont:
   dec InitPrs
   brne t0keyout
  ;done!
   cpi Sure,KEYTOL
   brge t0pressed
  ;was not a valid press
   clr KeyPd
   clr Press
   ldi KeyState,KS_IDLE
   rjmp t0keyout
t0pressed:
  ;valid press, wait for depress
   ldi KeyState,KS_DEPRESS
   ldi InitPrs,KEYTIME
   rjmp t0keyout
t0depress:
   rcall ReadKeyPd
   cpi Press,0
   brne t0keyout
   dec InitPrs
   brne t0keyout
   ldi KeyState,KS_KEYREADY
t0keyout:

;Delay Routine
   cpi DelayH,0
   brne t0hinonzero
   cpi DelayL,0
   breq t0allzero
   rjmp t0out
t0hinonzero:
   cpi DelayL,0
   brne t0out
   dec DelayH
   rjmp t0out
t0out:
   dec DelayL
t0allzero:

   ldi Temp, 255
   out TCNT0, Temp
   
   pop r0
   pop ZH
   pop ZL
   pop Temp
   out SREG, Temp
   pop Temp2
   pop Temp
reti

;==========================================================
;LCDClr
LCDClr:
   clr CharCnt
   ldi Temp,1
   rcall LCDCmd
   xDelay15
ret

;==========================================================
;       Initialize LCD module
;lcd0
LCDInit:
   ldi     Temp,0         ;Setup port pins
   out     PORTD,Temp     ;Pull all pins low
   ldi     Temp,0xFF      ;All pins are outputs
   out     DDRD,Temp

;       LCD specs call for 3 repetitions as follows
   ldi Temp2,0
lcd0loop:
   inc Temp2
   xDelay15
   ldi     Temp,3         ;Function set
   out     PORTD,Temp     ;to 8-bit mode
;   xDelay15;or nop
   nop
   sbi     PORTD,lcde     ;Toggle enable line
   cbi     PORTD,lcde
   cpi Temp2,3
   brne lcd0loop

   ldi     Temp,2         ;Function set, 4 line interface
   out     PORTD,Temp
;   xDelay15;or nop
   nop
   sbi     PORTD,lcde     ;Toggle enable line
   cbi     PORTD,lcde

   ldi     Temp,0b11110000;Make 4 data lines inputs
   out     DDRD,Temp
   
;       Finally,
;       At this point, the normal 4 wire command routine can be used
   ldi     Temp,0b00100000;Function set, 4 wire, 1 line, 5x7 font
   rcall   LCDCmd
   ldi     Temp,0b00001100;Display on, no cursor, no blink
   rcall   LCDCmd
   ldi     Temp,0b00000110;Address increment, no scrolling
   rcall   LCDCmd
ret

;==========================================================
;   Wait for LCD to go unbusy
;lcd1
LCDWait:
   ldi Temp,0xF0  ;Make 4 data lines inputs
   out DDRD,Temp
   sbi PORTD,lcdrw;Set r/w pin to read
   cbi PORTD,lcdrs;Set register select to command
lcd1loop:
   sbi PORTD,lcde ;Toggle enable line
   cbi PORTD,lcde
   in  lcdstat,PIND   ;Read busy flag
  ;Read, and ignore lower nibble
   sbi PORTD,lcde ;Toggle enable line
   cbi PORTD,lcde
   sbrc    lcdstat,3  ;Loop until done
   rjmp    lcd1loop
ret

;==========================================================
;       Send character data in wreg to LCD
;lcd2
LCDPut:
   push    Temp           ;Save character
   rcall   LCDWait        ;Wait for LCD to be ready
   ldi     Temp,0xFF      ;Make all port D pins outputs
   out     DDRD,Temp

   pop     Temp           ;Get character back
   push    Temp           ;Save another copy
   swap    Temp           ;Get upper nibble
   andi    Temp,0x0F      ;Strip off upper bits
   out     PORTD,Temp     ;Put on port
   sbi     PORTD,lcdrs    ;Register select set for data
   nop
   nop
;   nop
   sbi     PORTD,lcde     ;Toggle enable line
   cbi     PORTD,lcde

   pop     Temp           ;Recall character
   andi    Temp,0x0F      ;Strip off upper bits
   out     PORTD,Temp     ;Put on port
   sbi     PORTD,lcdrs    ;Register select set for data
   nop
   nop
;   nop
   sbi     PORTD,lcde     ;Toggle enable line
   cbi     PORTD,lcde
  ;cbi    PORTD,lcdrs    ;--

   ldi     Temp,0xF0      ;Make 4 data lines inputs
   out     DDRD,Temp

   inc CharCnt
   mov Temp,CharCnt
   cpi Temp,8
   brne    lcd2out
   ldi Temp,0xC0  ;Set address to last 8 chars    
   rcall   LCDCmd
lcd2out:
ret

;==========================================================
;       Write Message From Memory To LCD
;lcd3
LCDMsgPut:
lcd3nextchar:
   lpm
   tst R0
   breq    lcd3out
   mov Char,R0
   rcall   LCDPut
   adiw    ZL,1
   rjmp    lcd3nextchar
lcd3out:
   ret

;==========================================================
;       Send command in wreg to LCD
LCDCmd:
   push    Temp           ;Save character
   rcall   LCDWait        ;Wait for LCD to be ready
   ldi     Temp,0xFF      ;Make all port D pins outputs
   out     DDRD,Temp
   pop     Temp           ;Get character back
   push    Temp           ;Save another copy
   swap    Temp           ;Get upper nibble
   andi    Temp,0x0F      ;Strip off upper bits
   out     PORTD,Temp     ;Put on port
   nop                    ;wait for data setup time
   sbi     PORTD,lcde     ;Toggle enable line
   cbi     PORTD,lcde

   pop     Temp           ;Recall character
   andi    Temp,0x0F      ;Strip off upper bits
   out     PORTD,Temp     ;Put on port
   nop
   sbi     PORTD,lcde     ;Toggle enable line
   cbi     PORTD,lcde

   ldi     Temp,0xF0      ;Make 4 data lines inputs
   out     DDRD,Temp
ret

;==========================================================
;  LCD Ready
LCDReady:
   clr CharCnt
   ldi Char, 0x20
   ldi ZH, high(msgReady*2)
   ldi ZL, low(msgReady*2)
   rcall LCDMsgPut
ret

;=============================================; text data
msgReady:
   .db " --- Ready --- ", 0x00
msgTime:
   .db "Set Time: ",0x00
msgTgt:
   .db "Target: ",0x00

;==========================================================
;The following table is used to convert raw button-press high/low
;values to a sequence number. Invalid codes caused by multiple
;button presses are ignored.
keytbl: .db 0b11101110, 0b11101101, 0b11101011, 0b11100111
       .db 0b11011110, 0b11011101, 0b11011011, 0b11010111
       .db 0b10111110, 0b10111101, 0b10111011, 0b10110111
       .db 0b01111110, 0b01111101, 0b01111011, 0b01110111

;==========================================================
; Keypad Checking Routine
ReadKeyPd:
   ldi Temp,0x0F          ;set lower four lines to output
   out DDRC, Temp
   ldi Temp, 0xF0     ;and turn on the pullups on the inputs
   out PORTC, Temp
   nop;Need some time for the pullups to
   nop;charge the port pins
   nop
   nop
   in  Temp, PINC     ;read the high nibble
   mov Temp2, Temp      ;and store it (with zeros in the low nibble)
   ldi Temp, 0xF0     ;set upper four lines to outputs
   out DDRC, Temp
   ldi Temp, 0x0F     ;and turn on pullups on the inputs
   out PORTC, Temp
   nop;As before wait for the pin to charge
   nop    
   nop
   nop
   in  Temp, PINC     ;read the low nibble
   or  Temp2, Temp      ;combine to make key code
   
  ;At the point the raw key code should have exactly one zero each in
  ;the lower and upper nibbles. Any other number of zeros indicates
  ;either no-button pressed or multiple-button pressed.

  ;Now search the table for a match to the raw key code
  ;and exit with a button number
   ldi ZL, low(keytbl*2)      ;table pointer in FLASH
   ldi ZH, high(keytbl*2)     ;so convert from word to byte addr
   ldi Press, 0
tbllp:
   lpm                    ;get the table entry
   cp      Temp2, r0        ;match?
   breq    foundit
   inc     Press         ;if not, have we exhaused the
   cpi     Press, 0x10   ;table
   breq    illegal
   adiw    ZL, 1          ;if not, get the next table entry
   rjmp    tbllp
foundit:
   inc Press
   rjmp keyout
illegal:
   clr Press
keyout:
ret

;======================================================
; Math Routines
;======================================================

;======================================================
; 8.8 Fixed-Point Signed Add
;Arguments: A,B
;Returns: A
add:
   clc
   add AL,BL
   adc AH,BH
ret

;======================================================
; 8.8 Fixed-Point Signed Subtract
;Arguments: A,B
;Returns: A
sub:
   clc
   sub AL,BL
   sbc AH,BH
ret

;======================================================
; 8.8 Fixed-Point Signed Multiply
;Arguments: A,B
;Destroys: C,CX,Negative
;Returns: A
mul:
  ;Save Registers
   push Round
   push Temp
   push Temp2
   push BL
   push BH

   clr Temp2
   clr Negative
   clr Round
   clr CX
   clr CH
   ldi Temp,1
   mov CL,Temp

  ;Ensure Positive arguments
multstb:
   cpi BH,0
   brge multsta
  ;b is negative, make positive
   com Negative
   com BH
   com BL
   clc
   add BL,CL
   adc BH,CH
multsta:
   cp AH,Temp2
   brge multstout
  ;a is negative, make positive
   com Negative
   com AH
   com AL
   clc
   add AL,CL
   adc AH,CH;+0 unless carry

multstout:
   clr CL

mulnextround:
  ;Test B(0)
   sbrs BL,0
   rjmp mullowbitoff
mullowbiton:
  ;On: CH = CH + A
   clc
   add CH,AL
   adc CX,AH
mullowbitoff:
  ;Shift B Right
   clc
   asr BH
   ror BL
  ;Shift C Right
   clc
   asr CX
   ror CH
   ror CL
  ;Loop 16 Times
   inc Round
   cpi Round,16
   brne mulnextround

   mov AH,CH
   mov AL,CL

  ;Imbue correct signage
   cp Negative,Temp2
   breq mulout
   com AH
   com AL
   clr CH
   ldi Temp,1
   mov CL,Temp
   clc
   add AL,CL
   adc AH,CH

mulout:
  ;Restore Registers
   pop BH
   pop BL
   pop Temp2
   pop Temp
   pop Round
ret





Forumer™ is Voted #1 Free Forum Hosting provider
Build your own community today with the largest message board hosting company.