External Links

Creative Science Centre

 

Scheduling

There are several reasons why scheduling was not included in the older version of ByPic:

  1. It takes up a considerable amount of resources, not only in CPU time but also at each scheduled event the current state of the machine needs to be saved, pushed to a stack. There is a lot more to save on ByPic than on the old BV_Basic
  2. On a small machine it is questionable if it is needed? Scheduling is good for larger machines where multi-tasking is required with unspecified routines and lots of them. On a microcontroller there is much more control over the resources that are in use.
  3. Most of the time it is necessary to know the precise timing of events, this is not really practical with a scheduling arrangement, unless the timing is much slower than the microcontroller

I actually had an occasion to use the scheduling of the old BV_Basic on a reasonably large project and at first this seamed like a good idea, it certainly made things very simple at first as I could assign a process to a task and more or less forget about it.

Unfortunately however as things grew so did the unforeseen problems but worse the problems did not show themselves immediately, more at random. This is the worst kind of problem. Most systems are simply input – process – output. Assigning processes to manage the inputs & outputs turned out to be a bit of a mistake, although the inputs and outputs were not time critical there was the possibility of them overlapping and giving inconsistent results.

The morel of the story is to always KNOW what is happening, with scheduling (on any system) it is not possible to know exactly what and when something is happening.

The alternative

This is not really an alternative to scheduling but scheduling with much more control and without the overhead of having to save the machine state.

The method of controlling a system by input – process – output may seem obvious but it has got some interesting aspects. This does NOT need to be a linear process: input, process it, output. This can and quite often is handled in the main program loop. The loop waits for something to happen and then acts on it. There is noting wrong with this arrangement, however there is a possibly better way, particularly when the system gets a bit larger.

The technique is to use a variable to set an output or get an input, rather than the port itself, this has some advantages in that a certain amount of processing can go on 'behind the scenes' before it gets to the main loop. A switch debounce for example is a good candidate for this.

If a timer and interrupt are added to the mix then we have the basis of a powerful and scalable method of controlling any system. The only thing to watch out for is to make sure that the interrupt routine does not take longer then the time interval.

Example

In the following system we have a bit of wire to act as a switch and an LED. When the wire is connected to ground is is ON, we will call this a push button. The bush button can have 3 states, off, on and long press. The LED can also have 3 states, off, on and flashing. On the example it actually has 4 states, flashing fast and flashing slow.

Although the description of the system above is often used on 'real-life' systems and seams trivial it is in fact very difficult to implement and incorporate into another system without the use of an interrupt and timer as described below.

Although this is just a button and an LED, I am sure if you were to tackle this problem using a main loop, because of the different states, things would get complicated fast. Also any changes would be quite difficult to implement.

http://www.byvac.com/mBlib/flb/Tutorial/scheduling/scheduling.bas

The above is a complete example using RB4 for the LED port and RB13 for the Push Button Port. As said before there is no need to connect a push button, simply use a piece of wire and touch it to ground for ON. An explanation of how it works follows in detail.

// Variables used for I/O
dim Led, PbOn
dim LedControl, PbControl
dim realTime

Variables are used to control the program rather than the actual port values:

  • Led, 0 = off, 1 = on, 2 flashing fast, 3 flashing slow
  • PbOn, 0 = 0ff, 1 = on, 2 = long press
  • realTime gets incremented at each interrupt

The control variables are used in the interrupt function. They must all be global as the state is kept between interrupts.

// *****************************************************************************
// Initialise ports and set up timed interrupt
// *****************************************************************************
function init()
    // set both as outputs
    io_pinMode(PORTB,LED,OUT,WOFF)
    io_pinMode(PORTB,PB,IN,WPU)
    Led = 0
    LedControl = 0
    PbOn = 0
    PbControl = 0
    realTime = 0
    
    // set up timer and interrupt
    tmr_set(?TIMER23,10000) // 10 mS
    ir_set(?IrTIMER23,"ir_control")
endf

The initialisation must set the global variables to a known state and also of course the port directions. This is also where the timer and interrupt are started. The timer is set for a reasonable 10mS, this will allow for a quite large interrupt routine and thus allow for changes and expansion. Once init() is called the interrupt will begin.

// *****************************************************************************
// this is called regardless every 10mS
// *****************************************************************************
function ir_control()
    // used as and when required by other funcs
    realTime = realTime + 1
    
    // switch
    // short press lasting < 300mS is on PbOn = 1
    // long press > 300mS is PbOn = 2
    if io_read(PORTB,PB) = 0 then
        PbControl = PbControl + 1
        // 50mS is debounce
        if PbControl > 5 then
            PbOn = 1
        endif
        if PbControl > 30 then
            PbOn = 2
        endif
    else
        PbControl = 0 // nothing pressed so clear control
    endif
    
    // LED 0, off, 1 on, 2 flashing fast, 3 flashing slow
    if Led = 1 then
        io_write(PORTB,LED,HIGH)
    endif
    if Led = 0
        io_write(PORTB,LED,LOW)
    endif
    if Led > 1 then
        LedControl = LedControl + 1
        if (Led = 2) && (LedControl > 10) then
            LedControl = 0
            io_write(PORTB,LED,TOGGLE)
        endif
        if (Led = 3) && (LedControl > 30) then
            LedControl = 0
           io_write(PORTB,LED,TOGGLE)
        endif
    endif
endf

The interrupt routine is where all of the work is done. We can look at this as having 3 sections. The first section simply increments a counter called realTime, this global variable can be used by any function and as we see later it is used by the main loop.

The second part is the action for the push button. The button must be connected to ground on 5 consecutive interrupts for it to register that it is on. This will prevent any false multiple presses being detected. The variable PbOn is set to 1 to indicate that a valid on is present. If the push button is kept on for longer than 30 iterations of the interrupt then PbOn is set to 2 indicating that it is a long press. PbControl keeps track of this and is set to 0 whenever the button is not pressed.

Observe also that PbOn is not set to 0 at any time, the reason for this is so that a push button action cannot be missed. It is up to the main loop to recognise that there has been a push button event and then set the PbOn variable to 0.

The third part is for the LED (output). In this case we have an LedControl variable to make the LED do what we want. The variable Led is set to a value by the main loop; 0,1,2 or 3. LedControl will take care of the various timings.

Interactive

Before looking at the main function test(), if the program is downloaded and init() typed to get the timer and interrupt going, typing Led = 1 (case sensitive) the LED will illuminate, Led = 2, etc. will make it flash. We can see that the output is working as expected without having to write any test routines. Do the same with print PbOn.

function test()
dim ledOff
   init()
    while comkey?(2) = 0
        if PbOn = 1 then
            ledOff = realTime
            Led = 1
            PbOn = 0
        endif
        if PbOn = 2 then 
            ledOff = realTime
            Led = 2
            PbOn = 0
        endif
        if (ledOff + 200) < realTime then // 2 seconds
            Led = 0
        endif
    wend
endf

The test routine dosen't have much work to do, nothing complicated anyway. The ledOff variable is used to make a note of the time when the LED came on and switch it off after 2 seconds. The main loop is responsible for clearing PbOn.

Conclusion

The initial premiss was to explain why scheduling is not available in ByPic. If it was then this task could have been done just the same but at each interrupt all of the machine state would need to be saved. Using this method no machine state needs saving. All of the information that is required from interrupt to interrupt is carried by a few global variables.

The example used, although just a switch and LED is not in fact trivial. Because the LED is flashing and the push button distinguishes between on and long press, timing is involved. Another way to do this without using an interrupt is to simply call ir_control() as part of the main loop, when the timer interval is triggered a flag is set (by the CPU), ir_control() can monitor this flag. If the flag is not set simply return from ir_control() without doing anything. If it is set then carry out the code in ir_control() and clear the timer flag.

The timing will not be as accurate as the interrupt but if the main loop period is short compared to the timer interval it will certainly be good enough for this application, the interrupt could then be used for possibly a more time critical requirement.

The method described above lends itself to much larger projects. Because the inputs and outputs are not directly manipulated there is much greater freedom for applying individual control to each input or output. Also any changes are much easier to implement, this is because the physical aspect of the system (the physical port, RB14) is not part of the main process, it is just part of its own control.