<b>Poorman's Multitasking, Events, & Ticks </b>
<b>Multitasking </b>
- Real multitasking? - Use a bigger processor. C and/or Assembler
- Simplified? - Use a Picaxe
<b>Event Programming </b>
- Simplified? - Use a Picaxe
<b>System Time & Ticks </b>
- Fine precision & fast? - Use a bigger processor. Assembler
- Slower? - Use a Picaxe
Interested? Follow this thread.
No processor short of a dual core Pentium can "really (approximately)" multi-task its jobs. At some level, it is done using some way of interleaving short bursts of servicing each task in an appropriate sequence. Jumping between tasks is usually so fast as to be quite invisible at human level.
The Picaxe <b>Basic </b> is not multitasking. Certain of its commands are positively the enemy of multitasking - like PAUSE or PULSOUT or SERIN. Nothing else can be happening while such commands take their time to finish.
But multitasking software is satisfying to program. And it can make understanding a complex system a lot easier and less error-prone.
The good news is that with some care in structuring your program, a very usable degree of "multitasking" can be achieved on the Picaxe.
The simplest form is to arrange your program like this:
<code><pre><font size=2 face='Courier'>
main:
' Do one-off startup things
' fall through to loop below:
perpetual_loop:
Gosub Job1
Gosub Job2
Gosub Job2
Goto perpetual_loop
</font></pre></code>
All commands that hold up the processor are banned (or need a work-around). All your real program content is divided up into short logical "jobs". All jobs MUST as quickly as absolutely possible do what is needed, and return. The aim is to see that "perpetual-loop" cycled through as often as possible.
Things that need to be timed (eg a certain length pulse on a LED) must find some command rather smarter than PAUSE or PULSOUT. And so there needs to be some independent time-keeping system that can be looked at later to see if that LED should turn off yet! The Picaxe <b>Basic </b> does not have such a "system timer", but we can program one fairly easily.
These concepts get various names by different people, but I tend to use these terms myself:
<code><pre><font size=2 face='Courier'>
- Executive or executive loop - That central "perpetual-loop" that lists all the Gosub routines.
(That term executive on bigger systems can mean a very complicated software section
which apportions "time slices" out optimally to all the jobs needing done.)
- Driver routine - The code in each of those Gosubs.
- Round-robin - Describes the no-brainer regular calling of each driver in turn in our loop.
- System timer - an accumulating number counting the number of microsecs/secs/mins since the world began
(ie, since the processor last reset!!). It is NOT a calendar clock.
- Tick - a very regular pulse based on the system timer. The tick can be used to easily trigger some actions on
a known timing. Not essential, but a VERY handy auxiliary to the system timer.
- Flag - a stored variable that represents something is currently ON or OFF (whatever that means).
- Event - Something that happens, and which will become noticed somewhere, causing another action.
Often stored as a flag, waiting for someone to notice.
</font></pre></code>
If we can discipline our Picaxe programming like this, we really can have our "poorman's multitasking."
=======================================================================================
The main missing item on the Picaxe is the system timer. The code below is (extracted) from a working project of mine, and shows a way to implement a timer. It does not count in microseconds or even milliseconds. It is a smaller system timer, matching a smaller processor, our Picaxe.
Specifications: Uses the internal "timer1" part of the Picaxe chip. The system timer stores elapsed seconds (23 bit half-second accuracy) for up to 45 days. And you can easily read time in minutes (16 bit). Your own code has a 1 per second "tick" flag and a 1 per minute flag available to trigger certain functions.
Long term precision is good, dependent mainly on stability/accuracy of Picaxe resonator. Accuracy is another story. Each counted "second" is really 1048 mSec. And each counted "minute" is 64 such "seconds". But it is "free".
With the system timer in place, I found that I could get about 90 loops per second through the "Executive Loop" of the code with empty user driver routines (as below). In practice with a fully coded 28X project, I usually get about 15 to 30 passes per second ("LoopsPerTik". If your code holds the processor in any one loop to a time seriously over 524 mSec, you would start missing tick detections. Depending on your own job, that might be a serious problem or not.
Anyway, here is my code. You should be able to cut & paste it to your programming editor, compile & upload, and turn on the terminal window (F8) to see the achieved loops/second your code is running at:
<code><pre><font size=2 face='Courier'>
'---------------------------------------------------------------------
' 45 day system tick timer for Picaxe
' 1 second / tick.
' By: Brian Lavery (Queensland Australia)
' Inspired by:
' The Happy Hippy (http://www.hippy.freeserve.co.uk/picaxert.htm) <A href='http://www.hippy.freeserve.co.uk/picaxert.htm' Target=_Blank>External Web Link</a>
' homepage.ntlworld.com/the.happy.hippy/picaxe/timers.zip <A href='http://homepage.ntlworld.com/the.happy.hippy/picaxe/timers.zip' Target=_Blank>External Web Link</a>
'---------------------------------------------------------------------
' I used 4 Mhz 28X, which uses PIC chip 16F873.
' If you are smart enough to use higher speed Picaxes, then you should be smart enough to adapt the code below!
' Disclaimer: Same timer function should work on most Picaxes, I believe, exc old 08.
' A quick look at the several PIC documents suggests the internal Timer1 function is similar.
' (Assumes Picaxe designers don't in future modify the internal code to use PIC's Timer1)
' Not checked, but our code possibly does conflict with some Picaxe commands like PULSIN,
' but such timed commands are not OK in our programming style anyway.
' Enhanced compiler 5.0.7
' Defined values found from the PIC document for 16F873 chip:
symbol PIC_TMR1H = 0x0F ' (MSB) PIC Timer1 data (counter) register
symbol PIC_T1CON = 0x10 ' PIC Timer1 Control Register
' GP Picaxe bit variables that must be PRESERVED at all times in your code:
symbol Tick_flag = bit0 ' set for 1 exec loop per tick (second)
symbol Tick_hi = bit1 ' hi or lo half second of each tick
symbol Mins_Flag = bit2 ' like the 1/sec tick, but 1/min
' 1-byte peek/poke RAM variables:
' (choose suitable values in 0x50 - 0x7F area)
symbol Systime0 = 0x52 ' 6.5 bit tick counter in S S S S S S h 0 lsB
symbol Systime1 = 0x53 ' MINS counter (approx!. 1 "min" = 64 ticks!)
symbol Systime2 = 0x54 ' msB
symbol TimerLast = 0x55
' 2-byte peek/poke RAM variables:
' (again choose suitable values in 0x50 - 0x7F area)
symbol LoopCounter = 0x56
symbol LoopsPerTik = 0x58
main:
' Execution starts here
' Once-off setup task(s):
poke PIC_T1CON, 0x31 ' Start PIC timer1 running at 524 mSec 16-bit rollover
' (Read the PIC 16F873 documents!)
' Execution falls through to the loop below:
execloop:
' EXECUTIVE LOOP - runs forever, calling all your required jobs in turn
' All these Driver subroutines must return promptly, and NOT hold the processor.
' IE, no PAUSE, no PWM, no long PULSOUT, no waits on SERIN, KEYIN, etc !!!
gosub Tick_Dvr
gosub Dvr1 ' your routines
GoSub Dvr2 ' your routines
GoSub Dvr3 ' your routines
goto execloop
Tick_Dvr:
' 1 TICK defined as 1 second
' Maintain a 23 bit TICK counter at SYSTIME (in half-tix)
' TICK flag occurs once each tick. All drivers may detect ONE tick-flag each second
' MINS flag occurs once each minute. All drivers may detect ONE mins-flag each minute
' Any routine is entitled to peek the elapsed time from systime2, systime1, systime0
' MMMMMMMM mmmmmmmm SSSSSSh0
' Mm=Mins S=Secs h=half-tik (half sec)
' "Secs" is actually 524*2 = 1048 mSecs, "Mins" is actually 64 "secs".
Tick_flag = 0 ' Flags last only one exec loop
Mins_Flag = 0
peek PIC_TMR1H, b9 ' Read msb of PIC's timer1
peek TimerLast, b10 ' And what was the Timer1 value last time round the loop?
poke TimerLast, b9
if b10 > b9 then ' Test whether timer1 has started from zero again?
' Arrive here once per every 524 mSec
tick_flag = Tick_hi ' Flag a new tick (High only ONCE / 1048 mSec)
Tick_hi = 1- Tick_hi ' Toggle the Tick-hi flag. Half sec on, half sec off
Peek systime0, b5 ' Fetch system SECS counter - format = SSSSSSh0
b5 = b5 + 2 And 0xFE ' Add 2 every half-tick
Poke systime0, b5 ' Store SECS counter back again
if b5 = 0 then ' Byte overflow every 64 seconds
peek systime1, word w6 ' Fetch 16-bit MINS counter
inc w6 ' Rollover onto higher 16 bits (MINS counter - format = MMMMMMMM mmmmmmmm )
poke systime1, word w6 ' Store Mins back again
Mins_Flag = 1 ' Flag a new minute
EndIf
EndIf
' We can also maintain a LOOPCOUNTER during each tick,
' yielding a LOOPSPERTIK value.
' THIS SECTION IS "OPTIONAL EXTRA" - delete the next 9 lines of code at will.
' Your own routines could peek the LoopsPerTik value any time to check the "efficiency" of your code
' IE whether any driver is hogging unnecessary time.
Peek LoopCounter, word w6
w6 = w6 + 1
Poke LoopCounter, word w6
If Tick_flag = 1 then
w2 = 0
poke LoopCounter, word w2 ' reset loopcounter
Poke LoopsPerTik, word w6 ' Last loops/tik stored
sertxd ("[", #w6, "] " ' Could enable/disable this output message line as diagnostics tool
EndIf ' to see how many EXECUTIVE "loops" we get per tick
return
'
Dvr1:
'Your routines
return
Dvr2:
'Your routines
return
Dvr3:
'Your routines
return
'---------------------------------------------------------------------
</font></pre></code>
Good luck. Try it. Give feedback.
And more to come .....
 
Edited by - bflavery on 05/01/2007 05:22:33
<b>Multitasking </b>
- Real multitasking? - Use a bigger processor. C and/or Assembler
- Simplified? - Use a Picaxe
<b>Event Programming </b>
- Simplified? - Use a Picaxe
<b>System Time & Ticks </b>
- Fine precision & fast? - Use a bigger processor. Assembler
- Slower? - Use a Picaxe
Interested? Follow this thread.
No processor short of a dual core Pentium can "really (approximately)" multi-task its jobs. At some level, it is done using some way of interleaving short bursts of servicing each task in an appropriate sequence. Jumping between tasks is usually so fast as to be quite invisible at human level.
The Picaxe <b>Basic </b> is not multitasking. Certain of its commands are positively the enemy of multitasking - like PAUSE or PULSOUT or SERIN. Nothing else can be happening while such commands take their time to finish.
But multitasking software is satisfying to program. And it can make understanding a complex system a lot easier and less error-prone.
The good news is that with some care in structuring your program, a very usable degree of "multitasking" can be achieved on the Picaxe.
The simplest form is to arrange your program like this:
<code><pre><font size=2 face='Courier'>
main:
' Do one-off startup things
' fall through to loop below:
perpetual_loop:
Gosub Job1
Gosub Job2
Gosub Job2
Goto perpetual_loop
</font></pre></code>
All commands that hold up the processor are banned (or need a work-around). All your real program content is divided up into short logical "jobs". All jobs MUST as quickly as absolutely possible do what is needed, and return. The aim is to see that "perpetual-loop" cycled through as often as possible.
Things that need to be timed (eg a certain length pulse on a LED) must find some command rather smarter than PAUSE or PULSOUT. And so there needs to be some independent time-keeping system that can be looked at later to see if that LED should turn off yet! The Picaxe <b>Basic </b> does not have such a "system timer", but we can program one fairly easily.
These concepts get various names by different people, but I tend to use these terms myself:
<code><pre><font size=2 face='Courier'>
- Executive or executive loop - That central "perpetual-loop" that lists all the Gosub routines.
(That term executive on bigger systems can mean a very complicated software section
which apportions "time slices" out optimally to all the jobs needing done.)
- Driver routine - The code in each of those Gosubs.
- Round-robin - Describes the no-brainer regular calling of each driver in turn in our loop.
- System timer - an accumulating number counting the number of microsecs/secs/mins since the world began
(ie, since the processor last reset!!). It is NOT a calendar clock.
- Tick - a very regular pulse based on the system timer. The tick can be used to easily trigger some actions on
a known timing. Not essential, but a VERY handy auxiliary to the system timer.
- Flag - a stored variable that represents something is currently ON or OFF (whatever that means).
- Event - Something that happens, and which will become noticed somewhere, causing another action.
Often stored as a flag, waiting for someone to notice.
</font></pre></code>
If we can discipline our Picaxe programming like this, we really can have our "poorman's multitasking."
=======================================================================================
The main missing item on the Picaxe is the system timer. The code below is (extracted) from a working project of mine, and shows a way to implement a timer. It does not count in microseconds or even milliseconds. It is a smaller system timer, matching a smaller processor, our Picaxe.
Specifications: Uses the internal "timer1" part of the Picaxe chip. The system timer stores elapsed seconds (23 bit half-second accuracy) for up to 45 days. And you can easily read time in minutes (16 bit). Your own code has a 1 per second "tick" flag and a 1 per minute flag available to trigger certain functions.
Long term precision is good, dependent mainly on stability/accuracy of Picaxe resonator. Accuracy is another story. Each counted "second" is really 1048 mSec. And each counted "minute" is 64 such "seconds". But it is "free".
With the system timer in place, I found that I could get about 90 loops per second through the "Executive Loop" of the code with empty user driver routines (as below). In practice with a fully coded 28X project, I usually get about 15 to 30 passes per second ("LoopsPerTik". If your code holds the processor in any one loop to a time seriously over 524 mSec, you would start missing tick detections. Depending on your own job, that might be a serious problem or not.
Anyway, here is my code. You should be able to cut & paste it to your programming editor, compile & upload, and turn on the terminal window (F8) to see the achieved loops/second your code is running at:
<code><pre><font size=2 face='Courier'>
'---------------------------------------------------------------------
' 45 day system tick timer for Picaxe
' 1 second / tick.
' By: Brian Lavery (Queensland Australia)
' Inspired by:
' The Happy Hippy (http://www.hippy.freeserve.co.uk/picaxert.htm) <A href='http://www.hippy.freeserve.co.uk/picaxert.htm' Target=_Blank>External Web Link</a>
' homepage.ntlworld.com/the.happy.hippy/picaxe/timers.zip <A href='http://homepage.ntlworld.com/the.happy.hippy/picaxe/timers.zip' Target=_Blank>External Web Link</a>
'---------------------------------------------------------------------
' I used 4 Mhz 28X, which uses PIC chip 16F873.
' If you are smart enough to use higher speed Picaxes, then you should be smart enough to adapt the code below!
' Disclaimer: Same timer function should work on most Picaxes, I believe, exc old 08.
' A quick look at the several PIC documents suggests the internal Timer1 function is similar.
' (Assumes Picaxe designers don't in future modify the internal code to use PIC's Timer1)
' Not checked, but our code possibly does conflict with some Picaxe commands like PULSIN,
' but such timed commands are not OK in our programming style anyway.
' Enhanced compiler 5.0.7
' Defined values found from the PIC document for 16F873 chip:
symbol PIC_TMR1H = 0x0F ' (MSB) PIC Timer1 data (counter) register
symbol PIC_T1CON = 0x10 ' PIC Timer1 Control Register
' GP Picaxe bit variables that must be PRESERVED at all times in your code:
symbol Tick_flag = bit0 ' set for 1 exec loop per tick (second)
symbol Tick_hi = bit1 ' hi or lo half second of each tick
symbol Mins_Flag = bit2 ' like the 1/sec tick, but 1/min
' 1-byte peek/poke RAM variables:
' (choose suitable values in 0x50 - 0x7F area)
symbol Systime0 = 0x52 ' 6.5 bit tick counter in S S S S S S h 0 lsB
symbol Systime1 = 0x53 ' MINS counter (approx!. 1 "min" = 64 ticks!)
symbol Systime2 = 0x54 ' msB
symbol TimerLast = 0x55
' 2-byte peek/poke RAM variables:
' (again choose suitable values in 0x50 - 0x7F area)
symbol LoopCounter = 0x56
symbol LoopsPerTik = 0x58
main:
' Execution starts here
' Once-off setup task(s):
poke PIC_T1CON, 0x31 ' Start PIC timer1 running at 524 mSec 16-bit rollover
' (Read the PIC 16F873 documents!)
' Execution falls through to the loop below:
execloop:
' EXECUTIVE LOOP - runs forever, calling all your required jobs in turn
' All these Driver subroutines must return promptly, and NOT hold the processor.
' IE, no PAUSE, no PWM, no long PULSOUT, no waits on SERIN, KEYIN, etc !!!
gosub Tick_Dvr
gosub Dvr1 ' your routines
GoSub Dvr2 ' your routines
GoSub Dvr3 ' your routines
goto execloop
Tick_Dvr:
' 1 TICK defined as 1 second
' Maintain a 23 bit TICK counter at SYSTIME (in half-tix)
' TICK flag occurs once each tick. All drivers may detect ONE tick-flag each second
' MINS flag occurs once each minute. All drivers may detect ONE mins-flag each minute
' Any routine is entitled to peek the elapsed time from systime2, systime1, systime0
' MMMMMMMM mmmmmmmm SSSSSSh0
' Mm=Mins S=Secs h=half-tik (half sec)
' "Secs" is actually 524*2 = 1048 mSecs, "Mins" is actually 64 "secs".
Tick_flag = 0 ' Flags last only one exec loop
Mins_Flag = 0
peek PIC_TMR1H, b9 ' Read msb of PIC's timer1
peek TimerLast, b10 ' And what was the Timer1 value last time round the loop?
poke TimerLast, b9
if b10 > b9 then ' Test whether timer1 has started from zero again?
' Arrive here once per every 524 mSec
tick_flag = Tick_hi ' Flag a new tick (High only ONCE / 1048 mSec)
Tick_hi = 1- Tick_hi ' Toggle the Tick-hi flag. Half sec on, half sec off
Peek systime0, b5 ' Fetch system SECS counter - format = SSSSSSh0
b5 = b5 + 2 And 0xFE ' Add 2 every half-tick
Poke systime0, b5 ' Store SECS counter back again
if b5 = 0 then ' Byte overflow every 64 seconds
peek systime1, word w6 ' Fetch 16-bit MINS counter
inc w6 ' Rollover onto higher 16 bits (MINS counter - format = MMMMMMMM mmmmmmmm )
poke systime1, word w6 ' Store Mins back again
Mins_Flag = 1 ' Flag a new minute
EndIf
EndIf
' We can also maintain a LOOPCOUNTER during each tick,
' yielding a LOOPSPERTIK value.
' THIS SECTION IS "OPTIONAL EXTRA" - delete the next 9 lines of code at will.
' Your own routines could peek the LoopsPerTik value any time to check the "efficiency" of your code
' IE whether any driver is hogging unnecessary time.
Peek LoopCounter, word w6
w6 = w6 + 1
Poke LoopCounter, word w6
If Tick_flag = 1 then
w2 = 0
poke LoopCounter, word w2 ' reset loopcounter
Poke LoopsPerTik, word w6 ' Last loops/tik stored
sertxd ("[", #w6, "] " ' Could enable/disable this output message line as diagnostics tool
EndIf ' to see how many EXECUTIVE "loops" we get per tick
return
'
Dvr1:
'Your routines
return
Dvr2:
'Your routines
return
Dvr3:
'Your routines
return
'---------------------------------------------------------------------
</font></pre></code>
Good luck. Try it. Give feedback.
And more to come .....
 
Edited by - bflavery on 05/01/2007 05:22:33