Index

Cooperating Sequential Tasks

  1. Introduction
    1. Arduino
    2. Interrupts
  2. Simple Tasks
  3. Multiple Tasks
  4. Communicating Tasks
  5. Communicating Values
  6. Synchronising Tasks
  7. Buffered Communication
  8. Multiple Senders
  9. Conditional Tasks
  10. Transput
  11. Implementation
    1. Common Data
    2. Tasks
    3. task_builder
    4. task
    5. channel
    6. virtual_machine
    7. set
    8. queue
    9. clock
    10. standard
    11. Adjusting Limits

Multiple Senders



We have already seen how a single channel can connect a single sending task to multiple receiving tasks, and that the rule is that all the tasks involved have to synchronise at a single point in time, i.e. the sender has to be ready to send and all the receivers have to be ready to receive before communication takes place. We will now look at the more general situation where there may be many sending tasks connected to a single channel which may in turn be connected to many receiver.

For simplicity we will first consider the case where multiple senders are connected by a single channel to a single receiver.

images/9-1.png

It does not make any sense for many senders to simultaneously send values to a single receiver because the receiver can only receive one value at a time. Instead, when multiple senders are ready to send on a single channel at the point the receiver is ready to receive, one sender is selected and the others have to wait for the receiver to become ready to receive again. In many systems it may be the case that only one sender is ready to send at any particular time, in which case communication will occur as soon as the receiver is ready. However if more than one sender is waiting to send at the point the receiver attempts to receive, one sender will be chosen and the others will have to wait for another chance to pair with the receiver.

To demonstrate how this works, we will use a modified version of the button_manager from the previous Communicating Tasks section. Instead of simply sending a signal to indicate that a button has been pressed and released, it will send the number of the pin the button is connected to on its output channel. The new definition is shown below and should need no further explanation.

#include "Tasks.h"

bool button_down(int button_pin)
{
  return digitalRead(button_pin) == LOW;
}

bool button_up(int button_pin)
{
  return digitalRead(button_pin) == HIGH;
}

int get_pin()
{
  return get_task_data();
}

void button_manager(int button_pin, int button_signal)
{
  const int debounce_interval = 30;
  pinMode(button_pin, INPUT_PULLUP);
  new_task();
    set_task_data(button_pin);
    wait(button_down, button_pin);
    pause(debounce_interval);
    wait(button_up, button_pin);
    pause(debounce_interval);
    send_on(button_signal, get_pin);
  end_task();
}


A new receiver task similar to that used in the previous Buffered Communication section will be used to print to the serial output the pin numbers sent by a set of the new button_managers. It differs from the previous version only in that the pause after each receiver has been removed.

#include "Tasks.h"

void show_data(int data)
{
  Serial.println(data);
}

void receiver(byte input)
{
  init_serial(9600);
  new_task();
    receive_from(input, show_data);
  end_task();
}


The main program shown below creates and single channel and connects it to three button_managers monitoring buttons on pins 2, 3 and 4. A receiver is then created and connected to the same channel.

#include "Tasks.h"

void setup()
{
  init_tasks();
  
  byte button_signal = new_channel();
  button_manager(2, button_signal);
  button_manager(3, button_signal);
  button_manager(4, button_signal);
  receiver(button_signal);
}

void loop()
{
  run_system();
}


When this program is run with a terminal connected to the serial output, a pin number will be displayed every time a button is pressed and then released.

At this point It is worth looking at a very similar system which can be represented graphically as follows.

images/9-2.png

The important difference is that this system uses three different channels to connect to the receiver instead of one. Clearly this needs a different receiver which is able to connect to three different channels, but the button_managers can be the same as before. A suitably modified receiver might be as follows.

#include "Tasks.h"

void show_data(int data)
{
  Serial.println(data);
}

void receiver(byte input_1, byte input_2, byte input_3)
{
  init_serial(9600);
  new_task();
    receive_from(input_1, show_data);
    receive_from(input_2, show_data);
    receive_from(input_3, show_data);
  end_task();
}


The new system with three separate channels is defined as shown below.

#include "Tasks.h"

void setup()
{
  init_tasks();
  
  byte button_signal_1 = new_channel();
  byte button_signal_2 = new_channel();
  byte button_signal_3 = new_channel();
  button_manager(2, button_signal_1);
  button_manager(3, button_signal_2);
  button_manager(4, button_signal_3);
  receiver(button_signal_1, button_signal_2, button_signal_3);
}

void loop()
{
  run_system();
}


When this system is executed, no matter what order the three buttons are pressed in, the serial output will only ever display them in the order 2, 3, 4 because that is the order the receiver enforces on the system, e.g. if the buttons are pressed in reverse order 4, 3, 2, the button_managers for the buttons on pins 3 and 4 will have to wait for the receiver to get around to receiving from them. It will not do this until it first receives a signal from the manager for the button on pin 2, and at this point it will print out all the button pin numbers in order.

Thus we have two very similar system structures with very different behaviours. The first one with a single channel is useful when it is important to be able to accept input in any order. The second one is useful when it is required to enforce some specific order on the sequence in which inputs are processed.

Next: Conditional Tasks