Early in my exploration of robotics on the Arduino platform, I ran into a number of issues simultaneously that needed addressing before I could move forward like I wanted:

  1. The setup()/loop() model of programming was pretty simplistic. I was thinking in terms of multiple subsystems all doing their own thing and interacting, but the Arduino just gives you “do this, over and over”.
  2. loop() starts executing immediately; but especially with something like a robotics project you want a chance to disconnect the USB cable and put the robot on the floor before it gets started (or get to a safe distance, depending on your application).
  3. Sometimes you want to shut the robot down from a distance, when it gets out of control.

In my previous blog post I showed how to record then recognize an IR message from a remote control button press, and in this post I’m going to introduce the scheduling framework I am using, and show how to use the remote control to switch between a dormant ‘sleep mode’ (sits quietly while an LED blinks) and normal operating ‘awake’ mode when it otherwise follows it’s programming.

Humanity is safe as long as the blue LED is blinking

Humanity is safe as long as the blue LED is blinking

Follow me through the jump if you want to see how I did it. Or just check out the completed sketch [GitHub].

Overall Concept

Just having a single loop repeated over and over is good enough for learning to program, but once I started thinking about the types of inputs I might deal with (ultrasound distance sensor, line-following sensor, gyroscopes, compasses, etc., etc.) and the outputs (two different motors, reporting/telemetry) and the different tasks I’d kind of want to do on different schedules… well, it just didn’t seem like a big loop any more.

Instead it seemed like small tasks all running independently, using shared state to communicate what they know. Rather than just have one big function where everything runs every time (or you have to have special case if-statements), I want to have a task list: “Check the distance to an obstacle”, and “Drive forward until you get close to something”. The first one would do it’s thing, regularly polling the ultrasound sensor, making note of what it sees (“there is something 30cm ahead”) and publishing it to the rest of the system. The second turns the motors on as long as the published distance is more than 30cm.

And that’s the idea: define a series of tasks on different schedules and then loop() goes through each task and checks to see if it’s time to do it’s thing again. I take that basic idea and extend it by supporting multiple ‘states’ (asleep/awake) and allowing for a different task list per state. Note that nothing so far is pre-emptive, this isn’t true multitasking. It all relies on each task being well-behaved. If you have a task that takes 5 seconds to run and another task you want to run every 100ms, you’re going to have a bad day.

We do, however, use an interrupt to watch for an incoming IR message. That part will preempt whatever else is going on in order to record the incoming message and store it in memory, and then will return control back to the task to complete.

Coding the Task List

[Note: I’m not going to go through every line of code here. There’s a bunch that goes with the IR signal detection/matching I did in the last post, and a lot that is pretty boring compared to the cool parts. Please take a look at the completed sketch [GitHub] for the big picture. There’s tons of documentation, I’m hoping that it speaks for itself]

Programmatically speaking, I implement the task list as a linked list where each node is a task. If you’re not familiar with linked lists, now is a good time to learn about them, but the idea is each element in the list points to the next element (or has a NULL pointer to indicate it is the last item in the list). I could have done it just as an array, but if I’m going to go to the trouble of defining a struct anyway, I might as well add the pointer and then I don’t have to worry about defining array lengths or anything.

In this case, it looks like this:

// A task function accepts no input and returns no result 
typedef void (*TASKFUNC)();
 
// The task list is a linked list of individual tasks, which include...
typedef struct _task {
  TASKFUNC taskfunc;           // ...the function to call on each loop
  TASKFUNC onSleep;            // ...the function to call on sleeping (if any)
  TASKFUNC onWake;             // ...the function to call on waking (if any)
  uint16_t everyMs;            // ...how often to call it
  uint32_t nextMs;             // ...the last time it was called
  struct _task* nextTask;      // ...a pointer to the next task (or NULL)
} TASK;

If you’re not used to working with pointers to functions, this will seem a little weird. TASKFUNC is a pointer to a function that takes no arguments and has no return value. Unfortunately, the Arduino precompiler seems to be notoriously cranky when it comes to typedefs. It’s possible I got a little too C-ish here, but at least it seems to compile. :)

Each of my tasks is defined as one of these TASKFUNCs. When it’s time comes around, that the function will be called. Notice that I also define onSleep & onWake TASKFUNCs, which are optional and are called when the system switches state from asleep->awake or vice versa. For instance if one task is controlling the motors of a robot, then when it goes to sleep, you probably need to turn those motors off.

She could use an onSleep handler.

She could use an onSleep handler.

Here’s that specific example, driving forward:

void driveStraight() {
 
  if (g_distance < 20) {
    setSpeeds( 0, 0, 0, 0 );
  }
  else {
    setSpeeds( 200, 0, 200, 0 );
  }  
}

void allStop() {
  
  // When the robot goes to sleep, turn off the engines
  setSpeeds(0,0,0,0);
}

The definition of setSpeeds() just applies analog outputs to the pins controlling the motors and you can see it in the full sketch [GitHub] example.

In this example, I define 3 different tasks in two different task lists. In the Awake task list, I have a task to drive forward, and one to check the distance sensor; in sleep mode I just blink an LED to show that we are asleep. I have a helper function addTask() that hides the heavy lifting, here’s what it looks like in use inside setup():

// We define two task lists, one each for the Sleeping & Awake states. 
// (This could obviously be extended to having a different task list for
// any arbitrary set of states you might want to define for your project)
 
TASK* _pAwakeTaskHead = NULL;
TASK* _pSleepTaskHead = NULL;

void setup() {

  // ...

  // When we're in sleep mode, we won't do much except blink an LED 
  addTask( _pSleepTaskHead, &checkSleep, &clearLed, NULL, 500 );
 
  // When we're awake, drive forward until we're close to something
  addTask( _pAwakeTaskHead, &driveStraight, NULL, &allStop, 250 );
  addTask( _pAwakeTaskHead, &testDistance, NULL, NULL, 100 );
}

addTask() is defined in the sketch, but it’s not terribly interesting, it just adds an entry to the linked list.

Executing the Task List

The addTask() helper function builds up the linked list of tasks, and there is another helper function, runTaskList() that runs through the task list, checks that it’s time, and executes the tasks. It also checks if an IR message to change state has arrived. It’s very easy to use… here’s the complete loop() function for my PiBot:

void loop() {
  
  // Each time through the loop, decide which task list to execute based on sleep state.
  TASK* currentList = g_isAsleep ? _pSleepTaskHead : _pAwakeTaskHead;
 
  // and then run it. 
  runTaskList(currentList);   
}

It’s easy to use, but it’s the most complicated function in the whole thing, so let’s build it up, starting with simply executing each task:

void runTaskList( struct _task* head ) {
  TASK* current = head;

  while (current != NULL)  {
    (current->taskfunc)();         // execute the task function
    current = current->nextTask;   // move to the next task
  }
}

You can see that at it’s core, the execution of the task list is actually pretty simple; we’re just wrapping a little more functionality round it. First, adding in the “is this task scheduled to run?” logic gives you this:

void runTaskList( struct _task* head ) {
  TASK* current = head;

  while (current != NULL)  {
    // Check the current time, compare it against the next scheduled time for this task
    uint32_t currentMs = millis();
    if (currentMs >= current->nextMs) {
      // it's time.  Execute the function and then set the next scheduled time.
      (current->taskfunc)();
      current->nextMs = currentMs + current->everyMs;
    }
    // move on to the next task    
    current = current->nextTask;
  }
}

Then the final part, the code to check if the sleep-mode IR message has come in and toggle the system state if it has. This is now the complete function:

void runTaskList( struct _task* head ) {
  TASK* current = head;
  
  // Iterate through the tasks in the task list
  while (current != NULL)  {

    // If it's time, execute the next task in the list
    uint32_t currentMs = millis();
    if (currentMs >= current->nextMs) {
      (current->taskfunc)();
      current->nextMs = currentMs + current->everyMs;
    }    
    current = current->nextTask;
    
    // If during that process, we received an IR message...
    if (g_irMsgLen > 0) {
      // ...test to see if it matches our pattern...
      if (isMatch()) {
        // ...and if it does, toggle the sleep state
        g_isAsleep = !g_isAsleep;
        g_irMsgLen = 0;

        if (g_isAsleep) 
          Serial.println("Sleeping");
        else
          Serial.println("Waking up");
        
        // ...excute the onSleep() or onWake() functions as respectively...
        TASK* current = head;
        while (current != NULL) {
          if (g_isAsleep && (current->onSleep != NULL))
            (current->onSleep)();
          else if (!g_isAsleep && (current->onWake != NULL)) 
            (current->onWake)();
          
          current=current->nextTask;
        }

        // ...and break out of the current loop so we can go back and get
        // the new task list
        return;
      }
    }
  }  
}

That’s quite a bit going on, but it all happens pretty fast and smoothly keeps the trains running on time. Not bad for 50 lines of code! Here it is in operation:


Wrapping up

I started out with three goals: 1) Develop a more robust model for behavior, 2) Provide a way of delaying normal operation until I was ready to start, 3) Enable some sort of remote shutdown.

The code I have presented here meets those goals, but it’s not without it’s limitations and challenges.

  • The Arduino pre-processor is maddening. You’ll notice that half the time I use TASK and half the time I use struct _task — the compiler just randomly doesn’t want to resolve some of the typedefs. I really should just go back and take out the typedefs all together.
  • It is harder to debug, and the Arduino doesn’t have great debugging capabilities to start with.
  • It forces a certain amount of rigor when it comes to structuring your code, and it makes you really think about how long each task takes.

In truth I consider the last one a desirable side-effect. We should break our code down into small, reusable, functionally distinct pieces, and — especially on a processor with limit horsepower — we should know how long things take to run.

I’ve found this framework to be really pretty helpful. If you’re crazy enough to give it a try, please let me know your response to it. I’m not really planning to turn it into a full project itself — in fact, I’m specifically trying to stay away from that because the Arduino is small and this sort of thing can easily lead to feature creep. I want a small, nimble tool I can apply to multiple uses on the fly, and this seems like a good place to start.

Leave a Reply

Your email address will not be published. Required fields are marked *