Version of the arduino MILLIS function (OshonSoft Basic).

Status
Not open for further replies.

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:
Volatile won't help. Neither will doing the subtraction in a separate statement like your "aux = " statement.

The problem is accessing a multi-byte variable in both the main and interrupt context, which is never a safe thing to do.

There are two choices- disable interrupts when you access the variable in main, or write a function that will read _Millis until it gets two successive values that are equal, and return that value.

The method used in the original post is flawed.
 
No, the variable is static type.
But the microcontroller only moves 8bit at a time.
An operation with variables of the Long type generates intermediate steps.
If the interruptions generate a modification when the variable was not finished reading in the external part of the program, this reading will be wrong.

If I stop the interrupts before reading the variable in the outer part (main), the reading is always correct with whatever expression is used.
 
The problem is accessing a multi-byte variable in both the main and interrupt context, which is never a safe thing to do.
Indeed, didn't think of that even though I pointed it out in the servo thread.
I.E.
Note that in order to move a servo disable interrupts around it, I.E.
INTCON.GIE=0
servoPos(4)=nnnn
INTCON.GIE=1
Funny how you forget the most simple things when fixated on another (thing).

Mike.
 
I have finished a function to render a font in variable size. I've been wanting to finish it for years.
I have applied a 500mSec blink to the red led. and the refresh of the display every 1000mSec., applying the same program "_MILLIS" of the example in a pic 18F46k22.

Video:
 
You're right.
That is why you have to do many tests before giving something as valid.
In my case, I like to use better the time bases that I publish in the other thread. Because the counters are so inside the interruption vector and only return a flag (bit) indicating that the programmed time has finished counting.

I have been using the same version in "C" for many years in my projects and it works ok.
 
I forgot to indicate that I take into account that when working with values of several bytes, "Save System" is used, using values of more than 1 byte is crazy if it is not used.
 
I forgot to indicate that I take into account that when working with values of several bytes, "Save System" is used, using values of more than 1 byte is crazy if it is not used.
"Save system" won't help.

If you get interrupted while reading the 4 bytes of Millis, any operation will be invalid.
 
If you don't like either of the options from post 21, here's another method.
I consider it the same as "read until 2 accesses are equal", but it's a little more optimized.

I don't know Oshonsoft syntax, so here it is in pseudo code
Code:
dim milli as unsigned long	// msec counter used in ISR
dim has_updated as byte		// can also use bit or boolean type

interrupt msec_ticks()
	// clear IF, reload tmr, etc
	
	milli++				// incr msec count
	has_updated = 1		// signal intr has occured
end interrupt


// read milli
function millis() as unsigned long
	dim retval as unsigned long
	
	// read msec counter used in the ISR until we
	// know we weren't interrupted while accessing it
	repeat
		has_updated = 0
		retval = milli
	until (has_updated = 0)
	return retval
end function

You use the function millis() anywhere outside of the ISR in place of accessing the count variable directly
 
Unfortunately, subroutines cannot return a value in Oshonsoft.
Probable best do something like,
Code:
INTGON.GIE=0
current=Millis
INTCON.GIE=1
if (current-previous)>interval then
    .
    .
    .
    previous=current
Endif

Mike.
 
If it returns, the name of the function is a global variable, in this case the same name of the function is used, thus the value of _MILLIS is returned, directly or equal to another register.

Well the example at the beginning of the thread, I'm testing it in reality and it works. The level of reliability ....... because each one decides.

In any case, the topic is interesting and I will look for another way to complete the function.
 
Unfortunately, subroutines cannot return a value in Oshonsoft.

Subroutines can't (whatever the language) but 'functions' can - he's shows the routine as a function, complete with returned value, so presumably Oshonsoft supports functions? - my simple BASIC compiler certainly did, and I would certainly expect any modern version of BASIC to.

Interestingly, C doesn't have 'subroutines' it only has 'functions' - simply ignore the return value (void) if you want a subroutine - Pascal, been a better structured language, provides both.
 
Just checked the manual and Oshonsoft does indeed support functions. Wish I'd have known that a few days ago.
There's also PROC available but I don't know the difference between Function and Proc.

Mike.
 
"Function-End Function" if it returns value.
The Function name is a global variable.

"Proc-End Proc" returns no value.
Proc would be equivalent to C Void.

They can all be terminated prematurely with "Exit".
 
"Exit" is often used a lot when you want to configure the function and you don't want it to be executed completely, for example at the beginning of the program.
 
Just checked the manual and Oshonsoft does indeed support functions. Wish I'd have known that a few days ago.
There's also PROC available but I don't know the difference between Function and Proc.

Mike.

As DogFlu66 said, a Procedure is a sub-routine you can pass a value TO - a Function is a sub-routine you can pass a value TO and receive a result FROM.

They actually come from Pascal - modern BASIC's 'stole' a lot of Pascal functionality.
 
Actually.. The original was ALGOL which was very "BASIC" like. Pascal was derived from this! Ooh a few years back! The Function ( still called procedure ) came from ALGOL W which in turn was written by Niklaus Wirth, so Yes. Pascal.

ALGOrithmic Language. BUT!! we digress.
 
I'm testing it in reality and it works. The level of reliability ....... because each one decides.
The code below can appear to work fine, and probably 99% of the time it's ok.
It's that 1% of the time (or your bad luck) that will result in it failing.
You have to access _MILLIS at just the right moment in time to see the failure.
How are you testing it?

Code:
aux = _MILLIS - prev  'Auxiliary calculation
If aux > 1000 Then  'Wait time in mSec.
    prev = _MILLIS  'The last value of _MILLIS is stored.
    Toggle LedGreen  'Invers the state of the led.
Endif

There's no reason to accept unreliable operation when it's an easy fix to get it to work 100% of the time.
If "it works most of the time" is acceptable to you then you should print that right on the box of whatever you're selling and see how customers react.
 
In situations like this I find it best to mark the variable in question as 'private' to the ISR code, and force you to use an accessor function to get at it. That way you can't forget about it, can't cheat, and you can hide the details of making the access interrupt safe and appear 'atomic' .
 
There's an example on the Swordfish BASIC site that shows using a TMR + CCP to create millis() and micros() routines for a pic18F. These are more accurate than the original arduino versions (no timer reloading, no fudging around trying to correct usec delays, etc).

It also demonstrates two methods of safely accessing variables from both the ISR and main context.

See https://www.sfcompiler.co.uk/wiki/pmwiki.php?n=SwordfishUser.MsecAndUsecTimer
 
Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…