PDA

View Full Version : 1ms Elapsed Timer Demo with DT's Instant Interrupts



Tabsoft
- 3rd April 2015, 21:48
Recently on this forum there was an inquiry on how to setup an Elapsed Timer using Darrel Taylor's
Instant Interrupts "DT_INT-14.bas" to work at 1ms resolution.

Some time ago I had created just such a project for my own purposes.
I thought it might be worth sharing with the larger audience here.

*** First of all let me start by saying that Darrel gets all of the credit for this project. ***

His development of DT's Instant Interrupts for PBP and his initial Elapsed Timer demo were the basis for my project.
I merely made some minor changes to the Elapsed Timer demo to achieve what I needed.

I have attached a zip file with all of the independent files including DT's Instant Interrupt include files.

The project zip file includes (5) files:
1. NewElapsed_Demo.pbp v1.10 (My Elapsed Timer Demo main program)
2. ElapTimer_Interrupt.pbp v1.0 (My version of DT's Elapsed_INT.bas)
3. DT_INTS-14.bas v1.10 (This file has NOT been modified)
4. ReEnterPBP.bas v3.4 (This file has NOT been modified)
5. README.txt (Some Installation information)

My code files are commented fairly well, especially within the "ElapTimer_Interrupt.pbp" file,
which is based on DT's file, "Elapsed_INT.bas", and where I made my modifications.

The project was created, compiled and tested with PBP v3.0.7.4 and MPASM v8.90.

Other than making changes to support a 1ms Timer1 Interrupt period in the ElapTimer_Interrupt file,
I also incorporated modifications to the same file that were inspired by DT's "Elapsed_INT-18.bas" v1.2 file
which he created an Elapsed Demo project for 18F series PICs.
There were some excellent improvements he made that simplifies the use of the file.
Most notably, the removal of hard-coded TimerConst reload values for OSC frequencies.

The project displays an LCD clock with the following output:
dd-hh:mm:ss.mmm

dd = days
hh = hours
mm = minutes
ss = seconds
mmm = milliseconds

With the LCDOUT execution time being what it is, the milliseconds display will not increment by a single ms; however, the Timer1 interrupt is
firing at 1ms intervals and recording the number of elapsed milliseconds.
Rather, it will increment by a few ms.


In the spirit of Darrel's generosity to all of us here, I hope some may find this project helpful.

7762

Art
- 4th April 2015, 17:15
To do the digit lookups for 7 segment LEDs, you’d need to print and delay for Human vision all digits of the clock for every millisecond.
It’s a tough call, but the delay can be shorter the faster the multiplex is cycling.
For LCDs if I recall correctly the delays for writes were in microseconds, but were pretty high numbers.

I might have to take a look just to see how much instruction time is left between each ISR.

Art
- 4th April 2015, 17:36
Ok so the LCD write command must get interrupted, and is fine to continue the rest of the display
with updated values after the interrupt, or the entire display would never get written.
In that case you’re checking & clearing MSecsChanged for nothing,
and might as well go hard at printing the display though that’s not the time consumer.

I’m sure the LCD command can be used quicker in BASIC, but as you say it’s the LCD delays the main holdup.

Tabsoft
- 8th April 2015, 18:43
I have made a few updates to the ElapTimer_Interrupt.pbp include file for this demo.
The new version is 1.1.

Updates:
v1.1 04/06/2015:
1. Added "TcRem" compile-time asm variable.
Used to test for Interrupt Time Accuracy during compile phase.
2. Added compiler test/message for Interrupt Timer Accuracy.
3. Added compiler test/error for TimerConst variable overrun.

7767

Don't forget to rename the file by removing the ".txt" at the end of the filename.

Art
- 16th May 2015, 06:17
Hi :)

I was looking through your version and come across this again, as is also in DT’s original code:


; ----------------- ADD TimerConst to TMR1H:TMR1L
ADD2_TIMER macro
CHK?RP T1CON
BCF T1CON,TMR1ON ; 1 InstCycle, Turn off timer
MOVLW LOW(TimerConst) ; 1 InstCycle
ADDWF TMR1L,F ; 1 InstCycle, Reload timer with correct value
BTFSC STATUS,C ; 1 or 2 InstCycles
INCF TMR1H,F ; 1 InstCycle
MOVLW HIGH(TimerConst) ; 1 InstCycle
ADDWF TMR1H,F ; 1 InstCycle
endm


I am wondering about a few things like why this is here:


BTFSC STATUS,C ; 1 or 2 InstCycles


Depending on the carry status after the add, we would still have 8 instructions, because if the skip takes
two instructions, it skipped a line that would have taken another instruction.

I don’t understand why it’s necessary. If the ISR is called on timer overflow, then even if it is not zero
by the time it’s reset, it should be a known value, and not have to be checked.

This somehow looks like it’s setup as if the ISR was called a variable time after the overflow interrupt which should never happen.
I think there is something I’m not seeing, and the code it so simple, there must be a reason, otherwise it’s easily fixed.

I’m seeing error when I use a 10MHz crystal to generate a 1Hz LED pulse, and get it roughly synced (visible to Human) with
the output of a GPS pulse per second signal. After an hour I can see a difference between the two blinking LEDs,
which for a Human to see, would probably take some ms difference.
There could be error in my code, but I doubt that as I have only interfaced with DT’s timer,
or I have a terrible crystal, or board layout (very likely because the crystal is mounted to a two pin header to make it removable,
or crystals just really are that bad.

Art
- 16th May 2015, 11:18
Ok, so if you had other interrupts enabled, some timer ticks may have occurred before this routine?
I have only used the Elapsed Timer, and done others manually.

I hope you don’t mind discussion in your thread Tabsoft..
if another thread is needed, that’s ok, but I’m thinking some discussion is needed.

Incidentally, and not so related to this because DT does it slightly different... It appears MC don’t run their own samples:http://img.photobucket.com/albums/v186/ArtArt/TMR1_Oops_zpsnw8i52tx.png

Tabsoft
- 16th May 2015, 15:50
Art,

Tying to respond to your PM, but your inbox is full....

Art
- 20th May 2015, 12:04
This is about an issue of my own. There would be different ways to implement in a program.
It probably should have been commented like this:


; ----------------- ADD TimerConst to TMR1H:TMR1L -------------------------
ADD2_TIMER macro
BCF T1CON,TMR1ON, 0 ; 1 Turn off timer
MOVLW LOW(TimerConst) ; 1
ADDWF TMR1L,F, 0 ; 1
BTFSC STATUS,C ; 1 if carry set / 2 if carry clear
INCF TMR1H,F, 0 ; 1 if carry set /0 if carry clear
MOVLW HIGH(TimerConst) ; 1
ADDWF TMR1H,F, 0 ; 1
endm

‘ always up to seven instructions here since stopping the timer
‘ one more instruction will be used to turn on the timer in the
‘ code that called this, and there’s the eight instructions


I need to reset the timer (not add to it) if the ISR was triggered by port interrupt,
It looks like I’ll need another conditional and more instructions to check if it was portb.0 interrupt,
copy the constant values to the timer rather than add to it if it was called due to portb.0 interrupt,
and then also change the constant value to accomodate the extra instructions.

Because currently if the code was not called due to timer overflow, it is unknown what is in the timer.
It could be called by port.0 interrupt when a 0xFFFB value is in the timer for all I know.

My question if this is all because the whole DT interrupt set that can be enabled for multiple interrupts
to allow those interrupts be serviced first like I suggested above.

Tabsoft
- 20th May 2015, 14:38
Art,

First of all, let me say that I "assume" you are using DT's Instant Interrupts in the context of this question.

That being said, I am highly confident that DT "Adds" the Timer1 Preload constant (TimerConst) to the value of the Timer1 Counter
to account for the instructions executed in his Instant Interrupts Processor.
DT's Instant Interrupt Processor handles many things, including as you state multiple interrupts, but it also checks for and handles
Register Context saving for you. All of this takes instruction cycles to perform, ~91 instructions.

Also, the PIC MCU's Timer Interrupt is not instantaneous.
There is Interrupt Latency to contend with as well, which a good explanation can be found here in Microchip's Tech Bulletin TB3100.
http://ww1.microchip.com/downloads/cn/AppNotes/cn566554.pdf

While Timer1 is enabled, the Timer1 Counter increments for each instruction cycle even after an overflow of the counter occurs.
This is a good thing and is exploited by DT to keep track of all of the processing I mentioned above until the Instant Interrupt Processor vectors to the ISR and the "ADD2_TIMER" macro is called and the "BCF T1CON,TMR1ON" statement is executed which turns off Timer1.

By "adding" the TimerConst to the value of Timer1's Counter, the time-based interrupt period is maintained as consistently as possible.

Hopefully this helps.

Secondly, if you are using DT's Instant Interrupts, why wouldn't you use a different label to vector to, for the PORTB.0 Interrupt on Change?
Is there a reason you would put it all in a single ISR handler?
Instant Interrupts should allow you to define multiple sources each with its own Label to jump to when the interrupt occurs.

Just curious.

Art
- 20th May 2015, 15:11
Ok thanks for confirming, I figured as much.
Dt’s original timer, or a bastardised version of it, because I know for the project it will always be clocked at 10 MHz,
and I also know I will only ever be interested in 100 Hz interrupt form the timer. The portb.0 interrupt is simpler, and I’ve done that manually.

To answer your other question.. It’s still about synchronising the timer to a GPS PPS pulse, but I want to go better than simply resetting Ticks,
and load the timer correctly for that situation as well... So when the program decides to sync, it enables portb.0 int, resets the flag for portb.0 int,
and then the next rising edge interrupt should call the same timer where it is loaded and then the portb.0 interrupt turned straight back off
unless the program decides to do the sync again at some later time.

Dealing with the timer ISR.. it seems I could save myself some instruction counting, and deal with the timer value the same way the reload code does
by just making sure the next Tick will occur correct time, rather than dealing with the current portb.0 int Tick that called the ISR.

This is my first play.. not sure about it and haven’t thought about it too much.. it’s my first play in a compiler since I started talking to you.
I disable ext int just a little later when the time values are being incremented and timing isn’t as critical.
For this version the constant needs another 6 added to it.

For most of the time ext int is disabled it’s supposed to act as it normally would, but take a little longer to execute.




; ----------------- ADD TimerConst to TMR1H:TMR1L
ADD2_TIMER macro
CHK?RP T1CON

BCF T1CON,TMR1ON ; 1 Turn off timer
MOVLW LOW(TimerConst) ; 1

BTFSC INTCON,INTE ; 1/2
MOVWF TMR1L ; 1/0

BTFSS INTCON,INTE ; 1/2
ADDWF TMR1L,F ; 1/0

BTFSC STATUS,C ; 1/2
INCF TMR1H,F ; 1/0
MOVLW HIGH(TimerConst) ; 1

BTFSC INTCON,INTE ; 1/2
MOVWF TMR1H ; 1/0

BTFSS INTCON,INTE ; 1/2
ADDWF TMR1H,F ; 1/0

endm

Tabsoft
- 20th May 2015, 16:05
Two initial questions.

1. Are you using DT's Instant Interrupts to handle the Timer1 Interrupt?
2. Is the code snippet below used for both interrupt instances you mentioned?
A. Timer1 100Hz Interrupt
B. PORTB.0 Interrupt to re-synch

Art
- 20th May 2015, 17:00
1.. Yes.
There is a little more in the main program to handle the ext interrupt, but essentially yes (for question 2).

The main program turns external int off and clears it’s trigger flag as well at initialise time.
Then when the sync wants to happen for whatever reason (like the GPS has been locked for a minute),
the main program turns ext int on. Then I figure the next time the ISR is called by ext interrupt,
the timer should be loaded rather than added to... but added to every other time, because
the ISR turns ext int off just a little after this.

ps.. So far the time is still correct, but I can’t test lag or error of the sync to the pulse.

Art
- 20th May 2015, 18:12
It just occurred to me I’d have to comment out anything instant interrupts does with other interrupts
just in case activity on portb.0 changes the time that takes to execute (prior to the timer reload routine being called),
and also add it's instruction time in the value loaded to the timer if portb.0 was the source of the interrupt.

Tabsoft
- 20th May 2015, 20:24
I think we may be beginning to cloud up this thread.
Do you want to start another?

Art
- 21st May 2015, 03:35
There might not be a need to if I fix the second channel on my scope, and do what you did,
except look at the generated pulse on one ch, and the GPS pulse on the other.
Then if I can’t sort it out I’ll start another one.
Cheers, Brek.

Tabsoft
- 28th May 2015, 03:24
Art,

Just curious if you got this to work.
I would think that you would have to define a DT interrupt source for the EXT int and set the handler label.

Art
- 29th May 2015, 18:59
Hi Tabsoft :)
No, but I had a moment of inspiration. Wouldn’t even need the interrupt, but it will help.
The idea is to sync it roughly, with port.0 interrupt, then turn off ext int and the clock will be somewhere close.

Then although port.0 int is disabled, the IO pin still works... so next, increment or decrement the so called “constant value”
until the timer cycle you check port.0 straight after the interrupt and find the GPS PPS pin is high.
Then subtract as many instructions from the constant value as it took to check port.0 after the timer reload.

For 10MHz pic, that might have it down to the 400ns instruction time, but I’m not sure of that.

Tabsoft
- 29th May 2015, 21:06
What is the interval and duration of your input pulse on PORTB.0 from the GPS?

Art
- 18th June 2015, 20:06
Hi, looks like I ignored you there..... We took it to email!

Heckler
- 9th March 2016, 04:43
Hi Tabsoft (and others who might have some insight)

I have incorporated your 1ms timer (top of this thread) as a basis for my nixie clock. ( I had wanted to implement Darrells' instant interrupts so this was perfect)

I needed a 5 ms interrupt to update my nixie display and was able to modify your "elaptimer_interrupt.pbp" include file to give me the 5 ms interrupt by changing the include as shown below...



; Added 04/03/2015 "IntTime"
IntTime = 200 ; 200 = 5ms
; Change IntTime in calc for interrupt time
; 1000=1ms, 100=10ms, 10 = 100ms, etc.
; Needs to be a multiple of 10.

But I am finding that my clock gains about 2 minutes (give or take) in about 18 hours.

I wonder if the problem might be in this part of your include...


if Ticks = 200 then ' Modified 04/03/2015 from 100 to 1000
' change Ticks = x to right interrupt time
' 1000=1ms, 100=10ms, 10=100ms, etc.
Ticks = 0

when Ticks go from 0 to 200, wouldn't that be 201 steps.
Shouldn't it be from 0-199 or 1-200 to get the 200 steps??

But in my case where the clock is running fast I should probably start increasing this count to slow the clock down?

somewhere in the thread or in the comments in the include there is alluded to that the code is somehow self calibrating?? or am I misreading that??

What is the best way to arrive at a more accurate time base?

I am just using the internal 8mhz osc in my 16F1828 so I know it won't be super accurate.
My clock does read NTP time every morning so I don't need long term accuracy.
I will probably change the code to read the NTP time twice a day if necessary but I would like to fine tune it a bit if possible.

thanks (in advance) for any guidance from the forum

Tabsoft
- 13th March 2016, 18:36
Heckler,

I'll take a look when I get a few moments. See if I can set it up on some hardware and give it a test run for a while.

Heckler
- 15th March 2016, 03:32
Hi TabSoft,

Possibly I am expecting too much from the internal OSC. The documentation states "Factory Calibrated to within+/- 1%"
1% of 24 Hours is +/- 14.4 minutes so my complaint regarding 2-4 minutes in 24 hours is well within the 1% :o

I took a look at the elap_timer include file and can't quite tell how I might make fine adjustments to the timing routine other than adding or subtracting in "Ticks" in the following code segment...

if Ticks = 200 then ' Modified 04/03/2015 from 100 to 1000
' change Ticks = x to right interrupt time
' 1000=1ms, 100=10ms, 10=100ms, etc.
Ticks = 0

Am I on the right track??

Is it the case that this include file is somehow self calibrating??
If so how does that happen? and against what standard?

This is all somewhat low priority as it is just for a real time clock that only displays hours:minutes and I can opt to check NTP time (more than once a day) or go to an external crystal oscillator.

Thanks for any guidance or explanation you (or others) might be able to offer.