Timer1 ....a possible simple use for it

Jeremy Leach

Senior Member
Here's some thoughts about using Timer1:

I've just trawled some old posts and Hippy's excellent discussion at <A href='http://www.hippy.freeserve.co.uk/picaxert.htm' Target=_Blank>External Web Link</a>

I'm thinking of using Timer1 in a far less complex way than making a software RTC. I want it just to determine the elapsed time of running code - and I think it could be very handy for this and surprised no-one else has got into this before (unless I've missed it in the archives).

I'm NOT any expert with this Timer1 business, and am only going with the info that Hippy has come up with. But it seems that with a Prescaler of 3 you get Timer1 overflowing at 524ms (clock 4MHz).

It strikes me that half a second is a lot of code execution time and this Timer could be very useful to make code cycle at a precise rate without using interrupts.

What I mean is:

<code><pre><font size=2 face='Courier'>
1. Set Timer to 0

2. Block of Code executes. This takes a
variable elapsed time because of branches,
different values being evaluated etc etc.
Lets say the elapsed time varies up to
ElapsedMax.

3. Read Timer and calculate the ElapsedTime.

4. Pause for a time equal to ElapsedMax -
ElapsedTime. (Probably use Pulseout to a
dummy pin to get a precise Pause).

5. Goto 1
</font></pre></code>

The calculation of the Pause duration at step 4 ensures that the overall execution time from step 2 to step 4 remains precisely constant.

As long as the ElapsedTime is less than 524ms then Timer1 won't overflow and will give the ElapsedTime reading. I suppose there is also scope for going a bit beyond 524ms too, because you might be able to deduce when overflow has occured and still calculate the ElapsedTime.

Now and again we get posts regarding bit-banging and generating low frequency PWM signals from code. This could possibly be one use of this technique to ensure precise output frequency.

RexLan and myself are using Pulsin to measure and display engine RPM. One thing we've noticed is that the display refresh rate increases as the RPM increases - because the Pulsin duration decreases. So this technique could also be used to fix the refresh rate.


Edited by - jeremy leach on 29/12/2006 07:48:57
 

BeanieBots

Moderator
This looks very useful. Something I frequently require is an external trigger to provide consistent loop timing.
Have you actually got anywhere with this practice? The sort of problem I picture is when you need a time interval which is not an exact multiple of 524mS.
Please continue with this and post your results. It would certainly be of great value to me and I'm sure many others.
 

Jeremy Leach

Senior Member
This is all theory at the moment. The way I see it is that you can time up to 524ms with Timer1.

But lets say the block of code Executes in the range ElapsedTimeMin to ElapsedTimeMax. Then if you get a reading back from Timer1 <i>below </i> ElapsedTimeMin you can safely deduce that there must have been overflow, so all you need to do is add 524ms onto the calculated Elapsed time. So this trick will extend the range of Timer1 a bit.

Also, there's nothing to stop you using this technique multiple times on different sections of code in a bigger loop - ensuring that the overall loop time is constant.

Also, another general use for Timer1 is when we need to time code in general. I've used my scope a lot for doing this, but it might be better to just insert a temporary piece of timer code to report the result (although on small snippets of code being timed, the overhead of the code to get the timer value and work out the time might become very significant so this may not work too well).

Also, this could have a use in PID applications where ideally there is a very regular sample interval.

Edited by - jeremy leach on 28/12/2006 22:22:03
 

hippy

Ex-Staff (retired)
A problem with measuring elapsed time using Timer1 is that it's an always incrementing 16-bit register which has to be read as two 8-bit bytes using PEEK, so one can never capture the 16-bit value in one hit. Read the MSB first and the LSB will have incremented before reading that. Read the LSB first and the MSB may have overflowed before being read.

This is really the reason for ending up with the 524mS interrupt flag polling as there's little else which can be easily determined with any certainty of regular accuracy. Altering the pre-scaler does give shorter times, and Timer2 can give a wider range of rates up to 64mS but not all useful timeouts are easily achieved ( example, 50mS isn't possible ).

Timer2 is a single-byte timer so there may be more joy to be had in measuing timing with that than with Timer1 if the periods are short enough ( &lt; 4mS )

Using Timer1 interrupt polling and jumping through hoops to determine elapsed time isn't convenient and I've been reading the datasheets ( but not done any experimenting yet ). It appears that Timer1 in Compare Mode can be used to create a periodic timeout, but more importantly it can be used to toggle CCP1 ( a PORTB output ) which could then be fed back in as an interrupt to be handled as a genuine PICAXE interrupt with SETINT.

Assuming accurate oscillator and ignoring latency, one should be able to force an 'interrupt:' routine entry every N mS.

I'll have a go tomorrow ( if no one races off to try it ) and see if I can get the PICAXE toggling a LED every 524mS using nothing but on-chip timers plus interrupt flag polling or 'interrupt:' to toggle CCP1CON&lt;0&gt; mode.
 

Jeremy Leach

Senior Member
See what you mean Hippy. I'm being bold saying this, but I've got a feeling the reading delays can be overcome.

I'm thinking along the lines of reading the MSB and LSB values twice in quick succession then working backwards and predicting what the full 16bit value was.
 

Jeremy Leach

Senior Member
Ok, here goes, this is my idea for reading the 16bit Timer1 value, hopefully without error:

1. Peek LSB into LSB1

2. Peek LSB into LSB2

3. Peek MSB into MSB3

4. Now analyse the results !

The difference between LSB2 and LSB1 tells you the elapsed time between Peek instructions. Lets call the elapsed time PeekDuration.

We know that the full 16-bit Timer1 value overflows at 65536, which equates to 524ms. So the LSB will keep overflowing every [524/256]ms = 2ms. My guess is that PeekDuration will be equivalent to far less than 2ms. This means that if LSB2 is <i>less </i> than LSB1 we know the LSB overflowed between step1 and step2, so we can still work out PeekDuration:

<code><pre><font size=2 face='Courier'>
Symbol PeekDuration = w0
If LSB2 &lt; LSB1 Then
PeekDuration = 255 - LSB1 + LSB2
Else
PeekDuration = LSB2 - LSB1
Endif
</font></pre></code>

So, we now know PeekDuration. The only reason for working out this value is because we can now say that the time between Step 2 and 3 must equal PeekDuration too.

(Another guess is that the execution times of peek instructions are fixed and don't vary on the values being peeked. The only variation - and Hippy knows more here - is where the tokens are in the program code, but If the Peek instructions are adjacent I'm guessing any difference will be negligible.)


Now at step3, the MSB value was peeked into MSB3. If we add PeekDuration onto LSB2 we can deduce the LSB value for the instant the MSB3 value was peeked. We can call this LSB3. So LSB3 = LSB2 + PeekDuration.

It doesn't matter if there's an overflow on LSB3 because in theory this has already been captured in MSB3.

So this gives a final, deduced, 16-bit Timer1 value, at Step3 comprising of MSB3 and LSB3.

We can now also deduce the Timer1 value at Step1 (which might be more useful) by simply realising that it equals Timer1Step3 - (2 * PeekDuration).

One possible danger I can see is where the MSB has only just been incremented at Step3 but - due to inevitable slight inaccuracy in the calcs - the calculated values don't predict an increment (or vice versa). A possible way round this would be to take an initial MSB reading ...but I'm still chewing this over !

Also, Step1 is probably not necessary, because PeekDuration can be worked out then used as a constant. To be really clever it can be worked out by using Peeks in exactly the same code position as steps 2 and 3, therefore eliminating any issues with different PeekDuration for different token positions.


Edited by - jeremy leach on 29/12/2006 09:36:09
 

Jeremy Leach

Senior Member
This is just at 'concept' stage and I wanted to explain the reasoning behind it (and to myself). A code dump probably wouldn't do that ! (I can understand that guy down the pub...;-)

Plus I haven't got to grips with the actual Peek locations for Timer1 and the code to set it up - I'm reading up about that.


Edited by - Jeremy Leach on 29/12/2006 10:55:43
 

Dippy

Moderator
Blooming cheek. I chuckled, you're probably right, I've only ever used timer interrupts in another language on F88. There is a little useful info on reading Timer1 in the Data Sheets - albeit in assembler.

But I was wondering if you've considered using the Capture CCP1CON&lt;3:0&gt; method if possible - just for reading? Noting overwrite and interrupt caveats. Is it possible with PICAXE to switch Timer1 between Synchronous and Asynchronous modes?
 

Jeremy Leach

Senior Member
Ok, here's my initial stab at some code. This is untested, but hopefully might work. I've added a lot of explanation in the code.

Basically I'm hoping it should allow the use of Timer1 to do the things I've mentioned, albeit with a few caveats.

I've certainly stretched my brain cells to reliably predict the Timer1 Value in GetTimer1Value !! I can already see some improvements/developments, but this should give the gist of what I'm going on about.

<code><pre><font size=2 face='Courier'>
#rem
###########################################
# #
# PICAXE Timer1 Code Module #
# #
# Code version :1a #
# Status :UNTESTED #
# Date :December 2006 #
# PICAXE Type :18X (or poss other) #
# Editor Software :5.07 #
# Author: Jeremy Leach #
# #
###########################################

INTRODUCTION:
=============

This 119-byte code 'module' is to allow use of Timer1 to time code execution.
Timer1 has a 16-bit value stored as two bytes in T1MSB and T1LSB. These
bytes are peek/pokable.

After resetting the Timer1 value to 0, it will overflow (65536) in 524ms
if the clock frequency is 4MHz. This code assumes a 4MHz clock and that you
will be timing events less that 524ms in duration.

USING THE CODE
==============

Ths code should be inserted as a 'module' into your existing program.
The routines can then be called as required:

InitialiseTimer1: You must call this before using the Timer.

ResetTimer1: This sets the Timer1 value to 0, so will typically
be used before starting to time a block of code.

GetTimer1Value: This obtains the Timer1 value the moment the routine
is called.

PauseForDifference: This pauses for the time difference between Timer1
value and a user-definable constant DesiredTotalDurationW
This routine is used to obtain constant cycle-times
in code.

T1CON CONTROL REGISTER FORMAT:
==============================

The following information is from the Microchip Pic16F88 datasheet and
describes the Timer1 control register format:

| Bit7 | Bit6 | Bit5 | Bit4 | Bit3 | Bit2 | Bit1 | Bit0 |
| Unused | T1RUN | T1CKPS1| T1CKPS0| T1OSCEN| /T1SYNC| TMR1CS | TMR1ON |

T1RUN:
Timer1 System Clock Status bit
1 = System clock is derived from Timer1 oscillator
0 = System clock is derived from another source

T1CKPS1 &amp; T1CKPS0:
Timer1 Input Clock Prescale Select bits
11 =1:8 Prescale value
10 =1:4 Prescale value
01 =1:2 Prescale value
00 =1:1 Prescale value

T1OSCEN:
Timer1 Oscillator Enable Control bit
1 = Oscillator is enabled
0 = Oscillator is shut-off (the oscillator inverter is turned off to
eliminate power drain)


/T1SYNC:
Timer1 External Clock Input Synchronization Control bit
TMR1CS = 1:
1 = Do not synchronize external clock input
0 = Synchronize external clock input
TMR1CS = 0:
This bit is ignored. Timer1 uses the internal clock when TMR1CS = 0.

TMR1CS:
Timer1 Clock Source Select bit
1 = External clock from pin RB6/AN5/PGC/T1OSO/T1CKI (on the rising edge)
0 = Internal clock (FOSC/4)

TMR1ON:
Timer1 On bit
1 = Enables Timer1
0 = Stops Timer1

#endrem

'TIMER1 CONSTANTS
'================

'Control bits in T1CON:
Symbol T1RUN = %01000000 'Timer1 System clock is derived from Timer1 Osc
Symbol T1CKPS1 = %00100000 'These S1 and S0 values mean prescaler of 1:8
Symbol T1CKPS0 = %00010000 'These S1 and S0 values mean prescaler of 1:8
Symbol T1OSCEN = %00000000 'Timer1 Oscillator is shut-off
Symbol T1SYNC = %00000000 'This bit is ignored because TMR1CS is 0
Symbol TMR1CS = %00000000 'Timer1 clock source is internal clock.
Symbol TMR1ON = %00000001 'Timer1 is enabled.

'Register addresses:
Symbol RAM_T1CON = $10
Symbol RAM_T1MSB = $0F
Symbol RAM_T1LSB = $0E

'Other
Symbol DesiredTotalDurationW = 500 'ms. Put your value in here if required.

'TIMER1 VARIABLES
'================

Symbol MSB0 = b0
Symbol LSB1 = b1
Symbol LSB2 = b2
Symbol PeekDuration = b3
Symbol Timer1ValueW = w2 'b4 and b5
Symbol LSBPred3 = b4
Symbol MSB3 = b5

Symbol T1CON = b6

Symbol HowCloseTo255 =b7
Symbol TempW = w4 'b8 and b9
Symbol PauseDuration = w5' b10 and b11

'TIMER1 ROUTINES
'===============

InitialiseTimer1:
'Set the control bits in the T1CON register

T1CON = T1RUN | T1CKPS1 | T1CKPS0 | T1OSCEN | T1SYNC | TMR1CS | TMR1ON
Poke RAM_T1CON, T1CON
Return

ResetTimer1:
'Poke the LSB and MSB registers with 0.

Poke RAM_T1MSB,0
Poke RAM_T1LSB,0
Return

PauseForDifference:
'Pause for a duration such that:
'Timer1Value + PauseDuration = DesiredTotalDurationW
'where all values are in milliseconds.

PauseDuration = DesiredTotalDurationW - Timer1ValueW
Pause PauseDuration
Return

GetTimer1Value:
'Get the 16-bit Timer1 value expressed in milliseconds
'ON-EXIT: The value is stored in Time1ValueW. This is the predicted
'Timer1 value at the start of this routine.

'The code takes account of the fact that the Timer1 value is constantly
'changing between Peeks. The end result is a prediction but should be
'accurate.

Peek RAM_T1MSB, MSB0 'This is the point where the final Time1Value
'is predicted.
Peek RAM_T1LSB, LSB1
Peek RAM_T1LSB, LSB2
Peek RAM_T1MSB, MSB3

'Calculate PeekDuration
If LSB2 &lt; LSB1 Then
'The LSB value has definitely overflowed

PeekDuration = 255 - LSB1 + LSB2
Else
'LSB value hasn't overflowed
PeekDuration = LSB2 - LSB1
Endif

'Predict LSB3
LSBPred3 = LSB2 + PeekDuration

'Issues can arise where the LSB3 value is about to overflow or has
'just overflowed. This is because there may be a mis-match between the
'predicted and actual MSB value. If we get this wrong then there
'could be up to 255 error in Timer1Value.So need to carefully check
'for this condition and ammend the value of LSBPred3 if necessary.

'Calculate how close the predicted value is to 255
If LSBPred3 &gt; LSB2 Then
HowCloseTo255 = 255 - LSBPred3
Else
HowCloseTo255 = LSBPred3
Endif

If HowCloseTo255 &lt; 20 Then
If LSBPred3 &lt; LSB2 And MSB3 = MSB0 Then
'LSB3 overflow HAS been predicted but LSB3 HASN'T
'overflowed in practice, so set it's predicted value to 255
LSBPred3 = 255
Endif

If LSBPred3 &gt; LSB2 And MSB3 &lt;&gt; MSB0 Then
'LSB3 overflow has NOT been predicted but LSB3 HAS
'overflowed in practice, so set it's predicted value to 0
LSBPred3 = 0
Endif
Endif

'Now calculate the predicted value of Timer1 at the start of the routine.
TempW = 3 * PeekDuration
Timer1ValueW = Timer1ValueW - TempW

'Now convert the value to milliseconds: With a 4MHz clock, and a pre-scaler
'of 1:1, Timer1 increments every 1uS. With a pre-scaler of 1:8 it
'increments every 8uS. So the time in uS = 8 * Timer1ValueW. So the
'time in mS = 8 * Time1ValueW / 1000 = Timer1ValueW / 125.

Timer1ValueW = Timer1ValueW / 125
Return
</font></pre></code>

Edited by - jeremy leach on 29/12/2006 14:27:30
 

hippy

Ex-Staff (retired)
Dippy : Yes, the PICAXE can switch between Synchronous and Asynchronous modes, but for me the light hasn't come on yet - What is the difference ? Is Synchronous externally clocked ( ie, counting ) and Asynchronous internally clocked ( ie, free-running ) ? I'm gong to have to print that section off rather than keep scrolling up and down the PDF.

Jeremy : You could be onto something with double readings at the start and end and some adjusting afterwards. If you get an 'awkward case' you could always just repeat the measurement until you get numbers easier to deal with.
 

Dippy

Moderator
As I say, I've only used hardware interrupts, with my other hat on, but your mention of the CCP module caught my eye.

My (limited) undrstanding is that asynchronous is an external crystal clock type thing as the Timer 1 can work and interrupt in low power mode lending itself to RTC with a 32kHz nominal xtal. If the PIEs and PIRs etc can be twiddled to generate a software interrupt this could produce some very useful and interesting code.

The CCP module needs a synchronous internal source and won't work asynchronous. I'm not sure if PICAXE is fast enough to make the most use of the Capture/Compare? Sections 9.1 and 9.2 of 16F87/88 manual. My PICAXE experience isn't good enough to say whether or not feasible - I'm just throwing ideas into the hat.


I too am exhausted with scrolling... but the results of this could be very interesting and bloomin useful for rotary shaft encoder speed measurements. (e.g. Jeremy's weather-station / tachometer).
 

hippy

Ex-Staff (retired)
Jeremy : You'll kick yourself when you read this, and I've got my own matching bruises ...

Stop the timer, zero it, start the timer, run your timed code, stop the timer when done, read the 16-bit timer value at your leisure !

Use x1 pre-scaler and it's 1uS accurate ( ignoring PEEK/POKE memory position variances ) and all self-contained.

I'm going to be posting some example code later.
 

Jeremy Leach

Senior Member
Stop the timer .... of course!!

Oh my, why didn't I think of that. Well, lets put it down to a learning experience. Actually this is really good because it will make the code much simpler, and even more useful, and no prediction. Still, I was rather pleased with the prediction idea. Oh well...
 

hippy

Ex-Staff (retired)
Well some good things have come out of today's playing, all found as example programs here ...

homepage.ntlworld.com\the.happy.hippy\picaxe\timers.zip

Use of the CCP worked brilliantly, allowing an arbitrary period timer to be configured and overflow detected by polling (TIMERD) or by the 'interrupt:' routine (TIMERE).

The Instruction Timing example is TIMERH.

Also, 16F88.BAS, a list of all SFR's and bit allocations for anyone wanting to tuck into the SFR functions.

The only things left which haven't been touched on for the 18X which look feasible are high-speed SPI out and I2C Slave in.

Edited by - hippy on 30/12/2006 01:24:59
 

tarzan

Senior Member
This appears to work; maybe Hippy you could take a closer look please?
<code><pre><font size=2 face='Courier'>#rem
28X Picaxe
This is a demonstration of timer1 doing background task while 28X picaxe performs LED_test.bas
08M pin0 is used for counter signal and is powered by 28X portc pin5
28X external input0 for counter/TMR1
portc pin2 high on match with external counter Timer1
compare mode write %1000 @ location 0x17
write value @ locations 0x16 &amp; 0x15
#endrem

'28X code
eeprom ($0,$0F,$F0,$3C, $C3,$18,$3C,$7E, $FF,$E7,$C3,$81)
eeprom ($0,$03,$06,$0C, $30,$60,$C0,$60, $30,$0C,$06,$03, 0)

'Define symbols
symbol i = b0
symbol j = b1
symbol k = b2

'Define time delays
symbol const1 = 50
symbol const2 = 150

Symbol CCP1CON = 0x17
Symbol CCPR1H = 0x16
Symbol CCPR1L = 0x15

Symbol compare = %1000

init:

pause 100
poke $0F, 0
poke $0E, 0
poke $10, $03 'Configure T1CON for Timer1 On, External Clock
poke CCPR1H, 1 '256
poke CCPR1L, 0 'Put value in to be compared to.
Poke CCP1CON, compare
dirsc = %00100100 'Set TRISC &lt;2&gt; Output Enable
pinsc = %00100000 'power for 08M


Loop_: For i = 0 To 7 'bar climbing
pause const1
high i
Next i

For i = 0 To 7 'and disappearing...
pause const1
low i
Next i

Let j = 1 'single light moving up
For i = 0 To 7
Let pins = j
pause const1
Let j = j + j
Next i

Let j = 64 'and rolling back
GoSub roll_back

Let j = 3 'bar climbing up
For i = 0 To 6
Let pins = j
pause const1
j=j+j|1
Next i

j = 127 'and rolling back
GoSub roll_back

For i = 0 To 24 'some more effects
read i, j
Let pins = j
pause const2
Next i

goto loop_ 'loop forever!

roll_back:
For i = 0 To 6
Let pins = j
pause const1
j = j / 2
Next i
Return

'08M code

pause 1000
for w0 = 0 to 256
pulsout 0,1
next w0 </font></pre></code>
 

Jeremy Leach

Senior Member
That's a good days work Hippy !!

TimerH is the one I had in mind. Only 48 bytes, and could be less without the Sertxd. I like the way you've used bit0, bit1 etc - much better.

I personally think this is a significant set of routines. We can actually time code (or even individual instructions) to high precision, which could be very useful in optimising execution speed, and could lead to all sorts of analysis (if we have the time !).

It also allows code loops to be regulated precisely by using a balancing pause statement. Plus I'm sure a lot more.

One question : Can Timer1 be used on any PICAXE ?

Edited by - jeremy leach on 30/12/2006 07:43:15
 

hippy

Ex-Staff (retired)
Timer1 available on all PICAXE's, except PICAXE-08 because that doesn't support PEEK and POKE.
 

Jeremy Leach

Senior Member
Hey Hippy, just had another thought regarding your TimerH code...

If you also inspect the overflow flag then you can time up to <i>twice </i> the duration. i.e 524ms * 2 = 1048ms for a 4MHz clock, and 524ms for an 8MHz clock.

Example :
<code><pre><font size=2 face='Courier'>
#Rem
#########################################################
# TIMER1 'MODULE'. J.Leach December 2006 #
#########################################################

This module enables you to time events in milliseconds using Timer1.

These routines have been written following discovery of the detail
by the Happy Hippy.

#endrem

'RAM Addresses:
Symbol RAM_PIR1 = $0C
Symbol RAM_T1CON = $10
Symbol RAM_TMR1 = $0E

'PIR1:
Symbol ADIF_BIT = bit6
Symbol RCIF_BIT = bit5
Symbol TXIF_BIT = bit4
Symbol SSPIF_BIT = bit3
Symbol CCP1IF_BIT = bit2
Symbol TMR2IF_BIT = bit1
Symbol TMR1IF_BIT = bit0

'T1CON:
Symbol T1RUN_BIT = bit6
Symbol T1CKPS1_BIT = bit5
Symbol T1CKPS0_BIT = bit4
Symbol T1OSCEN_BIT = bit3
Symbol NOT_T1SYNC_BIT = bit2
Symbol T1INSYNC_BIT = bit2
Symbol TMR1CS_BIT = bit1
Symbol TMR1ON_BIT = bit0

'General Timer1 variable
Symbol Timer1ValueW = w5
Symbol T1CON = b0
Symbol PIR1 = b0

InitialiseTimer1:
'Sets the T1CON control register. Only needs to be called once, before
'using Timer1.

T1CKPS1_BIT = 1'Divide by 8 Pre-Scaler
T1CKPS0_BIT = 1'Divide by 8 Pre-Scaler
T1OSCEN_BIT = 0'External LP Oscillator Not Enabled
NOT_T1SYNC_BIT = 0'Ignored if Internal Clock
TMR1CS_BIT = 0'Internal Clock
TMR1ON_BIT = 1'Timer 1 Enabled

Poke RAM_T1CON,T1CON 'Set T1CON
Return

ResetAndStartTimer1:
'Clear Timer1 and the interrupt(overflow) flag. Start Timer1.

'Set both LSB and MSB of Timer1 to 0
Poke RAM_TMR1,0,0

'Clear Timer1 Interrupt
Peek RAM_PIR1,PIR1
TMR1IF_BIT = 0
Poke RAM_PIR1,PIR1

'Start Timer1
Peek RAM_T1CON,T1CON
TMR1ON_BIT = 1
Poke RAM_T1CON,T1CON
Return

GetTime:
'Stop Timer1, Load the result into Timer1ValueW and convert it
'to milliseconds (Assumes an 8MHz clock)

'Stop Timer1
Peek RAM_T1CON,T1CON
TMR1ON_BIT = 0
Poke RAM_T1CON,T1CON

'Load result
Peek RAM_TMR1,Word Timer1ValueW

'Convert result to milliseconds.
'With a 8MHz clock, and a pre-scaler of 1:1, Timer1 increments every 0.5uS.
'With a pre-scaler of 1:8 it increments every 4uS. So the time in uS =
'4 * Timer1ValueW. So the time in mS = 4 * Time1ValueW / 1000 =
'Timer1ValueW / 250.
Timer1ValueW = Timer1ValueW / 250

'Check for Timer1 overflow and if it has overflowed add on another 262ms
'onto Timer1ValueW

Peek RAM_PIR1,PIR1
If TMR1IF_BIT = 1 Then
Timer1ValueW = Timer1ValueW + 262
Endif
Return
</font></pre></code>

Edited by - jeremy leach on 01/01/2007 09:25:53
 

Jeremy Leach

Senior Member
Ok, I've done some testing with my Timer1 module code. The module and the Testcode are at the end of this post. The results are interesting. I've just used a pause command and used Timer1 to time the Pause. Here are the results....

<code><pre><font size=2 face='Courier'>
Paused = 0ms. Measured = 1ms.
Paused = 10ms. Measured = 11ms.
Paused = 20ms. Measured = 20ms.
Paused = 30ms. Measured = 30ms.
Paused = 40ms. Measured = 40ms.
Paused = 50ms. Measured = 49ms.
Paused = 60ms. Measured = 59ms.
Paused = 70ms. Measured = 69ms.
Paused = 80ms. Measured = 78ms.
Paused = 90ms. Measured = 88ms.
Paused = 100ms. Measured = 98ms.
Paused = 110ms. Measured = 107ms.
Paused = 120ms. Measured = 117ms.
Paused = 130ms. Measured = 127ms.
Paused = 140ms. Measured = 136ms.
Paused = 150ms. Measured = 146ms.
Paused = 160ms. Measured = 156ms.
Paused = 170ms. Measured = 166ms.
Paused = 180ms. Measured = 175ms.
Paused = 190ms. Measured = 185ms.
Paused = 200ms. Measured = 195ms.
Paused = 210ms. Measured = 204ms.
Paused = 220ms. Measured = 214ms.
Paused = 230ms. Measured = 224ms.
Paused = 240ms. Measured = 233ms.
Paused = 250ms. Measured = 243ms.
Paused = 260ms. Measured = 253ms.
Paused = 270ms. Measured = 262ms.
Paused = 280ms. Measured = 272ms.
Paused = 290ms. Measured = 282ms.
Paused = 300ms. Measured = 291ms.
Paused = 310ms. Measured = 301ms.
Paused = 320ms. Measured = 311ms.
Paused = 330ms. Measured = 320ms.
Paused = 340ms. Measured = 330ms.
Paused = 350ms. Measured = 340ms.
Paused = 360ms. Measured = 349ms.
Paused = 370ms. Measured = 359ms.
Paused = 380ms. Measured = 369ms.
Paused = 390ms. Measured = 378ms.
Paused = 400ms. Measured = 388ms.
Paused = 410ms. Measured = 398ms.
Paused = 420ms. Measured = 407ms.
Paused = 430ms. Measured = 417ms.
Paused = 440ms. Measured = 427ms.
Paused = 450ms. Measured = 436ms.
Paused = 460ms. Measured = 446ms.
Paused = 470ms. Measured = 456ms.
Paused = 480ms. Measured = 465ms.
Paused = 490ms. Measured = 475ms.
Paused = 500ms. Measured = 485ms.
Paused = 510ms. Measured = 495ms.
Paused = 520ms. Measured = 504ms.
</font></pre></code>

When the test is repeated, EXACTLY the same results are obtained. This is what you would expect because this is all being done internal to the PICAXE and based on the clock.

However what is strange is that the timed result is less than the paused value. In fact it's consistently around 97% of the Pause. The maths I've used in the timer routines are straightforward. If anything I would expect the measured value to be slightly higher, due to the small overhead of commands that are executed related to the Timer1 routines.

It may <i>possibly </i> point to a slight error in the Pause command - the Pauses are 3% more than what they should be?

It's good to know that the Timer1 routines work in practice though, and me and RexLAn are now incorporating this Timer1 module into our Tach display routines to ensure regular sampling and regular display refresh !

<code><pre><font size=2 face='Courier'>
#rem
#########################################################
# TIMER1 TEST CODE #
#########################################################
#endrem

Symbol TimePeriod = w2
Symbol TempW = w3
Symbol Timer1ValueW = w5

Initialise:
SetFreq M8
Gosub InitialiseTimer1

Main:
For TimePeriod = 0 To 520 Step 10 'in milliseconds
'Because using M8 need to double for Pause command
TempW = TimePeriod * 2

'Reset Timer1
Gosub ResetAndStartTimer1

'Simulate a delay dues to a block of code executing...
Pause TempW

'Get the elspased time
Gosub GetTime

'Transmit the results
SerTxd (&quot;Paused = &quot;,#TimePeriod,&quot;ms. Measured = &quot;,#Timer1ValueW,&quot;ms.&quot;,CR,LF)
Next
Goto Main

#Rem
#########################################################
# TIMER1 'MODULE'. J.Leach December 2006 #
#########################################################

This module enables you to time events in milliseconds using Timer1.

At 8MHz clock the maximum time that can be meaured is 524ms

These routines have been written following discovery of the detail
by the Happy Hippy.

#endrem

'RAM Addresses:
Symbol RAM_PIR1 = $0C
Symbol RAM_T1CON = $10
Symbol RAM_TMR1 = $0E

'PIR1:
Symbol ADIF_BIT = bit6
Symbol RCIF_BIT = bit5
Symbol TXIF_BIT = bit4
Symbol SSPIF_BIT = bit3
Symbol CCP1IF_BIT = bit2
Symbol TMR2IF_BIT = bit1
Symbol TMR1IF_BIT = bit0

'T1CON:
Symbol T1RUN_BIT = bit6
Symbol T1CKPS1_BIT = bit5
Symbol T1CKPS0_BIT = bit4
Symbol T1OSCEN_BIT = bit3
Symbol NOT_T1SYNC_BIT = bit2
Symbol T1INSYNC_BIT = bit2
Symbol TMR1CS_BIT = bit1
Symbol TMR1ON_BIT = bit0

'General Timer1 variable
'Symbol Timer1ValueW = w0 'Set to any suitable Word variable, but not w0.
Symbol T1CON = b0
Symbol PIR1 = b0

InitialiseTimer1:
'Sets the T1CON control register. Only needs to be called once, before
'using Timer1.

T1CKPS1_BIT = 1'Divide by 8 Pre-Scaler
T1CKPS0_BIT = 1'Divide by 8 Pre-Scaler
T1OSCEN_BIT = 0'External LP Oscillator Not Enabled
NOT_T1SYNC_BIT = 0'Ignored if Internal Clock
TMR1CS_BIT = 0'Internal Clock
TMR1ON_BIT = 0'Timer 1 Disabled

Poke RAM_T1CON,T1CON 'Set T1CON
Return

ResetAndStartTimer1:
'Clear Timer1 and the interrupt(overflow) flag. Start Timer1.

'Set both LSB and MSB of Timer1 to 0
Poke RAM_TMR1,0,0

'Clear Timer1 Interrupt
Peek RAM_PIR1,PIR1
TMR1IF_BIT = 0
Poke RAM_PIR1,PIR1

'Start Timer1
Peek RAM_T1CON,T1CON
TMR1ON_BIT = 1
Poke RAM_T1CON,T1CON
Return

GetTime:
'Stop Timer1, Load the result into Timer1ValueW and convert it
'to milliseconds (Assumes an 8MHz clock)

'Stop Timer1
Peek RAM_T1CON,T1CON
TMR1ON_BIT = 0
Poke RAM_T1CON,T1CON

'Load result
Peek RAM_TMR1,Word Timer1ValueW

'Convert result to milliseconds.
'With a 8MHz clock, and a pre-scaler of 1:1, Timer1 increments every 0.5uS.
'With a pre-scaler of 1:8 it increments every 4uS. So the time in uS =
'4 * Timer1ValueW. So the time in mS = 4 * Time1ValueW / 1000 =
'Timer1ValueW / 250.
Timer1ValueW = Timer1ValueW / 250

'Check for Timer1 overflow and if it has overflowed add on another 262ms
'onto Timer1ValueW

Peek RAM_PIR1,PIR1
If TMR1IF_BIT = 1 Then
Timer1ValueW = Timer1ValueW + 262
Endif
Return
</font></pre></code>



Edited by - jeremy leach on 01/01/2007 09:38:54
 

bflavery

New Member
Timer1 again - <b>as a SYSTEM TICK TIMER </b>

In the later posts to this thread the emphasis has been on execution timing measurements. The original post also raised the need for code to execute at precise repetition rates.

My requirement was for a &quot;system tick&quot; counter. I was used to using system timers on other software, so of course I implemented it on the Picaxe. A slow tick was sufficient for my requirement, and the accuracy needed was fairly slack also. So the Timer1 524 mSec cycle was made to drive a 1-second tick. The code below is (extracted) from a working project.

Your own code has a 1 per second &quot;tick&quot; flag and a 1 per minute flag available to trigger certain functions. It can also read elapsed seconds (23 bit half-second accuracy) for up to 45 days. Or it can easily read time in minutes (16 bit). Long term precision is dependent mainly on stability/accuracy of Picaxe resonator. Accuracy is another story. Each counted &quot;second&quot; is really 1048 mSec. And each counted &quot;minute&quot; is 64 such &quot;seconds&quot;.

Still, that is fine for my current jobs, and maybe it can suit you.

Your code must be written in callable routines (I called them &quot;drivers&quot; below) so that NOTHING holds onto the processor time in a waiting mode. No SERIN. No long PAUSE, etc. I found that I could get about 90 loops per second through the &quot;Executive Loop&quot; of the code with empty user driver routines. In practice with a fully coded 28X project, I usually get about 15 to 20 passes per second (&quot;LoopsPerTik&quot;). If your code holds the processor in any one loop to a time seriously over 524 mSec, you would start missing tick detections. Depending on your job, that might be a serious problem or not.

Anyway, here is my code:
<code><pre><font size=2 face='Courier'>
'======================================================================
' 45 day system tick timer for Picaxe
' 1 second / tick. No external parts.

' By: Brian Lavery (Queensland Australia)
' Inspired by:
' The Happy Hippy (http://www.hippy.freeserve.co.uk/picaxert.htm)
' homepage.ntlworld.com/the.happy.hippy/picaxe/timers.zip
'======================================================================

#picaxe 28X
#freq m4
' I used 4 Mhz 28X, which uses PIC chip 16F873.
' Same timer function should work on most Picaxes, I believe.
' A quick look at the several PIC documents suggests that the Timer1 function is similar.
' (Assumes Picaxe designers don't in future modify the internal code to use PIC's Timer1)

' Enhanced compiler 5.0.7

' Defined values found from the PIC document for 16F873 chip:
symbol PIC_TMR1H = 0x0F ' (MSB) PIC Timer1 data (counter) register
symbol PIC_T1CON = 0x10 ' PIC Timer1 Control Register

' GP Picaxe bit variables that must be PRESERVED at all times in your code:
symbol Tick_flag = bit0 ' set for 1 exec loop per tick (second)
symbol Tick_hi = bit1 ' hi or lo half second of each tick
symbol Mins_Flag = bit2

' 1-byte peek/poke RAM variables:
' (choose suitable values in 0x50 - 0x7F area)
symbol Systime0 = 0x52 ' 6.5 bit tick counter in S S S S S S h 0 lsB
symbol Systime1 = 0x53 ' MINS counter (approx!. 1 &quot;min&quot; = 64 ticks!)
symbol Systime2 = 0x54 ' msB
symbol TimerLast = 0x55

' 2-byte peek/poke RAM variables:
' (again choose suitable values in 0x50 - 0x7F area)
symbol LoopCounter = 0x56
symbol LoopsPerTik = 0x58


main:
' Execution starts here
' Once-off setup task(s):
poke PIC_T1CON, 0x31 ' Start PIC timer1 running at 524 mSec 16-bit rollover
' (Read the PIC 16F873 documents!)
' Execution falls through to the loop below:

execloop:
' EXECUTIVE LOOP - runs forever, calling all your required jobs in turn
' All these Driver subroutines must return promptly, and NOT hold the processor.
' IE, no PAUSE, no PWM, no long PULSOUT, no waits on SERIN, KEYIN, etc !!!
gosub Tick_Dvr
gosub Dvr1 ' your routines
GoSub Dvr2 ' your routines
GoSub Dvr3 ' your routines
goto execloop

Tick_Dvr:
' 1 TICK defined as 1 second
' Maintain a 23 bit TICK counter at SYSTIME (in half-tix)
' TICK flag occurs once each tick. All drivers may detect ONE tick-flag each second
' MINS flag occurs once each minute. All drivers may detect ONE mins-flag each minute
' Any routine is entitled to peek the elapsed time from systime0, systime1, systime2
' MMMMMMMM mmmmmmmm SSSSSSh0
' Mm=Mins S=Secs h=half-tik (half sec)
' &quot;Secs&quot; is actually 524*2 = 1048 mSecs, &quot;Mins&quot; is actually 64 &quot;secs&quot;.

Tick_flag = 0 ' Flags last only one exec loop
Mins_Flag = 0
peek PIC_TMR1H, b9 ' Read msb of PIC's timer1
peek TimerLast, b10 ' And what was the Timer1 value last time round the loop?
poke TimerLast, b9
if b10 &gt; b9 then ' Test whether timer1 has started from zero again?
' Arrive here once per every 524 mSec
tick_flag = Tick_hi ' Flag a new tick (High only ONCE / 1048 mSec)
Tick_hi = 1- Tick_hi ' Toggle the Tick-hi flag. Half sec on, half sec off
Peek systime0, b5 ' Fetch system SECS counter - format = SSSSSSh0
b5 = b5 + 2 And 0xFE ' Add 2 every half-tick
Poke systime0, b5 ' Store SECS counter back again
if b5 = 0 then ' Byte overflow every 64 seconds
peek systime1, word w6 ' Fetch 16-bit MINS counter
inc w6 ' Rollover onto higher 16 bits (MINS counter - format = MMMMMMMM mmmmmmmm )
poke systime1, word w6 ' Store Mins back again
Mins_Flag = 1 ' Flag a new minute
EndIf
EndIf


' We can also maintain a LOOPCOUNTER during each tick,
' yielding a LOOPSPERTIK value.
' THIS SECTION IS &quot;OPTIONAL EXTRA&quot; - delete the next 9 lines of code at will.
' Your own routines can peek the LoopsPerTik value any time to check the &quot;efficiency&quot; of your code
' IE whether any driver is hogging unnecessary time.
Peek LoopCounter, word w6
w6 = w6 + 1
Poke LoopCounter, word w6
If Tick_flag = 1 then
w2 = 0
poke LoopCounter, word w2 ' reset loopcounter
Poke LoopsPerTik, word w6 ' Last loops/tik stored
' sertxd (&quot;[&quot;, #w6, &quot;] &quot;) ' Could enable this output message line as diagnostics tool
EndIf ' to see how many EXECUTIVE &quot;loops&quot; we get per tick



return
'
Dvr1:
'Your routines
return

Dvr2:
'Your routines
return

Dvr3:
'Your routines
return

'======================================================================
</font></pre></code>



&#160;

Edited by - bflavery on 05/01/2007 05:14:04
 

Jeremy Leach

Senior Member
Can't say I've got my head round your code, but I'm sure it's good !!

I'm interested if anyone has got any comments about my Pause measurements 3 posts above, and that the measured value is 97% of the Pause value.

Just in case the message about Timer1 is getting lost in the noise in this thread (and that's not meant as critism - it's all interesting), thanks to Hippy's investigations we've basically got the equivalent of a set of new Basic commands. Taking my 'module' as an example it boils down to 3 'commands':

- InitialiseTimer1
- ResetAndStartTimer1
- GetTime

Hippy's gone further than this with CCP etc, but all I'm interested in for the moment is these.

You don't even need to understand how my module works - just add it to end of your code (although you'll need to move the Timer1ValueW symbol definition to the top, and note it's based on 8MHz clock).

In fact, I wonder if this (or similar) could be made into new commands in a future code release - or would that mean new firmware??


Edited by - Jeremy Leach on 03/01/2007 08:47:33
 

manuka

Senior Member
Great work on this guys! I've not had time to examine or even ponder, but how about OSCTUNE issues causing that 3% error? Stan
 

Jeremy Leach

Senior Member
Nope, it won't be that Stan. Pause and Timer1 are both derived from the same internal clock. Both totally predictable (deterministic).

One of the results is wrong, but I don't see how the Timer1 result can be wrong.

I can understand that the Pause command might have an associated execution overhead of setting up the routine before it runs etc, but I would expect this only to be a tiny constant value.

Edited by - jeremy leach on 03/01/2007 19:57:39
 

eclectic

Moderator
Jeremy.

I haven&#8217;t a clue if it might help, but I&#8217;ve just run your program
through four different Picaxes.

18x &gt; Almost exactly the same as your results.

28X &gt; (8 MHz res.) (Without line 80) &gt; Very similar pattern.

08M (v9.1 and then 9.2)

Measured values slightly FASTER ( + 1, then +2).

Watching in bewildered fascination.

e.
 
Top