Index

 

Introduction

 

Background

 

What it does it do and how it does it

 

How it automatically generates the code

 

How to use the IDT

 

Using IDT in a PBP program

 

Accuracy of the Timer routines

 

Programming Tricks

 

Introduction

The following text is an explanation of the interrupt timer routine IDT I wrote. It cover’s why and how I wrote it, along with instructions on using it in your PBP program. If you don’t want to read me prattling on about the reason why it wrote it and how it works, skip directly to How to use the IDT. However I believe it is quite informative and all helps toward improving you knowledge of using PIC’s and PBP.

 

 

Download all the Source Code in .Zip Format

 

Background

I currently have a thing about "Multitasking". While it is not possible to implement multitasking properly on the PIC due to the inability to PUSH and POP the stack. It is possible to implement other forms of multitasking i.e. having tasks running in the back ground via interrupts and by various programming techniques. One of which I will from now call "Branch based Pseudo multitasking" or BPMT for short. I am sure there is a correct name for this, but I am unaware of it. To implement BPMT properly you need a timer. The reason being is that when using BPMT you cannot sit around waiting for something to happen i.e. issuing a Pause 1000 command or looping for a key to settle in a de-bounce routine. What is need is a method of starting a timer and then keep coming back to check the timer and only when it has timed out do you continue with that thread.

 

Currently there are no timer routines other than Pause and Pauseu available to PBP, and as I stated earlier, they stop the processor from executing any other commands while performing them. It was to rectify this omission that I have written this interrupt driven timer routine.

 

At the conception stage it was obvious that more than one timer was needed, I decided therefore to implement 8 timers / counters. 8 were chosen, as that would enable the Timer Status Flags to occupy just one byte. A time base of 100hz was chosen. This was to prevent, unnecessary processor overheads.

 

Working on a 100hz time base, an 8 bit (byte) counter would last for just over 2.5 seconds. While there may be a lot of uses for a timer that only lasted that long, there are be just as many uses for one that last a lot longer. 16 bits (a Word) would last 655 seconds or about 10 minutes, and 24 bits 167772 seconds, which is 2796 minutes or about 46 hours. While 10 minutes is too short, 46 hours could be useful. 24 bits though is very hard to work with in PBP. I needed to be able to reduce it down to more manageable numbers. So why not implement some form of pre-scaling? 10:1, 50:1 and 100:1 would be nice, that would give a time base of 100hz, 10hz, 2hz and 1hz. While were at it a counter as well as a timer would be nice.

 

So I made a table of requirements up.

 

8 timers / counters

Timer base of 100hz

Pre-scalar values of 1:1, 10:1, 50:1 and 100:1

Timer counters in 8, 16, and 24 bits

And it should be able to work at all OSC speeds

 

When you look at the code that would be required to implement all of the above it would be end up on the large size. Very wasteful of valuable PIC code space when you only wanted 1 or two, 8 bit 100hz timers. To customise the routine for my self, would be very easy. Only adding only the code required for the project in mind, but as I wanted to publish the routine for others to use I wanted to make it as easy to implement as possible. I have in the past been there my self and found it very hard to modify when you don ' t understand assembler. After all is that why most people buy PBP, to keep away from the code. What was need was a piece of code that would write it ' s self to your requirements only producing the code required to do the job.

 

As I was writing the code as a learning exercise, I decided to make it worthwhile, and write a routine that would do as much of the work as possible. The main criteria being that it should be as fast and compact as possible. The routines produced are the result. If you can see any way to improve it/them please let me know.

 

 

 

What it does it do and how it does it

 

 

The program when included in your PBP program will, generate an interrupt routine that will make the PIC generate a regular 100hz interrupt. This interrupt calls a interrupt routine which handles the reloading of the TMR’s and the incrementing / decrementing of the timers.  To implement the pre-scalars a counter is used, counting down from a preset value till it reaches zero, when it starts again.

 

To start with I planned on using just one set of pre-scalars and adding the code for each timer as required after the appropriate stage. I soon discovered that method would lead to problems as you might start the timer as the pre-scalar was about to roll over so it could be up to 99/100 of a second out. I now have a pre-scalar for every timer. There is still the issue over timing but it is now limited to less than 1/100th of a second.

 

If we look at what is generated as a minimum by the code generator.

It consists of: -

1.         The code to reload the timer

2.         The code to clear the interrupt flag

3.         Lastly the code to reload the STATUS etc registers

On top of this there is always the variable declarations. But because the number of variables required varies with the type of Pic there has to be a different routine for each size. Unfortunately while I can figure out the code size of the Pic it is being compiled for, there is no way I can vary the number of variables declared in PBP. To cover for this there is a routine for each size.

 

Within PBP you are also required to make room for a number variables depending on the number of timers / timer types you require (for full info see HOW TO USING THE IDT).

 

Once you start DEFINING timers the space between 1 and 2 will expand with code. An example follows of what would be created if we DEFINED 2 timers: - 1  an 8 bit timer with no pre-scalar and the other a 16 bit timer with an 50:1 pre-scalar.

 

 btfsc _TMRFLAG.0                                                       ;' Check if timer running

 GoTo @@0000                                                                         ;' No so exit now

 decfsz _TIMER0,f                                                          ;' Dec timer byte counter

 GoTo @@0000                                                                         ;' If not zero exit

 bsf _TMRFLAG.0                                                                      ;'Timer countdown finished

 

 @@0000

 btfsc _TMRFLAG.1                                                       ;' Check if timer running

 GoTo @@0001                                                                         ;' No so exit now

 decfsz _TIMER1_PRE,f                                     ;' Decrement pre-scalar check if zero (time up)

 GoTo @@0001                                                                         ;' No so exit now

 movlw 50                                                                                              ;' Yes so reload pre-scalar

 movwf _TIMER1_PRE

 decfsz _TIMER1,F                                                        ;' Dec timer lower byte

 GoTo @@0001                                                                         ;' If not zero exit

 decfsz _TIMER1+1,F                                         ;' Dec timer upper byte

 GoTo @@0001                                                                         ;' If not zero exit

 @@0001

 

Don ' t be put off if it makes no sense. The idea was to write a program that did not require you to understand assembler. For those of you that know assembler you might be wondering why there are labels named @@0000 and @@0001.  These are labels are automatically generated by the compiler when you declare variables at a LOCAL level within in a macro.

 

The code in simple terms does this: -

 

First it checks if the timer is still running by looking at the Timer flag. In the first section of code, it is TMRFLAG.0. If it is not set (zero), it is still running and will decrement the timer variable _TIMER0. When this timer reaches zero it sets the TMRFLAG.0 thus stopping the timer routine running next time round. Next it processes the TIMER1 code. As stated earlier it is a 16-bit timer with a 50:1 pre-scalar. Once again it checks to see if the timer is running and decrements the pre-scalar which is set to count down from 50. If it has not reached zero it will jump past the rest of the code and exit. Once it does reach zero it reloads the pre-scalar with 50 and decrements the 16 bit variable TIMER1.  When TIMER1 has reached zero it sets TMRFLAG.1 thus stopping the timer routine running next time round.

 

There is on extra piece of code (not shown) that is generated every time the routine is compiled and this is an automatic variable loading routine. It is used to ensure that the pre-scalars are loaded with the correct values on power up. The number and type of timer required is automatically ascertained from the DEFINE statements you make in your main code section.

 

 

 

 

How it automatically generates the code

 

When you DEFINE a timer in PBP this information is passed on to the assembler in the form of a #DEFINE statement in the assembler listing. This is a very useful function; it enables assembler statements to utilise this information, either directly within the code or as a guide to the construction of the code.

  

I have exploited this function in the timer routines to enable the automatic generation of code.

 

The program revolves around the macro "TMR_GEN_MAC". When this macro is called, it is passed a number of variables, the variables being used are named: - TIMER_TYPE, TIMER_VAR, TIMERFLAG and TIMER_PRE, and they control the way the macro names and labels the variables used within its self. 

 

The option was there to write 8 macros, each one dedicated to a timer. This I decided was not very efficient. What was needed was to be able to make one macro write the code out as many times as required. Fortunately most assemblers have conditional assembly instructions. The most useful of these is the IF command, which works in roughly the same way as in Basic. This enables a line or more of code to be written on the out come of the IF/ElSE statements. An example being: -

 

 IF timer = 1

             movlw 4

             movwf _timer_count

 ELSE

             movlw 10

             movwf _timer_count

 ENDIF

 

 

 If timer is = 1 then the code following it will be added to the program at the current code position.

 

 If timer is not = 1 then code following the ELSE is added.

 

 Another conditional assembler statement is IFDEF/IFNDEF. This time no specific values are checked it is purely used to see if the if a value has been defined. I.e.

 

 IFNDEF timer

             timer = 1

 

 Here if "timer" has not pre-defined it is given a value of 1. The same method is used by PBP to define the defaults, OSC etc.

 

These feature’s has be exploited in the program, to list through all the possible timer no ' s. When a Timer has been found to contain a value it calls the macro, "TMR_GEN_MAC", passing to it all the parameters it requires to generate a unique section of code. The parameters passed on are TIMER_TYPE, TIMER_VAR, TIMERFLAG and TIMER_PRE.  As there is a requirement to make the each timer routine to be different from the rest, the parameters are all different. The following is an example of the code used to call “TMR_GEN_MAC” to generate timer0 and timer1.

            ifdef TIMER_0                                        ;' Check if TIMER_0 called for

            tmr_gen_mac TIMER_0, _TIMER0, _TMRFLAG.0, _TIMER0_PRE

            EndIF

           

            ifdef TIMER_1                                        ;' Check if TIMER_1 called for

            tmr_gen_mac TIMER_1, _TIMER1, _TMRFLAG.1, _TIMER1_PRE

            EndIF

It can be seen that the macro is only called if a DECLARE has been made for particular TIMER.  The various parameters actions are detailed below.

TIMER_1 passes on the number following the DECLARE TIMER statement in PBP

_TIMER1 tells it what the TIMER variable is to be named as.

_TMRFLAG.1 tells it what bit the flag is in the TMRFLAG variable. This controls whether the timer is running or not.

_TIMER1_PRE tells it what the variable for the pre-scalar counter variable is called

With all this information the macro can generate all the code required.

The macro it’s self is made up of a series of “IF” statements where the decision to insert code is based on the outcome of an & (AND) operand on the value held in TIMER_TYPE variable. The other variables are used within the code it’s self for variable locations, bit no’s etc. I will not go any further in an explanation of the construction of the code within the macro as they are fairly self explanatory and documented with comments.

One last think to point out is the automatic OSC speed compensation. PBP will if not told other wise define the OSC speed to 4, by using this value in OSC it was possible to work out a value to be loaded into TMR1 or the variable PRE_COUNT to compensate for the speed difference of the oscillator on the PIC timers them selves.

TMR1_Val =((65536+(12+4))-(OSC*2500))            ;' Calculate OSC offset values

Is used in the “t1” routines to do just that.

How to use the IDT

Configuring timers

Unlike a hand coded routine it is very easy to add / remove as many (up to 8) timers / counters as your program requires; however it is important to know what type, number off etc you require in advance in order to be able to declare the correct size / type of variables.

Follow the instructions below, to enable you to load the correct Timer generator, declare the variables and the generate the correct DEFINE number, for the configuration you require.

1. Include the right timer routine in you code.

“T0” at the start indicates it is based on a TMR0 interrupt and “T1” is a TMR1 interrupt.  Check the data sheet for the your device to see what timers are available. As a pointer I would use TMR1 if available as it is marginally smaller and does less interrupting, thus it will have less of an impact on the speed of the rest of your code.  The 2K, 4K, and 8K indicates that it is for a 2, 4 or 8K sized PIC.

INCLUDE “T1_INT8K.INC”

           

2. Next decide on how many timers/counters you want and how big they are, 8, 16 or 24 bits

           

3. Declare the variables:-

           

TIMER0 VAR BYTE BANK0        ‘For 8 Bit’s

TIMER0 VAR WORD BANK0      ‘For 16 Bit’s

TIMER0 VAR BYTE[3] BANK0    ‘For 24 Bit’s

           

Note, that for the 24-bit timer, an array is declared. If you use bytes you will be able to tailor it to exactly 24 bits, you will not be able to display / work on though, any more than 8 bits of it within a PBP program. It might therefore be more useful to declare it as a WORD variable and you then at least be able to display it as a 16 bit number, I have yet to find a way of displaying a 24 bit number in PBP, If you come up with one please let me know.

You can have up to 8 timers, so don't forget to declare all the variables TIMER1, TIMER2 etc.

If a pre-scalar is required you will also have to declare another variable for that timer: -

TIMER0_PRE VAR BYTE BANK0: 

TIMER1_PRE VAR BYTE BANK0 etc

Decide if the timer is to be a timer or counter, the pre-scalar value if required and then DEFINE the configuration you require:-

           

The timer/counter set up is decided by the configuration of the bits used in the DEFINE statement

Bit 0 indicates if a timer or counter is required

            0 = Timer

            1 = Counter

Bits 1 & 2 specify if a pre-scalar is required and if so what prescaling

            0 0 = no pre-scalar

            0 1 = 10:1 (10Hz) pre-scalar

            1 0 = 50:1 (2Hz) pre-scalar

            1 1 = 100:1 (1Hz) pre-scalar

Bits 3 to 4 specify the size of the counter

            0 0 = 8 bits

0 1 = 16 bits

1 0 = 24 bits

An example define is: -

DEFINE TIMER_0 8 (which is an 16 bit timer with no pre-scalar)

You can if it makes it easer DEFINE as a binary number

DEFINE  TIMER_0 1000b

Note the “b” following the number this indicates to the compiler that it is a binary number. This is different from PBP, which uses a “%”, This is because DEFINE is a machine code compiler pseudo-op as opposed to a PBP command.

An example of the above put together is: -

INCLUDE “T1_INT8K.INC”

TIMER0 VAR WORD BANK0

TIMER0_PRE VAR BYTE BANK0

DEFINE TIMER_0 8

All of which adds up a 16-bit timer with no pre-scalar.

Using IDT in a PBP program

The fist thing to note is that on start up no timers are running. Within the include file the timers are inactivated and all pre-scalars are loaded with their default values.

When using a Timer it is presumed you have a time you require it to run for. You should load this number into the variable TIMER? (? being the number of the variable you are using) taking into account the pre-scalar type.

TIMER0 = 3000              ‘This equates to 30 seconds when using a 16 bit timer with no pre-scalar

Next when required you can turn the timer on

TIMER.0 = 0

This will start the timer and it will not stop until it has timed out or you turn it off

TIMER.0 = 1

You can check if the timer has timed out easily by checking the state of the appropriate bit.

If TIMER.0 = 1 then timer_finished

Using the counter does not usually require you to place a value in the TIMER? variable other than 0. As and when required you can start and stop the timer by setting or clearing the appropriate bit.

If …….then TIMER.0 = 1

You can then read the value in TIMER? and perform calculations etc as required.

Accuracy of the Timer routines

The maths involved in setting and maintaining the correct numbers to load into TMR registers is to the best of my knowledge correct. However I have found on my test rig that TMR1 is slow. This I believe is due to the oscillator being slightly slow. If it is found that your particular set up is not keeping a to reasonable level of accuracy, you can compensate for it in the software. In the middle of the t1..inc files there is an equation: -

TMR1_Val =((65536+(12+4))-(OSC*2500))            ;' Calculate OSC offset values

If you find the timer is running slow you can increase the speed by adding to the (12+4) value. I would recommend that you only increase it by 1 at a time as 1 will increase the speed by about 1 second every 3 hours. To slow it down you can decrease the value.

Should you need utmost accuracy you can of cause connect a high accuracy crystal to TMR1 to clock the timer, you will have to though do some modifications to the code to compensate for the speed difference.

IMPORTANT

The timer is only as accurate if the starting value of the TMR is at the reload value when you start the Timer. In reality it will never be so. You therefore have to take into account that the timer may be up to 1/100 of a second fast. If timing is critical to that level and the time is short i.e 1/100th of a second use PAUSE instead.

Programming Tricks

If you are have to read a 16 or 24 bit timer while it is running you must be aware that it is possible to read the low bytes then the high byte just as they turn over. When an accurate read is required it is recommended that the following routine is used.

timerval = TIMER3

temp = timer3.byte1

IF timerval.byte1 – temp = 0 Then calcexit

timerval = TIMER3

calcexit:

timerval will now equal TIMER3 with no risk of a wrong reading.

The code in IDT is crying out to be hacked around. If for example you want a different pre-scalar you can define your own by changing the value directly in the code.

Example

            IF         ((TIMER_TYPE & 6) = 2)            ;' If a 1:10 pre-scalar selected insert this code

                        decfsz TIMER_PRE,f                  ;' Check if zero (pre-scalar count up)

                        GoTo TIMER_EXIT                     ;' No so exit now

                        movlw 10    <<<< change this value        

                        movwf TIMER_PRE

            EndIF

Note. By modifying the code in this way, will mean that all other timers DEFINED with this pre-scalar will take the same value.

If you only want to use 1 timer at a time but would like it to be infinitely variable, you can be really clever and you make a user adjustable pre-scalar version, add this code into the pre-scalar defining section.

           btfsc TIMERFLAG                                  ;' Check if timer is running

            GoTo TIMER_EXIT                                             ;' No so exit now

Add this code in

……………………………..

            IF         ((TIMER_TYPE & 6) = 0)            ;' If a user defined pre-scalar insert this code

                        decfsz TIMER_PRE,f                  ;' Check if zero (pre-scalar count up)

                        GoTo TIMER_EXIT                     ;' No so exit now

                        movfw _TIMER_PRE_VAL          ;' Yes so reload precaler and continue

                        movwf TIMER_PRE

            End

……………………………….

            IF         ((TIMER_TYPE & 6) = 2)            ;' If a 1:10 pre-scalar selected insert this code

                        decfsz TIMER_PRE,f                  ;' Check if zero (pre-scalar count up)

You must remember to declare the extra variable at the start of the code and set the TIMER_PRE_VAL variable to the correct value before starting the timer.

TIMER1_PRE_VAL  VAR  BYTE BANK0 ‘USER ADJUSTABLE PRE-SCALAR VAR

TIMER1_PRE_VAL = 250

And when altering the value remember that if you want 1:1 make TIMER1_PRE_VAL = 1. For a 250 :1: -

You can then by defining a 24 bit timer / counter and declaring a 24 bit  variable, have a universal timer / counter (only one type) that could be completely user adjustable in you PBP program.


Download all the Source code In .Zip Format

 

Or View them online:

T0_INT2K.INC


T0_INT4K.INC


T0_INT8K.INC


T1_INT2K.INC


T1_INT4K.INC


T1_INT8K.INC

TIMR_TEST.BAS


If you find this routine useful, have comments, find bugs please E-mail me at tim.box@boots.com, I would love to here from you.