Mini-Tip: Tick.bas

swordfish 50Anyone who has used the Arduino libraries will have probably come across the "millis" command. This useful function allows the user program to time events in millisecond resolution. In Swordfish I have mostly seen complex timer setups, or wasteful delay loops to perform similar operations. After having used the Swordfish Ethernet libraries for a while, I discovered a particular gem buried in it; the tick.bas module. In the ethernet libraries it is used for keeping track of various bits of timing, but I realised that it would work perfectly well standalone. I have been using for various projects for a while now and using tick.bas gives us the same millis functionality with minimal setup.

Using tick.bas

Example.bas
Device = 18F27J53
Clock = 48
 
Public Config
   OSC = INTOSCPLLO,                  ' internal osc 8MHz
   PLLDIV = 2,                        ' usb 48MHz
   CPUDIV = OSC1,                     ' 48MHz cpu clock
   FCMEN = OFF,
   IESO = OFF,
   WDTEN = OFF,
   WDTPS = 2048,
   STVREN = ON,
   XINST = OFF
   'DEBUG = OFF
 
// Enable this option to use with the Microchip USB HID bootloader
//#option org_reset = $1000
 
// import modules...
Include "system.bas"
Include "Tick.bas"
 
Dim 
	Sec1Timer As TICK,
	Sec10Timer As TICK
 
{
********************************************************************************
* Name    : Main				                                               *
* Purpose : 															       *
********************************************************************************
}
Sub Main()
	TickInit()					// Startup the timer
	Sec1Timer = TickGet()				// set timers
	Sec10Timer = TickGet()
 
	While True
		If (TickGet() - Sec1Timer > 500) Then 	// Update LED1 every 500 millisecond?
 
			Sec1Timer = TickGet()		// Reset timer
			Toggle(PORTB.0)
 
		EndIf
 
		If (TickGet() - Sec10Timer > (TICK_SECOND * 5)) Then 	// Update LED2 every 5 seconds?
 
			Sec10Timer = TickGet()		// Reset timer
			Toggle(PORTB.1)
 
		EndIf
 
		// Can do other tasks here!
		Toggle(PORTB.2)		
	Wend
End Sub
 
Main()

In the example program, i've set up a half second LED toggle, a 5 second LED toggle and an "as fast as it can" LED toggle. The 1 second and 10 second "tasks" are non blocking, but the other tasks must complete in a reasonable timespan for the toggles to occcur in the determined time. If another routine blocks them, they will occur on the next available loop. Instead of burning through the loop as fast as we can, we could potentially sleep each loop as we should be woken by the timer0 interrupt each time it fires.

The advantage of the module is the user does not have to deal with preloading the timer, interrupts, or any other complex concepts.

Limitations

There is a potential issue when the tick variable rolls over. However, this will only occur after 49 days (a Longword is the ms store), and I believe if used as I have in the example, it will not cause any problems. I haven't personally has chance to test this running for 49 days!

If this is a possible problem, you could reduce the number of TICKS_PER_SECOND to 100 to give a 10mSec resolution which takes you to 497 days.

Another issue is that this method is not suitable for precision timing operations due to the variable delays in the main loop, but for general coarse timing operations in the millisecond range this works remarkably well.

Tick.bas
{
*****************************************************************************
*  Name    : Tick.bas                                                       *
*  Date    : 28/08/2007                                                     *
*  Version : 1.0 - Based around Microchip TCP/IP statck version 4.11        *
*  Notes   :                                                                *
* ========================================================================= *
* Software License Agreement                                                *
*                                                                           *
* Copyright © 2002-2007 Microchip Technology Inc.  All rights reserved.     *
*                                                                           *
* Microchip licenses to you the right to use, modify, copy, and distribute: *
* (i)  the Software when embedded on a Microchip microcontroller or digital *
*      signal controller product (Device) which is integrated into          *
*      Licensee product; or                                                 *
* (ii) ONLY the Software driver source files ENC28J60.c and ENC28J60.h      *
*      ported to a non-Microchip device used in conjunction with a          *
*      Microchip ethernet controller for the sole purpose of interfacing    *
*      with the ethernet controller.                                        *
*                                                                           *
* You should refer to the license agreement accompanying this Software for  *
* additional information regarding your rights and obligations.             *
*                                                                           *
* THE SOFTWARE AND DOCUMENTATION ARE PROVIDED AS IS WITHOUT WARRANTY OF     *
* ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION, ANY    *
* WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND  *
* NON-INFRINGEMENT. IN NO EVENT SHALL MICROCHIP BE LIABLE FOR ANY           *
* INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR   *
* LOST DATA, COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR         *
* SERVICES, ANY CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY   *
* DEFENSE THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER      *
* SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT (INCLUDING *
* NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.                            *
*****************************************************************************
}
Module NETTick
 
Public Type TICK = LongWord 
(*
 * This value is used by TCP To implement timeout actions.
 * If SNMP Module is in use, this value should be 100 As required
 * by SNMP protocol unless main application is providing separate
 * Tick which is 10mS.
*)
 
#define TICKS_PER_SECOND   = 1000                  // 1ms
//#define TICKS_PER_SECOND   = 100                  // 10ms
 
Public Const
   TICK_SECOND = TICKS_PER_SECOND
 
// Clock frequency value.
// This value is used to calculate Tick Counter value
Const
   CLOCK_FREQ         = _clock * 1000000,
   INSTR_FREQ         = CLOCK_FREQ / 4,  
   TICK_LOAD As Word = ((-(INSTR_FREQ + 2 + TICKS_PER_SECOND / 2) / 4 / TICKS_PER_SECOND) + 4)
 
// local variables...
Dim
   TickCount As TICK,    
   TMR0IF As INTCON.Booleans(2),
   TMR0IE As INTCON.Booleans(5),
   TMR0On As T0CON.Booleans(7),
   TMR0IPHigh As INTCON2.Booleans(2)   
(*
 *********************************************************************
 * Function:        TICK TickGetDiff(TICK a, TICK b)
 ********************************************************************/
*)   
Public Function TickGetDiff(As TICK, b As TICK) As TICK
   result = a - b
End Function
(*
 *********************************************************************
 * Function:                                          
 *********************************************************************
*) 
Interrupt OnTick(1)
   Dim Val As Word
   If TMR0IF Then
      Val.Byte0 = TMR0L
      Val.Byte1 = TMR0H
      Val = Val + TICK_LOAD
      TMR0H = Val.Byte1
      TMR0L = Val.Byte0
      Inc(TickCount)
      TMR0IF = false
   EndIf
End Interrupt
(*
 *********************************************************************
 * Function:        void TickInit(void)
 * PreCondition:    None
 * Input:           None
 * Output:          Tick manager is initialized.
 * Side Effects:    None
 * Overview:        Initializes Timer0 as a tick counter.
 * Note:            None
 ********************************************************************/
*)
Public Sub TickInit()
   // buffered write to TMR0...
   TMR0H = Byte(TICK_LOAD >> 8)
   TMR0L = Byte(TICK_LOAD)
 
   TMR0IF = false
   TMR0IE = true
#if TIMER_PRIORITY = ipLow
	TMR0IPHigh = false
#else	
	TMR0IPHigh = true
#endif
   T0CON = $81
   Enable(OnTick)
End Sub
(*
 *********************************************************************
 * Function:        TICK TickGet(void)
 * PreCondition:    None
 * Input:           None
 * Output:          Current tick value is given
 *					1 tick represents approximately 10ms
 * Side Effects:    None
 * Overview:        None
 * Note:            None
 ********************************************************************/
*)
Public Function TickGet() As TICK
   Disable(OnTick)
   result = TickCount
   Enable(OnTick)
End Function
 
'Public Inline Function TickGet() As TickCount
'End Function
 
// initialisation
TickCount = 0

 


Posted: 5 years 4 months ago by jmessina #12763
jmessina's Avatar
Great tip.

You can also use tick.bas to implement loops with timeouts, say displaying a message for a period of time unless someone presses a button, or waiting for some hardware event to occur without using 'DelayXX' calls
// the tick timer increments every 1ms
// as a doubleword, it wraps every 2^32 msecs, or 1193 hours (49 days)
// if used as a word, it wraps every 65536ms (65 secs)
// if used as a byte, it wraps every 255ms, which is good for short hardware delays
//
// this can be used for polled timeouts, such as the pseudo-code example:
    word elap_time
    word timeout = msecs_to_wait
    word start_time = TickGet()
    repeat
        <do stuff>
        if (stuff = done) then
            break
        endif		
        Watchdog.Reset()
        elap_time = TickGet() - start_time
    until (elap_time > timeout)
Also, you don't have to use longwords if you don't need such a long delay, which can save code and ram for quicker timeouts. Just be sure to unsigned data types and the math works up to the max overflow time of the data type/timer period
Posted: 5 years 4 months ago by be80be #12764
be80be's Avatar
Man this is a god sent I been looking for a way to do just this all weekend. Thanks
Posted: 5 years 4 months ago by W4GNS #12765
W4GNS's Avatar
I have spent 1 1/2 hours looking for this file. I have looked in my Swordfish files, DIY.com and the Swordfish wiki, no luck.
Posted: 5 years 4 months ago by RangerBob #12766
RangerBob's Avatar
I included it on the end of the article, just copy it from there. Otherwise as I mentioned, you'll find it buried in the ethernet libraries here.

To be fair be80be, I saw your thread last week with regards to setting up the timers; realised how useful the module would be to others and thats what prompted me to write this tip about it. Afraid it took a while to filter through the system, and I had limited t'internet last week to follow it up.

Regards
Posted: 5 years 4 months ago by Baldor #12772
Baldor's Avatar
This is very similar to what I use for the delays in the camera trigger. I set one timer to overflow every ms, and add to a long int every overflow. Then I have a "System time" i can read and compare to. This allows me to sincronize "complex" sequences of events. Ej:
 while (!detector);
 time = mscounter; 
 openshutter();
i = 1;
j= 1;
while (i or j) {

if (button1){ //Abort
i=0;
j=0;
}
if (i) {
if ((mscounter - time) >= flashdelay1){
flash1();
i= 0;
}
}
if (j){
if ((mscounter - time) >= flashdelay2){
flash2();
j=0;
}
}
}
closeshutter()

It only uses one timer, an interrupt on overflow, an a long int global variable.

It can be configured even for shorter "ticks", but for now, enough for the kind of photos i'm making.
Posted: 5 years 4 months ago by jmessina #12773
jmessina's Avatar
It only uses one timer, an interrupt on overflow, an a long int global variable.
Pretty much the same thing.

The only thing to watch out for when you're doing this is to make sure you use an unsigned int, and use a function to read the global variable that disables the timer intr while the multi-byte variable is accessed. Otherwise, you could get interrupted in the middle of accessing the global and the code can fail.
Posted: 5 years 4 months ago by be80be #12807
be80be's Avatar
One more time? I posted this two times it keep saying try agin LOL. Here a sample that show this used I changed the sub
hope You like it RangerBob
Device = 18F2520
Clock = 20
 
// Enable this option to use with the Microchip USB HID bootloader
//#option org_reset = $1000
 
// import modules...
Include "system.bas"
Include "Tick.bas"
 
Dim 
	Sec1Timer As TICK,
	Sec10Timer As TICK
 
sub main ()
	If (TickGet() - Sec1Timer > 500) Then 	// Update LED1 every 500 millisecond?
 
			Sec1Timer = TickGet()		// Reset timer
			Toggle(PORTB.0)
 
		EndIf
 
		If (TickGet() - Sec10Timer > (TICK_SECOND * 5)) Then 	// Update LED2 every 5 seconds?
 
			Sec10Timer = TickGet()		// Reset timer
			Toggle(PORTB.1)
 
		EndIf
 end sub
 Output (PORTB.0)
 Output (PORTB.1)
 Output (PORTB.2)
 Low (PORTB.0)
 Low (PORTB.1)
 Low (PORTB.2)
 TickInit()					// Startup the timer
 Sec1Timer = TickGet()				// set timers
 Sec10Timer = TickGet()
While true
	
	main()
			
    Toggle(PORTB.2)	
    DelayMS(250)
Wend
Posted: 5 years 4 months ago by be80be #12808
be80be's Avatar
RangerBob there to you have a Merry Christmas all
I set the tick bas faster so no one has to wait for it to count down it works great

Forum Activity

Member Access