PDA

View Full Version : Clock using Instant Interrupts



PICpocket
- 13th February 2009, 01:20
I am new to PIC programming and had a question regarding using Interrupts. I had tried using the "Standard" way, but I stumbled upon Darrel's instant interrupts and they seemed much easier and more reliable, so I gave them a try. My goal is to make a programmable clock using a PIC16F627A using a Sony remote and TSOP receiver. I have made simple IR comms work and display, along with a clock, but my issue is that I was having trouble interrupting the clock function when I press my program key on the remote. It just wanted to keep timing and ignore my command, so I gave instant interrupts a try.

My goal in the end is to have my main routine looking for IR pulses, and the interrupt generating the time, but for now I just wanted to make sure I am using the interrupt right, so I am trying to use the interrupt to generate 1/10th second counts. Here is my code.


'************************************************* ***************
'* Name : UNTITLED.BAS *
'* Author : [select VIEW...EDITOR OPTIONS] *
'* Notice : Copyright (c) 2009 [select VIEW...EDITOR OPTIONS] *
'* : All Rights Reserved *
'* Date : 2/11/2009 *
'* Version : 1.0 *
'* Notes : *
'* : *
'************************************************* ***************
; Initialize your hardware first

INCLUDE "DT_INTS-14.bas" ; Base Interrupt System
INCLUDE "ReEnterPBP.bas" ; Include if using PBP interrupts
Include "modedefs.bas"

Define OSC
DEFINE LOADER_USED 1 ' Using boot-loader for initial prototype
DEFINE DEBUG_REG PORTB
DEFINE DEBUG_BIT 0
DEFINE DEBUG_BAUD 19200
define DEBUG_MODE 0 ' 1 = inverted, 0 = true



Second var byte
Minute2 VAR BYTE ' Ones column for minutes
Minute1 VAR BYTE ' Tens column for minutes
Hour2 VAR BYTE ' Ones column for hours
Hour1 VAR BYTE ' Tens column for hours
AMPM VAR BIT ' 1 when PM
Counts VAR BYTE ' Counts for timer

Second = 0
Minute1 = 0
Minute2 = 0
Hour1 = 1
Hour2 = 2
AMPM = 0

ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler TMR1_INT, ReloadTMR1, ASM, no ; MUST be first
INT_Handler TMR1_INT, _T1handler, PBP, yes
endm
INT_CREATE ; Creates the interrupt processor
ENDASM

;--- Change these to match the desired interrupt frequency -------------------
;--- See http://DarrelTaylor.com/DT_INTS-14/TimerTemplate.html for more Info.
@Freq = 10 ; Frequency of Interrupts in Hz
@Prescaler = 2 ; Timers Prescaler setting
T1CON = $10 ; $30 = Prescaler 1:8, TMR1 OFF
; $00=1:1, $10=1:2, $20=1:4, $30=1:8 -- Must match @Prescaler value

@ INT_ENABLE TMR1_INT ; enable Timer 1 interrupts
GOSUB StartTimer ; Start the Timer

Main:

IF counts = 10 then
Second = Second + 1 'Calculate time and change values

If Second = 60 then
Minute2 = Minute2 +1
Second = 0
IF Minute2 = 10 THEN
Minute1 = Minute1 + 1
Minute2 = 0
IF Minute1 = 6 then
Hour2 = Hour2 + 1
Minute1 = 0
IF Hour2 = 10 THEN
Hour1 = 1
Hour2 = 0
endif
endif
endif
endif

IF Hour1 = 1 AND HOUR2 = 3 THEN 'Roll clock over to 1 from 12
Hour1 = 0
Hour2 = 1
IF AMPM = 0 THEN 'Establish AM and PM times
AMPM = 1
endif
IF AMPM = 1 THEN
AMPM = 0
ENDIF
endif

If Hour1 = 0 then 'If hour 1 is 0 then do not display it
Hour1 = 20 'SLED-C4 will not display anything if the
Endif 'value is above 10

DEBUG "D", Hour1,Hour2,Minute1,Minute2 ' Display time
DEBUG "P",12,"~" ' Display Colon

Else

GOTO Main
ENDIF


T1handler:

Counts = Counts + 1 ; Increase the 1/10th sec count

@ INT_RETURN

;---[TMR1 reload - interrupt handler]-----------------------------------------
ASM ; Calculate Timer Reload Constant
ReloadInst = 8 ; # of Intructions used to reload timer
if ((Prescaler == 1)||(Prescaler == 2)||(Prescaler == 4)||(Prescaler == 8))
MaxCount = 65536 + (ReloadInst / Prescaler)
TimerReload = MaxCount - (OSC*1000000/4/Prescaler/Freq)
if ((TimerReload < 0) || (TimerReload > (65535-ReloadInst)))
error Invalid Timer Values - check "OSC", "Freq" and "Prescaler"
endif
else
error Invalid Prescaler
endif
ENDASM

@Timer1 = TMR1L ; map timer registers to a word variable
Timer1 VAR WORD EXT
TimerReload CON EXT ; Get the External Constant
TMR1ON VAR T1CON.0 ; Alias the Timers ON/OFF bit

;---Reload Timer1------
ASM
ReloadTMR1
MOVE?CT 0, T1CON, TMR1ON ; 1 stop timer
MOVLW LOW(TimerReload) ; 1 Add TimerReload to the
ADDWF TMR1L,F ; 1 value in Timer1
BTFSC STATUS,C ; 1/2
INCF TMR1H,F ; 1
MOVLW HIGH(TimerReload) ; 1
ADDWF TMR1H,F ; 1
MOVE?CT 1, T1CON, TMR1ON ; 1 start timer
INT_RETURN
ENDASM

;---Start/Stop controls -----
StartTimer:
Timer1 = TimerReload ; Load Timer
TMR1ON = 1 ; start timer
RETURN

StopTimer:
TMR1ON = 0 ; stop timer
RETURN


Basically the code sits at the default 12:00 and never moves. Judging from this, it would seem that it gets to the display time DEBUG statement, or I would have a display of 8888, but I'm not really sure what happens after that. I am assuming the PIC is fast enough to get through the clock routine when Counts = 10 before it is interrupted, but again, not sure where I am hanging up.

Any suggestions from some of the PIC veterans out there?

Another quick question. I don't think I saw how to disable instant interrupts from occuring when I get to a point that I dont want them?

Darrel Taylor
- 13th February 2009, 10:06
Hi PICpocket ... good name :D

I think the biggest problem is the IF block ...


IF counts = 10 then
; counts time here
ELSE
GOTO MAIN
ENDIF

T1handler:
...

When counts gets to 10, it adds to the time, then bypasses the GOTO Main and falls into the interrupt handler which has nowhere to return to.

Also, counts wasn't initialized to 0, so it may have to go all the way to 255 then rollover to 0, before making it to ten. Then once it gets to 10, you'll need to set counts to 0, or it will count all the way up again.

The timekeeping part would be better inside the interrupt handler. Depending on what else you add to the Main loop, it could lose time.

Any Software timed commands like DEBUG will be disturbed by interrupts.
To send data via RS232, you should use the USART with HSEROUT.
Or, you can disable the interrupts when sending data, but it can mess up the time if your not careful.

To disable the interrupt ...
@ INT_DISABLE TMR1_INT
To enable the int ...
@ INT_ENABLE TMR1_INT

Which brings us to the IR signal (code not included).
Trying to read that using PAUSEs or similar methods won't work reliably because the timing will be disrupted by the interrupts. You can probably use another timer for the IR reception.

hth,

Bruce
- 15th February 2009, 17:33
If you use Timer2 for your clock it leaves Timer1 free to be used with hardware capture. Then you can use capture to read your IR signal.

Here's an example tested with the SLED-C4. It only needs to test for the TMR2IF flag to keep time, so no interrupts are used. And you have quite a few instruction cycles to do other things between clock updates.



@ DEVICE LVP_OFF,WDT_OFF,MCLR_OFF,XT_OSC

DEFINE OSC 4
DEFINE DEBUG_REG PORTB
DEFINE DEBUG_BIT 0
DEFINE DEBUG_BAUD 19200
DEFINE DEBUG_MODE 0 ' SLED-C4 serial mode = true

' setup vars for clock
Time VAR WORD ' accumulates TMR2 to PR2 match counts
Minutes VAR BYTE ' minutes
Hours VAR BYTE ' hours
Match VAR PIR1.1 ' TMR2 to PR2 match interrupt flag bit

PORTB.0=1 ' make sure SLED-C4 data input pin idles high
TRISB = %00001000 ' RB3 = CCP1 capture input, rest outputs
CMCON = 7 ' disable comparators
INTCON = 0 ' not using interupts. Just monitoring int flag bits

PAUSE 250 ' let SLED-C4 power-up

Time = 0 ' clear TMR2 to PR2 match counter
Hours = 10 ' set clock starting hour here
Minutes = 03 ' set clock starting minutes here

' set SLED-C4 start time
DEBUG "D", Hours DIG 1,Hours DIG 0,Minutes DIG 1,Minutes DIG 0

' TMR2 increments from 0 until it matches the PR2 register value
' and then resets to 0 (on the next increment cycle) so place a
' value in PR2 1 cycle short of what you want. Here we want 250 for
' 60mS, so we write 249 to PR2.
PR2 = 249 ' 249 +1 extra cycle for reset = 250*16*15*1uS=60mS
Match = 0 ' clear match flag

' setup & start TMR2
T2CON = %01110110 ' 1:16 prescale, 1:15 postscale, TMR2 on

Main:
' every 60mS the TMR2IF flag is set, and this routine is entered.
' Plenty of time to do other stuff.
IF Match THEN ' has TMR2 matched PR2? (should happen every 60mS)
Match = 0 ' yes. clear TMR2 to PR2 match flag bit
Time = Time + 1 ' increment 60mS count
IF Time = 1000 THEN ' has 60 seconds (1000*60mS) passed?
Time = 0 ' yes. clear count
Minutes = Minutes + 1 ' increment minute count
IF Minutes = 60 THEN ' have 60 minutes passed?
Minutes = 0 ' yes. roll-over minutes from 59 to 00
Hours = Hours + 1 ' update hour every 60 minutes
IF Hours = 24 THEN Hours = 0 ' roll-over hours from 24 to 00
ENDIF
' print new time only once every minute
DEBUG "D", Hours DIG 1,Hours DIG 0,Minutes DIG 1,Minutes DIG 0
ENDIF
ENDIF

GOTO Main

END
It's pretty decent at keeping time. Ran for 3 hours, and it was spot-on with my PC clock to the second.

PICpocket
- 16th February 2009, 21:43
Thanks for the help both Darrel and Bruce!

I kicked myself for not seeing that I wasn't resetting the Counts in my first posting :mad: However, things are working well now, and I can read my IR signal as I want by putting the clock routine as my interrupt and the IR PULSIN command as my main (as Darrel suggested). At this point is it just some coding to recognize alarms and different programmable options that I will throw in.

I don't think I will have a problem with having enough time for tasks between interrupts since I am only doing the initial PULSIN command in my Main. If there was some kind of restriction in the interrupt routine I might have a problem them, but most of the heavy coding will be outside the main and the Interrupt routine. Though the code Bruce posted is very concise. I will try things out with both of them and see how things go.

Ill post here if I have any more questions.