Poorman's Multitasking, Events, & Ticks

bflavery

New Member
<b>Poorman's Multitasking, Events, &amp; 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 &amp; Ticks </b>
- Fine precision &amp; fast? - Use a bigger processor. Assembler
- Slower? - Use a Picaxe

Interested? Follow this thread.

No processor short of a dual core Pentium can &quot;really (approximately)&quot; 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 &quot;multitasking&quot; 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 &quot;jobs&quot;. All jobs MUST as quickly as absolutely possible do what is needed, and return. The aim is to see that &quot;perpetual-loop&quot; 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 &quot;system timer&quot;, 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 &quot;perpetual-loop&quot; that lists all the Gosub routines.
(That term executive on bigger systems can mean a very complicated software section
which apportions &quot;time slices&quot; 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 &quot;poorman's multitasking.&quot;

=======================================================================================

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 &quot;timer1&quot; 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 &quot;tick&quot; 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 &quot;second&quot; is really 1048 mSec. And each counted &quot;minute&quot; is 64 such &quot;seconds&quot;. But it is &quot;free&quot;.

With the system timer in place, I found that I could get about 90 loops per second through the &quot;Executive Loop&quot; 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 (&quot;LoopsPerTik&quot;). 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 &amp; paste it to your programming editor, compile &amp; 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 &quot;min&quot; = 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)
' &quot;Secs&quot; is actually 524*2 = 1048 mSecs, &quot;Mins&quot; is actually 64 &quot;secs&quot;.

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 &gt; 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 &quot;OPTIONAL EXTRA&quot; - delete the next 9 lines of code at will.
' Your own routines could peek the LoopsPerTik value any time to check the &quot;efficiency&quot; 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 (&quot;[&quot;, #w6, &quot;] &quot;) ' Could enable/disable this output message line as diagnostics tool
EndIf ' to see how many EXECUTIVE &quot;loops&quot; 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 .....



&#160;

Edited by - bflavery on 05/01/2007 05:22:33
 

Jeremy Leach

Senior Member
Good stuff. Couple of things:

We've discussed a number of times on this forum how using PICAXE08M slaves can give true multitasking (although scheduling between PICAXEs can be tricky), and it's really quite amazing what you can do.

Also, I guess this assumes that you must call Tick_DVR before the Timer overflows?

 

bflavery

New Member
Jeremy,

No, Tick_Dvr is exected to be called several times each 524 mSec period. It detects each rollover after the event. The only real requirement is for the overall loop not to be so slow that TWO rollovers occur before it gets back to look (between 0.5 and one second).

And even then, the only damage is a half-second glitch in tick timing. Most scenarios, so what?

Brian

 
 

bflavery

New Member
I'm slightly wrong. I had a second think. The real requirement is that Tick Driver gets visited at a timing not ever exceeding 523 mSec.
B


 
 

bflavery

New Member
<b>How much diagnostic info can one LED carry? </b>

The following code shows how to use a multitasking approach to getting some quite complex behaviour out of just one LED light. Remember, we are not allowed to do a PULSOUT to hold the LED on for some pulse length. Instead, we have to turn it on at certain times, and immediately as usual exit from the routine. Then later, on a different pass through this routine, we confirm that it is time to switch the LED off again.

The code uses the 1-second tick to turn the LED on. We could use a full elapsed time check using system time, but our tick driver system has the tick flag itself, as well as a &quot;half-tick&quot; flag. We can make do with just these. We will define a &quot;long&quot; flash as half a tick long, and a &quot;short&quot; one as lasting just one Exec loop (ie, turn off at very next pass).

The code below allows your own routines to flag up to 8 possible errors (for example, a CRC error in a communications link, or a circuit board that has got too hot, a wheel motor that has stalled, etc). The Led then repeatedly taps out 8 flashes, long or short to represent OK or FAULT, in order. With a bit of practice, the human eye can learn to read and decode the information.

Look at the DummyError driver routine to see how to register that a particular error has occurred. That Dummy error routine below holds off for 1 minute, then posts an error.

You should connect a LED (with a resistor to ground, in the approved way) to a Picaxe pin. Check the code below gets adjusted to show your correct output pin as OUT_DiagLed.

In your own large project, you may have to adjust the EEPROM and RAM locations to use, to fit in with your other needs. But for now, leave them alone.

Other than that, cut the code below, paste it in your programming editor, and upload it to your Picaxe. Nothing happens for 1 minute, then an 8-flash error message starts up.

So try it. Experiment. Try setting multiple errors. Can you &quot;read&quot; the flashing pattern?

<code><pre><font size=2 face='Courier'>
'======================================================================
' A method of displaying up to 8 error codes on a single LED
' (Uses a SYSTEM TICK method of programming)

' By: Brian Lavery (Queensland Australia)
'======================================================================

#freq m4

' Enhanced compiler 5.0.7

' ERROR CODES for DIAGNOSTICS LED
' YOU define up to 8 error codes (bit numbers) like this:
symbol ERROR_SPI = 1
symbol ERROR_BAT_TEMP = 2
symbol ERROR_STOVE_TEMP = 4
symbol ERROR_MOTBOARD_TEMP = 8
symbol ERROR_CHARGE_TIMEOUT = 16
' and 32, 64, and 128

' Some 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

' RAM STORAGE VARIABLES
' 0x50-0x7F &amp; 0xC0-0xFF
' Use peek/poke
' (choose suitable values)
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 &quot;min&quot; = 64 ticks!)
symbol Systime2 = 0x54 ' msB
symbol TimerLast = 0x55
symbol DiagBitRotator = 0x56 ' Bit rotator from ERR_flags

' EEPROM DATA MEMORY
' 0x00-0x7F
' Use read/write
symbol ERR_flags = 0x06 ' You choose suitable data location
DATA ERR_flags, (0) ' Initially no error bits

' I/O PINS
symbol OUT_DiagLed = 1 ' MY hardware. Change to YOUR situation.

'=================================================================

main:
poke PIC_T1CON, 0x31 ' Start PIC timer1 running at 524 mSec 16-bit rollover
Write ERR_flags, 0 ' No errors. Start with clean slate!

execloop:
' EXECUTIVE LOOP
' All subroutines must yield promptly, and NOT hold the processor.
GoSub Tick_Dvr ' Maintains system time &amp; tick flag
' GoSub Analog_Dvr
' GoSub SPI_Dvr ' These are MY callable driver routines in MY project.
' GoSub Power_Dvr ' Replace with YOURS!
GoSub DummyError ' As an illustration, we set an error code here after 1 minute
GoSub Led_Dvr ' Display error codes

GoTo execloop

'=================================================================

DummyError:
' Note the error code at ERR_flags is reset to zero each startup (in &quot;main:&quot;).
' But in this routine,
' we simulate the situation that we have detected an error (battery over-temperature, say),
' so we set the appropriate error bit ON.
' It really only needs to be done once, but in this dummy example it is set again each minute!
If Mins_flag = 1 Then ' just once every minute
Read ERR_flags, b4
b4 = b4 or ERROR_BAT_TEMP ' This &quot;OR&quot; method preserves any other error bits already set elsewhere
Write ERR_flags, b4
EndIf
Return

'=================================================================

Led_Dvr:
' Drive DIAGNOSTIC led
' Flash the LED in an 8-bit sequence of short &amp; long pulses.
' Note that PULSOUT is not used as that would violate a key principle of this style of programming,
' that no driver routine should hold the processor time for longer than absolutely essential.
' Instead, LED is turned ON or OFF on different passes thru this driver.

' Example: ERROR code %00001010 would over 16 ticks display on LED as:
' s s s s L s L s - - - - - - - - (s=short, L=long, -=off)
' That sequence would correspond to BAT_TEMP plus MOTORBOARD_TEMP errors (see my list at start of code)
' So you read it by eye, and practice your hex/binary mental skills !!! &lt;grin&gt;

peek systime0, b4 ' read LSB of system time. Format SSSSSSh0
b3 = b4 and %00111100 ' mask for 16 seconds of tik count
bit15 = b3/32 ' now bit15 = 1 for last 8 tix of each 16
if Tick_flag = 1 then
if b3 = 0 then
' once every 16 tiks
read ERR_flags, b5
poke DiagBitRotator, b5 ' reload LED bits every 16 tix
EndIf
peek DiagBitRotator, b5 ' bit rotator
b13 = b5 /128
b5 = b5 * 2 or b13 ' rotate left &amp; stow
poke DiagBitRotator, b5
if bit15 = 0 and b5 != 0 then ' start of flash (short or long) unless NO ERRORS AT ALL
High OUT_DiagLed ' but flash suppressed over tix 8 - 15
EndIf
EndIf
peek DiagBitRotator, b1 ' Use b1, its lsb (bit8) is easy to test. (not b0 as bit0 etc preserved!)
if bit8 = 0 Or Tick_hi = 1 then
if Tick_Flag = 0 Then
Low OUT_DiagLed ' LED off after either 1 loop (short flash) or a half tik (long flash)
EndIf
EndIf
Return

'=================================================================

Tick_Dvr:
' 1 TICK defined as 1 second
' Maintain a 45 day, 23 bit TICK counter at SYSTIME (in half-tix)
' One &quot;sec&quot; = really 524 mSec. 1 &quot;min&quot; = really 64 &quot;secs&quot;. (Hey, what do you want for free?)

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 &gt; 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 (h=half-sec)
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 (= another &quot;min&quot;)
peek systime1, word w6 ' Fetch 16-bit MINS counter
inc w6 ' Rollover onto higher 16 bits (MINS counter)
poke systime1, word w6 ' Store Mins back again
Mins_Flag = 1
EndIf
EndIf
Return

'=================================================================

</font></pre></code>

 
 

moxhamj

New Member
SOS in morse for Severely Overheated Silicon?

I like this concept of parallel processing. Some 10 years ago I built a similar thing using a Z80 and about 30 support chips. The concept was to draw a circuit diagram on a PC screen using standard logic blocks (nand gates, timers etc), then pushing a compile button would turn this into machine code and download it. I had it running about 60 gates cycling 10 times a second. The main funtion was to run neural network simulations, which it did very well.
Programming in parallel using logic gates and neuron simulations is a bit different to programming using languages, and can be quite powerful.
The picaxe opens up a whole new field. My current preference is a single bus with a 10k to ground, and every picaxe puts data on the bus via a 914 diode or simply reads it. Headers and checksums keep the data going to the right places.
Parallel processing has great potential. I remember seeing somewhere that a modern PC has about as much processing power as a honeybee. Uses a bit more power though.
 

bflavery

New Member
General Comment. About the concept of &quot;<b>Never holding up the processor </b> &quot; in this style of programming. There may be some times where a bit of compromise is fair. There might be parts of your software where a short PULSIN or PAUSE may be sorely needed. And sometimes we might judge that it is OK enough to allow it.

For example, say our project is cycling around at about 25 loops / second. So every loop through all the code takes about 40 mSec. Imagine you then want to implement something short like a 3 mSec PULSOUT, a 4 mSec PAUSE, and a 3 mSec PULSOUT each time around. 10 mSec of wait-around. 25% increase in loop time. So loop cycling slows down 25%, to maybe 18 / second. You could probably judge that this may still be a good enough system. Welcome to the real world.

(But a caution - I have not checked whether those timed commands like PULSOUT may themselves use the Picaxe's &quot;timer1&quot; portion, thereby messing up the tick timing system!!!)

Anyway, back to our purpose. I'll post up some &quot;multitasking&quot; style input routines shortly ....


&#160;

Edited by - bflavery on 06/01/2007 02:59:37
 

bflavery

New Member
Thanks for the feedback. So glad it worked.

(It can be tricky to put up an example that others can run easily - there is always something different at their end that you hadn't bargained on!)

And it is fun, isn't it, when some different way of doing things pops up, and it works, and it opens up all sorts of new possibilities?


 
 

bflavery

New Member
<b>Events, Flags &amp; Buttons </b>

Let's look at using a button. Sure, we could just use a simple reading of that pin on the Picaxe, but we can extract more functioning than that. We will capture the button press in a dedicated driver routine, then &quot;raise an event flag&quot;. Some other routine later will see the flag, and DO something.

Actually, we will record the button press downtime as well, and let's imagine different uses for SHORT presses, MEDIUM ones and LONG ones.

We will set up a Diags driver that is triggered by a long button event and reports system time back to the PC. And a LED driver that makes a flash at a short button event, and oops, nothing at all uses medium button presses. Short we will set at under 2 seconds, long as 5 seconds or more, quite arbitrary of course.

Note that each event should be &quot;used&quot; only once. So the &quot;user&quot; of that event should &quot;lower the event flag&quot;.

As usual, check your own hardware against this software - make sure the LED and button are on the same pins the software nominates. Paste the code to the programming editor and upload to your Picaxe. Set on the PC terminal mode (F8). Test all button modes and verify you get the expected results.

Remembering, of course, that the button event occurs as you let go, how responsive is the observed outcome? Fast enough for your projects?

<b>... and one more thing. Note how the &quot;multitasking&quot; is operating. A long button press using the code below, and an ongoing LED flashing sequence like in my previous posting, both can be happening while your own code routines are still happily processing away. Nothing is paused. </b>

<code><pre><font size=2 face='Courier'>
'======================================================================
' A method of reading several types of input on a single button

' By: Brian Lavery (Queensland Australia)
'======================================================================

' I/O PINS
symbol IN_BUTN1 = pin0 ' Button to ground, pullup resistor to positive.
symbol OUT_DiagLed = 3 ' Through resistor to ground

' PIC CHIP DEFINES
symbol PIC_TMR1H = 0x0F ' (MSB) PIC Timer1 data (counter) register
symbol PIC_T1CON = 0x10 ' PIC Timer1 Control Register

' RAM STORAGE VARIABLES
symbol Systime0 = 0x52 ' 6.5 bit tick counter in S S S S S S h 0
symbol Systime1 = 0x53 ' MINS counter (approx!. 1 &quot;min&quot; = 64 ticks!)
symbol systime2 = 0x54
symbol ButtonDowntime = 0x55 ' in tix (seconds)
symbol TimerLast = 0x5A



' GP VARIABLES
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 ' *
symbol ButtonDownLastLoop=bit3 ' *
symbol ButtonEventflag = bit4 ' * ALL THESE BITS ABOVE TO BE PRESERVED - Never clobber!

symbol ButtonIsDown = bit8 ' (Will be used only inside Button driver)

' Note: My own convention is that variables b2 and b0 (bit0 to bit7) are always preserved as global variables.
' System flags are in bit0 - bit7. A global STATE variable I use in b2 - not discussed here.
' That is enough for my needs. All other variable data gets poked away in RAM between uses.
' But that is personal - work it out for yourself!

'=================================================================

main:
poke PIC_T1CON, 0x31 ' start PIC timer1 at 524 mSec 16-bit rollover

execloop:
gosub Tick_Dvr
gosub Buttons_Dvr
GoSub Diag_Dvr
Gosub LED_Dvr

goto execloop

'=================================================================

Tick_Dvr:
' The usual standard tick driver
Tick_flag = 0
Mins_Flag = 0
peek PIC_TMR1H, b9
peek TimerLast, b10
poke TimerLast, b9
if b10 &gt; b9 then
tick_flag = Tick_hi
Tick_hi = 1- Tick_hi
Peek systime0, b5
b5 = b5 + 2 And 0xFE
Poke systime0, b5
if b5 = 0 then
peek systime1, word w6
inc w6
poke systime1, word w6
Mins_Flag = 1
EndIf
EndIf
return

'=================================================================

Buttons_Dvr:
' When button pressed, count its tix, &amp; raise button event flag when released.
' (eventual USER of button event should lower the flag)

ButtonIsDown = 1- IN_BUTN1 ' Inverted. Input=1 for button NOT down.
' General principle: Read it ONCE. We use it TWICE below.
if ButtonIsDown = 1 then
ButtonEventFlag = 0 ' Even if last event was not used, throw it away
if ButtonDownLastLoop = 0 then ' Start of new press?
Poke ButtonDownTime , 0 ' Restart downtime counter
EndIf
if Tick_flag = 1 then
peek ButtonDownTime, b6
inc b6 ' count the ticks that button stays down
poke ButtonDownTime, b6
sertxd(&quot;*&quot;) ' THIS LINE IS FOR TESTING: REMOVE AT WILL.
' IT SENDS A DOT EVERY TICK THAT BUTTON IS DOWN
' JUST FOR US TO LOOK AT
EndIf
Elseif ButtonDownLastLoop = 1 then ' Is button now UP but was DOWN at last loop? (ie end of press?)
ButtonEventFlag = 1 ' Raise &quot;Button Event&quot; flag. From now, any driver can see this event
EndIf
ButtonDownLastLoop = ButtonIsDown ' save button state to cross-check next time through here
return

'============================================================

Diag_Dvr:
' Will trigger on a long button event
' Just for us to look at on PC
If ButtonEventFlag = 1 then
Peek ButtonDownTime, b5
if b5 &gt; 4 then ' CAPTURE only very long button press. Others pass thru.
ButtonEventFlag = 0 ' We are the USER of this event. Lower the flag
peek systime0, word w6
w6 = w6 / 4 ' = system time in secs
sertxd (&quot; Secs=&quot;, #w6, 13, 10) ' The &quot;diagnostic job&quot; = display system time in seconds to PC.
EndIf
EndIf
return

'============================================================

LED_Dvr:
' Will trigger on a short button event
' This is a pretty trivial job for a driver - but it proves a point!
Low OUT_DiagLed ' LED never on for more than 1 loop
If ButtonEventFlag = 1 then ' exit if there is no event
Peek ButtonDownTime, b5
if b5 &lt; 2 then ' CAPTURE only very short button press. Others pass thru.
ButtonEventFlag = 0 ' We are the USER of this event. Lower the flag
High OUT_DiagLed ' Start a LED flash. (Off at next pass)
EndIf
EndIf
return

'============================================================
</font></pre></code>


Edited by - bflavery on 08/01/2007 02:31:07
 

bflavery

New Member
<b> Buttons, Events, State Machines. </b>

I mentioned a &quot;state variable&quot; earlier, but I did not explain.

If your project implements what's called a &quot;state machine&quot;, any one type of event might trigger different results depending on the &quot;state&quot;.

For example a VCR/DVD/CD player is a classic state driven device. It runs for periods doing just one activity. Something causes it to jump to a different activity, which again continues for a while. At any moment you could nominate exactly what it is doing just then. That is its current &quot;state&quot;. There is a limited list of total states.

A &quot;fast forward&quot; button might cause a jump to <b>high speed play </b> state if the &quot;state&quot; had been <b>videotape playing </b> . But that same button might cause a <b>CD track skip </b> if its current state was <b>playing MP3 </b> music tracks.

You can bet that the designer of that VCR reads the FF button in just one place in his code, and that the one button event is used separately by whatever is the state that is running.



 
 

bflavery

New Member
<b>Input Fodder </b>

In a simple program, we might directly read a robot's front bumper switch, and immediately halt the forward drive on the motors. Three lines of code. On a larger project, it can become much cleaner to devote dedicated driver routines that can read all the input signals at once.

We have already seen a driver for a single button which can raise different versions of button event. Easy. But you might have a dozen digital inputs, and they may have different hardware connections or Picaxe ports, etc. It can be really easy to read them all in a combination read (eg 8 pins in one read into a byte variable). Store them and use the separate pieces later as needed.

Of course, as they say, &quot;time is of the essence.&quot; If the inputs are read ahead of the needed time, we need to ensure the data is not stale and wrong by the time it is used.

How stale is stale? A dirty bottle spotter on an factory assembly line might need input data better than .01 seconds. A battery charger watching rising temperature to prevent overcharging may be happy with 30 seconds old data. There is no excuse for not studying and making judgments on your own project. Striving for faster performance than you truly need can be a waste of time.

Remember in the first posting in this thread, we wanted to know how many &quot;LoopsPerTick&quot; we got using our &quot;round robin&quot; Executive Loop programming model. One reason is to see just how stale the input data might get over one pass through the loop, if it is all collected in one driver. That can determine whether a single all-purpose inputs driver is a valid approach.

If we are getting, say, 40 loops per second, and we are really collecting inputs each loop, that means that on average data will not get older than 25 mSec. Lots of applications would be totally happy with that.

When data freshness is low priority, we might skip the input readings on most loops. We might skip 7 loops out of every 8, say, or all loops except where the tick flag is set (1 per second), or the mins flag even.

In most cases, this is all too hard, and not needed. Just read the inputs each time around!

Our example code comes from a project where I needed 11 analog inputs - various temperatures and voltages, etc. The 28X has 4 analog inputs, so I used a 4051 multiplexing chip to funnel the first 8 analogs into one Picaxe input. Three output pins on the Picaxe drive the 4051 to select the analog input I choose to read.

I used to use LM335 temperature sensors, nice and linear and predictable. I often use simple NTC thermistors now. $1 each, very easy, but quite non-linear &amp; tricky on a Picaxe if you want a range of precisely known temperatures. I found usually I wanted simply to test a set-point (eg over 50 degrees = overheating), and I just had to calibrate for that one point, so I do it by trial and error. &quot;What's that funny electrical thing in my fridge?&quot; Or in the chick-egg incubator?

<code><pre><font size=2 face='Courier'>
-------- ---------
TempMot ---&gt;| | | |
TempB3 ---&gt;| |---------------&gt;|a0 |
TempB2 ---&gt;| | VoltChg ---&gt;|a1 |
TempB1 ---&gt;| | TempAmb ---&gt;|a2 |
VoltCur ---&gt;| | TempStove ---&gt;|a3 |
VoltMot ---&gt;| | | Picaxe |
Volt19 ---&gt;| | | |
Volt8 ---&gt;| 4051 |&lt;---Sel0--------|out5 |
| |&lt;---Sel1--------|out6 |
| |&lt;---Sel2--------|out7 |
-------- | |
---------
</font></pre></code>

Collecting all the inputs at once saves us needing to change the pins on the 4051 multiplexer each time all over our code someone needs an analog reading. All analog values in user routines just need a PEEK from RAM. Very simple.

Assuming analog values on my project were not varying very fast, my code takes 8 loops (say a quarter of a second) to fetch all new values. For your project that could well be 10 times too slow, or 50 times in excess. You choose. You code accordingly.

The code below should paste and load OK to your Picaxe. But you surely won't have my 11 analog inputs passing through a 4051. You can just see a nonsense-values dump of analog stored readings once per minute on your PC terminal (F8).

<code><pre><font size=2 face='Courier'>
'======================================================================
' An example of an analog inputs driver

' By: Brian Lavery (Queensland Australia)
'======================================================================


#picaxe 28X


' I/O PINS
symbol OUT_Sel0 = pin5
symbol OUT_Sel1 = pin6
symbol OUT_Sel2 = pin7

symbol PIC_TMR1H = 0x0F
symbol PIC_T1CON = 0x10


' RAM STORAGE VARIABLES
symbol Anlg_ctr = 0x50
symbol Systime0 = 0x52
symbol Systime1 = 0x53
symbol systime2 = 0x54
symbol TimerLast = 0x5A

' Storage array for all the (raw) ADC readings:
' Note order of first 8 is as governed by hardware connections to my 4051 multiplexer
symbol TempMot = 0xC0
symbol TempB3 = 0xC2
symbol TempB2 = 0xC4
symbol TempB1 = 0xC6
symbol VoltCur = 0xC8
symbol VoltMot = 0xCA
symbol Volt19 = 0xCC
symbol Volt8 = 0xCE
symbol VoltChg = 0xD0
symbol TempAmb = 0xD2
symbol TempStove = 0xD4
' The Analog_Dvr puts all analog readings in the above RAM locations. It does this non-stop.
' Any &quot;user&quot; routine wanting to know a current analog value, PEEKs the data from RAM.
' To the user program, reading any input is now a very standardised and easy function.



' GP VARIABLES

symbol Tick_flag = bit0
symbol Mins_Flag = bit2
symbol Tick_hi = bit1

symbol MTempVar = b3 ' used in ANLG_Dvr


'=================================================================

main:
' A curious thing on the 28X: at reset, b0-b13 &amp; RAM 50-7f are auto zeroed, but RAM c0-ff are random/retained.
' I discovered this by accident one day.
' Let's clear all the rest - no remaining spurious data sitting there at startup:
for b5=0xC0 to 0xFF
poke b5,0
next

poke PIC_T1CON, 0x31 ' start PIC timer1 at 524 mSec 16-bit rollover

execloop:
gosub Tick_Dvr
gosub Analog_Dvr
GoSub diag_Dvr
goto execloop

'=================================================================

Tick_Dvr:
' The usual!
Tick_flag = 0
Mins_Flag = 0
peek PIC_TMR1H, b9
peek TimerLast, b10
poke TimerLast, b9
if b10 &gt; b9 then
tick_flag = Tick_hi
Tick_hi = 1- Tick_hi
Peek systime0, b5
b5 = b5 + 2 And 0xFE
Poke systime0, b5
if b5 = 0 then
peek systime1, word w6
inc w6
poke systime1, word w6
Mins_Flag = 1
EndIf
EndIf
return


'=================================================================
Analog_Dvr:
' This code takes 8 passes to cycle thru all first 8 analogs. One per loop.
' Multiplexer 4051 hardware has already been set at last loop.
' Your own requirement could read all 8 in a FOR/NEXT loop, if my version is too slow for you.
peek Anlg_Ctr, b1 ' Fetch counter to see which input was selected at 4051
MTempVar = b1 * 2 + TempMot ' Indexed into analog table. TempMot is first analog storage spot
readadc10 0, w3 ' Analog0 is the input that comes via the 4051
poke MtempVar, word w3 ' store adc data to RAM, indexed into 1 of 8 spots
b1 = b1 + 1 and 0x07 ' rotating counter 0 - 7
' bits 0-1-2 are used to set 4051 select lines
OUT_Sel2 = bit10 ' new multiplex setting preset for NEXT pass
OUT_Sel1 = bit9 ' note these bits belong to b1
OUT_Sel0 = bit8
poke Anlg_ctr, b1 ' stow counter back to RAM for next iteration
' ok, done the multiplexed first 8.
' Now do the last 3 regular Picaxe analog inputs
readadc10 1, w3
poke VoltChg, word w3 ' These 3 get done each loop. Lucky things.
readadc10 2, w3
poke TempAmb, word w3
readadc10 3, w3
poke TempStove, word w3
return

'===============================================================

Diag_Dvr:

if Mins_flag = 1 then
' Once a minute, dump the 11 analog values to the PC terminal.
for b5=TempMot to TempStove step 2
peek b5,word w6
sertxd (&quot; &quot;, #b5, &quot;=&quot;, #w6)
next
sertxd(CR,LF)
EndIf
return

'============================================================
</font></pre></code>


&#160;

Edited by - bflavery on 08/01/2007 02:16:30
 

bflavery

New Member
<b>System Timer - some options we have NOT used </b>

The Tick_Dvr code we have been using uses a 3 byte counter (SYSTIME0 to SYSTIME2). It stores a count of <b>seconds </b> (half-seconds actually) up to a total of about <b>45 days </b> . This method could easily be shrunk or expanded to a different number of bytes if you want.

Eg a 2 byte timer (throwing out SYSTIME2) would count to about <b>4 hours </b> before rolling over to zero again, while 4 bytes would go to about <b>31 years </b> ! I don't think we ever need that.

Another thought about the timer: The code I have given detects each time that the &quot;timer1&quot; rollover counter starts from zero again. It should be equally valid to detect the rollover by looking for the <b>interrupt flag </b> TMR1IF in register PIR1 of the chip. (Refer the chip documents from MocroChip - eg the 12F683 in the case of Picaxe 08M.) Reset the interrupt flag after each find (each half tick to us), and start looking for the next occurrence. No, I am not going to follow this method up here. Our current simple system seems adequate. (And other threads on this forum are actively covering that and other techniques.)

And a third thought: My &quot;standard&quot; tick timer is configured so that the best resolution is half a second, and in fact a &quot;tick&quot; is set at 1 second. Re-coding to a give a tick based on a quarter second or perhaps even <b>one eighth second </b> may be possible, provided your own program routines stay real lean and fast, and you still get a good fistful of &quot;loops per tick&quot;.

One more: Hippy and Jeremy Leach have some exciting ideas on capturing the &quot;timer1&quot; values directly. Provided you never stop the timer, you may &quot;sample&quot; values of the MSB of the timer (just as our tick driver does) anytime you like. That does not affect our tick driver at all. You may be able to time some very short things down to a theoretical resolution of about <b>2 mSec </b> (524 msec / 256).

Let's stick with what we have. It's easy and it works.

 
 

bflavery

New Member
<b>Battery voltage meter on one LED </b>

This LED function is rather like the earlier LED one. This time we show a way of displaying battery voltage on one LED. Now we can read the voltage to 2 decimal places. And without using a multimeter, nor a 3 digit display.

My system happens to use a 19 volt 15 cell NiMh battery pack. I use a voltage divider to send about 4 volts to the analog input. By experiment, I have determined that 24 volts (extreme top charging) gives an analog reading of 1006 (0x03EE).

I tend to think better in VOLTS/CELL rather than 19 volts total. So I want to know volts in the range about 1.05 (seriously flat) volts/cell to 1.60 (overcharged).

Now, the &quot;1.&quot; part is rather obvious, so I chose to display just the &quot;.xx&quot; part on the LED. So a reading of &quot;29&quot; would mean 1.29 volts/cell to me. (= &quot;battery still well charged&quot;).

We have already seen an 8-flash LED system used for diagnostics codes. I re-hashed that for this job. In 8 bits, I COULD send a hex/binary code for the voltage, but that is just too hard to interpret. Instead, I chose a BCD format, 2 digits (eg, &quot;2&quot; and &quot;9&quot;) each in a 4-bit nibble (%0010 and %1001).

Example: Assume 1.29 V/cell. I want to see &quot;29&quot; = %0010 1001 in BCD. Over 16 ticks the display on the LED is:
- - - - - - - - s s L s L s s L (-=off, s=short, L=long)

So you read it by eye, and practice your hex/binary mental skills !!! &lt;grin&gt;

The code below should work if you paste it to your programming editor and upload to the Picaxe. As before you just need a LED on the correct output pin to see the results. (And you can see the &quot;voltage&quot; on the PC terminal (F8) as well.)

The code includes a dummied up driver pretending to read the analog input over several minutes. It pretends that the voltage is falling from about 1.45V down to flat (1.05V) over 5 minutes.

&quot;But wait! There's more ....&quot; Bonus. I have included a CriticalBattery driver whose job is to shut everything down if the battery goes flat. Note how in good multitasking etiquette, it refrains from doing its full check (taking more computing time), except for just as often as necessary (once a minute, although perhaps that is a bit long??).

As usual, Try it. It should upload and run. Give suggestions and feedback.

<code><pre><font size=2 face='Courier'>
'======================================================================
' A method of displaying battery voltage on a single LED

' By: Brian Lavery (Queensland Australia)
'======================================================================

#freq m4

' Enhanced compiler 5.0.7

' Some 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

' RAM STORAGE VARIABLES
' 0x50-0x7F &amp; 0xC0-0xFF
' Use peek/poke
' (choose suitable locations)
' My own convention often is to use 50-7F for 1 byte values, C0-FF for 2 byte values

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 &quot;min&quot; = 64 ticks!)
symbol Systime2 = 0x54 ' msB
symbol TimerLast = 0x55
symbol BatBitRotator = 0x58 ' Bit rotator from ERR_flags

symbol Volt19 = 0xCC ' My own ANALOG_DVR stores raw ADC10 reading from battery pack voltage here.
' I found by testing that ADC10 value of 1006 corresponded to 24.0 battery volts
' which is 1.60 volts/cell (15 cells), highest expected voltage.
' Lowest expected value = 1.05 V/cell (ADC10 = 660).
' (And 1.05 triggers a HALT function to avoid battery damage! See below)

' EEPROM DATA MEMORY
' 0x00-0x7F
' Use read/write
' (choose suitable locations)
symbol BatScale = 0x40
DATA BatScale, (0xDD) ' I set this scaling value by experiment !!!
symbol B19FlatAlarm = BatScale + 1
DATA (0x95,0x02) ' 0x0295 = 661 decimal = 1.05V / cell
' (I store these scaling factors in EEPROM.
' And I myself have a mode of adjusting these
' factors &quot;online&quot; after project is installed.)

' I/O PINS
symbol OUT_BatLed = 2 ' MY hardware. Change to YOUR situation.

'=================================================================

main:
poke PIC_T1CON, 0x31 ' Start PIC timer1 running at 524 mSec 16-bit rollover

execloop:
' EXECUTIVE LOOP
' All subroutines must yield promptly, and NOT hold the processor.
' In a real project, you should get 15 - 50 loops through here per 1 second tick.
GoSub Tick_Dvr ' Maintains system time &amp; tick flag
GoSub Dummy_Analog_Dvr ' We &quot;read the battery voltage&quot; here, among other things
' GoSub SPI_Dvr ' These are MY callable driver routines in MY project.
' GoSub Power_Dvr ' Replace with YOURS!
GoSub Led_Dvr ' Display on LED
GoSub CriticalBattery ' Halt everything if battery is seriously flat

GoTo execloop

'=================================================================

Dummy_Analog_Dvr:
' Simulation of battery voltage falling from about 1.45 V/cell to 1.01 volts over about 5 minutes
' Using known calibration (from testing on MY hardware): ADC=1006 corresponds to 1.60 volts/cell
Peek systime0, word w2
w2 = w2 / 4 ' system time in seconds
w2 = 930 - w2 ' sliding downwards from pretend &quot;ADC10&quot; value of 930 (= 1.46 V/cell)
If w2 &lt; 641 Then
w2 = 641 ' bottom out at 1.01 volts for our simulation
EndIf
poke Volt19, word w2 ' This is where my REAL analog driver puts its readings
' So we put our dummy readings there instead

' A &quot;REAL&quot; Analog Driver would be doing a READACD10 at a pin that is sensing battery pack voltage
' (Via a suitable voltage divider, obviously)
Return

'=================================================================

Led_Dvr:
' Drive BATTERY VOLTAGE led
' Flash the LED in an 8-bit sequence of short &amp; long pulses.
' You interpret the flashes as 2 BCD digits. Eg &quot;29&quot; represents 1.29 volts.


peek systime0, b4
b3 = b4 and 0x3C ' mask for 16 seconds of tik count
bit15 = b3/32 ' = 1 for last 8 tix
if Tick_flag = 1 then
if b3 = 0 then
' once every 16 tiks
peek Volt19, word w6
read BatScale, b3
' For integer arithmetic, scale up first by arbitrary factor (I picked 35 here),
' but ensuring will <b>always </b> stop short of 0xFFFF overflow.
' This scaling up is to give a better precision of final result.
' Then divide down again by a suitably chosen factor so that 1.29V/cell comes to 129 decimal here.
' Now subtract the 100 to give &quot;29&quot;. This would be 0x1D hex, not what we want yet.
' Finally separate out the &quot;2&quot; and the &quot;9&quot; parts and recombine to give 0x29 (hex).
' We humans can read that as BCD &quot;29&quot;. Pheww.
w3 = w6 * 35 / b3 - 100
{
' Next 5 lines of code are just for simulation display on PC (F8). (Delete as needed.)
sertxd (&quot;1.&quot;)
If w3 &lt; 10 Then
sertxd(&quot;0&quot;)
EndIf
sertxd (#w3, &quot; volts&quot;, CR, LF)
}
b7 = b6 / 10 * 16
b6 = b6 % 10 + b7
poke BatBitRotator, b6 ' Store it. We need it over &amp; over
EndIf
peek BatBitRotator, b5 ' Fetch bit rotator
b13 = b5 / 128
b5 = b5 * 2 or b13 ' Rotate bits left &amp; stow back for next time
poke batBitRotator, b5
if bit15 = 1 Then ' start LED flash every tick
High OUT_BatLed ' exc NO flash over tix 0-7
EndIf
EndIf
peek BatBitRotator, b1
if bit8 = 0 Or Tick_hi = 1 then
if Tick_Flag = 0 then
Low OUT_BatLed ' LED off either after half tick or at very next loop
EndIf
EndIf
return

'=================================================================

Tick_Dvr:
' The usual standard tick driver
Tick_flag = 0
Mins_Flag = 0
peek PIC_TMR1H, b9
peek TimerLast, b10
poke TimerLast, b9
if b10 &gt; b9 then
tick_flag = Tick_hi
Tick_hi = 1- Tick_hi
Peek systime0, b5
b5 = b5 + 2 And 0xFE
Poke systime0, b5
if b5 = 0 then
peek systime1, word w6
inc w6
poke systime1, word w6
Mins_Flag = 1
EndIf
EndIf
Return

'=================================================================


CriticalBattery:
' Check battery - if FLAT (&lt; 1.05 V) - STOP EVERYTHING
If Mins_flag = 1 Then
' One test / min is sufficient
Read B19FlatAlarm, word w2 ' There is a calibrated number here representing 1.05 volts/cell
Peek Volt19, word w3 ' Fetch current volts
If w3 &lt; w2 Then ' Volts fallen below 1.05 / cell. We have a problem.
' The Picaxe might halt, but it leaves running anything else already running!!!
' So here, we need explicitly to turn off any LEDS or Motors, or anything else that draws power
Low OUT_BatLed ' Anything else??
sertxd(&quot; FLAT&quot;) ' Signal FLAT message in case any terminal or display is listening
End ' HALTS the Picaxe - very very low power
EndIf
EndIf
Return


'============================================================
</font></pre></code>

HINT: This Battery Voltage LED display could be merged with the earlier posted code for a Diagnostics LED display. One LED displays 8 flashes while the other is in its quiet 8-tick time. This ould make it possible to read each LED, without impossibly confusing the poor human eye!



 
 

bflavery

New Member
<b>Hardware generated tick? </b>

The original TICK / SYSTEM TIMER I used had a 1307 Real Time Clock chip nearby, and I could steal a 50:50 square wave 1 Hz signal from that other board!

When later I spotted Hippy's RTC simulation using Timer1, it was obvious there was a half Hz signal available for free. So the same tick &amp; timer function could now be made with no hardware assist.

Here for interest is the code for my original hardware RTC-aided version. It works out virtually identical to the software Timer1 version as in this forum thread.
<code><pre><font size=2 face='Courier'>
' I/O PINS
symbol IN_SQW = pin2
' my hardware: input from 1 Hz square wave signal

Tick_Dvr:
' &quot;Secs&quot; is = <b>1000 mSecs precisely </b> , excluding loop jitter.
' &quot;Mins&quot; is still 64 &quot;secs&quot;.
Tick_flag = 0 : Mins_Flag = 0
b6= IN_SQW
if b6 != Tick_hi then
tick_flag = Tick_hi
Peek systime0, b5 : b5 = b5 + 2 And 0xFE : Poke systime0, b5
if b5 = 0 then
peek systime1, word w6 : inc w6 : poke systime1, word w6
Mins_Flag = 1
EndIf
EndIf
Tick_hi = b6
Return
</font></pre></code>



Edited by - bflavery on 08/01/2007 03:03:28
 

bflavery

New Member
<b> OS and APP on the Picaxe??? </b>

The time has surely come to look at using the <b>PoorMan's Multitasking </b> programming style on a real project.

<b>A. </b> We have looked at the system timer module with its related tick flag(s).

<b>B. </b> We have seen button inputs (actually just 1, but that can be expanded), and analog inputs - input drivers that were centralised, isolated from our own user program routines, clean, and fully consistent with the multitasking model of &quot;never ever wait around&quot;.

<b>C. </b> We have seen how an &quot;event flag&quot; raised in one spot can trigger responses at another spot in the system.

<b>D. </b> We have seen a couple of LED output functions that, once again, effectively do long spread-out jobs, but still never hold up the multitasking executive loop.

Have we have created for ourselves a rather tiny multitasking &quot;operating system&quot;? Some big-system ideas actually working on our little Picaxe. Should we have bragging rights for &quot;My OS is tinier than your OS&quot;?

<b>NOTE. </b> We have NOT yet looked at a typical user program, doing the things that you probably wanted to solve in the first place!

We still need to create some &quot;application software&quot; for our little Picaxe. Then maybe we could start up a whole new very successful software company called PICO-NANO-SOFT and dominate the world of (small) computing. <code><pre><font size=2 face='Courier'>&lt;grin&gt; </font></pre></code>

Or perhaps just maybe the emperor has no clothes. The only substantial new software infrastructure is the 10 code lines or so of the system tick timer. The rest of this multitasking paradigm for the Picaxe is, in truth, based on only software style and discipline.

Does this style help to build a better software project in total? I believe it can, but you may beg to differ. Perhaps as Dr_Acula offered earlier, we are just making &quot;seriously overheated silicon.&quot;

But I will press on. I will post up a small &quot;application&quot; shortly ...

 
 

Jeremy Leach

Senior Member
Hey Brian, I don't want to stifle your creativity, but do you realise you've done 13 long posts with only 4 replies !

I guess everyone is a bit shell-shocked by this ongoing monologue - I just don't think we can digest the amount of info you're generating !
 

lbenson

Senior Member
Just a lurker here saying I'm happy to watch where this goes--as with most of these threads, but this is more generalized.
 

Dippy

Moderator
Well there's obviously a lot of time and thought going into this. So, I was wondering whether it would be better as a website article? Then you could colourise it etc. and perhaps produce a summary after the discussion with code links? With some nice graphics maybe.

Just a thought to make it more digestable.
 

wilf_nv

Senior Member
In principle, the details of the OS can be hidden, while the structure it creates can make it easier for users to quickly &quot;write&quot; moderately complex control programs.

This is especially true if the OS is accompanied by a set of compatible generic application modules.

The key is to describe the OS at a high level perhaps using a user friendly analogy as the OS model and then describe the generic modules in terms of the users language.

A more detailed description and programmer model for wrting other compatible application modules makes the OS system and the module library extendable for advanced users.

The results is a plug and play system that is customized at a descriptive and application level to use the natural language of a specific user group yet retains the ability to be modified and adapted as required.

bwevans would seem to have a need for just such a software package to accompany his simple picaxe hardware platform for the artists and art students in his community.

wilf


Edited by - wilf_nv on 08/01/2007 15:16:07
 

Brietech

Senior Member
I've been following this thread with great interest and have enjoyed the long postings. I've been quietly developing a self-hosted development platform for the picaxe (using an 18X), in an attempt to build a Picaxe-based &quot;personal computer&quot; of sorts, somewhat akin to a much slower version of early 1980's computers. The system timer (and the whole OS idea in general) might make an interesting addition to it. I currently have the ability to type code onto it (using a keyboard and currently the serial port for display, but in another day or two a serial LCD as it just arrived in the mail), line by line using hex and &quot;assembly-type&quot; mnemonics.

i.e. Writing a program in &quot;slot 1&quot; to display the letter &quot;A&quot; and then end would look like this:

Instrn:LOD
Oprnd:41
Loc:0000
writing . . . done
Instrn:DSP
Oprnd:00
Loc:0002
writing . . . done
Instrn:END
Oprnd:00
Loc:0004
writing . . . done

(and executing it)

Run Program[1-4]:1
A

I have a fairly full-fledged assembly-style interpreter that is reasonably fast (still haven't benchmarked it or optimized it, but it can run a &quot;hello world&quot; program without any kind of noticable delay while writing characters, so it's at least above the 30-40 instructions/sec range. I'm currently working on moving to the serial LCD screen, *soon* I should also be installing a 32kb i2c RAM chip so I can use it in conjunction with my 64kb EEPROM chip and provide some real storage/RAM for my programs.

I'm currently working on writing a more full-fledged text-editor style entry system for programming, along with a compiler, rather than doing it line-by-line like now, and i've only used up around 1250 bytes, so I should be able to fit everything. It would be great to make it run a full-fledged simplified operating system though!
 

Jeremy Leach

Senior Member
Actually Brian, that wasn't meant to sound so harsh earlier - it's actually extremely well-written, and now I've taken more time to digest it, is making more sense!!

One thing I can't help thinking though, is that although these 'bigger ideas' of computing are genuinely interesting and a challenge to apply to PICAXEs, I wonder if the overheads outweigh their benefit? I guess it's a case of trying them to see, and they might pay-off especially on the bigger PICAXEs.

From my experience so far with PICAXEs it seems to be a bit of an artform to get the slickest system with the set of PICAXE resources available and it's difficult to apply high-level ideas when projects need to be crafted case-by-case.

For instance, one of the first things I did when starting out with PICAXEs was to develop what I called a variable-stack, so that parameters could be passed into subroutines and subroutines could use their own variables, and on exit allow the calling routine to carry on where it had left off, with all variables intact.

However after gaining experience with some real-life little projects I soon realised that although good in theory it wasn't that useable in practice, and the overheads were significant.

Anyway, even if only 20% of this finds a use it will be well worth it - so keep going.



Edited by - jeremy leach on 08/01/2007 22:46:16
 
i haven't really read much of this post but some sort of tutorial forum should be opened and this should be posted there because this is really valuable information it should be easy to access.

any way good work u did bflavery
 

bflavery

New Member
<b>A Battery Charger - A state driven multitasking application for Picaxe </b>


Let us create some &quot;application software&quot; - a battery charger project for a NiMH battery pack. The recommended way to charge a NiMH, the more so as higher charge rates are planned, is to measure battery temperature, and stop when the temperature rise starts climbing too fast.

Oh, how fast is too fast? Not going to discuss that here, but there is LOTS of info on the web. And how do we calibrate all the voltage readings and temperature readings we get on our Picaxe? Not going to discuss that here either. But it may take you a while to sort that all out. Sorry. This posting is just about the software side.

Here are the features of our charger:

Hardware:
External power source with some current limiting
1 user button
3 Analog inputs: battery temperature, battery voltage, charger voltage
1 power MOSFET or TRANSISTOR circuit to control charging
Optional: Diagnostic LED and battery voltage LED as per earlier postings

Operation:
- Charger can be in 1 of 3 states: NOT charging, Charging 100%, PWM trickle charging
- Button can start either full or trickle charging, or switch off again
- If battery goes flat, will try to auto start into 100% charge
- If battery gets severely too hot, STOP immediately
- While charging, when battery temperature starts a steepening rise, drop to TRICKLE
- If too long a time goes by doing FULL CHARGE, battery must be overcharging. STOP
- TRICKLE normally lasts a standard trickle time
<code><pre><font size=2 face='Courier'>
Vchg
.-------. 25R 20W | (via Res divider)
| | ___ | ===================
| +o------|___|-----o---&gt;|--o---------. OUR PLANNED CIRCUIT
| | Very crude Diode | | ===================
| PWR | current | |
| PACK | limiting! .-. |
| 32V | (0.5A) 4K7 | | |
| 1A | | | | +5V
| -o-----. '-' | |
| | | | |s |
| | | | g | P-MosFet |
| | === o------||-+ |
'-------' GND | ||-&gt; .-.
| ||-+ | |
.-. |d .--------. | | NTC (47K)
6K8 | | | | | '-' (In Bat pack)
| | o-----------o+ | |
'-' | | &quot;19V&quot; | |
.----------------. | | | NiMH | o--- Tbat
| | | Vbat | BAT | |
Vbat ---o an0 | | (via .----o- | |
| Picaxe | | divider) | | | .-.
Vchg ---o an1 | |/ | | | | |
| out o-----| NPN === '--------' | | 47K
Tbat ---o an2 | |&gt; GND '-'
| | | |
Butn ---o in | | |
'----------------' | |
| |
=== ===
GND GND
(created by AACircuit www.tech-chat.de)
</font></pre></code>

We could describe the operation with a &quot;State Table&quot; like this:
<code><pre><font size=2 face='Courier'>

------------- --------------- --------- ----------
CURRENT STATE EVENT/CONDITION NEW STATE ' COMMENTS
------------- --------------- --------- ----------

(any) Short Button Idle ' Manual stop
(any) Medium Button TrickleCharge ' Manual Trickle start
(any) VChg = 0 Idle ' No Power!
Idle Long Button FullCharge ' Manual Charging start
Idle Vbat &lt;= Flat FullCharge ' Auto Charging startup
Idle Vbat &lt;= Very Flat System Shutdown ' &quot;CriticalBattery&quot; test - earlier posting
FullCharge Tbat &gt;= Extreme Idle ' Too hot! Abort
FullCharge Tbat rising faster TrickleCharge ' Regular test for &quot;charged&quot;
FullCharge Charge Time &gt;= Limit Idle ' Backup against overcharge
TrickleCharge Trickle Time &gt;= Limit Idle ' Regular end of trickle

</font></pre></code>
For every &quot;current state&quot; we look at the conditions or events that occur. Any time one of these conditions occurs, we look at the entries in the list to see if we should jump to a &quot;new state&quot;. And again we stay there until an appropriate event tells us to jump again.

Below is the battery charger software - but it is not really complete. For example, it does not include analog inputs nor LED outputs. (The analog values are just dummied up to be rather typical values.)

There is no special fancy software in this code that uses an actual lookup &quot;state&quot; table. We just keep the table in our heads as a target, while we use pretty ordinary Picaxe Basic coding.

The Monitor_Dvr section does all the &quot;looking&quot; for trigger events or conditions, and &quot;requests&quot; a jump to a new state. The Power_Dvr section's job is to unquestioningly start and/or maintain each mode (FullCharge or Trickle or Idle) as per the request.

And as usual the code has no wait loops or unnecessary delays anywhere.

You should connect a button as in the earlier posting and the button functioning should exercise the program enough to see some charger state changes on your PC terminal (F8).

Now, the code is getting bigger now that our project is growing. I compile the following code to 550 bytes on a 28X, so smaller memory Picaxes are not going to fit the code in. Sorry. If you have a suitable Picaxe, press onwards.

Just check the code against your own PWM and button pins. Then the code should paste and load to your Picaxe and run. View the results on the PC (F8). The button should change states as commanded. Overcharge protection timeout (20 secs!) and trickle timeout (10 secs) should also kick in. But nothing really &quot;analog&quot; like ailing battery voltage is implemented.

Try.

<code><pre><font size=2 face='Courier'>
'======================================================================
' An example of (nearly) a REAL project
' A NiMH Battery Charger using Temperature Rise detection

' Uses Multitasking, Events, and System Timer and a State Machine

' By: Brian Lavery (Queensland Australia)
'======================================================================

#picaxe 28X

'Power Modes (&quot;states&quot;)
' Only 3 of them!
symbol Pwr_Idle = 0
symbol Pwr_Trickle = 1
symbol Pwr_Chg100 = 2

' Reason Codes
' For logging purposes?
' For showing to a human?
symbol RC_Button = 1
symbol RC_ShortButton = 2
symbol RC_LongButton = 3
symbol RC_MedButton = 4
symbol RC_BatteryLowVolts = 5
symbol RC_TimeOut = 6
symbol RC_TempRising = 7
symbol RC_BatteryOverTemp = 8
symbol RC_NoSupply = 9

' I/O PINS
symbol OUTC_Chg = 4 ' on portC on my 28X. PWM capable
symbol IN_BUTN1 = pin0 ' Button to ground, pullup resistor to positive.

symbol PIC_TMR1H = 0x0F ' (MSB) PIC Timer1 data (counter) register
symbol PIC_T1CON = 0x10 ' PIC Timer1 Control Register

' FAULT CODES for DIAG LAMP (bit numbers)
symbol FAULT_BAT_TEMP = 2
symbol FAULT_CHARGE_TIMEOUT = 16

' RAM STORAGE VARIABLES

symbol Pwr_Mode_Current = 0x51
symbol Systime0 = 0x52
symbol Systime1 = 0x53
symbol systime2 = 0x54
symbol ButtonDowntime = 0x55
symbol ReasonCode = 0x59 ' Reason for mode change
symbol TimerLast = 0x5A

symbol TempBat = 0xC2
symbol VoltBat = 0xCC
symbol VoltChg = 0xD0

symbol ChgTime = 0xD4
symbol TrikTime = 0xD6
symbol temp4min = 0xD8
symbol temp2min = 0xDE

' VARIOUS SET-POINTS. These will be used as reference comparison values for some tests in the code.
' The Temp &amp; Volt values I worked out by separately calibrating my TEMP and VOLT analog inputs!
symbol SP_Temp_BatVeryHi = 0x0339 ' calibrate to 50 degr
symbol SP_Time_ChgLong = 0x0014 ' 20 secs. In real life it might be 3 hours!
symbol SP_Time_TrikTimeup = 0x000A ' 10 secs. In real life, maybe 30 mins
symbol SP_Volt_Charger = 0x0200 ' Charger voltage is present ready for charging
' (some rather arbitrary threshold to show high enough)
symbol SP_Temp_BatRise = 0x0027 ' I found this meant about 3 degrees Temp Rise
' around the temp range expected
symbol SP_Volt_BatFlat = 0x02b4 ' calibrate to 1.1 V/cell

' EEPROM DATA MEMORY
' 0x00-0x7F
' Use read/write
symbol ERR_flags = 0x00
DATA (0)

' GP VARIABLES

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 ' *
symbol ButtonDownLastLoop=bit3 ' *
symbol ButtonEventflag = bit4 ' * ALL THESE BITS ABOVE PRESERVED - Never clobber!

symbol ButtonIsDown = bit8 ' (Will be used only in Button driver)

symbol Pwr_Mode = b2 ' * PRESERVED

symbol PwrModeChange = bit10 ' used in PWR-set area


'=================================================================

main:
poke PIC_T1CON, 0x31
Pwr_Mode = Pwr_Idle

poke VoltBat, 0x0300 ' These 3 lines are simply to start with some dummy
poke TempBat, 0x0300 ' data that will let our simulation work on the Picaxe.
poke VoltChg, 0x0300 ' Normally, an Analog_Dvr would be supplying this data

execloop:
gosub Tick_Dvr
GoSub Buttons_Dvr
' gosub Analog_Dvr ' Omitted here
' gosub Led_Dvr ' Add them back in yourself
' GoSub CriticalBattery_Dvr
gosub Monitor_Dvr
gosub Power_Dvr

goto execloop

'=================================================================
Tick_Dvr:
' Standard
Tick_flag = 0
Mins_Flag = 0
peek PIC_TMR1H, b9
peek TimerLast, b10
poke TimerLast, b9
if b10 &gt; b9 then
tick_flag = Tick_hi
Tick_hi = 1- Tick_hi
Peek systime0, b5
b5 = b5 + 2 And 0xFE
Poke systime0, b5
if b5 = 0 then
peek systime1, word w6
inc w6
poke systime1, word w6
Mins_Flag = 1
EndIf
EndIf
Return

'=================================================================

Buttons_Dvr:
' Same button driver as earlier
ButtonIsDown = 1- IN_BUTN1
if ButtonIsDown = 1 then
ButtonEventFlag = 0
if ButtonDownLastLoop = 0 then
Poke ButtonDownTime , 0
EndIf
if Tick_flag = 1 then
peek ButtonDownTime, b6 : inc b6 : poke ButtonDownTime, b6
EndIf
Elseif ButtonDownLastLoop = 1 then
ButtonEventFlag = 1
EndIf
ButtonDownLastLoop = ButtonIsDown
return

'============================================================

Monitor_Dvr:

' Drivers to <b>monitor all conditions - &amp; revise power states </b>
' No actual mode changes are effected here - just <b>flag the new mode requested </b>

' Before we test any conditions, we make sure we are keeping a &quot;4-minute-old&quot; temperature reading
' This code might logically be placed in some other location??
peek systime0, word w3
peek Temp4min, word w2 ' will test this shortly, checking it is not still 0
if tick_flag = 1 then
w3 = w3 and 0x01FF ' now w3 = 0 for once every 2 mins
if w3 = 0 or w2 &lt;= 10 then
' we arrive here once per 2 mins
' EXCEPT: forced through here on 1st 2 ticks
' The w2&lt;=10 above is a trick:
' It spots that Temp4min has NEVER had any valid data,
' So quickly loads the initial values in (copy of current TempBat)
peek Temp2Min, word w4 ' bump 2 min temp
poke Temp4Min, word w4 ' ... into 4 min temp
peek TempBat, word w4 ' and set current battery temp
poke Temp2Min, word w4 ' ... into 2 min temp
EndIf
Endif

' 1. Button input?
if ButtonEventFlag = 1 then
peek ButtonDownTime, b3
If b3&lt;2 then
' short press - OFF
Pwr_Mode = Pwr_Idle
poke ReasonCode, RC_ShortButton
ElseIf b3 &lt; 5 then
' medium press - start Trickle
Pwr_Mode = Pwr_Trickle
poke ReasonCode, RC_MedButton
Else
' long press = start charging 100%
Pwr_Mode = Pwr_Chg100
poke ReasonCode, RC_LongButton
Endif
ButtonEventFlag = 0 ' We caught ALL button events here
EndIf

peek Pwr_Mode_Current, b5
ON b5 GOTO Mon_off, Mon_Trik, Mon_Chg

Mon_off:

' 2. Lo bat?
peek VoltBat, word w4
peek VoltChg, word w5
if w4 &lt; SP_Volt_BatFlat And w5 &gt; SP_Volt_Charger then
' Bat volts Low _AND_ charger supply is connected = start charge
Pwr_Mode = Pwr_Chg100
poke ReasonCode, RC_BatteryLowVolts
EndIf

Return

Mon_Trik:

' 2. time elapse?
peek TrikTime, word w4
If w4 &gt; SP_Time_TrikTimeUp then
Pwr_Mode = Pwr_Idle
poke ReasonCode, RC_TimeOut
EndIf

' 3. No charger supply?
peek VoltBat, word w5
if w5 &lt; SP_Volt_Charger then
Pwr_Mode = Pwr_Idle
poke ReasonCode, RC_NoSupply
EndIf
return


Mon_Chg:

' 2. total time elapsed too long? STOP. Dont even go to trickle
peek ChgTime, word w4
If w4 &gt;= SP_Time_ChgLong then
Pwr_Mode = Pwr_Idle
read ERR_flags, b4
b4 = b4 or FAULT_CHARGE_TIMEOUT
write ERR_flags, b4
poke ReasonCode, RC_Timeout
EndIf

' 3. bat o/temp?
Peek TempBat, word w4
If w4 &gt; SP_Temp_BatVeryHi Then
Pwr_Mode = Pwr_Idle ' Something wrong - STOP
read ERR_flags, b4
b4 = b4 or FAULT_BAT_TEMP
write ERR_flags, b4
poke ReasonCode, RC_BatteryOverTemp
EndIf

' 4. bat rising temp - the PRIMARY end-of-charge test
Peek Temp4min, word w4
Peek TempBat, word w5
w6 = w5 - w4 ' w6 = temp rise over 4 mins
If w6 &gt; SP_Temp_BatRise then
Pwr_Mode = Pwr_Trickle
poke ReasonCode, RC_TempRising
EndIf

' 5. No charger supply?
peek VoltBat, word w5
if w5 &lt; SP_Volt_Charger then
Pwr_Mode = Pwr_Idle
poke ReasonCode, RC_NoSupply
EndIf

return

'=================================================================

Power_Dvr:
' Drivers to <b>set &amp; maintain power state </b>
' ENTRY: Pwr_Mode_Current = mode already in place from an earlier loop
' Pwr_Mode = mode required from now on
' JOB: Start or maintain the (new?) required mode
PwrModeChange = 0
peek Pwr_Mode_Current, b5
if b5 != Pwr_Mode then
PwrModeChange = 1
Peek systime1, word w6
peek ReasonCode, b11
sertxd (&quot;State&quot;, #b5, &quot; to &quot;, #Pwr_Mode, &quot;@&quot;, #w6, &quot;min RC=&quot;, #b11, CR, LF)
gosub P_off ' STOP the expiring mode first before starting new mode
EndIf
ON Pwr_mode GoSub P_off, P_Trik, P_Chg
poke Pwr_Mode_Current, Pwr_Mode
Return

P_off:
pwmout portc OUTC_Chg, 0,0 ' STOP any pwm mode charging
LOW portc OUTC_Chg ' stop any 100% charging
return

P_Trik:
' Start or maintain trickle mode. Keep count of elapsed trickle time
if PwrModeChange = 1 then
pwmout portc OUTC_Chg, 255, 30
EndIf
peek TrikTime, word w4 ' -&gt; w4=tiks so far
if tick_flag != 0 then
w4 = w4 + 1 ' incr tiks
EndIf
if PwrModeChange != 0 then
w4 = 0 ' or zero trickle tiks
EndIf
poke TrikTime, word w4 ' store revised tiks count
return

P_Chg:
' Start or maintain Full-charge mode. Keep count of elapsed charging time
if PwrModeChange = 1 then
high portc OUTC_Chg
EndIf
peek ChgTime, word w4 ' -&gt; w4=tiks so far
if tick_flag != 0 then
w4 = w4 + 1 ' incr tiks
EndIf
if PwrModeChange != 0 then
w4 = 0 ' or zero trickle tiks
EndIf
poke ChgTime, word w4 ' store revised tiks count
return

'=================================================================
</font></pre></code>

Now, I should be getting off the streets at this hour. The local gangs seem to be getting restless. Maybe I'll just stay in and look out my window ... it looks like there is a light on now over the laneway at hippy's window.

&#160;

Edited by - bflavery on 10/01/2007 12:46:51
 

ArnieW

Senior Member
Hi Brian,

I'd like to thank you and encourage you to keep posting, although I agree that a web-based tutorial might be a good end place for all your work.

I've not had the chance to nut out what you've posted in detail, but conceptually I feel I have already benefited from it. I have a fairly complex network of picaxes that I'm still working on, and the 'no delay' concept is valuable towards this, as is the concept of doing an input read once and running multiple sub-programs that then use the info on a timely basis.

Given that serial (serin) comms is one of the worst culprits for delay (or lock-up), and that it is central to my networking of picaxes, I was wondering if you have thoughts on a work-around using your multi-tasking approach?

Currently I am relying on handshaking and interrupts to 'alert' the picaxes to comms input, get them to respond (serout) if comms has been received, and have a PC coordinate the network.

cheers, Arnie
 

bflavery

New Member
Arnie,
Assuming you have standard serial hardware connected, can you persuade both ends to use the same TD and RD lines to do a bit of bit-banging to implement the handshaking? You will doubtless need some IO driver function at the PC to allow hardware access (unless you base your stuff way back on old DOS which allowed anything). There are such drivers around on the web for NT/XP.

The Picaxe could sit idle at NOT READY, RS232 &quot;mark&quot; if I remember, and on request from PC, resume the standard &quot;space&quot; idle voltage level, while now &amp; immediately executing a SERIN.

That still can be a problem if the Picaxe SERIN fails to &quot;get its man&quot;, and waits and waits.

What speed do you need? Can you run the PC at 50 baud or something silly, and bit-bang the Picaxe end. Then you can create your own timeout escapes. Perhaps adding a dummy lead-in character(s) may ease the synchronising.

Really, though, I agree, the Picaxe SERIN is a problem.

Brian

 
 

Brietech

Senior Member
If you're already using a &quot;network&quot; of picaxes, and you were to use one which involved a common output/input line for serial comms (with diode protection, etc.), you could hang a 08/08M off the bus as a &quot;watchdog&quot;. If you alerted it prior to going into &quot;serin&quot;, it could be timed to output a character you could just test for to see if the communication had timed out or not.

i.e.
1) would-be receiver sets &quot;waiting to receive&quot; line high
2) Watchdog notices line goes high, begins timeout counter
3) at predetermined intervals (say 1-2 seconds depending how long your handshaking typically takes), if the &quot;wait&quot; line is still high, it sends &quot;TO&quot; out onto the serial bus
4) The initial chip waiting to receive tests the input with an &quot;If (input) = &quot;TO&quot; then timeout&quot; statement

It won't work with hippy's multitasking idea he currently has going on, but using a bigger chip like an 18x or something if you need several lines to be monitored should also be possible
 

bflavery

New Member
I have quite simply reproduced my postings as above on my own website. <A href='http://www.blavery.com' Target=_Blank>External Web Link</a>

In due course I ought re-format it better for web presentation, but for now it still looks like the original. Hope it serves some good somewhere.

Brian
www.blavery.com


 
 

bflavery

New Member
<b>Upgrading to hippy's Co-operative Multitasking </b>

All the examples I have presented earlier were based on a simple round-robin loop of fast-returning subroutines.

Hippy has on the thread &quot;Timesliced Multitasking Picaxe 18X&quot; shown ways to do pre-emptive and/or co-operative task switching. I have taken the liberty of tweaking his co-operative version to try to marry one of my earlier code examples to this newer technique.

I prefer his co-operative mode, and here are some of the thoughts that are relevant:

1. The pre-emptive mode is still blindly round-robin. It has no protection of critical code, no assignment of more time for priority code. Although some time priority adjustments using his StopTask and RunTask could help here.

2. The pre-emptive mode leaves some asynchronous problems such as split reads of word variables.

3. Pre-emptive mode makes virtually essential a saving of ALL variables b0 - b13 at switching. Saving of just some is OK in co-operative. That saves some time &amp; codelength.

4. Since task changing is done at interpreter level, not assembler, it adds very significant time overhead. Co-operative swapping naturally allows you to code to balance up how often &amp; where the swaps occur.

5. If the pre-emptive mode is driven too fast, it promptly adds unacceptable time overhead. If it is run too slow, time between consecutive visits to a task (ie, yield() return time) is too long. So interrupt rate needs tuning for best results.

6. The pre-emptive model uses up two I/O ports.

In any case, ALL of the 3 multitasking modes, being interpreter based, are SLOW. Projects needing fast multitasking are not really a proposition on the Picaxe.

In some (limited) testing, I find that where the &quot;loop of subroutines&quot; mode might get say 20-30 loops per second if well coded, the co-operative multitasking might give about 5-8 loops / second. (And my &quot;tick timer&quot; using timer1 might start getting marginal, running over the limiting 524 mSec for one full loop through all tasks.)

Anyway, I did re-code the earlier &quot;diagnostics LED driver&quot; to hippy's co-operative model. I saved variables b1 - b5 for each task. Note how those are safely useable now as &quot;local&quot; variables, a pleasure for the Picaxe!

Note too, that I integrated the earlier system timer driver right into the task switcher - more for efficiency than for code cleanness. It returns w6 as system time after every yield. It can of course be tossed aside by any task, but it is often useful in the task as a way of programming a delay(seconds) function using yield.

Conclusion? Would I re-code projects using this technique? Not sure. The earlier multitasking was SLOW. This is SLOWER. That is definitely the most likely killer. For projects where speed is <b>VERY </b> non-priority, then yes, I could use it. For NEW projects, if multitasking was a reasonable approach at all, I WOULD be trying first to make the co-operative style squeeze in and work. Because it is intellectually more satisfying.

But lots of projects would not suit.

Here is the re-jigged code. Reference it against my earlier posting. It's a bit rough. But set the LED to the correct pin, and it should compile and run.

<code><pre><font size=2 face='Courier'>
; *****************************************************************************
; * *
; * Co-Operative Multi-Tasking for the PICAXE-28X - Diagnostic LED *
; * This multitasking &quot;engine&quot; courtesy of hippy's clever TASKING9.BAS *
; * Blatant hacking by me. Timer added by me. *
; * Sorry - rough - proof of concept standard only. *
; * *
; *****************************************************************************


; -----------------------------------------------------------------------------
; Task State Variables - Held in User SFR
; -----------------------------------------------------------------------------

SYMBOL TASK = $C0 ; Executing Task Number

SYMBOL SAFEB2 = $C1 ; Protected b2 for switching
SYMBOL SAFEB1 = $C2 ; Protected b1 for switching

SYMBOL T1FLG = $C3 ; Task 1 TaskControlBlock (sizeof = 10)
SYMBOL T1SP1 = $C4
SYMBOL T1SP2 = $C5
SYMBOL T1SP3 = $C6
SYMBOL T1SP4 = $C7
SYMBOL T1B1 = $C8 'b1
SYMBOL T1B2 = $C9 'b2 w1
SYMBOL T1B3 = $CA 'b3
SYMBOL T1B4 = $CB 'b4 w2
SYMBOL T1B5 = $CC 'b5

SYMBOL T2FLG = $CD ; Task 2 TCB (10 bytes also ....)
SYMBOL T2SP1 = $CE

SYMBOL T3FLG = $D7 ; Task 3 TCB
SYMBOL T3SP1 = $D8

SYMBOL T4FLG = $E1 ; Task 4 TCB
SYMBOL T4SP1 = $E2


SYMBOL SP1 = $2A ; Stack SFR's
SYMBOL SP2 = $2B
SYMBOL SP3 = $A8
SYMBOL SP4 = $A9


symbol Systime0 = 0x52 ' half secs to 2 mins
symbol Systime1 = 0x53 ' 2 mins to 8.5 hrs
symbol Systime2 = 0x55 ' 8.5 hr to 91 days - NOT IMLEMENTED
symbol TimerLast = 0x55

symbol PIC_TMR1H = 0x0F ' (MSB) PIC Timer1 data (counter) register
symbol PIC_T1CON = 0x10 ' PIC Timer1 Control Register

symbol Tick_flag = bit0 ' * set for 1 exec loop per tick (second)
symbol Tick_hi = bit1 ' * hi or lo half second of each tick

; *****************************************************************************
; * *
; * &quot;OS&quot; Task Switching &amp; Timer Code *
; * *
; * 4 Tasks, 8 Hour system &amp; tick Timer *
; * *
;; *****************************************************************************

; These are the Task Initiators - Code can be added here but not GOSUB's

FOR b1 = $C0 TO $FF ; Initialise upper SFR
POKE b1,0
NEXT
POKE T2SP1,1 ; Initialise the tasks except Task 1
POKE T3SP1,2
POKE T4SP1,3
poke PIC_T1CON, 0x31 ' start PIC timer1 at 524 mSec 16-bit rollover

GOSUB Task1
GOSUB Led_Dvr
GOSUB Task3
GOSUB Task4

; ------------------------------------------------------------------------------
; Actual task switching. w6 always returns With system Time (half secs)
; Acknowledgments to hippy.

Yield:
POKE SAFEB2,b2
POKE SAFEB1,b1
PEEK TASK,b2

; Save current task

LOOKUP b2,(T1SP1,T1SP1,T2SP1,T3SP1,T4SP1),b1

PEEK SP1,b2 : POKE b1,b2 : b1 = b1 + 1 ; Save Stack
PEEK SP2,b2 : POKE b1,b2 : b1 = b1 + 1
PEEK SP3,b2 : POKE b1,b2 : b1 = b1 + 1
PEEK SP4,b2 : POKE b1,b2 : b1 = b1 + 1

PEEK SAFEB1,b2 : POKE b1,b2 : b1 = b1 + 1 ; Save b1
PEEK SAFEB2,b2 : POKE b1,b2 : b1 = b1 + 1 ; Save b2
POKE b1,b3 : b1 = b1 + 1 ; Save b3
POKE b1,b4 : b1 = b1 + 1 ; Save b4
Poke b1, b5 ' save b5

; Choose what to run next

Choose:

PEEK TASK,b2
LOOKUP b2,(2,2,3,4,1),b2 ' round robin
POKE TASK,b2

' at this point, b1 - b5 are shielded. W6 is global systime returner.
' Once thru round-robin, read the time
peek SysTime0, word w6
If b2=1 Then
tick_flag = 0 ' on for only 1 cycle / second
peek PIC_TMR1H, b3
peek TimerLast, b4
If b3 &lt; b4 Then
inc w6
b1 = b12
Tick_flag = bit8
Tick_hi = bit8 ' on for half every second
poke SysTime0, word w6 ' 8 hour capacity
'If w6 = 0 Then ' Not implemented
' peek Systime2, b2
' inc b2 ' 91 Day capacity
' poke Systime2, b2
'Endif
EndIf
Poke TimerLast, b3
EndIf

; Load the task

LOOKUP b2,(0,T1FLG,T2FLG,T3FLG,T4FLG),b1

PEEK b1,b2 : IF b2 &lt;&gt; 0 THEN Choose

b1 = b1 + 1 : PEEK b1,b2 : POKE SP1,b2 ; Load Stack
b1 = b1 + 1 : PEEK b1,b2 : POKE SP2,b2
b1 = b1 + 1 : PEEK b1,b2 : POKE SP3,b2
b1 = b1 + 1 : PEEK b1,b2 : POKE SP4,b2

b1 = b1 + 3 : PEEK b1,b3 ; Load b3
b1 = b1 + 1 : PEEK b1,b4 ; Load b4
b1 = b1 + 1 : PEEK b1,b5 ; Load b5
b1 = b1 - 3 ; Point to b2
PEEK b1,b2 : b1 = b1 - 1 ; Load b2
PEEK b1,b1 ; Load b1

RETURN

; *****************************************************************************
; * *
; * USER TASK CODE *
; * *
; * 4 tasks which must yield regularly. *
; * All variables are shared exc b1 - b5. So those are &quot;local&quot;. *
; * W6 contains systime after all yields. *
; * *
; *****************************************************************************

Task1:
DO
GOSUB Yield
LOOP

; -----------------------------------------------------------------------------

Led_Dvr:
Task2:

' Drive DIAGNOSTIC led
' Flash the LED in an 8-bit sequence of short &amp; long pulses.

' I/O PINS
symbol OUT_DiagLed = 2 ' Out pin with LED. MY hardware. Change to YOUR situation.

' EEprom storage space
symbol ERR_flags = 0 ' Where SEPARATE code places 8 bits of error data
DATA ERR_flags, (%01000011) ' Initial dummy error codes for our testing

' Local variables b1-b5 - won't be corrupted by OS or other tasks
symbol DiagBitRotator = b1

GoSub yield
b12 = b12 and %00011110 ' Synchronise at 16 seconds of tik count (w6 = timer)
If b12 != 0 Then Led_Dvr ' Start or wait?
read ERR_flags, DiagBitRotator ' Fetch current 8 error codes
For b2 = 1 to 8 ' Loop for each error bit
Do until Tick_flag = 1 ' Synchronise each flash on Tick
GoSub yield
Loop
b3 = bit15 ' Top bit. b3 = equiv to divide by 128!
DiagBitRotator = DiagBitRotator * 2 or b3
' Equiv to &quot;rotate bits left&quot;
if DiagBitRotator != 0 then
High OUT_DiagLed ' Start of each flash, unless NO ERRORS AT ALL
EndIf
pause 5 ' Short flash by PAUSE rather than by yield!
' Yield() = about quarter tick = too long for short flash
Do While bit8=1 and Tick_hi=1
GoSub yield ' If &quot;long&quot; flash, wait around for half tick
Loop
Low OUT_DiagLed ' LED off
GoSub Yield ' (To ensure Tick Flag off at NEXT/FOR)
Next
GoTo Led_Dvr


; -----------------------------------------------------------------------------

Task3:
DO
GOSUB Yield
LOOP
; -----------------------------------------------------------------------------

Task4:
DO
GOSUB Yield
LOOP
; ------------------------------------------------------------------------------

</font></pre></code>

&#160;

Edited by - bflavery on 15/01/2007 02:23:34
 

Jeremy Leach

Senior Member
<i>Projects needing fast multitasking are not really a proposition on the Picaxe </i>

Well, unless you use multiple Picaxes which many people have done.
 

hippy

Technical Support
Staff member
Some comments on your observations ...

<i>1. The pre-emptive mode is still blindly round-robin. It has no protection of critical code, no assignment of more time for priority code. </i>

That could be implemented, but obviously adds time to switching, needs variables/SFR to hold a task order list, and possibly a means to update the list at run-time.

<i>2. The pre-emptive mode leaves some asynchronous problems such as split reads of word variables. </i>

That's very true. A split word variable access isn't a problem if used only by one task, but if shared between tasks or reading on-chip registers it will be. Even shared bit / byte variables can cause a problem if reads and writes are not done safely; one task writes then another runs which completes an earlier interrupted write which overwrites the variable.

It would be possible to disable interrupts and later re-enable them for critical code sections, or simply stop the timer from running for a while.

<i>3. Pre-emptive mode makes virtually essential a saving of ALL variables b0 - b13 at switching. Saving of just some is OK in co-operative. That saves some time &amp; codelength. </i>

Both the pre-emptive and co-operative schedulers only need to use two byte variables.

[ Added ] I perhaps misunderstood your comment. If a task used a temporary variable at any time, no other task can use that variable ( unless it is saved ) because the pre-emption isn't predictable. That knowing a variable can be used because no other task is using it 'at that point in time' is no longer true.

<i>4. Since task changing is done at interpreter level, not assembler, it adds very significant time overhead. </i>

I haven't timed it but probably quite considerably, in the order of a good few mS, and more if switching variables.

If tasks are having to load and save variables anyway it's probably not too much of an additional overhead, no different than having those task call the Load and Save routines anyway.

<i>In any case, ALL of the 3 multitasking modes, being interpreter based, are SLOW. Projects needing fast multitasking are not really a proposition on the Picaxe. </i>

I'd concur with that, although when the X2 series arrive we will hopefully be looking at a five to ten-fold increase in speed which will improve matters. Hopefully any internal X2 architecture changes won't shut the door on multi-tasking.

<i>Conclusion? Would I re-code projects using this technique? Not sure. </i>

The simple list of GOSUB's to chain tasks is no bad thing, and exactly how I've done it in commercial systems and in other PICAXE projects. For most applications I think this is the best way to go.

The time-slicing and then the co-operative versions were really intellectual challenges to 'prove the impossible can be done on a PICAXE'. There are applications where co-operative tasking would be useful, particularly if an application is already task-based with local variables, as it's really just making the code logic more readable, but it does add some overhead. There's not really a lot of difference between the two ways of coding ...<code><pre><font size=2 face='Courier'> Main: Gosub Task1
Gosub Task2
Goto Main

Task1: Do Task1: Gosub LoadTask1Vars
' Do Things ' Do things
Gosub Yield Gosub SaveTask1Vars
Loop Return </font></pre></code> It's really just a matter of clarity and consequentially ease of coding. The right-hand GOSUB List is already an improvement over each task simply jumping to the next one which follows with no GOSUB List needed. As always, on one hand its having fine control over operation with more complex and harder to understand code, and on the other, clear code but with overheads tucked elsewhere. One has to decide which is most suitable or required for a task in hand.

Due to PICAXE speed, time-slicing isn't really as useful as the co-operative scheduler can be, and the co-operative scheduler is best suited where the design would benefit from it. Most programs probably wouldn't, but I do have one which would. That was designed as a set of asynchronous, co-operating tasks anyway, so it's ideal.

Edited by - hippy on 14/01/2007 18:10:48
 
Top