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.

Version of the arduino MILLIS function (OshonSoft Basic).

DogFlu66

Member
It is a version of the arduino function "MILLIS", it is a general counter that increments every mSec. and that it takes several weeks to restart.

You can download the "_FuncionesPic16F88.bas" functions from this other thread:

Note: updated code improved (3/2023)
Code:
'***********************************************************************
'MILLIS function, long type geneal counter.
'For replacing absolute pauses generated with WaitMs.
'The _MILLIS function increments every mSec. so it takes
'approximately 49.7 days To reinitialize.
'That is, it will pass through a zero value.
'************************************************************************
'Pic16F88, OshonSoft Pic Basic Compiler v.8.42
'By COS, 03/2023
'************************************************************************
#define CONFIG = 0x2f50  'Fuse definition.
#define CONFIG2 = 0x3ffc
Define CLOCK_FREQUENCY = 8  'Clock a 8Mhz
'Define SIMULATION_WAITMS_VALUE = 1'Waitms is used activate for simulation.
Include "_FuncionesPic16F88.bas"
'************************************************************************
main:
    'Pin configuration
    AllDigital
    ConfigPin PORTA = Output
    ConfigPin PORTB = Output
    'Clock internal 8Mhz.
    Call _setup_oscillator_mode_select_bit(_oscillator_mode_defined_by_fosc)
    Call _setup_oscillator(_osc_8mhz)
    'Initialize Timer1 desborde 1mSec.
    Call _setup_timer1(_tmr1_internal, _tmr1_div1)
    Call _set_timer1(0xf831)  'Interruption every 1mSec.
    Call _timer1(_on)
    Call _enable_interrupts(_int_timer1)
    'Call _enable_interrupts(_global)
    'Assign names to the Leds
    Symbol LedGreen = RB0
    Symbol LedYellow = RA7
    '********************************************************************
    Call _SetUp_MILLIS()  'Setup _MILLIS functions, before activate interrupts
    Call _enable_interrupts(_global)
    Dim PreMillis As Long
    Dim PreMillis1 As Long

    'Loop
    While True

        Call _MILLIS()  'Repeat As few times As possible, so As Not To interfere with interruptions.

        If (_MILLIS - PreMillis) >= 200 Then  'Wait time in mSec.
            PreMillis = _MILLIS  'The last value of _MILLIS is stored.
            Toggle LedYellow  'Invers the state of the led.
        Endif


        If (_MILLIS - PreMillis1) >= 400 Then  'Wait time in mSec.
            PreMillis1 = _MILLIS  'The last value of _MILLIS is stored.
            Toggle LedGreen  'Invers the state of the led.
        Endif

    Wend
End                                             
'********************************************************************
'Function _MILLIS
'Requires the use of a timer and interrupts.
'**************************************************
'Call _SetUp_MILLIS()  'Before triggering interrupts
'Call _MILLIS()  'Update _MILLIS counter
'Call _CounterMillis()  'Incrementa contador Millis
'**************************************************
'Inicialize functions
Proc _SetUp_MILLIS()
    _MILLIS = 0
    _CounterMillis = 0
End Proc                                        
'Return _MILLIS as Long and global variable
Function _MILLIS() As Long
    Disable  'Stop interruption
    _MILLIS = _CounterMillis
    Enable  'Enable interruption
End Function                                    
'Increment the counter Millis
'Return _CounterMillis as Long and global variable
Function _CounterMillis() As Long
    _CounterMillis++  'Increment counter millis
End Function                                    
'********************************************************************
'Interrupt vector
On Interrupt  'Disable interruptions
Save System
    If _tmr1if Then
        Call _set_timer1(0xf831)  'Reload TMR1 registers to count 1mSec and clear _tmr1if.
        Call _CounterMillis()  'Increments _CounterMILLIS every mSec.
    Endif
Resume  'Enable interruptions
 
Last edited:
Think I explained this in #64.

Mike.
Edit, and the IRQ lag? (1 or 2 instructions) cannot be fixed - it needs to be hardware.

Obviously you can, you just alter the timer value accordingly - to produce accurate 1mS interrupts, interrupt 'lag' itself is of no consequence and applies to all timers, all interrupts, on all processors.
 
If you want it to be accurate you have to compensate for all the code in the isr + interrupt latency (which can vary) + counter reload cycles. Instead of loading a fixed count you can read the current count and add to it, but that gets more complicated.
 
If you want it to be accurate you have to compensate for all the code in the isr + interrupt latency (which can vary) + counter reload cycles. Instead of loading a fixed count you can read the current count and add to it, but that gets more complicated.

The timer should be configured to reload on a period register match with the event interrupt enabled.
Once set up, it then never needs any further configuration or reload etc., as Nigel says.

There is no software involvement in the actual timing, it is pure hardware. I've used that system with numerous different PICs for many years.


It will reach the compare value, and generate an interrupt at whatever interval (count) is configured. The only actions needed in the interrupt are to reset the interrupt flag if the compiler does not do that automatically, and update the time counter variable(s) in your interrupt routine.

The software update will lag a few CPU cycles behind the interrupt, but that in no way affect the overall timing accuracy or interrupt frequency.


For timer 0, use 8 bit mode, internal clock, set the timer prescaler to give eg. 125 KHz, postscale 1, and counter compare register to 124 (= interrupt at every 125 counts).

I use the CCS compiler which has built in functions to configure timers. These three values are ORd together to create the config word:

0x1000 (8 bit - the compiler definition appears reversed, compared to the datasheet?)
0x8040 (Internal clock & mode)
0x0004 (Prescale; that's /16. Select to get a suitable counter input frequency so the period interval is appropriate for the 8 bit count range).


It looks like the upper half is TCON0 and the lower half is TCON1
(That's for a PIC16F18446).

The interval is set in TMR0H, which in 8 bit mode is the period value the counter (TMR0L) is compared with.
 
Not all the timers operate that way. The OP is using TMR1, which on it's own doesn’t have a period match, so you have to use it with a CCP module to get it to auto-reload.

Typically it's just the even numbered timers that can do that.
 
Obviously you can, you just alter the timer value accordingly - to produce accurate 1mS interrupts, interrupt 'lag' itself is of no consequence and applies to all timers, all interrupts, on all processors.
You can never compensate for the IRQ lag as there is no way to know if it's 1 or 2 instructions (I guess you could pop the return address and look what instruction was interrupted and work it out from there but I'm not even going to attempt that). Hence why it needs to be done in hardware.

Yes, interrupt lag still happens (but inconsequential as you state) but the timer has already been reset by hardware so it's consistent except for 1 instruction cycle.

Mike.
 
Unless you're using a crystal oscillator then it really doesn't matter much, and the vast majority of applications are perfectly fine using the in-built oscillators.
Without a hardware match facility, timer 1 cannot keep accurate 1mS ticks due to the need to reload it and the IRQ lag that's unknown, a crystal oscillator will not change this. The CCPx Special Event trigger gives timer 1 an accurate hardware period value and would enable an accurate 1mS tick.

Mike.
 
Out of curiosity, change the "if" from the previous example to a "While" and this is the result:

'********************************************************************
Function GetMillis() As Long
GetMillis = MS
While GetMillis.LB <> MS.LB 'an interrupt must have happened so fetch it again
GetMillis = MS 'another interrupt can't happen until a full milli second has passed
counter1++
Wend
End Function
 

Attachments

  • _Millis_03.JPG
    _Millis_03.JPG
    16.2 KB · Views: 139
Out of curiosity, change the "if" from the previous example to a "While" and this is the result:

'********************************************************************
Function GetMillis() As Long
GetMillis = MS
While GetMillis.LB <> MS.LB 'an interrupt must have happened so fetch it again
GetMillis = MS 'another interrupt can't happen until a full milli second has passed
counter1++
Wend
End Function
What is the result? Oh, yes, it changes the text.
That is completely superfluous.
The point is that if the LSB of ms has changed then an interrupt must have happened so simply read it again.
And, what is counter1?
And, why does the case keep changing?

Mike.
 
The while can only ever get executed once which is why it was an if. If you understood the code then you would see that. Counter1 will almost always be zero and maybe once in eight thousand times be one. One of those (possible) bugs that only happens every six months that's impossible to find.

Mike.
 
Getting rid of the intr flag and comparing low bytes like that just introduces another hazard, and could cause you to re-read ms when it wasn't really required.

As is, it shows that 54 times it had to re-read ms (assuming that's what the C1 display means)

As far as 'if' vs 'while', in this example with a single timer interrupt the 'if' should be fine. 'While' would ensure that no matter how complex the interrupt structure gets the result would be correct
 
As is, it shows that 54 times it had to re-read ms (assuming that's what the C1 display means)
If the low byte of ms and the variable holding the copy are different then it must have incremented whilst the long was being read. It cannot increment for another ms therefore it is completely safe to reread it. The while loop can never go around more than once!! The if is perfectly suited to the job and a while just adds superfluous code.

Note, the if or while will very rarely get executed so the execution time will (seemingly) not vary as they both do the same calculation the majority of the time.
I like the latter better.
Why? It's just longer for no reason. Assuming latter means while.

Mike.
 
The loop can never go around more than once!! The if is perfectly suited to the job and a while just adds superfluous code.

In this example that's true. It's not true for all cases, but I admit it would likely be rare when 'if' didn't work just as well. There would have to be a lot of interrupt activity for it to fail.
 
Just fell victim to this very problem, reading a multi byte variable outside of an interrupt.
Details here.
Had to refresh my memory of this thread.

Mike.
 

Latest threads

New Articles From Microcontroller Tips

Back
Top