One of the more useful, and misunderstood, functions on the Arduino platform is the millis() function.
millis() returns the elapsed time in milliseconds since the device was last reset. A 32 bit unsigned Long variable contains the count and will only rollover in slightly over 49 days, making it useful for both very small and very long timing situations. Many people assume that the millis() function is part of the C compiler, especially as a similar function is available to Compilers under Linux and Windows. As a result they ask why other versions of C, especially on the PIC platform, do not have such a function.
What they miss is that millis(), whilst implemented in software, is not a software function it is a doorway to a hardware timer. Arduino is a platform, not just a bare chip, and certain hardware is devoted to Arduino Functions in the same way that windows and linux use features of the motherboard chipset. In fact the equivalent of the millis() function predates microcontrollers and PC’s as it was a function of early discreet logic PLC’s and was used to record system up time for maintenance.
So what is Millis() good for?
millis() is a simple way of using a Hardware Timer without having to know anything about Hardware Timers.
In its simplest form millis() is a record of how long the system has been running since the last reset, think UNIX Time on a smaller scale, but it can also be used to time events, control the duration of actions, create a Clock / Calendar or replace the Wait or Delay Function amongst many other uses. Trying to port applications from Arduino to GCBASIC can be a daunting task if the application relies on functions such as millis(). In addition to that anyone who has ever used the millis() function will soon long for an equivalent function in their current platform.
More importantly, however, It allows user applications to implement rudementery multitasking and, for 8 Bit devices with limited resources, that alone makes it worth more than the effort required to implement it, so I set out to find a simple way to implement a millis() function as part of the GCBASIC language.
Implementation
Setting up a millis() function is not a particularly difficult task but, as it relies on a hardware timer, it could easily result in portability issues unless proper consideration is given in the design process. GCBASIC Supports PIC devices in the 12F, 16F, Enhanced 16F and 18F families along with AVR devices used by the Arduino family of boards. They are all different architectures but all of them have at least one hardware Timer and, as a hardware timer is the basis of the Millis function, all we need to do is set it up to generate an Interrupt every Millisecond and then count them.
Each family or architecture has its own set of registers to control the onboard timers whilst the devices themselves are are all capable of being clocked at a range of clock speeds, but with careful programming GCBASIC can hide a lot of that complexity through the use of Functions and Scripts, completely hidden from the users in the GCBASIC Preprocessor. As a result, once the hardware differences are factored out in the preprocessor, simple code written to use a Timer on the ATmega328p, the device in the Arduino UNO, or a PIC device on a Breadboard, should then run on a similar Timer on any of the supported device families, regardless of architecture.
I chose to use Timer0 for the millis() function as that timer is the most common amongst the supported devices, has a similar architecture regardless of family, is rarely used for other hardware functions such as PWM and being 8 bit (in most architectures) is the least useful timer in most applications.
Future Versions will hopefully be able to use alternative timers but, unless there is a major conflict with other core libraries that I am unaware of (Highly likely) that is not a short term goal and more of a convenience for advanced users.
All that remains is to implement it in such a way that it is easy to use for experienced Arduino Users and simple enough for PIC and other GCBASIC users to begin to utilise the benefits of a millis() Function on their chosen hardware platform too.
millis() version 0.1
This first release of the code is a Proof of concept / Beta Test release. Hopefully it will - once tested, massaged, expanded and polished by the GCBASIC community - become part of the GCBASIC Language.
The proof of concept was written and tested on Arduino UNO hardware for easy correlation and testing with the Arduino version of the function. It was then ported to a Microchip PIC16F690 microcontroller on a Low Pin Count Demo Board for comparison, and then tested on several PIC devices of varying Vintage and Clock Speed on the LPCDB. Once acceptable results were obtained Development and testing progressed satisfactorily to the 28 Pin demo board and a selection of Enhanced Core PIC16 and PIC18 devices.
The following points apply as a result:
1. As the Arduino UNO has a fixed clock rate of 16 Mhz that is the only supported clock rate for AVR devices as of this post.
2. The PIC has an internal clock which it divides by 4 before passing to the Prescaler, so a larger range of frequencies are available to PIC devices.
3. The Frequencies implemented so far are Binary divisions i.e 1Mhz, 2MHz, 4Mhz, 8Mhz, 16Mhz, 32Mhz and 64Mhz (The PIC18F26K22 was used to test 64Mhz)
4. Tested devices work at their default frequency or any frequency specified. i.e. #Chip 16F690, 2
5. If a frequency higher than the device is capable of is selected it should revert to its default frequency, however, unexpected results could be observed as this is not explicitly tested for.
6. Future versions may use scripts to setup the Timers to cope with additional and arbitrary Clock Speeds.
7. Despite not being integrated into the core Libraries Millis is still easy to use if you wish to test and give feedback to aid development. Testing and Feedback
If you would like to try the millis Function download the attached “millis.h” and place it in a working folder along with any *.gcb files that use it, then in the source for your GCBASIC program add the line:
#include “millis.h”
The quotes are important, do not remove them.
The two examples in below are a new take on the old BLINK function that most of us recall as our first ever program and serves to shows that portability has been achieved.
'#chip mega328p, 16 ' Declare the Target Processor and Speed#option explicit ' Require Explicit declaration of Variables#include"millis.h" ' Include the Library#define LED PORTB.5 ' Define the LED Pin - Digital Pin 13#define LEDRate 1000 ' Flash rate in mS'SetupDirLEDOut'MaketheLEDPinanOutputDimCurMs,LstMsasWord'declareworkingvariables'Main'Thislooprunsoverandoverforever.DoCurMs=millis()ifCurMs-LstMs>=LEDRatethen'requiredTimehasElapsedLED=!LED'SoTogglestateofLEDLstMs=CurMs'AndRecordToggleTimeendifLoop
By changing only the #Chip field and, of course the LED Pin, this same code will run on a PIC16F690 on a Microchip Low Pin Count Demo Board:
'#chip 16F690,8 ' Declare the Target Processor and Speed#option explicit ' Require Explicit declaration of Variables#include"millis.h" ' Include the Library'#define LED PORTC.0 ' Define the LED Pin#define LEDRate 1000 ' Flash rate in mS'SetupDirLEDOut'MaketheLEDPinanOutputDimCurMs,LstMsasWord'declareworkingvariables'Main'Thislooprunsoverandoverforever.DoCurMs=millis()ifCurMs-LstMs>=LEDRatethen'requiredTimehasElapsedLED=!LED'SoTogglestateofLEDLstMs=CurMs'AndRecordToggleTimeendifLoop
With Arduino millis() compatibility, Ease of use and Device Portability all in the bag we can now start to look at improvements and enhancements. But first I will run through a set of experiments to show why millis() is important and a couple of features that go beyond the Arduino version of millis, once again showing that GCBASIC is the better tool, even on the Arduino platform.
Subsequent posts in this thread will hold examples and descriptions in tutorial format to help you get started with millis() and will hopefully scratch the surface of the abilities of this useful function so stay tuned
Cheers
Chris
EDIT - Attachment updated to detect unsupported Devices
Description Returns the number of milliseconds since the Device began running the current program. This number will overflow (go back to zero), after approximately 50 days.
Syntax
time = millis()
Parameters Nothing
Returns
Number of milliseconds since the program started as a 32bit value (unsigned long)
Last edit: Chris Roper 2019-01-02
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
In the first post I showed an example that used millis() as an alternative to the wait x mS format.
Which raises the obvious question of why use a function that requires a hardware timer 177 words of Flash and 21 bytes of RAM when the wait requires no hardware timers, 55 words of flash and only 1 byte of RAM to do the same thing.
Well if you were creating a device with the sole purpose of toggling an LED every second then either way would work as they hardly impact the chips resources anyway. But if the device was intended to do other things in addition to flashing an LED then millis becomes the better choice, here's why.
The wait function is a tight loop that throws away instruction cycles, decrementing a number and looping back until the required time has elapsed. So whilst wait is executing the device can do nothing else.
Millis on the other hand is a simple comparison test.
The Start time is stored in LstMs and periodically compared to the current time, if the desired period LEDRate, has been reached or exceeded then the action is taken else the device is free to do other things whilst the milliseconds tick away in the background.
As an example let's see what happens if we need to Flash 2 LEDs at different rates.
'
#define LED0 PORTC.0 ' Define the LED0 Pin
#define LEDRate0 1000 ' Flash 0 rate in mS
#define LED1 PORTC.1 ' Define the LED1 Pin
#define LEDRate1 250 ' Flash 1 rate in mS
' Setup
Dir LED0 Out ' Make the LED0 Pin an Output
Dir LED1 Out ' Make the LED1 Pin an Output
'
Dim CurMs as Word ' declare working variables
Dim LstMs0, LstMs1 as Word ' declare working variables
' Main ' This loop runs over and over forever.
Do
CurMs = millis() ' Read current time
if CurMs - LstMs1 >= LEDRate1 then ' required Time has Elapsed
LED1 = !LED1 ' So Toggle state of LED
LstMs1 = CurMs ' And Record Toggle Time
end if
if CurMs - LstMs0 >= LEDRate0 then ' required Time has Elapsed
LED0 = !LED0 ' So Toggle state of LED
LstMs0 = CurMs ' And Record Toggle Time
end if
Loop
Run the above code and you will have two LEDs flashing simultaneously and independently of each other.
The real challenge is to do that with the wait statement, it is nigh on impossible.
Last edit: Chris Roper 2019-01-02
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Ram use can be an issue on smaller PIC’s and although the last example only used 24 bytes of RAM - 3 more than the single flash - it can still add up. One feature of the GCBASIC implementation of millis() that can’t be reproduced easily in the Arduino Compiler is bit testing. As most of the time flashing LEDs are just an indication of a state or that something is running, accurate timing is rarely that critical. If that is the case we can recreate the Dual flash example with flash rates of 1023 mS and 255 mS and save both Flash and RAM like this:
'#chip 16F690,8 ' Declare the Target Processor and Speed#option explicit ' Require Explicit declaration of Variables#include"millis.h" ' Include the Library'#define LED0 PORTC.0 ' Define the LED0 Pin#define LED1 PORTC.1 ' Define the LED1 Pin'SetupDirLED0Out'MaketheLED0PinanOutputDirLED1Out'MaketheLED1PinanOutput''Main'Thislooprunsoverandoverforever.DoLED0=millis.10LED1=millis.8Loop
RAM use is now down to only 15 Bytes and the difference in the Blink rate is hardly noticeable to the human eye.
The source code is a lot less readable but if RAM is getting tight it is worth doing, provided you comment the code well.
Last edit: Chris Roper 2019-01-02
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Testing has shown that millis fails silently on newer PIC16 devices with the 8/16 Bit Timer0.
We are working on a solution to support these devices but, in the interim , the attached Version of "millis.h" will issue an error and abort the compile if an unsupported device is detected.
Hello Chris, this is excellent. I had used something like this in the past borrowed from some code from this forum a while back. You may have been the one that posted it I believe. It has been working great. I will try this one now as an include.
I saw this statement in another thread about millis...
It returns a Long variable value
'that will return to zero after 49 days of running. If checking
'for values being greater than millis() in programs intended to
'run for long periods, remember to watch for it resetting to zero.
I am trying to wrap my head around what would happen if it resets to zero and if that case would wreak havic of somekind. Can you describe what would happen and can it be handled someway?
Thanks for your efforts, this is good stuff!
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
But millis() does not seem to return a zero value on initialising the device.
On my test for the 16F1825 the initial value of millis() gave values of:
419,373,823
419,103,487
419,365,631
To give a timing of mS since the device started I'm having to store the initial value of millis() and then deduct this from the current value.
Partial code follows:
#Chip16F1825,32#Optionexplicitinclude"millis.h"DimInitial_MillisAsLongDimCurrent_MillisAsLongDimCurMsAsWordDimLstMsAsWord' turn on the RS232 and terminal port.' Define the USART port#defineUSART_BAUD_RATE9600#defineUSART_BLOCKING#defineSerOutPortportC.4'Pin 6#DefineOutPinPortC.5'Pin 5'Set pin directionsDirSerOutPortOut'Pin 6DirOutPinOut'Pin 5#DefineLEDRate1000'mSLetInitial_Millis=Millis()DoIfInitial_Millis>Millis()ThenLetInitial_Millis=Millis()EndIfLetCurrent_Millis=Millis()-Initial_MillisHSerPrintCurrent_MillisCurMs=millis()IfCurMs-LstMs>=LEDRateThen'Required Time has ElapsedLetOutPin=!OutPin'So Toggle state of LEDLetLstMs=CurMs'And Record Toggle TimeEndIfLoop
Is this the expected behavior or have I done something daft again?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
If Initial_Millis > Millis() Then
Let Initial_Millis = Millis()
End If
Let Current_Millis = Millis() - Initial_Millis
HSerPrint Current_Millis
I am not even sure what it is attempting to achieve.
This is the correct code (also copied from your code) for measuring an elapsed period:
~~~
CurMs = millis()
If CurMs - LstMs >= LEDRate Then ' Required Time has Elapsed
doTimedEvent() ' Do something
Let LstMs = CurMs ' And Record Toggle Time
End If
~~~
The longest period timed in my examples is 1000 mS so I dimensioned CurMs and LstMs as Word to save memory. They should really be dimensioned as Long and if you were counting a period that would not fit in a word then Long is essential.
Hope that helps clarify.
Cheers
Chris
Last edit: Chris Roper 2019-01-04
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Ahhh... That code is printing out the value of millis(), since the processor was started. I was thinking I could use millis() as a runtime counter, but as it appears to start at some nominal value (rather than zero), when my program starts I initially store the value of millis() in a long variable and calculate the 'run time' by deducting the initial value, from the value returned by the millis() function.
IfInitial_Millis>Millis()Then'If the Initial_Millis value is greater than the current value, assume millis() has rolled over.LetInitial_Millis=Millis()'Store the new value of millis() in the Initial_Millis variableEndIfLetCurrent_Millis=Millis()-Initial_Millis'Calculate the time since the processor started by deducting the initial value from the current valueHSerPrintCurrent_Millis'Print the time since the processor started
For shorter timings (such as your example) the start value of millis() is, as you rightly point out, irrelevant.
Sorry if I have misunderstood.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Rollover should not be a problem as the count is stored as an unsigned long value.
If you need a long period you would also have to store the StartTime as a long and then subtract the StartTime from the current millis and compare it to the required duration in mS.
As all variables are unsigned the math's will work out as the difference will still be the same.
That is in theory, one could set up an experiment and let it run for 50 days to see what happens, but mathematically it should work.
More detailed discussion can be found in the Arduino forums.
Cheers
Chris
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
If you need a long period you would also have to store the StartTime as a long and then subtract the StartTime from the current millis and compare it to the required duration in mS.
As all variables are unsigned the math's will work out as the difference will still be the same.
That is in theory, one could set up an experiment and let it run for 50 days to see what happens, but mathematically it should work.
I see what you are saying. So my:
IfInitial_Millis>Millis()Then'If the Initial_Millis value is greater than the current value, assume millis() has rolled over.LetInitial_Millis=Millis()'Store the new value of millis() in the Initial_Millis variableEndIf
Is irrelevant as both Initial_Millis and millis() are unsigned Long variables.
That makes sense.
Thanks for the clarification. And sorry again for not grasping this earlier.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Nothing wrong with not instantly grasping it, that is part of learning and we all have to do it.
Infact the debate still rages on the Arduino forums. That is why I opened the thread with:
"One of the more useful, and misunderstood, functions on the Arduino platform "
I even had to run through the maths myself before I trusted it.
Cheers
Chris
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I have left my 'test' running for a total of 4,294,967,295 mS. All seemed happy once the rollover ocurred. My program comprised of a button test that uses the millis() function to time how long the button was pressed for, dispaying this, along with the current value of millis() on an LCD display.
Out of interest to probably no one, I have uploaded a video of the final ten seconds or so of the test as proof of functionality. Nothing went bang, and the program continued to work as expected.
Many, many thanks to Chris for this function, I will find it extremely useful as I'm sure will many others.
We will include millis() in the corr compiler within the next release. Currently (March 19) we are resolving an issue with 18fxK42 chips - when resolved I will update this thread.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
As much as I trust math's it is always comforting to know that the theory worked in practice - especially as I never would have had the patience to run a test for that long :)
We held off publication because of concerns around compatibility issues on certain devices and, indeed, it proved to be a good stress test in general for both the dev's and the devices.
We flagged a few issues for Microchip to solve and a few for ourselves, but nothing was broken beyond repair.
I see Evan replied about the release dates so I have nothing new to add but my thanks.
Cheers
Chris
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Nice work on this!!! @mkstevo, thanks for posting your rollover results and nice to see it works perfectly. BTW, how do you like your 4 piece tweezer set??? Kidding...
millis is a great and extremely useful function to have, thanks for the effort! Glad to see it will be included with the compiler.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Great News - Change log entry: 615 New Millis - Initial formal release of millis.h
It is ready for real time (Pun Intended) and will be included in the upcoming release of Great Cow BASIC as a core Library.
It has been extensively tested on all PIC (10,12,16,18) and AVR families supported by GCBASIC and example files will be included in the release.
The holdup was caused by uncovering a couple of low level compiler errors which have now been fixed so, whilst Evan and I have been silent for the past few weeks, millis was never dead it was still being used as a debug tool to stress test the compiler.
It has already proven useful in a couple of projects too so I expect to see more when it is formally released with the New Compiler and the community get to use it too.
Cheers
Chris
Last edit: Chris Roper 2019-04-04
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Introduction
One of the more useful, and misunderstood, functions on the Arduino platform is the millis() function.
millis() returns the elapsed time in milliseconds since the device was last reset. A 32 bit unsigned Long variable contains the count and will only rollover in slightly over 49 days, making it useful for both very small and very long timing situations. Many people assume that the millis() function is part of the C compiler, especially as a similar function is available to Compilers under Linux and Windows. As a result they ask why other versions of C, especially on the PIC platform, do not have such a function.
What they miss is that millis(), whilst implemented in software, is not a software function it is a doorway to a hardware timer. Arduino is a platform, not just a bare chip, and certain hardware is devoted to Arduino Functions in the same way that windows and linux use features of the motherboard chipset. In fact the equivalent of the millis() function predates microcontrollers and PC’s as it was a function of early discreet logic PLC’s and was used to record system up time for maintenance.
So what is Millis() good for?
millis() is a simple way of using a Hardware Timer without having to know anything about Hardware Timers.
In its simplest form millis() is a record of how long the system has been running since the last reset, think UNIX Time on a smaller scale, but it can also be used to time events, control the duration of actions, create a Clock / Calendar or replace the Wait or Delay Function amongst many other uses. Trying to port applications from Arduino to GCBASIC can be a daunting task if the application relies on functions such as millis(). In addition to that anyone who has ever used the millis() function will soon long for an equivalent function in their current platform.
More importantly, however, It allows user applications to implement rudementery multitasking and, for 8 Bit devices with limited resources, that alone makes it worth more than the effort required to implement it, so I set out to find a simple way to implement a millis() function as part of the GCBASIC language.
Implementation
Setting up a millis() function is not a particularly difficult task but, as it relies on a hardware timer, it could easily result in portability issues unless proper consideration is given in the design process. GCBASIC Supports PIC devices in the 12F, 16F, Enhanced 16F and 18F families along with AVR devices used by the Arduino family of boards. They are all different architectures but all of them have at least one hardware Timer and, as a hardware timer is the basis of the Millis function, all we need to do is set it up to generate an Interrupt every Millisecond and then count them.
Each family or architecture has its own set of registers to control the onboard timers whilst the devices themselves are are all capable of being clocked at a range of clock speeds, but with careful programming GCBASIC can hide a lot of that complexity through the use of Functions and Scripts, completely hidden from the users in the GCBASIC Preprocessor. As a result, once the hardware differences are factored out in the preprocessor, simple code written to use a Timer on the ATmega328p, the device in the Arduino UNO, or a PIC device on a Breadboard, should then run on a similar Timer on any of the supported device families, regardless of architecture.
I chose to use Timer0 for the millis() function as that timer is the most common amongst the supported devices, has a similar architecture regardless of family, is rarely used for other hardware functions such as PWM and being 8 bit (in most architectures) is the least useful timer in most applications.
Future Versions will hopefully be able to use alternative timers but, unless there is a major conflict with other core libraries that I am unaware of (Highly likely) that is not a short term goal and more of a convenience for advanced users.
All that remains is to implement it in such a way that it is easy to use for experienced Arduino Users and simple enough for PIC and other GCBASIC users to begin to utilise the benefits of a millis() Function on their chosen hardware platform too.
millis() version 0.1
This first release of the code is a Proof of concept / Beta Test release. Hopefully it will - once tested, massaged, expanded and polished by the GCBASIC community - become part of the GCBASIC Language.
The proof of concept was written and tested on Arduino UNO hardware for easy correlation and testing with the Arduino version of the function. It was then ported to a Microchip PIC16F690 microcontroller on a Low Pin Count Demo Board for comparison, and then tested on several PIC devices of varying Vintage and Clock Speed on the LPCDB. Once acceptable results were obtained Development and testing progressed satisfactorily to the 28 Pin demo board and a selection of Enhanced Core PIC16 and PIC18 devices.
The following points apply as a result:
1. As the Arduino UNO has a fixed clock rate of 16 Mhz that is the only supported clock rate for AVR devices as of this post.
2. The PIC has an internal clock which it divides by 4 before passing to the Prescaler, so a larger range of frequencies are available to PIC devices.
3. The Frequencies implemented so far are Binary divisions i.e 1Mhz, 2MHz, 4Mhz, 8Mhz, 16Mhz, 32Mhz and 64Mhz (The PIC18F26K22 was used to test 64Mhz)
4. Tested devices work at their default frequency or any frequency specified. i.e. #Chip 16F690, 2
5. If a frequency higher than the device is capable of is selected it should revert to its default frequency, however, unexpected results could be observed as this is not explicitly tested for.
6. Future versions may use scripts to setup the Timers to cope with additional and arbitrary Clock Speeds.
7. Despite not being integrated into the core Libraries Millis is still easy to use if you wish to test and give feedback to aid development.
Testing and Feedback
If you would like to try the millis Function download the attached “millis.h” and place it in a working folder along with any *.gcb files that use it, then in the source for your GCBASIC program add the line:
The quotes are important, do not remove them.
The two examples in below are a new take on the old BLINK function that most of us recall as our first ever program and serves to shows that portability has been achieved.
By changing only the #Chip field and, of course the LED Pin, this same code will run on a PIC16F690 on a Microchip Low Pin Count Demo Board:
With Arduino millis() compatibility, Ease of use and Device Portability all in the bag we can now start to look at improvements and enhancements. But first I will run through a set of experiments to show why millis() is important and a couple of features that go beyond the Arduino version of millis, once again showing that GCBASIC is the better tool, even on the Arduino platform.
Subsequent posts in this thread will hold examples and descriptions in tutorial format to help you get started with millis() and will hopefully scratch the surface of the abilities of this useful function so stay tuned
Cheers
Chris
EDIT - Attachment updated to detect unsupported Devices
Last edit: Chris Roper 2019-01-03
Using millis()
Description
Returns the number of milliseconds since the Device began running the current program. This number will overflow (go back to zero), after approximately 50 days.
Syntax
time = millis()
Parameters
Nothing
Returns
Number of milliseconds since the program started as a 32bit value (unsigned long)
Last edit: Chris Roper 2019-01-02
Why use millis rather than wait ?
In the first post I showed an example that used millis() as an alternative to the wait x mS format.
Which raises the obvious question of why use a function that requires a hardware timer 177 words of Flash and 21 bytes of RAM when the wait requires no hardware timers, 55 words of flash and only 1 byte of RAM to do the same thing.
Well if you were creating a device with the sole purpose of toggling an LED every second then either way would work as they hardly impact the chips resources anyway. But if the device was intended to do other things in addition to flashing an LED then millis becomes the better choice, here's why.
The wait function is a tight loop that throws away instruction cycles, decrementing a number and looping back until the required time has elapsed. So whilst wait is executing the device can do nothing else.
Millis on the other hand is a simple comparison test.
The Start time is stored in LstMs and periodically compared to the current time, if the desired period LEDRate, has been reached or exceeded then the action is taken else the device is free to do other things whilst the milliseconds tick away in the background.
As an example let's see what happens if we need to Flash 2 LEDs at different rates.
Run the above code and you will have two LEDs flashing simultaneously and independently of each other.
The real challenge is to do that with the wait statement, it is nigh on impossible.
Last edit: Chris Roper 2019-01-02
Ram use can be an issue on smaller PIC’s and although the last example only used 24 bytes of RAM - 3 more than the single flash - it can still add up. One feature of the GCBASIC implementation of millis() that can’t be reproduced easily in the Arduino Compiler is bit testing. As most of the time flashing LEDs are just an indication of a state or that something is running, accurate timing is rarely that critical. If that is the case we can recreate the Dual flash example with flash rates of 1023 mS and 255 mS and save both Flash and RAM like this:
RAM use is now down to only 15 Bytes and the difference in the Blink rate is hardly noticeable to the human eye.
The source code is a lot less readable but if RAM is getting tight it is worth doing, provided you comment the code well.
Last edit: Chris Roper 2019-01-02
Thanks Chris! Very useful tool.
Nice to know. Thanks for explaining. Hope it gets documented.
Many thanks for this. I really appreciate your hard work.
Testing has shown that millis fails silently on newer PIC16 devices with the 8/16 Bit Timer0.
We are working on a solution to support these devices but, in the interim , the attached Version of "millis.h" will issue an error and abort the compile if an unsupported device is detected.
The First Post attachment has also been updated.
Cheers
Chris
Last edit: Chris Roper 2019-01-03
Hello Chris, this is excellent. I had used something like this in the past borrowed from some code from this forum a while back. You may have been the one that posted it I believe. It has been working great. I will try this one now as an include.
I saw this statement in another thread about millis...
I am trying to wrap my head around what would happen if it resets to zero and if that case would wreak havic of somekind. Can you describe what would happen and can it be handled someway?
Thanks for your efforts, this is good stuff!
Showing my lack of understanding here I'm sure...
But millis() does not seem to return a zero value on initialising the device.
On my test for the 16F1825 the initial value of millis() gave values of:
419,373,823
419,103,487
419,365,631
To give a timing of mS since the device started I'm having to store the initial value of millis() and then deduct this from the current value.
Partial code follows:
Is this the expected behavior or have I done something daft again?
This code is redundant
I am not even sure what it is attempting to achieve.
This is the correct code (also copied from your code) for measuring an elapsed period:
~~~
CurMs = millis()
If CurMs - LstMs >= LEDRate Then ' Required Time has Elapsed
doTimedEvent() ' Do something
Let LstMs = CurMs ' And Record Toggle Time
End If
~~~
The longest period timed in my examples is 1000 mS so I dimensioned CurMs and LstMs as Word to save memory. They should really be dimensioned as Long and if you were counting a period that would not fit in a word then Long is essential.
Hope that helps clarify.
Cheers
Chris
Last edit: Chris Roper 2019-01-04
Ahhh... That code is printing out the value of millis(), since the processor was started. I was thinking I could use millis() as a runtime counter, but as it appears to start at some nominal value (rather than zero), when my program starts I initially store the value of millis() in a long variable and calculate the 'run time' by deducting the initial value, from the value returned by the millis() function.
For shorter timings (such as your example) the start value of millis() is, as you rightly point out, irrelevant.
Sorry if I have misunderstood.
Rollover should not be a problem as the count is stored as an unsigned long value.
If you need a long period you would also have to store the StartTime as a long and then subtract the StartTime from the current millis and compare it to the required duration in mS.
As all variables are unsigned the math's will work out as the difference will still be the same.
That is in theory, one could set up an experiment and let it run for 50 days to see what happens, but mathematically it should work.
More detailed discussion can be found in the Arduino forums.
Cheers
Chris
I see what you are saying. So my:
Is irrelevant as both Initial_Millis and millis() are unsigned Long variables.
That makes sense.
Thanks for the clarification. And sorry again for not grasping this earlier.
Nothing wrong with not instantly grasping it, that is part of learning and we all have to do it.
Infact the debate still rages on the Arduino forums. That is why I opened the thread with:
"One of the more useful, and misunderstood, functions on the Arduino platform "
I even had to run through the maths myself before I trusted it.
Cheers
Chris
I have left my 'test' running for a total of 4,294,967,295 mS. All seemed happy once the rollover ocurred. My program comprised of a button test that uses the millis() function to time how long the button was pressed for, dispaying this, along with the current value of millis() on an LCD display.
Out of interest to probably no one, I have uploaded a video of the final ten seconds or so of the test as proof of functionality. Nothing went bang, and the program continued to work as expected.
Many, many thanks to Chris for this function, I will find it extremely useful as I'm sure will many others.
15 second demonstration video
Last edit: mkstevo 2019-03-12
Great stuff. Really good to read this!
We will include millis() in the corr compiler within the next release. Currently (March 19) we are resolving an issue with 18fxK42 chips - when resolved I will update this thread.
As much as I trust math's it is always comforting to know that the theory worked in practice - especially as I never would have had the patience to run a test for that long :)
We held off publication because of concerns around compatibility issues on certain devices and, indeed, it proved to be a good stress test in general for both the dev's and the devices.
We flagged a few issues for Microchip to solve and a few for ourselves, but nothing was broken beyond repair.
I see Evan replied about the release dates so I have nothing new to add but my thanks.
Cheers
Chris
Nice work on this!!! @mkstevo, thanks for posting your rollover results and nice to see it works perfectly. BTW, how do you like your 4 piece tweezer set??? Kidding...
millis is a great and extremely useful function to have, thanks for the effort! Glad to see it will be included with the compiler.
Great News - Change log entry: 615 New Millis - Initial formal release of millis.h
It is ready for real time (Pun Intended) and will be included in the upcoming release of Great Cow BASIC as a core Library.
It has been extensively tested on all PIC (10,12,16,18) and AVR families supported by GCBASIC and example files will be included in the release.
The holdup was caused by uncovering a couple of low level compiler errors which have now been fixed so, whilst Evan and I have been silent for the past few weeks, millis was never dead it was still being used as a debug tool to stress test the compiler.
It has already proven useful in a couple of projects too so I expect to see more when it is formally released with the New Compiler and the community get to use it too.
Cheers
Chris
Last edit: Chris Roper 2019-04-04
hello where can i donwload millis library? it is no included in GCB donwload
This is in the Demo folder ..\GCB@Syn\GreatCowBasic\Demos\millis_solutions or grab from GitHub https://github.com/Anobium/Great-Cow-BASIC-Demonstration-Sources/tree/master/Millis%20Solutions