Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Laptimer (100th of a second) TMR

Status
Not open for further replies.

brodin

New Member
Laptimer
I am building a laptimer. So far i have tried using the TMR0, with not perfect results.

PIC i am using is 16F876A.

My timer is doing the interupt every 8.192 ms.

Isn't it possible to set it up better? I want to have a interupt every 10ms exactly. Or anything that is devideble with 10. Is that possible. What configuration should i use then?


I have also looked at a I2C RTC: PCF8583
This device can manage 10ms.


What would you recomend? Is there any other better choice?
 
You want exactly 10msec and it's set for 8.192msec right now? Replace the crystal you're using right now to 81.92% of the value. For example, if it is 4.000Mhz right now, then replace it with a 3.2768Mhz crystal.
 
You want exactly 10msec and it's set for 8.192msec right now? Replace the crystal you're using right now to 81.92% of the value. For example, if it is 4.000Mhz right now, then replace it with a 3.2768Mhz crystal.

Okay! That sounds good and easy! I am using a LCD and serial communication, but i guess that will work fine if i define the new crystal in the code, right?
I found a program on the net today, it's a TMR0 calculator. Watch this image: **broken link removed**

According to this, it would be possible to change the TMR0 Offset and get exactly 2 ms. If this is possible, how do i do that then?[/quote]
 
No need to replace the crystal. But you better get a calculator ready.
All you need to do is calculate a prescaler setting that is a bit too high. For example, of calculations say you need 1:27, then choose 1:32.
Then calculate how many ticks too short your timer will get before generating an interrupt and pre-load TMR0 register with this value...

To say it simple, you can give TMR0 a 'head-start' by already loading a value before it starts ticking.

Your pic also has a TMR1 module. If it REALY needs to be exact then you better use TMR1 - its 16 Bits so much more precise
 
Okay, i have thougt about that too, but how do i set the TMR0 value?

Look at the imgage i attached. Is it the TMR0 Offset you are talking about?
 
I am using PicBasic Plus 2.0

Can i write it just like this?

asm
MOVLW 6
MOVWF TMR0
endasm

Or do i have to write 6 in a different way than decimal?
 
I am using PicBasic Plus 2.0

Can i write it just like this?

asm
MOVLW 6
MOVWF TMR0
endasm

Or do i have to write 6 in a different way than decimal?
 
There are problems tinkering with the timer0.

"Writing to TMR0, when the prescaler is assigned to Timer0, will clear the prescaler count, but will not change the prescaler assignment."

Depending on when you write to Timer0, the prescaler will reset at that instant. This makes the precise time that Timer0 resumes counting after being reset unpredictable. This makes accurate time keeping impossible.

If you don't want to change crystals, use the Capture, Compare, PWM module (CCP1). To setup:

1. Setup timer1 for 1:1 prescale and internal clock source.
2. Set CCP1 to mode 1011, "compare mode - trigger special event".
3. Assuming a 4.000Mhz crystal, load the CCPR1H:CCPR1L register pair with 2710h (decimal 10000).

This periodically sets the CCP1F flag at a 10msec rate which can be used to trigger interrupts or can be polled in software.
 
You can access the timer register straight woth PICBASIC PRO.

Study this code and see how you can modify it for your needs.... If you "pre-set" the counter to certain number so that it takes so many tick to overflow... you can generate your 10msec interrupt.....

Code:
' PicBasic Pro program that demonstrates the use of the Timer1
' interrupt for a real-time clock.  Written for the LAB-X1
' experimenter board with a 16F877

DEFINE LOADER_USED 1

' Define interrupt handler
DEFINE  INTHAND myint


wsave   VAR     BYTE $20 system
wsave1  VAR     BYTE $a0 system ' Necessary for devices with RAM in bank1
wsave2  VAR     BYTE $120 system ' Necessary for devices with RAM in bank2
wsave3  VAR     BYTE $1a0 system ' Necessary for devices with RAM in bank3
ssave   VAR     BYTE bank0 system
psave   VAR     BYTE bank0 system

TICK    VAR     BYTE bank0      ' make sure that the variables are in bank 0 if they are to be used in the interrupt handler

seconds VAR BYTE        ' Elapsed seconds
minutes VAR WORD        ' Elapsed minutes
        
minutes = 0                     ' Clear time
seconds = 0

T1CON = $01                     ' Turn on Timer1, prescaler = 1
INTCON  = $C0           ' Enable global interrupts, peripheral interrupts
PIE1 = $01                      ' Enable TMR1 overflow interrupt

 
GoTo main       ' jump over the interrupt handler and sub
 
' Assembly language interrupt handler
Asm
myint

; Uncomment the following if the device has less than 2k of code space
                ;movwf  wsave                   ; Save W
                ;swapf  STATUS, W               ; Swap STATUS to W (swap avoids changing STATUS)
                ;clrf   STATUS                  ; Clear STATUS
                ;movwf  ssave                   ; Save swapped STATUS
                ;movf   PCLATH, W               ; Move PCLATH to W
                ;movwf  psave                   ; Save PCLATH


; Set the high register of Timer1 to cause an interrupt every
; 16384 counts (65536-16384=49152 or $C000). At 4MHz, prescale
; set to 1, this equates to a tick every 16384uS.  This works
; out to about 61 ticks per second, with a slight error.  The
; error could be reduced substantially by setting the TMR1L
; register and playing with different values for the prescaler
; and the ticks per second.

        movlw   0C0h                    ; Prepare to set TMR1 high register
        movwf   TMR1H                   ; Set TMR1H to C0h
        incf    _TICK,F                 ; INCREMENT TICK COUNT
        bcf     PIR1, 0                 ; Clear interrupt flag
        

        movf    psave, W        ; restore the state of everything
        movwf PCLATH
        swapf ssave, W
        movwf Status
        swapf wsave, F
        swapf wsave, W
        retfie                          ; Return from interrupt
EndAsm

' PicBasic subroutine to update the minutes and seconds variables
get_time:
        ' Update the time when needed. The TICK variable will
        ' overflow if you don't update within 4 seconds.  This could
        ' be done in the interrupt handler, but it's easier to do
        ' it in PicBasic, and you usually want the interrupt handler
        ' to be as short and fast as possible.
        
        PIE1 = 0                                ' Mask the interrupt while we're messing with TICK
        seconds = seconds + (tick / 61) ' Add the accumulated seconds
        tick = tick // 61               ' Retain the left-over ticks
        PIE1 = $01                              ' Interrupt on again
        minutes = minutes + (seconds / 60)      ' Add the accumulated minutes
        seconds = seconds // 60 ' Retain the left-over seconds
Return                                          ' Return to the main program


 
main
' **************************************************************
' Begin program code here.  The minutes and seconds variables can
' be used in your code.  The time will be updated when you call the
' get_time routine. Disable interrupts while executing timing-critical
' commands, like serial communications.

DEFINE  LCD_DREG        PORTD
DEFINE  LCD_DBIT        4
DEFINE  LCD_RSREG       PORTE
DEFINE  LCD_RSBIT       0
DEFINE  LCD_EREG        PORTE
DEFINE  LCD_EBIT        1


ADCON1 = 7                      ' Set PORTA and PORTE for digital operation
        
Low PORTE.2                     ' Enable the LCD
        
Pause 150                       ' Pause to allow LCD to initialize
LCDOut $fe,1            ' Clear LCD

loops   VAR WORD
loops = 0


Loop:
        
        loops = loops + 1
        LCDOut $fe,$C0,"Loops Counted: ", DEC5 loops
        

        GoSub get_time  ' Update minutes and seconds
        LCDOut $fe, 2, "Time: ",DEC5 minutes, ":", DEC2 seconds ' Display the elapsed time


        GoTo loop               ' Repeat main loop
        
        End

This program is at the examples section of melabs. You can get rid of the loader section... just use the code part of it....
Focus on the part that says:
; Set the high register of Timer1 to cause an interrupt every
; 16384 counts (65536-16384=49152 or $C000). At 4MHz, prescale
; set to 1, this equates to a tick every 16384uS. This works
; out to about 61 ticks per second, with a slight error. The
; error could be reduced substantially by setting the TMR1L
; register and playing with different values for the prescaler
; and the ticks per second.

if you need more help or have questions about this just asks. :wink:
Ley us know how it goes

Ivancho
 
Wow, it doesn't look too easy... But i will have to try!


I just want to check one thing first. I have always used software interupts in PBP. How big will the difference be if i write the interupthandler in ASM?

Is it possible to write the handler in ASM but anyway, when it is doing the interupt it goes to a label i the basic code?



When i am using the software interupts i am trying to avoid pauses in the code. My main loop looks like this:

Main:

if Lap = 1 and Second > 1 then gosub NewLap
if Func = 1 then goto SelectFunc
if pcDTR=1 then goto pcConnect

goto Main



PicBasic Plus have the float point routines, so i have tried to do like this. My tmr0 is making a interupt every 8.192ms.


Code:
disable
tickint:
    
    Milli = Milli + 8.192
	    
    if Milli >= 1000 then
	  Milli = (Milli - 1000)    
	  Second = Second + 1
      if Second = 60 then
	    minute = minute + 1
	    Second = 0
	  endif
	endif

    Hundred = Milli / 10

  INTCON.2 = 0 ' Reset timer interrupt flag
resume
enable


Should this work?
 
:arrow: Are you using a 4Mhz crystal?..... Inside the PIC this 4Mhz crystal gets divided by 4, so the actual clock is 1mHz, that is 1usec for thick. Timer1 is a 16Bit counter... that meant that it can count from 0 - 65535. If it increments every 1usec the Timer will overflow (interrupt) after 65535usec have passed. That is if you let the whole timer run you will get an interrupt every 6.5535 msec.

:!: What you need to do is pre-set the timer's register with a number so that it gives you a 1msec per interrupt. So that after 10 interrupts you have your 10msec. IF you set the timer to (65535-10000=45535) then you will have an interrupt every 2msec. So every 5 interrupts you know that 10mSec have elapsed.

Have in mind that Timer1 is a 16-bit timer while Timer0 is an 8-bit timer (0-255 @ 1usec is 255usec scalar 1:1) You can also play with the scalar and put it to 1:128 but you will loose resolution and gain time.

You can also write straight to the timer resgister.... a TMR0 = 0 will reset the timer 0.

Recheck the above code by parts... remember that only this part is assembler:
Code:
' Assembly language interrupt handler 
Asm 
myint 
        movlw   0C0h                    ; Prepare to set TMR1 high register 
        movwf   TMR1H                   ; Set TMR1H to C0h 
        incf    _TICK,F                 ; INCREMENT TICK COUNT 
        bcf     PIR1, 0                 ; Clear interrupt flag 
        

        movf    psave, W        ; restore the state of everything 
        movwf PCLATH 
        swapf ssave, W 
        movwf Status 
        swapf wsave, F 
        swapf wsave, W 
        retfie                          ; Return from interrupt 
EndAsm

:?: I am not sure if your code will work right. I have never used floating point with PICs since they are not good at crunching numbers. At least not the 16F family. So I am not sure how that will work out. You are better off keeping track of hoe many interrupts have occurred, and since you know that the interrupt occurrs every x usec then you know when to increment the seconds and all that.

:idea: Say you have the interrupt set so that every 10 interrupts is 1 second (just an example) So if you do at the interrupt handler:

Seconds= Seconds + (InterruptCounter/10)

InterruptCounter/10 will only add 1 to the seconds when InterruptCounter = 10 .... other wise it will add 0. The assmebly part of the program increments the counts in the InterruptCounter.

:arrow: Using a On Interrupt will make a way bigger program.... because it puts a command after every line of code to check for the interrupt flag.....

Try creating a program that lights an LED every certain time..... with an interrupt. Then move forward into manipulating with values.

And one last thing.... you have to get in and out of the interrupt handler ASAP. You can't do gosubs not sure about goto. But the more simple you have it the easier it is.... interrupts aren't too easy to star with.... go by KISS...Keep It Simple and Stupid :lol:
 
Hey, thanks for the very good explanation! I will try to start using more ASM code then. I have only written program i basic before...

I'll give it a try!
 
I just have to ask on thing about the code example you wrote. What do i have to write in the beginning of the code to configurate the timer?

Because this is only what happends every tick, right?



This i really hard to me because i don't know a single command in ASM, i don't know what: movf means for example...
 
That makes 2 of us..... I just know vary little ASM :oops: I've never really learn it because its to tedious, and sloooow.

1. You have to define interrupt variables
2. Jump the interrupt handler
3. Write the interrupt handler
4. Define your Program Variables
5. Start of program
6. Loop to start.

So when the program runs the first time it does 1,2,4,5. Then it should stay in in 5-6 infinitly until a interupt occurs. Then it will jump to 2 and will return where it left off between 5-6.

So what happens in the interrupt part?
You increase a variable you can use in PICBASIC.... PICBASIC variables are the same as the ASM variables with an "_". So a PICBASIC variable named myVar will be call in the compile ASM: _myVar.

That is what you are playing with. By incrementing in ASM:
Code:
incf    _TICK,F                 ; INCREMENT TICK COUNT
you are actually incrementing the PICBASIC variable TICK. So in your loop (5-6) you can check if the TICK value is greater that something. You know that this "TICK" value occurs every x usec according to what you set the Timer register to be.

So before you enable the interrupts you set the TMR0 or TMR1 register to the value you want, so that the amount of counts left are the time you are looking for. Then you enable the interrupts and the timer will be set and will interrupt what you set it for, set the timer register again, increment the TICK and go back to your program.

The timer register is set at:
Code:
movlw   0C0h                    ; Prepare to set TMR1 high register 
movwf   TMR1H                   ; Set TMR1H to C0h

0C0 here is the number that the example uses to increment timer 1 by. YOu can change this number to anything. It is in HEX so keep that in mind, I am not sure if you can out a decimal value here.... but I suppose you could.

Since the TMR! (timer resgister) is 16 bit, it is devided in two 8-bit parts TMR1H for the 8 most significat bits, and TMR1L for the remaining bits.
Also you may want to set this bits before you enable your interrupts in PICBASIC by doing a TMR1H = and TMR1L = so that you can start with the interrupts right away. Then keep track of how many interrupts occur and when the TICK is at the value you want it, then you reset it in PICBASIC by TICK = 0... then the interrupts in ASM will start incrementig them by 1

Hope it helps some, could not really understand what you were asking

Ivancho
 
You can avoid using interrupts by periodically polling the interrupt flag instead of allowing it to trigger an interrupt. If the flag is set, clear the flag, execute the timer routine and then resume other tasks. You only have to poll at a rate faster than 10msec to ensure you don't miss a beat.

To a PIC16F876A running at even 4Mhz, 10msec is a long time. In 10msec, the PIC can execute 10,000 instruction steps. That's greater than the F876's program space.
 
That is exactly what the on interrupt command does......

But say you are in a pause statement......

Pause 60000... that is 60 seconds.... your interrupt will not ocurr until 1 minute has passed.... not a real interrupt.

Just something to have in mind... how to manage and how important is to interrupt.

Ivancho
 
hhh

what triggers your laptimer..???

i want also to build one that works whit radio freq.. on a small range..


TKS
 
Just how much precision do you need? You can work from any clock you want.

Say you have 20MHz crystal, 5 MHz instruction cycle:
Set TMR0 to 8 bit mode with a 4x prescalar. So you've got 1024 instruction clks, 0.2048 mS per interrupt.

So keep a 16 bit counter in addition to the mS count. On TMR0 interrupt:
counter16=counter16+2048;
if(counter16>=10000){
mSCount=mSCount+1;
counter16=counter16-10000;
}

At worst case, you're 48 instruction clocks off.
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top