A very simple Arduino task manager

The LED chain project I'm working on requires that the AVR microcontroller handles several different tasks:

  • Read a rotary encoder and switch
  • Drive a seven-segment display
  • Drive a radio
  • Drive four LED strips each containing twenty LEDs
  • Provide logic to tie all the above together

Normally that sort of multiple-task workload might suggest the use of a RTOS, but the Arduino Pro Mini has only 16Kb of program memory (of which 2Kb is used by the bootloader) and 1Kb of RAM, so every byte is precious. So, whatever we come up with has to be as minimal as possible. OK, let's see if we can make some assumptions and trade-offs that will help keep things simple and create a small set of C++ classes we can use to implement the task management we need:

  • Use cooperative multitasking rather than preemptive multitasking. Out of that fall a couple of constraints:
    • Each task must complete quickly when it runs. Long-running operations such as delay() are forbidden.
    • Any interrupt service routines must also complete quickly, preferably by recording the details of the event and scheduling a task to handle it.
  • The task management system should not require the use of dynamic memory management (e.g. malloc()) so as to minimise memory requirements.
  • The list of tasks will be fixed at compile-time. That's reasonable as the configuration of the system is fixed - we aren't going to be adding new hardware on the fly.
  • Tasks will be scheduled in priority order to allow processing that has strict time constraints to be handled first, but to keep things simple the priority order will be fixed at compile time. Again, that's reasonable as the configuration of the system is known in advance.
  • Tasks can communicate with each other by making standard C++ method calls but (as for interrupts) any such methods should simply store the details of the event and schedule themselves to be run to handle the event.
  • Much of the processing we are doing is time-driven, e.g. sequencing the LED patterns, so explicit support for scheduling tasks at specific intervals should be provided, as well as more general 'triggered' tasks.

The last constraint needs some further thought - what timer 'tick' interval should we use? The 'real world' events we will be dealing with won't be happening quicker that 1 millisecond apart and the CPU is clocked at 16MHz which equates to 16000 instructions per millisecond. Scheduling time-based tasks at millisecond resolution will allow us to run several tasks within the same 'tick and will be more than fast enough to deal with the events we have to handle.

OK, so given all that, what would a suitable task implementation look like?

class Task {
public:
    virtual bool canRun(uint32_t now) = 0;
    virtual void run(uint32_t now) = 0;
};

class TimedTask : public Task {
public:
    inline TimedTask(uint32_t when) { runTime = when; }
    virtual bool canRun(uint32_t now);
    inline void setRunTime(uint32_t when) { runTime = when; }
protected:
    uint32_t runTime;
};

bool TimedTask::canRun(uint32_t now) {
    return now >= runTime;
}

Yep, that's really all there is to it. Each task can be queried to see if it can be run via the canRun(), method and if it can, it will be executed via a call to its run() method. We pass in the current time in milliseconds to avoid each task having to separately determine it. The canRun() and run() methods could be merged, but having them separate allows us to provide more flexible scheduling if we ever need to, e.g. by penalising tasks that are runnable too often.

OK, next step is to implement the actual task scheduler:

class TaskScheduler {
public:
    TaskScheduler(Task **task, uint8_t numTasks);
    void run();
private:
    Task **tasks;
    int numTasks;
};

TaskScheduler::TaskScheduler(Task **_tasks, uint8_t _numTasks) :
  tasks(_tasks),
  numTasks(_numTasks) {
}

void TaskScheduler::run() {
    while (1) {
        uint32_t now = millis();
        Task **tpp = tasks;
        for (int t = 0; t < numTasks; t++) {
            Task *tp = *tpp;
            if (tp->canRun(now)) {
                tp->run(now);
                break;
            }
            tpp++;
        }
    }
}

Again, that really is all there is to it. We create the task scheduler with a fixed list of tasks, then call its run method. The run method iterates endlessly over the task list, calling the canRun() method on each in turn to see if it needs to be run. If it does, its run() method is called to execute the task. One very important note: after running a task we break out of the iteration over the task list and start back at the top of the list. That gives us the fixed task priority feature - if multiple tasks are runnable the earlier tasks on the list will always be dispatched first and the later tasks on the list will be lower priority,

The last part is to show an example of how to actually use the scheduler. Each task is derived from either the base Task class or from TimedTask, depending on how it needs to be run. Then in the main body of the program we create a list of tasks, pass it to a TaskScheduler instance and then run the scheduler:

Display display();
Sequencer sequencer();
RotaryEncoder encoder();
Task *tasks[] = { &encoder, &sequencer, &display };
TaskScheduler sched(tasks, 3);
sched.run();

That's all there is to it. With this approach it's possible to provide a lightweight set of communicating tasks that are scheduled in priority order. The code is both high performance and lightweight, two vital attributes considering the constrained environment it must operate in. Providing the various run() methods are reasonably short, it will run tasks within less than one millisecond of when they are scheduled, which is perfectly adequate for our needs - I'll give an example of using this library to perform a timing-critical process (switch de-bouncing) in a later post.

It's simple enough to implement your own variant, but if you want the code it's available here.

Acknowledgement

I'd like to acknowledge my MSc tutor Dr. Colin Machin who sat down with me one afternoon back in 1984 and outlined this approach to me, which I then used for the Z80 robot controller that was the subject of my MSc thesis. He'd used used the same technique for a LIDAR data acquisition system he'd written to collect data on the wingtip vortices caused by commercial aircraft as they land. Good ideas always stand the test of time - thanks Colin :-)

Categories : Arduino, Tech

It's a secret

Went for a walk with the family yesterday evening after tea and took the following picture with the less than excellent camera on my phone but I rather like the soft effect. The location is less than 6 miles in a straight line from the house, and less than 200 metres from the nearest road, but I'm not telling you exactly where it is - it's a secret :-)

Secret waterfall

Characterising the 7805

As the LED strips are going to be mobile, I needed to find a way of powering them. After some investigation, the 7.2 volt NiMH battery packs used by radio controlled cars seemed like a good choice - they are high capacity, relatively cheap and readily available. Although the Arduino Pro Mini has an on-board regulator, the HL1606 strips don't and according to the datasheet they need between 3V and 5.5V, with 5V being the preferred voltage. That meant dropping the 7.2V from the batteries down to 5V. The obvious first choice was the venerable (and cheap) 7805, specifically the 2A version - STElectronics L78S05CV. I'd need 2 regs for each LED set to give the required 4A. The regs are £0.62 from BitsBox, with heatsinks and the recommended caps the total cost was about £3.00 per LED set. There was only one problem though, the 7805 datasheet says the minimum input voltage is 8V, the batteries only output 7.2, so it seemed they wouldn't work.

The next option was a LDO regulator such as the LM2940 but although they have a dropout voltage of only 0.5V, they are only rated at 1A and are £1.44 each, which would mean that the parts for each LED set would be over £9.00 - ouch. Other options included a homebrew switching regulator such as this one - not ideal because of the increased build and testing time, or a off-the-shelf switching regulator such as the PTH08T231W - again, not ideal because of cost - £11.00 each, plus £12.00 delivery from Mouser.

Bearing in mind this project is cost-constrained, none of the alternatives to the 7805 really looked viable. I decided to buy a couple of 7805s and see if they would actually work, despite what the datasheet said. What I needed to do was to use a 7805 to power my 2-strip prototype with a range of different input voltages, and that required something I didn't have, a variable output bench power supply - and at about £100, it wasn't something I was going to rush out and buy. Bob from Hacman pointed me in the direction of FabLab, and an email confirmed that they had a suitable bench power supply. As an aside, FabLab is a wonderful (and free!) resource that has all sorts of toys such as a laser cutter, a milling machine, a 3D printer and a CNC router - highly recommended!

The first test measured max/min output voltage against input voltage, using my standard test pattern set. The graph is below:

7805

Things of note:

  • Vout never got above 4.85V. That's obviously less than 5V, but still within the spec for the 7805, which is 4.8V to 5.2V. I compared a couple of regs, they were all much the same.
  • As Vin drops, Vout and quality of the regulation also drops. At Vin of 5.5V Vout swings by about 0.6V.
  • Above Vin of 7V the load-induced Vout swing is around 0.1V.
  • Below Vout of around 5.5V the operation of the strips became unstable. The datasheet says they will work down to 3V, but like much of the information in there, it's incorrect.
  • There was little perceived change in LED brightness across the usable voltage range.

The second test measured input and output current against input voltage, with all 20 LEDs turned fully on (R+G+B on 100%), representing the maximum load the LED strips can generate. The graph is below:

7805

Things of note:

  • Maximum load was at 7.2V and above, with Ain of 1.7A and Aout of 1.67A.
  • Below Vin of 7.2V Ain & Aout dropped fairly linearly with Vin.
  • Below Vin of 6V the LED strips became unstable.
  • The heatsink on the 7805 got very hot with voltages above 7V, increasing as the Vin increased. That's expected as the 7805 is a linear regulator and it dissipates the difference between Vin and Vout as heat, but a heatsink is absolutely necessary.

Much to my surprise, it appears that the 7805 will be OK for my application. The information I've found for NiMH battery performance suggests they have a flat output voltage of around 1.1V per cell until around 80% discharge, at which point the output voltage drops rapidly, That means I'll be getting about 6.6V from each pack, which whilst not ideal is certainly workable. The best solution would be a switched mode regulator, but as cost is a major constraint for this project, that isn't really an option.

Categories : Arduino, Tech

Manchester Day

Juba do Leao

On Sunday we played in the Manchester Day parade. Seemingly about 75,000 people watched, so it's probably our biggest audience yet. There are loads of pictures of us on flickr, and the costumes looked fab so all the work was worthwhile. My only gripe is it wasn't really a traditional Manchester parade as it didn't rain :-)

Categories : Drumming, Juba do Leao

Configuring NetBeans to use as an Arduino IDE

As I've said in an earlier post, I very quickly found the Arduino IDE to be way too primitive for serious use, so I decided to switch to using NetBeans as an alternative. First step was to create a Makefile, once I had that done I needed to configure NetBeans to use the avg-gcc toolchain, which was pretty straightforward. Ideally I'd just be able export the relevant settings from NetBeans and provide a file you could download and install, but unfortunately NetBeans doesn't provide a way to do this for individual compilers, just all of them at once :-(

First step is to set up a new compiler configuration, Tools -> Options -> C/C++ -> Add. Set the base directory to wherever you have avr-gcc installed, in my case this is under /opt/arduino/hardware/tools/gcc-avr/bin. Set the compiler family to GNU and save.

Then in the Build Tools tab, set the paths for the C compiler, the C++ compiler and for the assembler, i.e. the full paths to avr-gcc, avr-c++ and avr-as. Also set the path for gmake. Clicking on the Versions button should display the versions of the tools.

Switch to the Code Assistance tab, and for both the C and C++ compilers, click the Reset Settings button. This should fill in the default values, the include directories should be set to locations under your avr-gcc install tree. You also need to manually add the directory containing the source of the Arduino libraries to each compiler configuration, in my case this is /opt/arduino/hardware/cores/arduino, and them move it to the top of the include lists.

Finally, switch to the Other tab, and add pde to the list of C++ file extensions, and save. That's the tools set up.

The next steps apply when you are creating a new project and defining its properties. Obviously you need to choose the avr-gcc toolchain to compile the project, and provide a Makefile to build it with - don't use the standard NetBeans one, it won't work.

The Code Assistance sections for both the C and C++ compilers need setting up to refer to any additional library directories you are using, and if you want code completion to work properly you also need to define the requisite preprocessor macros. Do this by setting up a new Configuration for each board type you use, and within that define the macros. I have duemilanove and mega boards, so my settings are:

duemilanove

__AVR_ATmega328P__
F_CPU=16000000L

mega

__AVR_ATmega1280__
F_CPU=16000000L

If you have different boards you'll have to figure out the correct __AVR_ATXXX__ and F_CPU #defines. First find boards.txt in your Arduino install tree and find the section for your board. The f_cpu value is what you need for F_CPU, the other setting is a little more fiddly to find. Get the mcu value, then look that up in the second table on this page to find the corresponding macro that needs to be defined.

As the generated code needs to be run on the Arduino, the normal Run settings don't actually make much sense, but we can re-purpose them for our needs. In the Make section, set the Build Result value to the path of your gmake executable, then in the Run section, set the command-line arguments to upload_monitor. By doing this, when you run the project with F6, NetBeans will run the upload_monitor Makefile target which will build the project, upload it to the board and run the serial monitor.

With all that in place you should be able to use NetBeans as your IDE for developing for the Arduino, including all the nice features such as cross-referencing and code completion. The setup of projects is a little fiddly, so my suggestion is to set up an empty template project that you can copy and then change all the project name references in - I use TEMPLATE as the project name so I can use a little script to clone the project then rename and batch-edit the files with the correct project name.

Categories : Arduino, Tech