PDA

View Full Version : LAB-X1 keypad/timer



Bruce
- 25th July 2010, 02:18
I wrote this for someone else wanting a dual background timer with keypad entry to enable/disable individual timed outputs. Figured maybe someone would find it handy - or learn from it.

It runs on the LAB-X1 experimenter board from MeLabs. You can find these here (http://store.melabs.com/cat/boards.html).

This responds to a keypress very quickly, and there's plenty of room for more code in the main loop.



' Name : CNTDWN_1934X.pbp
' Compiler : PICBASIC PRO Compiler 2.6
' Assembler : MPASM
' Target PIC : 40-pin 16F1934 or similar
' Hardware : LAB-X1 Experimenter Board
' Oscillator : 4MHz external crystal
' Keywords : IF THEN ELSE, FOR NEXT, LCDOUT, WHILE WEND
' Description : PICBASIC PRO program to demonstrate using Timer1 assembly
' interrupts for dual countdown timers in the background. Interrupt periods
' are approximately 100mS intervals with a 4MHz oscillator.
'
' Note: Open the 16F1934.INC file in your PBP directory, and comment out the default
' __config settings.
Asm
__config _CONFIG1, _FOSC_XT & _WDTE_SWDTEN & _PWRTE_ON & _MCLRE_ON & _BOREN_ON & _IESO_ON & _FCMEN_ON
__config _CONFIG2, _PLLEN_OFF & _LVP_OFF & _VCAPEN_OFF & _STVREN_ON & _BORV_25
Endasm

' Define LCD pins
Define LCD_DREG PORTD
Define LCD_DBIT 4
Define LCD_RSREG PORTE
Define LCD_RSBIT 0
Define LCD_EREG PORTE
Define LCD_EBIT 1

Asm
#Define Alarm PORTD ; define port for outputs in interrupt handler
#Define Out1 0 ; output1 pin on RD0
#Define Out2 1 ; output2 pin on RD1
Endasm

Define INTHAND TmrInt ' point to assembler interrupt handler

' setup vars
Tick1 Var Byte Bank0 System ' Tick counter for alarm1. 255 * 100mS MAX
Time1 Var Byte Bank0 System ' # of 100mS ticks to compare with Tick1
Tick2 Var Word Bank0 System ' Tick counter for alarm2. 65,535 * 100mS MAX
Time2 Var Word Bank0 System ' # of 100mS ticks to compare with Tick2

TmrFlag Var Byte Bank0 System ' last 2-bits indicate timer enabled or expired
State Var Bit Bank0 System ' state var to indicate LCD needs updating.
IocFlag Var INTCON.0 ' portb int-on-change flag bit
TmrFlag = %00000011 ' 1 = enabled. 0 = expired. start enabled.
State = 1

' set output "on" times below by adjusting values in Time1 & Time2
Time1 = 100 ' 100 * 100mS = 10 seconds (255 MAX)
Time2 = 600 ' 600 * 100mS = 60 seconds (65535 MAX)

' clear interrupt tick counters
Tick1 = 0 ' clear Tick1 count
Tick2 = 0 ' clear Tick2 count
Goto Init ' jump to hardware initialization routine

' ===========\ Assembly Interrupt Handler /=========== '
Asm
TmrInt ; registers in bank0 so no switching necessary
bcf PIR1,TMR1IF ; clear Timer1 interrupt flag bit
btfss TmrFlag,0 ; has this timer expired?
bra DoTick2 ; yes, jump to Tick2
incf Tick1,F ; no, increment Tick1 count
movf Time1,W ; get Time1
subwf Tick1,W
btfsc STATUS,2 ; is Tick1 = Time1?
bra Alarm1 ; yes, do alarm1
bra DoTick2 ; nope, not time, do Tick2 count

Alarm1
bcf Alarm,Out1 ; clear PORTD,0. turn off alarm1
clrf Tick1 ; clear Tick1 count, we're done
bsf State ; indicate State change
bcf TmrFlag,0 ; expire Tick1. user has to re-arm by setting TmrFlag.0

DoTick2
btfss TmrFlag,1 ; has this timer expired?
bra ExitISR ; yes, so exit
incf Tick2,F ; no, increment Tick2 low byte count
btfsc STATUS,2 ; low byte over-flow?
incf Tick2+1,F ; yes, increment Tick2 high byte
movf Time2+1,W ; get Time2 high byte
subwf Tick2+1,W
btfsc STATUS,2 ; are Time2 & Tick2 high bytes the same?
bra DoLow ; yes, check low byte
bra ExitISR ; no, exit

DoLow
movf Time2,W ; get Time2 low byte
subwf Tick2,W
btfsc STATUS,2 ; are Time2 & Tick2 low bytes the same?
bra Alarm2 ; yes, do alarm2
bra ExitISR ; nope, exit
KillTmr1
bcf T1CON,TMR1ON ; stop Timer1
clrf TMR1H ; clear high/low counts
clrf TMR1L
retfie ; return with auto context restore

Alarm2
bcf Alarm,Out2 ; clear PORTD,1. turn off alarm2
clrf Tick2 ; clear Tick2 low
clrf Tick2+1 ; clear Tick2 high, we're done
bsf State ; indicate State change
bcf TmrFlag,1 ; expire Tick2. user has to re-arm by setting TmrFlag.1

ExitISR
movf TmrFlag,W ; load enable/disable flags
sublw 0x00
btfsc STATUS,2 ; are both tick timers expired?
bra KillTmr1 ; yes, stop Timer1, we're done
bcf T1CON,TMR1ON ; no, still have one or more enabled, so stop Timer1
movlw 0xb3 ; get low byte of reload value
addwf TMR1L,F ; add to existing count
btfsc STATUS,2 ; low byte over-flow?
incf TMR1H,F ; yes, bump-up TMR1 high byte by 1
movlw 0x3c ; no, get high byte to add
addwf TMR1H,F ; add to existing TMR1 count for ~100mS interrupt intervals
bsf T1CON,TMR1ON ; re-start Timer1
retfie ; return with auto context restore
Endasm

' =============\ Main Program Start /============= '
Start:
' using IOCBF you can tap 1 or more keys very fast, and never miss
' a press. Much faster than reading the entire port. And key presses
' do not generate an interrupt. Only set flag bits in IOCBF for each
' individual input that was edge-triggered.

If IocFlag Then ' if keys SW13, 14, 15 or 16 were pressed,
State = 1 ' indicate LCD update needed
Gosub GetKeys ' get the button/buttons pressed
Endif

If State Then ' update status on LCD only after a change
Gosub GetStatus ' in State
State = 0
Endif

Goto Start ' loop forever

' =============\ Update Output Status /============= '
GetStatus:
' Print status of timed outputs
If (PORTD.0 | TmrFlag.0) = 0 Then
Lcdout $fe,2,"Out1 off "
Else
Lcdout $fe,2,"Out1 on "
Endif

If (PORTD.1 | TmrFlag.1) = 0 Then
Lcdout $fe,$c0,"Out2 off "
Else
Lcdout $fe,$c0,"Out2 on "
Endif
Return

' ===========\ Get Keypad Switch Presses /=========== '
GetKeys:
Pause 10 ' crude debounce for key press

' SW13 resets Out1
If IOCBF.4 Then ' IOCBF.4 = 1 when low edge detected on RB4
While PORTB.4 = 0 ' wait for key release
Wend '
PORTD.0 = 1 ' activate output
Tick1 = 0 ' reset Tick0 count
TmrFlag.0 = 1 ' reset enable flag
If !T1CON.0 Then T1CON.0 = 1 ' if Timer1 disabled, re-enable
Endif

' SW14 resets Out2
If IOCBF.5 Then ' IOCBF.5 = 1 when low edge detected on RB5
While PORTB.5 = 0 ' wait for key release
Wend '
PORTD.1 = 1 ' activate output
Tick2 = 0 ' reset Tick2 count
TmrFlag.1 = 1 ' reset enablg flag
If !T1CON.0 Then T1CON.0 = 1 ' if Timer1 disabled, re-enable
Endif

' SW15 disables Out1
If IOCBF.6 Then ' IOCBF.6 = 1 when low edge detected on RB6
While PORTB.6 = 0 ' wait for key release
Wend '
TmrFlag.0 = 0 ' expire Out1 time immediately
Tick1 = 0 ' reset Tick1 count
PORTD.0 = 0 ' de-activate output
Endif

' SW16 disables Out2
If IOCBF.7 Then ' IOCBF.7 = 1 when low edge detected on RB7
While PORTB.7 = 0 ' wait for key release
Wend '
TmrFlag.1 = 0 ' expire Out2 time immediately
Tick2 = 0 ' reset Tick2 count
PORTD.1 = 0 ' de-activate output
Endif
IOCBF = 0 ' reset all int-on-change bits on exit
IocFlag = 0
Return

' ===========\ Hardware Initialization Routine /=========== '
Init:
ANSELA = 0 ' porta all digital
ANSELB = 0 ' portb all digital
ANSELD = 0 ' portd all digital
ANSELE = 0 ' porte all digital

PORTD = %00000011 ' turn on alarm outputs
TRISD = 0 ' PORTD all outputs

OPTION_REG.7 = 0 ' enable PORTB pullups
PORTB = 0 ' RB3 low
TRISB = %11110111 ' RB3 = 0 for output to keys 13, 14, 15, 16
WPUB = %11110000 ' weak pull-ups on for ----> RB4, 5, 6, 7
IOCBP = 0 ' positive edge disabled
IOCBN = %11110000 ' negative edge int-on-change enabled for keys
Pause 100 ' wait for LCD to start
Lcdout $fe,1 ' clear LCD

PIR1 = 0 ' clear interrupt flags before enabling interrupts below
PIE1 = %00000001 ' Timer1 interrupt enabled
INTCON = %11000000 ' global & peripheral interrupts enabled

' and finally -- setup & start Timer1
TMR1L = $b3 ' load Timer1 for ~100mS over-flows
TMR1H = $3c
T1CON = %00010001 ' set 1:2 prescale, Timer1 on
T1GCON = 0 ' Timer1 gate control not used

Goto Start ' start main program

End