Low-cost cooperative multitasking, Part 2 – Building an MP3 Player
The subsystems in our system interact in various ways. Some subsystems depend upon others for data, timing and output. How the different tasks communicate with each other determines their priority and frequency, as well as intertask communication strategies. In this section, we will first see how to determine the execution frequency and priority, followed by a review of methods for intertask communication.
Execution Periodicity and Order
Now that we have identified different state machines and functions within the tasks, let us identify the execution order of the tasks. We’ll define these terms, first.
Execution periodicity is the frequency with which a task is called from the scheduler. Some tasks may need more frequent calls than others. This also means that all the tasks need not be scheduled in every one of the scheduler loop’s iterations.
This saves a lot of CPU throughput. In the MP3 player example above, the LCD driver is a state machine that writes a single character from the buffer to the display and returns control back to the scheduler. This task will have to be called with the highest frequency to get a complete display written.
The USB-driver task will be called frequently, to send out the USB host signals and keep the connected device alive, but not at the top frequency. The touch-sense driver task should be called frequently to capture the capacitance data. However, the touch-sense detection algorithm task can be called leisurely, since it requires many samples of capacitance data before it can do anything useful.
Execution order is the order in which functions are grouped within a task, and also the order in which tasks are called from the scheduler. Task priority determines task order. The execution order follows the natural order of data flow.
Figure 2 below is a partial top-level data-flow diagram of our MP3 player system showing the data flow when a “Next track” touch pad is touched. Note how the “playback manager” task depends on the “system monitor” task which, in turn, depends upon the “touch sense-detection algorithm” task for its inputs. It makes sense to schedule the “playback manager” task after the system-monitor and touch sense-detection tasks at the same execution frequency, provided throughput criteria is met.
Figure 2: A Partial Data-Flow Diagram of our USB MP3 Player System
The tasks can be prioritized by staggering their execution across two passes of the scheduler loop. The higher-priority task should be scheduled in the first pass, and the lower-priority task for later. This aspect will become clearer in the “Task Scheduling” section of this article.
Intertask Communication
Let us recap here that our logical unit of functionality is a state machine, and thus we are interested in the communication between two independently-scheduled state machines. A task is only a set of function calls, related or unrelated.
Interstate machine communication in cooperative multi tasking is handled exclusively by global variables. This is due to the fact that all task switching is deterministic and there is no risk of simultaneous access of a shared resource. Since we have this robustness by design, global variables are used for intertask communication.
There are, of course, many types of data communication that need to be handled in a multitasking system. Since many dependent tasks run asynchronously, we need “flags’” to indicate the availability of inputs and population of data structures.
We need “error flags/codes” to communicate anomalies in one subsystem to another. We need “buffers” that hold data collected by one state machine but can be used by another task. “State variables” hold information about tasks’ states, which is handy for other tasks that delegate jobs to the former.
In our MP3 player example the touch-sense system reads the output of the capacitive pads being read by our “Capacitive Touch-Sense Driver.” This is done by using a buffer for each touch pad to store the Analog-to-Digital Converter (ADC) values. The “touch-sense detection” state machine shares a set of flags for each capacitive touch pad and these are constantly read by “system monitor”.
Let us say the user presses the “next track” touch pad. The touch-sense detection state machine analyses the ADC buffers from the lower layers of the subsystem and sets the touch-pad flags to be used for the upper layer, e.g. system monitor.
The system monitor, in turn, sets the “open next file” flag for the playback manager, as well as the “set new filename” flag to be used by the “display manager.” We see that the display manager gets the currently played file name in the “updated file name” buffer.
The display manager depends upon the playback manager for this data and needs to follow playback manager in execution, thus is of lower priority. The file content is again pumped out to the MP3 ASIC through a buffer. This is the gross data flow on a single button press. We also see some hardware action executed by the system monitor to enable perhaps an analog switch or multiplexer too.
In a well-designed cooperative multitasking system, all transitions of control are predetermined, and we therefore do not need to worry about synchronization issues. Thus, we do not have to employ costly synchronization constructs like pipes, messages and semaphores. These complex features are realized using inexpensive and simple flags, buffers, and queues.
Task Scheduling
Now that all our tasks are identified and all the communication variables are in place, it is time to schedule the tasks and execute them. The purpose of the scheduler is to execute a task at a known moment in time with a known periodicity. At the basic level, a scheduler is an infinite loop calling each task one after another. However, when we need to balance the CPU load and prioritize our tasks, things get interesting.
In this part of this three article series, we studied how a system is broken down into its component subsystems, as well as how we identify the periodicity and order of execution of the components. We also saw how the components speak to each other.
In the next and final Part 3 in this series, we will tie all of this together with a scheduler. We will also look at some methods to analyze our system and derive the CPU throughput used as well as deal with task/state machine scheduling, diagnostics, and provide some tips and tricks.
To read Part 1, go to “Building a simple FM player.”
As Senior Applications Engineer for Microchip Technology’s Advanced Microcontroller Architecture Division, Ganesh Krishna is group leader for Microchip’s graphics product portfolio, including display drivers, graphics and display libraries, manages peripheral libraries for PIC18 and PIC24F microcontrollers, and develops reference designs and performs benchmarking. Ganesh earned his Bachelor of Engineering Degree in Electronics and communication from Sri Jayachamarajendra College of Engineering, belonging to Vishveshwaraiah Technological University (VTU).


Loading comments... Write a comment